引入
说起 vue3 的新特性,就会不由自主想到 vue3 和 vue2 之间的差异,例如:双向绑定、根节点数量、生命周期、this 等等,详细可以见这篇文章(参考)——
vue2和vue3的差异整理(轻松过度到vue3)_vue2和vue3区别-CSDN博客
咱们今天就主要来大概梳理一下 vue3 的新特性。
1 | composition API |
2 | 生命周期 |
3 | 异步组件 |
4 | 自定义指令 |
5 | teleport |
6 | 自定义hooks |
目录
引入
vue3 和 vue2 响应式原理的区别
vue2 : Object.defineProperty()
vue3 : proxy
vue3 代码实现
特性一:composition API
1. 创建
2. 添加响应式的变化
reactive
shallowReactive
ref
vue3 和 vue2 响应式原理的区别
vue2 : Object.defineProperty()
我们知道,vue2 是通过 Object.defineProperty 这个api,重新定义对象的 getter、setter 对数据进行劫持,以此监测数据的响应变更,触发视图的更新,从而实现双向绑定。
Object.defineProperty(官网):Object.defineProperty() - JavaScript | MDN
数据劫持(参考): 第二十一章 javascript数据代理(数据劫持)_js 数据代理-CSDN博客
那具体怎么实现的呢?我们可以试着手写一个:
// 初始数据
const initData = {value: 1
}
// 响应式数据
const data = {}Object.keys(initData).forEach(key => {Object.defineProperty(// 挂载对象data,// 挂载对象的 keykey,{// 挂载对象的 getter 和 setterget() {console.log("访问:", key)// 返回初始数据中的值return initData[key];},set(val) {console.log("修改", key)initData[key] = val;},})
})
在浏览器的控制台中测试:
因为添加的响应式数据 data 只能针对遍历后的 initData 进行监测,所以 data 无法获取到 initData 的新增属性,也就没法实现新增属性的双向绑定,这也是 vue2 的一个缺陷。
解决方法:$set
作用:可以对新增属性添加响应式的变化,并触发视图更新。
参考:
vue中$set用法详细讲解_vue $set-CSDN博客
这里简单介绍下 set ——
用法:Vue.set ( target , key , value )
set 是如何实现的:
1. null undefined boolean 等这些类型会报错 因为只有 object 才可以设置属性
2. 数组 : splice
取两个数组的最大值,作为最新值,利用 splice 将更新值加入数组之中。
因为 splice 是原型 (Array.prototype.splice) 上的一个方法 , 当 target 在收集依赖的时候,原型上的方法也会改变。此时会重新遍历数组,也就相当于是手动触发了一次更新~
3. 对象:判断 key 是否存在 ( target [ key ] = value )
存在就替换
不存在就判断target是否是响应式:是就替换,不是就报错。
( 如果是响应式的话,会用到 vue2 中的依赖收集,就会触发 defineReactive 进行更新 )
vue3 : proxy
proxy 是 js 提供的一种响应式的依赖,作用是作为一层拦截。当外界想要访问本身的对象时,需要通过一层拦截机制。所以 proxy 相当于是一层代理。
proxy(官网):Proxy() 构造函数 - JavaScript | MDN
使用:
// target : 需要拦截的目标
// handler : 拦截的方法
const proxy = new Proxy(target,handler)
代码实现 proxy :
const initData = {value: 1
}
const proxy = new Proxy(initData, {// receiver 用来接收数据get: (target, key, receiver) => {console.log("访问", key)// Reflect 针对当前数据进行拦截,是 proxy 默认提供的apireturn Reflect.get(target, key, receiver)},set: (target, key, value, receiver) => {console.log("修改", key)Reflect.set(target, key, value, receiver)}
})
Reflect(官网):https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Reflect
浏览器控制台测试效果:
可以看到,proxy 实现了数据的双向绑定。无论是源数据还是新增属性,都可以监测到变化从而触发视图更新。
vue3 代码实现
现在正式开始讲 vue3 的新特性了~ 😜
首先创建一个项目:
// 创建项目
pnpm create vite-app demo// 进入文件
cd demo// 安装依赖
pnpm install
vue2 和 vue3 入口文件的差异:
vue2 中的声明只有一个 vue 的实例,因为 vue2 是单例模式,因此是用 this 上各种各样的属性去获取到内容。
new Vue({...
})this.xxx
vue3 通过 createApp 绑定到 App 实例上,再通过 mount 绑定到 app 这个节点上,来涵盖多种实例。
import { createApp } from 'vue'
import App from './App.vue'
import './index.css'createApp(App).mount('#app')
可以看到:
vue3 支持多实例,多实例之间相互隔离,互不影响。
特性一:composition API
vue2 的写法容易冗杂,很容易单页面就写出几千行代码的情况。就算有 mixin 但是容易因为变量命名而导致冲突。
vue3 则提供了 composition API ,把零散的逻辑耦合在一起,按照功能拆分成不同的子模块,利用 hooks,更方便代码阅读和后期维护。
由面向业务编程转为函数式编程,按照代码的本质进行操作,减少单文件的量级。
用代码示例 composition API:
1. 创建
main.js
import { createApp } from 'vue'
import App from './App.vue'
import './index.css'// 引入
import Composition from './Compostion.vue'
// 挂载到创建的 composition 节点上
createApp(Composition).mount('#composition')createApp(App).mount('#app')
index.html
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><link rel="icon" href="/favicon.ico"/><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Vite App</title>
</head>
<body>
<div id="app"></div>
<!--composition-->
<div id="composition"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>
Composition.vue (vue文件一般用大驼峰命名)
<template><div><div>count:{{ count }}</div></div>
</template><script>
import { defineComponent } from 'vue'
export default defineComponent({// setup:为 composition api 提供的新的属性,类似于 vue2 中的 beforeCreated 生命周期setup(){const count = 0return {count}}
})
</script><style scoped lang=scss></style>
页面效果:
当然,这只是一个非常简单的示例,也没有响应式,而且在项目中,更多的还是挂载到 #app 节点上。
2. 添加响应式的变化
reactive
是一个函数,接收一个对象,返回一个响应式的数据结构。
如果接收的对象层级很深(对象嵌套对象),则会通过递归去调用、关联。
官网:https://cn.vuejs.org/api/reactivity-core.html#reactive
创建个 reactive :
import {defineComponent, reactive} from 'vue'
export default defineComponent({setup() {const obj = {name: 'alikami',age: 24,contact: {phone: '1234567890',email: 'alikami@gmail.com'}}const reactiveObj = reactive(obj)console.log(reactiveObj, "<------reactiveObj-----------✨✨✨✨")return {obj}}
})
控制台打印结果:
说明,用 reactive 创建的对象是一个 proxy ,也就是响应式对象。
而且,reactiveObj 内部的对象也是响应式的,因为是递归关联的:
const obj = {name: 'alikami',age: 24,contact: {phone: '1234567890',email: 'alikami@gmail.com'}}const reactiveObj = reactive(obj)const reactiveContact = reactiveObj.contactconsole.log(reactiveContact, "<----------reactiveContact-------✨✨✨✨")
打印结果:
shallowReactive
只针对表层内容做响应式创建,而不去递归。
官网:https://cn.vuejs.org/api/reactivity-advanced.html#shallowreactive
reactive 与 shallowReactive (参考):Vue3中shallowReactive 与 shallowRef 的用法-CSDN博客
举个栗子:
const obj = {name: 'alikami',age: 24,contact: {phone: '1234567890',email: 'alikami@gmail.com'}
}const shallowReactiveObj = shallowReactive(obj)
const shallowReactiveContact = shallowReactiveObj.contactconsole.log(shallowReactiveObj, "<------shallowReactiveObj-----------✨✨✨✨")
console.log(shallowReactiveContact, "<------shallowReactiveContact-----------✨✨✨✨")
打印的 shallowReactiveObj 值为:
这时也是一个 proxy 对象,但是当我们打印其中的 contact 的时候,就会发现这只是个普通对象了:
所以 shallowReactive 只是实现浅层的响应式,对于深层次的对象不再具有响应性。
如果想转为响应式,就需要用到 toRef 或是 toRefs。要了解的话可以参考这篇——
vue3中的ref,toRef,toRefs三个是干嘛的,有什么作用呢。_vue3 ref torefs-CSDN博客
按照我们举的例子来说,如果想把 shallowReactiveContact 转化为响应式的,可以这样:
const obj = {name: 'alikami',age: 24,contact: {phone: '1234567890',email: 'alikami@gmail.com'}
}const shallowReactiveObj = shallowReactive(obj)
const shallowReactiveContact = shallowReactiveObj.contact
// 用 toRefs 进行转换
const toRefsShallowReactiveContact = toRefs(shallowReactiveContact)console.log(toRefsShallowReactiveContact, "toRefsShallowReactiveContact")
打印结果为:
ref
根据当前给定值,创建一个响应式对象,通过 .value 来获取。
官网:https://cn.vuejs.org/api/reactivity-core.html#ref
举个栗子:
const obj = {name: 'alikami',age: 24,contact: {phone: '1234567890',email: 'alikami@gmail.com'}
}const refObj = ref(obj)
console.log(refObj, "<-----------------✨✨✨✨")
console.log(refObj.value, "<-----------------✨✨✨✨")
打印结果:
ref 创建的也是深层次的响应。而且ref 对象是可更改的,也就是说你可以为 .value
赋予新的值。它也是响应式的,即所有对 .value
的操作都将被追踪,并且写操作会触发与之相关的副作用。
还需要注意,官网中有这样一段话:
① 如果将一个对象赋值给 ref,那么这个对象将通过 reactive() 转为具有深层次响应式的对象。这也意味着如果对象中包含了嵌套的 ref,它们将被深层地解包。
② 若要避免这种深层次的转换,请使用 shallowRef() 来替代。
我们先为 ① 举个栗子:
const contact = ref(0)
const obj = reactive({name: 'alikami',age: 24,contact
})// 结果为 0 因为会自动解包,不用.value来获取值,但是响应式还存在
console.log(obj.contact, "<-----contact.value------------✨✨✨✨")
// 结果为 true
console.log(obj.contact === contact.value, "<------------✨✨✨✨")contact.value = 1
// 结果为 1
console.log(contact.value, "<-----contact.value------------✨✨✨✨")
// 结果为 1
console.log(obj.contact, "<------obj.contact-----------✨✨✨✨")obj.contact = 2
// 结果为 2
console.log(contact.value, "<-----contact.value------------✨✨✨✨")
// 结果为 2
console.log(obj.contact, "<------obj.contact-----------✨✨✨✨")
响应式嵌套响应式,还是响应式的值。reactive 会把 ref 解包,这也是一种二者之间的关系。
未完待续。。。