Могу ли я использовать экземпляр компонента для нескольких деревьев в React?
Я создавал API, чтобы помочь управлять машинами состояний в React.
Он состоит из трех компонентов:
<StateMachine>
: Получаетxstate
машина как опора и устанавливает контекст для использования более глубоких компонентов.<StateView>
: Получает два реквизита:state
&children
и отображает его потомки, только если это состояние в данный момент активно.<StateControl>
Получает несколько произвольных реквизитов, каждый из которых является событием, используемым для перехода машины, и преобразует их в обратные вызовы перехода для передачи в свойchildren
(который НЕ является элементом, ноelementType
как определеноPropTypes
).
Вот визуальное представление того, что в игре:
Используя контекстный API React, я могу гибко включать / выключать узлы в дереве 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 ответ
Проблема в том, что компоненты внутри вашего StateView
экземпляры всегда создаются независимо от того, в правильном ли вы состоянии или нет.
Таким образом, в вашем примере формы, у вас всегда есть 3 Form
экземпляры, но только 1 отображается одновременно. Как вы уже сказали, вы можете иметь только 1 Form
экземпляр для того, чтобы сохранить состояние и предотвратить повторный монтаж.
Когда вы передаете условный компонент (MyForm
) в другой компонент (StateView
), вы всегда должны обернуть его внутри функции.
Твой StateView
класс может только создать MyForm
Например, если вы находитесь в правильном состоянии.
Теперь у вас есть только 1 экземпляр за раз (при условии, что за один раз сопоставлено только 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>
Обратите внимание, что для повторного использования компонента он должен быть одного типа. Я завернул 3-й MyForm
в пустом StateControl
так что каждое государство строит StateControl
и, следовательно, компоненты могут быть использованы повторно.
Также обратите внимание, что если у вас есть несколько совпавших состояний одновременно в одном StateView
Вы можете дать каждому StateControl
key
свойство. Такой же key
Значение свойства не должно использоваться для двух компонентов, которые могут быть созданы одновременно. Более простым решением было бы просто иметь отдельный StateView
случаи, когда каждый из них будет соответствовать только одному состоянию за один раз.