最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

手摸手教你實(shí)現(xiàn)Vue3 Reactivity

 更新時(shí)間:2021年08月25日 11:35:02   作者:HuberTRoy  
本文主要介紹了手摸手教你實(shí)現(xiàn)Vue3 Reactivity,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下

前言

Vue3的響應(yīng)式基于Proxy,對(duì)比Vue2中使用的Object.definedProperty的方式,使用Proxy在新增的對(duì)象以及數(shù)組的攔截上都有很好的支持。

Vue3的響應(yīng)式是一個(gè)獨(dú)立的系統(tǒng),可以抽離出來(lái)使用,那他到底是如何實(shí)現(xiàn)的呢?

都知道有Getter和Setter,那Getter和Setter中分別都進(jìn)行了哪些主要操作才能實(shí)現(xiàn)響應(yīng)式呢?

哼哼,帶著這些問(wèn)題一起來(lái)看看吧,文章會(huì)一步一步實(shí)現(xiàn)一個(gè)完整的響應(yīng)式系統(tǒng)(誤)~。

開(kāi)始

observer-util這個(gè)庫(kù)使用了與Vue3同樣的思路編寫(xiě),Vue3中的實(shí)現(xiàn)更加復(fù)雜,從一個(gè)更加純粹的庫(kù)開(kāi)始(我不會(huì)承認(rèn)是因?yàn)閂ue3中有些未看懂的,不會(huì))。

根據(jù)官網(wǎng)的例子:

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++;

這兩個(gè)類似Vue3里的reactive和普通的響應(yīng)式。

observable之后的對(duì)象被添加了代理,之后observe中添加的響應(yīng)函數(shù)會(huì)在依賴的屬性改變時(shí)調(diào)用一次。

小小思考

這里粗略思考是一個(gè)訂閱發(fā)布的模型,被observable代理之后的對(duì)象建立一個(gè)發(fā)布者倉(cāng)庫(kù),observe這時(shí)候會(huì)訂閱counter.num,之后訂閱的內(nèi)容改變時(shí)便會(huì)一一回調(diào)。
偽代碼:

// 添加監(jiān)聽(tīng)
xxx.addEventListener('counter.num', () => console.log(counter.num))
// 改變內(nèi)容
counter.num++
// 發(fā)送通知
xxx.emit('counter.num', counter.num)

而響應(yīng)式的核心也就是這個(gè),添加監(jiān)聽(tīng)與發(fā)送通知會(huì)經(jīng)由observable和observe自動(dòng)完成。

代碼實(shí)現(xiàn)

經(jīng)由上面的思考,在Getter里我們需要將observe傳過(guò)來(lái)的回調(diào)添加到訂閱倉(cāng)庫(kù)中。
具體的實(shí)現(xiàn)中observable會(huì)為這個(gè)觀察的對(duì)象添加一個(gè)handler,在Getter的handler中有一個(gè)

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)
  }
}

這個(gè)函數(shù)會(huì)獲取出一個(gè)reaction(也就是observe傳過(guò)來(lái)的回調(diào)),并且通過(guò)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)
  }
}

這里生成了一個(gè)Set,根據(jù)key,也就是實(shí)際業(yè)務(wù)中g(shù)et時(shí)候的key,將這個(gè)reaction添加進(jìn)Set中,整個(gè)的結(jié)構(gòu)是這樣的:

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

注意這里的reaction,const runningReaction = reactionStack[reactionStack.length - 1] 通過(guò)全局變量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中,會(huì)將傳入的reaction(也就是上面的const reaction = function() { runAsReaction(reaction) })執(zhí)行自己的包裹函數(shù)壓入棧中,并且執(zhí)行fn,這里的fn即我們想自動(dòng)響應(yīng)的函數(shù),執(zhí)行這個(gè)函數(shù)自然會(huì)觸發(fā)get,此時(shí)的reactionStack中則會(huì)存在這個(gè)reaction。這里注意fn如果里面有異步代碼的情況,try finally的執(zhí)行順序是這樣的:

