组织介绍

Vue2 的使用

框架介绍:

vue是一套用于构建用户界面的渐进式JavaScript框架。

框架特点:

  • 采用组件化模式,提高代码复用率、且让代码更好维护。
  • 声明式编码,让开发人员无需直接操作DOM、提高开发效率。
  • 使用虚拟DOM+优秀的Diff算法,尽量复用DOM节点。

参考资料:

  • vue官方网站:

  • vue插件推荐:

初识VUE:

vue需要引入一个root标签,并配置好数据。

<div id="app" class="container">
    <div class="alert alert-danger">
        {{title}}
    </div>
</div>
new Vue({
    el:"#app",
    data:{
    title:'这里是一个VUE应用'
    }
})

数据绑定:

插值语法:

插值语法往往用于指定标签体内容,使用{{}}来定义,插值语法只能是js表达式。

// 模板
<div id="app">
    <h1>{{title}}</h1>
    <ul>
        <li>{{name}}</li>
        <li>{{age}}</li>
    </ul>
</div>
// 实例
new Vue({
    el:"#app",
    data:{
        title:'这里是一个VUE应用',
        name:'cowen',
        age:33
    }
})

指令语法v-bind:

指令语法用于解析标签(标签属性、标签体内容、绑定事件等)。

指令语法使用v-bind:定义,简写成:即可。

// 模板
<div id="app" class="container">
    <a v-bind:href="url">百度</a>
    <div :data-title='title' class="alert alert-danger">{{title}}</div> 
</div>
// 实例
new Vue({
    el:"#app",
    data:{
        title:'这里是一个标题',
        url:'http://www.baidu.com'
    }
})

双向绑定v-model:

v-bind数据绑定其实是一种单向绑定,数据发生变化,页面视图进行更新,但是对于一些输入类的表单元素,可以借助v-model实现双向绑定。

v-model对应的修饰符有lazy / trim

  • lazy:

    lazy修饰符表示当输入类控件失去焦点时,数据才会更新。

  • trim:

    trim修饰符表示输入数据截断首尾的空字符串。

  • number:

    将绑定数据转换成数值型数据。

// 模板
<div id="app">
    单向数据:<input type="text" :value="value" /><br />
    <hr />
    双向数据:<input type="text" v-model:value="value" />
</div>
// 实例
new Vue({
    el: "#app",
    data: {
        value: '输入框内容'
    }
})

数据代理:

vue中的数据代理是通过vue的实例vm对象来实现的,vm对象代理data对象中的属性操作(读 / 写)。

数据代理的基本原理是通过Object.defineProperty()把data对象中的所有属性都加到vm实例上,为每一个加到vm实例上的属性都指定一个getter / setter。

let person = { name: 'cowen', age: 36 };
const obs = new Observer(person);

function Observer(obj) {
    const keys = Object.keys(obj);
    keys.forEach((key) => {
        Object.defineProperty(this, key, {
            get() {
                return obj[key];
            },
            set(val) {
                console.log('数据被更改,视图重新渲染');
                obj[key] = val;
            }
        })
    })
}
document.querySelector('.btn').addEventListener('click',() => {
    obs.name="郑昊"
}, false);

计算属性computed:

计算属性是通过已有属性(data中的值)计算得来,其原理是借助Object.defineproperty方法提供的setter / getter来实现的。

计算属性与方法相比,内部有缓存机制,效率更高,调试更方便。

new Vue({
    el: "#app",
    data: {
        title: '计算属性GETTER',
        firstName: '',
        lastName: ''
    },
    computed: {
        fullName: {
            get() {
                return this.firstName + '-' + this.lastName;
            }
        }
    }
})

计算属性也是可以定义setter,setter的实质是更改引起计算属性发生变化的data值,进而改变计算属性的值。

new Vue({
    el: "#app",
    data: {
        title: '计算属性SETTER',
        firstName: '',
        lastName: ''
    },
    computed: {
        fullName: {
            get() {
                return this.firstName + '-' + this.lastName;
            },
            set(val){
                const arr = val.split('-');
                this.firstName=arr[0];
                this.lastName=arr[1];
            }
        }
    }
})

监视属性watch:

监视属性使用watch配置对象,在该配置对象中可以实现对已经存在的属性进行监视。

// 模板
<div id="app">
    <h1>{{title}}</h1>
    <button @click="increment">增加</button>
    <p>当前值:{{num}}</p>
</div>
// 实例
new Vue({
        el: "#app",
        data: {
            title: '监视属性',
            num:0
        },
        methods: {
            increment(){
                this.num++;
            }
        },
        watch:{
            num:{
                handler(newvalue,oldvalue){
                    console.log(newvalue,oldvalue);
                }
            }
        }
})

vue中的watch默认不监测对象内部值的改变,配置deep:true可以监测对象内部值的改变。

immediate配置项可以在初始化时就对数据进行一次监测

列表渲染v-for:

列表渲染使用v-for指令,并指定一个唯一的key值。

