альтернатива какой-то доброте this.setState ()

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

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

иногда вы можете использовать хуки для управления состоянием формы. с помощью useState или useReducer.

Теперь давайте рассмотрим сценарий, в котором вам нужно управлять сложным состоянием формы с несколькими входами формы и несколькими различными типами ввода. состояние формы может даже иметь вложенную информацию, например информацию об адресе пользователя, которая имеет собственные подполя, такие как address.addressLine1, address.addressLine2 и т. д.

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

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

const [modalActive, updateModal] = useState(false)
.
.
.
updateModal(prev => !prev)

но если у вас слишком много отдельных полей формы, например 100+ (ДА !!. Я управлял более чем 100 полями формы), то этот подход неудобен.

представлять себе !!..

const [firstName, setFirstName] = useState('')
const [middleName, setMiddleName] = useState('')
const [lastName, setLastName] = useState('')
.
.
.

Итак, другой вариант - useReducer

const initialState = {
  firstName: '',
  lastName: ''
};

function reducer(state, action) {
  switch (action.type) {
    case 'firstName':
      return { firstName: action.payload };
    case 'lastName':
      return { lastName: action.payload };
    default:
      throw new Error();
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, initialState);
return (
    <>
      <input
        type="text"
        name="firstName"
        placeholder="First Name"
        onChange={(event) => {
          dispatch({
           type: 'firstName',
           payload: event.target.value
          })
        }}
        value={state.firstName} />
        <input
        type="text"
        name="lastName"
        placeholder="Last Name"
        onChange={(event) => {
          dispatch({
           type: 'lastName',
           payload: event.target.value
          })
        }}
        value={state.lastName} />
     </>
  );
}

эх !!, не хорошо.

вы не можете написать каждый вариант использования для этих n полей формы в редукторе. хотя функция reducer в useReducer - это обычная функция, возвращающая обновленный объект состояния. Итак, мы можем сделать это лучше.

function reducer(state, action) {
  // field name and value are retrieved from event.target
  const { name, value } = action
  
  // merge the old and new state
  return { ...state, [name]: value }
}

теперь этот редуктор выглядит лучше и чище.

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

this.setState((prev) => ({ isActive: !prev }))
// or
const [modalActive, updateModal] = useState(false)
.
.
.
updateModal(prev => !prev)

также как насчет обновления вложенного состояния, такого как address.addressLine1, address.pinCode.

Ok !!. мы много обсуждали управление состоянием сложной формы.

позвольте мне просто показать вам решение.

Итак, вот полный исходный код для обработки таких сложных сценариев формы.

Я немного объясню функцию редуктора (EnhancedReducer: P).

функция reducer получает два аргумента, первый аргумент - это текущее состояние до обновления. этот аргумент предоставляется автоматически, когда вы вызываете функцию updateState / dispatch для обновления состояния редуктора. второй аргумент функции reducer - это значение, с которым вы вызываете функцию updateState, это не обязательно должен быть типичный объект действия redux формы {type: ‘something’, payload: ‘something’}. это может быть что угодно, число, строка, объект или даже функция.

и это то, что мы используем, если updateArg является функцией обратного вызова, мы вызываем ее с текущим состоянием, чтобы вычислить новое. любой объект, который мы возвращаем из этой функции, становится нашим новым состоянием.

и если updateArg - простой старый объект javascript, то есть два случая.

1- Объект не имеет свойств _path и _value - и, следовательно, является обычным объектом обновления, точно так же, как мы передаем this.setState. вы просто вызываете updateState с новым объектом с частью состояния, которое вы хотите обновить, и он объединит его со старым и вернет новое состояние.

2- Объект имеет свойства _path и _value - когда функция обновления вызывается с объектом с этими двумя свойствами. мы рассматриваем это как особый случай, когда _path представляет путь к вложенному полю в строковой форме, например: «address.pinCode» или массив, представляющий путь [«адрес», «pinCode»].

но что мы делаем с такими представлениями пути для обновления вложенного поля в объекте. мы используем метод set lodash. он принимает обе формы пути как допустимые входные данные для обновления и объекта.

set(objectToUpdate, path, newValue)

но метод set изменяет объект на месте и не возвращает новую копию, но в режиме реагирования обнаружение изменения мира зависит от неизменяемости, новой новой копии данных.

поэтому, чтобы обойти это, мы используем immer, который помогает обрабатывать неизменяемость с объектами javascript в простой в использовании форме.

produce(state, draft => {
set(draft, _path, _value);
});

Функция произвести из immer берет объект для работы в качестве первого аргумента, который в нашем случае является текущим состоянием, а второй аргумент - это функция, которая получает черновик объект для изменения, все, что вы изменяете внутри этой функции в состоянии черновика, выполняется на копии. а затем автоматически возвращает новый объект с обновленными данными.

Итак, есть наш улучшенный редуктор: D

просто

yarn add lodash immer

и наслаждаться.

PS - пример в сущности может быть усовершенствован намного дальше, с большим количеством крайних случаев, обрабатываемых в расширенномReducer, а код полей формы может быть сокращен путем сопоставления объекта спецификации формы для его динамического создания и уменьшения дублирования кода, а также некоторых других вещей.

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

возможно, у некоторых из вас может возникнуть вопрос: если мы так пытаемся воспроизвести this.setState, то почему бы также не использовать функцию обратного вызова setState. ну это недостаточно декларативно !!!. Я бы использовал для этого useEffect.