欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

30行代碼實現(xiàn)React雙向綁定hook的示例代碼

 更新時間:2022年04月25日 08:53:10   作者:CreditFE信用前端  
本文主要介紹了30行代碼實現(xiàn)React雙向綁定hook的示例代碼,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習或者工作具有一定的參考學(xué)習價值,需要的朋友們下面隨著小編來一起學(xué)習學(xué)習吧

Vue和MobX中的數(shù)據(jù)可響應(yīng)給我們留下了深刻的印象,在React函數(shù)組件中我們也可以依賴hooks來實現(xiàn)一個簡易好用的useReactive。

看一下我們的目標

const CountDemo = () => {
  const reactive = useReactive({
    count: 0,
  });
  return (
    <div
      onClick={() => {
        reactive.count++;
      }}
    >
      {reactive.count}
    </div>
  );
};

簡單來說就是我們不需要再手動觸發(fā)setState的handler了,修改數(shù)據(jù),組件中的數(shù)據(jù)就會直接更新。

在Vue中我們實現(xiàn)數(shù)據(jù)可響應(yīng)概括來講需要:

1.解析模板收集依賴

2.發(fā)布訂閱實現(xiàn)更新

而React函數(shù)組件憑借函數(shù)的特性這個過程將更加簡單,因為函數(shù)組件每一次render都會重新"執(zhí)行"一遍,我們只需要改變數(shù)據(jù)之后再觸發(fā)組件渲染就能達到我們的目的。

因此實現(xiàn)這個自定義hook的核心就是:

1.維護同一份數(shù)據(jù)

2.劫持對數(shù)據(jù)的操作

3.在劫持操作中觸發(fā)組件更新 (setState)

使用Proxy代理數(shù)據(jù)

這個代理模式是實現(xiàn)響應(yīng)式數(shù)據(jù)的核心。Vue2.0 中使用defineProperty來做數(shù)據(jù)劫持,現(xiàn)在則是被Proxy模式所替代了,一句話概括defineProperty和proxy的區(qū)別就是前者劫持的是屬性訪問器,而后者可以代理整個對象(Vue3.0,MobX)。

Proxy有多達13種攔截器,我們這次用到的有 get, set, delete

const observer = (initialState, cb) => {
  const proxy = new Proxy(initialState, {
    get(target, key, receiver) {
      const val = Reflect.get(target, key, receiver);
      return typeof val === "object" && val !== null ? observer(val, cb) : val; // 遞歸處理object類型
    },
    set(target, key, val) {
      const ret = Reflect.set(target, key, val);
      cb();
      return ret;
    },
    deleteProperty(target, key) {
      const ret = Reflect.deleteProperty(target, key);
      cb();
      return ret;
    },
  });
  return proxy;
};

上面這個observer完成了對數(shù)據(jù)的基本操作代理。

這里補充一個知識點: 為什么Proxy代理的對象經(jīng)常搭配Reflect而不是操作符訪問?

Reflect更加全面,功能更強大:

  • 只要Proxy對象具有的代理方法,Reflect對象全部具有,以靜態(tài)方法的形式存在。這些方法能夠執(zhí)行默認行為,無論 Proxy 怎么修改默認行為,總是可以通過 Reflect 對應(yīng)的方法獲取默認行為。

比如上文第4行這里 Reflect.get(target,key,receiver)咋一看似乎可以和target[key]等價,但實際上不是的看下面的例子,正是由于Reflect的靜態(tài)方法的第三個參數(shù)receiver可以用來指定被調(diào)用時的this,所以使用 Reflect.get(target,key,receiver) 才能如我們預(yù)期返回正確結(jié)果。

let o = {
  getb() {
    return this.a;
  },
};
let o1 = Object.create(
  newProxy(o, {
    get(target, key, receiver) {
      return Reflect.get(target, key, receiver);
    },
  })
);
o1.a = 42;
o1.b; // 42

let o2 = Object.create(
  newProxy(o, {
    get(target, key) {
      return target[key];
    },
  })
);

o2.a = 42;
o2.b; // undefined
  • 修改某些Object方法的返回結(jié)果,讓其變得更合理。比如,Object.defineProperty(obj, name, desc)在無法定義屬性時,會拋出一個錯誤,而Reflect.defineProperty(obj, name, desc)則會返回false。
  • 讓Object操作都變成函數(shù)行為。某些Object操作是命令式,比如name in obj和delete obj[name],而Reflect.has(obj, name)和Reflect.deleteProperty(obj, name)讓它們變成了函數(shù)行為。
const useReactive = (initState) => {
  return observer(initState);
};

