Могу ли я установить состояние внутри хука useEffect

Допустим, у меня есть состояние, которое зависит от другого состояния (например, когда A изменяется, я хочу, чтобы B изменился).

Уместно ли создать ловушку, которая наблюдает за A и устанавливает B внутри ловушки useEffect?

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

let MyComponent = props => {
  let [a, setA] = useState(1)
  let [b, setB] = useState(2)
  useEffect(
    () => {
      if (/*some stuff is true*/) {
        setB(3)
      }
    },
    [a],
  )
  useEffect(
    () => {
      // do some stuff
    },
    [b],
  )

  return (
    <button
      onClick = {() => {
        setA(5)
      }}
    >
      click me
    </button>
  )
}
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
191
0
213 414
6
Перейти к ответу Данный вопрос помечен как решенный

Ответы 6

Ответ принят как подходящий

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

Сказав, что, вероятно, лучше предпринять оба действия с одним и тем же эффектом, если нет возможности, что b может измениться по причинам, отличным от changing a, и в этом случае вы также захотите выполнить ту же логику.

Итак, если A изменит B, компонент будет отображаться дважды, верно?

Dan Ruswick 11.12.2018 16:22

Я также хочу знать ответ на вопрос выше

alaboudi 17.07.2020 04:27

@alaboudi Да, если A изменяется, вызывая запуск useeffect, который устанавливает B, тогда компонент визуализируется дважды

Shubham Khatri 17.07.2020 06:56

@alaboudi Да ... как сказал Шубхам Хатри, он будет отображаться снова. но вы можете пропустить вызов эффекта после повторного рендеринга, используя второй аргумент refer reactjs.org/docs/…

Karthikeyan 28.12.2020 09:07

Вообще говоря, использование setState внутри useEffect создаст бесконечный цикл, который, скорее всего, вы не хотите вызывать. Из этого правила есть несколько исключений, о которых я расскажу позже.

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

Один из популярных случаев, когда использование useState внутри useEffect не приведет к бесконечному циклу, - это когда вы передаете пустой массив в качестве второго аргумента в useEffect, например useEffect(() => {....}, []), что означает, что функция эффекта должна вызываться один раз: только после первого монтирования / рендеринга . Это широко используется, когда вы выполняете выборку данных в компоненте и хотите сохранить данные запроса в состоянии компонента.

Другой случай использования setState внутри useEffect - это setting state внутри подписки или прослушивателей событий. Но не забудьте отменить подписку reactjs.org/docs/hooks-effect.html#effects-with-cleanup

timqian 29.04.2019 09:48

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

RobM 04.07.2019 19:17

Этот ответ неверен и не по существу вопроса: в коде в случае, если компонент отрисовывается только дважды при нажатии кнопки, бесконечного цикла нет

Bogdan D 23.07.2019 17:52

Это очень распространенный вариант использования для установки состояния внутри useEffect. Подумайте о загрузке данных, useEffect вызывает API, получает данные, устанавливает, используя часть set в useState.

badbod99 06.08.2019 13:20

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

nil 24.10.2019 17:21

Я обновил ответ, чтобы решить проблемы, о которых вы упомянули, теперь лучше?

Hossam Mourad 13.11.2019 08:06

Стоит отметить, что команда React теперь специально говорит вам делать обновления состояния (когда компонент A устанавливает состояние с помощью установщика, предоставляемого компонентом B) внутри ловушки useEffect, и если вы этого не сделаете, вы получите предупреждение в последней версии React.

machineghost 23.03.2020 23:38

Чувак, ты меня спас. Это то самое решение, которое я искал.

Deepak Panwar 13.02.2021 11:14

@machineghost Можете ли вы найти ссылку, где это говорит команда React?

Noumenon 14.02.2021 00:53

Когда я попытался установить состояние в теле компонента, я получил сообщение об ошибке (от React). Когда я увидел это сообщение (почти год назад), я считать, оно ссылалось на страницу, но, к сожалению, у меня нет под рукой URL-адреса. Но на самом деле это просто сводится к пониманию React: если вы установите состояние внутри функции компонента, это может вызвать бесконечный цикл, потому что изменение состояния запускает повторную визуализацию, поэтому ... вы устанавливаете состояние, оно повторно отображает, оно устанавливает состояние снова, повторное рендеринг снова ... useEffect избегает этого, запуская средство установки состояния только при первом монтировании компонента.

machineghost 14.02.2021 01:32

