vue3的响应式原理

搭建项目

拍平依赖

用来搭建monorepo管理项目

packages:
  - 'packages/*'

ts 配置

{
  "compilerOptions": {
    "outDir": "dist", //输出目录
    "sourceMap": true, //采用sourceMap
    "target": "es2016", //目标语法
    "module": "esnext", //模块格式
    "moduleResolution": "node", //模块解析方式
    "strict": false, //严格模式
    "resolveJsonModule": true, //解析JSON模块
    "esModuleInterop": true, //允许es6语法引入commonjs模块
    "jsx": "preserve", //js不转译
    "lib": ["esnext", "dom"], //支持的类库esnextdom
    "baseUrl": ".",
    "paths": {
      "@vue/*": ["packages/*/src"]
    }
  }
}

添加脚本命令

"dev": "node scripts/dev.js reactivity -f global"

脚本编译

const args = require("minimist")(process.argv.slice(2)) // node scripts/dev.js reactivity -f global
const { build } = require("esbuild");
// console.log(args)
const { resolve } = require('path');// node 内置模块

const target = args._[0] || "reactivity";
const format = args.f || 'global';// 打包格式

const pkg = require(resolve(__dirname, `../packages/${target}/package.json`));

// iife 立即执行函数 (function(){})();
// cjs node中的模块 module.exports
// esm 浏览器中的esModule模块 import
const outputFormat = format.startsWith("global") ? 'iife' : format == "cjs" ? "cjs" : "esm";

//打包之后文件存放地方
const outFile = resolve(__dirname, `../packages/${target}/dist/${target}.${format}.js`)


//esbuild
//天生就支持ts 
build({
  entryPoints: [resolve(__dirname, `../packages/${target}/src/index.ts`)],
  outfile: outFile, //输出的文件
  bundle: true, //把所有包全部打包到一起
  sourcemap: true,
  format: outputFormat, //输出格式
  globalName: pkg.buildOptions?.name, //打包全局名
  platform: format === "cjs" ? "node" : "browser",//项目运行平台
  watch: { //监听文件变化
    onRebuild (error) {
      if (!error) {
        console.log("rebuild~~~")
      }
    }
  }
}).then(() => {
  console.log(`watch~~~~`)
})

reactivity和shared

{
  "name": "@vue/reactivity",
  "version": "1.0.0",
  "description": "",
  "main": "index.ts",
  "buildOptions":{
    "name":"VueReactivity",
    "formats":[
      "global",
      "cjs",
      "esm-budler"
    ]
  }
}

{
  "name": "@vue/shared",
  "version": "1.0.0",
  "description": "",
  "main": "index.ts",
  "buildOptions":{
    "formats":[
      "cjs",
      "esm-budler"
    ]
  }
}

打包测试

export const isObject = (value: any) => {
    return value !== null && typeof value === 'object'
}
import { isObject } from "@vue/shared";


console.log(isObject(true))

Vuereactive方法

  • index.ts
export { effect } from './effect'
export { reactive } from './reactive'
  • src下新建reactive.ts
import {  isObject  } from '@vue/shared'

const reactiveMap = new WeakMap()
const enum ReactiveFlags {
    IS_REACTIVE = '_v_isReactive'
}
export function reactive(target){

    if(!isObject(target)){
        return
    }
    
    if(target[ReactiveFlags.IS_REACTIVE]){
        return target
    }

    let exisitingProxy = reactiveMap.get(target)
    if(exisitingProxy){
        return exisitingProxy
    }

    const proxy = new Proxy(target,{
        get(target,key,receiver){
            if (key === ReactiveFlags.IS_REACTIVE) {
                return true
            }
            return Reflect.get(target,key,receiver)
        },
        set(target,key,value,receiver){
            return Reflect.set(target,key,value,receiver)
        }
    })
    reactiveMap.set(target,proxy)

    return proxy

}
import { isObject } from "@vue/shared"
import { reactive } from "./reactive"

export const enum ReactiveFlags {
    IS_REACTIVE = '_v_isReactive'
}