我們的基本結(jié)構(gòu)大概如上面代碼段所示,但是這里有兩個問題 :

1.我們希望函數(shù)組件每次 執(zhí)行的時候它都引用同一個代理對象

2.在組件的生命周期里observer只需要代理一次

使用useRef創(chuàng)建同一份數(shù)據(jù)引用

看到維護同一份數(shù)據(jù)我們第一反應(yīng)可能就是使用閉包來創(chuàng)建引用,但是如此就還需要我們手動維護組件的創(chuàng)建卸載和這份數(shù)據(jù)的關(guān)系,而React中天生就包含了ref 這樣的api,所以我們不需要從自行管理數(shù)據(jù)的卸載和綁定,在函數(shù)組件中直接使用useRef就可以達到我們的目的。

const useReactive = (initState) => {
  const ref = useRef(initState);
  return observer(ref.current);
};

這樣子我們就使用useRef和Proxy實現(xiàn)了對initialState的代理

添加更新handler

我們發(fā)現(xiàn)還少了一個handler即數(shù)據(jù)更改后觸發(fā)組件更新,其實到這一步就比較簡單了只需要在操作ref的值之后setState一下就可以了。

因為是在函數(shù)組件內(nèi)部所以我們可以直接借用useState引入一個“更新觸發(fā)器”,并將這個觸發(fā)器傳入observer代理方法。

function useReactive<S extends object>(initialState: S): S {
    const [, setFlag] = useState({});
    const ref = useRef < S > (initialState)
    return observer(ref.current, () => {
        setFlag({}); // {} !== {} 因此會觸發(fā)組件更新
    });
}

去除多次Proxy

在完成上面幾個步驟之后,我們基本已經(jīng)可以實現(xiàn)開頭demo中的效果了,但是還有一個問題:

由于是函數(shù)組件在state更新之后useReactive也會執(zhí)行,因此observer就會被多次執(zhí)行,而我們的預(yù)期,這個代理行為應(yīng)該只在組件創(chuàng)建之初執(zhí)行一次就可以了,因此這里我們也需要進行一些改造,方法依然是依靠 ref在函數(shù)組件多次執(zhí)行時返回同一份數(shù)據(jù)這個特點:

function useReactive(initialState) {
  const refState = useRef(initialState);
  const [, setUpdate] = useState({});
  const refProxy = useRef({
    data: null,
    initialized: false,
  });
  // 在創(chuàng)建proxy的ref時我們加一個initialized標志位,這樣當組件state更新執(zhí)行時
  // useReactive再次執(zhí)行就可以根據(jù)這個標志位來決定是直接返回current上的data值還是重新執(zhí)行proxy了
  if (refProxy.current.initialized === false) {
    refProxy.current.data = observer(refState.current, () => {
      setUpdate({});
    });
    refProxy.current.initialized = true;
    return refProxy.current.data;
  }
  return refProxy.current.data;
}

添加緩存完善代碼

上面解決了函數(shù)組件更新方式所帶來的重復(fù)執(zhí)行問題,這里還需要解決外部操作導(dǎo)致的重復(fù)代理,即如果一個initialState已經(jīng)被代理過了,那么我們是不希望它被二次代理的(用戶可能使用了兩次useReactive來代理同一個對象),我們可以使用 WeakMap來進行緩存記錄

const proxyMap = new WeakMap();
const observer = (initialState, cb) => {
  const existing = proxyMap.get(initialState);
  // 添加緩存 防止重新構(gòu)建proxy
  if (existing) {
    return existing;
  }

  const proxy = new Proxy(initialState, {
    get(target, key, receiver) {
      const val = Reflect.get(target, key, receiver);
      return typeof val === "object" && val !== null ? observer(val, cb) : val; // 遞歸處理object類型
    },
    set(target, key, val) {
      const ret = Reflect.set(target, key, val);
      cb();
      return ret;
    },
    deleteProperty(target, key) {
      const ret = Reflect.deleteProperty(target, key);
      cb();
      return ret;
    },
  });
  proxyMap.set(initialState, proxy);
  return proxy;
};

總結(jié)

至此我們的useReactive就基本可用了,回顧一下全部代碼:

const proxyMap = new WeakMap();

const observer = (initialState, cb) => {
  const existing = proxyMap.get(initialState);
  if (existing) return existing;
  const proxy = new Proxy(initialState, {
    get(target, key, receiver) {
      const val = Reflect.get(target, key, receiver);
      return typeof val === "object" && val !== null ? observer(val, cb) : val; // 遞歸處理object類型
    },
    set(target, key, val) {
      const ret = Reflect.set(target, key, val);
      cb()
      return ret;
    },
    deleteProperty(target, key) {
      const ret = Reflect.deleteProperty(target, key);
      cb();
      return ret;
    },
  });
  return proxyMap.set(initialState, proxy) && proxy;
};

