国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

immer源碼閱讀

JerryZou / 2720人閱讀

摘要:我們看一下整個函數(shù)可以看到為的情況下,邏輯很簡單,對應(yīng)屬性沒變化的時候創(chuàng)建代理,返回值,對應(yīng)屬性變化了,直接返回對應(yīng)值。

immer是前端在immutable領(lǐng)域的另外一個實踐,相比較immutable而言,它擁有更低的學(xué)習(xí)成本,在使用上可以直接使用js 原生api去修改引用對象,得到一個新的不可變的引用對象。

import produce from "immer"

const baseState = [
    {
        todo: "Learn typescript",
        done: true
    },
    {
        todo: "Try immer",
        done: false
    }
]

const nextState = produce(baseState, draftState => {
    draftState.push({todo: "Tweet about it"})
    draftState[1].done = true
})

immer的實現(xiàn)主要有兩種方案,在支持Proxy的環(huán)境下會使用Proxy,在不支持Proxy的環(huán)境下會使用defineProperty。在這里主要介紹Proxy的實現(xiàn)方案,因為基本的實現(xiàn)思路都是相似的,通過學(xué)習(xí)Proxy的實現(xiàn)方案,我們也能熟悉一下在平時業(yè)務(wù)開發(fā)時很少用的Proxy api。

從上面的例子可以看出使用immer,主要通過調(diào)用produce這個api,從源碼中可以看到produce這個函數(shù)其實調(diào)用了produceProxy函數(shù):

function produce(baseState, producer) {
    ...
    return getUseProxies()
        ? produceProxy(baseState, producer)
        : produceEs5(baseState, producer)
}
produceProxy

在繼續(xù)閱讀immer的源碼之前,我們不妨想一下,如何通過Proxy實現(xiàn)immer的功能?

我們必須在沒修改對象的情況下獲取原對象的屬性,在修改的情況下又不要修改原對象的屬性。我們可以很容易想到get handler的操作:

new Proxy(data, {
  get(target, prop){
    return target[prop]
  },
  set(target, prop, value){
    
  }
})

但是set如何處理?所以我們代理的對象不能只是數(shù)據(jù)本身,在immer中每個代理的對象都是以下結(jié)構(gòu):

function createState(parent, base) {
    return {
        base,            // 要代理的原數(shù)據(jù)
        parent,          // 要代理數(shù)據(jù)的父對象
        copy: undefined,    // 在set時,修改這個數(shù)據(jù)對應(yīng)的值
        proxies: {},        // 講解get時,再談這個
        modified: false,    // 有沒有要修改這份數(shù)據(jù)
        finalized: false    //  本文最后會講解
    }
}

接下來我們回到produceProxy函數(shù):

export function produceProxy(baseState, producer) {
    ...
    const previousProxies = proxies
    proxies = [] // 通過createProxy創(chuàng)建的proxy都會在這里面
    try {
        // create proxy for root
        const rootProxy = createProxy(undefined, baseState) // 創(chuàng)建根代理
        // execute the thunk
        const returnValue = producer.call(rootProxy, rootProxy) // 執(zhí)行函數(shù),拿到返回值
        // and finalize the modified proxy
        let result
        // check whether the draft was modified and/or a value was returned
        if (returnValue !== undefined && returnValue !== rootProxy) {
            ...
        } else {
            result = finalize(rootProxy)
        }
        // revoke all proxies
        each(proxies, (_, p) => p.revoke())  // 銷毀代理,主要是為了防止外層的變量拿到這個代理做一些操作
        return result
    } finally {
        proxies = previousProxies
    }
}

可以看到主要邏輯還是很清晰的,為數(shù)據(jù)創(chuàng)建代理,然后調(diào)用producer函數(shù),最后finalize(rootProxy)。

接下來看一下createProxy的相關(guān)邏輯:

function createProxy(parentState, base) {
    if (isProxy(base)) throw new Error("Immer bug. Plz report.")
    const state = createState(parentState, base)
    const proxy = Array.isArray(base)
        ? Proxy.revocable([state], arrayTraps)
        : Proxy.revocable(state, objectTraps)
    proxies.push(proxy)
    return proxy.proxy
}

