我可以在 React 中跨多个树共享一个组件实例吗?

Posted

技术标签:

【中文标题】我可以在 React 中跨多个树共享一个组件实例吗?【英文标题】:Can I share a component instance across multiple trees in React? 【发布时间】:2019-11-26 04:42:08 【问题描述】:

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

它由三个部分组成:

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

这里是正在发生的事情的视觉表示:

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

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 将机器转换为“已初始化”。此时,&lt;p&gt; 被渲染。

现在的问题:

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

但是,如果我们只想将机器应用于单个组件怎么办?例如,我有一个 &lt;Form&gt; 组件,它处理一些表单元素的呈现,并且应该根据表单当前所处的状态接收不同的道具

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,在每个 &lt;StateView&gt; 中渲染一个 &lt;MyForm&gt; 将导致 &lt;MyForm&gt; 在状态更改发生时重新挂载(从而破坏与之关联的任何内部状态)。 DOM 节点本身也将被重新挂载,这可能会重新触发 autofocus 之类的东西(例如)。

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

非常感谢任何帮助。

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

【问题讨论】:

IMO 你的情况看起来很做作。与重新安装相比,我没有看到任何合理的共享论据。顺便说一句,要坚持的“内部状态”到底是什么意思?形成焦点?为什么不同的州应该保持相同? @hindmost 我已经用代码 sn-p 更新了 OP,这应该澄清你的要求 到目前为止,我能想到的最佳解决方案是将 &lt;MyForm&gt; 道具存储为“状态”,并让每个视图操作这些值,然后将其传递给单个 @ 987654345@组件 你可以尝试给每个MyForm 相同的id 吗?不过可能行不通 【参考方案1】:

问题在于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>

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

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

【讨论】:

以上是关于我可以在 React 中跨多个树共享一个组件实例吗?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 C++ 中跨多个源文件共享变量?

XState react - 跨多个组件共享机器实例

在vertx中跨顶点共享单例客户端的正确方法

我可以在我的 IIS 托管网站中跨 AppDomain 共享 dll 吗?

在 JOGL 中跨多个 QWidget 共享 VBO

我可以在屏幕上的多个位置重用组件的单个实例吗?