Когда я пытаюсь обновить состояние в моем useEffect, переменная состояния не обновляется. Судя по другим комментариям, мой вызов выглядит так: setSizeGuideData(sizeGuideData => ({ ...sizeGuideData, getFilteredSizeGuide }));. Когда я регистрирую sizeGuideData, я получаю возвращенный []. getFilteredSizeGuide имеет правильные данные. Так что понятия не имею, почему это не работает.

Mike S. 03.08.2021 17:12

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

Например:

useEffect(myeffectCallback, [])

Указанный выше эффект сработает только после рендеринга компонента. это похоже на жизненный цикл componentDidMount

const [something, setSomething] = withState(0)
const [myState, setMyState] = withState(0)
useEffect(() => {
  setSomething(0)
}, myState)

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

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

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

adriaanbd 20.10.2020 05:30

Я не понимаю, что представляет собой withState(). Я не могу найти ссылку на это в документе.

Mike S. 03.08.2021 17:07

В будущем это также может помочь:

Можно использовать setState в useEffect, вам просто нужно обратить внимание, как уже было описано, чтобы не создавать петли.

Но это не единственная проблема, которая может возникнуть. Увидеть ниже:

Представьте, что у вас есть компонент Comp, который получает props от родителя, и в соответствии с изменением props вы хотите установить состояние Comp. По какой-то причине вам нужно изменить каждую опору в другом useEffect:

НЕ ДЕЛАЙ ЭТО

useEffect(() => {
  setState({ ...state, a: props.a });
}, [props.a]);

useEffect(() => {
  setState({ ...state, b: props.b });
}, [props.b]);

Это может никогда не изменить состояние a, как вы можете видеть в этом примере: https://codesandbox.io/s/confident-lederberg-dtx7w

Причина, по которой это происходит в этом примере, заключается в том, что оба useEffects работают в одном и том же реакционном цикле, когда вы меняете и prop.a, и prop.b, поэтому значение {...state}, когда вы делаете setState, точно такое же в обоих useEffect, потому что они находятся в одном контексте. Когда вы запустите второй setState, он заменит первый setState.

СДЕЛАЙТЕ ЭТО ВМЕСТО

Решением этой проблемы является вызов setState следующим образом:

useEffect(() => {
  setState(state => ({ ...state, a: props.a }));
}, [props.a]);

useEffect(() => {
  setState(state => ({ ...state, b: props.b }));
}, [props.b]);

Проверьте решение здесь: https://codesandbox.io/s/mutable-surf-nynlx

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

Я надеюсь, что это помогает кому-то!

выше решение помогло мне setName(name => ({ ...name, a: props.a }));

mufaddal_mw 14.04.2020 11:29

это помогло мне также в части со стрелочной функцией setItems(items => [...items, item])

Stefan Zhelyazkov 09.06.2020 18:00

Провел с 7 утра до 3 вечера, не придумав решения, и теперь ты спас меня.

Dinindu Kanchana 19.09.2020 11:24

Я пробовал это решение. setState(state => ({ ...state, useEffectValue })); Когда я регистрирую state, он остается пустым. Если я передаю useEffectValue в выходной массив, я получаю бесконечный цикл. ¯_ (ツ) _ / ¯

Mike S. 03.08.2021 17:05

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

JSancho 11.08.2021 17:45

▶ 1. Могу ли я установить состояние внутри ловушки useEffect?

В принципе, вы можете свободно устанавливать состояние там, где оно вам нужно, в том числе внутри useEffect и даже во время рендеринга. Просто убедитесь, что вы избегаете бесконечных циклов, правильно установив Hook deps и / или установив условное состояние.


▶ 2. Допустим, у меня есть состояние, которое зависит от другого состояния. Уместно ли создать ловушку, которая наблюдает за A и устанавливает B внутри ловушки useEffect?

Вы только что описали классический вариант использования в для useReducer:

useReducer is usually preferable to useState when you have complex state logic that involves multiple sub-values or when the next state depends on the previous one. (React docs)

When setting a state variable depends on the current value of another state variable, you might want to try replacing them both with useReducer. [...] When you find yourself writingsetSomething(something => ...), it’s a good time to consider using a reducer instead. (Dan Abramov, Overreacted blog)

let MyComponent = () => {
  let [state, dispatch] = useReducer(reducer, { a: 1, b: 2 });

  useEffect(() => {
    console.info("Some effect with B");
  }, [state.b]);

  return (
    <div>
      <p>A: {state.a}, B: {state.b}</p>
      <button onClick = {() => dispatch({ type: "SET_A", payload: 5 })}>
        Set A to 5 and Check B
      </button>
      <button onClick = {() => dispatch({ type: "INCREMENT_B" })}>
        Increment B
      </button>
    </div>
  );
};