createProxy的邏輯看起來很簡單,但是你可能會有兩個疑問:

為什么使用Proxy.revocable做代理,而不是new Proxy?

為什么要把數(shù)組的state包裹到一個數(shù)組里面[state]

先來回答第一個問題,使用Proxy.revocable主要是為了防止以下情況的出現(xiàn):

let proxy
const nextState = produce(baseState, s => {
    proxy = s
    s.aProp = "hello"
})
proxy.aProp = "Hallo"

如代碼所示,如果produce執(zhí)行完成后,proxy不做revoke,會導(dǎo)致外部變量拿到的proxy,還有作用,就會造成不期望的情況出現(xiàn)。所以在produceProxy最后,會把函數(shù)執(zhí)行周期所有創(chuàng)建的proxy都revoke掉。

第二個問題,通過produceProxy的代碼,我們可以看到在調(diào)用外部傳入的producer函數(shù)的時候,傳給producer函數(shù)的是proxy,如果不使用[state],proxy代理的state就是一個對象。此時如果對其類型進行判斷Array.isArray(proxy)就會返回false。

我們可以看一下objectTraps和arrayTraps分別是什么:

const objectTraps = {
    get,
    has(target, prop) {
        return prop in source(target)
    },
    ownKeys(target) {
        return Reflect.ownKeys(source(target))
    },
    set,
    deleteProperty,
    getOwnPropertyDescriptor,
    defineProperty,
    setPrototypeOf() {
        throw new Error("Immer does not support `setPrototypeOf()`.")
    }
}

const arrayTraps = {}
each(objectTraps, (key, fn) => {
    arrayTraps[key] = function() {
        arguments[0] = arguments[0][0]
        return fn.apply(this, arguments) // state push proxy
    }
})

可以看到objectTraps是一個很普通的handlers,而arrayTraps則是在objectTraps上包裹了一層,傳入的參數(shù)將[state]改為了state

Handler

接下來看一下get,先看一下數(shù)據(jù)沒有被修改過的情況(即還沒調(diào)用過set)

function get(state, prop) {
    if (prop === PROXY_STATE) return state  // PROXY_STATE是一個symbol值,有兩個作用,一是便于判斷對象是不是已經(jīng)代理過,二是幫助proxy拿到對應(yīng)state的值
    if (state.modified) {
        ...
    } else {
        if (has(state.proxies, prop)) return state.proxies[prop] 
        const value = state.base[prop]
        if (!isProxy(value) && isProxyable(value))
            return (state.proxies[prop] = createProxy(state, value))
        return value
    }
}

get函數(shù)主要有兩個作用:

返回對應(yīng)的數(shù)據(jù)

為對應(yīng)的數(shù)據(jù)創(chuàng)建代理

通過get的時候創(chuàng)建代理就保證了不管在produce中操作的數(shù)據(jù)嵌套有多深,我們操作的都是代理對象,如:

a.b.c = 1           // a.b是一個代理對象
a.b.c.push(1)       // a.b.c是一個代理對象

接下來看set函數(shù)

function set(state, prop, value) {
    // set的關(guān)鍵是不改老的值,所以改的copy上的值
    if (!state.modified) {
        if (
            (prop in state.base && is(state.base[prop], value)) ||
            (has(state.proxies, prop) && state.proxies[prop] === value) //值不變的情況下直接return true
        )
            return true
        markChanged(state)
    }
    state.copy[prop] = value
    return true
}

set的邏輯相對簡單,set值就是改state.copy上的值,同時如果state是第一次修改,就markChanged(state)

function markChanged(state) {
    if (!state.modified) {
        state.modified = true
        state.copy = shallowCopy(state.base)
        // copy the proxies over the base-copy
        Object.assign(state.copy, state.proxies) // yup that works for arrays as well
        if (state.parent) markChanged(state.parent)
    }
}

