简介
Vue是一个动态构建用户界面的渐进式 Javascript 框架
特点
- 遵循mvvm模式
- 编码简洁,体积小,运行效率高,适合移动/pc 端开发
- 它本身只关注UI,也可以引入其他第三方库开发项目
浅尝一下
引入Vue.js
<script src="https://cn.vuejs.org/js/vue.js"></script>
HTML容器:
<!-- 准备一个容器 -->
<div id="root">
<!-- 插值 -->
<p>Hello {{name}}</p>
<p>事{{name2}}捏</p>
</div>
Vue脚本:
//创建Vue实例
const x = new Vue({
//配置对象
el: '#root', //el 用于指定当前Vue实例为哪个容器服务,css选择器格式
data: { //data 中用于指定存储的数据,数据供el所指定的容器去使用
name: 'kanokano',
name2: '嘉然'
}
});
Vue的工作条件
- 想让Vue工作,就必须创建一个Vue实例,且要传入一个配置对象
- root容器里的代码依然符合html的规范标准,只是混入了vue的一些语法
- root容器里的代码被称为Vue模板
- 一个容器对应一个Vue实例,反之亦然,Vue实例配合组件一起使用
- {{}}中需要写的是js表达式,且变量可以自动读取到data中所有的属性
- 一旦data中的数据发生改变,模板(页面)中的目标也会自动更新
模板语法
插值语法
插值语法直接在html标签体内使用Vue插值模板,填入data中的属性即可
<div id="root">
<!-- 插值 -->
<p>Hello {{name}}</p>
</div>
//创建Vue实例
const x = new Vue({
//配置对象
el: '#root',
data: {
name: 'kanokano',
}
});
指令语法
指令语法以 v- 开头,用于解析标签(包括标签属性,标签体内容,绑定事件等)
举例 (v-bind)
使用v-bind:来模板化href属性
<div id="root">
<a v-bind:href="url">kanokano的博客</a>
</div>
const x = new Vue({
//配置对象
el: '#root',
data: {
url: 'https://kanokano.cn'
}
});
有时,v-bind: 也可以简写为 :
<div id="root">
<a :href="url">kanokano的博客</a>
</div>
总结:标签体内用插值语法,标签属性用指令语法
数据绑定
由于v-bind是单向的数据绑定,只能通过对象传递到DOM
有了单向数据绑定这个概念,那就一定会有双向数据绑定
双向数据绑定使用:v-model:
<span>单向数据绑定:</span><input type="text" :value="name2"><br>
<span>双向数据绑定:</span><input type="text" v-model:value="name2">
注意,以下代码是错误的
<h1 v-model:data="name"></h1>
这是因为 v-model 只支持表单类等输入型元素内
v-model:value 可以简写为 v-model 因为 v-model 默认收集的就是value的值
<span>双向数据绑定:</span><input type="text" v-model="name2">
el与data的两种写法
el的两种写法:
对象属性
const v = new Vue({
el: '#root',
data: {
text: "你好呀"
}
});
使用实例对象的原型对象上的$mount方法挂载元素
const v = new Vue({
data: {
text: "你好呀"
}
});
v.$mount('#root')
data的两种写法
对象式:
data: {
text: "你好呀"
}
函数式:
data: function(){
//此处的this式Vue的实例对象
console.log(this)
return {
name:'你好呀'
}
}
注意,函数写法不可以是箭头函数,因为会扰乱this的指向(原本this指向一个Vue对象)
![]()
//函数式可适用于vue组件
MVVM模型
- M:模型(Model): 对应 data中的数据
- V:视图(View): 模板
- VM:视图模型(ViewModel): vue实例对象
<body>
<!-- View -->
<div id="root">
<p>{{kano}}</p>
</div>
<script>
new Vue({ //ViewModel
el: '#root',
data: { //Model
kano: "kano"
}
});
</script>
</body>
数据代理
Object.defineproperty 方法
此方法可以设置一个对象的属性,该方法有三个参数,区别于直接赋值,该方法的作用更多,更高级
简单用法:
let singer = {
name: 'kano'
}
Object.defineProperty(singer, 'age', {
value: 8
})
console.log(singer);
这样就能给一个对象添加属性了,但是你会发现,这个属性居然不能修改,也不能删除,更不能被遍历出来!
其实是因为 Object.defineProperty 方法默认创建出来的属性,他的 writable, enumerable, configurable的值都为false.
console.log(Object.getOwnPropertyDescriptors(singer));
//该方法返回指定对象所有 自身属性 的描述对象
想要解决无法修改,配置,遍历属性的问题,其实很简单,只需要在第三个参数中指定属性的特征即可:
Object.defineProperty(singer, 'age', {
value: 8,
enumerable: true, //控制属性是否可被枚举
writable: true, //控制属性是否可被修改
configurable: true //控制属性是否可被删除
});
singer.age=11;
console.log(singer);
以上代码配置了可修改,可配置,可被枚举的属性特征,所以是可以遍历且值可以被修改删除
getter与setter
如标题所言,JavaScript已经开始逐渐像java/c#类这样的面向对象语言靠拢了(喜)
直接看使用方法:
//数据代理
let msg = 'hello~';
Object.defineProperty(singer, 'msg', {
get: () => {
console.log('调用了getter');
return msg
},
set: value => {
console.log('调用了setter');
msg = value
}
});
console.log(singer);
当读取msg属性的时候,get访问器就会自动调用函数获取相应的值,返回的值就是msg属性的值
修改(set访问器)也是一样,有java/C#类语言的基础会更好理解
这样,通过get访问器就可以实现代理msg变量了,实现了数据的双向/单向同步
总结:Object.defineProperty 是一个比较高级的给对象添加属性的方法,不仅增加了对象属性的安全性,也可以让我们更灵活使用对象中的属性,更重要的是 这个方法可以设置访问代理,Vue的数据双向绑定就是依照这个方法构建的
数据代理实例
我们可以通过一个简单的例子来实现数据代理:
let obj = {
a: 10
};
let objj = {
b: 20
};
Object.defineProperty(objj, 'a', {
get: () => obj.a,
set: (value) => {
obj.a = value
}
})
console.log(objj.a);
console.log(obj.a);
obj.a = 111;
console.log(objj.a);
console.log(obj.a);
Vue中的数据代理
- Vue中的数据代理:
- V通过vm对象来代理data对象中属性的操作读和写
- Vue中数据代理的好处:
- 更加方便操作data中的数据
- 基本原理:
- 通过Object。defineProperty() 把data对象中所有的属性添加到vm上,为每一个添加到vm上的属性,都指定一个getter/setter 在里面进行读写data中对应的属性
原理图:
示例代码:
let data = {
name: 'kano',
age: 10
}
var vm = new Vue({
el: '#root',
data
});
console.log(data === vm._data); //true
vm.name = "kanokano"
console.log(data.name, vm._data.name); //一致
事件处理
事件的基本使用
- 使用v-on:xxx 或 @xxx 绑定事件,其中xxx是事件名
- 事件的回调需要配置在methods对象中,最终会在vm上
- methods中配置的函数不能使用箭头函数,会造成this指向错误
- methods中配置的函数,都是被Vue所管理的函数,this的指向是 vm 或 组件的实例对象
- @click 中的字符串可以使用函数写法进行传参,
@click="fun"
和@click=”fun($event)“
是一样的 - 函数传参的时候如有多个参数,会造成event无法使用,可以在实参列表中,添加$event 进行占位,就可以使用了
代码演示
<div id="root">
<h1>{{hello}}</h1>
<button v-on:click="showInfo">点我</button>
<!-- 简写 -->
<!-- 函数没有参数时候可以省略括号 -->
<!-- 想用event可以使用$event关键词占位 -->
<button @click="showInfo1(123,$event)">点我点我</button>
</div>
const vm = new Vue({
el: '#root',
data: {
hello: '你好!'
},
methods: { //methods内的方法不做数据代理
showInfo(e) {
alert('okk')
console.log(this); //这里的this是vm
console.log(e.target.innerText);
},
showInfo1(num, e) {
alert(num)
console.log(this); //这里的this是vm
console.log(e.target.innerText);
}
}
});
事件修饰符
Vue中的事件修饰符:
- prevent:阻止默认事件(常用)
- stop:阻止事件冒泡(常用)
- once:事件只触发一次(常用)
- capture:使用事件捕获模式
- self:只有event.target 是当前操作的元素才会触发事件
- passive:事件的默认行为是立即执行,无需等待事件回调即可执行完毕
注意,事件修饰符是可以连用的 比如 @click.stop.prevent 先阻止冒泡。再阻止默认行为
<div id="root">
<h1>{{hello}}</h1>
<!-- .prevent就可以阻止默认行为 -->
<!-- .prevent就是事件修饰符 -->
<a href="//baidu.com" @click.prevent="showInfo">点我</a>
<!-- 阻止事件冒泡(常用) -->
<div @click="showInfo" style="background-color:skyblue">
<button @click.stop="showInfo">点点点</button>
</div>
<!-- 一次性事件 -->
<button @click.once="showInfo">我是一次性的</button>
<!-- 事件捕获模式 -->
<!-- 点击紫色盒子,先输出1再输出2 -->
<div @click.capture="show(1)" style="background-color:skyblue;padding: 10px;">1
<div @click.capture="show(2)" style="background-color:blueviolet;">2</div>
</div>
<!-- 只有event.target 是当前操作的元素才会触发事件,点击紫色盒子的时候会触发事件,此时e.target是点击的那个button,然后会触发事件冒泡 -->
<!-- 事件冒泡到上层绑定事件的元素时,由于加了.self修饰,发现了触发事件的event不是自己,就不会触发事件操作 -->
<div @click.self="show(1)" style="background-color:skyblue;padding: 10px;">1
<button @click="show(2)" style="background-color:blueviolet;">2</button>
</div>
<!-- 事件的默认行为是立即执行,无需等待事件回调即可执行完毕 -->
<!-- wheel是鼠标滚轮事件 -->
<ul @wheel.passive="scrollbig">
<li>1111</li>
<li>2222</li>
<li>3333</li>
<li>4444</li>
</ul>
</div>
const vm = new Vue({
el: '#root',
data: {
hello: '你好!'
},
methods: { //methods内的方法不做数据代理
showInfo(e) {
alert('okk')
console.log(this); //这里的this是vm
console.log(e.target.innerText);
},
show(s) {
console.log(s);
},
scrollbig() {
for (var i = 0; i < 999; i++) {
console.log('dddd');
}
}
}
});
键盘事件
- 常用按键别名:
- 回车 enter删除 delete (删除和退格都行
- 退出 esc空格 space换行 tab (特殊,必须配合keydown)
- 上 up
- 下 down
- 左 left
- 右 right
- Vue未提供别名的按键,可以使用按键原始值的key值绑定,但要注意要转为 keybab-case 短横线小写命名
- 系统修饰键(特殊) ctrl alt shift 等
- 配合keyup使用:按下修饰键的同时,再按下其他键,随后释放,事件才能触发
- 配合keydown使用,正常触发事件
- 可以使用keyCode指定具体的按键(不建议)
- Vue.config.keyCodes.自定义按键名=键码,可以自定义按键别名
- 可以利用修饰符连写的特性,进行组合键的侦听
<div id="root"> <h2>{{name}}</h2> <input type="text" placeholder="按下回车键提示输入" @keyup.enter="into"> <!-- 修饰符连写 --> <input type="text" placeholder="按下ctrl+y提示输入" @keyup.ctrl.y="into"> </div> <script> Vue.config.keyCodes = 41; new Vue({ el: '#root', data: { name: '键盘事件演示' }, methods: { into(e) { console.log(e.target.value); } } }); </script>
计算属性
- 定义:要用的属性不存在,要通过已有的属性计算得来。
- 原理:底层借助了Object.defineproperty方法提供的getter和setter
- get函数什么时候执行?
- 初次读取时会执行一次
- 当依赖的数据发生改变的时候会被再次调用
- 优势:与methods实现相比,内部有缓存机制,效率更高,调试方便
- 注意:
- 计算属性最终会出现在vm上,直接读取使用即可
- 如果计算属性要被修改,那必须写set函数去响应修改,且set中要引起计算时依赖的数据发生变化(firstname、lastname)
代码演示
<div id="root">
<!-- v-model 双向数据绑定 -->
<p> 姓:<input type="text" v-model="firstname"> </p>
<p> 名:<input type="text" v-model="lastname"></p>
<!-- 上面的input内容只要发生变化,vue的模板都会重新解析一遍html,从而带动下面的函数的重载 -->
<p> 姓名:<span>{{fullname}}</span></p>
</div>
const vm = new Vue({
el: '#root',
data: {
firstname: '张',
lastname: '三'
},
//计算属性,computed内的就是计算属性,用来处理复杂的属性计算,里面的属性值也是用数据代理
//计算属性是有缓存的,get调用了一次,如果值没有改变,再次调用属性的时候就不会调用get函数了
//初次读取fullname时候,get会被调用,所依赖的数据发生变化时,get也会被调用
//computed 属性中的get不能被手动调用,Vue会在使用到属性的时候自动调用
computed: {
fullname: {
get() {
//此处的this是vm
return this.firstname + '-' + this.lastname;
},
//当fullname被修改的时候自动调用set
set(value) {
let arr = value.split('-');
//改的是这两个变量,而不是fullname
this.firstname = arr[0];
this.lastname = arr[1]
}
}
}
});
此外,fullname还有一种简写形式:
//fullname的简写形式
fullname() {
return this.firstname + '-' + this.lastname;
}
可以把属性写成一个函数,函数名就是属性名(es6)函数体默认就有getter的作用
注意!! 不要把fullname理解为一个函数了,它本身还是一个属性,函数只是负责计算返回结果,在模板内只需要写属性名即可!!!
监视属性watch
- 当被监视的属性变化时,回调函数自动调用,进行相关操作
- 监视的属性必须存在,才能进行监视操作
- 监视的两种写法
- new Vue 时传入 watch配置
- 通过vm.$watch('属性名',配置)
代码演示
<div id="root">
<h1>今天天气很{{Info}}</h1>
<button v-on:click="changeWeather">切换天气</button>
<!-- 如果函数功能很简单,可以直接在click里面写语句 -->
<button v-on:click="isHot = !isHot;">切换天气</button>
<!-- 但是这里面不能写alert等函数,因为Vue中的原型对象不包含window对象 -->
<!-- 解决方法:可以在vm实例中添加window属性,指向window对象即可 -->
<!-- <button v-on:click="isHot = !isHot;">切换天气</button> -->
</div>
const vm = new Vue({
el: '#root',
data: {
isHot: true
},
methods: {
changeWeather() {
this.isHot = !this.isHot;
}
},
computed: {
Info() {
return this.isHot ? '炎热' : '凉爽';
}
},
//监视属性
// watch: {
// isHot: {
// //立即执行,初始化时候,让handler调用一次
// immediate: true,
// //handler当ishot发生改变时调用
// handler() {
// console.log('isHot被修改了');
// }
// }
// },
});
//监视属性的另一种写法$watch('属性名',配置)
//属性名如果找不到的话也不会报错
vm.$watch('isHot', {
handler() {
console.log('isHot被修改了');
}
})
这里有一个小问题注意:当模板里不使用info变量的时候,点击切换天气,vue开发者工具里的变量显示可能不会更新,但vm内属性实际是更新了的
深度监视
看完了监视属性的简单使用,这时候我会提出一个需求,那就是,如果data属性内有一个对象,我们该怎么监视该对象里面的特定属性呢?
监视多级结构中某属性的变化
答案是使用 ’对象名.属性名‘:{监视配置}
看代码:
data: {
isHot: true,
numbers: {
a: 1,
b: 1
}
}
还原成原始写法,就可以使用 点. 来进行单个数据的监视了,也就是深度监视
'numbers.a': {
handler() {
console.log('a被修改了');
}
},
这样就可以监视多级结构中的某个属性的变化了
深度监视的简易写法
如果监视属性里面的语句很简单,就可以使用简易写法:
//正常写法
isHot: {
// immediate: true,
// deep: true,
handler(newValue, oldValue) {
console.log('isHot被修改了');
}
}
//简写
isHot() {
console.log('isHot被修改了');
}
外部写法也是如此:
//正常外部写法
vm.$watch('isHot', {
immediate: true,
deep: true,
handler(newValue, oldValue) {
console.log('isHot被修改了');
}
})
//简写
vm.$watch('isHot', function(newValue, oldValue) {
console.log('isHot被修改了', newValue, oldValue);
});
watch 对比 computed
computed和watch的区别:
- computed能完成的功能,watch都可以完成
- watch能完成的功能,computed不一定能完成,例如,watch可以执行异步操作
重要的两个小原则:
- 所被vue管理的函数,最好写成普通函数,这样this的指向才会是vm
- 所有不会被vue所管理的函数,(定时器,ajax回调)。最好写成箭头函数,这样this指向才是vm
比如下面的姓名案例,用watch实现:
<div id="root">
<p> 姓:<input type="text" v-model="firstname"> </p>
<p> 名:<input type="text" v-model="lastname"></p>
<p> 姓名:<span>{{fullName}}</span></p>
</div>
new Vue({
el: '#root',
data: {
firstname: '张',
lastname: '三',
fullName: '张-三'
},
watch: {
firstname(newValue) {
//可以写定时器
setTimeout(() => {
this.fullName = newValue + '-' + this.lastname;
}, 1000);
},
lastname(newValue) {
this.fullName = this.firstname + '-' + newValue;
}
},
});
以上代码,会发现我在firstname的处理函数内写了一个定时器,达到了数据更改之后一秒后才执行更改的目的
绑定样式
绑定class样式
在vue中,绑定class的方式是使用 v-bind来动态处理class样式的
一共有三种方式:
- 字符串写法,适用于:样式类名不确定 ,需要动态指定
- 数组写法,适用于:样式类名不确定 ,个数也不确定,名字也不确定,需要动态指定
- 对象写法,适用于:样式类名确定 ,个数也确定,名字也确定,需要动态决定用不用
代码:
<div id="root">
<!-- 绑定class样式 字符串写法,适用于:样式类名不确定 ,需要动态指定 -->
<div class="basic normal" :class="mood" @click="changeMood">{{a}}1</div>
<hr>
<!-- 绑定class样式 数组写法,适用于:样式类名不确定 ,个数也不确定,名字也不确定,需要动态指定 -->
<div class="basic" :class="array">{{a}}2</div>
<button @click="md">点我删除类</button>
<hr>
<!-- 绑定class样式 对象写法,适用于:样式类名确定 ,个数也确定,名字也确定,需要动态决定用不用 -->
<div class="basic" :class="classObj">{{a}}2</div>
</div>
new Vue({
el: '#root',
data: {
a: 'hello',
mood: 'normal',
//存放类的数组
array: ['normal', 'happy', 'sad'],
classObj: {
//类的对象写法,false为不启用
happy: true,
sad: true
}
},
methods: {
changeMood() {
const arr = ['happy', 'sad', 'normal'];
const index = Math.floor(Math.random() * 3);
this.mood = arr[index]
},
md() {
this.array.shift();
}
},
});
绑定style样式
绑定style样式的方法和绑定class样式大同小异
使用 :style="styleObj"
进行绑定
对象名可以使用驼峰命名的css属性 fontSize: '44px'
也可以是原始css属性,但需要加上引号 'font-size': '44px'
代码演示
<!-- 绑定style样式 也是对象写法 -->
<div class="basic" :style="styleObj">{{a}}2</div>
<!-- 绑定style样式 数组对象写法 -->
<div class="basic" :style="[styleObj1,styleObj]">{{a}}2</div>
data: {
//样式的对象写法
styleObj: {
//vue中的css属性写法
// fontSize: '44px'
//也可以写原生css写法
'font-size': '44px'
},
styleObj1: {
//vue中的css属性写法
// fontSize: '44px'
//也可以写原生css写法
color: 'red'
}
}
条件渲染
1.v-if
写法:
- v-if="表达式"
- v-else-if="表达式"
- v-else="表达式"
适用于:切换频率较低的场景
特点:不展示DOM元素直接被移除
注意:v-if可以用:v-else-if ,v-else一起使用,但中间不能有元素打断
2.v-show
写法:v-show=”表达式“
适用于:切换频率较高的场景
特点:不展示dom元素,未被移除,仅仅是使用样式隐藏掉
注意:使用v-if时候,元素可能无法被获取到,但是使用v-show是一定可以获取到的
v-if可以配合template模板使用
代码演示
<div id="root">
<!-- 条件渲染 相当于css的display -->
<h2 v-show="true">{{a}}</h2>
<h2 v-show="1===1">{{a}}</h2>
<h2 v-show="aa">{{a}}</h2>
<hr>
<!-- 使用v-if做条件渲染 影响结构-->
<h2 v-if="aa" @click="n++">{{n}}</h2>
<h2 v-if="true">{{a}}</h2>
<h2 v-if="1===1">{{a}}</h2>
<!-- v-else-if -->
<!-- if和elseif 中间不能有元素打断 -->
<div v-if="n===1">1111</div>
<div v-else-if="n===2">2222</div>
<div v-else-if="n===3">3333</div>
<!-- v-else 不用写参数,是上面条件都不符合的情况 -->
<div v-else>好</div>
<!-- 使用template模板配合if渲染多个元素 -->
<!-- 使用template模板只能配合if,不能配合show -->
<template v-if="n===4">
<h2 v-show="true">template{{a}}</h2>
<h2 v-show="1===1">template{{a}}</h2>
<h2 v-show="aa">template{{a}}</h2>
</template>
</div>
new Vue({
el: '#root',
data: {
a: 'hello',
n: 1,
aa: true
}
});
v-for 指令
v-for指令可以用来循环遍历对象、数组、字符串、数字 到相应个数的标签中
作用、语法:
- 用于展示列表数据
- 语法:
v-for="(item,index) in xxx" :key="yyy"
- 可遍历: 数组、对象、字符串、数字
注意:只要你使用了遍历语法,你就必须要使用key这个关键字,给每一个li设定唯一的标识
格式::key="xxx“
代码演示:
<div id="root">
<h2>人员列表</h2>
<ul>
<!-- 使用v-for进行遍历输出 -->
<!-- 只要你使用了遍历语法,你就必须要使用key这个关键字,给每一个li设定唯一的标识 -->
<li v-for="p in persons" :key="p.id">{{p.name}}</li>
</ul>
<ul>
<!-- key的另一种写法 -->
<!-- a就相当于item,b就相当于index -->
<li v-for="(a,b) in persons" :key="b">A:{{a}} B:{{b}} </li>
</ul>
<ul>
<!-- 也可以使用of 遍历对象(in也可以) 这样a就是value b就是key了 -->
<li v-for="(a,b) of car" :key="b">key: {{b}} ---- value :{{a}}</li>
</ul>
<ul>
<!-- 甚至还能遍历字符串 -->
<li v-for="(a,b) in str" :key="b">key: {{b}} ---- value :{{a}}</li>
</ul>
<ul>
<!-- 甚至还能遍历输出数字 -->
<li v-for="(a,b) in 10" :key="b">key: {{b}} ---- value :{{a}}</li>
</ul>
</div>
new Vue({
el: '#root',
data: {
persons: [{
id: '001',
name: '张三',
age: '18'
}, {
id: '002',
name: '李四',
age: '19'
}, {
id: '003',
name: '王五',
age: '20'
}],
car: {
name: '马自达',
price: '114514'
},
str: 'kanokano123'
}
});
v-for中key的作用与坑
key在v-for中扮演者极为重要的角色,key是vue作为判断数据的唯一性的重要标识
所以,key的唯一性成了一个非常重要的前提保证
这里就要提一下Vue在生成数据的时候的步骤了:
- 首先vue会在渲染页面前,在计算机内存中计算并生成一个虚拟DOM,然后才会渲染成真实DOM文档到页面上
- 如果需要在相同的地方进行重新渲染,Vue就会依次对比新数据和之前的虚拟DOM数据是否一致,如果发现旧数据有一致的地方,则会复用旧的虚拟DOM缓存渲染的真实DOM,以提高渲染效率,如果不一致,则会按照新dom来重新渲染
既然是依次对比,所以,这里就会牵扯到顺序问题,vue是按照key进行顺序遍历对比的,所以,key一定要是一个唯一的值!!!
要点归纳:
- 虚拟DOM中key的作用:
- key是虚拟dom对象的标识,当状态中的数据发生变化时,Vue会根据新数据生成“新虚拟dom”
随后Vue进行“新虚拟dom” 与 ”旧虚拟dom“ 的差异比较,规则如下:
- key是虚拟dom对象的标识,当状态中的数据发生变化时,Vue会根据新数据生成“新虚拟dom”
- 对比规则:
- 旧虚拟DOM中找到了与新的虚拟DOM相同的key
- 若虚拟DOM中的内容没有改变,则会直接复用之前的真实dom
- 若虚拟DOM中的内容改变了,则会生成新的真实DOM,随后替换掉页面中之前的真实DOM
- 旧虚拟DOM中未找到与新虚拟DOM相同的key
- 创建真实的DOM,随后渲染到页面
- 旧虚拟DOM中找到了与新的虚拟DOM相同的key
- 用index作为key可能会引发的问题:
- 若对数据进行:逆序添加,逆序删除等破坏顺序的操作:
- 产生没有必要的真实DOM更改 => 界面没有问题,但是渲染效率降低
- 如果结构中还包括输入类的dom
- 会产生错误的DOM更新 => 界面有问题,顺序错乱
- 若对数据进行:逆序添加,逆序删除等破坏顺序的操作:
- 开发中如何选择key?
- 最好使用每条数据的唯一标识作为key,比如id,手机号,身份证号,学号等唯一值
- 如果不存在对数据的逆序添加、逆序删除等破坏顺序的操作,仅用于渲染列表用于显示,使用index作为key是没有问题的
错误示例
<div id="root">
<h2>人员列表</h2>
<ul>
<button @click="add">点我添加老六</button>
<!-- 使用v-for进行遍历输出 -->
<li v-for="(i,index) in persons" :key="index">
{{i.name}}-{{i.id}}
<input type="text">
</li>
</ul>
</div>
data: {
persons: [{
id: '001',
name: '张三',
age: '18'
}, {
id: '002',
name: '李四',
age: '19'
}, {
id: '003',
name: '王五',
age: '20'
}]
},
methods: {
add() {
let p = {
id: '004',
name: '老六',
age: 21
};
//往数组第一个写入数据
this.persons.unshift(p);
}
}
点击之前:
点击之后 :
你会发现,老六旁边的输入框本应该是空的,结果变成了张三,导致下面的输入框全都错了一位
这是因为:Vue在对比新老虚拟DOM的时候,发现张三在index 0的位置,无法渲染,但是旁边的输入框在虚拟DOM中都是空的,Vue则会复用之前的实际DOM,然后继续往后依次比对,发现每一个的姓名和index都对不上,所以只能使用新虚拟DOM重新渲染名字了,但是,输入框在新老虚拟DOM中,都是空的,Vue就会误以为可以直接复用,就会把前三个复用掉,最后才会渲染新的input元素到最后
这里的主要问题就是,index不是唯一的,修改DOM树的时候容易造成查找不正确,从而进行错误的操作
解决方法
把 :key="index"
替换为 :key="i.id"
即可
或者在这个项目例子里,没有必要非要把老六放在第一位,可以使用push方法插入数据到数组尾部,这样就不会打乱index的标号顺序了
使用列表过滤实现简单搜索与排序
基本原理就是,使用计算属性于filter,sort函数
切记,计算属性执行的条件是:页面刚加载时和依赖数据发生变化时
<div id="root">
<h2>人员列表</h2>
<input type="text" placeholder="请输入姓名" v-model="Keyword">
<button @click="sortType=2">年龄升序</button>
<button @click="sortType=1">年龄降序</button>
<button @click="sortType=0">原顺序</button>
<ul>
<!-- key的另一种写法 -->
<!-- a就相当于item,b就相当于index -->
<li v-for="(a,b) in findPersons" :key="a.id">{{a.name}}-{{a.age}}-{{a.sex}}</li>
</ul>
</div>
new Vue({
el: '#root',
data: {
Keyword: '',
sortType: 0, //0原序 1 降序 2升序
persons: [{
id: '000',
name: '张三',
age: '11',
sex: '男'
}, {
id: '001',
name: '张麻子',
age: '28',
sex: '男'
}, {
id: '002',
name: '李四',
age: '19',
sex: '男'
}, {
id: '003',
name: '王五',
age: '50',
sex: '男'
}, {
id: '004',
name: '马冬梅',
age: '12',
sex: '女'
}],
// findPersons: []
},
//使用计算属性实现
computed: {
//这玩意开始的时候执行一次
//所依赖的keyword变化时候又会执行一次
//里面用到了sortType 所以sortType改变的时候也会触发这个函数
findPersons() {
const arr = this.persons.filter((p) => {
//true就是符合,返回一个新数组
//indexof对于空字符串的返回是0(特别重要)
return p.name.indexOf(this.Keyword) !== -1
});
//别急着返回,判断一下是否要排序
if (this.sortType) {
arr.sort((a, b) => {
//由于a和b在这里都是对象,不能直接比,我们要获取里面的age
return this.sortType == 1 ? b.age - a.age : a.age - b.age
})
}
return arr;
}
}
//#region
//使用监视属性实现
// watch: {
// Keyword: {
// //立即执行一次,否则页面没数据
// immediate: true,
// handler(val) {
// this.findPersons = this.persons.filter((p) => {
// //true就是符合,返回一个新数组
// //indexof对于空字符串的返回是0(特别重要)
// return p.name.indexOf(val) !== -1
// })
// }
// }
// },
//#endregion
});
Vue数据侦测
实现类似vue的简单数据检测
代码演示
//需要传入的data对象
let data = {
name: 'kano',
adress: 'Janpan'
};
//创建一个监视的实例对象,用于监视data中属性的变化
const obs = new Observer(data);
console.log(obs);
//创建一个vm
let vm = {};
vm._data = obs;
vm.data = obs;
//创建一个名叫监视者的构造函数
function Observer(obj) {
//汇总对象中所有的属性,形成一个数组
const keys = Object.keys(obj);
//遍历
keys.forEach((k) => {
Object.defineProperty(this, k, {
get() {
return obj[k];
},
set(val) {
console.log("我被修改了,我要去处理页面响应");
obj[k] = val;
}
})
})
}
不完善的地方:无法找出数组中的对象并赋予getter setter , 多层对象也一样
使用Vue.set方法新增响应式属性
首先得注意的是:
这个set方法只能给vue data里面某个对象增加属性!!!
也就是要操作的对象不能是Vue实例,或者Vue实例的根数据对象(data、_data)
语法
Vue.set(vue实例, '属性', 值或对象)
例子
const vm = new Vue({
el: '#root',
data: {
name: 'kanokano',
adress: '湖南',
student: {
name: 'tony',
// sex: '男',
}
},
});
//vm.$set(vm.student, 'sex', '男')
//或者
Vue.set(this.student, 'sex', '男')
数组更新检测
Vue将被监听的数组的变更方法进行了包装,所以也可以触发网页元素更新,方法包括:
- push()
- pop()
- shift()
- unshift()
- splice()
- sort()
- reverse()
注意:使用下标索引直接修改数组数据是不会触发视图更新的!!!
错误示范
vm.hobby[0]='ddd';
上面的操作是不会生效的(
对于非修改性质的方法,比如filter() ,过滤完成后可以直接替换赋值掉原对象
vm.hobby = vm.hobby.filter((x)=>{
return x != '抽烟'
})
代码演示
const vm = new Vue({
el: '#root',
data: {
name: 'Xukun Cai',
hobby: ['唱', '跳', 'rap', '篮球'],
}
});
//添加元素
vm.hobby.push('你干嘛~ 哎哟');
vm.hobby.shift();
此外,除了可以用上面提到的几个方法操作数组之外,其实也可以使用Vue.set方法进行变更数据:
Vue.set(vm.hobby,0,'唱');
//或者
vm.$set(vm.hobby,0,'唱');
总结
Vue监视数据的原理:
-
Vue会监视data中所有层次的数据
-
如何检测对象中的数据
- 通过setter实现监视,且要造new Vue 时就要传入要监测的数据
- 对象中后追击啊的属性,Vue默认不做响应式处理
- 如需给后添加的属性做响应式,请使用如下API
vue.set(target,propertyName/index,value)
vm.$set(target,propertyName/index,value)
- 通过setter实现监视,且要造new Vue 时就要传入要监测的数据
-
如何监测数组中的数据?
- 通过包装数组对应更新元素的方法来实现,本质就是
- 调用原生对应的方法对数组进行更新
- 重新解析没模板,进而更新页面
- 通过包装数组对应更新元素的方法来实现,本质就是
-
vue修改数组中某一个元素一定要使用支持的方法
- API: push , pop , shift , unshift , splice , sort , reverse
- Vue.set 或者 vm.$set
特别注意的是:Vue.set 和 vm.$set 不能给vm或者vm的根数据对象添加属性!
对于非修改性质的方法,比如filter() ,过滤完成后可以直接替换赋值掉原对象
后续内容学习中....