列表渲染可以遍历数组、对象、字符串,指定次数等,常用的时遍历数组。

// 模板
<div id="app">
    <ul>
        <li v-for="(p,index) in list" :key="p.id">{{p.name}}</li>
    </ul>
</div>
// 实例
new Vue({
    el:'#app',
    data:{
        list:[
            {id:1,name:'cowen'},
            {id:2,name:'min'}
        ]
    }
})

案例:列表过滤(数据监视watch):

案例:列表过滤(计算属性computed):

$set和Vue.set响应式数据添加:

在vue操作的data中的对象数据,如果需要新增一个新的响应式数据,可以使用this.$set和全局方法Vue.set进行操作。

// 模板
<div id="app">
    <h1>响应式数据添加</h1>
    <ul>
        <li>{{person.name}}</li>
        <li v-if="person.age">{{person.age}}</li>
    </ul>
    <button @click="addAge">增加年龄</button>
</div>
// 实例
    new Vue({
        el: '#app',
        data: {
            person:{
                name:'cowen'
            }   
        },
        methods: {
            addAge(){
                // this.$set(this.person,'age',33);
                Vue.set(this.person,'age',33);
            }   
        }
    })

vue会监视data中所有层次的数据;

vue通过setter实现监视,且要在new Vue时就传入要监测的数据,如果需要给后添加的属性做响应式,可以使用Vue.set / vm.$set方法。

Vue.set方法不能直接给**实例中的根数据(data里面的值)**添加新属性。

数组更新监测:

vue中的数组操作方法类似于原生数组的方法,但其实是已包装过的方法,这些方法包括push / pop / unshift / reverse 等。

new Vue({
    el: '#app',
    data: {
        list:[
           {name:'cowen',age:35},
           {name:'刘敏',age:31},
           {name:'tina',age:20}
        ]
    },
    methods:{
        addData(){
            this.list.push({name:'Min',age:18})
        },
        updateData(){
            this.list[0].name="郑昊";
        }
    }
})

表单数据的搜集:

表单元素中的数据收集主要包括以下几种:

  • 文本框text:

    使用v-model绑定。

  • 单选框radio:

    使用v-model绑定,每个radio需要给出一个value值。

  • 复选框checkbox:

    使用v-model绑定,每个checkbox需要给出一个value值,并且提前定义对应的数据类型为一个数组。

  • 下拉框select:

    使用v-model绑定,每个option配置value值。

// 输入文本框
<input type="text" v-model="account">
data:{
   account:''
}

// radio单选框
<input type="radio" name="sex" v-model="sex" value="male"> 
<input type="radio" name="sex"  v-model="sex" value="female">
data:{
   sex:''
}

// 复选框
吃饭<input type="checkbox" v-model="hobby" value="吃饭">   
上网<input type="checkbox" v-model="hobby" value="上网">   
打豆<input type="checkbox" v-model="hobby" value="打豆豆">
data:{
   hobby:[]
}

过滤器:

过滤器其实质就是一个函数,使用管道符|进行分割,会自动接收管道符前面的参数。

// 模板
<div id="app">
    <h1>过滤器</h1>
    <p>当前时间:{{time | timeFormater}}</p>
</div>
// 实例
    new Vue({
        el: '#app',
        data: {
            time: new Date().getTime()
        },
        filters: {
            timeFormater(value) {
                return dayjs(value).format('YYYY年MM月DD日 HH:mm:ss')
            }
        }
    })

使用Vue.filter可以定义全局过滤器,全局过滤器可以在所有的组件中使用。

// 全局过滤器
Vue.filter('stringFormater', function (value) {
	return value.split("").join(' | ')
})
new Vue({
    el: '#app',
    data: {
        str: '这里是一个字符串'
    }
})

内置指令:

vue提供了很多的内置指令,除了常用的v-on / v-bind等,还有以下的几种:

  • v-text:

    解析文本,类似于插值语法。

  • v-html:

    解析html代码。

  • v-once:

    只会在初次渲染时调用,监测的值再发生变化时,不会更新。

<p class="alert alert-info" v-text="desc"></p>
<div v-html="html" class="alert alert-danger"></div>
<div v-once class="alert alert-info">{{num}}</div>
<div class="alert alert-danger">{{num}}</div>

自定义指令:

自定义指令的功能就是用于解析标签(标签属性、标签体内容等)。

借助配置项directives配置自定义指令,自定义指令可以是一个函数,也可以是一个对象,简单的情况下,使用函数即可。

// 模板
<p>自定义指令值为<strong v-big="num"></strong></p>
// 实例
    new Vue({
        el: '#app',
        data: {
            num:1
        },
        methods: {
            increment(){
                this.num++;
            }
        },
        directives:{
            big(element,binding){
                element.innerHTML = binding.value*10;
            }
        }
    })

自定义指令定义成函数时,函数接收两个参数,一个是当前绑定的元素,一个是binding的配置项。

