1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        當 React Hooks 遇見 Vue3 Composition API

        共 13925字,需瀏覽 28分鐘

         ·

        2021-04-29 09:13

        1. 前言

        前幾天在知乎看到了一個問題,React 的 Hooks 是否可以改為用類似 Vue3 Composition API 的方式實現(xiàn)?

        關(guān)于 React HooksVue3 Composition API 的熱烈討論一直都存在,雖然兩者本質(zhì)上都是實現(xiàn)狀態(tài)邏輯復(fù)用,但在實現(xiàn)上卻代表了兩個社區(qū)的不同發(fā)展方向。

        我想說,小孩子才分好壞,成年人表示我全都要。


        2. 你不知道的 Object.defineProperty

        那今天我們來討論一下怎么用 React Hooks 來實現(xiàn) Vue3 Composition 的效果。

        先來看一下我們最終要實現(xiàn)的效果。

        看到這個 API 的用法你會聯(lián)想到什么?沒錯,很明顯這里借用了 Proxy 或者 Object.defineProperty

        在《你不知道的 Proxy:ES6 Proxy 能做哪些有意思的事情?》一文中,我們已經(jīng)對比過兩者的用法了。


        其實這里還有一個不為人知的區(qū)別,那就是可以通過 Object.defineProperty 給對象添加一個新屬性。

        const person = {}
        Object.defineProperty(person, "name", {
            enumerabletrue,
            get() {
                return "sh22n"
            }
        })

        打印出來的效果是這樣的:

        這就很有意思了,意味著我們可以把某個對象 A 上所有屬性都掛載到對象 B 上,這樣我們不必對 A 進行任何監(jiān)聽,即不會污染 A。

        const state = { count0 }
        Object.defineProperty({}, "count", {
            get() {
                return state.count
            }
        })

        3. React Hooks + Object.defineProperty = ?

        如果將上面的代碼結(jié)合 React Hooks,那會出現(xiàn)什么效果呢?沒錯,我們的 React 變得更加 reactive 了。

        const [state, setState] = useState({ count0 })
        const proxyState = Object.defineProperty({}, "count", {
            get() {
                return state.count
            },
            set(newVal) {
                setState({ ...state, count: newVal })
            }
        })
        return (
            <h1 onClick={() => proxyState.count++}>
                { proxyState.count }
            </h1>

        )

        將這段代碼進一步封裝,可以得到一個 Custom Hook,也就是我們今天要說的 Composition API。

        const ref = (value) => {
            const [state, setState] = useState(value)
            return Object.defineProperty({}, "count", {
                get() {
                    return state.count
                },
                set(newVal) {
                    setState({ ...state, count: newVal })
                }
            })
        }
        function Counter({
            const count = ref({ value0 })
            return (
                <h1 onClick={() => count.value++}>
                    { count.value }
                </h1>

            )
        }

        當然,這段代碼還存在很多問題,依賴了對象的結(jié)構(gòu)、不支持更深層的 getter/setter 等等,我們接下來就一起來優(yōu)化一下。

        4. 實現(xiàn) Composition

        4.1 遞歸劫持屬性

        對于屬性的對象來說,我們可以遍歷,配合 Object.defineProperties 來劫持它的所有屬性。

        const descriptors = Object.keys(state).reduce((handles, key) => {
            return {
                ...handles,
                [key]: {
                    get() {
                        return state[key]
                    },
                    set(newVal) {
                        setState({ ...state, [key]: newVal })
                    }
                }
            }
        }, {})
        Object.defineProperty({}, descriptors)

        而對于更深層的對象來說,不僅要做遞歸,還要考慮 setState 這里應(yīng)該根據(jù)訪問路徑來設(shè)置。
        首先,我們來對深層對象做一次遞歸。

        const descriptors = (obj) => {
            return Object.keys(obj).reduce((handles, key) => {
                let value = obj[key];
                // 如果 value 是個對象,那就遞歸其屬性進行 `setter/getter`
                if (Object.prototype.toString.call(obj) === "[object Object]") {
                    value = Object.defineProperty({}, descriptors(value));
                }
                return {
                    ...handles,
                    [key]: {
                        get() {
                            return value
                        },
                        set(newVal) {
                            setState({ ...state, [key]: newVal })
                        }
                    }
                }
            }, {})
        }

        如果你仔細觀察了這段代碼,會發(fā)現(xiàn)有個非常致命的問題。那就是在做遞歸的時候,set(newVal) 里面的代碼并不對,state 是個深層對象,不能這么簡單地對其外層進行賦值。


        這意味著,我們需要將訪問這個對象深層屬性的一整條路徑保存下來,以便于 set 到正確的值,可以用一個數(shù)組來收集路徑上的 key 值。
        這里用使用 lodash 的 set 和 get 來做一下演示。

        const descriptors = (obj, path) => {
            return Object.keys(obj).reduce((handles, key) => {
                // 收集當前路徑的 key 
                let newPath = [...path, key],
                    value = _.get(state, newPath);

                // 如果 value 是個對象,那就遞歸其屬性進行 `setter/getter`
                if (Object.prototype.toString.call(obj) === "[object Object]") {
                    value = Object.defineProperty({}, descriptors(value, newPath));
                }
                return {
                    ...handles,
                    [key]: {
                        get() {
                            return value
                        },
                        set(newVal) {
                            _.set(state, newPath, newVal)
                            setState({ ...state })
                        }
                    }
                }
            }, {})
        }

        但是,如果傳入的是個數(shù)組,這里就會有問題了。因為我們只是對 Object 進行了攔截,沒有對 Array 進行處理。

        const isArray = arr => Object.prototype.toString.call(arr) === '[object Array]'
        const isObject = obj => Object.prototype.toString.call(arr) === '[object Object]'

        const descriptors = (obj, path) => {
            return Object.keys(obj).reduce((handles, key) => {
                // 收集當前路徑的 key 
                let newPath = [...path, key],
                    value = _.get(state, newPath);

                // 如果 value 是個對象,那就遞歸其屬性進行 `setter/getter`
                if (isObject(value)) {
                    value = Object.defineProperties({}, descriptors(value, newPath));
                }
                if (isArray(value)) {
                    value = Object.defineProperties([], descriptors(value, newPath));
                }
                return {
                    ...handles,
                    [key]: {
                        get() {
                            return value
                        },
                        set(newVal) {
                            _.set(state, newPath, newVal)
                            setState({ ...state })
                        }
                    }
                }
            }, {})
        }

        5. 完整版

        這樣,我們就實現(xiàn)了一個完整版的 ref,我將代碼和示例都放到了 codesandbox 上面:Compostion API

        const isArray = arr => Object.prototype.toString.call(arr) === '[object Array]'
        const isObject = obj => Object.prototype.toString.call(arr) === '[object Object]'
        const ref = (value) => {
          if (typeof value !== "object") {
            value = {
              value
            };
          }
          const [state, setState] = useState(value);
          const descriptors = (obj, path) => {
            return Object.keys(obj).reduce((result, key) => {
              let newPath = [...path, key];
              let v = _.get(state, newPath);
              if (isObject(v)) {
                    v = Object.defineProperties({}, descriptors(state, newPath));
                } else if (isArray(v)) {
                    v = Object.defineProperties([], descriptors(state, newPath));
                }

              return {
                ...result,
                [key]: {
                  enumerabletrue,
                  get() {
                    return v;
                  },
                  set(newVal) {
                    setState(
                        _.set(state, newPath, newVal)
                        setState({ ...state })
                    );
                  }
                }
              };
            }, {});
          };
          return Object.defineProperties(isArray(value) ? [] : {}, descriptors(state, []));
        };


        瀏覽 60
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            国产久一色综合久久精品国 | 开心五月天成人网 | 亚洲综合色色色 | 国产做受 | 黄色一级AV片 | 巨年少根与熟艳美妇 | 亚洲色欲av | 中国日逼 | 久久精品视频偷窥 | 久久久艹艹艹 |