我们来看一下这个 + 1 的案例,里面使用了 useState ,useState是如何运行的呢?

    我们不妨自己手写探讨一下。

    1. function App() {
    2. const [n, setN] = React.useState(0);
    3. return (
    4. <div className="App">
    5. <p>{n}</p>
    6. <p>
    7. <button onClick={() => setN(n + 1)}>+1</button>
    8. </p>
    9. </div>
    10. );
    11. }

    39 React中useState原理 - 图1

    setN-定会修改数据x,将n+1存入

    setN 一定会触发重新渲染(re-render)

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

    setN 并不会改变n,App()会重新执行。

    第一尝试手写

    1. function myUseState(initialValue) {
    2. var state = initialValue;
    3. function setState(newState) {
    4. state = newState;
    5. render();
    6. }
    7. return [state, setState];
    8. }

    出现的BUG 每次都会重新复制为0。

    myUseState 会将 state 重置。

    这样我们将外部声明一个变量,他就不会重置

    1. let _state;
    2. function myUseState(initialValue) {
    3. _state = _state === undefined ? initialValue : _state;
    4. function setState(newState) {
    5. _state = newState;
    6. render();
    7. }
    8. return [_state, setState];
    9. }

    出现的BUG,如果出现了2个useState就发生冲突,原因是我们存储的所有数据都在_state

    所以一旦出现多个使用useState,j就会出现BUG。

    接着尝试_state变为一个数组,用下标去定位想要获取的值。

    1. let _state = [];
    2. let index = 0;
    3. function myUseState(initialValue) {
    4. const currentIndex = index;
    5. index += 1;
    6. _state[currentIndex] = _state[currentIndex] || initialValue;
    7. const setState = newState => {
    8. _state[currentIndex] = newState;
    9. render();
    10. };
    11. return [_state[currentIndex], setState];
    12. }

    但是这个方案还是存在一些问题。我们每次渲染的值必须保证顺序完全一致。所以子啊Reactl里面不允许出现以下代码。

    1. let m, setM;
    2. if (n % 2 === 1) {
    3. [m, setM] = React.useState(0);
    4. }

    上面这样看似在筛选数据,但是这样就打乱了它本存在的数据。所以它不能用在if条件判断。

    但是我们的代码还是存在命名重复的问题。

    解决办法:

    在每个组件对应的虚拟节点的对象上创建一个_state 和 index。

    总结以上的规律

    • 每个函数组件对应一个React节点*
    • 每个节点保存着state和index
    • useState会读取state[index]
    • index由useState出现的顺序决定
    • setState会修改state,并触发更新

    我们不妨再试一下这个案例

    当我们快速点击log 再点击+1 3秒后发现打出来的 log 是0

    为什么呢 ,因为在这个useState会有多个n

    setN不会改变 n ,它会在这个时间点多出现一个新的 n

    39 React中useState原理 - 图2

    如果想先按 log 后按 +1 同步打出 1

    方法一:useRef

    1. const nRef = React.useRef(0);
    2. const update = React.useState()[1];
    3. const log = () => setTimeout(() => console.log(`n: ${nRef.current}`), 1000);

    详细案例

    方法二: useContext

    详细案例

    总结:

    • 每次重新渲染,组件函数就会执行
    • 对应的所有state都会出现「分身」
    • 如果你不希望出现分身
    • 可以用useRef | useContext等