自定义指令也可以定义成全局指令使用Vue.directive

样式动画:

样式绑定:

样式绑定使用:class进行绑定,:class绑定的是一个动态数据。

  • 固定类名定义好,绑定类名使用:class
<div id="app" class="container">
    <h1 class="alert alert-danger">{{title}}</h1>
    <div class="box" :class='mode' @click='changeMode'>
        当前类名为box {{mode}}
    </div>
</div>
new Vue({
    el: "#app",
    data: {
        title: '样式绑定',
        mode:''
    },
    methods:{
        changeMode(){
            this.mode===""?this.mode='green':this.mode='';
        }
    }
})
  • 类名使用数组绑定:
<div id="app" class="container">
    <h1 class="alert alert-danger">{{title}}</h1>
    <div class="box" :class='mode' @click.once='changeMode'>
        当前类名为box {{mode}}
    </div>
</div>
new Vue({
    el: "#app",
    data: {
        title: '样式绑定',
        mode:[]
    },
    methods:{
        changeMode(){
            this.mode.push('green');
        }
    }
})

v-show和v-if控制隐藏显示:

v-showv-if都可以控制元素或者组件的隐藏和显示,不同的是v-show只是元素的隐藏和显示,而v-if则是元素是否渲染到容器中。

<h1 v-show="isshow">标题</h1>
<h1 v-if="isshow">{{title}}</h1>

帧动画:

vue中可以使用帧动画定义元素的进入离开时的动画形态,元素使用transition包裹,并且通过transition组件上的appear来定义元素初始化时是否加载动画。

<transition name='box' appear>
    <div v-show='isshow' class="box"></div>
</transition>

<style scoped>
@keyframes Enter{
    from{
        transform:translateX(-100%);
        opacity:0;
    }
    to{
        transform:translateX(0%);
        opacity:1;
    }
}
.box-enter-active{
    animation:Enter 1s ease-out;
}
.box-leave-active{
    animation:Enter 1s ease-out reverse;
}
</style>

transition组件中的name属性用于定义css样式中的进入、离开样式类名前缀。

04 过渡动画:

在vue中,动画的定义也可以使用过渡transition来定义,vue将元素的进入和离开分为两个阶段:起始位置、最终位置。

<transition name='box' appear>
    <div v-show='isshow' class="box"></div>
</transition>

<style scoped>
.box-enter-active,.box-leave-active{
    transition:all 0.4s;
}
/*进入起点*/
.box-enter{
    transform: translateX(-100%);
    opacity:0;
}
/*进入终点*/
.box-enter-to{
    transform: translateX(0%);
    opacity:1;
}
    
/*离开起点*/
.box-leave{
    transform: translateX(0%);
    opacity:1;
}
/*离开终点*/
.box-leave-to{
    transform: translateX(200%) scaleY(0);
    opacity:0;
}
</style>

动画组transition-group:

vue中的transition只能包裹一个元素的定义,如果有多个元素定义相同的动画,可以使用transition-group进行包裹。

transition-group中的元素都要指定一个特定的key值。

<transition-group name="box" :appear="true">
    <div class="box" v-show="isShow" key="box1">这里是一个容器</div>
    <div class="box" v-show="!isShow" key="box2">这里是一个容器</div>
</transition-group>

事件绑定:

指令语法v-on和@:

定义事件时,使用v-on@都可以,事件要定义在实例的methods配置项中。

// 模板
<div id="app">
    <h1>{{title}}</h1>
    <p>当前值为:{{num}}</p>
    <button @click="increment">自增1</button>
    <button @click="add($event,4)">增加</button>
</div>
// 实例
const vm = new Vue({
    el: "#app",
    data: {
        title: '这里是一个VUE应用',
        num: 1
    },
    methods: {
        increment(e) {
        console.log(e);
        this.num++;
    },
    add(event, step) {
        console.log(event);
        this.num += step;
    }
    }
})

事件在没有参数的情况下,可以将事件对象传递给函数,如果事件需要传递参数,则需要使用**$event**对事件对象进行占位。

methods中配置的函数,不需要使用箭头函数,否则this指向的就不会是实例对象。

事件修饰符:

常见的事件修饰符有以下几种:

  • prevent:阻止默认事件。
  • stop:阻止事件冒泡。
  • once:事件只触发一次。
  • capture:使用事件的捕获模式。
  • self:只有event.target是当前操作的元素时才会触发事件。
  • passive:事件的默认行为立即执行,无需等待事件回调执行完毕。
// 模板
<div id="app">
    <h1>{{title}}</h1>
    <div class="box">
        <a v-on:click.prevent="linkEvent" href="http://www.baidu.com">百度</a>
    </div>
    <div class="box" @click="buttonEvent">
        <button @click.stop="buttonEvent">按钮点击</button>
    </div>
</div>
// 实例
new Vue({
    el: "#app",
    data: {
    	title: '事件修饰符'
    },
    methods: {
        linkEvent(){
            console.log("ffffff")
        },
        buttonEvent(){
            console.log(new Date().getTime())
        }
    }
})

