vue3.0初体验(例子解读reactive响应式)
目录准备vue3 reactive原理例子重点讲解vue3 reactive原理例子完整代码准备:下载vue-next安装依赖 npm install核心部分package,里面的vue整合package内各个文件,__tests__里面的单元测试文件(以spect.ts结尾)用来测试功能是否符合预期的启动开发环境 npm run dev,默认将代码打包到d...
目录
- 准备
- vue3 reactive原理例子重点讲解
- vue3 reactive原理例子完整代码
准备:
-
下载vue-next
-
安装依赖
npm install
核心部分package,里面的vue整合package内各个文件,
__tests__
里面的单元测试文件(以spect.ts
结尾)用来测试功能是否符合预期的
-
启动开发环境
npm run dev
,默认将代码打包到dist,会把ts编译成js -
引用编译出来的文件即可进行测试vue3.0
packages里面的文件说明
-
compiler- 用于编译
-
reactivity- 用于响应式
-
runtime- 代码运行时的方法
-
server-randerer 服务器端渲染
-
shared 所有包中的共享方法
-core是核心,-dom是基于core封装针对浏览器的
没有对比没有伤害,复习:vue2响应式原理
vue2响应式原理:核心使用Object.defineProperty给属性定义get和set
vue3 reactive的基本使用
let proxy = Vue.reactive({name:'fur'})
//effect:副作用,默认会调用一次,数据变化时触发
Vue.effect(()=>{
console.log(proxy.name)
})
proxy.name = 'furfur-jiang'
//fur
//furfur-jiang
vue3 reactive原理例子
先分开逐个重点讲解,再整合起来
划重点部分:弱映射表,多层递归,Reflect,数组,依赖收集
弱映射表
需要用到弱引用映射表记录一下, 防止对象已经代理过了或者多次代理同一个对象,即防止多次出现 reactive(object) 与 reactive(proxy);弱引用映射表(es6),使用get和set方法进行存取
弱映射表定义
let toProxy = new WeakMap()//原对象=>被代理对象
let toRaw = new WeakMap()//被代理对象=>原对象
下面代码位于createReactive方法中,因为在createReactive会new observed,目的就是为了避免多次new observed
if(toProxy.get(target)){
return proxy
}
//如果对象再次被代理,返回原对象
//即判断该对象已经是代理过的,不再次代理
//不需要使用get即不需要获取,直接判断target有无代理过既可
if(toRaw.has(target)){
return target
}
多层递归
关键在于isObject(res)?reactive(res):res
;若res为对象,需要递归,与vue2一上来就递归不同,会判断需要递归再递归,
get(target,key,receiver){
console.log("获取")
let res = Reflect.get(target,key,receiver)
return isObject(res)?reactive(res):res;
// return target[key] 效果等同Reflect.get方法
}
Reflect
observed的参数baseHandler对象,Reflect.get,Reflect.set,Reflect.deleteProperty
reflect 优点:不会报错,而且有返回值,会替代掉Object上的方法
let baseHandler = {
//receiver表示代理后对象
get(target,key,receiver){
console.log("获取")
let res = Reflect.get(target,key,receiver)
return isObject(res)?reactive(res):res;
//实现多层代理,若res为对象,需要递归
// return target[key] 效果等同Reflect.get方法
},
set(target,key,value,receiver){
console.log("设置")
let res = Reflect.set(target,key,value,receiver)
return res
//target[key] = value
//效果等同Reflect.set方法,但是上面会有布尔类型返回值,明确设置成功或者失败
},
deleteProperty(target,key){
console.log("删除")
return Reflect.deleteProperty(target,key)
}
}
let observed = new Proxy(target,baseHandler)
数组
存在问题,因为会涉及length修改,所以如果不屏蔽,会触发两次,即两次视图更新,不需要
即第一次将元素 push 进去,第二次将 length 改成 对应的值,但是第二次是无意义的更新,需要屏蔽
关键:判断是否为新增属性
function hasOwn(target,key){
return target.hasOwnProperty(key)
}
在set中进行处理
set(target,key,value,receiver){
let hadKey = hasOwn(target,key)
let oldValue = target[key]
let res = Reflect.set(target,key,value,receiver)
//判断是否新增属性
if(!hadKey){
console.log('新增属性')
}else if(oldValue !== value){
//如果修改的不等于原来的,才会执行,即需要手动改length才会执行
console.log('修改属性')
}
return res
//target[key] = value
//效果等同Reflect.set方法,但是上面会有布尔类型返回值,明确设置成功或者失败
}
调用:
let arr = [1,2,3]
let proxyArr = reactive(arr)
proxyArr.push(4) //新增属性
proxyArr.length = 5 //修改属性
依赖收集
activeEffectStacks 栈:先进后出,目的让属性和方法关联,形成响应
effect方法:响应式副作用,默认先执行一次,依赖数据变了再执行
createReactiveEffect方法:创建响应式的副作用
run方法:1.让fn执行, 2.将effect存入栈
//响应式副作用
function effect(fn){
//createReactiveEffect:创建响应式的副作用
let effect = createReactiveEffect(fn)
effect()//默认先执行一次
}
function createReactiveEffect(fn){
let effect = function(){
return run(effect,fn)
}
return effect;
}
function run(effect,fn){
//防止由于报错而不继续执行
try{
activeEffectStacks.push(effect);
fn();
}finally{
activeEffectStacks.pop()
}
}
let obj = reactive({name:'fur'})
effect(()=>{
//运行get时进行依赖收集
console.log(obj.name)
})
get方法调用(跟踪)track()和set方法调用(触发)trigger()
function track(target,key){//跟踪
let effect = activeEffectStacks[activeEffectStacks.length-1]
if(effect){//有对应关系才关联,动态创建依赖关系
let depsMap = targetsMap.get(target)
if(!depsMap){
targetsMap.set(target,depsMap = new Map)
}
let deps = depsMap.get(key)
if(!deps){
depsMap.set(key,deps = new Set())
}
if(!deps.has(effect)){
deps.add(effect)
}
}
}
function trigger(target,type,key){//触发
let depsMap = targetsMap.get(target)
if(depsMap){
let deps = depsMap.get(key)
if(deps){
//将当前key对应的effect一次执行
deps.forEach(effect=>{
effect()
})
}
}
}
完整代码
下面贴出完整代码myreactive.js:包含详细注释
//弱映射表
let toProxy = new WeakMap()//原对象=>被代理对象
let toRaw = new WeakMap()//被代理对象=>原对象
function isObject(val){
return typeof val === 'object' && val !== null;
}
//判断当前对象上有没有这个属性
function hasOwn(target,key){
return target.hasOwnProperty(key)
}
function reactive(target){
return createReactive(target)
}
function createReactive(target){
//如果已经被代理过了,返回代理结果
if(toProxy.get(target)){
return proxy
}
//如果对象再次被代理,返回原对象
//即判断该对象已经是代理过的,不再次代理
//不需要使用get即不需要获取,直接判断target有无代理过既可
if(toRaw.has(target)){
return target
}
if(!isObject(target)){
return target
}
let baseHandler = {
//receiver表示代理后对象
// reflect 优点:不回报错,而且有返回值,会替代掉Object上的方法
get(target,key,receiver){
// console.log("获取")
let res = Reflect.get(target,key,receiver)
//收集依赖,把当前key和effect对应起来
//如果目标上的key变了,重新让数组里的fn执行即可
track(target,key)
return isObject(res)?reactive(res):res;
//实现多层代理,若res为对象,需要递归
// return target[key] 效果等同Reflect.get方法
},
set(target,key,value,receiver){
let hadKey = hasOwn(target,key)
let oldValue = target[key]
let res = Reflect.set(target,key,value,receiver)
//判断是否新增属性
if(!hadKey){
trigger(target,'add',key)
console.log('新增属性')
}else if(oldValue !== value){
trigger(target,'set',key)
//如果修改的不等于原来的,才会执行,即需要手动改length才会执行
console.log('修改属性')
}
console.log("设置")
return res
//target[key] = value
//效果等同Reflect.set方法,但是上面会有布尔类型返回值,明确设置成功或者失败
},
deleteProperty(target,key){
console.log("删除")
return Reflect.deleteProperty(target,key)
}
}
let observed = new Proxy(target,baseHandler)
toProxy.set(target,observed)
toRaw.set(observed,target)
return observed
}
let object = {name:'fur',hobby:{play:'code'}}
let proxy = reactive(object)
//需要记录一下, 防止对象已经代理过了或者多次代理同一个对象
//即防止多次出现 reactive(object) 与 reactive(proxy)
//利用弱引用映射表(es6),使用get和set方法进行存取
proxy.name//获取
proxy.name = 'furfur-jiang' //设置 获取
console.log(proxy.name)//furfur-jiang
delete proxy.name //删除
//验证实现了多层代理
proxy.hobby.play = 'study'
console.log(proxy.hobby.play)//study
//对数组操作
//存在问题,因为会涉及length修改,所以如果不屏蔽,会触发两次,第一次将4push进去,第二次将length改成4
let arr = [1,2,3]
let proxyArr = reactive(arr)
proxyArr.push(4) //新增属性
proxyArr.length = 5 //修改属性
console.log(proxyArr)
//依赖收集,也称发布订阅
//栈:先进后出,目的让属性和方法关联,形成响应
let activeEffectStacks = []
let targetsMap = new WeakMap(); // 集合和hash表
function track(target,key){
let effect = activeEffectStacks[activeEffectStacks.length-1]
if(effect){//有对应关系才关联,动态创建依赖关系
let depsMap = targetsMap.get(target)
if(!depsMap){
targetsMap.set(target,depsMap = new Map)
}
let deps = depsMap.get(key)
if(!deps){
depsMap.set(key,deps = new Set())
}
if(!deps.has(effect)){
deps.add(effect)
}
}
}
function trigger(target,type,key){
let depsMap = targetsMap.get(target)
if(depsMap){
let deps = depsMap.get(key)
if(deps){
//将当前key对应的effect一次执行
deps.forEach(effect=>{
effect()
})
}
}
}
//响应式副作用
function effect(fn){
//createReactiveEffect:创建响应式的副作用
let effect = createReactiveEffect(fn)
effect()//默认先执行一次
}
function createReactiveEffect(fn){
let effect = function(){
return run(effect,fn) //1.让fn执行, 2.将effect存入栈
}
return effect;
}
function run(effect,fn){
//防止由于报错而不继续执行
try{
activeEffectStacks.push(effect);
fn();
}finally{
activeEffectStacks.pop()
}
}
let obj = reactive({name:'fur'})
//effect:响应式副作用,默认先执行一次,依赖数据变了再执行
effect(()=>{
//运行get,
console.log(obj.name)
})
obj.name = 'furfur-jiang'
obj.name = 'furfur-jiang'//无意义修改,只运行一次
obj.name = 'furfurJiang'//有意义修改,会运行两次
更多推荐
所有评论(0)