export const mutableHandlers = {
    get(target, key, receiver) {
        if (key === ReactiveFlags.IS_REACTIVE) {
            return true
        }
        let res = Reflect.get(target, key, receiver)
        if (isObject(res)) {
            return reactive(res)
        }
        return res
    },
    set(target, key, value, receiver) {
        return Reflect.set(target, key, value, receiver)
    }
}
  • reactive.ts
import {  isObject  } from '@vue/shared'
import { mutableHandlers,ReactiveFlags } from './baseHandler'
const reactiveMap = new WeakMap()

export function reactive(target){
    if(!isObject(target)){
        return
    }
    if(target[ReactiveFlags.IS_REACTIVE]){
        return target
    // const state1 = reactive(data)
    // const state2 = reactive(state1)
    }
    let exisitingProxy = reactiveMap.get(target)
    if(exisitingProxy){
        return exisitingProxy
    }
    const proxy = new Proxy(target,mutableHandlers)
    reactiveMap.set(target,proxy)
    // const state1 = reactive(data)
    // const state2 = reactive(data)
    return proxy
}

Vue中的effect方法

  • src下新增effect.ts
export let activeEffect = undefined

class ReactiveEffect{
    public active = true
    public parent = null
    public deps = []
    constructor(public fn){}
    run(){
        if(!this.active){
            this.fn()
        }
        try {
            this.parent = activeEffect
            activeEffect = this
            return this.fn()
        } finally {
            activeEffect = this.parent
        }
    }
}

export function effect(fn){
  let _effect =  new ReactiveEffect(fn)
  _effect.run()
}


const targetMap = new WeakMap()
export function track(target, type, key){
    if(!activeEffect) return

    let depsMap = targetMap.get(target)

    if(!depsMap){
        targetMap.set(target,(depsMap = new Map()))
    }

    let dep = depsMap.get(key)

    if(!dep){
        depsMap.set(key,(dep = new Set()))
    }

    let shouldTrack = !dep.has(activeEffect)

    if(shouldTrack){
        dep.add(activeEffect)
        activeEffect.deps.push(dep)
    }
 
}

export function trigger(target,type,key,value,oldValue){

    const depsMap = targetMap.get(target)

    if(!depsMap) return

    const effects = depsMap.get(key)

    effects && effects.forEach(effect => {
        if(effect !== activeEffect){
            effect.run()
        }
    });
}
  • baseHandler.ts
import { track, trigger } from "./effect"
import { isObject } from "@vue/shared"
import { reactive } from "./reactive"

export const enum ReactiveFlags {
    IS_REACTIVE = '_v_isReactive'
}
export const mutableHandlers =   {
    get(target,key,receiver){
        if (key === ReactiveFlags.IS_REACTIVE) {
            return true
        }
        track(target,'get',key)
        let res = Reflect.get(target, key, receiver)
        if (isObject(res)) {
            return reactive(res)
        }
        return res
    },
    set(target,key,value,receiver){
        let oldValue = target[key]
        let result = Reflect.set(target, key, value, receiver)
        if(oldValue !== value){
            trigger(target,'set',key,value,oldValue)
        }
        return result
    }
}

Vue3中的分支切换原理

 const {effect,reactive } = VueReactivity
        const state = reactive({
            name:'xiaonshunshi',
            age:33,
            flag:true
        })
        effect(()=>{
            console.log('render')
            document.getElementById('app').innerHTML = state.flag ? state.name:state.age
        })

        setTimeout(()=>{
            state.flag = false
            setTimeout(() => {
                console.log('修改name,原则更新一次')
                state.name = 'song'
            }, 1000);
           
        },1000)
render
render
修改name,原则上更新一次
render
render
修改name,原则上更新一次
render
export let activeEffect = undefined

class ReactiveEffect {
    public active = true
    public parent = null
    public deps = []
    constructor(public fn) { }
    run() {
        if (!this.active) {
            this.fn()
        }
        try {
            this.parent = activeEffect
            activeEffect = this

            cleanupEffect(this)
            return this.fn()
        } finally {
            activeEffect = this.parent
        }
    }
}
// 新增清除effect的deps
function cleanupEffect(effect) {
    const { deps } = effect
    for (let i = 0; i < deps.length; i++) {
        deps[i].delete(effect)
    }
    effect.deps.length = 0
}

