首页 > 解决方案 > 我可以在 React 中跨多个树共享一个组件实例吗?

问题描述

我一直在创建一个 API 来帮助管理 React 中的状态机。

它由三个组件组成:

  1. <StateMachine>:接收xstate机器作为道具,并设置上下文供更深的组件使用。
  2. <StateView>: 接收两个道具:state& children,并且仅当该状态当前处于活动状态时才呈现其子级。
  3. <StateControl>: 接收一些任意的 props - 每个都是用于转换机器的事件 - 并将它们转换为转换回调以传递给它children(这不是一个元素,而是由elementType确定的PropTypes)。

以下是正在发生的事情的视觉表示:

状态机 API 可视图

使用 React 的 context API,我可以根据机器的状态灵活地打开/关闭 React 树中的节点。这是一个示例代码片段,演示了这一点:

const MyMachine = () => 
{
  return (
    <StateMachine machine={sampleMachine}>
      <StateView state="initializing">
        <StateControl onSuccess="success">
          {MySampleInitializer}
        </StateControl>
      </StateView>
      <StateView state="initialized">
        <p>{"App initialized"}</p>
      </StateView>
    </StateMachine>
  );

这很好用!当机器处于“初始化”状态时,MySampleInitializer被渲染。初始化完成后,onSuccess调用将机器转换为“已初始化”。在这一点上,<p>得到渲染。

现在的问题:

在大多数情况下,每个“状态视图”都会呈现不同的组件(当适当的状态变为活动状态时,它会被创建和安装)。

但是,如果我们只想将机器应用于单个组件怎么办?例如,我有一个<Form>组件处理某些表单元素的渲染,并且应该根据表单当前所处的状态接收不同的道具。

const MyFormMachine = () => 
{
  return (
    <StateMachine machine={formMachine}>
      <StateView state="unfilled">
        <StateControl onFill="filled">
          {(props) => <MyForm {...props} disableSubmit/>}
        </StateControl>
      </StateView>
      <StateView state="filled">
        <StateControl onClear="unfilled" onSubmit="submit">
          {(props) => <MyForm {...props}/>}
        </StateControl>
      </StateView>
      <StateView state="submitting">
        <MyForm disableInput disableSubmit showSpinner/>
      </StateView>
    </StateMachine>
  );

使用我当前的 API,<MyForm>在每个中渲染 a<StateView>将导致<MyForm>在状态更改发生时重新安装(从而破坏与之关联的任何内部状态)。DOM 节点本身也将被重新挂载,这可能会重新触发autofocus(例如)之类的事情。

我希望可能有一种方法可以<MyForm>在各种“视图”中共享相同的实例,这样就不会发生这种重新安装。这可能吗?如果没有,是否有适合此 API 的替代解决方案?

非常感谢任何帮助。

PS:如果问题标题不合适,请提出更改建议,以便更容易理解这个问题。谢谢

标签: javascriptreactjsxstate

解决方案


问题在于,StateView无论您是否处于正确的状态,您的实例中的组件总是被构建。

因此,在您的表单示例中,您始终有 3 个Form实例,但一次只呈现 1 个。正如您所说,您只能拥有 1 个Form实例以保持状态并防止重新安装。

当您将一个条件组件 ( MyForm) 传递给另一个组件 ( StateView) 时,您应该始终将它包装在一个函数中。

只有当您处于正确的状态时,您的StateView班级才能创建实例。MyForm

您现在一次只有 1 个实例(假设一次只有 1 个状态匹配),但每个StateView实例仍然有自己的未共享实例。

据我所知,除非在同一个父组件内,否则您无法避免单独的实例。

我会更改您的StateView组件以处理多个状态检查而不是一个。这样,当状态更改时,您的实例将被重用(再次假设一次只匹配 1 个状态)。

您的StateView构造可能如下所示:

<StateMachine machine={formMachine}>
  <StateView>
  {{
    "unfilled": () => (
      <StateControl onFill="filled">
        {(props) => <MyForm {...props} disableSubmit/>}
      </StateControl>
    ),
    "filled": () => (
      <StateControl onClear="unfilled" onSubmit="submit">
        {(props) => <MyForm {...props}/>}
      </StateControl>
    ),
    "submitting": () => (
      <StateControl>
        {(props) => <MyForm disableInput disableSubmit showSpinner/>}
      </StateControl>
    )
  }}
  </StateView>
</StateMachine>

请注意,为了重用一个组件,该组件必须是相同的类型。我将第 3 个包裹MyForm在一个空中StateControl,以便每个状态构造 a StateControl,因此可以重用组件。

另请注意,如果您在单个 中一次匹配多个状态StateView,则可以为每个状态赋予StateControl一个key属性。key不应在可能同时实例化的两个组件上使用相同的属性值。一个更简单的解决方案是只拥有单独的StateView实例,其中每个实例一次只匹配一个状态。


推荐阅读