理解Vue的概念以及作用
大部分教程会选择在这个地方开始搭建环境,但是我希望在这里多介绍一些我对于框架的理解,个人觉得框架本身就是工具,理解其优势以及作用对于我们对于项目技术选型更有优势,以下仅个人理解,如有不同的想法欢迎指正。
Vue框架概念
Vue是一款渐进式框架,起初学习的时候我对于渐进式理解并不深刻,而后在学习完node的egg后我才对渐进式有有一定的概念,渐进式框架在我的理解中了类似于分包化的框架,你不必一来便下载关于Vue的所有生态链第三方包,你可以根据你企业级项目的不同的业务需求来添加组成你自己的企业级上层应用级框架,你也可以根据基础的底层框架配合自己的二次开发制作属于自己的高级的脚手架工具构建自己的企业级框架使用,Vue的核心库是一款基于MVVM设计模式的仅关注图层的库,在这之上你可以添加路由,Vuex进行共享数据管理等,大部分功能类似模块化可以由你自行选配。
对比其余框架
我们所知的三大前端主流框架分别有AngularJS,Vue,React,Vue是早起是参考AngularJS制作的,很多语法糖两者是类似的(例如 v-if vs ng-if),但是AngularJS中存在许多问题在Vue中得到了解决,所以现代戏化的网站开发已经很少使用AngularJS了,此处就不多做比较,我们主要比较Vue与React,后续部分没有基础的同学可能阅读有一定困难,但是你仅需理解一个概念即可,在之后的篇幅都会详细介绍(主要比较的是Vue2.X版本与React16版本之前,Vue3与react16版本之后都做了较大的更新后面有可能会再做介绍)
Vue对比React
好看的大致相通,丑的千奇百怪,两个组件也有很多相通之处:
- 使用 虚拟 DOM
- 提供了响应式 (Reactive) 和组件化 (Composable) 的视图组件。
- 将注意力集中保持在核心库,而将其他功能如路由和全局状态管理交给相关的库。 不可否认React拥有更丰富的生态系统,在国外以及一些知名大厂中使用React的占比确实会更大,但在国内大部分公司Vue才是主流,笔者两种框架都有实践过,个人觉得做web端开发的话Vue比其React更简洁易上手,选择框架不仅仅与业务相关也需要考虑到研发组的技术栈,Vue在国内的知名度明显比React更高,同时大部分的后端与前端程序员对React知之甚少,并且React对于js功底要求更高,故国内大部分项目都是使用的Vue开发的。
Css&Html
在 React 中,一切都是 JavaScript。不仅仅是 HTML 可以用 JSX 来表达,现在的潮流也越来越多地将 CSS 也纳入到 JavaScript 中来处理。这类方案有其优点,但也存在一些不是每个开发者都能接受的取舍。
Vue 的整体思想是拥抱经典的 Web 技术,并在其上进行扩展。我们下面会详细分析一下。
JSX vs Templates
在 React 中,所有的组件的渲染功能都依靠 JSX。JSX 是使用 XML 语法编写 JavaScript 的一种语法糖。
使用 JSX 的渲染函数有下面这些优势:
- 你可以使用完整的编程语言 JavaScript 功能来构建你的视图页面。比如你可以使用临时变量、JS 自带的流程控制、以及直接引用当前 JS 作用域中的值等等。
- 开发工具对 JSX 的支持相比于现有可用的其他 Vue 模板还是比较先进的 (比如,linting、类型检查、编辑器的自动完成)。
事实上 Vue 也提供了渲染函数,甚至支持 JSX。然而,我们默认推荐的还是模板。任何合乎规范的 HTML 都是合法的 Vue 模板,这也带来了一些特有的优势:
- 对于很多习惯了 HTML 的开发者来说,模板比起 JSX 读写起来更自然。这里当然有主观偏好的成分,但如果这种区别会导致开发效率的提升,那么它就有客观的价值存在。
- 基于 HTML 的模板使得将已有的应用逐步迁移到 Vue 更为容易。
- 这也使得设计师和新人开发者更容易理解和参与到项目中。
- 你甚至可以使用其他模板预处理器,比如 Pug 来书写 Vue 的模板。
有些开发者认为模板意味着需要学习额外的 DSL (Domain-Specific Language 领域特定语言) 才能进行开发——我们认为这种区别是比较肤浅的。首先,JSX 并不是没有学习成本的——它是基于 JS 之上的一套额外语法。同时,正如同熟悉 JS 的人学习 JSX 会很容易一样,熟悉 HTML 的人学习 Vue 的模板语法也是很容易的。最后,DSL 的存在使得我们可以让开发者用更少的代码做更多的事,比如 v-on 的各种修饰符,在 JSX 中实现对应的功能会需要多得多的代码。
更抽象一点来看,我们可以把组件区分为两类:一类是偏视图表现的 (presentational),一类则是偏逻辑的 (logical)。我们推荐在前者中使用模板,在后者中使用 JSX 或渲染函数。这两类组件的比例会根据应用类型的不同有所变化,但整体来说我们发现表现类的组件远远多于逻辑类组件。
组件作用域内的Css
当我们组件变多的时候不可避免的可能会出现样式命名重复,导致的样式重叠的可能,我们大多数情况下都我们在组件中写的样式会扩散到其他组件,这时候就要做样式作用域处理,两者都有相应的解决方案,但是不尽相同
React中的做法通常是会引入一些专业的第三方包来处理,例如(例如 CSS Modules)
,使用CSS Modules我们通常会做以下几步
- 创建一个xxx.modelue.scss的文件(内部以一个root的根元素包裹后续所有样式)
- 在js文件中引入该样式文件并命名为styles
- 在jsx中的根标签上的类名命名为{styles.root}
原理本质上就是对css的类名重命名,让你的类名保证不重复给你的类名加一个随机的hash值,这显然是很复杂的且麻烦的
在Vue则简洁很多仅需要在style 标签中 添加一个scoped即可实现仅作用于该组件的样式文件
<style scoped>
/* 这里写样式 */
</style>
MVC对比MVVM
- React是一款MCV框架
这里通常体现在当我们数据发生改变的时候我们通常需要手动的调用
this.setState()来更新状态,即数据的更新并不会直接触发视图的更新,只有当调用setState的时候才会更新Dom,这在我们写一个输入框的时候就会变得很麻烦,这种组件我们通常称之为受控组件,这类组件我们需要监听用户的动作导致的改变,例如输入框我们通常要绑定输入改变事件,以及显示的值,我们会将用户输入的值赋值给我们要显示的那个字段以达到用户输入了什么输入框显示什么并记录下输入值的效果 - Vue则是MVVM框架
MVVM在更新状态以及对于受控组件有着天然的优势,Vue中数据更新就会直接触发视图的更新,无需多余调用
this.setState()等方法来更新Dom,在受控组件中 我们也只需要一个Vue指令v-model即可实现数据以及视图双向绑定,任何一方的修改都会厨房另一方的修改,非常方便
安装浏览器插件以及vsCode插件
我们通常建议使用谷歌浏览器或者edge浏览器来进行开发调试,两者都有提供vue-devtools插件,谷歌浏览器需要梯子或者下载一些可以访问谷歌访问助手,不会或者懒得做的可以直接使用edge浏览器,现在做的还不错,商店不要梯子就可以访问,直接搜索下载即可
vsCode 安装一定的插件能让你的开发过程如虎添翼,建议安装Veter(Vue代码高亮插件)以及VueHelper(Vue代码提示插件)
Vue指令
接下来我们会开始介绍Vue的指令,这是Vue框架提供的一系列的语法糖,为你封装好的方法,以处理大多数前端场景的时候节省你大部分的精力耗费,接下来推荐在之前01中的实现的vue基础结构的app.vue中复制我最先的结构测试使用,多敲代码更有助于理解
插值表达式
<template>
<div>
<h1>{{ msg }}</h1>
<h2>{{ obj.name }}</h2>
<h3>{{ obj.age > 18 ? '成年' : '未成年' }}</h3>
</div>
</template>
<script>
export default {
data() { // 格式固定, 定义vue数据之处
return { // key相当于变量名
msg: "hello, vue",
obj: {
name: "vue",
age: 8
}
}
}
}
</script>
<style>
</style>
这种语法又称为又叫声明式渲染/文本插值,语法格式为:
{{ data中的一个变量名 }}
我们可以看到Vue用一个对象包裹了一个data方法,而这个方法返回了我们定义的数据,Vue会获取其中的数据,当template也就是虚拟Dom中出现插值表达式的时候会将data中对于的值赋值给当前插值表达式的位置
注意:
{{}}中可以
-
写data中对于的数据字段名称
-
对data中数据字段进行的表达式运算包括(拼接,算术运算,三元运算) {{}}中哒咩
-
声明变量,分支,循环
-
访问该vue示例data中已定义data之外的变量 以上代码实现效果如图
v-bind动态属性
<template>
<div>
<img :src="src" />
<a :href="href">个人主页</a>
</div>
</template>
<script>
export default {
data() {
return {
src: "http://tukuimg.bdstatic.com/scrop/603c1c68c48333f64ddaa7ad26e1ac68.gif",
href: "https://juejin.cn/user/4143424364089175",
};
},
};
</script>
<style></style>
- 语法:
v-bind:属性名="vue变量" - 简写:
:属性名="vue变量"这个指令可以为任意html标签绑定一个动态的属性,我们后续还可以绑定一些事件对这个属性修改
动态class
<template>
<div>
<p :class="{ red: isRed }">动态class</p>
<!-- 布尔值为true的时候那么这个类名就生效否则就不生效 -->
<button @click="changeColor">修改颜色</button>
</div>
</template>
<script>
export default {
data() {
return {
isRed: true,
};
},
methods: {
changeColor() {
this.isRed = !this.isRed;
},
},
};
</script>
<style scoped>
.red {
color: red;
}
</style>
-
语法:
- :class="{类名: 布尔值}" 将类名保存在vue变量中赋值给标签,当我们做组件多元化的时候会常用到
动态style
<template>
<div>
<p :style="{ color: pColor }">动态style</p>
<!-- 布尔值为true的时候那么这个类名就生效否则就不生效 -->
<input type="text" name="" id="" v-model="pColor" />
</div>
</template>
<script>
export default {
data() {
return {
pColor: "blue",
};
},
methods: {},
};
</script>
<style scoped>
.red {
color: red;
}
</style>
-
语法
- :style="{css属性: 值}" 注意动态style的key都是css属性名
v-on绑定事件
<template>
<div>
<div>当前点赞数: {{ good }}</div>
<div>当前转发数 {{ forward }}</div>
<button @click="goodAdd">点赞</button>
<button v-on:click="forwardAdd">转发</button>
</div>
</template>
<script>
export default {
data() {
return {
forward: 0,
good: 0,
};
},
methods: {
goodAdd() {
this.good++;
},
forwardAdd() {
this.forward++;
},
},
};
</script>
<style></style>
-
语法
- v-on:事件名="要执行的==少量代码=="
- v-on:事件名="methods中的函数"
- v-on:事件名="methods中的函数(实参)"
-
简写: @事件名="methods中的函数" 这里给button绑定了一个点击事件 当我们点击点赞的时候 展示的点赞数就会+1,@可绑定的事件很多,详细可以自己百度,这里不多做介绍
v-on事件对象
<template>
<div>
<a @click="one" href="http://www.baidu.com">阻止百度</a>
<hr>
<a @click="two(10, $event)" href="http://www.baidu.com">阻止去百度</a>
</div>
</template>
<script>
export default {
methods: {
one(e){
e.preventDefault()
},
two(num, e){
e.preventDefault()
}
}
}
</script>
我们可以通过拿到事件对象的形式阻止标签的默认行为 比如a标签点击就会触发跳转,当我们阻止了后跳转就不会进行本来的默认跳转行为了
v-on修饰符
<template>
<div @click="fatherClick">
<!-- vue对事件进行了修饰符设置, 在事件后面.修饰符名即可使用更多的功能 -->
<button @click.stop="btn">.stop阻止事件冒泡</button>
<a href="http://www.baidu.com" @click.prevent="btn">.prevent阻止默认行为</a>
<button @click.once="btn">.once程序运行期间, 只触发一次事件处理函数</button>
<input type="text" @keydown.enter="enterFn" />
<hr />
<input type="text" @keydown.esc="escFn" />
</div>
</template>
<script>
export default {
methods: {
fatherClick() {
console.log("爸爸点击事件被触发");
},
btn() {
console.log("儿子点击事件触发了");
},
enterFn() {
console.log("输入的时候enter了");
},
escFn() {
console.log("输入的时候esc了");
},
},
};
</script>
这里介绍三个基于事件的修饰符事件修饰符可以帮我们快捷开发,不必像上面那样拿到事件对象再阻止更为渐变
-
@事件名.修饰符="函数名"
- .stop - 阻止事件冒泡
- .prevent - 阻止默认行为
- .once - 程序运行期间, 只触发一次事件处理函数
当我们点击触发子标签的click事件的时候通常父页面的click事件也会触发,当我们给一个
.stop将会阻止这种事件冒泡行为,而当我们点击a标签的时候会触发的跳转而可以通过.prevent阻止,而.once则可以使得点击事件仅触发一次 更多修饰符查看更多修饰符
v-model 数据双向绑定
<template>
<div>
<!--
v-model:是让标签值与data中的属性值双向绑定的指令
-->
<div>
<span>用户名:</span>
<input type="text" v-model="username" />
</div>
<div>
<span>密码:</span>
<input type="password" v-model="pass" />
</div>
<div>
<span>我的工作是: </span>
<!-- 下拉菜单要绑定在select上 -->
<select v-model="from">
<option value="北京市">前端</option>
<option value="南京市">后端</option>
<option value="天津市">全栈</option>
</select>
</div>
<div>
<!-- (重要)
遇到复选框, v-model的变量值
非数组 - 关联的是复选框的checked属性
数组 - 关联的是复选框的value属性
-->
<span>爱好: </span>
<input type="checkbox" v-model="hobby" value="抽烟" />点赞
<input type="checkbox" v-model="hobby" value="喝酒" />转发
<input type="checkbox" v-model="hobby" value="写代码" />收藏
</div>
<div>
<span>性别: </span>
<input type="radio" value="男" name="sex" v-model="gender" />男
<input type="radio" value="女" name="sex" v-model="gender" />女
</div>
<div>
<span>评论</span>
<textarea v-model="intro"></textarea>
</div>
</div>
</template>
<script>
export default {
data() {
return {
username: "",
pass: "",
from: "",
hobby: [],
sex: "",
intro: "",
};
// 总结:
// 特别注意: v-model, 在input[checkbox]的多选框状态
// 变量为非数组, 则绑定的是checked的属性(true/false) - 常用于: 单个绑定使用
// 变量为数组, 则绑定的是他们的value属性里的值 - 常用于: 收集勾选了哪些值
},
};
</script>
-
语法: v-model="vue数据变量"
-
双向数据绑定
- 数据变化 -> 视图自动同步
- 视图变化 -> 数据自动同步
-
演示: 用户名绑定 - vue内部是MVVM设计模式
这个指令用于实现数据与视图的双向绑定,是vue很常用的一个指令,在其余的框架中基本上在写输入多选框等组件的时候通常都要进行绑定一个change事件从而实现视图到数据层的修改,在vue中使用一个指令就可以一键实现
v-model修饰符
<template>
<div>
<div>
<span>点赞数量:</span>
<input type="text" v-model.number="good" />
</div>
<div>
<span>评论:</span>
<input type="text" v-model.trim="comment" />
</div>
<div>
<span>草稿:</span>
<textarea v-model.lazy="draft"></textarea>
</div>
</div>
</template>
<script>
export default {
data() {
return {
good: "",
comment: "",
draft: "",
};
},
};
</script>
-
语法:
-
v-model.修饰符="vue数据变量"
- .number 以parseFloat转成数字类型()
- .trim 去除首尾空白字符
- .lazy 在change时触发而非inupt时
-
v-html与v-text标签数据绑定
<template>
<div>
<p v-text="str"></p>
<p v-html="str"></p>
</div>
</template>
<script>
export default {
data() {
return {
str: "<h1>针不戳</h1>",
};
},
};
</script>
这两个指令都是用于标签的数据绑定,不同的是v-text是吧数据当作字符串展示,而v-html将数据作为html解析
-
语法:
- v-text="vue数据变量"
- v-html="vue数据变量"
-
注意: 会覆盖插值表达式
v-show与v-if控制Dom显影
<template>
<div id="box">
<div class="tab">
<p v-if="show">好诗好诗!</p>
<p v-else>我的诗呢?</p>
<button @click="show = !show">
{{ show ? "收起" : "展开" }}
</button>
</div>
<div v-show="show">
<p>我遇见阳光在白昼逃遁 雾霾的强吻让天空窒息</p>
<p>我遇见河流脱离了河床 鱼儿在空气里学习呼吸</p>
<p>我遇见黄叶追逐风的虚无 光秃的树在企图寻找</p>
<p>我遇见幸福在高速路上逆行 所有的堕落都勇往直前</p>
<p>
我遇见爱情用狂草在脊背涂满苦涩的印记 那些字句都似是而非
这就是我每天的遇见
</p>
</div>
</div>
</template>
<script>
export default {
data() {
return {
show: false,
};
},
};
</script>
<style scoped>
#box {
width: 500px;
margin: 20px auto;
background-color: #fff;
border: 4px solid rgb(231, 113, 77);
border-radius: 1em;
box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.5);
padding: 1em 2em 2em;
}
.tab {
width: 500px;
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
}
</style>
两者都是控制显影的指令,不同的是,用的显影方式不同,v-show采用的是css的display:none的方式,意味着隐藏Dom并不会直接删除Dom结构,当我们的元素需要多次修改显影的时候我更推荐使用v-show,v-if采用的是js添加以及删除Dom的方式进行控制显影,如果当一个元素需要仅需要一次性判断是否显影后续很少修改,那么可以采用v-if,并且可以配合v-else使用,当v-else表示当v-if的条件为false的时候则显示.
-
语法:
- v-show="vue变量"
- v-if="vue变量"
-
原理
- v-show 用的display:none隐藏 (频繁切换使用)
- v-if 直接从DOM树上移除
v-for遍历渲染
<template>
<div>
<!-- 语法 v-for(值变量名,索引变量名) in 目标结构
想要谁循环将放在谁的身上
key优化我们的更新的性能 跟diff算法有关
-->
<ul>
<li v-for="(item, index) in arr" :key="index">{{ item }} --- {{ index }}</li>
</ul>
<!-- 遍历数组对象的时候 -->
<p>学生详细信息</p>
<ul>
<li v-for="item in stuArr" :key="item.id">
<h3>{{ item.name }}</h3>
<h3>{{ item.sex }}</h3>
<h3>{{ item.hobby }}</h3>
</li>
</ul>
<!-- v-for="(value,key) in 对象" -->
<!-- 遍历对象的时候 -->
<div class="div" v-for="(value,key) in obj" :key="key">{{key}} ==> {{value}}</div>
<!-- 语法4
v-for ="变量名 in 固定数字"
从1开始循环到置顶的数字 如果是 5 那么将会遍历出 1,2,3,4,5
-->
<div v-for="n in count" :key="n">{{n}}</div>
</div>
</template>
<script>
export default {
data () {
return {
arr: ["点赞", "转发", "收藏"],
stuArr: [
{
name: "张三",
sex: "男",
hobby: "挑战法律",
id: 1
},
{
name: "李四",
sex: "男",
hobby: "没有存在感",
id: 2
},
{
name: "小丽",
sex: "女",
hobby: "打豆豆",
id: 4
},
],
obj: {
name: "张三",
age: 21,
waihao: "法外狂徒"
},
count: 5
};
},
methods: {
showOrhide () {
this.isShow = !this.isShow;
},
},
};
</script>
<style></style>
-
语法
- v-for="(值, 索引) in 目标结构"
- v-for="值 in 目标结构"
-
目标结构:
- 可以遍历数组 / 对象 / 数字 / 字符串 (可遍历结构)
-
注意:
v-for的临时变量名不能用到v-for范围外 该指令作用于遍历变量结构的情况下,是个很常用的指令,前端有很多重复dom结构都可以用这种形式去遍历出来,精简代码,优化代码复用性,这里有个很优化性能的算法就是
diff算法,这种算法在vue与react中都有,在更新dom的时候,框架都会先用虚拟Dom进行比对在对发生更新的Dom节点进行单独更新,而非全体一起更新,优化了更新时候的性能,这在我们dom节点很多的时候尤为明显,需要注意的是遍历必须指定一个key值,key值是必须且唯一的(通常为一个不可重复的字符串或者数字类型,一般选用数组的id作为key)
了解v-for的更新机制
<template>
<div>
<div class="row">
<span class="item" v-for="(val, index) in arr" :key="index">
{{ val }}
</span>
</div>
<button @click="revBtn">两级反转</button>
<button @click="sliceBtn">截取或者重置</button>
<button @click="changeBtn">{{ isChange ? "更新a为i" : "更新i为a" }}</button>
</div>
</template>
<script>
export default {
data() {
return {
arr: ["c", "o", "d", "e", "i", "s", "w", "o", "r", "l", "d"],
isSlice: false,
isChange: false,
};
},
methods: {
revBtn() {
// reverse方法会更改原数组,所以有效
this.arr.reverse();
},
sliceBtn() {
// 数组的slice方法不会改变原始数组而是返回一个新的数组
// 这里我们要初始化一个新的数组重新赋值给原数组
if (!this.isSlice) {
let newArr = this.arr.slice(0, 4);
this.arr = newArr;
this.isSlice = !this.isSlice;
} else {
this.arr = ["c", "o", "d", "e", "i", "s", "w", "o", "r", "l", "d"];
this.isSlice = !this.isSlice;
}
},
changeBtn() {
// 这样更新数组其中一个值的时候数组检测不到
// this.arr[4] = 'a';
// 解决 - this.$set()
// 参数1 更新的数组
// 参数2 更新的数组位置
// 参数3 要更改为的数据
if (this.isChange) {
this.isChange = !this.isChange;
this.$set(this.arr, 4, "i");
} else {
this.isChange = !this.isChange;
this.$set(this.arr, 4, "a");
}
},
},
};
</script>
<style>
.row {
display: flex;
flex-direction: row;
}
.item {
font-size: 32px;
font-weight: bold;
margin-right: 10px;
background-color: pink;
}
</style>
js拥有大量的操作数组的方法,只要是会触发原数组改变的方法就可以直接对数据源使用触发更新,v-for会自动监测并更改,有一部分是返回一个新的数组需要我们用一个变量重新赋值一下来触发更新,当我们需要修改数组中某一个值的时候,我们需要使用this.$set()方法,第一个参数为数据源,第二个参数为修改的index值,第三个参数为要修改为的值。
这些方法会触发数组改变, v-for会监测到并更新页面
push()pop()shift()unshift()splice()sort()reverse()
这些方法不会触发v-for更新
slice()filter()concat()
注意: vue不能监测到数组里赋值的动作而更新, 如果需要请使用Vue.set() 或者this.$set(), 或者覆盖整个数组
总结: 改变原数组的方法才能让v-for更新
走进虚拟DOM与Diff算法
基本概念
我们了解了v-for的更新机制,知道了怎么操作vue数组的更新来驱动视图的更新,那么vue底层又是如何进行渲染更新的呢?
我们需要知道vue与React在更新渲染这方面用的都是虚拟DOM+Diff算法来进行更新渲染的,vue通过v = render(m)的方式来渲染视图的,即视图 = 提供(模型),当数据发生改变的时候,视图更新的方式就是采用重新renderDOM元素,如果采用原生的render方式去更新视图那么整个组件都需要更新,岂不是很浪费性能与时间?这里vue就采用了旧的虚拟DOM与新的虚拟DOM比较的形式,来更新DOM,只有在数据发生改变的情况下才会对改变的地方单独进行重新渲染
何为虚拟DOM?
vue文件中的template内写的标签,都是模板,会被vue处理成为虚拟DOM对象,而后通过vue处理后再显示在真实得DOM页面上 我们在vue中书写的模板
<template>
<div id="box">
<p class="msg">666</p>
</div>
</template>
对应的虚拟DOM结构
const dom = {
type: 'div',
attributes: [{id: 'box'}],
children: {
type: 'p',
attributes: [{class: 'msg'}],
text: '666'
}
}
- vue会根据模板生成一个虚拟dom结构(实质上是个js对象)
- 真实Dom属性有好几百个,无法快速定位有改变的属性 而后的vue数据更新
- 生成新的虚拟DOM结构
- 和旧的虚拟DOM结构对比
- 利用diff算法, 找不不同, 只更新变化的部分(重绘/回流)到页面 - 也叫打补丁 这样做的好处
- 提高了更新DOM的性能(不用把页面全删除重新渲染)
- 虚拟DOM只包含必要的属性(没有真实DOM上百个属性)
走进Diff算法
Diff算法本质就是找不同,那么怎么找也省事就变成了一个学问,这就会涉及到key值的不同我们Diff的比较也会不同\
没有key值或者key值为index的情况
<template>
<div>
<ul>
<li v-for="(val, index) in arr" :key="index">
{{ val }}
<input type="text" />
</li>
</ul>
<button @click="addBtn">中间插入光头强</button>
</div>
</template>
<script>
export default {
data() {
return {
arr: ["熊大", "熊二"],
};
},
methods: {
addBtn() {
this.arr.splice(1, 0, "光头强");
},
},
};
</script>
<style></style>
当绑定的key值为索引值或者没有的时候,更新Dom为原地更新
什么是原地更新,让我们看看这个图
Diff算法是根据key值来进行更新的,会以key值相同的两个虚拟dom进行比对,当我们key值使用的index索引作为key或者不指定key值的时候就会出现如下的问题,因为index的值是会随着数组的增加删除而发生改变的,当我们在中间插入一个光头强的时候,原本属于熊二的key1则会落在插入的光头强节点上,这时候Diff算法会比较新旧Dom key值为1的Dom节点的属性,发现熊二变成光头强了,则把key值为1的Dom节点数据由熊二改为光头强,而后进行key值为2的节点比对,发现没有则重新创建一个key值为2的节点,可以发现这个操作并非最优解,有很多的冗余,而且当这个数组足够长有上千个节点的时候这样在第二个位置插入一个节点将是灾难性的,并且我们在input写入的字段,也出现了错位,这将会引发一些bug,所以我不推荐使用index索引作为key值,key值必须是唯一且不可更改的
key值为唯一不可变值时的情况
<template>
<div>
<ul>
<li v-for="obj in arr" :key="obj.id">
{{ obj.name }}
<input type="text" />
</li>
</ul>
<button @click="addBtn">中间插入光头强</button>
</div>
</template>
<script>
export default {
data() {
return {
arr: [
{
name: "熊大",
id: 1,
},
{
name: "熊二",
id: 2,
},
],
};
},
methods: {
addBtn() {
this.arr.splice(1, 0, {
name: "光头强",
id: 3,
});
},
},
};
</script>
<style></style>
当key为index等可变或者没有的唯一值的时候v-for会尝试原地更新,不会移动Dom,而当key为唯一不可变的的值的时候v-for会尝试移动Dom
这样当我们修改Dom的时候才是性能的最优解,所有我们在使用key的时候最好使用唯一的不可变的值作为key,否则更新Dom的时候性能将大打折扣
总结
v-for什么时候会更新页面呢?
- 数组采用更新方法, 才导致v-for更新页面
vue是如何提高更新性能的?
- 采用虚拟DOM+diff算法提高更新性能
虚拟DOM是什么?
- 本质是保存dom关键信息的JS对象
diff算法如何比较新旧虚拟DOM?
- 根元素改变 – 删除当前DOM树重新建
- 根元素未变, 属性改变 – 更新属性
- 根元素未变, 子元素/内容改变
- 无key – 就地更新 / 有key – 按key比较
本文引入
本文部分vue与其余框架引入了官网的解释对比其他框架 — Vue.js (vuejs.org)
来看看我的其他章节吧,正在长更中
从0到Vue3企业项目实战【01.Vue的基本概念与学习指南】 - 掘金 (juejin.cn)
从0到Vue3企业项目实战【02.了解并理解Vue指令以及虚拟Dom】 - 掘金 (juejin.cn)
从0到Vue3企业项目实战【03.vue基本api入门】 - 掘金 (juejin.cn)
从0到Vue3企业项目实战【04.从vue组件通讯到eventBus以及vuex(附mock接口与axios简单实践)】 - 掘金 (juejin.cn)
从0到Vue3企业项目实战【05.vue生命周期】 - 掘金 (juejin.cn)
从0到Vue3企业项目实战【06.nextTick使用】 - 掘金 (juejin.cn)
从0到Vue3企业项目实战【07.vue动态组件,组件缓存,组件插槽,子组件直接修改props,自定义指令看这一篇就够了】 - 掘金 (juejin.cn)