// B depends on A. If B >= A, then reset B to 1.
function reducer(state, { type, payload }) {
  const someCondition = state.b >= state.a;

  if (type === "SET_A")
    return someCondition ? { a: payload, b: 1 } : { ...state, a: payload };
  else if (type === "INCREMENT_B") return { ...state, b: state.b + 1 };
  return state;
}

ReactDOM.render(<MyComponent />, document.getElementById("root"));
<script src = "https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js" integrity = "sha256-32Gmw5rBDXyMjg/73FgpukoTZdMrxuYW7tj8adbN8z4 = " crossorigin = "anonymous"></script>
<script src = "https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js" integrity = "sha256-bjQ42ac3EN0GqK40pC9gGi/YixvKyZ24qMP/9HiGW7w = " crossorigin = "anonymous"></script>
<div id = "root"></div>
<script>var { useReducer, useEffect } = React</script>

▶ 3. Будут ли эффекты каскадированы таким образом, что, когда я нажимаю кнопку, срабатывает первый эффект, вызывая изменение b, вызывая срабатывание второго эффекта перед следующей визуализацией?

useEffect всегда запускает после, рендеринг фиксируется и применяются изменения DOM. Срабатывает первый эффект, изменяет b и вызывает повторный рендеринг. После завершения этого рендеринга запустится второй эффект из-за изменений b.

let MyComponent = props => {
  console.info("render");
  let [a, setA] = useState(1);
  let [b, setB] = useState(2);

  let isFirstRender = useRef(true);

  useEffect(() => {
    console.info("useEffect a, value:", a);
    if (isFirstRender.current) isFirstRender.current = false;
    else setB(3);
    return () => {
      console.info("unmount useEffect a, value:", a);
    };
  }, [a]);
  useEffect(() => {
    console.info("useEffect b, value:", b);
    return () => {
      console.info("unmount useEffect b, value:", b);
    };
  }, [b]);

  return (
    <div>
      <p>a: {a}, b: {b}</p>
      <button
        onClick = {() => {
          console.info("Clicked!");
          setA(5);
        }}
      >
        click me
      </button>
    </div>
  );
};

ReactDOM.render(<MyComponent />, document.getElementById("root"));
<script src = "https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js" integrity = "sha256-32Gmw5rBDXyMjg/73FgpukoTZdMrxuYW7tj8adbN8z4 = " crossorigin = "anonymous"></script>
<script src = "https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js" integrity = "sha256-bjQ42ac3EN0GqK40pC9gGi/YixvKyZ24qMP/9HiGW7w = " crossorigin = "anonymous"></script>
<div id = "root"></div>
<script>var { useReducer, useEffect, useState, useRef } = React</script>

▶ 4. Есть ли у такого структурирования кода какие-либо недостатки в производительности?

да. Обернув изменение состояния b в отдельный useEffect для a, браузер получает дополнительную фазу макета / рисования - эти эффекты потенциально видны пользователю. Если вы не хотите попробовать useReducer, вы можете напрямую изменить состояние b вместе с a:

let MyComponent = () => {
  console.info("render");
  let [a, setA] = useState(1);
  let [b, setB] = useState(2);

  useEffect(() => {
    console.info("useEffect b, value:", b);
    return () => {
      console.info("unmount useEffect b, value:", b);
    };
  }, [b]);

  const handleClick = () => {
    console.info("Clicked!");
    setA(5);
    b >= 5 ? setB(1) : setB(b + 1);
  };

  return (
    <div>
      <p>
        a: {a}, b: {b}
      </p>
      <button onClick = {handleClick}>click me</button>
    </div>
  );
};

ReactDOM.render(<MyComponent />, document.getElementById("root"));
<script src = "https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js" integrity = "sha256-32Gmw5rBDXyMjg/73FgpukoTZdMrxuYW7tj8adbN8z4 = " crossorigin = "anonymous"></script>
<script src = "https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js" integrity = "sha256-bjQ42ac3EN0GqK40pC9gGi/YixvKyZ24qMP/9HiGW7w = " crossorigin = "anonymous"></script>
<div id = "root"></div>
<script>var { useReducer, useEffect, useState, useRef } = React</script>

Попробуйте обернуть setState внутри оператора if, который проверяет, нужно ли изменить состояние - если да, измените его, иначе return () => {}

например.,

useEffect(() => {
    if (a.currentCondition !== a.desiredCondition) {
        setA();
    }
    return cleanup;
}, [b])

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