优势
- 性能更好
- 体积更小
- 更好的ts支持
- 更好的代码组织
- 更好的逻辑抽离
- 更多新功能
Vue3生命周期
- Options API 生命周期
- Composition API 生命周期
Options API 生命周期
- beforeDestroy 改为 beforeUnmount
- destroyed 改为 unmount
- 其他沿用Vue2的生命周期
Composition API 生命周期
- setup //相当于beforeCreate 和 created
- onBeforeMount // beforeMount
- onMount // mount
- onBeforeUpdate // beforeUpdate
- onUpdate // update
- onBeforeUnmount // beforeUnmount
- onUnmount // unmount
Composition API 带来了什么
- 更好的代码组织
- 更好的逻辑复用
- 更好的类型推到
如何理解ref toRef 和 toRefs
-
Ref
- 生成值类型的响应式数据
- 可用于模板和reactive
- 通过.value修改值
- 获取dom元素
import { ref } from 'vue'
export default {
setup(){
// 如果是用ref声明,建议在后面加上Ref后缀, 后面修改的时候不会显得很怪异
const ageRef = ref(20)
ageRef.value = 21
}
}
-
toRef 和 toRefs
- 针对一个响应式对象(reactive封装)的prop
- 创建一个ref,具有响应式
- 两者保持引用关系
import { ref, reactive, toRefs } from 'vue'
export default {
setup(){
const state = reactive({
age: 20,
name: 'yoy'
})
// const { age,name } = state 直接解构会丢失响应式
const ageRef = toRef(state,'age')
const stateAsRefs = toRefs(state)
const { age: ageRef, name: nameRef } = stateAsRefs // 每一个属性,都是ref
return stateAsRefs
}
}
ref toRef和toRefs 的最佳使用方式
// 合成函数返回响应式对象
import { reactive, toRefs } from 'vue'
function useFeatureX() {
const state = reactive({
x:1,
y:2
})
return toRefs(state)
}
export default {
setup(){
// 可以在不失去响应式的情况下进行解构
const {x,y} = useFeatureX()
return { x, y }
}
}
- 用reactive做对象的响应式,用ref做值类型的响应式
- setup中返回toRefs(state), 或者toRef(state,‘xx’)
- 合成函数返回响应式对象时,使用toRefs
为什么需要用ref
- 返回值类型,会丢失响应式
- 如在setup, computed, 合成函数, 都有可能返回值类型
- Vue 不定义ref, 用户将自造ref, 造成混乱
为什么需要.value
- ref是一个对象(不丢失响应式), value存储值
- 通过.value属性的get 和 set 实现响应式
- 用于模板,reactive时,不需要.value, 其他情况都需要
// 错误
function computed(getter) {
let value = 0
setTimeout(()=>{
value = getter()
})
return value
}
// 正确
function computed1(getter) {
const ref = {
value: null
}
setTimeout(()=>{
ref.value = getter()
})
return ref
}
// 测试
let test = computed(()=> 100) // 0
let test1 = computed(()=> 100) // {value: 100}
为何需要 toRef 和 toRefs
- 初衷:不丢失响应式的情况下,把对象数据
分解/扩散
- 前提: 针对的是响应式对象(reactive封装的)非普通对象
- 注意:
不创建
响应式,而是延续
响应式
Composition API 实现逻辑复用
- 抽离逻辑代码到一个函数
- 函数命令约定为useXxxx格式 (React Hooks 也是)
// demo 获取鼠标移动位置
import { ref, onMounted, onUnMounted } from 'vue'
function useMousePosition(){
const x = ref(0)
const y = ref(0)
function update (e) {
x.value = e.pageX
y.value = e.pageY
}
onMounted(()=>{
window.addEventListener('mousemove', update )
})
onMounted(()=>{
window.removeEventListener('mousemove', update )
})
return {
x,
y
}
}
// test
export default {
setup(){
const {x, y} = useMousePosition()
return {
x,y
}
}
}
vue3 和 vue2 响应式对比
vue2使用Object.defineProperty, vue3 使用Proxy
-
Object.defineProperty的缺点
- 深度监听需要一次性递归
- 无法监听新增属性/删除属性(Vue.set Vue.delete)
- 无法监听原生数组,需要重写覆盖数组方法 Proxy的缺点
- Proxy无法兼容所有浏览器,无法polyfill
// Object.defineProperty
const 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)
}
})
function observer(target) {
if (typeof target !== 'object' || target == null ) {
// 不是对象或者数组,则直接返回
return target
}
// 如果是数组则需要修改监听的对象数组的原型,避免全局污染
if (Array.isArray(target)) {
target.__proto__ = arrProto
}
for (let key in target) {
defineReactive(target, key, target[key])
}
}
function defineReactive(target, key, value){
// 深度监听
observer(value)
Object.defineProperty(target, key, {
get(){
return value
},
set(newValue){
if (newValue !== value) {
// 深度监听
observer(newValue)
value = newValue
// 更新视图
updateView()
}
}
})
}
// test
const data = {
name: 'zhangsan',
age: 20,
info:{ adddres: 'guangzhou'},
nums: [10,20,30]
}
observer(data)
data.name = 'list'
data.x = '100' // 新增属性, 监听不到 -- 所以有Vue.set
delete data.name // 删除属性,监听不到 -- 所以有Vue.delete
data.nums.push(40)
// Proxy
function reactive(target = {}){
if (typeof target !== 'object' || target == null ) {
// 不是对象或者数组,则直接返回
return target
}
// 代理配置
const proxyConfig = {
get(target, key, receiver){
// 只处理本身 (非原型的)属性
const ownKeys = Reflect.ownKeys(target)
const result = Reflect.get(target, key, receiver)
// 深度监听
// 性能如何提升的? -> 这里是在get的时候在进行(递归)深度代理,
// 而Object.definePropert则需要一次性递归监听
return reactive(result)
},
set(target,key, val, receiver){
// 重复的数据,不处理
if (val === target[key]) {
return true
}
const result = Reflect.set(target, key, val, receiver)
return result
},
deleteProperty(target,key){
const result = Reflect.deleteProperty(target, key)
return result
}
}
const observe = new Proxy(target, proxyConfig)
return observe
}
vue3 为何要比 vue2快
- Proxy响应式
- PatchFlag
- hoistStatic
- cacheHandler
- SSR优化
- tree-shaking
-
PatchFlag
- 编译模板时,动态节点做标记
- 标记,分为不同类型,如 TEXT,PROPS
-
diff算法时,可以区分静态节点,以及不同类型的动态节点
如果是纯静态的则不需要比较,是不会变化的,在输入的时候会新增PatchFlag参数,对比的时候在根据PatchFlag参数进行对比
hoistStatic
- 将静态节点的定义,提升到父作用域,缓存起来
- 多个相邻的静态节点, 会被合并起来
- 典型的拿空间换时间的优化策略
在输入的时候加了-1的标识,标记该节点已经被缓存起来,多个相邻的静态节点, 会被合并起来
-
cacheHandler
-
缓存事件
先去缓存中找cache[0],没有则定义一个函数
SSR
- 静态节点直接输出,绕过了vdom
-
动态节点,还是需要动态渲染
tree shaking
-
编译时, 根据不同的情况,引入不同的API
加入插值表达式,import的参数增多了