Допустим, у меня есть состояние, которое зависит от другого состояния (например, когда 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>
)
}



![Безумие обратных вызовов в javascript [JS]](https://i.imgur.com/WsjO6zJb.png)


Эффекты всегда выполняются после завершения фазы рендеринга, даже если вы устанавливаетеState внутри одного эффекта, другой эффект будет считывать обновленное состояние и предпринимать действия с ним только после фазы рендеринга.
Сказав, что, вероятно, лучше предпринять оба действия с одним и тем же эффектом, если нет возможности, что b может измениться по причинам, отличным от changing a, и в этом случае вы также захотите выполнить ту же логику.
Я также хочу знать ответ на вопрос выше
@alaboudi Да, если A изменяется, вызывая запуск useeffect, который устанавливает B, тогда компонент визуализируется дважды
@alaboudi Да ... как сказал Шубхам Хатри, он будет отображаться снова. но вы можете пропустить вызов эффекта после повторного рендеринга, используя второй аргумент refer reactjs.org/docs/…
Вообще говоря, использование setState внутри useEffect создаст бесконечный цикл, который, скорее всего, вы не хотите вызывать. Из этого правила есть несколько исключений, о которых я расскажу позже.
useEffect вызывается после каждого рендеринга, и когда setState используется внутри него, он вызывает повторный рендеринг компонента, который вызывает useEffect и так далее, и так далее.
Один из популярных случаев, когда использование useState внутри useEffect не приведет к бесконечному циклу, - это когда вы передаете пустой массив в качестве второго аргумента в useEffect, например useEffect(() => {....}, []), что означает, что функция эффекта должна вызываться один раз: только после первого монтирования / рендеринга . Это широко используется, когда вы выполняете выборку данных в компоненте и хотите сохранить данные запроса в состоянии компонента.
Другой случай использования setState внутри useEffect - это setting state внутри подписки или прослушивателей событий. Но не забудьте отменить подписку reactjs.org/docs/hooks-effect.html#effects-with-cleanup
Это не совсем так - useState срабатывает только в том случае, если значение, с помощью которого вы обновляете состояние, отличается от предыдущего, поэтому бесконечный цикл предотвращается, если значение не изменяется между циклами.
Этот ответ неверен и не по существу вопроса: в коде в случае, если компонент отрисовывается только дважды при нажатии кнопки, бесконечного цикла нет
Это очень распространенный вариант использования для установки состояния внутри useEffect. Подумайте о загрузке данных, useEffect вызывает API, получает данные, устанавливает, используя часть set в useState.
В первом абзаце этого ответа безоговорочно говорится, что этого делать не следует, но это очень часто делается. Возможно, измените порядок, чтобы сделать условие, которое вы добавили в третьем абзаце, более заметным.
Я обновил ответ, чтобы решить проблемы, о которых вы упомянули, теперь лучше?
Стоит отметить, что команда React теперь специально говорит вам делать обновления состояния (когда компонент A устанавливает состояние с помощью установщика, предоставляемого компонентом B) внутри ловушки useEffect, и если вы этого не сделаете, вы получите предупреждение в последней версии React.
Чувак, ты меня спас. Это то самое решение, которое я искал.
@machineghost Можете ли вы найти ссылку, где это говорит команда React?
Когда я попытался установить состояние в теле компонента, я получил сообщение об ошибке (от React). Когда я увидел это сообщение (почти год назад), я считать, оно ссылалось на страницу, но, к сожалению, у меня нет под рукой URL-адреса. Но на самом деле это просто сводится к пониманию React: если вы установите состояние внутри функции компонента, это может вызвать бесконечный цикл, потому что изменение состояния запускает повторную визуализацию, поэтому ... вы устанавливаете состояние, оно повторно отображает, оно устанавливает состояние снова, повторное рендеринг снова ... useEffect избегает этого, запуская средство установки состояния только при первом монтировании компонента.
Когда я пытаюсь обновить состояние в моем useEffect, переменная состояния не обновляется. Судя по другим комментариям, мой вызов выглядит так: setSizeGuideData(sizeGuideData => ({ ...sizeGuideData, getFilteredSizeGuide }));. Когда я регистрирую sizeGuideData, я получаю возвращенный []. getFilteredSizeGuide имеет правильные данные. Так что понятия не имею, почему это не работает.
useEffect может подключиться к определенной опоре или состоянию. Итак, что вам нужно сделать, чтобы избежать бесконечного цикла, - это привязать некоторую переменную или состояние для воздействия
Например:
useEffect(myeffectCallback, [])
Указанный выше эффект сработает только после рендеринга компонента. это похоже на жизненный цикл componentDidMount
const [something, setSomething] = withState(0)
const [myState, setMyState] = withState(0)
useEffect(() => {
setSomething(0)
}, myState)
вышеупомянутый эффект сработает, только мое состояние изменилось, это похоже на componentDidUpdate, за исключением того, что не каждое изменение состояния будет запускать его.
Вы можете прочитать более подробную информацию, хотя этот ссылка на сайт
Спасибо, этот ответ обращается к массиву зависимостей useEffect так, как не сделал другой ответ. Включение пустого массива в качестве второго аргумента для useEffect обеспечит выполнение useEffect после рендеринга компонента, но включение массива с определенным состоянием или определенными состояниями приведет к выполнению useEffect при изменении состояний в ссылке.
Я не понимаю, что представляет собой withState(). Я не могу найти ссылку на это в документе.
В будущем это также может помочь:
Можно использовать 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 }));
это помогло мне также в части со стрелочной функцией setItems(items => [...items, item])
Провел с 7 утра до 3 вечера, не придумав решения, и теперь ты спас меня.
Я пробовал это решение. setState(state => ({ ...state, useEffectValue })); Когда я регистрирую state, он остается пустым. Если я передаю useEffectValue в выходной массив, я получаю бесконечный цикл. ¯_ (ツ) _ / ¯
не могу отблагодарить вас за этот невероятный совет. Я действительно переопределял состояние из нескольких вызовов useEffect, которые запускались при монтировании компонента.
▶ 1. Могу ли я установить состояние внутри ловушки useEffect?
В принципе, вы можете свободно устанавливать состояние там, где оно вам нужно, в том числе внутри useEffect и даже во время рендеринга. Просто убедитесь, что вы избегаете бесконечных циклов, правильно установив Hook deps и / или установив условное состояние.
▶ 2. Допустим, у меня есть состояние, которое зависит от другого состояния. Уместно ли создать ловушку, которая наблюдает за A и устанавливает B внутри ловушки useEffect?
Вы только что описали классический вариант использования в для useReducer:
useReduceris usually preferable touseStatewhen 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])
Итак, если A изменит B, компонент будет отображаться дважды, верно?