在markChanged函數(shù)中,把base的屬性和proxies的上的屬性都淺拷貝給了copy,從此,對目標(biāo)對象的取值還是設(shè)值都是操作state.copy。

我們看一下整個get函數(shù),可以看到state.modified為true的情況下,邏輯很簡單,對應(yīng)屬性沒變化的時候創(chuàng)建代理,返回值,對應(yīng)屬性變化了,直接返回對應(yīng)值。

function get(state, prop) {
    if (prop === PROXY_STATE) return state
    if (state.modified) {
        const value = state.copy[prop]
        if (value === state.base[prop] && isProxyable(value))
            return (state.copy[prop] = createProxy(state, value))
        return value
    } else {
        if (has(state.proxies, prop)) return state.proxies[prop]
        const value = state.base[prop]
        if (!isProxy(value) && isProxyable(value))
            return (state.proxies[prop] = createProxy(state, value))
        return value
    }
}
finalize

除了get和set,還有6個其他的handler,但整體思路和get、set一致,就不一一介紹了。我們看一下produceProxy的最后一塊,也是我認(rèn)為最不好理解的一部分finalize。

export function produceProxy(baseState, producer) {
    ...
    const previousProxies = proxies
    proxies = [] // 通過createProxy創(chuàng)建的proxy都會在這里面
    try {
        // create proxy for root
        const rootProxy = createProxy(undefined, baseState) // 創(chuàng)建根代理
        // execute the thunk
        const returnValue = producer.call(rootProxy, rootProxy) // 執(zhí)行函數(shù),拿到返回值
        // and finalize the modified proxy
        let result
        // check whether the draft was modified and/or a value was returned
        if (returnValue !== undefined && returnValue !== rootProxy) {
            ...
        } else {
            result = finalize(rootProxy)
        }
        // revoke all proxies
        each(proxies, (_, p) => p.revoke())  // 銷毀代理,主要是為了防止外層的變量拿到這個代理做一些操作
        return result
    } finally {
        proxies = previousProxies
    }
}

前面通過producer函數(shù)對rootProxy進行了一系列的操作,現(xiàn)在我們要返回下一次的state,我們要遞歸地state上的屬性,把屬性對應(yīng)的代理對象,改為對應(yīng)的值。

export function finalize(base) {
    if (isProxy(base)) {
        const state = base[PROXY_STATE]
        if (state.modified === true) {
            if (state.finalized === true) return state.copy 
            state.finalized = true
            return finalizeObject(
                useProxies ? state.copy : (state.copy = shallowCopy(base)),
                state
            )
        } else {
            return state.base
        }
    }
    finalizeNonProxiedObject(base) // base不是代理則說明base下面的屬性會有代理
    return base
}

因為我們第一次傳入finalize函數(shù)的是rootProxy,是一個Proxy,我們先看isProxy(base)為true的情況,簡化一下對應(yīng)的邏輯,可以看到邏輯很簡單:

const state = base[PROXY_STATE]
if (state.modified === true) {
    return finalizeObject(
        state.copy,
        state
    )
} else {
    return state.base
}

如果state沒有被修改過,就直接返回state.base,如果state修改過,就返回finalizeObject(state.copy, state)函數(shù)的返回值。

至于為什么要設(shè)置state.finalized的值,我們稍后再講,我們先看一下finalizeObject函數(shù)的邏輯。

function finalizeObject(copy, state) {
    const base = state.base
    each(copy, (prop, value) => {
        if (value !== base[prop]) copy[prop] = finalize(value)
    })
    return freeze(copy)
}

finalizeObject函數(shù)遍歷copy上的屬性,對于value和base[prop]不相等的情況,調(diào)用finalize(value),最后freeze copy對象,然后返回。

value和base[prop]不相等說明可能存在兩種情況:

由于value被get過,此時value是一個代理對象。

value被set過,此時value可能是一個普通的值也可能是一個代理對象(比如把rootProxy的某個子孫代理屬性賦值給了copy[prop],即value)。

所以我們就好理解finalize函數(shù)中為什么既要處理value是proxy的情況,又要處理value不是proxy的情況了。

