Нам, вероятно, не нужен компонент React Form

React.js

В предыдущем посте Маленькие десерты«Нам может не понадобиться библиотека имен классов»

Теперь давайте настроим сложность и удалим относительно сложный компонент в React: компонент формы.

Прежде чем удалить компонент формы, нам нужно сделать некоторыедумать, Почему существует связь между компонентами формы и компонентами формы и управлением состоянием React

Обратите внимание, что следует очень неудобно для разработчиков реагирования и чрезвычайно противоречивы

Однонаправленный поток данных и контролируемые компоненты

Angular, Vue, все имеют двустороннюю привязку, а официальная документация React также дает официальное решение для двусторонней привязки входного тега — контролируемые компоненты:

реагировать JS.org/docs/forms. …

Код, упомянутый в этой статье, можно напрямую вставить в проект для проверки.

// 以下是官方的受控组件例子:
class NameForm extends React.Component {
  constructor(props) {
    super(props);
    this.state = {value: ''};

    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  }

  handleChange(event) {
    this.setState({value: event.target.value});
  }

  handleSubmit(event) {
    alert('A name was submitted: ' + this.state.value);
    event.preventDefault();
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          Name:
          <input type="text" value={this.state.value} onChange={this.handleChange} />
        </label>
        <input type="submit" value="Submit" />
      </form>
    );
  }
}

Я считаю, что те, кто писал проекты React, очень опытны, и контролируемый компонент: связать значение ввода с onChange с определенным состоянием.

Долгое время с управляемыми компонентами нас смущало следующее:

  1. Для страниц с большим количеством содержания формы обременительно писать контролируемые компоненты.
  2. Контролируемые компоненты между компонентами должны использовать реквизиты, такие как onChange, чтобы передавать цветы и передавать их слой за слоем.В этом случае станет проблематичным выполнять связывание форм.

Решение сообщества для вышеизложенного состоит в том, чтобы предоставить некоторые компоненты формы, наиболее часто используемые из них:

В том числе я сам написал Form компоненты

Они решают следующие проблемы:

  1. Получить содержимое формы по компонентам
  2. Связь с формой
  3. Выполнять или изменять определенное поведение компонентов формы на основе условий, таких как:
    • проверка формы
    • контроль собственности реквизита
    • REF получает функцию и выполняет ее

По сути, эти формы основаны на инкапсуляции официальных контролируемых компонентов React, из которых Antd Form и no-form относятся к нашим.先知Философия Дэна Абрамова:

  1. Не блокирует поток данных
  2. Всегда готов к рендерингу
  3. нет одноэлементного компонента
  4. Изолировать локальное состояние

Конкретная статья Дэна Абрамова находится здесь:Напишите устойчивые компоненты

Односторонний раствор потока данных настоятельно рекомендуется в отрасли. Я использовал redux + Immutable, как управление проектами в моих предыдущих проектах, и проект стабильно работает до тех пор, пока не появился решение React-Cloots (это еще одна тема).

Односторонний поток данных характеризуется обменом вычислением времени для времени разработчиков. Давайте возьмем небольшой пример для иллюстрации:

Если в текущем дереве компонентов 100 компонентов, 50 из которых вводятся в состояние по коннекту, то при инициации действия диспетчеризации нужно обновить 1 компонент, а 50 компонентов будут обновлены, нам нужно отфильтровать ненужные в данных состояния mapPropsToState, а затем используйте immutable для вынесения суждений с меньшими накладными расходами в shouldComponentUpdate, чтобы перехватить еще 49 компонентов, которые не нужно обновлять.

Преимущество одностороннего потока данных заключается в том, что нам всегда нужно поддерживать только самое верхнее состояние, уменьшая путаницу в системе.

Недостаток также очевиден: нам нужны дополнительные накладные расходы, чтобы определить, следует ли обновлять

Идея большинства форм Form для получения данных также заключается в связном одностороннем потоке данных.Каждый раз, когда onChange, состояние в форме модифицируется, а подкомпоненты получают и обновляют соответствующие значения, регистрируя контекст.Это соответствует концепции дизайна Дэна Абрамова.

