持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第6天,点击查看活动详情
vue3技术调研
vue3比vue2有什么优势
- 性能更好
- 体积更小
- 更好的ts支持
- 更好的代码组织(不同业务逻辑的代码在不同区域)
- 更好的逻辑抽离(因为不同逻辑的代码区域不混乱因此更好的逻辑抽离)
- 更多新功能
描述vue3生命周期
Options API生命周期
- beforeDestroy改为beforeUnmount
- destroyed改为unmouted
- 其他沿用vue2的生命周期
Composition API生命周期
- 各生命周期调用时,要先从vue中引入,写在setup中
- setup等于beforeCreate和created
- setup里的生命周期写法:onBeforeMount(()=>{})
- onBeforeMount
- onMounted
- onBeforeUpdate
- OnUpdated
- onBeforeUnmount
- onUmounted
如何看待Composition API和Options API
Composition API带来了什么
- 更好的代码组织(抽离逻辑到函数,统一setup中调用再返回出去)
- 更好的逻辑复用
- 更好的类型推导( 更符合js语法,不使用this.fn()语法糖调用methods里的方法)
Composition API和Options API如何选择
- 不建议共用,会引起混乱
- 小型项目、业务逻辑简单,用Options API
- 中大型项目,逻辑复杂,用Composition API
别误解Composition API,应该按需使用
- Composition API属于高阶技巧,不是基础必会
- Composition API是为解决复杂业务逻辑而设计的
- Composition API就像Hooks在React中的地位
如何理解ref、toRef和toRefs
是什么
ref
<p>{{ageRef}}-{{state.name}}</p>
<!-- 可以用于模板 -->
<p ref="elemRef"></p>
// 先从vue中引入
import {ref,reactive, onMounted} = from 'vue'
setup(){
const ageRef = ref(20)
const nameRef = ref('小洪')
const elemRef = ref(null)
const state = reactive({
name:nameRef
})
onMounted(()=>{
console.log('ref template',elemRef.value.innerHTml)
})
return {
state,
ageRef,
eleRef
}
}
- ref生成值类型的响应式数据,reactive创建引用类型的响应式数据,ref的本质是通过reactive创建的,Ref(10)=>Reactive({value:10})
- 可用于模板和reactive
- 通过.value修改值
- 除了在模版或者在reactive可以直接用,其他地方修改要用ageRef.value的方式修改
toRef
<p>{{ageRef}}-{{state.age}}-{{state.name}}</p>
const state = reactive({
age: 20,
name: '小洪'
})
const ageRef = roRef(state, 'age')
setTimeout(() => {
state.age = 25
},1500)
setTimeout(() => {
ageRef.value = 30
})
- 针对一个响应式对象(reactive封装)的prop
- 创建一个ref,具有响应式
- toRef 如果用于普通对象(非响应式对象,如上例子不用reactive包裹),产出的结果不具备响应式,即1.5s后模版内容不会发生变化
- 一个对象要实现响应式的时候用reactive即可 一个对象里的某个属性要实现响应式用toRef
- 两者保持引用关系
toRefs
- 将响应式对象(reactive封装)转换为普通对象
- 如果不用toRefs转换,响应式对象里的属性直接解构return到模版将失去响应式
- 对象的每个prop都是对应的ref
- 两者保持引用关系
最佳使用方式
- 用reactive做对象的响应式,用ref做值类型响应式
- setup中返回toRefs(state)或toRef(state,'xxx')
- ref的变量名都用xxxRef
- 合成函数返回响应式对象,使用toRefs,方便使用解构
function useFeatureX() {
const state = reactive({
age: 20,
name: '小洪'
})
// 返回数据为ref
return toRefs(state)
}
export default {
name: 'WhyRef'
setup(){
// 可以在不失去响应式的情况下破坏结构
const {age , name} = useFeatureX()
}
}
进阶、深入理解
为什么需要ref
- 返回值类型,会丢失响应式,proxy只对对象起作用
- 如在setup、computed、合成函数,都有可能返回值类型
- vue如不定义ref,用户将自造ref,反而混乱
为何ref需要value属性
- ref是一个对象(不丢失响应式),value存储值
- 通过.value属性的get和set实现响应式
- 用于模版,reactive时,不需要.value,其他情况都需要
为什么需要toRef和toRefs
- 初衷:不丢失响应式的情况下,把对象进行分解 /扩散,即结构
- 前提:针对的是响应式对象(reactive封装的),非普通对象
- 注意:不创造响应式,而是延续响应式
vue3升级了哪些重要功能
createApp
vue2
const app =new Vue({/*选项*/})
Vue.use(/*…*/)
Vue.mixin(/*…*/)
vue3
const app = Vue.CreateApp({/*选项*/})
app.use(/*…*/)
app.mixin(/*…*/)
emits属性
<!-- 父组件 -->
<HelloWorld :msg="msg" @onSayHello="sayHello"></HelloWorld>
<!-- 子组件 -->
export default {
name: 'HelloWorld'
props: {
msg: String
},
emits:['onSayHello']
setup(props, { emit }){
emit('onSayHello','aaa')
}
}
- 父组件引入子组件,传入事件时,子组件要用emits:[‘onSayHello’]接收,事件要用on开头
- setup的第二个参数接收{emits}
- 通过emit(‘onSayHello’,’aaa’)调用
生命周期
多事件
- 在methods里定义one two两个函数
- 如@click=“one(event)”
Frament
vue2 Template里只能有一个节点
vue3 可以有多个节点
移除.sync
vue2
v-bind:title.sync="title"
vue3改为
v-model:title="title"
异步组件的写法
vue2
new Vue({
components: {
'my-component': () => import('./my-component.vue')
}
})
vue3
import { createApp, defineAsyncComponent } from 'vue'
createApp({
AsyncComponent: defineAsyncComponent(() => {
import('./my-component.vue')
})
})
移除filter
<!-- 以下filter在vue3中不可用了 -->
<!-- 在双花括号中 -->
{{ message | capitalize }}
<!-- 在 `v-bind` 中 -->
<div v-bind:id="rawId | formatId"></div>
Teleport
把组件放到外面
<button @click="modalOpen = true">
open modal button
</button>
<teleport to="body">
<div v-if="modalOpen" class="modal">
<div>
teleport dialog
<button @click="modalOpen = false">close</button>
</div>
</div>
</teleport>
Suspense
相当于封装了一个插槽给我们用
<Suspense>
<template>
<!-- 是一个异步组件 -->
<Test1/>
</template>
<!-- #fallback 就是一个具名插槽。
即 Suspense组件内部,有两个slot,
其中一个具名为fallback -->
<template #fallback>
loading...
</template>
</Suspense>
Composition API
- reactive
- ref相关
- readonly
- watch和watchEffect
- setup
- 生命周期钩子函数
Composition API如何实现代码逻辑复用
vue3如何实现响应式
回顾Vue2.x的Object.defineProperty
// 触发更新视图
function updateView() {
console.log('视图更新')
}
// 重新定义数组原型
const oldArrayProperty = Array.prototype
// 创建新对象,原型指向 oldArrayProperty ,再扩展新的方法不会影响原型
const arrProto = Object.create(oldArrayProperty);
['push', 'pop', 'shift', 'unshift', 'splice'].forEach(methodName => {
arrProto[methodName] = function () {
updateView() // 触发视图更新
oldArrayProperty[methodName].call(this, ...arguments)
// Array.prototype.push.call(this, ...arguments)
}
})
// 重新定义属性,监听起来
function defineReactive(target, key, value) {
// 深度监听
observer(value)
// 核心 API
Object.defineProperty(target, key, {
get() {
return value
},
set(newValue) {
if (newValue !== value) {
// 深度监听
observer(newValue)
// 设置新值
// 注意,value 一直在闭包中,此处设置完之后,再 get 时也是会获取最新的值
value = newValue
// 触发更新视图
updateView()
}
}
})
}
// 监听对象属性
function observer(target) {
if (typeof target !== 'object' || target === null) {
// 不是对象或数组
return target
}
// 污染全局的 Array 原型
// Array.prototype.push = function () {
// updateView()
// ...
// }
if (Array.isArray(target)) {
target.__proto__ = arrProto
}
// 重新定义各个属性(for in 也可以遍历数组)
for (let key in target) {
defineReactive(target, key, target[key])
}
}
// 准备数据
const data = {
name: 'zhangsan',
age: 20,
info: {
address: '北京' // 需要深度监听
},
nums: [10, 20, 30]
}
// 监听数据
observer(data)
// 测试
// data.name = 'lisi'
// data.age = 21
// // console.log('age', data.age)
// data.x = '100' // 新增属性,监听不到 —— 所以有 Vue.set
// delete data.name // 删除属性,监听不到 —— 所有已 Vue.delete
// data.info.address = '上海' // 深度监听
data.nums.push(4) // 监听数组
缺点:
- 深度监听需要一次性递归,如果data比较长的话会造成卡住
- 无法监听新增属性/删除属性(需要用Vue.set Vue.delete)
- 无法原生监听数组,需要特殊处理
Proxy基本使用
// const data = {
// name: 'zhangsan',
// age: 20,
// }
const data = ['a', 'b', 'c']
const proxyData = new Proxy(data, {
get(target, key, receiver) {
// 只处理本身(非原型的)属性
const ownKeys = Reflect.ownKeys(target)
if (ownKeys.includes(key)) {
console.log('get', key) // 监听
}
const result = Reflect.get(target, key, receiver)
return result // 返回结果
},
set(target, key, val, receiver) {
// 重复的数据,不处理
if (val === target[key]) {
return true
}
const result = Reflect.set(target, key, val, receiver)
console.log('set', key, val)
// console.log('result', result) // true
return result // 是否设置成功
},
deleteProperty(target, key) {
const result = Reflect.deleteProperty(target, key)
console.log('delete property', key)
// console.log('result', result) // true
return result // 是否删除成功
}
})
Reflect作用
- 和Proxy能力一一对应
- 规范化、标准化、函数式 如:Reflect.has(obj,'a')比 js的 'a' in obj好用
- 替代掉Object上的工具函数 如Reflect.ownKeys(obj)替代Object.getOwnPropertyNames(obj)
Vue3如何用proxy实现响应式
// 创建响应式
function reactive(target = {}) {
if (typeof target !== 'object' || target == null) {
// 不是对象或数组,则返回
return target
}
// 代理配置
const proxyConf = {
get(target, key, receiver) {
// 只处理本身(非原型的)属性
const ownKeys = Reflect.ownKeys(target)
if (ownKeys.includes(key)) {
console.log('get', key) // 监听
}
const result = Reflect.get(target, key, receiver)
// 深度监听
// 性能如何提升的?什么时候用get到那一层再递归到那一层级,因此性能提升
return reactive(result)
},
set(target, key, val, receiver) {
// 重复的数据,不处理
if (val === target[key]) {
return true
}
const ownKeys = Reflect.ownKeys(target)
if (ownKeys.includes(key)) {
console.log('已有的 key', key)
} else {
console.log('新增的 key', key)
}
const result = Reflect.set(target, key, val, receiver)
console.log('set', key, val)
// console.log('result', result) // true
return result // 是否设置成功
},
deleteProperty(target, key) {
const result = Reflect.deleteProperty(target, key)
console.log('delete property', key)
// console.log('result', result) // true
return result // 是否删除成功
}
}
// 生成代理对象
const observed = new Proxy(target, proxyConf)
return observed
}
// 测试数据
const data = {
name: 'zhangsan',
age: 20,
info: {
city: 'beijing',
a: {
b: {
c: {
d: {
e: 100
}
}
}
}
}
}
const proxyData = reactive(data)
Proxy实现响应式优点
- 深度监听,性能更好
- 可监听 新增/删除 属性
- 可监听数组变化 总结:
- Proxy能规避Object.defineProperty的问题
- Proxy无法兼容所有浏览器,无法ployfill(webpack知识)
watch和watchEffect的区别是什么
- 两者都可监听data属性变化
- watch需要明确监听哪个属性
watch(numberRef, (newNumber, oldNumber) => {
console.log('ref watch', newNumber, oldNumber)
}
// , { // 配置项
// immediate: true // 初始化之前就监听,可选参数
// deep: true // 是否深度监听,可选参数
// }
)
- watchEffect会根据其中的属性,自动监听其变化
watchEffect(() => {
// 初始化时,一定会执行一次(收集要监听的数据)
console.log('hello watchEffect')
})
watchEffect(() => {
console.log('state.name', state.name)
})
watchEffect(() => {
console.log('state.age', state.age)
})
watchEffect(() => {
console.log('state.age', state.age)
console.log('state.name', state.name)
})
setup中如何获取组件实例
- 在setup和其他Composition API中没有this
- 可通过getCurrentInstance获取当前实例,代替this
- 若使用Options API可照常使用this
const instance = getCurrentInstance
console.log('instance', instance)
vue3为何比vue2快
Proxy响应式
PatchFlag
- 编译模板时,动态节点做标记
- 标记,分为不同的类型,如TEXT PROPS
- diff算法,可以区分静态节点,以及不同类型的动态节点
hoistStatic
- 将静态节点的定义,提升到父作用域,缓存起来
- 多个相邻的静态节点,会被合并起来
- 典型的拿空间换时间的优化策略
cacheHandler
- 缓存事件
SSR优化
- 静态节点直接输出,绕过了vdom
- 动态节点,还是需要动态渲染
tree-shaking
- 编译时,根据不同的情况,引入不同的API
vite是什么
...后续补充
Composition API和React Hooks的对比
...后续补充