Files
flys_blog/docs/notes/vue.md
T
2025-06-13 16:30:38 +08:00

1456 lines
36 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 1. Vue2
## 1.1 脚手架文件结构
```
├── node_modules
├── public
│ ├── favicon.ico: 页签图标
│ └── index.html: 主页面
├── src
│ ├── assets: 存放静态资源
│ │ └── logo.png
│ ├── component: 存放组件
│ │ └── HelloWorld.vue
│ ├── App.vue: 汇总所有组件
│ └── main.js: 入口文件
├── .gitignore: git版本管制忽略的配置
├── babel.config.js: babel的配置文件
├── package.json: 应用包配置文件
├── README.md: 应用描述文件
└── package-lock.json:包版本控制文件
```
## 1.2 关于不同版本的Vue
### 1.2.1 完整版与运行版区别
1. vue.js(完整版):包含核心功能 + 模板解析器。
2. vue.runtime.xxx.js(运行版):仅包含核心功能,无模板解析器。
### 1.2.2 运行版限制
因无模板解析器,运行版无法使用 `template` 配置项,需通过 `render` 函数的 `createElement` 指定内容。
## 1.3 vue.config.js配置文件
1. 使用 `vue inspect > output.js` 可以查看到Vue脚手架的默认配置。
2. 使用 `vue.config.js` 可以对脚手架进行个性化定制,详情见:https://cli.vuejs.org/zh
## 1.4 ref属性
1. 被用来给元素或子组件注册引用信息(id的替代者)
2. 应用在html标签上获取的是真实DOM元素,应用在组件标签上是组件实例对象(vc)
3. 使用方式:
1. 打标识:```<h1 ref="xxx">.....</h1>``` 或 ```<School ref="xxx"></School>```
2. 获取:```this.$refs.xxx```
## 1.5 props配置项
### 1.5.1 核心功能
用于组件接收外部传递的数据。
### 1.5.2 数据传递
```vue
<Demo name="xxx"/>
```
### 1.5.3 数据接收
1. 简单接收:`props: ['name']`
2. 类型限制:`props: { name: String }`
3. 完整配置:
```js
props: {
name: {
type: String, // 类型限制
required: true, // 必传校验
default: '老王' // 默认值
}
}
```
> 备注:props是只读的,Vue底层会监测你对props的修改,如果进行了修改,就会发出警告,若业务需求确实需要修改,那么请复制props的内容到data中一份,然后去修改data中的数据。
## 1.6 mixin(混入)
1. 功能:可以把多个组件共用的配置提取成一个混入对象
2. 使用方式:
第一步定义混合:
```
{
data(){....},
methods:{....}
....
}
```
第二步使用混入:
全局混入:```Vue.mixin(xxx)```
局部混入:```mixins:['xxx'] ```
## 1.7 插件
1. 功能:用于增强Vue
2. 本质:包含install方法的一个对象,install的第一个参数是Vue,第二个以后的参数是插件使用者传递的数据。
3. 定义插件:
```js
对象.install = function (Vue, options) {
// 1. 添加全局过滤器
Vue.filter(....)
// 2. 添加全局指令
Vue.directive(....)
// 3. 配置全局混入(合)
Vue.mixin(....)
// 4. 添加实例方法
Vue.prototype.$myMethod = function () {...}
Vue.prototype.$myProperty = xxxx
}
```
4. 使用插件:```Vue.use()```
## 1.8 scoped样式
1. 作用:让样式在局部生效,防止冲突。
2. 写法:```<style scoped>```
## 1.9 总结TodoList案例
### 1.9.1 组件化编码流程
1. 拆分静态组件:按功能点拆分,避免与HTML元素重名。
2. 实现动态组件:
- 单一组件使用:数据存放于自身
- 多组件共享:数据提升至共同父组件(状态提升)
3. 交互实现:从事件绑定开始。
### 1.9.2 props使用场景
1. 父→子通信
2. 子→父通信(需父组件传递回调函数)
### 1.9.3 注意事项
- `v-model`不可绑定`props`值(`props`只读)
- 虽可修改`props`对象属性,但不推荐。
## 1.10 webStorage
1. 存储内容大小一般支持5MB左右(不同浏览器可能还不一样)
2. 浏览器端通过 Window.sessionStorage 和 Window.localStorage 属性来实现本地存储机制。
3. 相关API
1. ```xxxxxStorage.setItem('key', 'value');```
该方法接受一个键和值作为参数,会把键值对添加到存储中,如果键名存在,则更新其对应的值。
2. ```xxxxxStorage.getItem('person');```
​ 该方法接受一个键名作为参数,返回键名对应的值。
3. ```xxxxxStorage.removeItem('key');```
​ 该方法接受一个键名作为参数,并把该键名从存储中删除。
4. ``` xxxxxStorage.clear()```
​ 该方法会清空存储中的所有数据。
4. 备注:
1. SessionStorage存储的内容会随着浏览器窗口关闭而消失。
2. LocalStorage存储的内容,需要手动清除才会消失。
3. ```xxxxxStorage.getItem(xxx)```如果xxx对应的value获取不到,那么getItem的返回值是null。
4. ```JSON.parse(null)```的结果依然是null。
## 1.11 组件的自定义事件
1. 一种组件间通信的方式,适用于:<strong style="color:red">子组件 ===> 父组件</strong>
2. 使用场景:A是父组件,B是子组件,B想给A传数据,那么就要在A中给B绑定自定义事件(<span style="color:red">事件的回调在A中</span>)。
3. 绑定自定义事件:
1. 第一种方式,在父组件中:```<Demo @atguigu="test"/>``` 或 ```<Demo v-on:atguigu="test"/>```
2. 第二种方式,在父组件中:
```js
<Demo ref="demo"/>
......
mounted(){
this.$refs.xxx.$on('atguigu',this.test)
}
```
3. 若想让自定义事件只能触发一次,可以使用```once```修饰符,或```$once```方法。
4. 触发自定义事件:```this.$emit('atguigu',数据)```
5. 解绑自定义事件```this.$off('atguigu')```
6. 组件上也可以绑定原生DOM事件,需要使用```native```修饰符。
7. 注意:通过```this.$refs.xxx.$on('atguigu',回调)```绑定自定义事件时,回调<span style="color:red">要么配置在methods中</span><span style="color:red">要么用箭头函数</span>,否则this指向会出问题!
## 1.12 全局事件总线(GlobalEventBus
1. 一种组件间通信的方式,适用于<span style="color:red">任意组件间通信</span>。
2. 安装全局事件总线:
```js
new Vue({
......
beforeCreate() {
Vue.prototype.$bus = this //安装全局事件总线,$bus就是当前应用的vm
},
......
})
```
3. 使用事件总线:
1. 接收数据:A组件想接收数据,则在A组件中给$bus绑定自定义事件,事件的<span style="color:red">回调留在A组件自身。</span>
```js
methods(){
demo(data){......}
}
......
mounted() {
this.$bus.$on('xxxx',this.demo)
}
```
2. 提供数据:```this.$bus.$emit('xxxx',数据)```
4. 最好在beforeDestroy钩子中,用$off去解绑<span style="color:red">当前组件所用到的</span>事件。
## 1.13 消息订阅与发布(pubsub
1. 一种组件间通信的方式,适用于<span style="color:red">任意组件间通信</span>。
2. 使用步骤:
1. 安装pubsub```npm i pubsub-js```
2. 引入: ```import pubsub from 'pubsub-js'```
3. 接收数据:A组件想接收数据,则在A组件中订阅消息,订阅的<span style="color:red">回调留在A组件自身。</span>
```js
methods(){
demo(data){......}
}
......
mounted() {
this.pid = pubsub.subscribe('xxx',this.demo) //订阅消息
}
```
4. 提供数据:```pubsub.publish('xxx',数据)```
5. 最好在beforeDestroy钩子中,用```PubSub.unsubscribe(pid)```去<span style="color:red">取消订阅。</span>
## 1.14 nextTick
1. 语法:```this.$nextTick(回调函数)```
2. 作用:在下一次 DOM 更新结束后执行其指定的回调。
3. 什么时候用:当改变数据后,要基于更新后的新DOM进行某些操作时,要在nextTick所指定的回调函数中执行。
## 1.15 Vue封装的过度与动画
1. 作用:在插入、更新或移除 DOM元素时,在合适的时候给元素添加样式类名。
2. 图示:<img src="https://img04.sogoucdn.com/app/a/100520146/5990c1dff7dc7a8fb3b34b4462bd0105" style="width:60%" />
3. 写法:
1. 准备好样式:
- 元素进入的样式:
1. v-enter:进入的起点
2. v-enter-active:进入过程中
3. v-enter-to:进入的终点
- 元素离开的样式:
1. v-leave:离开的起点
2. v-leave-active:离开过程中
3. v-leave-to:离开的终点
2. 使用```<transition>```包裹要过度的元素,并配置name属性:
```vue
<transition name="hello">
<h1 v-show="isShow">你好啊!</h1>
</transition>
```
3. 备注:若有多个元素需要过度,则需要使用:```<transition-group>```,且每个元素都要指定```key```值。
## 1.16 vue脚手架配置代理
### 1.16.1 方法一
在vue.config.js中添加如下配置:
```js
devServer:{
proxy:"http://localhost:5000"
}
```
说明:
1. 优点:配置简单,请求资源时直接发给前端(8080)即可。
2. 缺点:不能配置多个代理,不能灵活的控制请求是否走代理。
3. 工作方式:若按照上述配置代理,当请求了前端不存在的资源时,那么该请求会转发给服务器 (优先匹配前端资源)
### 1.16.2 方法二
编写vue.config.js配置具体代理规则:
```js
module.exports = {
devServer: {
proxy: {
'/api1': {// 匹配所有以 '/api1'开头的请求路径
target: 'http://localhost:5000',// 代理目标的基础路径
changeOrigin: true,
pathRewrite: {'^/api1': ''}
},
'/api2': {// 匹配所有以 '/api2'开头的请求路径
target: 'http://localhost:5001',// 代理目标的基础路径
changeOrigin: true,
pathRewrite: {'^/api2': ''}
}
}
}
}
/*
changeOrigin设置为true时,服务器收到的请求头中的host为:localhost:5000
changeOrigin设置为false时,服务器收到的请求头中的host为:localhost:8080
changeOrigin默认值为true
*/
```
说明:
1. 优点:可以配置多个代理,且可以灵活的控制请求是否走代理。
2. 缺点:配置略微繁琐,请求资源时必须加前缀。
## 1.17 插槽
1. 作用:让父组件可以向子组件指定位置插入html结构,也是一种组件间通信的方式,适用于 <strong style="color:red">父组件 ===> 子组件</strong> 。
2. 分类:默认插槽、具名插槽、作用域插槽
3. 使用方式:
1. 默认插槽:
```vue
父组件中:
<Category>
<div>html结构1</div>
</Category>
子组件中:
<template>
<div>
<!-- 定义插槽 -->
<slot>插槽默认内容...</slot>
</div>
</template>
```
2. 具名插槽:
```vue
父组件中:
<Category>
<template slot="center">
<div>html结构1</div>
</template>
<template v-slot:footer>
<div>html结构2</div>
</template>
</Category>
子组件中:
<template>
<div>
<!-- 定义插槽 -->
<slot name="center">插槽默认内容...</slot>
<slot name="footer">插槽默认内容...</slot>
</div>
</template>
```
3. 作用域插槽:
1. 理解:<span style="color:red">数据在组件的自身,但根据数据生成的结构需要组件的使用者来决定。</span>games数据在Category组件中,但使用数据所遍历出来的结构由App组件决定)
2. 具体编码:
```vue
父组件中:
<Category>
<template scope="scopeData">
<!-- 生成的是ul列表 -->
<ul>
<li v-for="g in scopeData.games" :key="g">{{g}}</li>
</ul>
</template>
</Category>
<Category>
<template slot-scope="scopeData">
<!-- 生成的是h4标题 -->
<h4 v-for="g in scopeData.games" :key="g">{{g}}</h4>
</template>
</Category>
子组件中:
<template>
<div>
<slot :games="games"></slot>
</div>
</template>
<script>
export default {
name:'Category',
props:['title'],
//数据在子组件自身
data() {
return {
games:['红色警戒','穿越火线','劲舞团','超级玛丽']
}
},
}
</script>
```
## 1.18 Vuex
### 1.18.1 概念
​ 在Vue中实现集中式状态(数据)管理的一个Vue插件,对vue应用中多个组件的共享状态进行集中式的管理(读/写),也是一种组件间通信的方式,且适用于任意组件间通信。
### 1.18.2 何时使用?
多个组件需要共享数据时
### 1.18.3 搭建vuex环境
1. 创建文件:```src/store/index.js```
```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
})
```
2. 在```main.js```中创建vm时传入```store```配置项
```js
......
//引入store
import store from './store'
......
//创建vm
new Vue({
el:'#app',
render: h => h(App),
store
})
```
### 1.18.4 基本使用
1. 初始化数据、配置```actions```、配置```mutations```,操作文件```store.js```
```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,
})
```
2. 组件中读取vuex中的数据:```$store.state.sum```
3. 组件中修改vuex中的数据:```$store.dispatch('action中的方法名',数据)``` 或 ```$store.commit('mutations中的方法名',数据)```
> 备注:若没有网络请求或其他业务逻辑,组件中也可以越过actions,即不写```dispatch```,直接编写```commit```
### 1.18.5 getters的使用
1. 概念:当state中的数据需要经过加工后再使用时,可以使用getters加工。
2. 在```store.js```中追加```getters```配置
```js
......
const getters = {
bigSum(state){
return state.sum * 10
}
}
//创建并暴露store
export default new Vuex.Store({
......
getters
})
```
3. 组件中读取数据:```$store.getters.bigSum```
### 1.18.6 四个map方法的使用
1. <strong>mapState方法:</strong>用于帮助我们映射```state```中的数据为计算属性
```js
computed: {
//借助mapState生成计算属性:sum、school、subject(对象写法)
...mapState({sum:'sum',school:'school',subject:'subject'}),
//借助mapState生成计算属性:sum、school、subject(数组写法)
...mapState(['sum','school','subject']),
},
```
2. <strong>mapGetters方法:</strong>用于帮助我们映射```getters```中的数据为计算属性
```js
computed: {
//借助mapGetters生成计算属性:bigSum(对象写法)
...mapGetters({bigSum:'bigSum'}),
//借助mapGetters生成计算属性:bigSum(数组写法)
...mapGetters(['bigSum'])
},
```
3. <strong>mapActions方法:</strong>用于帮助我们生成与```actions```对话的方法,即:包含```$store.dispatch(xxx)```的函数
```js
methods:{
//靠mapActions生成:incrementOdd、incrementWait(对象形式)
...mapActions({incrementOdd:'jiaOdd',incrementWait:'jiaWait'})
//靠mapActions生成:incrementOdd、incrementWait(数组形式)
...mapActions(['jiaOdd','jiaWait'])
}
```
4. <strong>mapMutations方法:</strong>用于帮助我们生成与```mutations```对话的方法,即:包含```$store.commit(xxx)```的函数
```js
methods:{
//靠mapActions生成:increment、decrement(对象形式)
...mapMutations({increment:'JIA',decrement:'JIAN'}),
//靠mapMutations生成:JIA、JIAN(对象形式)
...mapMutations(['JIA','JIAN']),
}
```
> 备注:mapActions与mapMutations使用时,若需要传递参数需要:在模板中绑定事件时传递好参数,否则参数是事件对象。
### 1.18.7 模块化+命名空间
1. 目的:让代码更好维护,让多种数据分类更加明确。
2. 修改```store.js```
```javascript
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
}
})
```
3. 开启命名空间后,组件中读取state数据:
```js
//方式一:自己直接读取
this.$store.state.personAbout.list
//方式二:借助mapState读取:
...mapState('countAbout',['sum','school','subject']),
```
4. 开启命名空间后,组件中读取getters数据:
```js
//方式一:自己直接读取
this.$store.getters['personAbout/firstPersonName']
//方式二:借助mapGetters读取:
...mapGetters('countAbout',['bigSum'])
```
5. 开启命名空间后,组件中调用dispatch
```js
//方式一:自己直接dispatch
this.$store.dispatch('personAbout/addPersonWang',person)
//方式二:借助mapActions
...mapActions('countAbout',{incrementOdd:'jiaOdd',incrementWait:'jiaWait'})
```
6. 开启命名空间后,组件中调用commit
```js
//方式一:自己直接commit
this.$store.commit('personAbout/ADD_PERSON',person)
//方式二:借助mapMutations
...mapMutations('countAbout',{increment:'JIA',decrement:'JIAN'}),
```
## 1.19 路由
1. 理解: 一个路由(route)就是一组映射关系(key - value),多个路由需要路由器(router)进行管理。
2. 前端路由:key是路径,value是组件。
### 1.19.1 基本使用
1. 安装vue-router,命令:```npm i vue-router```
2. 应用插件:```Vue.use(VueRouter)```
3. 编写router配置项:
```js
//引入VueRouter
import VueRouter from 'vue-router'
//引入Luyou 组件
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
```
#
4. 实现切换(active-class可配置高亮样式)
```vue
<router-link active-class="active" to="/about">About</router-link>
```
5. 指定展示位置
```vue
<router-view></router-view>
```
### 1.19.2 几个注意点
1. 路由组件通常存放在```pages```文件夹,一般组件通常存放在...(9398 characters truncated)
## 1.20 响应式原理
### 1.20.1 概述
Vue2 使用 `Object.defineProperty` 实现数据响应式,当数据发生变化时,Vue 能自动更新视图。
### 1.20.2 实现原理
```js
// 模拟 Vue 响应式原理
function defineReactive(obj, key, val) {
Object.defineProperty(obj, key, {
get() {
console.log(`get ${key}: ${val}`);
return val;
},
set(newVal) {
if (newVal !== val) {
console.log(`set ${key}: ${newVal}`);
val = newVal;
}
}
});
}
const data = { name: 'Vue' };
defineReactive(data, 'name', data.name);
data.name = 'Vue2'; // set name: Vue2
```
### 1.20.3 局限性
- 无法检测对象属性的添加或删除。
- 无法检测数组的变化,Vue 重写了数组的部分方法(`push`, `pop`, `shift`, `unshift`, `splice`, `sort`, `reverse`)来实现响应式。
## 1.21 组件生命周期
### 1.21.1 生命周期图示
![Vue2 生命周期图示](https://vuejs.org/images/lifecycle.png)
### 1.21.2 生命周期钩子函数
| 钩子函数 | 描述 |
|----------------|----------------------------------------------------------------------|
| beforeCreate | 实例初始化之后,数据观测 (data observer) 和 event/watcher 事件配置之前被调用。 |
| created | 实例已经创建完成之后被调用。在这一步,实例已完成以下的配置:数据观测 (data observer),属性和方法的运算,watch/event 事件回调。然而,挂载阶段还没开始,`$el` 属性目前不可见。 |
| beforeMount | 在挂载开始之前被调用:相关的 `render` 函数首次被调用。 |
| mounted | 实例被挂载后调用,这时 `el` 被新创建的 `vm.$el` 替换了。如果根实例挂载到了一个文档内的元素上,当 `mounted` 被调用时 `vm.$el` 也在文档内。 |
| beforeUpdate | 数据更新时调用,发生在虚拟 DOM 打补丁之前。这里适合在更新之前访问现有的 DOM,比如手动移除已添加的事件监听器。 |
| updated | 由于数据更改导致的虚拟 DOM 重新渲染和打补丁,在这之后会调用该钩子。当这个钩子被调用时,组件 DOM 已经更新,所以你现在可以执行依赖于 DOM 的操作。 |
| beforeDestroy | 实例销毁之前调用。在这一步,实例仍然完全可用。 |
| destroyed | 实例销毁后调用。该钩子被调用后,对应 Vue 实例的所有指令都被解绑,所有的事件监听器被移除,所有的子实例也都被销毁。 |
| activated | keep-alive 组件激活时调用。 |
| deactivated | keep-alive 组件停用时调用。 |
| errorCaptured | 当捕获一个来自子孙组件的错误时被调用。 |
### 1.21.3 示例代码
```vue
<template>
<div>{{ message }}</div>
</template>
<script>
export default {
data() {
return {
message: 'Hello, Vue2!'
};
},
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>
## 1.22 路由高级用法
### 1.22.1 动态路由匹配
```js
// 配置动态路由
const router = new VueRouter({
routes: [
{
path: '/user/:id',
component: User
}
]
});
// 在组件中获取参数
export default {
computed: {
userId() {
return this.$route.params.id;
}
}
};
```
### 1.22.2 嵌套路由
```js
// 配置嵌套路由
const router = new VueRouter({
routes: [
{
path: '/user/:id',
component: User,
children: [
{
path: 'profile',
component: UserProfile
},
{
path: 'posts',
component: UserPosts
}
]
}
]
});
```
### 1.22.3 编程式导航
```js
// 跳转到指定路由
this.$router.push('/user/123');
// 返回上一个页面
this.$router.go(-1);
```
### 1.22.4 路由守卫
```js
// 全局前置守卫
router.beforeEach((to, from, next) => {
if (to.meta.requiresAuth && !isAuthenticated) {
next('/login');
} else {
next();
}
});
// 路由独享守卫
const router = new VueRouter({
routes: [
{
path: '/admin',
component: Admin,
beforeEnter: (to, from, next) => {
if (!isAdmin) {
next('/');
} else {
next();
}
}
}
]
});
// 组件内守卫
export default {
beforeRouteEnter(to, from, next) {
// 在渲染该组件的对应路由被 confirm 前调用
// 不能获取组件实例 `this`
// 因为当守卫执行前,组件实例还没被创建
},
beforeRouteUpdate(to, from, next) {
// 在当前路由改变,但是该组件被复用时调用
// 举例来说,对于一个带有动态参数的路径 `/foo/:id`,在 `/foo/1` 和 `/foo/2` 之间跳转的时候,
// 由于会渲染同样的 `Foo` 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
// 可以访问组件实例 `this`
},
beforeRouteLeave(to, from, next) {
// 导航离开该组件的对应路由时调用
// 可以访问组件实例 `this`
}
};
## 1.23 虚拟 DOM 和 Diff 算法
### 1.23.1 虚拟 DOM
虚拟 DOM 是一个轻量级的 JavaScript 对象,它是对真实 DOM 的抽象描述。Vue 使用虚拟 DOM 来提高渲染性能,避免频繁操作真实 DOM。
### 1.23.2 Diff 算法
Diff 算法用于比较新旧虚拟 DOM 的差异,只更新需要更新的部分。Vue 的 Diff 算法采用双端比较的方式,时间复杂度为 O(n)。
### 1.23.3 示例代码
```js
// 虚拟 DOM 示例
const vnode = {
tag: 'div',
props: {
id: 'app'
},
children: [
{
tag: 'p',
children: ['Hello, Vue2!']
}
]
};
```
## 1.24 服务端渲染 (SSR)
### 1.24.1 概述
服务端渲染是指在服务器端将组件渲染成 HTML 字符串,然后发送给客户端。Vue 提供了 `vue-server-renderer` 来支持服务端渲染。
### 1.24.2 优势
- 更好的 SEO,搜索引擎可以直接抓取渲染后的 HTML 内容。
- 更快的首屏加载速度,用户可以更快地看到页面内容。
### 1.24.3 示例代码
```js
const Vue = require('vue');
const renderer = require('vue-server-renderer').createRenderer();
const app = new Vue({
template: '<div>Hello, Vue2 SSR!</div>'
});
renderer.renderToString(app, (err, html) => {
if (err) throw err;
console.log(html);
});
```
## 1.25 单元测试
### 1.25.1 常用工具
- **Jest**:一个 JavaScript 测试框架,提供了简单易用的 API 和丰富的断言库。
- **Vue Test Utils**Vue 官方提供的测试工具库,用于测试 Vue 组件。
### 1.25.2 示例代码
```vue
<!-- HelloWorld.vue -->
<template>
<div>{{ message }}</div>
</template>
<script>
export default {
data() {
return {
message: 'Hello, Vue2!'
};
}
};
</script>
```
```js
// HelloWorld.spec.js
import { shallowMount } from '@vue/test-utils';
import HelloWorld from './HelloWorld.vue';
describe('HelloWorld.vue', () => {
it('renders message', () => {
const message = 'Hello, Vue2!';
const wrapper = shallowMount(HelloWorld);
expect(wrapper.text()).toMatch(message);
});
});
```
## 1.26 性能优化
### 1.26.1 路由懒加载
```js
const Foo = () => import('./Foo.vue');
const router = new VueRouter({
routes: [
{
path: '/foo',
component: Foo
}
]
});
```
### 1.26.2 异步组件
```js
const AsyncComponent = () => ({
// 需要加载的组件 (应该是一个 `Promise` 对象)
component: import('./AsyncComponent.vue'),
// 异步组件加载时使用的组件
loading: LoadingComponent,
// 加载失败时使用的组件
error: ErrorComponent,
// 展示加载时组件的延时时间。默认值是 200 (毫秒)
delay: 200,
// 如果提供了超时时间且组件加载也超时了,
// 则使用加载失败时使用的组件。默认值是:`Infinity`
timeout: 3000
});
```
### 1.26.3 防抖和节流
```js
// 防抖函数
function debounce(fn, delay) {
let timer = null;
return function(...args) {
if (timer) clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(this, args);
}, delay);
};
}
// 节流函数
function throttle(fn, interval) {
let lastTime = 0;
return function(...args) {
const now = Date.now();
if (now - lastTime >= interval) {
fn.apply(this, args);
lastTime = now;
}
};
}
```
## 1.27 响应式 API 补充
### 1.27.1 `vm.$set` 和 `vm.$delete`
由于 Vue2 无法检测对象属性的添加或删除,可以使用 `vm.$set` 和 `vm.$delete` 来实现响应式更新。
```js
// 添加属性
this.$set(this.obj, 'newProp', 'value');
// 删除属性
this.$delete(this.obj, 'prop');
```
### 1.27.2 `vm.$watch`
```js
// 监听数据变化
this.$watch('message', (newVal, oldVal) => {
console.log(`message changed from ${oldVal} to ${newVal}`);
});
// 深度监听
this.$watch('obj', (newVal, oldVal) => {
console.log('obj changed');
}, {
deep: true
});
// 立即执行
this.$watch('message', (newVal) => {
console.log(`message is ${newVal}`);
}, {
immediate: true
});
```
## 1.28 自定义指令
### 1.28.1 全局指令
```js
// 注册一个全局自定义指令 `v-focus`
Vue.directive('focus', {
// 当被绑定的元素插入到 DOM 中时...
inserted: function (el) {
// 聚焦元素
el.focus();
}
});
// 在模板中使用
<template>
<input v-focus>
</template>
```
### 1.28.2 局部指令
```vue
<template>
<div v-color="color">Hello, Vue2!</div>
</template>
<script>
export default {
data() {
return {
color: 'red'
};
},
directives: {
color: {
bind(el, binding) {
el.style.color = binding.value;
}
}
}
};
</script>
```
## 1.29 过滤器
### 1.29.1 全局过滤器
```js
// 注册一个全局过滤器
Vue.filter('capitalize', function (value) {
if (!value) return '';
value = value.toString();
return value.charAt(0).toUpperCase() + value.slice(1);
});
// 在模板中使用
<template>
<div>{{ message | capitalize }}</div>
</template>
```
### 1.29.2 局部过滤器
```vue
<template>
<div>{{ message | formatDate }}</div>
</template>
<script>
export default {
data() {
return {
message: new Date()
};
},
filters: {
formatDate(value) {
return new Date(value).toLocaleDateString();
}
}
};
</script>
## 1.30 依赖注入
### 1.30.1 `provide` 和 `inject`
```vue
<!-- 父组件 -->
<template>
<ChildComponent />
</template>
<script>
import ChildComponent from './ChildComponent.vue';
export default {
provide: {
message: 'Hello from parent!'
},
components: {
ChildComponent
}
};
</script>
<!-- 子组件 -->
<template>
<div>{{ message }}</div>
</template>
<script>
export default {
inject: ['message']
};
</script>
# 2. Vue3
## 2.1 组合式 API
### 2.1.1 setup 函数
1. **功能**:Vue3 中用于组合组件逻辑的入口函数,在组件创建之前执行,`data` 和 `methods` 之前。
2. **参数**
- `props`:父组件传递的属性,是响应式的,不能使用 ES6 解构,否则会失去响应性。
- `context`:上下文对象,包含 `attrs`、`slots`、`emit` 等属性。
3. **返回值**:返回一个对象,对象中的属性和方法可以在模板中使用。
```vue
<template>
<div>
<p>{{ count }}</p>
<button @click="increment">加 1</button>
</div>
</template>
<script>
export default {
setup() {
let count = ref(0)
const increment = () => {
count.value++
}
return {
count,
increment
}
}
}
</script>
```
### 2.1.2 ref 和 reactive
1. **ref**:用于创建基本类型的响应式数据,通过 `.value` 访问和修改值。
```javascript
import { ref } from 'vue'
const count = ref(0)
console.log(count.value) // 0
count.value++
```
2. **reactive**:用于创建对象类型的响应式数据,直接访问和修改属性。
```javascript
import { reactive } from 'vue'
const person = reactive({
name: '张三',
age: 20
})
console.log(person.name) // 张三
person.age = 21
```
## 2.2 响应式系统
### 2.2.1 响应式原理
Vue3 使用 Proxy 对象实现响应式系统,相比 Vue2 的 `Object.defineProperty`,Proxy 可以监听对象属性的新增、删除,以及数组的变化。
### 2.2.2 计算属性和监听器
1. **computed**:计算属性,根据响应式数据计算出新的值。
```javascript
import { ref, computed } from 'vue'
const count = ref(0)
const doubleCount = computed(() => count.value * 2)
```
2. **watch**:监听响应式数据的变化,执行回调函数。
```javascript
import { ref, watch } from 'vue'
const count = ref(0)
watch(count, (newValue, oldValue) => {
console.log(`count 从 ${oldValue} 变为 ${newValue}`)
})
```
## 2.3 新组件特性
### 2.3.1 Teleport
1. **功能**:将组件的模板内容渲染到 DOM 中的任意位置,常用于创建模态框、提示框等。
```vue
<template>
<div>
<button @click="isModalOpen = true">打开模态框</button>
<teleport to="body">
<div v-if="isModalOpen" class="modal">
<p>这是一个模态框</p>
<button @click="isModalOpen = false">关闭</button>
</div>
</teleport>
</div>
</template>
<script>
export default {
data() {
return {
isModalOpen: false
}
}
}
</script>
```
### 2.3.2 Suspense
1. **功能**:用于处理异步组件的加载状态,提供加载中的占位内容。
```vue
<template>
<Suspense>
<template #default>
<AsyncComponent />
</template>
<template #fallback>
<div>加载中...</div>
</template>
</Suspense>
</template>
<script>
import AsyncComponent from './AsyncComponent.vue'
export default {
components: {
AsyncComponent
}
}
</script>
```
## 2.4 新 API
### 2.4.1 provide 和 inject
1. **功能**:实现跨层级组件通信,`provide` 在父组件中提供数据,`inject` 在子组件中注入数据。
```javascript
// 父组件
import { provide, ref } from 'vue'
export default {
setup() {
const count = ref(0)
provide('count', count)
}
}
// 子组件
import { inject } from 'vue'
export default {
setup() {
const count = inject('count')
return {
count
}
}
}
```
### 2.4.2 自定义 hooks
1. **功能**:将可复用的逻辑提取到自定义函数中,提高代码的复用性。
```javascript
// useCounter.js
import { ref } from 'vue'
export function useCounter() {
const count = ref(0)
const increment = () => {
count.value++
}
return {
count,
increment
}
}
// 组件中使用
import { useCounter } from './useCounter.js'
export default {
setup() {
const { count, increment } = useCounter()
return {
count,
increment
}
}
}
```
## 2.5 Vue3 脚手架
### 2.5.1 创建项目
使用 Vite 创建 Vue3 项目:
```bash
npm init vite@latest my-vue3-app -- --template vue
cd my-vue3-app
npm install
npm run dev
```
### 2.5.2 项目结构
```
├── node_modules
├── public
│ └── index.html
├── src
│ ├── assets
│ ├── components
│ ├── App.vue
│ └── main.js
├── index.html
├── package.json
├── vite.config.js
└── README.md
```
## 2.6 Vue3 与 Vue2 的区别
### 2.6.1 性能优化
1. **虚拟 DOM 优化**Vue3 采用了静态提升、Patch Flag 等技术,减少了虚拟 DOM 的比对开销。
2. **编译优化**:Vue3 的编译器会对模板进行静态分析,将静态节点提取出来,减少运行时的计算量。
### 2.6.2 语法变化
1. **组合式 API**:替代了 Vue2 的选项式 API,使代码逻辑更加清晰,易于复用。
2. **Composition API 生命周期**`setup` 函数替代了 `beforeCreate` 和 `created` 钩子,其他生命周期钩子前加 `on`,如 `onMounted`。
```javascript
import { onMounted } from 'vue'
setup() {
onMounted(() => {
console.log('组件挂载完成')
})
}
```
### 2.6.3 生态系统
1. **Vue Router**Vue3 需要使用 `vue-router@4` 版本。
2. **Vuex**Vue3 需要使用 `vuex@4` 版本。
3. **第三方库**:部分第三方库需要更新到支持 Vue3 的版本。
```