И react-final-form не использует вышеуказанный режим, а через публикацию и подписку добавляет обновление каждого компонента в подписку и делает соответствующие обновления в соответствии с поведением.Согласно приведенному выше примеру, они работают так:

Если текущее дерево компонентов имеет 100 компонентов, 50 компонентов были помечены формой, то при запуске входного поведения вам необходимо обновить сборку, вы обнаружите, что компонент имеет setState внутри, и соответствующее значение обновляется до формы данных .

Нарушает ли этот дизайн первоначальное намерение React?Я не думаю, что это так, потому что контент, поддерживаемый Form, является локальным, а не целым, нам просто нужно, чтобы вся форма не управляла потоком данных.

На примере компонента react-final-form хочу понять одну вещь:

  1. Односторонний поток данных помогает нам легче им управлять, но это не означает, что состояние неоднонаправленного потока данных должно быть хаотичным, как и состояние формы, управляемое компонентом react-final-form.

  2. Поскольку react-final-form можно спроектировать таким образом, почему мы не можем спроектировать частичные формы, выходящие за рамки контролируемых компонентов?

Хорошо, давайте к делу:

Компоненты внутри формы могут существовать независимо от контролируемого компонента, просто сделайте саму форму контролируемым компонентом

Вместо использования сборки тега формы React Form

Мы используем простой пример для реализации примера кода официальных контролируемых компонентов React:

class App extends React.Component {
  formDatas = {};

  handleOnChange = event => {
    // 在input事件中, 我们将dom元素的值存储起来, 用于表单提交
    this.formDatas[event.target.name] = event.target.value;
  };

  handleOnSubmit = event => {
    console.log('formDatas: ', this.formDatas);
    event.preventDefault();
  };

  render() {
    return (
      <form onChange={this.handleOnChange} onSubmit={this.handleOnSubmit}>
        <input name="username" />
        <input name="password" />
        <button type="submit" />
      </form>
    );
  }
}

Это самый простой способ получить значение и сохранить его в объекте.Мы шаг за шагом опишем, как управлять значением и состоянием без контролируемого компонента, но для простоты последующего кода мы используем хуки для выполнения вышеописанного поведение:

получить содержимое формы

function App() {
  // 使用 useRef 来存储数据, 这样可以防止函数每次被重新执行时无法存储变量
  const { current: formDatas } = React.useRef({});

  // 使用 useCallback 来声明函数, 减少组件重绘时重新声明函数的开销
  const handleOnChange = React.useCallback(event => {
    // 在input事件中, 我们将dom元素的值存储起来, 用于表单提交
    formDatas[event.target.name] = event.target.value;
  }, []);

  const handleOnSubmit = React.useCallback(event => {
    // 提交表单
    console.log('formDatas: ', formDatas);
    event.preventDefault();
  }, []);

  return (
    <form onChange={handleOnChange} onSubmit={handleOnSubmit}>
      <input name="username" />
      <input name="password" />
      <button type="submit" />
    </form>
  );
}

На этой основе будет написан следующий код с использованием синтаксиса хуков.

Получить содержимое формы по компонентам

Нам не нужно выполнять какую-либо обработку, тег формы может получить все содержимое формы внутри него.

// 子组件, form标签一样可以获取相应的输入
function PasswordInput(){
  return <div>
    <p>密码:</p>
    <input name="password" />
  </div>
}

function App() {
  const { current: formDatas } = React.useRef({});

  const handleOnChange = React.useCallback(event => {
    formDatas[event.target.name] = event.target.value;
  }, []);

  const handleOnSubmit = React.useCallback(event => {
    console.log('formDatas: ', formDatas);
    event.preventDefault();
  }, []);

  return (
    <form onChange={handleOnChange} onSubmit={handleOnSubmit}>
      <input name="username" />
      <PasswordInput />
      <button type="submit" />
    </form>
  );
}

Связывание формы\проверка

Теперь реализуем спрос на основе предыдущего:

Если длина пароля больше 8, сбросьте имя пользователя и пароль по умолчанию.

