Могу ли я использовать экземпляр компонента для нескольких деревьев в React?

Я создавал API, чтобы помочь управлять машинами состояний в React.

Он состоит из трех компонентов:

  1. <StateMachine>: Получает xstate машина как опора и устанавливает контекст для использования более глубоких компонентов.
  2. <StateView>: Получает два реквизита: state & children и отображает его потомки, только если это состояние в данный момент активно.
  3. <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 случаи, когда каждый из них будет соответствовать только одному состоянию за один раз.

Другие вопросы по тегам