установщик ловушки useState неправильно перезаписывает состояние - PullRequest
29 голосов
/ 02 октября 2019

Вот проблема: я пытаюсь вызвать 2 функции одним нажатием кнопки. Обе функции обновляют состояние (я использую хук useState). Первая функция корректно обновляет value1 до «new 1», но через 1 с (setTimeout) запускается вторая функция, и она меняет значение 2 на «new 2» НО! Он устанавливает значение 1 обратно в «1». Почему это происходит? Заранее спасибо!

import React, { useState } from "react";

const Test = () => {
  const [state, setState] = useState({
    value1: "1",
    value2: "2"
  });

  const changeValue1 = () => {
    setState({ ...state, value1: "new 1" });
  };
  const changeValue2 = () => {
    setState({ ...state, value2: "new 2" });
  };

  return (
    <>
      <button
        onClick={() => {
          changeValue1();
          setTimeout(changeValue2, 1000);
        }}
      >
        CHANGE BOTH
      </button>
      <h1>{state.value1}</h1>
      <h1>{state.value2}</h1>
    </>
  );
};

export default Test;

Ответы [ 4 ]

23 голосов
/ 02 октября 2019

Добро пожаловать в аду закрытия . Эта проблема возникает из-за того, что всякий раз, когда вызывается setState, state получает новую ссылку на память, но функции changeValue1 и changeValue2 из-за закрытия сохраняют старую начальную ссылку state.

Решение , обеспечивающее получение setState из changeValue1 и changeValue2 последнего состояния, заключается в использовании обратного вызова (имеющего свежее текущее состояние в качестве параметра):

import React, { useState } from "react";

const Test = () => {
  const [state, setState] = useState({
    value1: "1",
    value2: "2"
  });

  const changeValue1 = () => {
    setState((state) => ({ ...state, value1: "new 1" }));
  };
  const changeValue2 = () => {
    setState((state) => ({ ...state, value2: "new 2" }));
  };

  // ...
};

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

17 голосов
/ 02 октября 2019

Ваши функции должны быть такими:

const changeValue1 = () => {
    setState((prevState) => ({ ...prevState, value1: "new 1" }));
};
const changeValue2 = () => {
    setState((prevState) => ({ ...prevState, value2: "new 2" }));
};

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

5 голосов
/ 02 октября 2019

Когда вызывается changeValue2, начальное состояние сохраняется, поэтому состояние возвращается в исходное состояние, а затем записывается свойство value2.

В следующий раз, когда после этого вызывается changeValue2, оно удерживаетсясвойство состояния {value1: "1", value2: "new 2"}, поэтому value1 перезаписывается.

Вам необходима функция стрелки для параметра setState.

const Test = () => {
  const [state, setState] = React.useState({
    value1: "1",
    value2: "2"
  });

  const changeValue1 = () => {
    setState(prev => ({ ...prev, value1: "new 1" }));
  };
  const changeValue2 = () => {
    setState(prev => ({ ...prev, value2: "new 2" }));
  };

  return (
    <React.Fragment>
      <button
        onClick={() => {
          changeValue1();
          setTimeout(changeValue2, 1000);
        }}
      >
        CHANGE BOTH
      </button>
      <h1>{state.value1}</h1>
      <h1>{state.value2}</h1>
    </React.Fragment>
  );
};

ReactDOM.render(<Test />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>
3 голосов
/ 02 октября 2019

Происходит следующее: changeValue1 и changeValue2 видят состояние от рендеринга, в котором они были созданы в , поэтому, когда ваш компонент рендерит в первый раз, эти 2 функции видят:

state= {
  value1: "1",
  value2: "2"
}

Когда вы нажимаете на кнопку, сначала вызывается changeValue1 и, как и ожидалось, изменяется на {value1: "new1", value2: "2"}.

Теперь, через 1 секунду, вызывается changeValue2, но эта функциявсе еще видите начальное состояние ({value1; "1", value2: "2"}), поэтому, когда эта функция обновляет состояние следующим образом:

setState({ ...state, value2: "new 2" });

вы в конечном итоге видите: {value1; "1", value2: "new2"}.

источник

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