Мы сохраняем DOM-элементы ввода через форму, а затем в некоторых случаях выполняем DOM-операции и обновляем их напрямую.Код выглядит следующим образом:

function App() {
  const { current: formDatas } = React.useRef({});
  const { current: formTargets } = React.useRef({});

  const handleOnChange = React.useCallback(event => {
    // 在input事件中, 我们将dom元素的值存储起来, 用于表单提交
    formDatas[event.target.name] = event.target.value;
    // 在input事件中, 我们将dom元素储存起来, 接下来根据条件修改value
    formTargets[event.target.name] = event.target;

    // 如果密码长度大于8, 将用户名和密码重置为默认值
    if (formTargets.password && formDatas.password.length > 8) {
      // 修改DOM元素的value, 更新视图
      formTargets.password.value = formTargets.password.defaultValue;
      // 如果存储过
      if (formTargets.username) {
        // 修改DOM元素的value, 更新视图
        formTargets.username.value = formTargets.username.defaultValue;
      }
    }
  }, []);

  const handleOnSubmit = React.useCallback(event => {
    console.log('formDatas: ', formDatas);
    event.preventDefault();
  }, []);

  return (
    <form onChange={handleOnChange} onSubmit={handleOnSubmit}>
      <input defaultValue="hello" name="username" />
      <input defaultValue="" name="password" />
      <button type="submit" />
    </form>
  );
}

ReactDOM.render(<App />, document.getElementById('root'));

Как показано в приведенном выше коде, мы можем легко реализовать связь формы. Поскольку DOM управляется напрямую, весь компонент не выполняет повторное выполнение рендеринга. Производительность этой схемы обновления превосходна (предел HTML).

При написании React мы все очень忌讳Напрямую манипулировать DOM, потому что, если мы манипулируем DOM, но обновляем Diff узла через React, это может перезаписать некоторые из поведений предыдущего манипулирования DOM, Но если мы гарантируем, что эти DOM не являются контролируемыми компонентами, то вышеизложенное не случится.

Что не так? Есть ли какие-либо проблемы, когда другое поведение вызвало React RedRaw?

Очевидно, что нет, пока компонент React не уничтожен, даже если он перерисовывается, React просто заставит объект dom изменить свои свойства:

function App() {
  const { current: formDatas } = React.useRef({});
  const { current: formTargets } = React.useRef({});
  const [value, setValue] = React.useState(10);

  // 我们这里每隔 500ms 自动更新, 并且重绘我们的输入框的字号
  React.useEffect(() => {
    setInterval(() => {
      setValue(v => v + 1);
    }, 300);
  }, []);

  const handleOnChange = React.useCallback(event => {
    formDatas[event.target.name] = event.target.value;
    formTargets[event.target.name] = event.target;

    if (formTargets.password && formDatas.password.length > 8) {
      formTargets.password.value = formTargets.password.defaultValue;
      if (formTargets.username) {
        formTargets.username.value = formTargets.username.defaultValue;
      }
    }
  }, []);

  const handleOnSubmit = React.useCallback(event => {
    console.log('formDatas: ', formDatas);
    event.preventDefault();
  }, []);

  return (
    <form onChange={handleOnChange} onSubmit={handleOnSubmit}>
      <p>{value}</p>
      <input defaultValue="hello" name="username" />
      {/* p 标签会一直被 setState 更新, 字号逐步增大, 我们输入的值并没有丢失 */}
      <input defaultValue="" name="password" style={{ fontSize: value }} />
      <button type="submit" />
    </form>
  );
}

Однако, если метка будет уничтожена, значение неуправляемого компонента не будет сохранено.

В следующем примере, после ввода значения, оно потребляется, а затем перерисовывается.В это время предыдущее значение ввода было потеряно.

