我可以在React中的多棵树上共享组件实例吗?

时间:2019-07-17 10:58:16

标签: javascript reactjs xstate

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

它由三个部分组成:

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

以下是正在播放的内容的视觉表示:

State Machine API Visual Diagram

使用React的上下文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>中渲染一个<StateView>会导致<MyForm>在状态更改发生时重新挂载(从而破坏与其相关的任何内部状态)。 DOM节点本身也将重新安装,这可能会重新触发autofocus之类的事情。

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

任何帮助都将不胜感激。

PS::如果问题标题不合适,请提出更改建议,以便可以更轻松地访问此问题。谢谢

1 个答案:

答案 0 :(得分:1)

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

因此在您的表单示例中,您始终有3个Form实例,但一次仅渲染1个。如前所述,您只能有1个Form实例才能维护状态并防止重新安装。

将条件组件(MyForm)传递到另一个组件(StateView)时,应始终将其包装在函数中。

如果您处于正确状态,那么您的StateView类只能创建MyForm实例。

您现在一次仅具有一个实例(假设一次仅匹配1个状态),但是每个StateView仍具有自己的实例,该实例不共享。

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

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

您的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实例,其中每个实例一次仅匹配一个状态。