React Hook + TypeScript 手把手帶你打造use-watch自定義Hook,實現Vue中的watch功能。

前言

在Vue中,咱們常常須要用watch去觀察一個值的變化,經過新舊值的對比去作一些事情。git

可是React Hook中好像並無提供相似的hook來讓咱們實現相同的事情github

不過好在Hook的好處就在於它能夠自由組合各類基礎Hook從而實現強大的自定義Hook。函數

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

實現

實現雛形

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

type Callback<T> = (prev: T | undefined) => void;

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

如今咱們使用的時候就能夠code

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發生變化的時候,會執行傳入的回調函數。xml

如今咱們加入舊值的保存邏輯,以便於在每次調用傳進去的回調函數的時候,能夠在回調函數中拿到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函數把上一次保留的值給到外部。get

如今外部使用的時候 就能夠

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…

相關文章
相關標籤/搜索