键盘事件:

键盘事件主要是针对键盘的按下和抬起事件,常见的使用keyupkeydown。配合键盘事件修饰符enter / delete等。

// 模板
<div id="app">
    <h1>{{title}}</h1>
    <div class="box">
        <input type="text" @keyup.enter="InputEvent" placeholder="请输入文字" />
        <input type="text" @input="ChangeEvent" />
    </div>
</div>
// 实例
new Vue({
	el: "#app",
    data: {
    	title: '键盘事件'
    },
    methods: {
        InputEvent(){
        	console.log("EEE")
        },
        ChangeEvent(){
        	console.log("FFFF")
        }
    }
})

生命周期:

生命周期函数:

生命周期函数、生命周期钩子,是vue在关键时期帮我们调用的一些特殊名称的函数,生命周期函数的名字不可更改,但函数的具体内容要根据需求而定。

生命周期函数中的this指向是当前实例或者组件实例对象

挂载阶段beforeCreate:

挂载阶段的beforeCreate()函数,此时无法通过vm实例访问到data中的数据、methods中的方法。

在该阶段主要完成的是初始化生命周期函数、事件,但是数据代理还未开始。

挂载阶段created:

挂载阶段的created()函数,此时可以访问到vm实例中的data数据、methods中配置的方法。

挂载阶段beforeMount:

beforeMount()函数阶段,页面呈现的是未经Vue编译的DOM结构。

挂载阶段mounted:

页面最终呈现的是经Vue编译的DOM。

更新阶段beforeUpdate:

更新之前调用,此时数据已经更新,但虚拟DOM还没有完成渲染。

更新阶段updated:

更新完成,虚拟DOM已更新。

销毁阶段beforeDestroy和destroyed:

销毁阶段,首先触发beforeDestroy()函数以及destroyed()函数。

环境搭建:

组件相关:

组件的创建:

组件的创建使用Vue.extend(option)进行创建,创建之后在父组件中要注册组件才可以使用。

  • 组件的定义使用Vue.extend
  • 组件的注册使用components:{}
  • 全局组件使用Vue.component(),全局组件不需要注册,直接使用即可。
// 组件的定义
    const myheader = Vue.extend({
        template:`<h1 class="alert alert-danger">{{title}}</h1>`,
        data(){
            return {
                title:'组件的创建'
            }
        }
    })

    const mycounter = Vue.extend({
        template:`<div class="form-group">
                    <h2>当前值为{{num}}</h2>
                    <div class="btn btn-primary" v-on:click="increment">自增1</div>
                    <div class="btn btn-danger" @click="add($event,4)">自增4</div>
                 </div>`,
        data(){
            return {
                num:0
            }
        },
        methods:{
            increment(e) {
                this.num++;
            },
            add(event, step) {
                this.num+=step;
            }
        }
    })

    // 组件的引入
    new Vue({
        el: "#app",
        components:{
            myheader,
            mycounter
        }
    })

ref的使用:

ref被用来给元素或子组件注册引用信息,应用在元素上获取的是真实的DOM对象,应用在组件标签上获取的则是组件的实例对象。

所有的ref对象都是通过**this.$refs**进行管理。

  • ref获取组件:
  • ref获取DOM元素:
  • ref更改组件的data数据:
  • ref调取组件的方法:
import Title from './components/title.vue'
export default {
	name:'App',
	components:{
		Title
	},
	data(){
		return {
				title:'这里是一个VUE应用',
				list:['JavaScript','CSS3','React']
		}
	},
	methods:{
		getRef(){
			console.log(this.$refs.title)
		},
		getDomRef(){
			console.log(this.$refs.list)
		},
		updateTitle(){
			this.$refs.title.content="标题被更改"
		}
	}
}

props的使用:

props用于父组件向子组件传递数据:

  • 传递数据的方式:

    <Com name='xxx' />

  • 子组件接收数据:

    第一种方式:只接收,props:['name']

    第二种方式:限制类型,props:{name:Number}

    第三种方式:限制类型,限制必要性,指定默认值:props:{name:{type:String,required:true}}

混入mixins:

对于多个组件中公共的数据、方法甚至生命周期方法,我们可以将这些公共部分进行处理,组件中使用mixins配置项进行。

  • 定义方式:

    定义混合,直接将公共部分作为一个对象导出即可。

  • 使用方式:

    使用混合,局部混合使用mixins配置项,全局混合则是使用Vue.mixin(xxx)

// mixins定义:包括公共数据和公共方法
export default {
	data(){
		return {
			content:'这里是一个VUE应用',
			list:['A','B','C']
		}
	},
	methods:{
		addItem(){
			this.list.unshift('D')
		}
	}
}