// 執(zhí)行try的內(nèi)容,
// 如果有return執(zhí)行return內(nèi)容,但不會(huì)返回,執(zhí)行finally后返回,這里面不會(huì)阻塞。

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執(zhí)行,那么就不會(huì)收集到這個(gè)依賴。

模仿

目標(biāo)實(shí)現(xiàn)observable和observe以及衍生出來(lái)的Vue中的computed。
借用Vue3的思路,get時(shí)的操作稱為track,set時(shí)的操作稱為trigger,回調(diào)稱為effect。

先來(lái)個(gè)導(dǎo)圖:

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中應(yīng)該會(huì)做一個(gè)遞歸的封裝。

區(qū)別是只做一層封裝的話只能檢測(cè)到外層的=操作,內(nèi)層的如Array.push,或者嵌套的替換等都是無(wú)法經(jīng)過(guò)set和get的。

實(shí)現(xiàn)track

在track中我們會(huì)將當(dāng)前觸發(fā)的effect也就是observe的內(nèi)容或者其他內(nèi)容壓入關(guān)系鏈中,以便trigger時(shí)可以調(diào)用到這個(gè)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是一個(gè)weakMap,使用weakMap的好處是當(dāng)我們observable的對(duì)象不存在其他引用的時(shí)候會(huì)正確的被垃圾回收掉,這一條鏈?zhǔn)俏覀冾~外建立的內(nèi)容,原對(duì)象不存在的情況下不應(yīng)該在繼續(xù)存在。

這里面最終會(huì)形成一個(gè):

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

activeEffectStack和activeEffect是兩個(gè)用于數(shù)據(jù)交換的全局變量,我們?cè)趃et中會(huì)把當(dāng)前的activeEffect添加到get的key的生成的Set中保存起來(lái),讓set操作可以拿到這個(gè)activeEffect然后再次調(diào)用,實(shí)現(xiàn)響應(yīng)式。

實(shí)現(xiàn)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這里按照思路實(shí)現(xiàn)一個(gè)最小的內(nèi)容,只是將get中添加的effect逐個(gè)調(diào)用。

實(shí)現(xiàn)observe

根據(jù)導(dǎo)圖,在observe中我們需要將傳入的function壓入activeEffectStack并調(diào)用一次function觸發(fā)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有可能出錯(cuò),finally中的代碼保證activeEffectStack中對(duì)應(yīng)的那個(gè)會(huì)被正確刪除。

測(cè)試

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++

實(shí)現(xiàn)computed

在Vue中一個(gè)很有用的東西是計(jì)算屬性(computed),它是依賴于其他屬性而生成的新值,會(huì)在它依賴的其他值更改時(shí)自動(dòng)更改。
我們?cè)趯?shí)現(xiàn)了ovserve之后computed就實(shí)現(xiàn)了一大半。

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)這樣會(huì)當(dāng)做get,另外還可以設(shè)置setter,setter更多的像是一個(gè)回調(diào)可以和依賴的其他屬性完全沒(méi)有關(guān)系。

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
// 并沒(méi)有為w設(shè)置setter所以并沒(méi)有生效
// 我是computed 1:test computed setter3000
console.log(w.value)

到此這篇關(guān)于手摸手教你實(shí)現(xiàn)Vue3 Reactivity的文章就介紹到這了,更多相關(guān)Vue3 Reactivity內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家! 

