vue3.0源码学习(reactivity)

3.0源码:https://github.com/vuejs/vue-next/

根据作者尤大在直播中讲过大致可以把整个vue分为三个部分来看,reactivity(vue响应式核心),compiler(将template转换成render方法),runtime(运行时的与reactivity进行响应式处理,包含自定义标签的生命周期)。

git下来之后build一下,会在package/vue/dist下创建一个vue.global.js的文件,写个单页试试。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <div id="app"></div>
</body>
<script src="./dist/vue.global.js"></script>
<script>
    const { createApp, reactive, watchEffect, computed } = Vue
    const App = {
        template:`
        
            

state.count:{{state.count}}

state.count1:{{state.count1}}

count2:{{count2}}

state.count: `
, setup(){ const state = reactive({ count:0, }) let count2 = computed(()=>state.count1+1) watchEffect(()=>{ state.count1 = state.count+1 }) function click(){ state.count += 1 } return { state, click, count2 } } } createApp(App).mount('#app') </script> </html>

reactivity(对象响应式)

核心:通过函数reactive()给对象新增一个Proxy对象监听内部的属性来实现数据监听。

首先从reactive方法开始

let toProxy = new WeakMap()  //正向缓存
let toRaw = new WeakMap()  //反向缓存
function reactive(target){  //构建Proxy监听对象
    let observed = toProxy.has(target) //判断是否已经监听
    if(observed){
        return observed
    }
    if(toRaw.has(target)){
        return target
    }
    observed = new Proxy(target,handle)
    //handle监听方法
    toProxy.set(target,observed)  //缓存存储
    toRaw.set(observed,target)
    return observed  //返回被被监听的对象,读取,修改observed时触发Proxy中的get
,set方法
}

handle:

const handle = { //Proxy监听跟设置属性
    get(target, key){  //收集依赖
        const res = Reflect.get(target, key)
        track(target,key) //属性被读取时关联effect中的方法
        return typeof res==='object'?reactive(res):res //深度监听
    },
    set(target, key, value){  //通知更新方法
        const res = Reflect.set(target, key, value)
        trigger(target,key) //属性被修改时执行effect中的方法
        return res
    }
}

为了实现computed和effect,必须对方法进行标记,对象修改的时候,通知执行computed和eatchEffect方法,所以需要设置一个存储方法队列的数组,并且对对象的每个属性变化都要执行一次,为了保证在数据被监听时的方法,创建一个WeakMap对象来关联target,key和对应的effect,在修改target的key。

let effectStack = []
let targetMap = new WeakMap()
function track(target,key){ //收集依赖
    const effect = effectStack[effectStack.length-1] //获取当前属性被读取时执行函数
    if(effect){
        let depMap = targetMap.get(target) // 以下为创建关联时的判断,将方法存到targetMap中
        if(depMap===undefined){
            depMap = new Map()
            targetMap.set(target,depMap)
        }
        let dep = depMap.get(key)
        if(dep===undefined){
            dep = new Set()
            depMap.set(key, dep)
        }
        if(!dep.has(effect)){
            dep.add(effect)
            effect.deps.push(dep)
        }
    }
}
function trigger(target,key,info){ //触发更新
    const depMap = targetMap.get(target)
    if(depMap===undefined){
        return
    }
    const effects = new Set() //保证唯一性
    if(key){
        let deps = depMap.get(key)
        deps.forEach(effect=>{
            effect()
        })
    }
}

然后实现effect和computed

function effect(fn,options={}){ 
    let e = createReavtiveEffect(fn,options)
    if(!options.lazy){ // 判断是否为computed,不是的话直接执行
        e()
    }
    return e
}
function createReavtiveEffect(fn,options){
    const effect = function effect(...args){  //存储之后返回这个函数被执行的函数
        return run(effect,fn,args)
    }
    effect.deps = []
    effect.lazy = options.lazy
    return effect
}
function run(effect, fn, args){
    if(effectStack.indexOf(effect)===-1){ //虽然已经有new Set去重了,但还是做二次判断
        try{
            effectStack.push(effect)  //将方法存入effectStack中
            return fn(...args)
        }finally{
            effectStack.pop()  //读取完之后清除effectStack,
        }
    }
}
function computed(fn){ //computed就是一个后触发的effect事件,加上lazy判断
    const runner = effect(fn,{lazy:true})
    return { //直接读取读取不到只能获取到返回的对象
        get value(){
            return runner()
        }
    }
}

computed这里有个问题,返回的是个对象,源码的处理方式设置对象再返回value值
vue3.0源码学习(reactivity)_第1张图片
调用试试

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>demo</title>
</head>
<body>
    <div id="app"></div>
    <button id="btn" onclick="countAdd()">add</button>
    <script src="./vue3.js"></script>
    <script>
        const root = document.getElementById('app')
        const btn = document.getElementById('btn')
        const obj = reactive({
            count:0,
        })
        let obj1 = computed(()=>obj.count*2)
        effect(()=>{
            root.innerHTML = `
                

count:${obj.count}-${obj1.value}

`
}) function countAdd(){ obj.count+=1 } </script> </body> </html>

在这里插入图片描述

你可能感兴趣的:(vue3.0源码学习(reactivity))