// list组件引入混入
<template>
	<div class="list-wrapper">
		<ul class="list-group">
			<li v-for='(item,index) in list' class="list-group-item" :key='index'>{{item}}</li>
		</ul>
		<div class="btn btn-danger" @click='addItem'>新增</div>
	</div>
</template>

<script>
	import mixins from '../mixins/'
	export default {
		name:'list',
		mixins:[mixins]
	}
</script>

插件:

Vue中插件的功能用于增强Vue,其本质就是包含install方法的一个对象,install方法的第一个参数是Vue,第二个参数是插件使用者传递的数据。

// 插件定义
export default {
	install(Vue,...args){
		console.log(Vue)
		console.log(args)
		console.log('插件注册')
	}
}

// 插件使用
import Plugins from './plugins/'
Vue.use(Plugins,'a','b')

自定义事件:

  • 自定义事件是只适用于组件上的,是子组件向父组件通信的一种方法。

  • 对于父组件而言,父组件中的函数通过类似于props(子组件不需要接收)的形式进行传递,子组件调用时直接使用**this.$emit(xxx)**进行回调调用。

  • 如果自定义事件只想触发一次,可以使用once修饰符进行修饰。

  • 自定义事件的解绑使用this.$off,常在组件销毁之前进行自定义事件的解绑。

  • 组件也可以绑定原生事件,但是需要使用native修饰符。

// APP组件中
<AddToDo @addTodo='addTodo' />
// 触发自定义事件
export default {
    name:'addtodo',
    data(){
        return {
        	title:''
        }
    },
    methods:{
        addTodo(){
            if(this.title){
                this.$emit('addTodo',this.title);
                this.title=''
            }
        }
    }
}

全局事件总线:

全局事件总线是一种借助于自定义事件和原型链原理而衍生的一种组件间通信方式,适用于任意间组件通信

  • 全局事件总线的安装:

    在实例生命周期函数**beforeCreate**的时候,安装全局事件总线。

  • 全局事件总线的使用:

    接收数据的一方在mounted时向总线$bus绑定自定义事件,而事件的回调则保留在接收组件自身。

    提供数据的组件调用全局总线传递数据给接收方。

  • 全局事件总线的解绑:

    最好在beforeDestroy钩子中,使用$off去解绑当前组件所用到的事件。

// 1. 事件总线安装
new Vue({
	el:'#app',
	render:h=>h(App),
	beforeCreate(){
		Vue.prototype.$bus=this;
	}
})
// 2. 事件总线注册
<AddToDo />
import AddToDo from './components/addTodo.vue'
export default {
    name:'App',
    components:{AddToDo},
    data(){
        return {
            title:'这里是一个VUE应用',
            list:['JavaScript','CSS3','React']
        }
    },
    methods:{
        addTodo(title){
            console.log("APP",title)
            this.list.push(title)
        }
    },
    mounted(){
    	this.$bus.$on('addTodo',this.addTodo);
    }
}
// 3. 事件总线使用
<div @click='addTodo' class="btn btn-primary">添加</div>
export default {
	name:'addtodo',
	data(){
		return {
			title:''
		}
	},
	methods:{
		addTodo(){
			if(this.title){
				this.$bus.$emit('addTodo',this.title);
				this.title=''
			}
		}
	}
}

发布订阅模式:

发布订阅主要借助于pubsub-js插件,在组件mounted时,事件订阅,组件即将销毁时取消订阅。

import pubsub from 'pubsub-js'
import AddToDo from './components/addTodo.vue'
export default {
	name:'App',
	components:{AddToDo},
	data(){
		return {
			list:['JavaScript','CSS3','React']
		}
	},
	methods:{
		addTodo(title){
			this.list.push(title)
		}
	},
	mounted(){
		// 事件订阅
		this.addId=pubsub.subscribe('addTodo',this.addTodo);
	},
	beforeDestroy(){
		// 取消事件订阅
		pubsub.unsubscribe(this.addId);
	}
}
// 组件使用
import pubsub from 'pubsub-js'
export default {
	name:'addtodo',
	data(){
		return {
			title:''
		}
	},
	methods:{
		addTodo(){
			if(this.title){
                  // 事件发布
				pubsub.publish('addTodo',this.title);
				this.title=''
			}
		}
	}
}

插槽:

插槽的分类主要包括以下三种:

  • 默认插槽:

    定义插槽占位,内容直接填充到插槽位置。

  • 具名插槽:

    插槽定义时使用name=xxx,组件使用时使用slot=xxx

  • 作用域插槽:

    数据在组件自身,但根据数据生成的结构需要组件的使用者来决定。

// 默认插槽
<template>
	<div class="col col-lg-4">
		<h3>{{title}}</h3>
		<slot></slot>
	</div>
</template>

<script>
	export default {
		name:'Category',
		props:['title']
	}
</script>

// 具名插槽
<template>
	<div class="col col-lg-4">
		<h3>{{title}}</h3>
		<slot name='list'></slot>
		<slot name='link'></slot>
	</div>