相關(guān)文章

  • 一篇文章教你簡(jiǎn)單使用Vue的watch偵聽(tīng)器

    一篇文章教你簡(jiǎn)單使用Vue的watch偵聽(tīng)器

    這篇文章主要為大家介紹了Vue watch偵聽(tīng)器,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來(lái)幫助
    2021-12-12
  • 詳解Vue的mixin策略

    詳解Vue的mixin策略

    這篇文章主要介紹了Vue的mixin策略的相關(guān)資料,幫助大家更好的理解和學(xué)習(xí)vue框架,感興趣的朋友可以了解下
    2020-11-11
  • Vue實(shí)現(xiàn)調(diào)用PC端攝像頭實(shí)時(shí)拍照

    Vue實(shí)現(xiàn)調(diào)用PC端攝像頭實(shí)時(shí)拍照

    這篇文章主要為大家詳細(xì)介紹了Vue實(shí)現(xiàn)調(diào)用PC端攝像頭實(shí)時(shí)拍照,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-09-09
  • ant design vue中table表格滾動(dòng)加載實(shí)現(xiàn)思路

    ant design vue中table表格滾動(dòng)加載實(shí)現(xiàn)思路

    在處理一寫(xiě)數(shù)據(jù)量特別大的情況下,我們不能把后端的數(shù)據(jù)一次性全部拿到前端在table表格中展示,為了考慮性能優(yōu)化,使用了滾動(dòng)加載表格數(shù)據(jù),這篇文章主要介紹了ant design vue中table表格滾動(dòng)加載實(shí)現(xiàn)思路,需要的朋友可以參考下
    2024-07-07
  • Vue?配置代理詳情

    Vue?配置代理詳情

    這篇文章主要介紹了Vue?配置代理詳情,文章圍繞主題的相關(guān)資料展開(kāi)詳細(xì)內(nèi)容介紹,具有一定的參考價(jià)值,需要的小伙伴可以參考一下,希望對(duì)你的學(xué)習(xí)有所幫助
    2022-04-04
  • vue 實(shí)現(xiàn)LED數(shù)字時(shí)鐘效果(開(kāi)箱即用)

    vue 實(shí)現(xiàn)LED數(shù)字時(shí)鐘效果(開(kāi)箱即用)

    這篇文章主要介紹了vue 實(shí)現(xiàn)LED數(shù)字時(shí)鐘效果(開(kāi)箱即用),每一個(gè)數(shù)字由七個(gè)元素構(gòu)成,即每一個(gè)segment元素,本文給大家分享實(shí)現(xiàn)實(shí)例,感興趣的朋友一起看看吧
    2019-12-12
  • Element Popover 彈出框的使用示例

    Element Popover 彈出框的使用示例

    這篇文章主要介紹了Element Popover 彈出框的使用示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-07-07
  • vue-pdf打包后無(wú)法預(yù)覽問(wèn)題及修復(fù)方式

    vue-pdf打包后無(wú)法預(yù)覽問(wèn)題及修復(fù)方式

    這篇文章主要介紹了vue-pdf打包后無(wú)法預(yù)覽問(wèn)題及修復(fù)方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-03-03
  • vue中for循環(huán)作用域問(wèn)題處理方式

    vue中for循環(huán)作用域問(wèn)題處理方式

    這篇文章主要介紹了vue中for循環(huán)作用域問(wèn)題處理方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-11-11
  • 前端實(shí)現(xiàn)簡(jiǎn)單的sse封裝方式(React hook Vue3)

    前端實(shí)現(xiàn)簡(jiǎn)單的sse封裝方式(React hook Vue3)

    這篇文章主要介紹了前端實(shí)現(xiàn)簡(jiǎn)單的sse封裝方式(React hook Vue3),具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-08-08

最新評(píng)論

泰顺县| 普格县| 长乐市| 秀山| 蒙城县| 全椒县| 鄢陵县| 孟州市| 云安县| 屏山县| 昂仁县| 册亨县| 冀州市| 江阴市| 巨野县| 桂东县| 崇信县| 汝南县| 云南省| 玉树县| 孟村| 黔西| 西丰县| 闸北区| 乌兰察布市| 贵州省| 明光市| 格尔木市| 岑巩县| 濉溪县| 乐陵市| 象山县| 磐安县| 上蔡县| 德令哈市| 龙州县| 贵溪市| 宕昌县| 奉节县| 赣榆县| 阿勒泰市|