Vue脚手架
# Vue脚手架
# ref属性
- 被用来给元素或子组件注册引用信息(id的替代者)
- 应用在html标签上获取的是真实DOM元素,应用在组件标签上是组件实例对象(vc)
- 使用方式:
- 打标识:
<h1 ref="xxx">.....</h1>或<School ref="xxx"></School> - 获取:
this.$refs.xxx
- 打标识:
<template>
<div>
<h1 v-text="msg" ref="title"></h1>
<button ref="btn" @click="showDOM">点我输出上方的DOM元素</button>
<School ref="sch" />
</div>
</template>
<script>
//引入School组件
import School from "./components/School";
export default {
name: "App",
components: { School },
data() {
return {
msg: "欢迎学习Vue!",
};
},
methods: {
showDOM() {
console.log(this.$refs.title); //真实DOM元素
console.log(this.$refs.btn); //真实DOM元素
console.log(this.$refs.sch); //School组件的实例对象(vc)
},
},
};
</script>
# props配置项
- 功能:让组件接收外部传过来的数据
- 传递数据:
<Demo name="xxx"/> - 接收数据:
- 第一种方式(只接收):
props:['name'] - 第二种方式(限制类型):
props:{name:String} - 第三种方式(限制类型、限制必要性、指定默认值):
- 第一种方式(只接收):
props:{
name:{
type:String, //类型
required:true, //必要性
default:'老王' //默认值
}
}
备注:props是只读的,Vue底层会监测你对props的修改,如果进行了修改,就会发出警告,若业务需求确实需要修改,那么请复制props的内容到data中一份,然后去修改data中的数据。
<template>
<div>
<h1>{{ msg }}</h1>
<h2>学生姓名:{{ name }}</h2>
<h2>学生性别:{{ sex }}</h2>
<h2>学生年龄:{{ myAge + 1 }}</h2>
<button @click="updateAge">尝试修改收到的年龄</button>
</div>
</template>
<script>
export default {
name: "Student",
data() {
console.log(this);
return {
msg: "我是一个尚硅谷的学生",
myAge: this.age,
};
},
methods: {
updateAge() {
this.myAge++;
},
},
//简单声明接收
// props:['name','age','sex']
//接收的同时对数据进行类型限制
/* props:{
name:String,
age:Number,
sex:String
} */
//接收的同时对数据:进行类型限制+默认值的指定+必要性的限制
props: {
name: {
type: String, //name的类型是字符串
required: true, //name是必要的
},
age: {
type: Number,
default: 99, //默认值
},
sex: {
type: String,
required: true,
},
},
};
</script>
# mixin(混入)
- 功能:可以把多个组件共用的配置提取成一个混入对象
- 使用方式: 第一步定义混合:
{
data(){....},
methods:{....}
....
}
第二步使用混入:
- 全局混入:
Vue.mixin(xxx) - 局部混入:
mixins:['xxx']
mixin.js
export const hunhe = {
methods: {
showName() {
alert(this.name);
},
},
mounted() {
console.log("你好啊!");
},
};
export const hunhe2 = {
data() {
return {
x: 100,
y: 200,
};
},
};
main.js
//引入Vue
import Vue from 'vue'
//引入App
import App from './App.vue'
import {hunhe,hunhe2} from './mixin'
//关闭Vue的生产提示
Vue.config.productionTip = false
Vue.mixin(hunhe)
Vue.mixin(hunhe2)
//创建vm
new Vue({
el:'#app',
render: h => h(App)
})
组件中
<template>
<div>
<h2 @click="showName">学生姓名:{{ name }}</h2>
<h2>学生性别:{{ sex }}</h2>
</div>
</template>
<script>
// import {hunhe,hunhe2} from '../mixin'
export default {
name: "Student",
data() {
return {
name: "张三",
sex: "男",
};
},
// mixins:[hunhe,hunhe2]
};
</script>
# Vue插件
- 功能:用于增强Vue
- 本质:包含install方法的一个对象,install的第一个参数是Vue,第二个以后的参数是插件使用者传递的数据。
- 定义插件:
对象.install = function (Vue, options) {
// 1. 添加全局过滤器
Vue.filter(....)
// 2. 添加全局指令
Vue.directive(....)
// 3. 配置全局混入(合)
Vue.mixin(....)
// 4. 添加实例方法
Vue.prototype.$myMethod = function () {...}
Vue.prototype.$myProperty = xxxx
}
- 使用插件:
Vue.use()
plugins.js
export default {
install(Vue, x, y, z) {
console.log(x, y, z);
//全局过滤器
Vue.filter("mySlice", function (value) {
return value.slice(0, 4);
});
//定义全局指令
Vue.directive("fbind", {
//指令与元素成功绑定时(一上来)
bind(element, binding) {
element.value = binding.value;
},
//指令所在元素被插入页面时
inserted(element, binding) {
element.focus();
},
//指令所在的模板被重新解析时
update(element, binding) {
element.value = binding.value;
},
});
//定义混入
Vue.mixin({
data() {
return {
x: 100,
y: 200,
};
},
});
//给Vue原型上添加一个方法(vm和vc就都能用了)
Vue.prototype.hello = () => {
alert("你好啊");
};
},
};
main.js
//引入Vue
import Vue from "vue";
//引入App
import App from "./App.vue";
//引入插件
import plugins from "./plugins";
//关闭Vue的生产提示
Vue.config.productionTip = false;
//应用(使用)插件
Vue.use(plugins, 1, 2, 3);
//创建vm
new Vue({
el: "#app",
render: (h) => h(App),
});
组件中使用
<template>
<div>
<h2>学校名称:{{ name | mySlice }}</h2>
<h2>学校地址:{{ address }}</h2>
<button @click="test">点我测试一个hello方法</button>
</div>
</template>
<script>
export default {
name: "School",
data() {
return {
name: "尚硅谷atguigu",
address: "北京",
};
},
methods: {
test() {
this.hello();
},
},
};
</script>
# scoped样式
- 作用:让样式在局部生效,防止冲突。
- 写法:
<style scoped>
# TodoList案例
User Footer.vue
<template>
<div class="todo-footer" v-show="completeAll">
<label>
<input type="checkbox" :checked="isAll" @change="checkAll"/>
</label>
<span>
<span>已完成{{completeTodo}}</span> / 全部 {{completeAll}}
</span>
<button class="btn btn-danger" @click="clearAll">清除已完成任务</button>
</div>
</template>
<script>
export default {
name:'UserFooter',
props:['todos','checkAllTodo','clearAllTodo'],
computed: {
completeTodo(){
let numberSum = 0
this.todos.forEach((todo) => {
if(todo.complete) numberSum++
})
return numberSum
},
completeAll(){
return this.todos.length
},
isAll(){
return this.completeTodo === this.completeAll && this.completeAll > 0
}
},
methods: {
checkAll(e){
this.checkAllTodo(e.target.checked)
},
clearAll(){
this.clearAllTodo()
}
},
}
</script>
<style scoped>
.todo-footer {
height: 40px;
line-height: 40px;
padding-left: 6px;
margin-top: 5px;
}
.todo-footer label {
display: inline-block;
margin-right: 20px;
cursor: pointer;
}
.todo-footer label input {
position: relative;
top: -1px;
vertical-align: middle;
margin-right: 5px;
}
.todo-footer button {
float: right;
margin-top: 5px;
}
</style>
UserHeader.vue
<template>
<div class="todo-header">
<input type="text" placeholder="请输入你的任务名称,按回车键确认" v-model="title" @keyup.enter="add"/>
</div>
</template>
<script>
import {nanoid} from 'nanoid'
export default {
props:['addTodo'],
name:'UserHeader',
data() {
return {
title: '',
};
},
methods:{
add(){
if(!this.title.trim()) return alert('输入不能为空')
var todoObj = {id:nanoid(),title:this.title,complete:true}
this.addTodo(todoObj)
this.title = ''
}
},
}
</script>
<style scoped>
.todo-header input {
width: 560px;
height: 28px;
font-size: 14px;
border: 1px solid #ccc;
border-radius: 4px;
padding: 4px 7px;
}
.todo-header input:focus {
outline: none;
border-color: rgba(82, 168, 236, 0.8);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
}
</style>
UserItem.vue
<template>
<li>
<label>
<input
type="checkbox"
:checked="todo.complete"
@change="handleCheck(todo.id)"
/>
<span>{{ todo.title }}</span>
</label>
<button class="btn btn-danger" @click="handleDelete(todo.id)">删除</button>
</li>
</template>
<script>
export default {
name: "UserItem",
props: ["todo", "checkTodo",'deleteTodo'],
methods: {
handleCheck(id) {
this.checkTodo(id);
},
handleDelete(id){
if(confirm('确定删除吗?')){
console.log(id)
this.deleteTodo(id)
}
}
},
};
</script>
<style scoped>
li {
list-style: none;
height: 36px;
line-height: 36px;
padding: 0 5px;
border-bottom: 1px solid #ddd;
}
li label {
float: left;
cursor: pointer;
}
li label li input {
vertical-align: middle;
margin-right: 6px;
position: relative;
top: -1px;
}
li button {
float: right;
display: none;
margin-top: 3px;
}
li:before {
content: initial;
}
li:last-child {
border-bottom: none;
}
li:hover {
background-color: #ddd;
}
li:hover button {
display: block;
}
</style>
UserList.vue
<template>
<div class="todo-main">
<user-item v-for="todoObj in todos" :key="todoObj.id" :todo="todoObj" :checkTodo="checkTodo" :deleteTodo="deleteTodo"/>
</div>
</template>
<script>
import UserItem from './UserItem.vue'
export default {
name:'UserList',
components: {
UserItem
},
props:['todos','checkTodo','deleteTodo']
}
</script>
<style scoped>
.todo-main {
margin-left: 0px;
border: 1px solid #ddd;
border-radius: 2px;
padding: 0px;
}
.todo-empty {
height: 40px;
line-height: 40px;
border: 1px solid #ddd;
border-radius: 2px;
padding-left: 5px;
margin-top: 10px;
}
</style>
App.vue
<template>
<div class="todo-container">
<div class="todo-wrap">
<user-header :addTodo="addTodo"/>
<user-List :todos="todos" :checkTodo="checkTodo" :deleteTodo="deleteTodo"/>
<user-footer :todos="todos" :clearAllTodo="clearAllTodo" :checkAllTodo="checkAllTodo"/>
</div>
</div>
</template>
<script>
import UserHeader from './components/UserHeader.vue'
import UserFooter from './components/UserFooter.vue'
import UserList from './components/UserList.vue'
export default {
name: 'APP',
components: {
UserHeader,
UserFooter,
UserList,
},
data() {
return {
todos: [
{ id:'001',title: '吃饭', complete: true },
{ id:'002',title: '睡觉', complete: false },
{ id:'003',title: '敲代码', complete: true }
],
}
},
methods:{
addTodo(todoObj){
this.todos.unshift(todoObj)
},
checkTodo(id){
this.todos.forEach((todo)=>{
if(todo.id === id) todo.complete = !todo.complete
});
},
deleteTodo(id){
this.todos = this.todos.filter(todo => todo.id !== id)
},
checkAllTodo(complete){
this.todos.forEach((todo) => {
todo.complete = complete
})
},
clearAllTodo(){
this.todos = this.todos.filter((todo) => {
return !todo.complete
})
}
}
}
</script>
<style>
.todo-container {
width: 600px;
margin: 0 auto;
}
.todo-container .todo-wrap {
padding: 10px;
border: 1px solid #ddd;
border-radius: 5px;
}
</style>
# 总结TodoList案例
- 组件化编码流程: (1).拆分静态组件:组件要按照功能点拆分,命名不要与html元素冲突。
(2).实现动态组件:考虑好数据的存放位置,数据是一个组件在用,还是一些组件在用:
1).一个组件在用:放在组件自身即可。
2). 一些组件在用:放在他们共同的父组件上(状态提升)。
(3).实现交互:从绑定事件开始。
- props适用于: (1).父组件 ==> 子组件 通信
(2).子组件 ==> 父组件 通信(要求父先给子一个函数)
使用v-model时要切记:v-model绑定的值不能是props传过来的值,因为props是不可以修改的!
props传过来的若是对象类型的值,修改对象中的属性时Vue不会报错,但不推荐这样做。
# LocalStorage
LocalStorage的优点:
- 在大小方面,LocalStorage的大小一般为5MB,可以储存更多的信息
- LocalStorage是持久储存,并不会随着页面的关闭而消失,除非主动清理,不然会永久存在
- 仅储存在本地,不像Cookie那样每次HTTP请求都会被携带
LocalStorage的缺点:
- 存在浏览器兼容问题,IE8以下版本的浏览器不支持
- 如果浏览器设置为隐私模式,那我们将无法读取到LocalStorage
- LocalStorage受到同源策略的限制,即端口、协议、主机地址有任何一个不相同,都不会访问
LocalStorage的使用场景:
- 有些网站有换肤的功能,这时候就可以将换肤的信息存储在本地的LocalStorage中,当需要换肤的时候,直接操作LocalStorage即可
- 在网站中的用户浏览信息也会存储在LocalStorage中,还有网站的一些不常变动的个人信息等也可以存储在本地的LocalStorage中
<body>
<h2>localStorage</h2>
<button onclick="saveData()">保存数据</button>
<button onclick="readData()">读取数据</button>
<button onclick="deleteData()">删除数据</button>
<button onclick="deleteAllData()">清空数据</button>
<script>
let p = {name:"郝佳瑶",age:'18'}
function saveData(){
localStorage.setItem('msg','hello')
localStorage.setItem('msg2','519')
localStorage.setItem('person',JSON.stringify(p))
}
function readData(){
console.log(localStorage.getItem('msg'))
console.log(localStorage.getItem('msg2'))
console.log(JSON.parse(localStorage.getItem('person')))
}
function deleteData(){
localStorage.removeItem('person')
}
function deleteAllData(){
localStorage.clear()
}
</script>
</body>
# SessionStorage
SessionStorage与LocalStorage对比:
- SessionStorage和LocalStorage都在本地进行数据存储;
- SessionStorage也有同源策略的限制,但是SessionStorage有一条更加严格的限制,SessionStorage只有在同一浏览器的同一窗口下才能够共享;
- LocalStorage和SessionStorage都不能被爬虫爬取;
SessionStorage的使用场景
- 由于SessionStorage具有时效性,所以可以用来存储一些网站的游客登录的信息,还有临时的浏览记录的信息。当关闭网站之后,这些信息也就随之消除了。
<body>
<h2>sessionStorage</h2>
<button onclick="saveData()">保存数据</button>
<button onclick="readData()">读取数据</button>
<button onclick="deleteData()">删除数据</button>
<button onclick="deleteAllData()">清空数据</button>
<script>
let p = {name:"郝佳瑶",age:'18'}
function saveData(){
sessionStorage.setItem('msg','hello')
sessionStorage.setItem('msg2','519')
sessionStorage.setItem('person',JSON.stringify(p))
}
function readData(){
console.log(sessionStorage.getItem('msg'))
console.log(sessionStorage.getItem('msg2'))
console.log(JSON.parse(sessionStorage.getItem('person')))
}
function deleteData(){
sessionStorage.removeItem('person')
}
function deleteAllData(){
sessionStorage.clear()
}
</script>
</body>
# 组件的自定义事件
- 一种组件间通信的方式,适用于:子组件 ===> 父组件
- 使用场景:A是父组件,B是子组件,B想给A传数据,那么就要在A中给B绑定自定义事件(事件的回调在A中)。
- 绑定自定义事件:
第一种方式,在父组件中:
<Demo @atguigu="test"/>或<Demo v-on:atguigu="test"/>第二种方式,在父组件中:
<Demo ref="demo"/>
......
mounted(){
this.$refs.xxx.$on('atguigu',this.test)
}
若想让自定义事件只能触发一次,可以使用once修饰符,或$once方法。
- 触发自定义事件:this.$emit('atguigu',数据)
- 解绑自定义事件this.$off('atguigu')
- 组件上也可以绑定原生DOM事件,需要使用native修饰符。
- 注意:通过this.$refs.xxx.$on('atguigu',回调)绑定自定义事件时,回调要么配置在methods中,要么用箭头函数,否则this指向会出问题!
# 全局事件总线
- 一种组件间通信的方式,适用于任意组件间通信。
- 安装全局事件总线: main.js
import Vue from 'vue'
import App from './App.vue'
Vue.config.productionTip = false
new Vue({
render: h => h(App),
beforeCreate(){
Vue.prototype.$bus = this //安装全局总线
}
}).$mount('#app')
- 使用事件总线:
接收数据:A组件想接收数据,则在A组件中给$bus绑定自定义事件,事件的回调留在A组件自身。
School.vue
<template>
<div class="school">
<h2>学校名称:{{name}}</h2>
<h2>学校地址:{{address}}</h2>
</div>
</template>
<script>
export default {
name:'mySchool',
data() {
return {
name: '绿野',
address: '陕西'
};
},
mounted() {
this.$bus.$on('hello',(data) => {
console.log('school组件,收到数据',data)
})
},
beforeDestroy() {
this.$bus.$off('hello')
},
}
</script>
<style>
.school {
background-color: skyblue;
padding: 5px;
}
</style>
提供数据:this.$bus.$emit('xxxx',数据)
Student.vue
<template>
<div class="student">
<h2>学生姓名:{{name}}</h2>
<h2>学生性别:{{sex}}</h2>
<button @click="sendStudentName">点击</button>
</div>
</template>
<script>
export default {
name:'myStudent',
data() {
return {
name: '郝佳瑶',
sex:'男'
};
},
methods: {
sendStudentName(){
this.$bus.$emit('hello',this.name)
}
},
}
</script>
<style>
.student{
background-color: pink;
padding: 5px;
margin-top: 30px;
}
</style>
- 最好在beforeDestroy钩子中,用$off去解绑当前组件所用到的事件。最好在beforeDestroy钩子中,用$off去解绑当前组件所用到的事件。
# 消息订阅与发布(PubSubJS 库)
# 订阅消息
PubSub.subscribe('msg', function(msg, data){})
# 发布消息
PubSub.publish('msg', data)
# 示例
订阅消息(绑定事件监听)
import PubSub from 'pubsub-js'
export default {
mounted () {
// 订阅消息(deleteTodo)
PubSub.subscribe('deleteTodo', (msg, index) => {
this.deleteTodo(index)
})
}
}
发布消息(触发事件)
// this.deleteTodo(this.index)
// 发布消息(deleteTodo)
PubSub.publish('deleteTodo', this.index)
# 注意
优点: 此方式可实现任意关系组件间通信(数据)
# 事件的2 个重要操作
- 绑定事件监听(订阅消息)
目标: 标签元素
<button>事件名(类型):click/focus回调函数:function(event){} - 触发事件(发布消息) DOM 事件: 用户在浏览器上对应的界面上做对应的操作 自定义: 编码手动触发
# 总结
- 一种组件间通信的方式,适用于任意组件间通信。
- 使用步骤:
- 安装pubsub:
npm i pubsub-js - 引入:
import pubsub from 'pubsub-js' - 接收数据:A组件想接收数据,则在A组件中订阅消息,订阅的回调留在A组件自身。
- 提供数据:
pubsub.publish('xxx',数据) - 最好在beforeDestroy钩子中,用
PubSub.unsubscribe(pid)去取消订阅。
# 动画与过渡
- 作用:在插入、更新或移除 DOM元素时,在合适的时候给元素添加样式类名。
- 写法:
准备好样式:
元素进入的样式: v-enter:进入的起点 v-enter-active:进入过程中 v-enter-to:进入的终点 元素离开的样式: v-leave:离开的起点 v-leave-active:离开过程中 v-leave-to:离开的终点 使用包裹要过度的元素,并配置name属性:
<transition name="hello">
<h1 v-show="isShow">你好啊!</h1>
</transition>
- 备注:若有多个元素需要过渡,则需要使用:
<transition-group>,且每个元素都要指定key值。