Vue 学习
1 vue 核心
1.1 什么是 vue?
Vue (读音 /vjuː/,类似于 view) 是一套用于构建用户界面的渐进式框架。与其它大型框架不同的是,Vue 被设计为可以自底向上逐层应用。Vue 的核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合。另一方面,当与现代化的工具链以及各种支持类库结合使用时,Vue 也完全能够为复杂的单页应用提供驱动。
1.2 vue 的特点
- 采用组件化模式,提高代码复用率,且让代码更好维护。
- 声明式编码,让编码人员无需直接操作 DOM,提高开发效率。
- 使用虚拟 DOM + 优秀的 Diff 算法,尽量复用 DOM 节点。
1.3 vue 的特性
- 数据驱动视图
使用了 vue 的页面中,vue 会监听数据的变化,从而自动重新渲染页面的结构。如下图:
好处:当页面数据发生变化时,页面会自动重新渲染!
注意:数据驱动视图是单向的数据绑定。
- 双向数据绑定
在填写表单时,双向数据绑定可以辅助开发者在不操作 DOM 的前提下,自动把用户填写的内容同步到数据源中。
好处:开发者不再需要手动操作 DOM 元素,来获取表单最新的值!
1.4 基本使用
<body>
<!--
1. 想让 vue 工作,就必须创建一个 vue 实例,且要传入一个配置对象;
2. app 容器里的代码依然符合 html 规范,只不过混入了一些特殊的 vue 语法;
3. app 容器里的代码被称为 vue 模板;
4. vue 实例和容器是一一对应的;
5. 真实开发中只会有一个 vue 实例,并且会配合着组件一起使用;
6. {{xxx}} 中的 xxx 要写 js 表达式,且 xxx 可以自动读取到 data 中的所有属性;
7. 一旦 data 中的数据发生改变,那么页面中用到该数据的地方也会自动更新。
-->
<!-- 1.声明要被 vue 所控制的 DOM 区域(容器) -->
<div id="app">
hello,{{name}}
</div>
<!-- 2.导入 vue.js 的 script 脚本文件 -->
<script src="./js/vue.js"></script>
<script>
Vue.config.productionTip = false // 阻止 vue 在启动时生成生产提示
// 3.创建 vue 实例对象
const vm = new Vue({
el: '#app',
data: {
name: '张三'
}
})
</script>
</body>
1.5 模板语法
1.5.1 插值语法
作用:用于解析标签体内容。
语法:{{xxx}}
,xxx 是 js 表达式,且可以直接读取到 data 中的所有属性。
1.5.2 指令语法
作用:用于解析标签(包括:标签属性、标签体内容、绑定事件…)。
举例:v-bind:href="xxx"
或简写为:herf="xxx"
,xxx同样要写 js 表达式,且可以直接读取到 data 中的所有属性。
vue 中的其他指令都是以
v-
开头。
1.6 数据绑定
1.6.1 单向绑定(v-bind)
数据只能从 data 流向页面。
1.6.2 双向绑定(v-model)
数据不仅能从 data 流向页面,还可以从页面流向 data。
- 双向绑定一般都应用在表单类元素上(如:input、select等);
v-model:value
可以简写为 v-model,因为 v-model 默认收集的就是 value 值。
1.7 el 和 data 的两种写法
1.7.1 el 的两种写法
Vue.config.productionTip = false // 阻止 vue 在启动时生成生产提示
// 3.创建 vue 实例对象
const vm = new Vue({
// el: '#app', // 第一种
data: {
name: '张三'
}
})
vm.$mount('#app') // 第二种
1.7.2 data 的两种写法
// 对象方式
data: {
name: '张三'
}
// 函数方式
data() {
return {
name: '张三'
}
}
1.8 MVVM 模型
其中:
- M:模型(Model):对应 data 中的数据;
- V:视图(View):模板;
- VM:视图模型(ViewModel):Vue 实例对象。
发现:
- data 中所有的属性,最后都出现在了 vm(vue 实例化对象)身上。
- vm 身上所有的属性以及 Vue 原型上的所有属性,在 Vue 模板中都可以直接使用。
1.9 数据代理
- vue 中的数据代理
通过 vm 对象来代理 data 对象中属性的操作(读/写)。
- vue 中数据代理的好处
更加方便的操作 data 中的数据。
- 基本原理
通过 object.defineProperty()
把 data 对象中所有属性添加到 vm 上。
为每一个添加到 vm 上的属性,都指定一个 getter/setter。
在 getter/setter 内部去操作(读/写)data 中对应的属性。
1.10 事件处理
1.10.1 基本使用
- 使用
v-on:xxx
或@xxx
绑定事件,其中xxx是事件名; - 事件的回调需要配置在 methods 对象中,最终会在 vm 上;
- methods 中配置的函数,不要用箭头函数!否则 this 就不是 vm 了;
- methods 中配置的函数,都是被 vue 所管理的函数,this 的指向是 vm 或组件实例对象;
@click="fun"
和@click="fun($event)"
效果一致,但后者可以传参。
1.10.2 事件修饰符
事件修饰符 | 描述 |
---|---|
prevent | 阻止默认事件。 |
stop | 阻止事件冒泡。 |
once | 事件只触发一次。 |
capture | 使用事件的捕获模式。 |
self | 只有 event.target 是当前操作的元素时才触发事件。 |
passive | 事件的默认行为立即执行,无需等待事件回调执行完毕。 |
1.10.3 键盘事件
- vue 常用的按键别名
别名 | 按键 |
---|---|
.delete | delete(删除)/BackSpace(退格) |
.tab | Tab(配合 keydown 使用) |
.enter | Enter(回车) |
.esc | Esc(退出) |
.space | Space(空格键) |
.left | Left(左箭头) |
.up | Up(上箭头) |
.right | Right(右箭头) |
.down | Down(下箭头) |
- vue 未提供别名的按键,可以使用按键原始的 key 值去绑定,但注意要转为 kebab-case (短横线命名)。
- 系统修饰键
别名 | 按键 |
---|---|
.ctrl | Ctrl |
.alt | Alt |
.shift | Shift |
.meta | windows 中为 window 键,mac 中为 command 键 |
- 配合 keyup 使用时:按下修饰键的同时,再按下其他键,随后释放其他键,事件才被触发。
- 配合 keydown 使用时:正常触发事件。
- 定制按键别名
Vue.config.keyCodes.自定义键名 = 键码
1.11 计算属性
- **定义:**要用的属性不存在,要通过已有的属性计算得来。
- **原理:**底层借助了 Object.defineproperty 方法来提供的 getter 和 setter。
- get 函数什么时候执行?
- 初次读取时会执行一次。
- 当依赖的数据发生改变时会被再次调用。
- **优势:**与 methods 实现相比,内部有缓存机制(复用),效率更高,调试方便。
- 补充:
- 计算属性最终会出现在 vm 上,直接读取使用即可。
- 如果计算属性要被修改,那必须写 set 函数去响应修改,且 set 中要引起计算时依赖的数据发生改变。
- 语法示例
computed: {
// 完整写法
demo: {
get() {
// 执行操作
},
set(value) {
// 执行操作
}
},
// 简写(注意:在不考虑 set 的情况下,才可以用简写形式)
demo2() {
// 执行操作
}
}
1.12 监视属性
-
当被监视的属性变化时,回调函数自动调用,进行相关操作。
-
监视的属性必须存在,才能进行监视。
-
监视的两种写法:
- new Vue 时传入 watch 配置
- 通过 vm.$watch 监视
-
深度监视
- vue 中的 watch 默认不检测对象内部值的改变(一层)。
- 配置 deep:true 可以检测对象内部值改变(多层)。
-
补充
- vue 自身可以检测对象内部值的改变,但 vue 提供的 watch 默认不可以!
- 使用 watch 时根据数据的具体结构,决定是否采用深度监视。
-
语法示例
watch: {
// 完整写法
name: {
immediate: true, // 页面初始化时就执行一次
deep: true, // 深度监视
handler(newVal, oldVal) {
// 代码逻辑
}
},
// 简写(注意:简写不能有配置项,如 immediate、deep 等。
name2(newVal, oldVal) {
// 代码逻辑
}
}
1.13 computed 和 watch 之间的区别
- computed 能完成的功能,watch 都可以完成。
- watch 能完成的功能,computed 不一定能完成,例如:watch 可以进行异步操作。
两个原则:
- 所被 vue 管理的函数,最好写成普通函数,这样 this 的指向才是 vm 或组件实例对象。
- 所有不被 vue 所管理的函数(定时器的回调函数、Ajax 的回调函数等),最好写成箭头函数。这样 this 的指向才是 vm 或组件实例对象。
1.14 class 与 style 绑定
- class
// 字符串写法适用于:类名不确定、需要动态获取。
:class="xxx" // xxx可以是字符串、对象、数组。
// 对象写法适用于:要绑定多个样式,个数确定,名字也确定,不确定用不用。
:class="{ active: isActive }"
// 数组写法适用于:要绑定多个样式,个数不确定,名字也不确定。
:class="[activeClass, errorClass]"
- style
:style="{fontSize:xxx}" // 其中 xxx 是动态的值,如 18px。
:style="[a,b]" // 其中 a,b 是样式对象
1.15 条件渲染
- v-if
// 写法:
v-if="表达式"
v-else-if="表达式"
v-else="表达式"
// 适用于:切换频率较低的场景。
// 特点:不展示的 DOM 元素直接被移除。
// 注意:v-if 可以和 v-else-if、v-else 一起使用,但要求结构不能被“打断”。
- v-show
// 写法:
v-show="表达式"
// 适用于:切换频率较高的场景。
// 特点:不展示的 DOM 元素未被移除,仅仅是使用样式隐藏掉。
- 补充
使用 v-if 时,元素可能无法获取到,但是用 v-show 元素一定能获取到。
1.16 列表渲染
- v-for
- 用于展示列表数据
- 语法:
v-for="item in items" :key="item.message"
- 可遍历:数组、对象、字符串、指定次数。
- 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 是没有问题的。
- 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) 使用这些 push(), pop(), shift(), splice(), sort(), reverse()
(2) Vue.set() 或 vm.$set()
(3) Vue.delete() 或 vm.$delete()
注意:Vue.set() 和 vm.$set() 不能给 vm 或 vm 的根数据对象添加属性!!!
1.17 收集表单数据
- 若
<input type="text"/>
,则 v-model 收集的是 value 值,用户输入的就是 value 值。 - 若
<input type="radio"/>
,则 v-model 收集的是 value 值,并且要给标签配置 value 值。 - 若
<input type="checkbox"/>
:- 没有配置 input 的初始值是非数组,那么收集的就是 checked 值。
- 配置 input 的value 属性:
- v-model 的初始值是非数组,那么收集就是 checked 值。
- v-model 的初始值是数组,那么收集的就是 value 组成的数组。
- v-model 的修饰符
- lazy:失去焦点再收集数据;
- number:输入字符串转为有效的数字;
- trim:过滤首尾空格。
1.18 过滤器(filter)
定义:对要显示的数据进行特定格式化后再显示(使用一些简单的逻辑处理)。
语法:
// 注册过滤器
Vue.filter(name, callback) 或 new Vue(filters:{})
// 使用过滤器
{{xxx | 过滤器名} 或 v-bind:属性="xxx | 过滤器名"
注意:
- 过滤器也可以接收额外参数、多个过滤器也可以串联。
- 并没有改变原本的数据,是参数新的对应的数据。
vue 3 中会移除过滤器(filter)。
1.19 内置指令
1.19.1 v-text
-
作用:更新元素的
textContent
。如果要更新部分的textContent
,Mustache
插值。与插值语法的区别是 v-text 会替换节点中的内容,而插值不会。 -
示例:
<span v-text="msg"></span> <!-- 等价于 --> <span>{{msg}}</span>
1.19.2 v-html
- 作用:向指定节点中铺垫包含 html 结构的内容。
- 与插值语法的区别:
- v-html 会替换节点中的内容,而插值不会。
- v-html 可以识别 html 结构。
注意:在网站上动态渲染任意 HTML 是非常危险的,因为容易导致 XSS 攻击。只在可信内容上使用
v-html
,永不用在用户提交的内容上。
-
示例:
<div v-html="'<h1>Hello World</h1>'"></div>
1.19.3 v-cloak
-
作用:
这个指令保持在元素上直到关联组件实例结束编译。和 CSS 规则如
[v-cloak] { display: none }
一起用时,这个指令可以隐藏未编译的 Mustache 标签直到组件实例准备完毕。 -
示例:
[v-cloak] { display: none; }
不会显示,直到编译结束。<div v-cloak> {{ message }} </div>
1.19.4 v-once
-
作用:
只渲染元素和组件一次。随后的重新渲染,元素/组件及其所有的子节点将被视为静态内容并跳过。这可以用于优化更新性能。
<!-- 单个元素 --> <span v-once>This will never change: {{msg}}</span> <!-- 有子元素 --> <div v-once> <h1>comment</h1> <p>{{msg}}</p> </div> <!-- 组件 --> <my-component v-once :comment="msg"></my-component> <!-- `v-for` 指令 --> <ul> <li v-for="i in list" v-once>{{i}}</li> </ul>
1.19.5 v-pre
-
作用:
跳过这个元素和它的子元素的编译过程。可以用来显示原始 Mustache 标签。跳过大量没有指令的节点会加快编译。
-
示例:
<span v-pre>{{ this will not be compiled }}</span>
1.20 自定义指令
- 语法
// 局部定义
// 对象式
new Vue({
directives:{指令名:配置对象}
})
// 函数式
new Vue({
directives:{指令名:回调函数}
})
// 全局定义
// 对象式
Vue.directives(指令名, 配置对象)
// 函数式
Vue.directives(指令名, 回调函数)
-
配置对象常用的 3 个回调
- bind:指令与元素成功绑定时调用。
- inserted :指令所在元素被插入页面时调用。
- update:指令所在模板被重新解析时调用。
-
补充
- 指令定义时不加
v-
,但使用时要加v-
。 - 指令名如果是多个单词组成,要使用 kebab-case 命名方式,不要使用 camelCase 命名方式。
- 指令定义时不加
1.21 生命周期
- 流程图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ScQvzAXj-1658564648097)(…/typora-user-images/生命周期.png)]
- 常用的生命周期钩子
- mounted():发送 Ajax 请求、开启定时器、绑定自定义事件、订阅消息等。
- beforeDestory(): 做收尾工作, 如: 清除定时器、解绑自定义事件、取消订阅等。
2 组件化编程
2.1 模块与组件、模块化与组件化
2.1.1 模块
- 理解:向外提供特定功能的 js 程序,一般就是一个 js 文件。
- 为什么:js 文件很多很复杂。
- 作用:复用 js,简化 js 的编写,提高 js 运行效率。
2.1.2 组件
- 理解:用来实现局部(特定)功能效果的代码集合(html/css/js/image……)。
- 为什么:一个界面的功能很复杂。
- 作用:复用编码,,简化项目编码,提高运行效率。
2.1.3 模块化
当应用中的 js 都以模块来编写的,那这个应用就是一个模块化的应用。
2.1.4 组件化
当应用中的功能都是多组件的方式来编写的,那这个应用就是一个组件化的应用。
2.2 非单文件组件
2.2.1 缺点
- 模板编写没有提示
- 没有构建过程,无法将 ES6 转换成 ES5。
- 不支持组件的 CSS。
- 真正开发中几乎不用。
2.2.2 基本使用
- 步骤
- 定义组件
- 注册组件
- 使用组件
- 如何定义一个组件?
使用 Vue.extend(options) 创建,与 new Vue(options) 的区别:
- el 不用写,因为最终所有的组件都要经过一个 vm 的管理,由 vm 中的 el 决定服务哪个容器。
- data 必须写成函数形式,为了避免组件复用时,数据存在引用问题。
使用 template 可以配置页面结构。
- 如何注册组件?
- 局部注册:
new Vue({
components: {
// '组件名': 组件
}
})
- 全局注册:
Vue.component('组件名', 组件)
- 如何使用组件?
// 把组件名作为标签使用
<组件名></组件名>
- 实例代码
<body>
<!-- 声明要被 vue 所控制的 DOM 区域(容器) -->
<div id="app">
// 3. 使用组件
<headers></headers>
<contents></contents>
</div>
<!-- 导入 vue.js 的 script 脚本文件 -->
<script src="./js/vue.js"></script>
<script>
Vue.config.productionTip = false // 阻止 vue 在启动时生成生产提示
// 1. 定义组件
const headers = Vue.extend({
template: `
<div>
<p>{{title}}</p>
</div>
`,
data() {
return {
title: '这里是头部'
}
}
})
const contents = Vue.extend({
template: `
<div>
<p>{{title}}</p>
</div>
`,
data() {
return {
title: '这里是内容部分'
}
}
})
// 创建 vue 实例对象
const vm = new Vue({
el: '#app',
// 2. 局部注册
components: {
headers, // 这里是 headers: headers 的简写
contents
}
})
</script>
</body>
2.2.3 注意点
- 关于组件名
- 一个单词组成:
- 第一种写法(首字母大写):School
- 第二种写法(首字母小写):school
- 多个单词组成:
- 第一种写法(Kebab-case 命名):my-school
- 第二种写法(CamelCase 命名):MySchool(需要 Vue 脚手架支持)
- 备注:
- 组件名尽可能回避 HTML 中已有的元素名称,例如:h1、header等。
- 可以使用 name 配置项指定组件再开发者工具中呈现的名字。
- 一个单词组成:
- 关于组件标签
- 第一种写法:
- 第二种写法:
- 备注:不用使用脚手架时, 会导致后续组件不能渲染。
- 简写方式
const school = Vue.extend(options)
// 简写为
const school = options
2.2.4 VueComponent
- school 组件本质是一个名为 VueComponent 的构造函数,且不是程序所定义的,是 Vue.extend 生成的。
- 我们只需要写 或 ,Vue 解析时会自动帮我们创建 school 组件的实例对象。
- 注意:每次调用 Vue.extend,所返回的都是一个全新的 VueComponent,如下源码所示:
Vue.extend = function (extendOptions) {
// 略
var Sub = function VueComponent (options) {
this._init(options);
};
// 略
return Sub
};
-
关于 this 指向
-
组件配置中:
data 函数、methods 中的函数、watch 中的函数、computed 中的函数,它们的 this 指向均是【VueComponent 实例对象】
-
new Vue(options) 配置中:
data 函数、methods 中的函数、watch 中的函数、computed 中的函数,它们的 this 指向均是【Vue 实例对象】
-
-
VueComponent 实例对象可以简称 vc,而 Vue 实例对象可以简称 vm。
2.2.5 一个重要的内置关系
VueComponent.prototype.__proto__ === Vue.prototype
组件实例对象可以访问到 Vue 原型上的属性和方法。
2.3 单文件组件
2.3.1 基本组成
// 文件以 .vue 扩展名结尾,例如 Student.vue
<template>
</template>
<script>
export default {
}
</script>
<style>
</style>
2.3.2 示例代码
<template>
<div id="school">
<h1>{{msg}}</h1>
</div>
</template>
<script>
export default {
name: 'School',
data() {
return {
msg: 'hello!'
}
}
}
</script>
<style>
#school {
color: red;
}
</style>
3 使用脚手架
3.1 初始化脚手架
3.1.1 说明
Vue 脚手架是 Vue 官方提供的标准化开发工具(开发平台)。
3.1.2 使用步骤
① 安装脚手架依赖 nodejs,安装 nodejs 参考node.js 安装教程 (Windows zip 版)
② 在终端输入如下命令,全局安装@vue/cli。
npm install -g @vue/cli
③ 切换到你要创建项目的目录,然后使用命令创建项目。
vue create xxx
④ 在当前项目目录下启动项目。
npm run serve
3.1.3 脚手架文件结构
├─node_modules
├─public
| ├─favicon.ico: 页签图标
| └index.html: 主页面
├─src
| ├─App.vue: 汇总所有组件
| ├─main.js: 入口文件
| ├─components: 存放组件
| ├─assets: 存放静态资源
| | └logo.png
├─.browserslistrc
├─.editorconfig
├─.eslintrc.js
├─.gitignore: git 版本管制忽略的配置
├─babel.config.js: babel 的配置文件
├─jsconfig.json
├─package-lock.json:包版本控制文件
├─package.json: 应用包配置文件
├─README.md: 应用描述文件
├─vue.config.js
3.1.4 关于不同版本的 Vue
- vue.js 与 vue.runtime.xxx.js 的区别:
- vue.js 是完整版的 Vue,包含:核心功能 + 模板解析器。
- vue.runtime.xxx.js 是运行版的 Vue,只包含:核心功能。
- 因为 vue.runtime.xxx.js 没有模板解析器,所以不能使用 template 配置项,需要使用 render 函数接收到 createElement 函数去指定具体内容。
3.1.5 vue.config.js 配置文件
Vue 脚手架隐藏了所有 webpack 相关的配置,若想查看具体的 webpack 配置, 请执行:
vue inspect > output.js
如果想要对脚手架进行修改定制,可配置 vue.config.js,具体配置信息详见Vue CLI (vuejs)。
3.2 ref 与 props
3.2.1 ref 属性
- 被用来给元素或子组件注册引用信息(id 的替代者)。
- 应用在 html 标签上获取的是真实的 DOM 元素,应用在组件标签上获取的是组件实例对象。
- 简单使用:
// 元素
<h1 ref="title">welcome</h1>
// 组件
<Hello ref="hello"></Hello>
// 获取
this.$refs.xxx
3.2.2 配置项 props
- 功能:让组件接收外部传过来的数据。
- 传送数据:
<Hello :msg="123"></Hello>
- 接收数据:
// 第一种方式(只接收)
props: ['msg']
// 第二种方式(限制类型)
props: {
msg: String
}
// 第三种方式(限制类型、限制必要性、指定默认值)
props: {
msg: {
type: String,
required: true,
default: 'hello'
}
}
3.3 mixin(混入)
- 功能:可以把多个组件共用的配置提取成一个混入对象。
- 使用方式:
// 定义混合
{
data() {return {...}},
methods: {...}
}
// 使用混合
// 1. 全局混入
Vue.mixin(xxx)
// 2. 局部混入
mixins: ['xxx']
3.4 插件
- 功能:用于增强 Vue。
- 本质:包含 install 方法的一个对象,install 的第一个参数是 Vue,第二个以后的参数是插件使用者传递的数据。
- 定义插件:
MyPlugin.install = function (Vue, options) {
// 1. 添加全局方法或 property
Vue.myGlobalMethod = function () {
// 逻辑...
}
// 2. 添加全局资源
Vue.directive('my-directive', {
bind (el, binding, vnode, oldVnode) {
// 逻辑...
}
...
})
// 3. 注入组件选项
Vue.mixin({
created: function () {
// 逻辑...
}
...
})
// 4. 添加实例方法
Vue.prototype.$myMethod = function (methodOptions) {
// 逻辑...
}
}
- 使用插件:
Vue.use(MyPlugin)
3.5 scoped 样式
- 作用:让样式在局部生效,防止冲突。
- 写法:
<style scoped>
</style>
3.6 自定义事件
- 一种组件间通信的方式,用于 子组件 => 父组件 传数据。
- 使用场景:A 是父组件,B 是子组件,B 向 A 传数据,那么就要在 A 中给 B 绑定自定义事件(事件的回调在 A 中)。
- 绑定自定义事件:
// 1. 第一种方式,在父组件中
<Demo @test="test"/>
// 或者
<Demo v-on:test="test"/>
// 2. 第二种方式,在父组件中
<Demo ref="demo"/>
//.......
mounted() {
this.$refs.demo.$on('test', this.test)
}
// 3.若想让自定义事件只触发一次,可以使用 once 修饰符,或 $once 方法。
- 触发自定义事件:
this.$emit('test', data)
- 解绑自定义事件:
this.$off('test')
- 组件上也可以绑定原生 DOM 事件,需要使用
native
修饰符。 - 通过
this.$refs.xxx.$on('test', 回调)
绑定自定义事件时,回调要么配置在methods
中,要么用箭头函数,否则 this 指向会出问题。
3.7 全局事件总线
- 一种组件间通信的方式,适用于任意组件间通信。
- 安装全局事件总线:
new Vue({
render: h => h(App),
beforeCreate() {
Vue.prototype.$bus = this
}
}).$mount('#app')
- 使用事件总线:
// 1. 接收数据:A 组件想接收数据,则在 A 中给 $bus 绑定自定义事件,时间的回调留在 A 组件身上
// 组件 A
methods: {
demo(data) {......}
},
mounted: {
this.$bus.$on('xxx', this.demo)
}
// 2. 提供数据
// 组件 B
this.$bus.$emit('xxx', data)
- 最好在 beforeDestroy 钩子中,用 $off 区解绑当前组件所用到的事件。
3.8 消息订阅与发布
-
一种组件间通信的方式,适用于任意组件间通信。
-
使用步骤:
// 1. 安装 pubsub
npm i pubsub-js
// 2. 引入
import pubsub from 'pubsub-js'
- 接收数据:A 组件想接收数据,则在 A 组件中订阅消息,订阅的回调留在 A 组件的自身
methods: {
demo(data) {......}
},
mounted() {
this.demoPub = pubsub.subscribe('xxx', this.demo)
}
- 提供数据
pubsub.publish('xxx', data)
- 最好在 beforeDestroy 钩子中,用
pubsub.unsubscribe(this.demoPub)
取消订阅。
3.9 $nextTick()
- 语法:
this.$nextTick(回调函数)
- 作用:在下一次 DOM 更新结束后执行指定的回调。
- 什么时候用:当改变数据后,要基于更新后的新 DOM 进行某些操作时,要在 nextTick 所指定的回调函数中执行。
3.10 过渡与动画
- 概述:Vue 在插入、更新或者移除 DOM 时,提供多种不同方式的应用过渡效果。包括以下工具:
- 在 CSS 过渡和动画中自动应用 class
- 可以配合使用第三方 CSS 动画库,如 Animate.css
- 在过渡钩子函数中使用 JavaScript 直接操作 DOM
- 可以配合使用第三方 JavaScript 动画库,如 Velocity.js
- 图示:
-
过渡的类名:
-
元素进入的样式:
-
v-enter
:定义进入过渡的开始状态。 -
v-enter-active
:定义进入过渡生效时的状态。 -
v-enter-to
:2.1.8 版及以上定义进入过渡的结束状态。
-
-
元素离开的样式:
-
v-leave
:定义离开过渡的开始状态。 -
v-leave-active
:定义离开过渡生效时的状态。 -
v-leave-to
:2.1.8 版及以上定义离开过渡的结束状态。
-
-
-
写法:
// 使用 <transition> 包裹要过渡的元素,并配置 name 属性
<transition name="slide-fade">
<p v-if="show">hello</p>
</transition>
- 备注:若有多个元素需要过渡,则需要使用:,且每个元素都要指定 key 值。
3.11 vue 脚手架配置代理
- 方法一:
// 在 vue.config.js 中添加如下配置
devServer: {
proxy: 'http://localhost:4000'
}
// 1. 优点:配置简单,请求资源时直接发送给前端(8080)即可
// 2. 缺点:不能配置多个代理,不能灵活的控制请求是否走代理
// 3. 工作方式:若按照上述配置代理,当请求了前端不存在的资源时,那么该请求会转发给服务器(有限匹配前端资源)
- 方法二:
// 在 vue.config.js 中配置具体代理规则
devServer: {
proxy: {
'/api': {
target: '<url>',
ws: true,
changeOrigin: true // 是否改变请求路径端口号为服务器端口号
},
'/foo': {
target: '<other_url>'
}
}
}
// 1. 优点:可以配置多个代理,且可以灵活的控制请求是否走代理
// 2. 缺点:配置略微繁琐,请求资源时必须加qin
3.12 插槽
-
作用:让父组件可以向子组件指定位置插入 html 结构,也是一种组件通信的方式,适用于父组件 => 子组件。
-
分类:默认插槽、具名插槽、作用域插槽。
-
使用方式:
- 默认插槽
// 父组件中: <navigation-link> Your Profile </navigation-link> // 子组件 navigation-link 中: <a> <slot></slot> </a>
- 具名插槽
// 父组件中: <base-layout> <template v-slot:header> <h1>Here might be a page title</h1> </template> <p>A paragraph for the main content.</p> <p>And another one.</p> <template v-slot:footer> <p>Here's some contact info</p> </template> </base-layout> // 子组件 base-layout 中: <div class="container"> <header> <slot name="header"></slot> </header> <main> <slot></slot> </main> <footer> <slot name="footer"></slot> </footer> </div> // 一个不带 name 的 <slot> 出口会带有隐含的名字“default”。 // 具名插槽的缩写形式: // 把参数之前的所有内容 (v-slot:) 替换为字符 #。例如 v-slot:header 可以被重写为 #header: <template #header> <h1>Here might be a page title</h1> </template>
- 作用域插槽
// 理解:数据在组件的自身,但根据数据生成的结构需要组件的使用者来决定。 // 注意:这里将包含所有插槽 prop 的对象命名为 slotProps,但你也可以使用任意你喜欢的名字。 // 父组件中: <current-user> <template v-slot:default="slotProps"> {{ slotProps.user.firstName }} </template> </current-user> // 子组件 current-user 中: <span> <slot :user="user"> {{ user.lastName }} </slot> </span> // 作用域插槽的内部工作原理是将你的插槽内容包裹在一个拥有单个参数的函数里: function (slotProps) { // 插槽内容 } // 这意味着 v-slot 的值实际上可以是任何能够作为函数定义中的参数的 JavaScript 表达式。所以在支持的环境下 (单文件组件或现代浏览器),你也可以使用 ES2015 解构来传入具体的插槽 prop,如下: <current-user v-slot="{ user }"> {{ user.firstName }} </current-user>
4 vuex
4.1 vuex 是什么?
专门在 Vue 中实现集中式状态(数据)管理的一个 Vue 插件,对 vue 应 用中多个组件的共享状态进行集中式的管理(读/写),也是一种组件间通信的方 式,且适用于任意组件间通信。
4.2 什么时候用?
- 多个视图依赖于同一状态。
- 来自不同视图的行为需要变更同一状态。
4.3 搭建vuex环境
-
创建文件:
src/store/index.js
//引入Vue核心库 import Vue from 'vue' //引入Vuex import Vuex from 'vuex' //应用Vuex插件 Vue.use(Vuex) //准备actions对象——响应组件中用户的动作 const actions = {} //准备mutations对象——修改state中的数据 const mutations = {} //准备state对象——保存具体的数据 const state = {} //创建并暴露store export default new Vuex.Store({ actions, mutations, state })
-
在
main.js
中创建vm时传入store
配置项...... //引入store import store from './store' ...... //创建vm new Vue({ el:'#app', render: h => h(App), store })
4.4 基本使用
-
初始化数据、配置
actions
、配置mutations
,操作文件store.js
//引入Vue核心库 import Vue from 'vue' //引入Vuex import Vuex from 'vuex' //引用Vuex Vue.use(Vuex) const actions = { //响应组件中加的动作 jia(context,value){ // console.log('actions中的jia被调用了',miniStore,value) context.commit('JIA',value) }, } const mutations = { //执行加 JIA(state,value){ // console.log('mutations中的JIA被调用了',state,value) state.sum += value } } //初始化数据 const state = { sum:0 } //创建并暴露store export default new Vuex.Store({ actions, mutations, state, })
-
组件中读取vuex中的数据:
$store.state.sum
-
组件中修改vuex中的数据:
$store.dispatch('action中的方法名',数据)
或$storemit('mutations中的方法名',数据)
备注:若没有网络请求或其他业务逻辑,组件中也可以越过actions,即不写
dispatch
,直接编写commit
4.5 getters的使用
-
概念:当state中的数据需要经过加工后再使用时,可以使用getters加工。
-
在
store.js
中追加getters
配置...... const getters = { bigSum(state){ return state.sum * 10 } } //创建并暴露store export default new Vuex.Store({ ...... getters })
-
组件中读取数据:
$store.getters.bigSum
4.6 四个map方法的使用
-
mapState方法:用于帮助我们映射
state
中的数据为计算属性computed: { //借助mapState生成计算属性:sum(对象写法) ...mapState({sum:'sum'}), //借助mapState生成计算属性:sum(数组写法) ...mapState(['sum']), },
-
mapGetters方法:用于帮助我们映射
getters
中的数据为计算属性computed: { //借助mapGetters生成计算属性:bigSum(对象写法) ...mapGetters({bigSum:'bigSum'}), //借助mapGetters生成计算属性:bigSum(数组写法) ...mapGetters(['bigSum']) },
-
mapActions方法:用于帮助我们生成与
actions
对话的方法,即:包含$store.dispatch(xxx)
的函数methods:{ //靠mapActions生成:incrementOdd、incrementWait(对象形式) ...mapActions({incrementOdd:'jiaOdd',incrementWait:'jiaWait'}) //靠mapActions生成:incrementOdd、incrementWait(数组形式) ...mapActions(['jiaOdd','jiaWait']) }
-
mapMutations方法:用于帮助我们生成与
mutations
对话的方法,即:包含$storemit(xxx)
的函数methods:{ //靠mapActions生成:increment、decrement(对象形式) ...mapMutations({increment:'JIA',decrement:'JIAN'}), //靠mapMutations生成:JIA、JIAN(对象形式) ...mapMutations(['JIA','JIAN']), }
备注:mapActions 与 mapMutations 使用时,若需要传递参数需要:在模板中绑定事件时传递好参数,否则参数是事件对象。
4.7 模块化+命名空间
-
目的:让代码更好维护,让多种数据分类更加明确。
-
修改
store.js
const countAbout = { namespaced:true,//开启命名空间 state:{x:1}, mutations: { ... }, actions: { ... }, getters: { bigSum(state){ return state.sum * 10 } } } const personAbout = { namespaced:true,//开启命名空间 state:{ ... }, mutations: { ... }, actions: { ... } } const store = new Vuex.Store({ modules: { countAbout, personAbout } })
-
开启命名空间后,组件中读取state数据
//方式一:自己直接读取 this.$store.state.personAbout.list //方式二:借助mapState读取: ...mapState('countAbout',['sum','school','subject']),
-
开启命名空间后,组件中读取getters数据
//方式一:自己直接读取 this.$store.getters['personAbout/firstPersonName'] //方式二:借助mapGetters读取: ...mapGetters('countAbout',['bigSum'])
-
开启命名空间后,组件中调用dispatch
//方式一:自己直接dispatch this.$store.dispatch('personAbout/addPersonWang',person) //方式二:借助mapActions: ...mapActions('countAbout',{incrementOdd:'jiaOdd',incrementWait:'jiaWait'})
-
开启命名空间后,组件中调用commit
//方式一:自己直接commit this.$store.commit('personAbout/ADD_PERSON',person) //方式二:借助mapMutations: ...mapMutations('countAbout',{increment:'JIA',decrement:'JIAN'}),
5 路由
5.1 概念
- 理解: 一个路由(route)就是一组映射关系(key - value),多个路由需要路由器(router)进行管理。
- 前端路由:key 是路径,value 是组件。
5.2 基本使用
-
安装vue-router,命令:
npm i vue-router
-
应用插件:
Vue.use(VueRouter)
-
编写router配置项:
//引入VueRouter import VueRouter from 'vue-router' //引入组件 import About from '../components/About' import Home from '../components/Home' //创建router实例对象,去管理一组一组的路由规则 const router = new VueRouter({ routes:[ { path:'/about', component:About }, { path:'/home', component:Home } ] }) //暴露router export default router
-
实现切换(active-class可配置高亮样式)
<router-link active-class="active" to="/about">About</router-link>
-
指定展示位置
<router-view></router-view>
5.3 几个注意点
- 路由组件通常存放在
pages
文件夹,一般组件通常存放在components
文件夹。 - 通过切换,“隐藏”了的路由组件,默认是被销毁掉的,需要的时候再去挂载。
- 每个组件都有自己的
$route
属性,里面存储着自己的路由信息。 - 整个应用只有一个 router,可以通过组件的
$router
属性获取到。
5.4 多级路由(多级路由)
-
配置路由规则,使用children配置项:
routes:[ { path:'/about', component:About, }, { path:'/home', component:Home, children:[ //通过children配置子级路由 { path:'news', //此处一定不要写:/news component:News }, { path:'message',//此处一定不要写:/message component:Message } ] } ]
-
跳转(要写完整路径):
<router-link to="/home/news">News</router-link>
5.5 路由的 query 参数
-
传递参数
<!-- 跳转并携带query参数,to的字符串写法 --> <router-link :to="/home/message/detail?id=001&title=007">跳转</router-link> <!-- 跳转并携带query参数,to的对象写法 --> <router-link :to="{ path:'/home/message/detail', query:{ id: 001, title:'007' } }" >跳转</router-link>
-
接收参数:
$route.query.id $route.query.title
5.6 命名路由
-
作用:可以简化路由的跳转。
-
如何使用?
-
给路由命名:
{ path:'/demo', component:Demo, children:[ { path:'test', component:Test, children:[ { name:'hello', //给路由命名 path:'welcome', component:Hello, } ] } ] }
-
简化跳转:
<!--简化前,需要写完整的路径 --> <router-link to="/demo/test/welcome">跳转</router-link> <!--简化后,直接通过名字跳转 --> <router-link :to="{name:'hello'}">跳转</router-link> <!--简化写法配合传递参数 --> <router-link :to="{ name:'hello', query:{ id: 001, title: '007' } }" >跳转</router-link>
-
5.7 重定向和别名
5.7.1 重定向
重定向也是通过 routes
配置来完成,下面例子是从 /home
重定向到 /
:
const routes = [{ path: '/home', redirect: '/' }]
重定向的目标也可以是一个命名的路由:
const routes = [{ path: '/home', redirect: { name: 'homepage' } }]
甚至是一个方法,动态返回重定向目标:
const routes = [
{
// /search/screens -> /search?q=screens
path: '/search/:searchText',
redirect: to => {
// 方法接收目标路由作为参数
// return 重定向的字符串路径/路径对象
return { path: '/search', query: { q: to.params.searchText } }
},
},
{
path: '/search',
// ...
},
]
请注意,导航守卫并没有应用在跳转路由上,而仅仅应用在其目标上。在上面的例子中,在 /home
路由中添加 beforeEnter
守卫不会有任何效果。
在写 redirect
的时候,可以省略 component
配置,因为它从来没有被直接访问过,所以没有组件要渲染。唯一的例外是嵌套路由:如果一个路由记录有 children
和 redirect
属性,它也应该有 component
属性。
5.7.2 相对重定向
也可以重定向到相对位置:
const routes = [
{
// 将总是把/users/123/posts重定向到/users/123/profile。
path: '/users/:id/posts',
redirect: to => {
// 该函数接收目标路由作为参数
// 相对位置不以`/`开头
// 或 { path: 'profile'}
return 'profile'
},
},
]
5.7.3 别名
重定向是指当用户访问 /home
时,URL 会被 /
替换,然后匹配成 /
。那么什么是别名呢?
将 /
别名为 /home
,意味着当用户访问 /home
时,URL 仍然是 /home
,但会被匹配为用户正在访问 /
。
上面对应的路由配置为:
const routes = [{ path: '/', component: Homepage, alias: '/home' }]
通过别名,你可以自由地将 UI 结构映射到一个任意的 URL,而不受配置的嵌套结构的限制。使别名以 /
开头,以使嵌套路径中的路径成为绝对路径。你甚至可以将两者结合起来,用一个数组提供多个别名:
const routes = [
{
path: '/users',
component: UsersLayout,
children: [
// 为这 3 个 URL 呈现 UserList
// - /users
// - /users/list
// - /people
{ path: '', component: UserList, alias: ['/people', 'list'] },
],
},
]
如果你的路由有参数,请确保在任何绝对别名中包含它们:
const routes = [
{
path: '/users/:id',
component: UsersByIdLayout,
children: [
// 为这 3 个 URL 呈现 UserDetails
// - /users/24
// - /users/24/profile
// - /24
{ path: 'profile', component: UserDetails, alias: ['/:id', ''] },
],
},
]
5.8 路由的 params 参数
-
配置路由,声明接收 params 参数
{ path:'/home', component:Home, children:[ { path:'news', component:News }, { component:Message, children:[ { name:'xiangqing', path:'detail/:id/:title', //使用占位符声明接收 params 参数 component:Detail } ] } ] }
-
传递参数
<!-- 跳转并携带params参数,to的字符串写法 --> <router-link :to="/home/message/detail/001/007">跳转</router-link> <!-- 跳转并携带params参数,to的对象写法 --> <router-link :to="{ name:'xiangqing', params:{ id: 001, title: '007' } }" >跳转</router-link>
特别注意:路由携带params参数时,若使用to的对象写法,则不能使用path配置项,必须使用name配置!
-
接收参数:
$route.params.id $route.params.title
5.9 路由的 props 配置
作用:让路由组件更方便的收到参数
{
name:'xiangqing',
path:'detail/:id',
component:Detail,
//第一种写法:props 值为对象,该对象中所有的 key-value 的组合最终都会通过 props 传给 Detail 组件
// props:{a:900}
//第二种写法:props 值为布尔值,布尔值为 true,则把路由收到的所有params参数通过props传给 Detail 组件
// props:true
//第三种写法:props 值为函数,该函数返回的对象中每一组 key-value 都会通过 props 传给 Detail 组件
props(route){
return {
id:route.query.id,
title:route.query.title
}
}
}
5.10 router-link 的replace属性
- 作用:控制路由跳转时操作浏览器历史记录的模式。
- 浏览器的历史记录有两种写入方式:分别为
push
和replace
,push
是追加历史记录,replace
是替换当前记录。路由跳转时候默认为push。
- 如何开启
replace
模式:<router-link replace .......>News</router-link>
。
5.11 编程式路由导航
-
作用:不借助
<router-link>
实现路由跳转,让路由跳转更加灵活。 -
具体编码:
//$router的两个API this.$router.push({ name:'xiangqing', params:{ id:xxx, title:xxx } }) this.$router.replace({ name:'xiangqing', params:{ id:xxx, title:xxx } }) this.$router.forward() //前进 this.$router.back() //后退 this.$router.go() //可前进也可后退
5.12 缓存路由组件
-
作用:让不展示的路由组件保持挂载,不被销毁。
-
具体编码:
<keep-alive include="News"> <router-view></router-view> </keep-alive>
5.13 两个新的生命周期钩子
- 作用:路由组件所独有的两个钩子,用于捕获路由组件的激活状态。
- 具体名字:
activated
路由组件被激活时触发。deactivated
路由组件失活时触发。
5.14 路由守卫
-
作用:对路由进行权限控制
-
分类:全局守卫、独享守卫、组件内守卫
-
全局守卫:
//全局前置守卫:初始化时执行、每次路由切换前执行 router.beforeEach((to,from,next)=>{ console.log('beforeEach',to,from) if(to.meta.isAuth){ //判断当前路由是否需要进行权限控制 if(localStorage.getItem('school') === 'atguigu'){ //权限控制的具体规则 next() //放行 }else{ alert('暂无权限查看') // next({name:'guanyu'}) } }else{ next() //放行 } }) //全局后置守卫:初始化时执行、每次路由切换后执行 router.afterEach((to,from)=>{ console.log('afterEach',to,from) if(to.meta.title){ document.title = to.meta.title //修改网页的title }else{ document.title = 'vue_test' } })
-
独享守卫:
beforeEnter(to,from,next){ console.log('beforeEnter',to,from) if(to.meta.isAuth){ //判断当前路由是否需要进行权限控制 if(localStorage.getItem('school') === 'atguigu'){ next() }else{ alert('暂无权限查看') // next({name:'guanyu'}) } }else{ next() } }
-
组件内守卫:
//进入守卫:通过路由规则,进入该组件时被调用 beforeRouteEnter (to, from, next) { }, //离开守卫:通过路由规则,离开该组件时被调用 beforeRouteLeave (to, from, next) { }
5.15 路由器的两种工作模式
- 对于一个url来说,什么是hash值?—— #及其后面的内容就是 hash 值。
- hash 值不会包含在 HTTP 请求中,即:hash 值不会带给服务器。
- hash 模式:
- 地址中永远带着#号,不美观 。
- 若以后将地址通过第三方手机 app 分享,若 app 校验严格,则地址会被标记为不合法。
- 兼容性较好。
- history 模式:
- 地址干净,美观 。
- 兼容性和hash模式相比略差。
- 应用部署上线时需要后端人员支持,解决刷新页面服务端 404 的问题。
6 Vue 3
6.1 概述
- 2020年9月18日,Vue.js发布3.0版本,代号:One Piece(海贼王)
- 耗时2年多、2600+次提交、30+个RFC、600+次PR、99位贡献者
- github上的tags地址:https://github/vuejs/vue-next/releases/tag/v3.0.0
6.2 Vue3带来了什么
6.2.1 性能的提升
-
打包大小减少41%
-
初次渲染快55%, 更新渲染快133%
-
内存减少54%
…
6.2.2 源码的升级
-
使用Proxy代替defineProperty实现响应式
-
重写虚拟DOM的实现和Tree-Shaking
…
6.2.3 拥抱TypeScript
- Vue3可以更好的支持TypeScript
6.2.4 新的特性
-
Composition API(组合API)
- setup配置
- ref与reactive
- watch与watchEffect
- provide与inject
- …
-
新的内置组件
- Fragment
- Teleport
- Suspense
-
其他改变
- 新的生命周期钩子
- data 选项应始终被声明为一个函数
- 移除keyCode支持作为 v-on 的修饰符
- …
6.3 创建 vue 3.0 工程
6.3.1 使用 vue-cli 创建
官方文档:https://cli.vuejs/zh/guide/creating-a-project.html#vue-create
## 查看@vue/cli版本,确保@vue/cli版本在4.5.0以上
vue --version
## 安装或者升级你的@vue/cli
npm install -g @vue/cli
## 创建
vue create vue_test
## 启动
cd vue_test
npm run serve
6.3.2 使用 vite 创建
官方文档:https://v3.vuejs/guide/installation.html#vite
vite官网:https://vitejs
- 什么是vite?—— 新一代前端构建工具。
- 优势如下:
- 开发环境中,无需打包操作,可快速的冷启动。
- 轻量快速的热重载(HMR)。
- 真正的按需编译,不再等待整个应用编译完成。
- 传统构建 与 vite构建对比图
6.4 常用 Composition API
6.4.1 setup
- 理解:Vue3.0 中一个新的配置项,值为一个函数。
- setup 是所有Composition API(组合API)“ 表演的舞台 ”。
- 组件中所用到的:数据、方法等等,均要配置在 setup 中。
- setup 函数的两种返回值:
- 若返回一个对象,则对象中的属性、方法, 在模板中均可以直接使用。(重点关注!)
- 若返回一个渲染函数:则可以自定义渲染内容。(了解)
- 注意点:
- 尽量不要与Vue2.x配置混用
- Vue2.x 配置(data、methos、computed…)中可以访问到 setup 中的属性、方法。
- 但在 setup 中不能访问到 Vue2.x 配置(data、methos、computed…)。
- 如果有重名,setup 优先。
- setup 不能是一个 async 函数,因为返回值不再是 return 的对象, 而是 promise,模板看不到 return 对象中的属性。(后期也可以返回一个 Promise 实例,但需要 Suspense 和异步组件的配合)
- 尽量不要与Vue2.x配置混用
6.4.2 ref 函数
- 作用: 定义一个响应式的数据
- 语法:
const xxx = ref(initValue)
- 创建一个包含响应式数据的引用对象(reference对象,简称ref对象)。
- JS中操作数据:
xxx.value
- 模板中读取数据: 不需要 .value,直接:
<div>{{xxx}}</div>
- 备注:
- 接收的数据可以是:基本类型、也可以是对象类型。
- 基本类型的数据:响应式依然是靠
Object.defineProperty()
的get
与set
完成的。 - 对象类型的数据:内部 “ 求助 ” 了 Vue3.0 中的一个新函数——
reactive
函数。
6.4.3 reactive 函数
- 作用: 定义一个对象类型的响应式数据(基本类型不要用它,要用
ref
函数) - 语法:
const 代理对象= reactive(源对象)
接收一个对象(或数组),返回一个代理对象(Proxy的实例对象,简称proxy对象) - reactive定义的响应式数据是“深层次的”。
- 内部基于 ES6 的 Proxy 实现,通过代理对象操作源对象内部数据进行操作。
6.4.4 Vue 3.0 中的响应式原理
- vue 2.x 的响应式
-
实现原理:
-
对象类型:通过
Object.defineProperty()
对属性的读取、修改进行拦截(数据劫持)。 -
数组类型:通过重写更新数组的一系列方法来实现拦截。(对数组的变更方法进行了包裹)。
Object.defineProperty(data, 'count', { get () {}, set () {} })
-
-
存在问题:
- 新增属性、删除属性, 界面不会更新。
- 直接通过下标修改数组, 界面不会自动更新。
- Vue 3.0 的响应式
-
实现原理:
-
通过Proxy(代理): 拦截对象中任意属性的变化, 包括:属性值的读写、属性的添加、属性的删除等。
-
通过Reflect(反射): 对源对象的属性进行操作。
-
MDN文档中描述的Proxy与Reflect:
-
Proxy:https://developer.mozilla/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy
-
Reflect:https://developer.mozilla/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Reflect
new Proxy(data, { // 拦截读取属性值 get (target, prop) { return Reflect.get(target, prop) }, // 拦截设置属性值或添加新属性 set (target, prop, value) { return Reflect.set(target, prop, value) }, // 拦截删除属性 deleteProperty (target, prop) { return Reflect.deleteProperty(target, prop) } }) proxy.name = 'tom'
-
-
6.4.5 reactive 对比 ref
- 从定义数据角度对比:
- ref 用来定义:基本类型数据。
- reactive 用来定义:对象(或数组)类型数据。
- 备注:ref 也可以用来定义对象(或数组)类型数据, 它内部会自动通过
reactive
转为代理对象。
- 从原理角度对比:
- ref 通过
Object.defineProperty()
的get
与set
来实现响应式(数据劫持)。 - reactive 通过使用Proxy来实现响应式(数据劫持), 并通过 Reflect 操作源对象内部的数据。
- ref 通过
- 从使用角度对比:
- ref 定义的数据:操作数据需要
.value
,读取数据时模板中直接读取不需要.value
。 - reactive 定义的数据:操作数据与读取数据:均不需要
.value
。
- ref 定义的数据:操作数据需要
6.4.6 setup 的两个注意点
- setup 执行的时机
- 在 beforeCreate 之前执行一次,this 是 undefined。
- setup 的参数
- props:值为对象,包含:组件外部传递过来,且组件内部声明接收了的属性。
- context:上下文对象
- attrs: 值为对象,包含:组件外部传递过来,但没有在 props 配置中声明的属性,相当于
this.$attrs
。 - slots: 收到的插槽内容,相当于
this.$slots
。 - emit: 分发自定义事件的函数,相当于
this.$emit
。
- attrs: 值为对象,包含:组件外部传递过来,但没有在 props 配置中声明的属性,相当于
6.4.7 计算属性与监视
- computed 函数
-
与 Vue2.x 中 computed 配置功能一致
-
写法
import {computed} from 'vue' setup(){ ... //计算属性——简写 let fullName = computed(()=>{ return person.firstName + '-' + person.lastName }) //计算属性——完整 let fullName = computed({ get(){ return person.firstName + '-' + person.lastName }, set(value){ const nameArr = value.split('-') person.firstName = nameArr[0] person.lastName = nameArr[1] } }) }
- watch 函数
-
与 Vue2.x 中 watch 配置功能一致
-
两个小“坑”:
- 监视 reactive 定义的响应式数据时:oldValue 无法正确获取、强制开启了深度监视(deep 配置失效)。
- 监视 reactive 定义的响应式数据中某个属性时:deep 配置有效。
//情况一:监视ref定义的响应式数据 watch(sum,(newValue,oldValue)=>{ console.log('sum变化了',newValue,oldValue) },{immediate:true}) //情况二:监视多个ref定义的响应式数据 watch([sum,msg],(newValue,oldValue)=>{ console.log('sum或msg变化了',newValue,oldValue) }) /* 情况三:监视reactive定义的响应式数据 若watch监视的是reactive定义的响应式数据,则无法正确获得oldValue!! 若watch监视的是reactive定义的响应式数据,则强制开启了深度监视 */ watch(person,(newValue,oldValue)=>{ console.log('person变化了',newValue,oldValue) },{immediate:true,deep:false}) //此处的deep配置不再奏效 //情况四:监视reactive定义的响应式数据中的某个属性 watch(()=>person.job,(newValue,oldValue)=>{ console.log('person的job变化了',newValue,oldValue) },{immediate:true,deep:true}) //情况五:监视reactive定义的响应式数据中的某些属性 watch([()=>person.job,()=>person.name],(newValue,oldValue)=>{ console.log('person的job变化了',newValue,oldValue) },{immediate:true,deep:true}) //特殊情况 watch(()=>person.job,(newValue,oldValue)=>{ console.log('person的job变化了',newValue,oldValue) },{deep:true}) //此处由于监视的是reactive素定义的对象中的某个属性,所以deep配置有效
- watchEffect 函数
-
watch 的套路是:既要指明监视的属性,也要指明监视的回调。
-
watchEffect 的套路是:不用指明监视哪个属性,监视的回调中用到哪个属性,那就监视哪个属性。
-
watchEffect 有点像 computed:
- 但 computed 注重的计算出来的值(回调函数的返回值),所以必须要写返回值。
- 而 watchEffect 更注重的是过程(回调函数的函数体),所以不用写返回值。
//watchEffect所指定的回调中用到的数据只要发生变化,则直接重新执行回调。 watchEffect(()=>{ const x1 = sum.value const x2 = person.age console.log('watchEffect配置的回调执行了') })
6.4.8 生命周期
vue2.x的生命周期 vue3.0的生命周期- Vue3.0 中可以继续使用 Vue2.x 中的生命周期钩子,但有两个被更名:
beforeDestroy
改名为beforeUnmount
destroyed
改名为unmounted
- Vue3.0 也提供了 Composition API 形式的生命周期钩子,与 Vue2.x 中钩子对应关系如下:
beforeCreate
===>setup()
created
=======>setup()
beforeMount
===>onBeforeMount
mounted
=======>onMounted
beforeUpdate
===>onBeforeUpdate
updated
=======>onUpdated
beforeUnmount
==>onBeforeUnmount
unmounted
=====>onUnmounted
6.4.9 自定义 hook 函数
-
什么是 hook?—— 本质是一个函数,把 setup 函数中使用的 Composition API 进行了封装。
-
类似于 vue2.x 中的 mixin。
-
自定义 hook 的优势: 复用代码, 让 setup 中的逻辑更清楚易懂。
6.4.10 toRef
-
作用:创建一个 ref 对象,其value值指向另一个对象中的某个属性。
-
语法:
const name = toRef(person,'name')
-
应用: 要将响应式对象中的某个属性单独提供给外部使用时。
-
扩展:
toRefs
与toRef
功能一致,但可以批量创建多个 ref 对象,语法:toRefs(person)
return{
...toRefs(person)
}
6.5 其它 Composition API
6.5.1 shallowReactive 与 shallowRef
-
shallowReactive:只处理对象最外层属性的响应式(浅响应式)。
-
shallowRef:只处理基本数据类型的响应式,不进行对象的响应式处理。
-
什么时候使用?
- 如果有一个对象数据,结构比较深, 但变化时只是外层属性变化 ===> shallowReactive。
- 如果有一个对象数据,后续功能不会修改该对象中的属性,而是生新的对象来替换 ===> shallowRef。
6.5.2 readonly 与 shallowReadonly
- readonly: 让一个响应式数据变为只读的(深只读)。
- shallowReadonly:让一个响应式数据变为只读的(浅只读)。
- 应用场景: 不希望数据被修改时。
6.5.3 toRaw 与 markRaw
- toRaw:
- 作用:将一个由
reactive
生成的响应式对象转为普通对象。 - 使用场景:用于读取响应式对象对应的普通对象,对这个普通对象的所有操作,不会引起页面更新。
- 作用:将一个由
- markRaw:
- 作用:标记一个对象,使其永远不会再成为响应式对象。
- 应用场景:
- 有些值不应被设置为响应式的,例如复杂的第三方类库等。
- 当渲染具有不可变数据源的大列表时,跳过响应式转换可以提高性能。
6.5.4 customRef
-
作用:创建一个自定义的 ref,并对其依赖项跟踪和更新触发进行显式控制。
-
实现防抖效果:
<template> <input type="text" v-model="keyword"> <h3>{{keyword}}</h3> </template> <script> import {ref,customRef} from 'vue' export default { name:'Demo', setup(){ // let keyword = ref('hello') //使用Vue准备好的内置ref //自定义一个myRef function myRef(value,delay){ let timer //通过customRef去实现自定义 return customRef((track,trigger)=>{ return{ get(){ track() //告诉Vue这个value值是需要被“追踪”的 return value }, set(newValue){ clearTimeout(timer) timer = setTimeout(()=>{ value = newValue trigger() //告诉Vue去更新界面 },delay) } } }) } let keyword = myRef('hello',500) //使用程序员自定义的ref return { keyword } } } </script>
6.5.5 provide 与 inject
-
作用:实现祖与后代组件间通信
-
套路:父组件有一个
provide
选项来提供数据,后代组件有一个inject
选项来开始使用这些数据 -
具体写法:
-
祖组件中:
setup(){ ...... let car = reactive({name:'奔驰',price:'40万'}) provide('car',car) ...... }
-
后代组件中:
setup(props,context){ ...... const car = inject('car') return {car} ...... }
-
6.5.6 响应式数据的判断
- isRef: 检查一个值是否为一个 ref 对象
- isReactive: 检查一个对象是否是由
reactive
创建的响应式代理 - isReadonly: 检查一个对象是否是由
readonly
创建的只读代理 - isProxy: 检查一个对象是否是由
reactive
或者readonly
方法创建的代理
6.6 Composition API 的优势
6.6.1 Options API 存在的问题
使用传统 OptionsAPI 中,新增或者修改一个需求,就需要分别在 data,methods,computed 里修改 。
6.6.2 Composition API 的优势
我们可以更加优雅的组织我们的代码、函数。让相关功能的代码更加有序的组织在一起。
## 6.7 新的组件6.7.1 Fragment
- 在 Vue2 中: 组件必须有一个根标签
- 在 Vue3 中: 组件可以没有根标签, 内部会将多个标签包含在一个 Fragment 虚拟元素中
- 好处: 减少标签层级, 减小内存占用
6.7.2 Teleport
-
什么是 Teleport?——
Teleport
是一种能够将我们的组件 html 结构移动到指定位置的技术。<teleport to="移动位置"> <div v-if="isShow" class="mask"> <div class="dialog"> <h3>我是一个弹窗</h3> <button @click="isShow = false">关闭弹窗</button> </div> </div> </teleport>
6.7.3 Suspense
-
等待异步组件时渲染一些额外内容,让应用有更好的用户体验
-
使用步骤:
-
异步引入组件
import {defineAsyncComponent} from 'vue' const Child = defineAsyncComponent(()=>import('./components/Child.vue'))
-
使用
Suspense
包裹组件,并配置好default
与fallback
<template> <div class="app"> <h3>我是App组件</h3> <Suspense> <template v-slot:default> <Child/> </template> <template v-slot:fallback> <h3>加载中.....</h3> </template> </Suspense> </div> </template>
-
6.8 其他
6.8.1 全局 API 的转移
-
Vue 2.x 有许多全局 API 和配置。
-
例如:注册全局组件、注册全局指令等。
//注册全局组件 Vue.component('MyButton', { data: () => ({ count: 0 }), template: '<button @click="count++">Clicked {{ count }} times.</button>' }) //注册全局指令 Vue.directive('focus', { inserted: el => el.focus() }
-
-
Vue3.0 中对这些 API 做出了调整:
-
将全局的 API,即:
Vue.xxx
调整到应用实例(app
)上2.x 全局 API( Vue
)3.x 实例 API ( app
)Vue.config.xxxx app.config.xxxx Vue.config.productionTip 移除 Vueponent appponent Vue.directive app.directive Vue.mixin app.mixin Vue.use app.use Vue.prototype app.config.globalProperties
-
6.8.2 其他改变
-
data 选项应始终被声明为一个函数。
-
过度类名的更改:
-
Vue2.x 写法
.v-enter, .v-leave-to { opacity: 0; } .v-leave, .v-enter-to { opacity: 1; }
-
Vue3.x 写法
.v-enter-from, .v-leave-to { opacity: 0; } .v-leave-from, .v-enter-to { opacity: 1; }
-
-
移除 keyCode 作为 v-on 的修饰符,同时也不再支持
config.keyCodes
-
移除
v-on.native
修饰符-
父组件中绑定事件
<my-component v-on:close="handleComponentEvent" v-on:click="handleNativeClickEvent" />
-
子组件中声明自定义事件
<script> export default { emits: ['close'] } </script>
-
-
移除过滤器(filter)
过滤器虽然这看起来很方便,但它需要一个自定义语法,打破大括号内表达式是 “只是 JavaScript” 的假设,这不仅有学习成本,而且有实现成本!建议用方法调用或计算属性去替换过滤器。
fineAsyncComponent(()=>import(‘./components/Child.vue’))- 使用```Suspense```包裹组件,并配置好```default```与 ```fallback``` ```vue <template> <div class="app"> <h3>我是App组件</h3> <Suspense> <template v-slot:default> <Child/> </template> <template v-slot:fallback> <h3>加载中.....</h3> </template> </Suspense> </div> </template>
6.8 其他
6.8.1 全局 API 的转移
-
Vue 2.x 有许多全局 API 和配置。
-
例如:注册全局组件、注册全局指令等。
//注册全局组件 Vue.component('MyButton', { data: () => ({ count: 0 }), template: '<button @click="count++">Clicked {{ count }} times.</button>' }) //注册全局指令 Vue.directive('focus', { inserted: el => el.focus() }
-
-
Vue3.0 中对这些 API 做出了调整:
-
将全局的 API,即:
Vue.xxx
调整到应用实例(app
)上2.x 全局 API( Vue
)3.x 实例 API ( app
)Vue.config.xxxx app.config.xxxx Vue.config.productionTip 移除 Vueponent appponent Vue.directive app.directive Vue.mixin app.mixin Vue.use app.use Vue.prototype app.config.globalProperties
-
6.8.2 其他改变
-
data 选项应始终被声明为一个函数。
-
过度类名的更改:
-
Vue2.x 写法
.v-enter, .v-leave-to { opacity: 0; } .v-leave, .v-enter-to { opacity: 1; }
-
Vue3.x 写法
.v-enter-from, .v-leave-to { opacity: 0; } .v-leave-from, .v-enter-to { opacity: 1; }
-
-
移除 keyCode 作为 v-on 的修饰符,同时也不再支持
config.keyCodes
-
移除
v-on.native
修饰符-
父组件中绑定事件
<my-component v-on:close="handleComponentEvent" v-on:click="handleNativeClickEvent" />
-
子组件中声明自定义事件
<script> export default { emits: ['close'] } </script>
-
-
移除过滤器(filter)
过滤器虽然这看起来很方便,但它需要一个自定义语法,打破大括号内表达式是 “只是 JavaScript” 的假设,这不仅有学习成本,而且有实现成本!建议用方法调用或计算属性去替换过滤器。
更多推荐
vue 2 + 3 笔记总结
发布评论