初始vue
(Ⅰ)重点知识目标
1. Vue的基本语法和数据渲染 2. Vue中常见的指令 3. Vue中常见的修饰符 4. v-bind用法 5. 数据双向绑定v-model 6. v-for实际应用 7. key值的作用
二、本单元知识详讲
1.1 前端框架和Vue简介
什么是框架?为什么有框架?
通过下面两个图的对比我相信,你能很快明白。
通过上图的对比,我们可以发现,明显图一的生产力满足不了,我们发展的需要了,也就是说随着互联网的发展,前端要处理的业务逻辑,不在是单一的数据展示那么简单了。他的交互体验感越来越高,组件化,高内聚,低耦合,等名字不断的进入我们的视线。前端框架就营运而生了。
框架发展历史
2009年,Angular诞生,Google开发的,国内用的非常非常少;
2012年,React诞生,Facebook开发的,国内20%市场份额;
2014年,Vue诞生,尤雨溪开发的,国内80%市场份额。
1.2 vue简介
Vue是一套响应式的 JavaScript 开发框架
Vue官网:http://cn.vuejs.org
起源:
它的作者尤雨溪(Evan You)是一位美籍华人,下面是作者本人对于Vue框架产生的描述:
"Vue 一开始完全是一个个人兴趣项目。2013 年的时候我还在谷歌工作,那时候前端框架还处于比较草莽的阶段,React 刚刚发布还没几个人知道,最成熟的是 AngularJS (Angular 1)。我当时一方面是想自己实现一个简单的框架练练手,另一方面是想尝试一下用 ES5 的 Object.defineProperty 实现数据变动侦测。众所周知 AngularJS 使用的是脏检查,而当时大部分的应用还需要支持 IE8,所以不能全面使用 ES5,而个人项目则不需要考虑这些,(所以Vue项目从一开始就不支持IE8以下版本)Vue 就是这样作为一个实验性质的项目开始的"。
读音:
Vue (读音 /vjuː/,类似于 view) 是一套用于构建用户界面的渐进式JavaScript框架
。简单小巧的核心,渐进式技术栈,足以应付任何规模的应用。 无论是简单还是复杂的界面,Vue 都可以胜任。
-
vue(核心库)
-
vue-router(路由方案)
-
vuex(状态管理方案)
-
构建工具脚手架vite、webpack
-
其他组件库Element Plus(快速搭建页面UI 效果的方案)
-
vue的全家桶
特点:
-
Vue是一个遵循MVVM模式的渐进式框架
-
Vue比较易学,体积更小(30多kb),灵活,高效。
-
Vue的本身只关注UI视图,可以更简单的导入Vue插件和第三方库
-
Vue通过Vue对象把数据和视图完全分离开来,对视图的改变无需在操作DOM元素,只需要操作对应的数据,即可改变对应的视图结构,也就是通过双向数据绑定把View层和Model层连接了起来,通过对数据的操作就可以完成对页面视图的渲染。
1.2.1 Vue版本
-
Vue2(目前主流,2023年年底就不维护了)
-
Vue3(2023年开始会逐步取代Vue2,它的语法兼容Vue2)
我们教学用Vue3演示,但是所有语法都兼容Vue2。有不同的地方老师会额外介绍。
1.2.2 vue初体验
前面我们说了Vue的一些特点,那下面我们来看一下我们如何具体使用Vue呢? 通常情况下,我们学一门语言都会先从浏览器中打印出内容学起,那么我们来看一下,Vue如何打印内容
呢?
使用Vue我们有两种方法,一种方法类似于使用JQuery,我们可以引入Vue.js文件,还有一种方式是借助于npm来安装Vue的脚手架(后期我们会学到),做到真正的前后端分离,我们先来看一下第一种方式。
我们可以通过下面的方式引入Vue的文件
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
下面是官网给我们的一个小例子,我们一起来看一下
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script> <div id="app">{{ message }}</div> <script> const { createApp } = Vue createApp({ data() { return { message: 'Hello Vue!' } } }).mount('#app') </script>
当你打开页面的时候,你会惊奇的发现,页面上竟然显示的是 Hellow Vue! 好像我们全程都没有操作过 dom 节点,文本怎么就显示到页面上呢? ---- 别着急下面课程我们会一点一点的分析。
1.2 设计模式
1.2.1 mvc
MVC是模型-视图-控制器,它是MVC、MVP、MVVM这三者中最早产生的框架,其他两个框架是以它为基础发展而来的。
MVC的目的就是将M和V的代码分离,且MVC是单向通信,必须通过Controller来承上启下。
Model:模型层,数据模型及其业务逻辑,是针对业务模型建立的数据结构,Model与View无关,而与业务有关。
View:视图层,用于与用户实现交互的页面,通常实现数据的输入和输出功能。
Controller:控制器,用于连接Model层和View层,完成Model层和View层的交互。还可以处理页面业务逻辑,它接收并处理来自用户的请求,并将Model返回给用户。
上图可以看出各部分之间的通信是单向的,呈三角形状。
具体MVC框架流程图如图
从上图可以看出,Controller层触发View层时,并不会更新View层中的数据,View层的数据是通过监听Model层数据变化自动更新的,与Controller层无关。换言之,Controller存在的目的是确保M和V的同步,一旦M改变,V应该同步更新。
同时,我们可以看到,MVC框架大部分逻辑都集中在Controller层,代码量也集中在Controller层,这带给Controller层很大压力,而已经有独立处理事件能力的View层却没有用到;而且,Controller层与View层之间是一一对应的,断绝了View层复用的可能,因而产生了很多冗余代码。
MVC 房东 -房客 -中介
1.2.2 mvvm
MVVM是模型-视图-视图模型。MVVM与MVP框架区别在于:MVVM采用双向绑定:View的变动,自动反映在ViewModel,反之亦然。
Model:数据模型(数据处理业务),指的是后端传递的数据。
View:视图,将Model的数据以某种方式展示出来。
ViewModel:视图模型,数据的双向绑定(当Model中的数据发生改变时View就感知到,当View中的数据发生变化时Model也能感知到),是MVVM模式的核心。ViewModel 层把 Model 层和 View 层的数据同步自动化了,解决了 MVP 框架中数据同步比较麻烦的问题,不仅减轻了 ViewModel 层的压力,同时使得数据处理更加方便——只需告诉 View 层展示的数据是 Model 层中的哪一部分即可。
上图可以看出各部分之间的通信是双向的,而且我们可以看出,MVVM框架图和MVP框架图很相似,两者都是从View层开始触发用户的操作,之后经过第三层,最后到达Model层。而关键问题就在于这第三层的内容,Presenter层是采用手动写方法来调用或修改View层和Model层;而ViewModel层双向绑定了View层和Model层,因此,随着View层的数据变化,系统会自动修改Model层的数据,反之同理。
具体MVVM框架流程图如图
从上图可以看出,View层和Model层之间的数据传递经过了ViewModel层,ViewModel层并没有对其进行“手动绑定”,不仅使速度有了一定的提高,代码量也减少很多,相比于MVC框架和MVP框架,MVVM框架有了长足的进步。
从MVVM第一张图可以看出,MVVM框架有大致两个方向:
1、模型-->视图 ——实现方式:数据绑定
2、视图-->模型 ——实现方式:DOM事件监听
存在两个方向都实现的情况,叫做数据的双向绑定。双向数据绑定可以说是一个模板引擎,它会根据数据的变化实时渲染。如图View层和Model层之间的修改都会同步到对方。
MVVM模型中数据绑定方法一般有四种:
数据劫持vue2
原生Proxy vue3
发布-订阅模式
脏值检查
Vue2.js使用的就是数据劫持和发布-订阅模式两种方法。了解Vue.js数据绑定流程前,我们需要了解这三个概念:
Observer:数据监听器,用于监听数据变化,如果数据发生改变,不论是在View层还是在Model层,Observer都会知道,然后告诉Watcher。
Compiler:指定解析器,用于对数据进行解析,之后绑定指定的事件,在这里主要用于更新视图。
Watcher:订阅者。
首先将需要绑定的数据劫持方法找出来,之后用Observer监听这堆数据,如果数据发生变化,Observer就会告诉Watcher,然后Watcher会决定让那个Compiler去做出相应的操作,这样就完成了数据的双向绑定。
vue3.js使用更快的原生 Proxy,消除了之前 Vue2.x 中基于 Object.defineProperty 的实现所存在的很多限制:无法监听 属性的添加和删除、数组索引和长度的变更,并可以支持 Map、Set、WeakMap 和 WeakSet!
带来的特性:
vue3.0实现响应式
Proxy支持监听原生数组
Proxy的获取数据,只会递归到需要获取的层级,不会继续递归
Proxy可以监听数据的手动新增和删除
Proxy对象用于定义基本操作的自定义行为(如属性查找,赋值,枚举,函数调用等)。 其实就是在对目标对象的操作之前提供了拦截,可以对外界的操作进行过滤和改写,修改某些操作的默认行为,这样我们可以不直接操作对象本身,而是通过操作对象的代理对象来间接来操作对象,达到预期的目的~
1.3 插值表达式
别名:
胡子表达式
语法结构:
{{ 变量名 }}
使用方式:
<div id="app"> <h3>{{message}}</h3> <div>{{msg}}</div> </div>
在vue中通过插值表达式定义页面结构
下面我们在来看一下js部分
Vue2的起步,从new Vue() 开始。
<script src="js/vue2.js"></script> <script> new Vue({ // el表示element元素,表示Vue运行的盒子 el: '#app', // 定义数据。每次定义数据默念: // data是个函数,返回一个对象。 data() { return { message: "hellow", msg: "vue" }; } }); </script>
注意:el属性要记住,表示Vue运行的盒子。(也可以理解解析vue代码的作用域)
Vue3不需要new Vue,而是结构出createApp函数,然后mount。
<script src="js/vue3.js"></script> <script> // 解构出createApp函数,表示创建App const { createApp } = Vue; // 创建出一个Vue实例,变量名通常叫做vm。 const vm = createApp({ data() { return { message: "hellow", msg: "vue" } } }); // 让vm运行在指定的div盒子中。 vm.mount('#app'); </script>
1.4 js表达式
Vue 实际上在所有的数据绑定中都支持完整的 JavaScript 表达式。
双大括号能够写简单算式:
<h1>{{ 1 + 4 * 2 }}</h1>
双大括号能够写调用函数:
<h1>{{ parseInt(3.8) + 2 + Math.ceil(3.4) }}</h1>
花板子,能够写IIFE,但是别这么写,就是告诉你有这个能力识别,但是工作中不能这么玩儿。
<h1>{{(function(){return '么么哒'})()}}</h1>
双大括号能够写三元运算符:
<h1>{{ 8 > 3 ? '哈哈' : '么么哒'}}</h1>
不能在双大括号中定义变量、定义函数、使用if语句、使用for……都是不能的。
注意:双大括号只能写在标签对儿中。不能写在HTML属性中。
简单来说,{{}}
极其简单。
1.5 vue指令
什么是指令?
在vue中提供了一些对于页面 + 数据的更为方便的输出,这些操作就叫做指令, 以v-xxx表示 类似于html页面中的属性 <div v-xxx ></div>
指令的作用是什么呢?
指令中封装了一些DOM行为, 结合属性作为一个暗号, 暗号有对应的值,根据不同的值,框架会进行相关DOM操作的绑定
vue中常用的v-指令有那些呢?
-
v-cloak
-
v-text
-
v-html
-
v-pre
-
v-on
-
v-bind
-
v-model
-
v-if
-
v-show
-
v-for
1.5.1 v-cloak
Vue这个包加载好之后,Vue运行之后,会把 v-cloak
标记从HTML标签身上去掉。
用于:隐藏尚未完成编译的 DOM 模板。
<div id="app-container" v-cloak> <h1>好{{xinqing}}啊,我买了{{dongxi}}</h1> </div>
必须配合这个CSS用:
<style> [v-cloak] { display: none; } </style>
当Vue没有加载好,那么HTML标签上有v-cloak
,Vue运行好之后,就去掉了这个v-cloak
直到编译完成前,
<div>
将不可见。
当使用直接在 DOM 中书写的模板时,可能会出现一种叫做“未编译模板闪现”的情况:用户可能先看到的是还没编译完成的双大括号标签,直到挂载的组件将它们替换为实际渲染的内容。
v-cloak
会保留在所绑定的元素上,直到相关组件实例被挂载后才移除。配合像 [v-cloak] { display: none }
这样的 CSS 规则,它可以在组件编译完毕前隐藏原始模板。
1.5.2 v-text、v-html、v-pre
v-html & v-text
v-text:插入纯文本,HTML标签不会被识别,而是原样呈现;
v-html:插入HTML代码,HTML标签会被识别。
<h1 v-text="m"></h1> <h1 v-html="m"></h1>
createApp({ data() { return { m: '<i>燕子,你别走!</i>' } } });
应用场景:
比如后端发来的就是HTML代码,那么需要使用v-html渲染,前端通过v-html渲染字符串dom结构
特别注意:
在网站上动态渲染任意 HTML 是非常危险的,因为这非常容易造成 XSS 漏洞。请仅在内容安全可信时再使用
v-html
,并且永远不要使用用户提供的 HTML 内容(script也属于HTML内容)。
1.5.3 v-pre
元素内具有 v-pre
,所有 Vue 模板语法都会被保留并按原样渲染。最常见的用例就是显示原始双大括号标签及内容。
不再解析双大括号模板。
<div v-pre>{{ rawHTML }}</div>
1.5.4 v-on
v-on的简写形式@
事件监听现在是直接写在HTML标签上的,我们是声明式了。
<!-- 事件监听 --> <!-- 冒号后面是修饰符 --> <button v-on:click="a++">按我</button> <!-- v-on: 可以简写为@ --> <button @click="a++">按我</button> <!-- 鼠标进入 --> <button @mouseenter="a++">鼠标进入我</button> <!-- 双击 --> <button @dblclick="a++">双击我</button>
加上 .stop
可以阻止冒泡:
<div @click="a++" style="background: pink"> <!-- .stop表示阻止事件继续冒泡,就是原生JS中的e.stopPropagation() --> <button @click.stop="a++">按我</button> </div>
加上 .enter
表示按下回车,结合 @keydown
使用。
<!-- 这个文本框中只有按回车能够触发 --> <input type="text" @keydown.enter="a++">
加上 .prevent
表示阻止这个标签的默认事件,比如a的超级链接:
<!-- 这个a标签现在被阻止了默认事件 --> <a href="http://www.sohu.com" @click.prevent="a++">啊啊啊</a>
加上 .once
只能按一次:
<!-- 这个按钮只能按一次 --> <button @click.once="a++">按我</button>
看看手册:内置指令 | Vue.js
1.5.5 v-bind
属性绑定
我们希望让HTML属性,比如src变为动态的。
错误的:
<img src="images/{{a}}.jpg" />
属性中不能用双大括号。
正确:
<img :src="'images/' + a + '.jpg'" alt=""> <img :src="`images/${a}.jpg`" alt="">
全称是 v-bind:
指令。
<img v-bind:src="'images/' + a + '.jpg'" alt="">
等价于
<img :src="'images/' + a + '.jpg'" alt="">
总结梳理:
v-bind
简写为 :
v-on
简写为 @
超级链接的href也可以加冒号,变为动态:
<a :href="link_url">{{link_word}}</a>
表单元素的value属性也能够加冒号:
<input type="text" :value="b">
这里有一个小例子:图片轮播图
类名绑定
class前面加冒号,值就是对象。对象的键名就是类名,对象键的值就是它有没有这个类名。
下面这个h1标签,现在有da、xie两个类名。
<h1 :class="{da: true, hong: false, xie: true}">哈哈哈哈</h1>
Vue允许我们同时有动态class、和死class:
<h1 class="box" :class="{da: true, hong: false, xie: true}">哈哈哈</h1>
上面这个h1有box、da、xie三个类名。
也可以写成数组:
<h1 :class="['da', 'hong']">哈哈哈哈</h1>
上面这个h1有da、hong两个类名。
数组中的项也可以是对象:
<!-- 数组中的项也可以是对象 --> <h1 class="box" :class="['da', {hong: 3 > 8, xie: 8 < 10}]">哈哈哈哈</h1>
上面这个h1有box、da、xie三个类名。
样式绑定
比如,我们希望data中的a是字号,错误写法:
<h1 style="font-size:{{a}}px">哈哈哈哈哈哈</h1>
正确写法:
<h1 :style="{fontSize: a + 'px'}"> 哈哈哈哈哈哈 </h1>
冒号style里面是对象。对象的键名就是CSS属性名,要记得驼峰。值就是斩断链接,变量拼接写法。
再来一个例子:
<div class="box" :style="{transform: `rotate(${b}deg)`}"></div>
总结一下v-bind
指令:
普通属性: <img :src="'images/' + a + '.jpg'" /> <img :src="`images/${a}.jpg`" /> 类名: <p :class="{da: true, xie: m, hong: false}" ></p> <p :class="['da', 'xie']" ></p> <p :class="['da', 'xie', {hong: false}]" ></p> <p :class="['da', 'xie']" class="box"></p> 样式: <p :style="{fontSize: `${a}px`}"></p>
1.5.6 v-model双向绑定
什么是双向绑定
v-model
指令叫做“双向绑定”。
model是英语中“模型”,
v-model是两个方向:
① 数据的值就是表单元素的值;
② 当用户更改表单元素的时候,数据的值也变化。
所以,底层就是 :value
和 @input
事件:
<input type="range" min="0" max="100" :value="a" @input="a=$event.target.value">
各种表单元素的双向绑定
单行文本、多行文本是与字符串进行双向绑定的。
<p> 用户名: <input type="text" v-model="username"> </p> <p> 留言: <textarea v-model="liuyan"></textarea> </p>
数据:
data() { return { username: '', liuyan: '' }; }
单选按钮,绑定到字符串上,每个单选按钮都要进行v-model
<p> 性别: <!-- 把字和单选框用label包裹起来,那么点字的时候也能触发单选框 --> <label> <input type="radio" value="男" name="sex" v-model="sex"> 男 </label> <label> <input type="radio" value="女" name="sex" v-model="sex"> 女 </label> <label> <input type="radio" value="保密" name="sex" v-model="sex"> 保密 </label> </p>
JS:
data() { return { sex: '女' }; }
复选框一般要双向绑定到数组上:
<p> 爱好: <label> <input type="checkbox" value="编程" v-model="hobbies"> 编程 </label> <label> <input type="checkbox" value="游泳" v-model="hobbies"> 游泳 </label> <label> <input type="checkbox" value="乒乓球" v-model="hobbies"> 乒乓球 </label> <label> <input type="checkbox" value="羽毛球" v-model="hobbies"> 羽毛球 </label> </p>
数据:
data() { return { hobbies: ['游泳', '乒乓球'] }; }
复选框也可以绑定到布尔值上:
<input type="checkbox" v-model="yidu"> 我已经阅读了本协议
数据:
data() { return { yidu: false }; }
下拉菜单也绑定到字符串上:
<!-- 下拉菜单也绑定到字符串上 --> <select v-model="nianji"> <option value="">请选择年级</option> <option value="初一">初一</option> <option value="初二">初二</option> <option value="初三">初三</option> </select>
数据:
data() { return { nianji: '' }; }
双向绑定的修饰符
.lazy修饰符表示当失去焦点时再改变数据
<!-- lazy修饰符表示当失去焦点时再改变数据 --> <input type="text" v-model.lazy="a">
也就是说,通常 v-model
相当于 @input
事件。加上 .lazy
之后就是 @change
事件。
.number修饰符,那么双向绑定一定是数字,不是字符串
<!-- 加上.number修饰符,那么双向绑定一定是数字,不是字符串 --> <p> <input type="text" v-model.number="b"> </p> <p> <input type="text" v-model.number="c"> </p> <p> {{b + c}} </p>
.trim能够去掉前后空格
<!-- trim能够去掉前后空格 --> <input type="text" v-model.trim="d"> <p> 【{{d}}】 </p>
小案例:只能选3个
更加理解了声明式编程,HTML标签上有程序,:disabled
表达式可要花时间想想了。
<p> 爱好: <label> <input type="checkbox" value="编程" v-model="hobbies" :disabled="hobbies.length >= 3 && !hobbies.includes('编程')"> 编程 </label> <label> <input type="checkbox" value="游泳" v-model="hobbies" :disabled="hobbies.length >= 3 && !hobbies.includes('游泳')"> 游泳 </label> <label> <input type="checkbox" value="乒乓球" v-model="hobbies" :disabled="hobbies.length >= 3 && !hobbies.includes('乒乓球')"> 乒乓球 </label> <label> <input type="checkbox" value="羽毛球" v-model="hobbies" :disabled="hobbies.length >= 3 && !hobbies.includes('羽毛球')"> 羽毛球 </label> <label> <input type="checkbox" value="蓝球" v-model="hobbies" :disabled="hobbies.length >= 3 && !hobbies.includes('蓝球')"> 蓝球 </label> <label> <input type="checkbox" value="唱歌" v-model="hobbies" :disabled="hobbies.length >= 3 && !hobbies.includes('唱歌')"> 唱歌 </label> <label> <input type="checkbox" value="RAP" v-model="hobbies" :disabled="hobbies.length >= 3 && !hobbies.includes('RAP')"> RAP </label> </p>
1.5.7 v-if 和 v-show条件渲染指令
v-if 和 v-show 都是条件渲染指令,有本质不同:
-
v-if 决定元素上下树,有这个HTML标签、没有这个HTML标签
-
v-show 决定元素显示和隐藏,
display: block ;
和display: none
<div id="app"> <h1 v-show="false">AAAAAAAAA</h1> <h1 v-if="false">BBBBBBBBBBBBBB</h1> </div>
如果简单的做DOM特效,那么就用v-show,不用引起页面的回流,性能更好。(后面详细讲解repaint、reflow)。如果要做频繁切换的DOM就用v-show。
v-if 是惰性的:如果在初次渲染时条件值为 false,则不会做任何事。条件区块只有当条件首次变为 true 时才被渲染。
所以,工作常用来抑制初始 undefined 错误:
<!-- 这里是工作中最常见的v-if的使用场景:抑制错误 --> <!-- obj是服务器返回的数据,这个数据天生是undefined --> <!-- 而undefined不能打点,就会报错 --> <!-- v-if是惰性的,当它第一次为true时,才执行里面的双大括号 --> <div v-if="obj != undefined"> <h1>姓名:{{obj.name}}</h1> <h1>年龄:{{obj.age}}</h1> </div>
v-if
可以结合 v-else
使用:
<div v-if="obj != undefined"> <h1>姓名:{{obj.name}}</h1> <h1>年龄:{{obj.age}}</h1> </div> <div v-else> 数据还没有请求回来 </div>
而 v-show
不能结合 v-else
使用。
甚至可以结合v-else-if使用:
<!-- 跳楼现象 --> <p v-if="fenhu >= 85">优秀</p> <p v-else-if="fenhu > 70">良好</p> <p v-else-if="fenhu > 60">及格</p> <p v-else>不及格</p>
v-if能够触发子组件的全部生命周期。
1.5.8 v-for 列表渲染
语法要会:
<ul> <li v-for="(item,index) in hobby" :key="index"> <input type="checkbox" :value="item">{{item}} </li> </ul>
数据中要准备数组:
data() { return { hobby: ['睡觉', '吃饭', '打豆豆', '编程', '游戏', '篮球'] }; }
index表示下标:下标是从0开始的。
<ul> <li v-for="(item,index) in hobby" :key="index"> {{index}} -- <input type="checkbox" :value="item">{{item}} </li> </ul>
key值的作用
列表渲染需要添加key值,对于多层嵌套的 v-for,作用域的工作方式和函数的作用域很类似
值类型:number | string 唯一 有相同父元素的子元素必须有独特的 key,重复的 key 会造成渲染错误,key应唯一。
key为:index
index作为key不建议将数组的index索引作为key值,能用,但是对性能不太友好。如:
<ul> <li v-for="(item,index) in hobby" :key="index"> {{index}} -- <input type="checkbox" :value="item">{{item}} </li> </ul> <script> createApp({ data() { return { hobby: ['睡觉', '吃饭', '打豆豆', '编程', '游戏', '篮球'] } } }).mount('#app'); </script>
-
原来页面元素的样子:
<--html--> <--key--> <li>0 -- <input type="checkbox" value="睡觉">睡觉</li> 0 <li>1 -- <input type="checkbox" value="吃饭">吃饭</li> 1 <li>2 -- <input type="checkbox" value="打豆豆">打豆豆</li> 2 <li>3 -- <input type="checkbox" value="编程">编程</li> 3 <li>4 -- <input type="checkbox" value="游戏">游戏</li> 4 <li>5 -- <input type="checkbox" value="篮球">篮球</li> 5
当我们在给爱好的数组前面在添加一条数据时,你会发现变成下面这样了:
<--html--> <--key--> <li>0 -- <input type="checkbox" value="看电影">看电影</li> 也就是key="0" 的元素由 睡觉 变成了 看电影 <li>1 -- <input type="checkbox" value="睡觉">睡觉</li> 1 <li>2 -- <input type="checkbox" value="吃饭">吃饭</li> 2 <li>3 -- <input type="checkbox" value="打豆豆">打豆豆</li> 3 <li>4 -- <input type="checkbox" value="编程">编程</li> 4 <li>5 -- <input type="checkbox" value="游戏">游戏</li> 5 <li>6 -- <input type="checkbox" value="篮球">篮球</li> 6
-
key没变的话只是会移动位置,不会删除重新生成。
key 不仅在v-for中使用
-
如一个例子:一个输入框和点击显示隐藏按钮,当在输入框内输入内容后点击隐藏,输入的内容还在,复用了。
-
加上key,强制替换元素,而不是重复使用它:
<ul id="app"> <button @click="show = !show">{{ show ? '显示' : '隐藏'}}</button> <input type="text" v-if="show" key="a" /> <input type="text" v-else key="b" /> </ul> <script> createApp({ data(){ return { show: true } } }) </script>
为什么需要key
为什么需要这个 key 属性呢,其实和 Vue 的虚拟 DOM 的 Diff 算法有关系,通过一张图来简单说明一下:
当某一层有很多相同的节点时,也就是列表节点时,我们希望插入一个新的节点
我们希望可以在 B 和 C 之间加一个 F,Diff 算法默认执行起来是这样的:
即:把 C 更新成 F,D 更新成 C,E 更新成 D,最后再插入 E:
是不是很没有效率?
所以我们需要使用 key 来给每个节点做一个唯一标识,Diff 算法就可以正确的识别此节点,找到正确的位置区插入新的节点,所以,key 的作用主要是为了高效的更新虚拟 DOM
下图是我们通过 key为 index 写的一个案例,注意看下面的错误:
通过上图我们不难发现,本来的是选中的睡觉这个爱好,但是当我在最前面新增了一个之后,发现看书这个爱好被勾选了,而我们之前勾选的睡觉这个爱好反而没了。这就是我们使用index作为key值可能出现的bug.
v-if 与 v-for
v-if 与 v-for 同时存在于一个元素上,会发生什么?
12_vue3_if_for.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>12_vue3的v-if与v-for优先级</title> </head> <body> <div id="app"> <ul> <li v-for="(item, index) of arr" :key="index" v-if="flag"> {{ item }} </li> </ul> </div> </body> <script src="lib/vue.global.js"></script> <script> Vue.createApp({ data () { return { arr: ['a', 'b', 'c', 'd'], flag: false } } }).mount('#app') </script> </html>
13_vue2_if_for.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>13_vue2的v-if与v-for优先级</title> </head> <body> <div id="app"> <ul> <li v-for="(item, index) of arr" :key="index" v-if="flag"> {{ item }} </li> </ul> </div> </body> <script src="lib/vue.js"></script> <script> new Vue({ data: { arr: ['a', 'b', 'c', 'd'], flag: false } }).$mount('#app') </script> </html>通过
审查元素
得知:vue3中,v-if的优先级高于v-for
vue2中,v-for的优先级高于v-if
1.6 响应式原理
vue2的defineProperty
当你把一个普通的 JavaScript 对象传入 Vue 实例作为 data
选项,Vue 将遍历此对象所有的 property,并使用 Object.defineProperty
把这些 property
全部转为 getter/setter
。
这些 getter/setter
对用户来说是不可见的,但是在内部它们让 Vue 能够追踪依赖,在 property 被访问和修改时通知变更。
// 如何知道一个对象的属性值发生变化了? // JavaScript提供了原生的能力,数据劫持 // 方法1:Object.defineProperty()。就是Vue2的底层原理。 // 方法2:Proxy代理,ES6新增的技术。就是Vue3的底层原理。 // 现在演示方法1:Object.defineProperty() let obj = { a: 123 }; // 让a变为响应式的 defineReactive(obj, 'a'); // 让数据变为响应式 function defineReactive(obj, keyname) { let temp = obj[keyname]; // 数据劫持 Object.defineProperty(obj, 'a', { // getter get: function() { console.log('试图读取'); return temp; }, // setter set: function(v){ console.log('试图设置'); temp = v; } }); }
vue3的Proxy
vue 3是通过proxy直接代理整个对象来实现的,而不是像Object.defineProperty针对某个属性。所以,只需做一层代理就可以监听同级结构下的所有属性变化,包括新增属性和删除属性。
// 现在演示Proxy代理,是Vue3的底层 let obj = { a: 123 }; obj2 = new Proxy(obj, { // getter get(target, keyname) { console.log('getter'); return target[keyname]; }, // setter set(target, keyname, v) { console.log('setter'); target[keyname] = v; } });
Proxy非常好用,可以直接侦测到对象的所有属性、属性的属性变化。但是Object.defineProperty()是不行的。小思考题:递归实现Object.defineProperty()。能够检测所有属性变化、属性的属性的变化。