export function effect(fn) {
    let _effect = new ReactiveEffect(fn)
    _effect.run()
}


const targetMap = new WeakMap()
export function track(target, type, key) {
    if (!activeEffect) return

    let depsMap = targetMap.get(target)

    if (!depsMap) {
        targetMap.set(target, (depsMap = new Map()))
    }

    let dep = depsMap.get(key)

    if (!dep) {
        depsMap.set(key, (dep = new Set()))
    }

    let shouldTrack = !dep.has(activeEffect)

    if (shouldTrack) {
        dep.add(activeEffect)
        activeEffect.deps.push(dep)
    }

}

export function trigger(target, type, key, value, oldValue) {

    const depsMap = targetMap.get(target)

    if (!depsMap) return

    let effects = depsMap.get(key)

    // effects &amp;& effects.forEach(effect => {
    //     if(effect !== activeEffect){
    //         effect.run()
    //     }
    // });

    if (effects) {
        // 要循环一下新的effect,避免死循环
        effects = new Set(effects)
        effects.forEach(effect => {
            if (effect !== activeEffect) {
                effect.run()
            }
        });
    }
}

Vue中的调度scopeEffect

const {
    effect,
    reactive
} = VueReactivity
 const state = reactive({
    name: 'xiaoshunshi',
    age: 13,
    flag: true
 })
 let ruuner = effect(() => {
    document.getElementById('app').innerHTML = state.age
  })

  ruuner.effect.stop()
  setTimeout(() => {
      state.age = 1000
       setTimeout(() => {
           ruuner()
        }, 1000);
  }, 1000)
class ReactiveEffect {
    public active = true
    public parent = null
    public deps = []
    constructor(public fn) { }
    run() {
        if (!this.active) {
            this.fn()
        }
        try {
            this.parent = activeEffect
            activeEffect = this

            cleanupEffect(this)
            return this.fn()
        } finally {
            activeEffect = this.parent
        }
    }
    stop(){
        if(this.active){
            this.active = false
            cleanupEffect(this)
        }
    }
}


.....................
.....................
.....................


