­

TypeScript從零實現React自定義Hook,實現Vue中的watch功能。

  • 2020 年 4 月 11 日
  • 筆記

前言

在Vue中,我們經常需要用watch去觀察一個值的變化,通過新舊值的對比去做一些事情。

但是React Hook中好像並沒有提供類似的hook來讓我們實現相同的事情

不過好在Hook的好處就在於它可以自由組合各種基礎Hook從而實現強大的自定義Hook。

本篇文章就帶你打造一個簡單好用的use-watch hooks。

實現

實現雛形

首先分析一下Vue中watch的功能,就是一個響應式的值發生改變以後,會觸發一個回調函數,那麼在React中自然而然的就想到了useEffect這個hook,我們先來打造一個基礎的代碼雛形,把我們想要觀察的值作為useEffect的依賴傳入。

type Callback<T> = (prev: T | undefined) => void;    function useWatch<T>(dep: T, callback: Callback<T>) {    useEffect(() => {     callback();    }, [dep]);  }  複製代碼

現在我們使用的時候就可以

const App: React.FC = () => {    const [count, setCount] = useState(0);      useWatch(count, () => {      console.log('currentCount: ', count);    })      const add = () => setCount(prevCount => prevCount + 1)      return (      <div>        <p> 當前的count是{count}</p>        {count}        <button onClick={add} className="btn">+</button>      </div>    )  }  複製代碼

實現oldValue

在每次count發生變化的時候,會執行傳入的回調函數。

現在我們加入舊值的保存邏輯,以便於在每次調用傳進去的回調函數的時候,可以在回調函數中拿到count上一次的值。

什麼東西可以在一個組件的生命周期中充當一個存儲器的功能呢,當然是useRef啦。

function useWatch<T>(dep: T, callback: Callback<T>) {    const prev = useRef<T>();      useEffect(() => {      callback(prev.current);      prev.current = dep;    }, [dep]);      return () => {      stop.current = true;    };  }  複製代碼

這樣就在每一次更新prev里保存的值為最新的值之前,先調用callback函數把上一次保留的值給到外部。

現在外部使用的時候 就可以

const App: React.FC = () => {    const [count, setCount] = useState(0);      useWatch(count, (oldCount) => {      console.log('oldCount: ', oldCount);      console.log('currentCount: ', count);    })      const add = () => setCount(prevCount => prevCount + 1)      return (      <div>        <p> 當前的count是{count}</p>        {count}        <button onClick={add} className="btn">+</button>      </div>    )  }  複製代碼

實現immediate

其實到此為止,已經實現了Vue中watch的主要功能了,

現在還有一個問題是useEffect會在組件初始化的時候就默認調用一次,而watch的默認行為不應該這樣。

現在需要在組件初始化的時候不要調用這個callback,還是利用useRef來做,利用一個標誌位inited來保存組件是否初始化的標記。

並且通過第三個參數config來允許用戶改變這個默認行為。

type Callback<T> = (prev: T | undefined) => void;  type Config = {    immediate: boolean;  };    function useWatch<T>(dep: T, callback: Callback<T>, config: Config = { immediate: false }) {    const { immediate } = config;      const prev = useRef<T>();    const inited = useRef(false);      useEffect(() => {      const execute = () => callback(prev.current);        if (!inited.current) {        inited.current = true;        if (immediate) {          execute();        }      } else {        execute();      }      prev.current = dep;    }, [dep]);  }    複製代碼

實現stop

還是通過useRef做,只是把控制ref標誌的邏輯暴露給外部。

type Callback<T> = (prev: T | undefined) => void;  type Config = {    immediate: boolean;  };    function useWatch<T>(dep: T, callback: Callback<T>, config: Config = { immediate: false }) {    const { immediate } = config;      const prev = useRef<T>();    const inited = useRef(false);    const stop = useRef(false);      useEffect(() => {      const execute = () => callback(prev.current);        if (!stop.current) {        if (!inited.current) {          inited.current = true;          if (immediate) {            execute();          }        } else {          execute();        }        prev.current = dep;      }    }, [dep]);      return () => {      stop.current = true;    };  }  複製代碼

這樣在外部就可以這樣去停止本次觀察。

const App: React.FC = () => {    const [prev, setPrev] = useState()    const [count, setCount] = useState(0);      const stop = useWatch(count, (prevCount) => {      console.log('prevCount: ', prevCount);      console.log('currentCount: ', count);      setPrev(prevCount)    })      const add = () => setCount(prevCount => prevCount + 1)      return (      <div>        <p> 當前的count是{count}</p>        <p> 前一次的count是{prev}</p>        {count}        <button onClick={add} className="btn">+</button>        <button onClick={stop} className="btn">停止觀察舊值</button>      </div>    )  }  複製代碼

源碼地址:

github.com/sl1673495/u…

文檔地址:

文檔是基於docz生成的,配合mdx還可以實現非常好用的功能預覽: sl1673495.github.io/use-watch-h…