簡單的 useState 實現


簡單的 useState 實現

本文寫於 2020 年 10 月 21 日

以下是一段非常簡單的 React 代碼:

const App = () => {
  const [n, setN] = useState(0);
  return (
    <div>
      {n}
      <button onClick={() => setN(x => x + 1)}>+1</button>
    </div>
  );
}

React.render(<App />, rootElement)

這樣的用法和以往的 setState 是有明顯的不同的,他看起來更像 redux——我們初始化一個 state,然后 dispatch 一個 action,再由 reducer 改變 state 后返回新的 state

Redux 思想實現 useState

既然我們覺得它像,那我們就來自己實現一個吧。

不熟悉 Redux 思想的同學請自行閱讀文檔

const useState = (initialValue) => {
  let state = initialValue;
  const dispatch = (newState) => {
    state = newState;
    render(<App />, document.getElementById('root'));
  };
  return [state, dispatch];
};

然后我們用這個自定義的 useState 代替 React 的 useState——就會發現我們失敗了,setN 無論如何都不會有任何反應。

這是因為我們每次重新 render 的時候都重新執行了函數,於是我們總是會重新賦值

為什么不會重新賦值?

對於這段 React 代碼來說,當我們第一次運行時,React 會進行首次渲染,即 render(<App />, ...)

在此過程中,會先調用 App(),之后便會得到虛擬 DOM,再創建真實的 Div。

當我們觸發點擊事件時,會調用 setN,再次 render()。之后調用 App(),然后得到新的虛擬 DOM,進行 diff 算法,根據 diff 算法的結果去更新新的 Div。

而不管是第一次渲染,還是第二次調用,都會調用 useState()

但是我們寫的是 useState(0) 啊,兩次調用明明是一樣的代碼,為何 n 的值不同?怎么解決這個問題呢?

很簡單,閉包嘛。

const createUseState = () => {
  let state;
  const useState = (initialValue) => {
    if (!state) {
      state = initialValue;
    }
    const dispatch = (newState) => {
      state = newState;
      render(<App />, document.getElementById('root'));
    };
    return [state, dispatch];
  };
};

這樣就解決了重新賦值的問題。

多次調用

但是我們需要多次調用 useState 呀,不可能只用一次的。

於是我們將 state 改為一個數組:const state = [];

const createUseState = () => {
  const state = [];
  let index = 0;
  return (initialValue) => {
    state[index] = state[index] || initialValue;
    const currentIndex = index;
    const dispatch = (newState) => {
      state[currentIndex] = newState;
      // 重點
      index = 0;
      ReactDOM.render(
        <React.StrictMode>
          <App />
        </React.StrictMode>,
        rootElement
      );
    };
    return [state[index++], dispatch];
  };
};

我們創建了一個 index 變量來控制索引。它需要我們保證每次重新渲染 App 傳入數組的元素是一樣的——這就是為什么我們不可以將 useState 寫在 if 判斷中。

在上述代碼中有一處重點,在於我們需要在每次 set 之后將索引歸零 index = 0

因為每次 render 結束后,React 都會重新執行該函數。

(完)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM