如何在使用 useState 钩子的功能组件的不同实例之间隔离状态?

How to isolate state among different instances of functional component that uses useState hook?

示例是使用 useState 保持点击计数器的简单功能组件。

单步执行 Stepper MUI 组件,我想创建具有不同初始值的 Example 组件的实例,例如在第 0 步,初始值 100,在第 1 步,初始值 111,在第 2 步,初始值 112。

在单步执行步骤的每个案例时,尽管传递了不同的初始值,但示例功能组件仅将状态保持为第一个初始值,即 100。

文件为/Components/Navigation/Stepper01a.js,StepContent中引用了Example组件,Horizo​​ntalLinearStepper组件中引用了该组件。整体代码是来自 Material UI Stepper 组件的示例。我只是想测试它以在每一步创建具有不同初始值的其他功能组件的不同实例(在本例中为示例)。

示例组件:

function Example({ init }) {
  // Declare a new state variable, which we'll call "count"
  const [count, setCount] = React.useState(init)

  return (
    <div>
      <p>init {init} </p>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>Click me</button>
    </div>
  )
}

StepContent 组件:

function StepContent({ step }) {
  console.log("step", step)

  switch (step) {
    case 0:
      return <Example init={100} />
    case 1:
      return <Example init={111} />
    case 2:
      return <Example init={112} />
    default:
      return "Unknown step"
  }
}

Horizo​​ntalLinearStepper 组件:

export default function HorizontalLinearStepper() {
...
 <div>
     <StepContent step={activeStep} />
 </div>
...
}

参见 运行 示例 https://mj3x4pj49j.codesandbox.io/ 代码 https://codesandbox.io/s/mj3x4pj49j

在第 0 步单击“下一步”后,计数应设置为初始值 111,但仍为 100,在第 1 步单击“下一步”后,计数应设置为初始值 112,但仍为 100。它似乎一旦在第 0 步将状态计数初始化为 100,第 1 步和第 2 步就会使用相同的状态,即状态不是孤立的。

是否由于某种原因违反了 Hooks 规则而出现此问题?

你需要给React一个提示,在不同的步骤中你需要不同的Example组件实例。这个提示可以这样完成:

function StepContent({ step }) {
  console.log("step", step)

  switch (step) {
    case 0:
      return <Example init={100} key={0} />
    case 1:
      return <Example init={111} key={1} />
    case 2:
      return <Example init={112} key={2}/>
    default:
      return "Unknown step"
  }
}

原因是 React 在所有情况下都在虚拟 dom 中看到一个示例,并且根据它的算法,它认为它是相同的组件但具有不同的 prop 所以他只是改变它的状态而不是re-init它。

在组件内部你做的是正确的。初始值取自 props - 这是常见的做法。

但是请看,useState() 仅在初始渲染时应用初始值(componentDidMountconstructor 用于 class-based 组件)。您使用该组件的方式不是 re-create <Example 而是更新它。

所以换句话说,同样的情况你会得到 class-based 没有 componentDidUpdate 的组件:React 更新现有的 <Example> 而不是 re-creating 并且不适用componentDidMount 了(对于你的情况,它不会用初始值初始化 useState)。

我看到了不同的处理方式。

  1. 强制对 re-create 元素做出反应,而不是使用 key 进行更新。 Hacky 但工作方式:
switch (step) {
  case 0:
    return <Example key="step-1" init={100} />
  case 1:
    return <Example key="step-2" init={111} />
  case 2:
    return <Example key="step-3" init={112} />
  default:
    return "Unknown step"
}
  1. 您可以使用 useEffect 作为 componentDidUpdate 的 hook-version:
useEffect(() => {
  setCount(init);
}, [init]);

这是一个棘手的时刻,因为您正在计算点击次数。所以只做 setCount(init) 可能不是一个好的举动,并且会破坏你的计算。 所以实际的代码可能要复杂得多。我在这里不确定,因为不了解计数背后的逻辑。

  1. 提升状态(将 count 推送到父组件)在 init 更新后您将不需要更新任何内容。