我可以在 React 中跨多个树共享一个组件实例吗?
Can I share a component instance across multiple trees in React?
我一直在创建一个 API 来帮助管理 React 中的状态机。
它由三部分组成:
<StateMachine>
:接收一个xstate
机器作为道具,并设置一个上下文供更深层次的组件使用。
<StateView>
:接收两个道具:state
& children
,并仅在该状态当前处于活动状态时呈现其 children。
<StateControl>
:接收一些任意道具 - 每个道具都是用于转换机器的事件 - 并将它们转换为转换回调以传递给它的 children
(这不是一个元素,但是 elementType
由 PropTypes
) 决定。
这是正在发生的事情的直观表示:
利用React的上下文API,我可以根据机器的状态灵活切换React树中的on/off个节点。下面是演示此内容的示例代码片段:
const MyMachine = () =>
{
return (
<StateMachine machine={sampleMachine}>
<StateView state="initializing">
<StateControl onSuccess="success">
{MySampleInitializer}
</StateControl>
</StateView>
<StateView state="initialized">
<p>{"App initialized"}</p>
</StateView>
</StateMachine>
);
这太棒了!当机器处于 "initializing" 状态时,将呈现 MySampleInitializer
。初始化完成后,调用 onSuccess
将机器转换为 "initialized"。此时,<p>
得到渲染。
现在问题:
在大多数情况下,每个 "state view" 都会呈现一个 不同的组件 (当适当的状态变为活动状态时创建并安装)。
但是如果我们只想将机器应用于单个组件怎么办?例如,我有一个 <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,在每个 <StateView>
中渲染一个 <MyForm>
将导致 <MyForm>
成为 re-mounted 任何时候发生状态变化(从而破坏与之关联的任何内部状态)。 DOM 节点本身也将是 re-mounted,这可能 re-trigger 类似 autofocus
的东西(例如)。
我希望有一种方法可以在各种 "views" 之间共享相同的 <MyForm>
实例,这样 re-mounting 就不会发生。这可能吗?如果没有,是否有适合此 API 的替代解决方案?
非常感谢任何帮助。
PS: 如果问题标题不合适,请提出更改建议,以便该问题更易于访问。谢谢
问题是无论您是否处于正确的状态,您的 StateView
实例中的组件总是被构建。
因此在您的表单示例中,您总是有 3 个 Form
个实例,但一次只呈现 1 个。如您所述,您只能拥有 1 个 Form
实例以保持状态并防止 re-mounting.
当您将条件组件 (MyForm
) 传递到另一个组件 (StateView
) 时,您应该始终将其包装在一个函数中。
如果您的状态正确,您的 StateView
class 只能创建 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
中,这样每个状态都构造一个 StateControl
,因此组件可以重复使用。
另请注意,如果您确实在单个 StateView
中同时匹配多个状态,则可以为每个 StateControl
分配一个 key
属性。不应在可能同时实例化的两个组件上使用相同的 key
属性 值。一个更简单的解决方案是只拥有单独的 StateView
个实例,其中每个实例一次只匹配一个状态。
我一直在创建一个 API 来帮助管理 React 中的状态机。
它由三部分组成:
<StateMachine>
:接收一个xstate
机器作为道具,并设置一个上下文供更深层次的组件使用。<StateView>
:接收两个道具:state
&children
,并仅在该状态当前处于活动状态时呈现其 children。<StateControl>
:接收一些任意道具 - 每个道具都是用于转换机器的事件 - 并将它们转换为转换回调以传递给它的children
(这不是一个元素,但是elementType
由PropTypes
) 决定。
这是正在发生的事情的直观表示:
利用React的上下文API,我可以根据机器的状态灵活切换React树中的on/off个节点。下面是演示此内容的示例代码片段:
const MyMachine = () =>
{
return (
<StateMachine machine={sampleMachine}>
<StateView state="initializing">
<StateControl onSuccess="success">
{MySampleInitializer}
</StateControl>
</StateView>
<StateView state="initialized">
<p>{"App initialized"}</p>
</StateView>
</StateMachine>
);
这太棒了!当机器处于 "initializing" 状态时,将呈现 MySampleInitializer
。初始化完成后,调用 onSuccess
将机器转换为 "initialized"。此时,<p>
得到渲染。
现在问题:
在大多数情况下,每个 "state view" 都会呈现一个 不同的组件 (当适当的状态变为活动状态时创建并安装)。
但是如果我们只想将机器应用于单个组件怎么办?例如,我有一个 <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,在每个 <StateView>
中渲染一个 <MyForm>
将导致 <MyForm>
成为 re-mounted 任何时候发生状态变化(从而破坏与之关联的任何内部状态)。 DOM 节点本身也将是 re-mounted,这可能 re-trigger 类似 autofocus
的东西(例如)。
我希望有一种方法可以在各种 "views" 之间共享相同的 <MyForm>
实例,这样 re-mounting 就不会发生。这可能吗?如果没有,是否有适合此 API 的替代解决方案?
非常感谢任何帮助。
PS: 如果问题标题不合适,请提出更改建议,以便该问题更易于访问。谢谢
问题是无论您是否处于正确的状态,您的 StateView
实例中的组件总是被构建。
因此在您的表单示例中,您总是有 3 个 Form
个实例,但一次只呈现 1 个。如您所述,您只能拥有 1 个 Form
实例以保持状态并防止 re-mounting.
当您将条件组件 (MyForm
) 传递到另一个组件 (StateView
) 时,您应该始终将其包装在一个函数中。
如果您的状态正确,您的 StateView
class 只能创建 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
中,这样每个状态都构造一个 StateControl
,因此组件可以重复使用。
另请注意,如果您确实在单个 StateView
中同时匹配多个状态,则可以为每个 StateControl
分配一个 key
属性。不应在可能同时实例化的两个组件上使用相同的 key
属性 值。一个更简单的解决方案是只拥有单独的 StateView
个实例,其中每个实例一次只匹配一个状态。