Обновление и объединение объекта состояния с помощью React useState () hook - PullRequest
12 голосов
/ 25 марта 2019

Я нахожу эти две части документов React Hooks немного запутанными. Какой метод является лучшим для обновления объекта состояния с помощью хука состояния?

Представьте себе, что хотите сделать следующее обновление состояния:

INITIAL_STATE = {
  propA: true,
  propB: true
}

stateAfter = {
  propA: true,
  propB: false   // Changing this property
}

ВАРИАНТ 1

Из статьи Используя статью React Hook , мы получаем, что это возможно:

const [count, setCount] = useState(0);
setCount(count + 1);

Чтобы я мог сделать:

const [myState, setMyState] = useState(INITIAL_STATE);

А потом:

setMyState({
  ...myState,
  propB: false
});

ВАРИАНТ 2

А из ссылки на крючки получаем:

В отличие от метода setState, найденного в компонентах класса, useState делает не автоматически объединять объекты обновления. Вы можете повторить это поведение путем объединения формы средства обновления функций с распространением объекта Синтаксис:

setState(prevState => {
  // Object.assign would also work
  return {...prevState, ...updatedValues};
});

Насколько я знаю, оба работают. Так в чем же разница? Какой из них является лучшей практикой? Должен ли я использовать функцию pass (OPTION 2) для доступа к предыдущему состоянию или просто получить доступ к текущему состоянию с расширенным синтаксисом (OPTION 1)?

Ответы [ 6 ]

12 голосов
/ 25 марта 2019

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

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

const { useState } = React;

function App() {
  const [count, setCount] = useState(0);

  function brokenIncrement() {
    setCount(count + 1);
    setCount(count + 1);
  }

  function increment() {
    setCount(count => count + 1);
    setCount(count => count + 1);
  }

  return (
    <div>
      <div>{count}</div>
      <button onClick={brokenIncrement}>Broken increment</button>
      <button onClick={increment}>Increment</button>
    </div>
  );
}

ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>

<div id="root"></div>
4 голосов
/ 25 марта 2019

Лучше всего использовать отдельные звонки:

const [a, setA] = useState(true);
const [b, setB] = useState(true);

Вариант 1 может привести к большему количеству ошибок, потому что такой код часто оказывается внутри замыкания с устаревшим значением myState.

Вариант 2 следует использовать, когда новое состояние основано на старом:

setCount(count => count + 1);

Для сложной структуры состояния используйте useReducer

Для сложных структур, которые имеют некоторую форму и логику, вы можете создать собственный хук:

function useField(defaultValue) {
  const [value, setValue] = useState(defaultValue);
  const [dirty, setDirty] = useState(false);
  const [touched, setTouched] = useState(false);

  function handleChange(e) {
    setValue(e.target.value);
    setTouched(true);
  }

  return {
    value, setValue,
    dirty, setDirty,
    touched, setTouched,
    handleChange
  }
}

function MyComponent() {
  const username = useField('some username');
  const email = useField('some@mail.com');

  return <input name="username" value={username.value} onChange={username.handleChange}/>;
}
3 голосов
/ 25 марта 2019

Один или несколько параметров, относящихся к типу состояния, могут подходить в зависимости от вашего варианта использования.

Как правило, вы можете следовать следующим правилам, чтобы решить, какой тип состояния вы хотите

Первое:Связаны ли отдельные состояния

Если отдельные состояния, которые есть в вашем приложении, связаны друг с другом, вы можете сгруппировать их вместе в объекте.Иначе лучше хранить их отдельно и использовать несколько useState, чтобы при работе с определенными обработчиками вы обновляли только свойство релевантного состояния и не интересовались другими

Например, такими пользовательскими свойствами, как name, email связаны между собой, и вы можете сгруппировать их вместе. Принимая во внимание, что для обслуживания нескольких счетчиков вы можете использовать multiple useState hooks

Второе: логика обновления состояния сложна и зависит от обработчика или взаимодействия с пользователем

В вышеприведенном случае лучше использовать useReducer для определения состояния.Подобный сценарий очень распространен, когда вы пытаетесь создать, например, приложение todo, в котором вы хотите update, create и delete элементы для различных взаимодействий

Должен ли я использовать passфункция (ВАРИАНТ 2) для доступа к предыдущему состоянию, или я должен просто получить доступ к текущему состоянию с расширенным синтаксисом (ОПЦИЯ 1)?

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

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

2 голосов
/ 25 марта 2019

Какой из методов является лучшим для обновления объекта состояния с помощью хука состояния?

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