function App() {
  const { current: formDatas } = React.useRef({});
  const { current: formTargets } = React.useRef({});
  const [value, setValue] = React.useState(0);

  React.useEffect(() => {
    setInterval(() => {
      setValue(v => v + 1);
    }, 500);
  }, []);

  const handleOnChange = React.useCallback(event => {
    formDatas[event.target.name] = event.target.value;
    formTargets[event.target.name] = event.target;
  }, []);

  const handleOnSubmit = React.useCallback(event => {
    console.log('formDatas: ', formDatas);
    event.preventDefault();
  }, []);

  return (
    <form onChange={handleOnChange} onSubmit={handleOnSubmit}>
      {/* 如果 value 是 5 的整数倍, input 会被销毁, 已输入的值会丢失 */}
      {value % 5 !== 0 && <input name="username" />}
      {/* 我们可以使用 defaultValue 去读取历史的值, 让重绘时读取之前输入的值 */}
      {value % 5 !== 0 && <input defaultValue={formDatas.password} name="password" />}
      {/* 如果可能, 我们最好使用 display 代替条件渲染 */}
      <input name="code" style={{ display: value % 5 !== 0 ? 'block' : 'none' }} />
      <button type="submit" />
    </form>
  );
}

Как упоминалось в комментариях в коде:

  1. Если ввод уничтожен, введенное значение будет потеряно
  2. Мы можем использовать defaultValue для чтения исторического значения, чтобы ранее введенное значение считывалось при перерисовке.
  3. Если возможно, нам лучше использовать отображение вместо условного рендеринга.

Что ж, после того как мы разобрались в преимуществах и недостатках прямого манипулирования DOM, давайте перейдем к реализации других типов поведения, характерных для форм.

Взаимодействие между иерархическими компонентами

Для выполнения функции подкомпонента по условию нам нужно получить только реф компонента, но если речь идет о многоуровневых компонентах, это будет очень хлопотно.

Традиционный компонент формы предоставит FormItem, а FormItem получит контекст для обеспечения связи между многоуровневыми компонентами.

И как раз мы получили элемент DOM, нам нужно только привязать событие к элементу DOM, мы можем добиться безболезненной связи по всей иерархии.Такое поведение полностью нарушает наше обычное мышление и нормальную работу по написанию React, но через предыдущую нашу понимание «разрушения этикетки» обычно может держать его под контролем.

Смотрим на реализацию Code Case:

// 此为子子组件
function SubInput() {
  const ref = React.useRef();

  React.useEffect(() => {
    if (ref.current) {
      // 在DOM元素上捆绑一个函数, 此函数可以执行此组件的上下文事件
      ref.current.saved = name => {
        console.log('do saved by: ', name);
      };
    }
  }, [ref]);

  return (
    <div>
      {/* 获取表单的DOM元素 */}
      <input ref={ref} name="sub-input" />
    </div>
  );
}

// 此为子组件, 仅引用了子子组件
function Input() {
  return (
    <div>
      <SubInput />
    </div>
  );
}

function App() {
  const { current: formDatas } = React.useRef({});
  const { current: formTargets } = React.useRef({});

  const handleOnChange = React.useCallback(event => {
    formDatas[event.target.name] = event.target.value;
    formTargets[event.target.name] = event.target;

    // 直接通过dom元素上的属性, 获取子子组件的事件
    event.target.saved && event.target.saved(event.target.name);
  }, []);

  const handleOnSubmit = React.useCallback(event => {
    console.log('formDatas: ', formDatas);
    event.preventDefault();
  }, []);

  return (
    <form onChange={handleOnChange} onSubmit={handleOnSubmit}>
      {/* 我们应用了某个子子组件, 并且没用传递任何 props, 也没有捆绑任何 context, 没有获取ref */}
      <Input />
    </form>
  );
}

В соответствии с этим примером мы видим, что большинство наших сценариев компонентов формы могут быть выполнены с использованием тега формы html, а эффективность разработки и эффективность выполнения выше.

спор

Манипулируя DOM, мы естественным образом можем решить некоторые проблемы, которые React решить очень сложно.Конечно, это немного похоже на танец на кончике ножа, но в этой статье приведены некоторые проблемы и решения.

Я очень приветствую обсуждение таких проблем, и с какими проблемами придется столкнуться.Если можно будет четко описать обоснование и причины и вернуться к этой статье, это будет полезно всем читателям.

напиши в конце

Пожалуйста, не связывайтесь догмой, попробуйте бросить ей вызов.