中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

如何實現Vue3 Reactivity

發布時間:2021-08-25 13:27:50 來源:億速云 閱讀:151 作者:小新 欄目:開發技術

這篇文章主要介紹了如何實現Vue3 Reactivity,具有一定借鑒價值,感興趣的朋友可以參考下,希望大家閱讀完這篇文章之后大有收獲,下面讓小編帶著大家一起了解一下。

開始

observer-util這個庫使用了與Vue3同樣的思路編寫,Vue3中的實現更加復雜,從一個更加純粹的庫開始(我不會承認是因為Vue3中有些未看懂的,不會)。

根據官網的例子:

import { observable, observe } from '@nx-js/observer-util';

const counter = observable({ num: 0 });
const countLogger = observe(() => console.log(counter.num));

// this calls countLogger and logs 1
counter.num++;

這兩個類似Vue3里的reactive和普通的響應式。

observable之后的對象被添加了代理,之后observe中添加的響應函數會在依賴的屬性改變時調用一次。

小小思考

這里粗略思考是一個訂閱發布的模型,被observable代理之后的對象建立一個發布者倉庫,observe這時候會訂閱counter.num,之后訂閱的內容改變時便會一一回調。
偽代碼:

// 添加監聽
xxx.addEventListener('counter.num', () => console.log(counter.num))
// 改變內容
counter.num++
// 發送通知
xxx.emit('counter.num', counter.num)

而響應式的核心也就是這個,添加監聽與發送通知會經由observable和observe自動完成。

代碼實現

經由上面的思考,在Getter里我們需要將observe傳過來的回調添加到訂閱倉庫中。
具體的實現中observable會為這個觀察的對象添加一個handler,在Getter的handler中有一個

registerRunningReactionForOperation({ target, key, receiver, type: 'get' })
const connectionStore = new WeakMap()
// reactions can call each other and form a call stack
const reactionStack = []

// register the currently running reaction to be queued again on obj.key mutations
export function registerRunningReactionForOperation (operation) {
  // get the current reaction from the top of the stack
  const runningReaction = reactionStack[reactionStack.length - 1]
  if (runningReaction) {
    debugOperation(runningReaction, operation)
    registerReactionForOperation(runningReaction, operation)
  }
}

這個函數會獲取出一個reaction(也就是observe傳過來的回調),并且通過registerReactionForOperation保存。

export function registerReactionForOperation (reaction, { target, key, type }) {
  if (type === 'iterate') {
    key = ITERATION_KEY
  }

  const reactionsForObj = connectionStore.get(target)
  let reactionsForKey = reactionsForObj.get(key)
  if (!reactionsForKey) {
    reactionsForKey = new Set()
    reactionsForObj.set(key, reactionsForKey)
  }
  // save the fact that the key is used by the reaction during its current run
  if (!reactionsForKey.has(reaction)) {
    reactionsForKey.add(reaction)
    reaction.cleaners.push(reactionsForKey)
  }
}

這里生成了一個Set,根據key,也就是實際業務中get時候的key,將這個reaction添加進Set中,整個的結構是這樣的:

connectionStore<weakMap>: {
    // target eg: {num: 1}
    target: <Map>{
        num: (reaction1, reaction2...)
    }
}

注意這里的reaction,const runningReaction = reactionStack[reactionStack.length - 1] 通過全局變量reactionStack獲取到的。

export function observe (fn, options = {}) {
  // wrap the passed function in a reaction, if it is not already one
  const reaction = fn[IS_REACTION]
    ? fn
    : function reaction () {
      return runAsReaction(reaction, fn, this, arguments)
    }
  // save the scheduler and debugger on the reaction
  reaction.scheduler = options.scheduler
  reaction.debugger = options.debugger
  // save the fact that this is a reaction
  reaction[IS_REACTION] = true
  // run the reaction once if it is not a lazy one
  if (!options.lazy) {
    reaction()
  }
  return reaction
}

export function runAsReaction (reaction, fn, context, args) {
  // do not build reactive relations, if the reaction is unobserved
  if (reaction.unobserved) {
    return Reflect.apply(fn, context, args)
  }

  // only run the reaction if it is not already in the reaction stack
  // TODO: improve this to allow explicitly recursive reactions
  if (reactionStack.indexOf(reaction) === -1) {
    // release the (obj -> key -> reactions) connections
    // and reset the cleaner connections
    releaseReaction(reaction)

    try {
      // set the reaction as the currently running one
      // this is required so that we can create (observable.prop -> reaction) pairs in the get trap
      reactionStack.push(reaction)
      return Reflect.apply(fn, context, args)
    } finally {
      // always remove the currently running flag from the reaction when it stops execution
      reactionStack.pop()
    }
  }
}

在runAsReaction中,會將傳入的reaction(也就是上面的const reaction = function() { runAsReaction(reaction) })執行自己的包裹函數壓入棧中,并且執行fn,這里的fn即我們想自動響應的函數,執行這個函數自然會觸發get,此時的reactionStack中則會存在這個reaction。這里注意fn如果里面有異步代碼的情況,try finally的執行順序是這樣的:

// 執行try的內容,
// 如果有return執行return內容,但不會返回,執行finally后返回,這里面不會阻塞。

function test() {
    try { 
        console.log(1); 
        const s = () => { console.log(2); return 4; }; 
        return s();
    } finally { 
        console.log(3) 
    }
}

// 1 2 3 4
console.log(test())

所以如果異步代碼阻塞并且先于Getter執行,那么就不會收集到這個依賴。

模仿

目標實現observable和observe以及衍生出來的Vue中的computed。
借用Vue3的思路,get時的操作稱為track,set時的操作稱為trigger,回調稱為effect。

先來個導圖:

如何實現Vue3 Reactivity

