React Strictmode 阻止自定义的 useState 挂钩在第一次调用时更改状态

React Strictmode preventing a custom made useState hook to change the state at the first call

我正在关注 Ryan florence(混音和 ReactTraining.com 的创作者)的 presentation。在那里,他通过自己制作一个钩子来消除 useState 钩子的雾气。
我遵循了这个过程,并在一个组件中实现了它。

import React from 'react'
import ReactDOM from 'react-dom';



function MadeUseStateMyself() {
  const [minutes,setMinutes] = useState(5)
  const [error,setError] = useState(null)
  
  const handleAdd = () => {
      if(minutes<9){
          setMinutes(minutes+1)
          setError(null)
      }
      else{
          setError('Less than 10 please')
      }
  }
  const handleSubStract = () => {
    if(minutes>0){
        setMinutes(minutes-1)
        setError(null)
    }
    else{
        setError('More than 0 please')
    }
}

  return (
    <React.Fragment>
        <button onClick={handleSubStract} > - </button>
        <p>{minutes}</p>
        <button onClick={handleAdd} > + </button>
        {error && <div>{error}</div>}
    </React.Fragment>
  )
}
const states = []
let calls = -1
const useState = def => {
    const callId = ++calls
    if(states[callId]) {
        return states[callId]
    }
    const setDef = newV => {
       states[callId][0] = newV
       reRender()
    }
    const state = [def,setDef]
    states.push(state)
    return state
}
function reRender(){
    calls = -1
    ReactDOM.render(<MadeUseStateMyself/>,document.getElementById('root'))
}
export default MadeUseStateMyself

在这里我注意到一个非常奇怪的行为,即每当我使用 React.StrictMode 包装我的整个组件时,状态在第一次函数调用后不会改变(在在这种情况下,我实现了一个递增按钮和一个递减按钮,它们改变了一个奇异整数状态的状态。)
如果我去掉 React.StrictMode 包装器,这个问题就不会出现。 这是一个CodeSandbox

阅读 StrictMode docs ,我的一个假设是,在严格模式下,函数组件主体被调用两次,它在第二次函数调用期间失去了它的第一个状态,因此失去了第一次状态变化。
我什至接近?

我看了大约 13 分钟的视频才发现您的代码与演示中的代码之间的第一个区别。演示者说要立即手动调用自定义 reRender 函数来进行初始渲染。从这里我注意到你渲染了你的应用程序两次。 MadeUseStateMyself 在自定义 reRender 函数中渲染一次,然后导出并再次渲染到 index.js 文件中的 DOM。

您实际上已经将 MadeUseStateMyself 组件的两个实例呈现为相互踩踏的 React。

如果你拿走你的代码并立即调用 reRender,并且还完全删除 index.js 渲染代码,同时 包装 MadeUseStateMyselfReact.StrictMode 组件中,您会看到它的渲染和更新没有问题。

MadeUseStateMyself

import React from "react";
import ReactDOM from "react-dom";

const states = [];
let calls = -1;

const useState = (def) => {
  const callId = ++calls;
  if (states[callId]) {
    return states[callId];
  }
  const setDef = (newV) => {
    states[callId][0] = newV;
    reRender();
  };
  const state = [def, setDef];
  states.push(state);
  return state;
};

function MadeUseStateMyself() {
  const [minutes, setMinutes] = useState(5);
  const [error, setError] = useState(null);

  const handleAdd = () => {
    if (minutes < 9) {
      setMinutes(minutes + 1);
      setError(null);
    } else {
      setError("Less than 10 please");
    }
  };

  const handleSubStract = () => {
    if (minutes > 0) {
      setMinutes(minutes - 1);
      setError(null);
    } else {
      setError("More than 0 please");
    }
  };

  return (
    <React.Fragment>
      <button onClick={handleSubStract}> - </button>
      <p>{minutes}</p>
      <button onClick={handleAdd}> + </button>
      {error && <div>{error}</div>}
    </React.Fragment>
  );
}

function reRender() {
  calls = -1;
  ReactDOM.render(
    <React.StrictMode> // <-- add the React.StrictMode
      <MadeUseStateMyself />
    </React.StrictMode>,
    document.getElementById("root")
  );
}

reRender(); // <-- Invoke immediately