в чем разница?

Кажется, что путаница связана с "Unlike the setState method found in class components, useState does not automatically merge update objects", особенно с частью "слияния".

Давайте сравним this.setState & useState

class SetStateApp extends React.Component {
  state = {
    propA: true,
    propB: true
  };

  toggle = e => {
    const { name } = e.target;
    this.setState(
      prevState => ({
        [name]: !prevState[name]
      }),
      () => console.log(`this.state`, this.state)
    );
  };
  ...
}

function HooksApp() {
  const INITIAL_STATE = { propA: true, propB: true };
  const [myState, setMyState] = React.useState(INITIAL_STATE);

  const { propA, propB } = myState;

  function toggle(e) {
    const { name } = e.target;
    setMyState({ [name]: !myState[name] });
  }
...
}

Они оба переключают propA/B в toggle обработчик. И они оба обновляют только одну пропущенную пропозицию как e.target.name.

Обратите внимание на разницу при обновлении только одного свойства в setMyState.

Следующая демонстрация показывает, что нажатие на propA вызывает ошибку (которая возникает только setMyState),

Вы можете следовать по

Edit nrrjqj30wp

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

error demo

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

И this.setState обновляет только одно свойство за раз, но оно merges другое свойство, поэтому флажки остаются под контролем.


Я выкопал исходный код, и поведение вызвано useState вызовом useReducer

Внутренне, useState вызывает useReducer, который возвращает любое состояние, которое возвращает редуктор.

https://github.com/facebook/react/blob/2b93d686e3/packages/react-reconciler/src/ReactFiberHooks.js#L1230

    useState<S>(
      initialState: (() => S) | S,
    ): [S, Dispatch<BasicStateAction<S>>] {
      currentHookNameInDev = 'useState';
        ...
      try {
        return updateState(initialState);
      } finally {
        ...
      }
    },

где updateState - внутренняя реализация для useReducer.

function updateState<S>(
  initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
  return updateReducer(basicStateReducer, (initialState: any));
}

    useReducer<S, I, A>(
      reducer: (S, A) => S,
      initialArg: I,
      init?: I => S,
    ): [S, Dispatch<A>] {
      currentHookNameInDev = 'useReducer';
      updateHookTypesDev();
      const prevDispatcher = ReactCurrentDispatcher.current;
      ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnUpdateInDEV;
      try {
        return updateReducer(reducer, initialArg, init);
      } finally {
        ReactCurrentDispatcher.current = prevDispatcher;
      }
    },

Если вы знакомы с Redux, вы обычно возвращаете новый объект, распространяясь по предыдущему состоянию, как вы это делали в варианте 1.

setMyState({
  ...myState,
  propB: false
});

Таким образом, если вы установите только одно свойство, другие свойства не будут объединены.

1 голос
/ 27 июня 2019

Оба варианта действительны, но они имеют значение. Используйте опцию 1 (setCount (count + 1)), если

  1. При обновлении браузера свойство не имеет значения визуально
  2. Частота обновления жертвы для производительности
  3. Обновление состояния ввода на основе события (т. Е. Event.target.value); если вы используете вариант 2, он установит событие в null по причинам производительности, если у вас нет event.persist () - см. пул событий .

Использовать опцию 2 (setCount (c => c + 1)), если

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

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

Примечание: у меня нет статистики, подтверждающей разницу в производительности, но она основана на конференции React по оптимизации производительности React 16.

0 голосов
/ 25 марта 2019

Оба отлично подходят для этого варианта использования.Функциональный аргумент, который вы передаете setState, действительно полезен только в том случае, если вы хотите условно установить состояние, дифференцируя предыдущее состояние (я имею в виду, что вы можете просто сделать это с помощью логики, окружающей вызов setState, но я думаю, что это выглядит чищев функции) или если вы устанавливаете состояние в замыкании, которое не имеет немедленного доступа к самой свежей версии предыдущего состояния.

Примером может служить что-то вроде прослушивателя событий, связанного только один раз (по любой причине) на креплении к окну.Например,

useEffect(function() {
  window.addEventListener("click", handleClick)
}, [])

function handleClick() {
  setState(prevState => ({...prevState, new: true }))
}

Если бы handleClick только устанавливал состояние с помощью опции 1, это выглядело бы как setState({...prevState, new: true }).Однако это может привести к ошибке, поскольку prevState будет регистрировать состояние только при начальном рендеринге, а не из каких-либо обновлений.Аргумент функции, переданный setState, всегда будет иметь доступ к самой последней итерации вашего состояния.

...