useState 原理

1,751 阅读2分钟

useState 用法

看下例代码,可以想象一下,点击 button 会发送什么

function App() {
  const [n, setN] = React.useState(0);
  return (
    <div className="App">
      <p>{n}</p>
      <p>
        <button onClick={() => setN(n + 1)}>+1</button>
      </p>
    </div>
  );
}
ReactDOM.render(<App />, rootElement);

脑补过程

首次渲染 render <App />

调用 App() ,得到虚拟 DIV ,创建真实 DIV

点击 button ,调用 setN(n+1), 再次 render <App />

调用 App() ,得到虚拟 DIV , DOM Diff ,更新真实 DIV

some questions

脑补之后,可以思考几个问题

执行 setN 的时候会发生什么?n会变吗?App()会重新执行吗?

如果App()会重新执行,那么 useState(0) 的时候,n 每次的值会有不同吗?

执行setN 的时候,n 并不会马上变,而是稍会儿变。

可以看到,App() 确实会重新执行,重新执行useState(0),每次n是不同的。

分析

setN 一定会修改某个数据x,将 n+1存入 x,并且一定会触发 <App />重新渲染(re-render)

useState肯定会从x读取n的最新值

每个组件都有自己的数据x,可以将其命名为 state

尝试实现 React.useState

看下面代码 示例

let _state;

function myUseState(initialValue) {
  _state = _state === undefined ? initialValue : _state;
  function setState(newState) {
    _state = newState;
    render();
  }
  return [_state, setState];
}

// 理解需要,不用在意 render 的实现
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 难道就这么简单?

别急,还有问题

如果一个组件用了两个useState怎么办,由于所有数据都放在 _state ,所以会冲突

改进思路

把 _state 做成一个对象,比如 _state={a:0,b:0} ,其实不行,因为 useState(0) 并不知道变量叫 a 还是 b

把 _state 做成一个数组, 比如 _state=[0,0], 貌似可以, 来试试看

let _state = [];
let index = 0;

function myUseState(initialValue) {
  const currentIndex = index;
  index += 1;
  _state[currentIndex] = _state[currentIndex] || initialValue;
  const setState = newState => {
    _state[currentIndex] = newState;
    render();
  };
  return [_state[currentIndex], setState];
}

_state 数组方案缺点

useState 调用顺序

若第一次渲染时 n 是第一个,m 是第二个, k 是第三个,则第二次渲染时必须保证顺序完全一致

所以 React 不允许出现如下代码, 不能存在 if 中

现在的实现代码还有一个问题

App 用了 _state 和 index , 那其他组件用什么?

解决办法:给每个组件创建一个 _state 和 index

又有问题:放全局作用域重名了怎么办?

解决办法:放在组件对应的虚拟节点对象上

总结

每个函数组件对应着一个 React 节点

每个节点保存着 state 和 index

useState 会读取 state[index]

index 由useState 出现的顺序决定

setState 会修改 state,并触发更新

文中的实现都是基于思想逻辑的简化处理