vue手写响应式_Vue3响应式原理 + 手写reactive

reactive## 关于Vue3

话说,Vue3已经进行到rc4版本了,4月份beta发布的时候前端圈红红火火,不知道大家开始学了没

整理了一些资源,现在开始学习应该还不算晚[狗头]

vue2 响应式原理回顾对象响应化:遍历每个key,通过 Object.defineProperty API定义getter,setter// 伪代码

function observe(){

if(typeof obj !='object' || obj == null){

return

}

if(Array.isArray(obj)){

Object.setPrototypeOf(obj,arrayProto)

}else{

const keys = Object.keys()

for(let i=0;i

const key = keys[i]

defineReactive(obj,key,obj[key])

}

}

}

function defineReactive(target, key, val){

observe(val)

Object.defineProperty(obj, key, {

get(){

// 依赖收集

dep.depend()

return val

},

set(newVal){

if(newVal !== val){

observe(newVal)

val = newVal

// 通知更新

dep.notify()

}

}

})

}数组响应化:覆盖数组的原型方法,增加通知变更的逻辑// 伪代码

const originalProto = Array.prototype

const arrayProto = Object.create(originalProto)

['push','pop','shift','unshift','splice','reverse','sort'].forEach(key=>{

arrayProto[key] = function(){

originalProto[key].apply(this.arguments)

notifyUpdate()

}

})

vue2响应式痛点递归,消耗大

新增/删除属性,需要额外实现单独的API

数组,需要额外实现

Map Set Class等数据类型,无法响应式

修改语法有限制

vue3响应式方案

使用ES6的

Proxy可以在目标对象上加一层拦截/代理,外界对目标对象的操作,都会经过这层拦截

相比 Object.defineProperty ,Proxy支持的对象操作十分全面:get、set、has、deleteProperty、ownKeys、defineProperty......等等// reactive 伪代码

function reactice(obj){

return new Proxy(obj,{

get(target, key, receiver){

const ret = Reflect.get(target, key, receiver)

return isObject(ret) ? reactice(ret) : ret

},

set(target, key, val, receiver){

const ret = Reflect.set(target, key, val, receiver)

return ret

},

deleteProperty(target, key){

const ret = Reflect.deleteProperty(target, key)

return ret

},

})

}

响应式原理

通过 effect 声明依赖响应式数据的函数cb ( 例如视图渲染函数render函数),并执行cb函数,执行过程中,会触发响应式数据 getter

在响应式数据 getter中进行 track依赖收集:建立 数据&cb 的映射关系存储于 targetMap

当变更响应式数据时,触发 trigger ,根据 targetMap 找到关联的cb执行

映射关系 targetMap 结构:targetMap: WeakMap{

target:Map{

key: Set[cb1,cb2...]

}

}

手写vue3响应式

大致结构// mini-vue3.js

/* 建立响应式数据 */

function reactice(obj){}

/* 声明响应函数cb(依赖响应式数据) */

function effect(cb){}

/* 依赖收集:建立 数据&cb 映射关系 */

function track(target,key){}

/* 触发更新:根据映射关系,执行cb */

function trigger(target,key){}

reactive/* 建立响应式数据 */

function reactive(obj){

// Proxy:http://es6.ruanyifeng.com/#docs/proxy

// Proxy相当于在对象外层加拦截

// Proxy递归是惰性的,需要添加递归的逻辑

// Reflect:http://es6.ruanyifeng.com/#docs/reflect

// Reflect:用于执行对象默认操作,更规范、更友好,可以理解成操作对象的合集

// Proxy和Object的方法Reflect都有对应

if(!isObject(obj)) return obj

const observed = new Proxy(obj,{

get(target, key, receiver){

const ret = Reflect.get(target, key, receiver)

console.log('getter '+ret)

// 跟踪 收集依赖

track(target, key)

return reactive(ret)

},

set(target, key, val, receiver){

const ret = Reflect.set(target, key, val, receiver)

console.log('setter '+key+':'+val + '=>' + ret)

// 触发更新

trigger(target, key)

return ret

},

deleteProperty(target, key){

const ret = Reflect.deleteProperty(target, key)

console.log('delete '+key+':'+ret)

// 触发更新

trigger(target, key)

return ret

},

})

return observed

}

effect/* 声明响应函数cb */

const effectStack = []

function effect(cb){

// 对函数进行高阶封装

const rxEffect = function(){

// 1.捕获异常

// 2.fn出栈入栈

// 3.执行fn

try{

effectStack.push(rxEffect)

return cb()

}finally{

effectStack.pop()

}

}

// 最初要执行一次,进行最初的依赖收集

rxEffect()

return rxEffect

}

track/* 依赖收集:建立 数据&cb 映射关系 */

const targetMap = new WeakMap()

function track(target,key){

// 存入映射关系

const effectFn = effectStack[effectStack.length - 1] // 拿出栈顶函数

if(effectFn){

let depsMap = targetMap.get(target)

if(!depsMap){

depsMap = new Map()

targetMap.set(target, depsMap)

}

let deps = depsMap.get(key)

if(!deps){

deps = new Set()

depsMap.set(key, deps)

}

deps.add(effectFn)

}

}

trigger/* 触发更新:根据映射关系,执行cb */

function trigger(target, key){

const depsMap = targetMap.get(target)

if(depsMap){

const deps = depsMap.get(key)

if(deps){

deps.forEach(effect=>effect())

}

}

}

测试demo

{ {msg}}

// 定义一个响应式数据

const state = reactive({

msg:'message'

})

// 定义一个使用到响应式数据的 dom更新函数

function updateDom(){

document.getElementById('app').innerText = state.msg

}

// 用effect声明更新函数

effect(updateDom)

// 定时变更响应式数据

setInterval(()=>{

state.msg = 'message' + Math.random()

},1000)

效果:

你可能感兴趣的:(vue手写响应式)