<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          當 React Hooks 遇見 Vue3 Composition API

          共 13925字,需瀏覽 28分鐘

           ·

          2021-04-29 09:13

          1. 前言

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

          關于 React HooksVue3 Composition API 的熱烈討論一直都存在,雖然兩者本質(zhì)上都是實現(xiàn)狀態(tài)邏輯復用,但在實現(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 這里應該根據(jù)訪問路徑來設置。
          首先,我們來對深層對象做一次遞歸。

          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
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

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

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  中文字幕日韩乱伦 | 中文字幕一区二区三区四区 | 操逼视频网. | 久久免费激情视频 | 大香蕉在线伊 |