簡易實現React的useState

setState

  1. 首次渲染組件時,App會被調用,獲得虛擬dom,建立真實的dom
  2. 當點擊button時,調用setN,再次調用App,獲得虛擬dom,用DOM Diff算法更新dom
import React from "react";
import ReactDOM from "react-dom";
const rootElement = document.getElementById("root");

function App() {
  const [n, setN] = React.useState(0);
  console.log("App 運行了"); // App 被調用就會執行一次
  console.log(`n: ${n}`); // App 被調用後 n 每次都不同
  return (
    <div className="App">
      <p>{n}</p>
      <p>
        <button onClick={() => setN(n + 1)}>+1</button>
      </p>
    </div>
  );
}

ReactDOM.render(<App />, rootElement);

nApp被調用後每次都會變化,可是setN()卻不會改變nreact

  • setN必定會修改數據x,將n + 1存入x
  • setN必定會觸發App從新渲染`
  • useState確定會從x讀取n的最新值
  • 每一個組件都有本身的數據x,咱們將其命名成state

本身來實現setState

  • 聲明一個myUseState,接收一個初始值initialValue
  • 將初始值initialValue賦值給一箇中間變量state
  • 內部聲明一個函數setState,接收一個newValue,再將newValue賦值給state,並執行App
  • 返回statesetState
import React from "react";
import ReactDOM from "react-dom";
const rootElement = document.getElementById("root");

const myUseState = initialValue => {
  let state = initialValue;
  const setState = newValue => {
    state = newValue;
    render();
  };
  return [state, setState];
};

const render = () => {
  ReactDOM.render(<App />, rootElement);
};

function App() {
  const [n, setN] = myUseState(0);
  return (
    <div className="App">
      <p>{n}</p>
      <p>
        <button onClick={() => setN(n + 1)}>+1</button>
      </p>
    </div>
  );
}

ReactDOM.render(<App />, rootElement);

可是這樣有個問題每次執行setN時,都會把state設置爲初始值,由於每次執行setN都會傳入一個初始值0算法

解決這個問題就是將state變成全局變量數組

import React from "react";
import ReactDOM from "react-dom";
const rootElement = document.getElementById("root");

let _state;
const myUseState = initialValue => {
  _state = _state === undefined ? initialValue : _state;
  const setState = newValue => {
    _state = newValue;
    render();
  };
  return [_state, setState];
};

const render = () => {
  ReactDOM.render(<App />, rootElement);
};

function App() {
  const [n, setN] = myUseState(0);
  return (
    <div className="App">
      <p>{n}</p>
      <p>
        <button onClick={() => setN(n + 1)}>+1</button>
      </p>
    </div>
  );
}

ReactDOM.render(<App />, rootElement);

若是一個組件使用了兩個useState

function App() {
  const [n, setN] = myUseState(0);
  const [M, setM] = myUseState(1); // 會出問題,第一個會被覆蓋
  return (
    <div className="App">
      <p>{n}</p>
      <p>
        <button onClick={() => setN(n + 1)}>+1</button>
      </p>
      <p>{m}</p>
      <p>
        <button onClick={() => setM(m + 1)}>+1</button>
      </p>
    </div>
  );
}

因爲全部數據都存在一個_state中,因此會衝突。微信

可使用數組去解決_state重複問題。dom

  • _state聲明爲[],同時聲明一個索引index = 0
  • myUseState方法內部聲明一個臨時變量currentIndex,用來保存索引index
  • 用索引去初始化_state
  • setState時也將經過索引去操做_state
  • index += 1
  • 返回_state[currentIndex]setState
  • 每次調用render方法是將index重置爲0
import React from "react";
import ReactDOM from "react-dom";
const rootElement = document.getElementById("root");

let _state = [];
let index = 0;
const myUseState = initialValue => {
  const currentIndex = index;
  _state[currentIndex] =
    _state[currentIndex] === undefined ? initialValue : _state[currentIndex];

  const setState = newValue => {
    _state[currentIndex] = newValue;
    render();
  };
  index += 1;
  return [_state[currentIndex], setState];
};

const render = () => {
  index = 0;
  ReactDOM.render(<App />, rootElement);
};

function App() {
  const [n, setN] = myUseState(0);
  const [m, setM] = myUseState(0);
  return (
    <div className="App">
      <p>{n}</p>
      <p>
        <button onClick={() => setN(n + 1)}>N+1</button>
      </p>
      <p>{m}</p>
      <p>
        <button onClick={() => setM(m + 1)}>M+1</button>
      </p>
    </div>
  );
}

ReactDOM.render(<App />, rootElement);

useState調用順序

  • 若第一渲染時n是第一個,m是第二個,k是第三個
  • 則第二次渲染時必須保證順序徹底一致
  • React不容許出現以下代碼
function App() {
  const [n, setN] = myUseState(0);
  let m, setM;
  if (n % 2 === 1) [m, setM] = myUseState(0); // 報錯
  return (
    <div className="App">
      <p>{n}</p>
      <p>
        <button onClick={() => setN(n + 1)}>N+1</button>
      </p>
      <p>{m}</p>
      <p>
        <button onClick={() => setM(m + 1)}>M+1</button>
      </p>
    </div>
  );
}

報錯信息:React has detected a change in the order of Hooks called by App. This will lead to bugs and errors if not fixed.函數

問題:

App用了_stateindex那其餘組件用什麼?post

  • 給每一個組件建立一個_stateindex

放在全局做用域重名了咋整code

  • 放在組件對象的虛擬節點對象上

感受會是bug

function App() {
  const [n, setN] = myUseState(0);
  const log () => setTimeout(() => console.log(`n: ${n}`), 3000)
  return (
    <div className="App">
      <p>{n}</p>
      <p>
        <button onClick={() => setN(n + 1)}>N+1</button>
        <button onClick={log}>log</button>
      </p>
    </div>
  );
}

先點擊N+1時,再點擊log,輸出是沒有問題對象

若是先點擊log,在點擊N+1,就會發現輸出的竟然是0。難道N+1後輸出不是1麼。索引

由於setN是不會改變n,而是生成一個新的n

解決上面那個感受是bug

  • 全局變量,window.xxx
  • useRef不只能夠用於div,還能用於任意數據
  • useContext不能能貫穿始終,還能貫穿不一樣組件
function App() {
  const nRef = React.useRef(0);   // { current: 0 }
  const log () => setTimeout(() => console.log(`n: ${React.useRef(0)}`), 3000);
  const update = React.useState(null)[1];
  return (
    <div className="App">
      <p>{nRef.current}</p>
      <p>
        <button onClick={() => { 
          nRef.current += 1;  // 這裏不能實時更新
          update(nRef.current;
          )}}>N+1</button>
        <button onClick={log}>log</button>
      </p>
    </div>
  );
}

React.current += 1不會讓App從新渲染。

總結

  • 每一個函數組件對象一個React節點
  • 每一個節點保存着stateindex
  • useState會讀取state[index]
  • indexuseState出現的順序決定
  • setState會修改state,並觸發更新。

Tips

  1. 這裏是對React作了簡化,方便理解。
  2. React對象節點應該是FiberNode
  3. _state的真實名稱爲memorizedStateindex的實現用到了鏈表

另外可添加微信ttxbg180218交流

相關文章
相關標籤/搜索