</template>
<NameSlot title='美食'>
    <ul class="list-group" slot='list'>
        <li class="list-group-item" v-for='item in foods' :key='item'>
            {{item}}
        </li>
    </ul>
    <div class="links" slot='link'>
        <a href="#">链接一</a>
        <a href="#">链接二</a>
    </div>
</NameSlot>

// 作用域插槽:[数据定义在插槽组件中,只是在引用组件中展示方式不同]
<template>
	<div class="col col-lg-4">
		<h3>{{title}}</h3>
		<slot :list='list'></slot>
	</div>
</template>
<script>
	export default {
		name:'templateslot',
		props:['title'],
		data(){
			return {
				list:['泡泡堂','传奇世界','庆余年']
			}
		}
	}
</script>

<TemplateSlot title='美食'>
    <template slot-scope='{list}'>
        <ul class="list-group">
            <li class="list-group-item" v-for='(item,index) in list' :key='index'>{{item}}</li>
        </ul>
    </template>
</TemplateSlot>

nextTick的使用:

this.$nextTick(callback) 适用于当改变数据后,要基于更新后的新DOM进行某些操作时(比如获取焦点),要在nextTick所指定的回调函数中执行。

Vue3 新特性

环境搭建:

vue-cli安装vue3:

vue-cli是vue的官方脚手架工具,在安装完脚手架工具之后,可以使用vue create xxxx初始化一个vue应用。

// 01 全局安装vue-cli
npm i @vue/cli
// 02 初始化应用
vue create vue-app

webpack配置vue3:

vue3需要搭建不同的vue-loader和vue-template-compiler,并且两者要对应,因此需要一起安装。

// npm 安装依赖
npm install -D vue-loader vue-template-compiler
// webpack配置
const { VueLoaderPlugin } = require('vue-loader')
module.exports = {
  module: {
    rules: [
      // ... 其它规则
      {
        test: /\.vue$/,
        loader: 'vue-loader'
      }
    ]
  },
  plugins: [
    // 请确保引入这个插件!
    new VueLoaderPlugin()
  ]
}

全局API:

应用创建createApp:

vue3不同于vue2使用构造函数创建应用的方式,vue3使用函数创建createApp创建应用。

createApp创建的应用实例只是一个虚拟对象,需要使用mount()函数将该虚拟对象挂载在DOM容器上。

import {createApp} from 'vue'
import App from './App.vue'
const app = createApp(App)

应用挂载/卸载:

app.mount(DOM)将应用实例挂载在一个容器元素中。而app.unmount()将应用实例卸载。

import {createApp} from 'vue'
import App from './App.vue'

const app = createApp(App)
// 应用挂载
app.mount('#app')
// 应用卸载
app.unmount();

全局注入/组件/指令:

在vue3中,所有的全局配置使用都是createApp创建的实例对象app,这不同于vue2使用Vue创建。

  • app.provide('xxx',xxx)

    创建一个全局provide数据,所有的子孙组件都可以使用inject获取到传递的数据。

  • app.component()

    创建一个全局的component对象,所有的组件均可使用该全局组件。

  • app.directive()

    如果同时传递一个名字和一个指令定义,则注册一个全局指令;如果只传递一个名字,则会返回用该名字注册的指令 (如果存在的话)。

// 使用插件plugin定义
import header from '../components/header.vue'
export default {
	install(app){
		// 提供全局provide数据
		app.provide('title','provide依赖注入');
		
		// 注册全局组件
		app.component('MyHeader',header)

		// 注册自定义指令
		app.directive('big',(element,binding)=>{
            element.innerHTML = binding.value*10;
		})
	}
}

生命周期:

setup的使用:

setup是所有组合式api表演的舞台,是vue3 中的一个新的配置项,它的值是一个函数。

setup本质是一个函数,该函数可以返回对象或者函数返回的函数和对象可以在模板中直接使用

setup可以作为组件的一个配置项,也可以使用内嵌式定义。

// 配置项定义
<script>
import { ref } from 'vue'
export default {
    setup(){
        let num =ref(0);
        return {num}
    }
}
</script>

// 内嵌式定义
<script setup>
import { ref } from 'vue';
let num = ref(0);
function increment() {
    num.value = num.value + 1;
}
</script>

生命周期的改变:

相对于vue2中的生命周期,vue3剔除了beforeDestroy / destroyed 生命周期函数,增加了beforeunmount / unmounted生命周期函数。

此外,vue3将生命周期函数配置成组合式函数的模式,需要从vue中单独引入,所有的生命周期函数写在setup函数中。

在vue3中,所有的生命周期函数都被定义成组合式api,每一个生命周期函数在使用时都需要引入。

<script setup>
import { ref, onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted } from 'vue';
let num = ref(0);
function increment() {
    num.value = num.value + 1;
}
onBeforeMount(() => {
    console.log('----onBeforeMount----')
})
onMounted(() => {
    console.log('----onMounted----')
})
onBeforeUpdate(() => {
    console.log('----onBeforeUpdate----')
})
onUpdated(() => {
    console.log('----onUpdated----')
})
onBeforeUnmount(() => {
    console.log('----onBeforeUnmount----')
})
onUnmounted(() => {
    console.log('----onUnmounted----')
})
</script>