function createObserve(obj)  {
    
    let handler = {
        get: function (target, key, receiver) {
            let result = Reflect.get(target, key, receiver)
            track(target, key, receiver)            
            return result
        },
        set: function (target, key, value, receiver) {
            let result = Reflect.set(target, key, value, receiver)
            trigger(target, key, value, receiver)        
            return result
        }
    }

    let proxyObj = new Proxy(obj, handler)

    return proxyObj
}

function observable(obj) {
    return createObserve(obj)
}

這里我們只作了一層Proxy封裝,像Vue中應該會做一個遞歸的封裝。

區別是只做一層封裝的話只能檢測到外層的=操作,內層的如Array.push,或者嵌套的替換等都是無法經過set和get的。

實現track

在track中我們會將當前觸發的effect也就是observe的內容或者其他內容壓入關系鏈中,以便trigger時可以調用到這個effect。

const targetMap = new WeakMap()
let activeEffectStack = []
let activeEffect

function track(target, key, receiver?) {
    let depMap = targetMap.get(target)

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

    let dep = depMap.get(key)

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

    if (!dep.has(activeEffect)) {
        dep.add(activeEffect)
    }
}

targetMap是一個weakMap,使用weakMap的好處是當我們observable的對象不存在其他引用的時候會正確的被垃圾回收掉,這一條鏈是我們額外建立的內容,原對象不存在的情況下不應該在繼續存在。

這里面最終會形成一個:

targetMap = {
    <Proxy 或者 Object>observeable: <Map>{
        <observeable中的某一個key>key: ( observe, observe, observe... )
    }
}

activeEffectStack和activeEffect是兩個用于數據交換的全局變量,我們在get中會把當前的activeEffect添加到get的key的生成的Set中保存起來,讓set操作可以拿到這個activeEffect然后再次調用,實現響應式。

實現trigger

function trigger(target, key, value, receiver?) {
    let depMap = targetMap.get(target)

    if (!depMap) {
        return
    }

    let dep = depMap.get(key)

    if (!dep) {
        return
    }

    dep.forEach((item) => item && item())
}

trigger這里按照思路實現一個最小的內容,只是將get中添加的effect逐個調用。

實現observe

根據導圖,在observe中我們需要將傳入的function壓入activeEffectStack并調用一次function觸發get。

function observe(fn:Function) {
    const wrapFn = () => {

        const reaction = () => {
            try {
                activeEffect = fn     
                activeEffectStack.push(fn)
                return fn()
            } finally {
                activeEffectStack.pop()
                activeEffect = activeEffectStack[activeEffectStack.length-1]
            }
        }

        return reaction()
    }

    wrapFn()

    return wrapFn
}

function有可能出錯,finally中的代碼保證activeEffectStack中對應的那個會被正確刪除。

測試

let p = observable({num: 0})
let j = observe(() => {console.log("i am observe:", p.num);)
let e = observe(() => {console.log("i am observe2:", p.num)})

// i am observe: 1
// i am observe2: 1
p.num++

實現computed

在Vue中一個很有用的東西是計算屬性(computed),它是依賴于其他屬性而生成的新值,會在它依賴的其他值更改時自動更改。
我們在實現了ovserve之后computed就實現了一大半。

class computedImpl {
    private _value
    private _setter
    private effect

    constructor(options) {
        this._value = undefined
        this._setter = undefined
        const { get, set } = options
        this._setter = set

        this.effect = observe(() => {
            this._value = get()
        })
    }

    get value() {
        return this._value
    }

    set value (val) {
        this._setter && this._setter(val)
    }
}

function computed(fnOrOptions) {

    let options = {
        get: null,
        set: null
    }

    if (fnOrOptions instanceof Function) {
        options.get = fnOrOptions
    } else {
        const { get, set } = fnOrOptions
        options.get= get
        options.set = set
    }

    return new computedImpl(options)
}

computed有兩種方式,一種是computed(function)這樣會當做get,另外還可以設置setter,setter更多的像是一個回調可以和依賴的其他屬性完全沒有關系。

let p = observable({num: 0})
let j = observe(() => {console.log("i am observe:", p.num); return `i am observe: ${p.num}`})
let e = observe(() => {console.log("i am observe2:", p.num)})
let w = computed(() => { return '我是computed 1:' + p.num })
let v = computed({
    get: () => {
        return 'test computed getter' + p.num
    },

    set: (val) => {
        p.num = `test computed setter${val}`
    }
})

p.num++
// i am observe: 0
// i am observe2: 0
// i am observe: 1
// i am observe2: 1
// 我是computed 1:1
console.log(w.value)
v.value = 3000
console.log(w.value)
// i am observe: test computed setter3000
// i am observe2: test computed setter3000
// 我是computed 1:test computed setter3000
w.value = 1000
// 并沒有為w設置setter所以并沒有生效
// 我是computed 1:test computed setter3000
console.log(w.value)

感謝你能夠認真閱讀完這篇文章,希望小編分享的“如何實現Vue3 Reactivity”這篇文章對大家有幫助,同時也希望大家多多支持億速云,關注億速云行業資訊頻道,更多相關知識等著你來學習!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

正镶白旗| 枣阳市| 秭归县| 屏边| 新巴尔虎左旗| 蒙自县| 喀喇| 若羌县| 于田县| 东乌| 修文县| 德阳市| 河源市| 陆丰市| 且末县| 锦州市| 阜南县| 贵阳市| 连江县| 敦煌市| 宿迁市| 定南县| 安福县| 普陀区| 秦皇岛市| 衡南县| 建湖县| 崇文区| 大同市| 永川市| 东海县| 鄯善县| 昌乐县| 电白县| 苏州市| 股票| 龙门县| 抚顺县| 惠来县| 大名县| 临武县|