function useReactive(initialState) {
  const refState = useRef(initialState);
  const [, setUpdate] = useState({});
  const refProxy = useRef({
    data: null,
    initialized: false,
  });
  if (refProxy.current.initialized === false) {
    refProxy.current.data = observer(refState.current, () => {
      setUpdate({});
    });
    refProxy.current.initialized = true;
    return refProxy.current.data;
  }
  return refProxy.current.data;
}

Sandbox 示例

https://codesandbox.io/s/silly-haze-xfuxoy?file=/src/App.js

代碼雖少但五臟俱全,上面這個useReactive實現(xiàn)方式幾乎和ahooks中的useReactive一致,這個包里還包含了很多其他簡單有用的hooks集合,感興趣的朋友可以了解一下其他hooks的實現(xiàn),輔助你業(yè)務(wù)開發(fā)的同時幫助你加深對 React 工作原理的理解。

到此這篇關(guān)于30行代碼實現(xiàn)React雙向綁定hook的示例代碼的文章就介紹到這了,更多相關(guān)React雙向綁定hook內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • React之使用useState異步刷新的問題

    React之使用useState異步刷新的問題

    這篇文章主要介紹了React之使用useState異步刷新的問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-03-03
  • 在React中寫一個Animation組件為組件進入和離開加上動畫/過度效果

    在React中寫一個Animation組件為組件進入和離開加上動畫/過度效果

    這篇文章主要介紹了在React中寫一個Animation組件為組件進入和離開加上動畫/過度效果,本文通過實例代碼給大家介紹的非常詳細,具有一定的參考借鑒價值,需要的朋友可以參考下
    2019-06-06
  • 解決react中useState狀態(tài)異步更新的問題

    解決react中useState狀態(tài)異步更新的問題

    本文主要介紹了react中useState狀態(tài)異步更新的問題,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習或者工作具有一定的參考學(xué)習價值,需要的朋友們下面隨著小編來一起學(xué)習學(xué)習吧
    2022-07-07
  • react父組件調(diào)用子組件的方式匯總

    react父組件調(diào)用子組件的方式匯總

    在react中常用props實現(xiàn)子組件數(shù)據(jù)到父組件的傳遞,但是父組件調(diào)用子組件的功能卻不常用,下面這篇文章主要給大家介紹了關(guān)于react父組件調(diào)用子組件的相關(guān)資料,需要的朋友可以參考下
    2022-08-08
  • 聊聊jenkins部署vue/react項目的問題

    聊聊jenkins部署vue/react項目的問題

    本文給大家介紹了jenkins部署vue/react項目的問題,文末給大家提到了centOS安裝jenkins的腳本,本文給大家介紹的非常詳細,對大家的學(xué)習或工作具有一定的參考借鑒價值,需要的朋友參考下吧
    2022-02-02
  • 在react中使用vuex的示例代碼

    在react中使用vuex的示例代碼

    這篇文章主要介紹了在react中使用vuex的示例代碼,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-07-07
  • react+redux仿微信聊天界面

    react+redux仿微信聊天界面

    這篇文章主要介紹了react+redux仿微信聊天IM實例|react仿微信界面 ,本文圖文并茂給大家介紹的非常詳細,具有一定的參考借鑒價值,需要的朋友可以參考下
    2019-06-06
  • React?Hooks?實現(xiàn)的中文輸入組件

    React?Hooks?實現(xiàn)的中文輸入組件

    這篇文章主要為大家介紹了React?Hooks實現(xiàn)的中文輸入組件示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-05-05
  • 使用Axios在React中請求數(shù)據(jù)的方法詳解

    使用Axios在React中請求數(shù)據(jù)的方法詳解

    這篇文章主要給大家介紹了初學(xué)React,如何規(guī)范的在react中請求數(shù)據(jù),主要介紹了使用axios進行簡單的數(shù)據(jù)獲取,加入狀態(tài)變量,優(yōu)化交互體驗,自定義hook進行數(shù)據(jù)獲取和使用useReducer改造請求,本文主要適合于剛接觸React的初學(xué)者以及不知道如何規(guī)范的在React中獲取數(shù)據(jù)的人
    2023-09-09
  • React如何以Hook的方式使用Echarts

    React如何以Hook的方式使用Echarts

    這篇文章主要介紹了React如何以Hook的方式使用Echarts問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-03-03

最新評論