vue3取消了vue2中的beforeCreate / created生命周期函数,代之以setup函数代替。

组合式API:

深入setup函数:

setup函数接收两个参数,第一个参数是子组件传递的props值,在props中包含所有传递的数据;第二个参数context(上下文对象)。

<Title title='props值' />

// title组件
<template>
	<h4 class="alert alert-danger">{{title}}</h4>
</template>

<script>
export default {
	name:'title',
	props:['title'],
	setup(props){
		console.log(props)
	}
}
</script>

传入 setup 函数的第二个参数是一个 Setup 上下文对象。上下文对象暴露了其他一些在 setup 中可能会用到的值:

export default {
  setup(props, context) {
    // 透传 Attributes(非响应式的对象,等价于 $attrs)
    console.log(context.attrs)

    // 插槽(非响应式的对象,等价于 $slots)
    console.log(context.slots)

    // 触发事件(函数,等价于 $emit)
    console.log(context.emit)

    // 暴露公共属性(函数)
    console.log(context.expose)
  }
}

ref函数:

vue3中的响应式数据改变时,需要借助于ref函数进行加工,更改的时候则是使用xxx.value进行赋值更改。

import {ref} from 'vue'
export default {
    name: 'App',
    setup(){
        let num = ref(0);
        function increment(){
            num.value+=1;
        }
        return {num,increment}
    }
}

reactive函数:

reactive函数是定义一个对象类型的响应式数据,基本数据类型不要用它,要使用ref函数。

reactive接收一个对象(或数组),返回一个代理对象(proxy对象)

reactive定义的响应式数据是深层次的。

内部基于ES6的Proxy实现,通过代理对象操作源对象内部数据进行操作。

<script setup>
import { ref, reactive } from 'vue'
let text = ref('')
let todos = reactive([{ id: 1, title: '吃饭' }, { id: 2, title: '上网' }]);

function addTodo() {
    todos.unshift({
        id: new Date().getTime(),
        title: text
    })
    text = '';
}
    
</script>

reactive对比ref的区别:

  • 定义数据的类型不同:

    ref用来定义基本数据类型,reactive用来定义对象(或数组)类型数据。ref也可以用来定义对象类型数据,它的内部也是自动通过reactive转换为代理对象的

  • 响应式原理不同:

    ref通过object.defineProperty()get / set来实现响应式(数据劫持)。

  • 使用角度不同:

    ref定义的数据,操作数据需要使用xxx.value,读取数据则不需要,而reactive定义的数据则是直接读取和操作。

计算属性computed:

vue3中的计算属性不同于vue2中配置项的计算属性,使用computed模块抽离。

<script setup>
import { ref, reactive,computed } from 'vue'
let text = ref('')
let todos = reactive([{ id: 1, title: '吃饭' }, { id: 2, title: '上网' }]);

function addTodo() {
    todos.unshift({
        id: new Date().getTime(),
        title: text
    })
    text = '';
}

let counts = computed(()=>{
    return todos.length;
})
</script>

vue3中的计算属性也可以使用get set完整定义。

import { reactive,computed } from 'vue'
export default {
    name: 'counter',
    props:['title','num'],
    setup(props) {
        let data = reactive({
            num: props.num
        });
        // 计算属性完整版定义
        data.biggerNum=computed({
            get(){
                return data.num*20;
            },
            set(value){
                const num = parseInt(value/20);
                data.num=num;
            }
        })
        function addEvent() {
            data.num++;
        }
        return { data, addEvent}
    }
}

监视属性watch:

vue3中的监视属性watch也是被抽离出来的。

<script setup>
    import {ref,watch} from 'vue'

    let num = ref(0);
    function add(){
        num.value++;
    }
    watch(num,(newValue,oldValue)=>{
        console.log(newValue,oldValue)
    },{immediate:true})

</script>

对于多个数据也可以使用组合监视:

import {ref,watch} from 'vue'
export default {
    name:'box2',
    setup(){
        let num = ref(0);
        let age = ref(18);
        /*同时监视num和age*/
        watch([num,age],(oldValue,newValue)=>{
            console.log(oldValue,newValue)
        })
        return {num,age}
    }
}

VUE3中对于监视变更了一下几点:

  • 对于reactive包装的数据,无法正确获得oldValue
  • reactive包装的数据,默认强制开启了深度监视(deep:true),并且deep配置无效。

watchEffect函数:

不同于watch既要指明监视的属性,还要指明监视的回调。

watchEffect函数则不用指明要监视的属性名称,只要回调函数中用到该数据或者属性,就会监视对应的数据和属性。