當(dāng)value不是一個proxy的時候,value的子屬性可能是一個proxy(因為賦值的時候,可能值的子屬性是proxy),immer用finalizeNonProxiedObject處理這種情況。

function finalizeNonProxiedObject(parent) {
    // If finalize is called on an object that was not a proxy, it means that it is an object that was not there in the original
    // tree and it could contain proxies at arbitrarily places. Let"s find and finalize them as well
    if (!isProxyable(parent)) return
    if (Object.isFrozen(parent)) return
    each(parent, (i, child) => {
        if (isProxy(child)) {
            parent[i] = finalize(child)
        } else finalizeNonProxiedObject(child)
    })
    // always freeze completely new data
    freeze(parent)
}

如果屬性值是一個proxy,就調(diào)用finalize,以去除proxy,否則就遞歸的去找下面屬性是不是proxy。

最后我們說一下finalize函數(shù)中為什么要state.finalized = true,按照正常的邏輯屬性在finalize函數(shù)中只會訪問一次,根本這行代碼。

這行代碼是為了防止一種情況,某個屬性值被賦值給了另外一個屬性,這兩個屬性訪問的是一個數(shù)據(jù),此時如果state已經(jīng)finalized,就直接返回他的copy。

至此,我們已經(jīng)閱讀完immer的核心邏輯,和所有比較難以理解的地方。

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/96341.html

相關(guān)文章

  • immer.js 實戰(zhàn)講解文檔

    摘要:無奈網(wǎng)絡(luò)上完善的文檔實在太少,所以自己寫了一份,本篇文章以貼近實戰(zhàn)的思路和流程,對進行了全面的講解。這使得成為了真正的不可變數(shù)據(jù)。的使用非常靈活,多多思考,相信你還可以發(fā)現(xiàn)更多其他的妙用參考文檔官方文檔 文章在 github 開源, 歡迎 Fork 、Star 前言 Immer 是 mobx 的作者寫的一個 immutable 庫,核心實現(xiàn)是利用 ES6 的 proxy,幾乎以最小的成...

    zhiwei 評論0 收藏0
  • 精讀《源碼學(xué)習(xí)》

    摘要:精讀原文介紹了學(xué)習(xí)源碼的兩個技巧,并利用實例說明了源碼學(xué)習(xí)過程中可以學(xué)到許多周邊知識,都讓我們受益匪淺。討論地址是精讀源碼學(xué)習(xí)如果你想?yún)⑴c討論,請點擊這里,每周都有新的主題,周末或周一發(fā)布。 1. 引言 javascript-knowledge-reading-source-code 這篇文章介紹了閱讀源碼的重要性,精讀系列也已有八期源碼系列文章,分別是: 精讀《Immer.js》源...

    aboutU 評論0 收藏0
  • immer.js 簡介及源碼解析

    摘要:例如維護一份在內(nèi)部,來判斷是否有變化,下面這個例子就是一個構(gòu)造函數(shù),如果將它的實例傳入對象作為第一個參數(shù),就能夠后面的處理對象中使用其中的方法上面這個構(gòu)造函數(shù)相比源代碼省略了很多判斷的部分。 showImg(https://segmentfault.com/img/bV27Dy?w=1400&h=544); 博客鏈接:下一代狀態(tài)管理工具 immer 簡介及源碼解析 JS 里面的變量類...

    Profeel 評論0 收藏0
  • Immer.js簡析

    摘要:所以整個過程只涉及三個輸入狀態(tài),中間狀態(tài),輸出狀態(tài)關(guān)鍵是是如何生成,如何應(yīng)用修改,如何生成最終的。至此基本把上的模式解析完畢。結(jié)束實現(xiàn)還是相當(dāng)巧妙的,以后可以在狀態(tài)管理上使用一下。 開始 在函數(shù)式編程中,Immutable這個特性是相當(dāng)重要的,但是在Javascript中很明顯是沒辦法從語言層面提供支持,但是還有其他庫(例如:Immutable.js)可以提供給開發(fā)者用上這樣的特性,所...

    Aceyclee 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<