Vue核心知识点(三)
前言
本博客是对以下视频教程做的一个笔记总结,这个笔记和视频不是一一对应的,但可作为参考,不喜勿喷。
2021最新Vue迅速上手教程丨vue3.0入门到精通
本博客是Vue核心知识点的第三篇,第二篇在这儿:Vue核心(二) 。由于内容太多,分三篇来写。
如果对你有所帮助,欢迎点赞,评论,转发。
这篇博客的主要内容有:
v-for (列表渲染)深入了解。Vue 数据监测原理分析。Vue 生命周期。
1、v-for(列表渲染)深入了解
这里主要是对v-for中,key 的原理进行分析。
1.1 示例代码
代码如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8"/>
<title>key的原理</title>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
</head>
<body>
<div id="root">
<h3>遍历数组</h3>
<button @click.once="add">添加一个老王</button>
<ul>
<li v-for="(p,index) in persons" :key="p.id">
{{p.name}}--{{p.age}}
<input type="text">
</li>
</ul>
</div>
</body>
<script type="text/javascript">
Vue.config.productionTip = false;
const vm = new Vue({
data() {
return {
persons: [
{id: '001', name: '张三', age: 18},
{id: '002', name: '李四', age: 19},
{id: '003', name: '王五', age: 20},
]
};
},
methods: {
add() {
console.log("add...");
let p = {id: '004', name: '老王', age: 22};
this.persons.unshift(p);
}
}
});
vm.$mount('#root');
</script>
</html>
1.2 key原理分析
1、index作为key:
手动在后面的输入框中输入内容,如下:
往数组头添加一个老王之后:
很显然,出现了问题,元素的顺序出现了问题。
原理如下:
2、id作为key :
还是之前的数据,在数组头插入一个老王之后,如下图:
可以看到,下面的数据并没有发生混乱。
原理如下:
1.3 总结
面试题:React、Vue中的key有什么作用?(key的内部原理)
-
虚拟DOM中key的作用:
-
用index作为key可能会引发的问题:
- 若对数据进行:逆序添加、逆序删除等破坏顺序操作,
- 会产生没有必要的真实DOM更新 ==> 界面效果没问题, 但效率低。
- 如果结构中还包含输入类的DOM:
- 会产生错误DOM更新 ==> 界面有问题。
-
开发中如何选择key?
- 最好使用每条数据的唯一标识作为key, 比如id、手机号、身份证号、学号等唯一值。
- 如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表用于展示,
- 使用index作为key是没有问题的。
1.4 案例_过滤列表
输入名字,显示对应的人。
代码如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8"/>
<title>列表过滤</title>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
</head>
<body>
<div id="root">
<h2>人员列表</h2>
<input type="text" placeholder="请输入名字" v-model="keyWord"/>
<ul>
<li v-for="(p,index) of filPersons" :key="p.id">
{{p.name}}--{{p.age}}--{{p.sex}}
</li>
</ul>
</div>
</body>
<script type="text/javascript">
Vue.config.productionTip = false;
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: '女'}
],
},
computed: {
filPersons() {
return this.persons.filter((p) => {
return p.name.indexOf(this.keyWord) !== -1;
});
}
}
});
</script>
</html>
1.5 案例_列表排序
对列表进行升序,降序排列,也可以恢复原顺序。
代码如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8"/>
<title>列表过滤</title>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
</head>
<body>
<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>
<li v-for="(p,index) of filPersons" :key="p.id">
{{p.name}}--{{p.age}}--{{p.sex}}
</li>
</ul>
</div>
</body>
<script type="text/javascript">
Vue.config.productionTip = false;
const vm = new Vue({
data() {
return {
keyWord: '',
sortType: 0,
persons: [
{id: '003', name: '赵丽颖', age: 21, sex: '女'},
{id: '002', name: '周冬雨', age: 20, sex: '女'},
{id: '005', name: '王老胡', age: 23, sex: '男'},
{id: '004', name: '杨幂', age: 22, sex: '女'},
{id: '001', name: '胡歌', age: 19, sex: '男'},
],
};
},
computed: {
filPersons() {
let arr = this.persons.filter((p) => {
return p.name.indexOf(this.keyWord) !== -1;
});
if (this.sortType) {
arr.sort((p1, p2) => {
return this.sortType === 1 ? p2.age - p1.age : p1.age - p2.age;
});
}
return arr;
}
}
});
vm.$mount('#root');
</script>
</html>
2、Vue数据监测原理分析
2.1 Vue数据更新的问题
点击按钮,更新胡歌的信息。
代码如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8"/>
<title>Vue数据更新的问题</title>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
</head>
<body>
<div id="root">
<h2>人员列表</h2>
<button @click="updateUserInfo()">更新胡歌的信息</button>
<ul>
<li v-for="(p,index) of persons" :key="p.id">
{{p.name}}--{{p.age}}--{{p.sex}}
</li>
</ul>
</div>
</body>
<script type="text/javascript">
Vue.config.productionTip = false;
const vm = new Vue({
data() {
return {
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: '女'}
],
filPersons: []
};
},
methods: {
updateUserInfo() {
this.persons[0] = {id: '001', name: '飞蓬将军', age: 1200, sex: '男'};
console.log(this.persons[0]);
}
}
});
vm.$mount('#root');
</script>
</html>
结果如下:
出现了数据不一致的问题。那又该如何解决呢?
别急,下面我们就一起来探究一下Vue监测数据变化的原理。
2.2 Vue数据监测原理_对象
2.2.1 示例代码
代码如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8"/>
<title>Vue监测数据变化的原理</title>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
</head>
<body>
<div id="root">
<h2>学校:{{school}}</h2>
<h2>地址:{{address}}</h2>
</div>
</body>
<script type="text/javascript">
Vue.config.productionTip = false;
const vm = new Vue({
data() {
return {
school: '北京理工',
address: '北京',
student: {
name: 'tom',
age: {
rAge: 30,
sAge: 18,
},
friends: [
{name: 'jerry', age: 29}
]
}
};
}
});
vm.$mount('#root');
</script>
</html>
总结
- Vue内部会递归查找属性,如果发现是对象属性,则会继续递归查找,
- 直到找到单一的属性为止。
- Vue会给data中的每一个自定义属性分配一组getter和setter。
2.2.2 原理分析
2.2.3 总结
Vue 为data 中的每一个自定义属性,都匹配了一组getter 和setter 。- 假如
data 中定义了一个school 属性,则当school 属性值发生改变的时候,对应的setter 就会被调用, - 然后
Vue 就会重新解析模板,接下来就会生成新的虚拟DOM , - 然后
Vue 就会对比新旧的虚拟DOM (diff算法), - 最后生成真实
DOM ,更新页面。
2.3 模拟一个数据监测
为了对Vue数据监测原理有更深的理解,我们来看看和模拟一下Vue中是怎么做的数据监测。
以监测对象数据为例。
2.3.1 示例代码
代码如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8"/>
<title>模拟一个数据监测</title>
</head>
<body>
<div id="root">
</div>
</body>
<script type="text/javascript">
let data = {
school: '北京理工',
address: '北京'
};
function Observer(obj) {
const keys = Object.keys(obj);
keys.forEach((k) => {
Object.defineProperty(this, k, {
get() {
return obj[k];
},
set(newValue) {
console.log(`${k}被修改了,我要去解析模板,生成虚拟DOM,对比新旧虚拟DOM...要开始忙了~`);
obj[k] = newValue;
}
});
});
}
const obs = new Observer(data);
let vm = {};
vm._data = data = obs;
</script>
</html>
对属性值进行修改,控制台输出如下:
2.4 Vue.set的使用
向响应式对象中添加一个 property,并确保这个新 property 同样是响应式的,且触发视图更新。
代码如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8"/>
<title>Vue.set的使用</title>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
</head>
<body>
<div id="root">
<button @click="addSex">添加一个性别属性,默认值是男</button>
<h2>学生信息</h2>
<h3>名字:{{student.name}}</h3>
<h3>年龄:{{student.age}}</h3>
<h3 v-if="student.sex">性别:{{student.sex}}</h3>
<h3>朋友们:</h3>
<ul>
<li v-for="(f,index) in student.friends" :key="index">
{{f.name}}==={{f.age}}
</li>
</ul>
</div>
</body>
<script type="text/javascript">
Vue.config.productionTip = false;
const vm = new Vue({
data() {
return {
student: {
name: '张三',
age: 20,
friends: [
{name: '李四', age: 18},
{name: '王五', age: 19},
]
}
};
},
methods: {
addSex() {
this.$set(this.student, 'sex', '男');
}
}
});
vm.$mount('#root');
</script>
</html>
总结
Vue.set( target, propertyName/index, value ) vm.$set( target, propertyName/index, value ) 【vm为Vue实例对象】- 向响应式对象中添加一个 property,并确保这个新 property 同样是响应式的,且触发视图更新。
- 参数:
{Object | Array} target {string | number} propertyName/index {any} value - 返回值:设置的值。
官网:全局API-Vue.set的使用
2.5 Vue数据监测原理_数组
2.5.1 示例代码
代码如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8"/>
<title>Vue监测数据变化的原理_数组</title>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
</head>
<body>
<div id="root">
<h3>名字:</h3>
<ul>
<li v-for="(name,index) in fullName" :key="index">
{{name}}
</li>
</ul>
<h3>爱好:</h3>
<ul>
<li v-for="(h,index) of hobby" :key="index">
{{h}}
</li>
</ul>
</div>
</body>
<script type="text/javascript">
Vue.config.productionTip = false;
const vm = new Vue({
data() {
return {
fullName: {
firstName: '张',
lastName: '三'
},
hobby: ['抽烟', '喝酒', '烫头']
};
}
});
vm.$mount('#root');
let arr = [1, 3, 5];
</script>
</html>
2.5.2 原理分析
官网链接:Vue监测数组的更新改变
如下图,在控制台输出_data 发现,数组元素没有对应的getter 和setter 。
可见,数组里的每个元素不是靠getter和setter来实现数据劫持的,
也就是说,数组里的每个元素都没有对应的getter和setter,
当数组中的元素的值发生改变时,vue不会监测到这次改变,页面也就不会同步更新。
从下图中我们可以看到,JS中原生数组中调用的方法,在数组原型对象上可以找到,
也就是说,当我们调用数组上的方法时,实际上内部会去原型对象上找对应的方法,
调用的是数组原型对象上的方法。
从下面这张图中可以验证上面的结论,可以看出,原生数组上的方法就是数组原型对象上的方法,
但也可以发现,Vue实例管理的数组调用的方法,却不是原生数组的方法。
通过查阅资料发现,Vue实例上的数组调用的方法,实际上是Vue中的方法,
这些方法是被包装(加工)过的,所以和原生数组上的方法不一样,
Vue在其中做了两件事情(以push 方法为例):
- 第一步,还是调用一下
Array 原型上的方法(push ), - 第二步,调用方法,去解析模板和更新页面。
这也就解释了**【2.1 Vue数据更新的问题】**当中的这段代码不奏效的原因。
//更新用户信息
updateUserInfo() {
// this.persons[0].name = '景天'; //奏效(页面同步更新)
// this.persons[0].age = 30; //奏效(页面同步更新)
// this.persons[0].sex = '男'; //奏效(页面同步更新)
this.persons[0] = {id: '001', name: '飞蓬将军', age: 1200, sex: '男'}; //不奏效(页面没有同步更新)
console.log(this.persons[0]);
}
因为Vue 当中没有为数组中的每一个元素提供getter 和setter 方法,Vue 监测不到数组元素值的变化,
所以导致数组中元素值发生改变了,页面也没有同步更新,也就是操作不奏效,
只有调用对应改变数组的方法,页面才会同步更新。
Vue 将被侦听的数组的变更方法进行了包裹(包装,加工),所以它们也将会触发视图更新。
这些被包裹过的方法包括:
push() :往数组最后位置新增一个元素。pop() :删除数组中最后一个元素。shift() :删除数组中的第一个元素。unshift() :往数组第一个位置插入一个元素。splice() :往数组中指定位置插入(或删除,或替换)某个元素。sort() :对数组中的元素进行排序。reverse() :反转数组。
原因弄明白之后,**【2.1 Vue数据更新的问题】**中遗留的问题也就解决了。如下:
//更新用户信息
updateUserInfo() {
// this.persons[0].name = '景天'; //奏效(页面同步更新)
// this.persons[0].age = 30; //奏效(页面同步更新)
// this.persons[0].sex = '男'; //奏效(页面同步更新)
//this.persons[0] = {id: '001', name: '飞蓬将军', age: 1200, sex: '男'}; //不奏效(页面没有同步更新)
/*splice(start,deleteCount,items)
* start: 要替换的元素位置,从0开始
* deleteCount: 替换(删除)的数量
* items: 新元素*/
this.persons.splice(0, 1, {id: '001', name: '飞蓬将军', age: 1200, sex: '男'}); //奏效(页面同步更新)
console.log(this.persons[0]);
}
也可以采用set 方式来解决,如下:
//参数一:要修改的元素,
//参数二:要修改的元素的位置,
//参数三:新元素
Vue.set(this.persons,0,{id: '001', name: '飞蓬将军', age: 1200, sex: '男'}); //奏效
vm.$set(this.persons,0,{id: '001', name: '飞蓬将军', age: 1200, sex: '男'}); //奏效
备注:使用过滤器,将过滤结果重新赋值给数组也会改变数组,触发Vue重新解析模板,更新页面。
2.6 总结Vue数据监测
代码如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8"/>
<title>总结Vue数据监测</title>
<style>
button {
margin-top: 10px;
}
</style>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>s
</head>
<body>
<div id="root">
<h1>学生信息</h1>
<button @click="student.age++">年龄+1岁</button>
<br/>
<button @click="addSex">添加性别属性,默认值:男</button>
<br/>
<button @click="student.sex = '未知' ">修改性别</button>
<br/>
<button @click="addFriend">在列表首位添加一个朋友</button>
<br/>
<button @click="updateFirstFriendName">修改第一个朋友的名字为:张三</button>
<br/>
<button @click="addHobby">添加一个爱好</button>
<br/>
<button @click="updateHobby">修改第一个爱好为:开车</button>
<br/>
<button @click="removeSmoke">过滤掉爱好中的抽烟</button>
<br/>
<h3>姓名:{{student.name}}</h3>
<h3>年龄:{{student.age}}</h3>
<h3 v-if="student.sex">性别:{{student.sex}}</h3>
<h3>爱好:</h3>
<ul>
<li v-for="(h,index) in student.hobby" :key="index">
{{h}}
</li>
</ul>
<h3>朋友们:</h3>
<ul>
<li v-for="(f,index) in student.friends" :key="index">
{{f.name}}--{{f.age}}
</li>
</ul>
</div>
</body>
<script type="text/javascript">
Vue.config.productionTip = false;
const vm = new Vue({
el: '#root',
data: {
student: {
name: 'tom',
age: 18,
hobby: ['抽烟', '喝酒', '烫头'],
friends: [
{name: 'jerry', age: 20},
{name: 'tony', age: 21}
]
}
},
methods: {
addSex() {
this.$set(this.student, 'sex', '男')
},
addFriend() {
this.student.friends.unshift({name: 'jack', age: 70})
},
updateFirstFriendName() {
this.student.friends[0].name = '张三'
},
addHobby() {
this.student.hobby.push('学习')
},
updateHobby() {
this.$set(this.student.hobby, 0, '开车')
},
removeSmoke() {
this.student.hobby = this.student.hobby.filter((h) => {
return h !== '抽烟'
})
}
}
})
</script>
</html>
总结
Vue监视数据的原理
-
Vue 会监视data 中所有层次的数据。 -
如何监测对象中的数据?
- 通过
setter 实现监视,且要在new Vue 时就传入要监测的数据。 - 对象中后追加的属性,
Vue 默认不做响应式处理。 - 如需给后添加的属性做响应式,请使用如下API:
Vue.set(target,propertyName/index,value) vm.$set(target,propertyName/index,value) -
如何监测数组中的数据?
- 通过包裹(包装,加工)数组更新元素的方法实现,本质就是做了两件事:
- 调用原生对应的方法对数组进行更新。
- 重新解析模板,进而更新页面。
- 在
Vue 修改数组中的某个元素一定要用如下方法:
push()、pop()、shift()、unshift()、splice()、sort()、reverse() Vue.set() 或 vm.$set() - 特别注意:Vue.set() 和 vm.$set() 不能给
vm 或 vm的根数据对象(data,_data) 添加属性!!! -
使用过滤器,将过滤结果重新赋值给数组也会改变数组,触发Vue重新解析模板,更新页面。
3、Vue生命周期
3.1 引出生命周期
需求:文字从清晰变透明,透明度逐渐变化,透明度小于或等于0时,重新变为1。
代码如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8"/>
<title>引出生命周期</title>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
</head>
<body>
<div id="root">
<h2 v-if="a">你好啊</h2>
<h2 :style="{opacity}">欢迎学习Vue</h2>
</div>
</body>
<script type="text/javascript">
Vue.config.productionTip = false;
new Vue({
el: '#root',
data: {
a: false,
opacity: 1
},
methods: {},
mounted() {
console.log('mounted', this);
setInterval(() => {
this.opacity -= 0.01;
if (this.opacity <= 0) {
this.opacity = 1
}
}, 16)
},
})
</script>
</html>
总结
生命周期:
- 也叫:生命周期回调函数、生命周期函数、生命周期钩子。
- 是什么:
Vue 在关键时刻帮我们调用的一些特殊名称的函数。 - 生命周期函数的名字不可更改,但函数的具体内容是程序员根据需求编写的。
- 生命周期函数中的
this 指向是 vm 或 组件实例对象。
3.2 分析生命周期
如图:
代码如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8"/>
<title>分析生命周期</title>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
</head>
<body>
<div id="root">
<h2 v-text="n"></h2>
<h2>当前的n值是:{{n}}</h2>
<button @click="add">点我n+1</button>
<button @click="bye">点我销毁vm</button>
</div>
</body>
<script type="text/javascript">
Vue.config.productionTip = false;
new Vue({
el: '#root',
data: {
n: 1
},
methods: {
add() {
console.log('add');
this.n++;
},
bye() {
console.log('bye');
this.$destroy();
}
},
watch: {
n() {
console.log('n变了');
}
},
beforeCreate() {
console.log('beforeCreate');
},
created() {
console.log('created');
},
beforeMount() {
console.log('beforeMount');
},
mounted() {
console.log('mounted');
},
beforeUpdate() {
console.log('beforeUpdate');
},
updated() {
console.log('updated');
},
beforeDestroy() {
console.log('beforeDestroy');
},
destroyed() {
console.log('destroyed');
},
});
</script>
</html>
3.3 总结生命周期
代码如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8"/>
<title>总结生命周期</title>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
</head>
<body>
<div id="root">
<h2 :style="{opacity}">欢迎学习Vue</h2>
<button @click="opacity = 1">透明度设置为1</button>
<button @click="stop">点我停止变换</button>
</div>
</body>
<script type="text/javascript">
Vue.config.productionTip = false;
new Vue({
el: '#root',
data: {
opacity: 1
},
methods: {
stop() {
this.$destroy()
}
},
mounted() {
console.log('mounted', this);
this.timer = setInterval(() => {
console.log('setInterval');
this.opacity -= 0.01;
if (this.opacity <= 0) this.opacity = 1
}, 16)
},
beforeDestroy() {
clearInterval(this.timer);
console.log('vm即将驾鹤西游了')
},
})
</script>
</html>
总结
常用的生命周期钩子:
mounted :发送ajax 请求、启动定时器、绑定自定义事件、订阅消息等【初始化操作】。beforeDestroy :清除定时器、解绑自定义事件、取消订阅消息等【收尾工作】。- 关于销毁
Vue 实例:
- 销毁后借助
Vue 开发者工具看不到任何信息。 - 销毁后自定义事件会失效,但原生DOM事件依然有效。
- 一般不会在
beforeDestroy 操作数据,因为即便操作数据,也不会再触发更新流程了。
4、结语
到此,Vue的核心内容差不多学习完了,接下来就是组件的内容了,如果这篇博客对你有帮助,欢迎点赞,评论,转发,一起进步,谢谢。
|