<script setup>
    import {ref,reactive,watchEffect} from 'vue'
    let num = ref(0);
    let userinfo = reactive({name:'cowen',age:35});
    watchEffect(()=>{
        let total = num.value+1;
        let age = userinfo.age+1;
        console.log('监控触发回调');
    })

    function addEvent(){
        num.value++;
        userinfo.age++;
    }
</script>

toRef和toRefs:

toRef是用来创建一个ref对象,其value值指向另一个对象中的某个属性。

toRef的作用是将响应式对象中的某个属性单独提供给外部使用(模板使用)。

toRefs是用来批量创建多个ref对象,但是这种创建只是单层次的创建。

import {reactive,toRef,toRefs} from 'vue'
export default {
    name:'app',
    setup(){
        let data = reactive({
            num:0,
            todos:[
                {id:1,title:'吃饭'},
                {id:2,title:'上网'},
                {id:3,title:'打豆豆'}
            ]
        });
        // return { num:toRef(data,'num'),todos:toRef(data,'todos')}
        return {...toRefs(data)}
    }
}

toRaw和markRaw:

toRaw的作用将一个由reactive生成的响应式对象转换成普通对象

markRaw的作用是标记一个对象,使其永远不会成为响应式对象。

<script setup>
    import {reactive,toRaw} from 'vue'
    let userinfo = reactive({
        name:'cowen',
        age:35
    })
    const data = toRaw(userinfo);
    console.log(data)
    console.log(userinfo)
</script>

customRef自定义ref:

创建一个自定义的ref,并对其依赖项跟踪和更新触发进行显式控制。

import { customRef } from 'vue'
export default (value)=>{
	let timer = null;
	return customRef((track,trigger)=>{
		return {
			get(){
				track(); // 通知VUE追踪数据的改变
				return value;
			},
			set(newValue){
			    clearTimeout(timer);
			    timer = setTimeout(()=>{
			    	value = newValue;
			    	trigger(); // 通知VUE重新解析模板
			    },500)	
			}
		}
	})
}

// 使用自定义的ref
import { ref, customRef } from 'vue'
import myRef from './customRefs'
export default {
    name: 'App',
    setup() {
        const keyword = myRef('');
        return { keyword };
    }
}

provide和inject用于祖孙组件通信:

provide和inject用于祖孙组件之间的传值,而对于父子之间的通信,常用props传值。

// 组件传递
<script>
import { reactive, provide } from 'vue'
import Son from './components/Son.vue'
export default {
    name: 'App',
    components: { Son },
    setup() {
        const userinfo = reactive({ name: 'cowen', age: 36 });
        provide('userinfo',userinfo);
        return { userinfo }
    }
}
</script>

// 组件接收
<script>
import {inject,toRefs} from 'vue'
export default {
	name:'child',
	setup(){
		const userinfo = inject('userinfo');
		return {...toRefs(userinfo)}
	}
}
</script>

Teleport组件移动:

Teleport在组件中可以移动组件中的DOM元素到指定位置,这很利于整体页面的布局。

<template>
    <div class="dialog-wrapper">
        <div class="btn btn-primary" @click='switchEvent'>{{isshow?'隐藏':'显示'}}</div>
        <teleport to='body'>
            <transition name='box'>
                <div @click='switchEvent' v-if='isshow' class="pop">
                </div>
            </transition>
        </teleport>
    </div>
</template>

Suspense组件:

等待异步组件渲染时渲染一些额外的内容,让整个应用有更好的用户体验,其内部实现原理是插槽。

<template>
    <h1 class="alert alert-danger">Sequence组件</h1>
    <Suspense>
        <template v-slot:default> 
            <Son />
        </template>
        <template v-slot:fallback>
            <div class="alert alert-info">加载中....</div>
        </template>
    </Suspense>
</template>
<script>
import { defineAsyncComponent } from 'vue'
const Son = defineAsyncComponent(() => import('./components/Son.vue'))
export default {
    name: 'App',
    components: { Son },
    setup() {}
}
</script>

setup单文件组件:

是在单文件组件 (SFC) 中使用组合式 API 的编译时语法糖。相比较普通的语法,它具有更多的优势:

  • 更少的样板内容,更简洁的代码。
  • 更好的运行时性能 (其模板会被编译成同一作用域内的渲染函数,避免了渲染上下文代理对象)。

使用单文件组件,我们不必再注册组件、定义props值、返回响应式数据和函数等。

// app组件
<template>
    <h1 class="alert alert-danger">setup单文件组件</h1>
    <Son :age="15" />
</template>
<script setup>
import Son from './components/Son.vue'
</script>

// son组件
<template>
	<div class="wrapper son-wrapper">
		<h3 class="alert alert-success">{{ title }}-{{ age }}</h3>
	</div>
</template>
<script setup>
import { ref, defineProps } from 'vue'
const title = ref('子组件');
const props = defineProps({
	age: Number
})
</script>
成就
0
Star
0
Fork
成员(1)
2135738 chuanshi fr 1663569148
cowen

搜索帮助