export function effect(fn) {
    let _effect = new ReactiveEffect(fn)
    _effect.run()
    const runner = _effect.run.bind(_effect)
    runner.effect = _effect
    return runner
}
    let waiting = false
    const {
        effect,
        reactive
    } = VueReactivity
    const state = reactive({
        name: 'zcf',
        age: 13,
        flag: true
    })
    let ruuner = effect(() => {
        document.getElementById('app').innerHTML = state.age
    }, {
        scheduler() {
            console.log('run')

            if (!waiting) {
                waiting = true
                setTimeout(() => {
                    ruuner()
                    waiting = false
                }, 1000);
            }

        }
    })

    // ruuner.effect.stop()
    state.age = 1000
    state.age = 10001
    state.age = 10002
    state.age = 10003
    state.age = 10004
    state.age = 10005
    state.age = 10006
  class ReactiveEffect {
  
  		constructor(public fn,public scheduler) { }
.....................
.....................
.....................


export function effect(fn, options: any = {}) {
    let _effect = new ReactiveEffect(fn,options.scheduler)
    _effect.run()
    const runner = _effect.run.bind(_effect)
    runner.effect = _effect
    return runner
}
.....................
.....................
.....................
 function trigger(target, type, key, value, oldValue) {
 .....................
 	if (effects) {
        effects = new Set(effects)
        effects.forEach(effect => {
            if (effect !== activeEffect) {
                if (effect.scheduler) {
                    effect.scheduler()
                } else {
                    effect.run()
                }
            }
        });
    }
    .....................
 }

Vue的计算属性

const {effect,reactive, computed} = VueReactivity
const state = reactive({
    fastname:'xiao',
    lastname:'shunshi'
})

const fullName = computed(()=>{
    console.log('render')
    return state.fastname+ state.lastname
})
effect(()=>{
	document.getElementById('app').innerHTML = fullName.value
})
// const fullName = computed({
//     get(){
//         return state.fastname+ state.lastname
//     },
//     set(){}
// })
setTimeout(()=>{
	state.fastname = '宋'
},1000)
  • 新建computed.ts,在index.ts中导出方法
import { isFunction } from "@vue/shared";
import { ReactiveEffect,trackEffects,triggerEffects} from "./effect";

export function computed(getterOrOptions){
    let onlyGetter = isFunction(getterOrOptions)

    let getter
    let setter

    if (onlyGetter) {
        getter = getterOrOptions
        setter = () => { console.warn('no set') }
    } else {
        getter = getterOrOptions.get
        setter = getterOrOptions.set
    }

    return new ComputedRefImpl(getter, setter)
}

class ComputedRefImpl{
    public effect
    public _dirty = true
    public __v_isReadonly = true
    public __v_isRef = true
    public _value
    public dep = new Set
    constructor(public getter,public setter){
        this.effect =  new ReactiveEffect(getter,()=>{
            if(!this._dirty){
                this._dirty = true
            }
            triggerEffects(this.dep)
        })
    }
    get value(){
        trackEffects(this.dep)
        if (this._dirty) {
            this._dirty = false
            this._value = this.effect.run()
        }
        return this._value
    }
    set value(newValue){
        this.setter(newValue)
    }
}
export let activeEffect = undefined

export  class ReactiveEffect {
    public active = true
    public parent = null
    public deps = []
    constructor(public fn,public scheduler) { }
    run() {
        if (!this.active) {
            this.fn()
        }
        try {
            this.parent = activeEffect
            activeEffect = this

            cleanupEffect(this)
            return this.fn()
        } finally {
            activeEffect = this.parent
        }
    }
    stop(){
        if(this.active){
            this.active = false
            cleanupEffect(this)
        }
    }
}

function cleanupEffect(effect) {
    const { deps } = effect
    for (let i = 0; i < deps.length; i++) {
        deps[i].delete(effect)
    }
    effect.deps.length = 0
}

export function effect(fn, options: any = {}) {
    let _effect = new ReactiveEffect(fn,options.scheduler)
    _effect.run()
    const runner = _effect.run.bind(_effect)
    runner.effect = _effect
    return runner
}


const targetMap = new WeakMap()
export function track(target, type, key) {
    if (!activeEffect) return

    let depsMap = targetMap.get(target)

    if (!depsMap) {
        targetMap.set(target, (depsMap = new Map()))
    }

    let dep = depsMap.get(key)

    if (!dep) {
        depsMap.set(key, (dep = new Set()))
    }

    // let shouldTrack = !dep.has(activeEffect)

    // if (shouldTrack) {
    //     dep.add(activeEffect)
    //     activeEffect.deps.push(dep)
    // }
    trackEffects(dep)
}
export function trackEffects(dep){
    let shouldTrack = !dep.has(activeEffect)
    if (shouldTrack) {
        dep.add(activeEffect)
        activeEffect.deps.push(dep)
    }
}
export function trigger(target, type, key, value, oldValue) {

    const depsMap = targetMap.get(target)

    if (!depsMap) return

    let effects = depsMap.get(key)

    // effects && effects.forEach(effect => {
    //     if(effect !== activeEffect){
    //         effect.run()
    //     }
    // });

    if (effects) {
        // effects = new Set(effects)
        // effects.forEach(effect => {
        //     if (effect !== activeEffect) {
        //         if (effect.scheduler) {
        //             effect.scheduler()
        //         } else {
        //             effect.run()
        //         }
        //     }
        // });
        triggerEffects(effects)
    }
}


export function triggerEffects(effects){
    effects = new Set(effects)
    effects.forEach(effect => {
        if (effect !== activeEffect) {
            if (effect.scheduler) {
                effect.scheduler()
            } else {
                effect.run()
            }
        }
    });
}

原文地址:https://blog.csdn.net/xiaoshunshi/article/details/125832155

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。

如若转载,请注明出处:http://www.7code.cn/show_6695.html

如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱:suwngjj01@126.com进行投诉反馈,一经查实,立即删除

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注