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:`
        <button @click="click">点击增加</button>
            <p>state.count:{
     
     {state.count}}</p>
            <p>state.count1:{
     
     {state.count1}}</p>
            <p>count2:{
     
     {count2}}</p>
        state.count:<input v-model.number="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值
在这里插入图片描述
调用试试

<!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 = `
                <p>count:${
      
      obj.count}-${
      
      obj1.value}</p>
            `
        })
        function countAdd(){
    
    
            obj.count+=1
        }
    </script>   
</body>
</html>

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_37195804/article/details/106183141