Чи можу я встановити стан всередині useEffect гачка


124

Скажімо, у мене є якийсь стан, який залежить від якогось іншого стану (наприклад, коли 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>
  )
}

Відповіді:


31

Ефекти завжди виконуються після завершення фази візуалізації, навіть якщо ви встановили State всередині одного ефекту, інший ефект буде зчитувати оновлений стан і вживати дії щодо нього лише після фази візуалізації.

Сказавши, що, ймовірно, краще вживати обидві дії з однаковим ефектом, якщо не існує ймовірності, яка bможе змінитися через інші причини, ніж changing aу цьому випадку ви також хочете виконати одну і ту ж логіку


4
Отже, якщо A змінює B, компонент відображатиметься двічі правильно?
Dan Ruswick

2
Я також хочу знати відповідь на вищезазначене питання
alaboudi

2
@alaboudi Так, якщо зміна A спричиняє запуск useeffect, що встановлює B, тоді компонент відображається двічі
Shubham Khatri

@alaboudi Так .. як сказав Шубхам Хатрі, це відтвориться знову. але ви можете пропустити виклик вашого ефекту після повторного рендерингу, використовуючи другий аргумент, зверніться към responsejs.org/docs/…
Karthikeyan

108

Взагалі кажучи, використання setStateвсередині useEffectстворить нескінченний цикл, який, швидше за все, ви не хочете викликати. Є кілька винятків із цього правила, до яких я розгляну пізніше.

useEffectвикликається після кожного візуалізації, і коли setStateвикористовується всередині нього, це призведе до повторного відтворення компонента, який буде викликати useEffectтощо, тощо.

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


3
Інший випадок, який слід використовувати setStateвсередині, useEffect- це setting stateпрослуховувачі підписки або події. Але не забудьте скасувати підписку responsejs.org/docs/hooks-effect.html#effects-with-cleanup
timqian

35
Це не є суворою правдою - useState спрацьовує лише в тому випадку, якщо значення, яким ви оновлюєте стан, відрізняється від попереднього, тож запобігає нескінченний цикл, якщо значення не змінюється між циклами.
RobM

14
Ця відповідь неправильна і не суть питання: у коді на випадок компонент відображається лише двічі при натисканні кнопки, немає нескінченного циклу
Богдан Д

15
Це дуже поширений випадок використання для встановлення стану всередині useEffect. Подумайте про завантаження даних, useEffect викликає API, отримує дані, встановлює за допомогою встановленої частини useState.
badbod99

3
Я оновив відповідь, щоб вирішити проблеми, про які ви згадали. Чи краще зараз?
Хоссам Мурад,

58

Для майбутніх цілей це також може допомогти:

Це нормально використовувати 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.

Сподіваюся, це комусь допоможе!


3
мені допомогло вищевказане рішенняsetName(name => ({ ...name, a: props.a }));
mufaddal_mw

2
це мені також допомогло в частині з функцією стрілкиsetItems(items => [...items, item])
Стефан Желязков

Ого, ти чудова. Ви врятували мої * сьогодні.
Пратік Соні

Провів з 7 ранку до 3 вечора, не маючи рішення, і тепер ви мене врятували.
Dinindu Kanchana,

24

useEffectможе підключити певний реквізит або стан. Отже, все, що вам потрібно зробити, щоб уникнути нескінченного гачка петлі, це прив’язка якоїсь змінної чи стану до ефекту

Наприклад:

useEffect(myeffectCallback, [])

ефект вище буде спрацьовувати лише після того, як компонент відтвориться. це схоже на componentDidMountжиттєвий цикл

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

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

Ви можете прочитати докладніше за цим посиланням


1
Дякую, ця відповідь звертається до масиву залежностей useEffect так, як це не робила інша відповідь. Включення порожнього масиву в якості другого аргументу useEffect забезпечить виконання useEffect після відображення компонента, але включення масиву з певним станом або певними станами призведе до того, що useEffect буде виконуватися, коли згадані стани змінюються.
adriaanbd

11

▶ 1. Чи можу я встановити стан всередині гачка useEffect?

В принципі, ви можете вільно встановлювати стан там, де вам це потрібно - в тому числі всередині useEffectі навіть під час візуалізації . Просто не забудьте уникнути нескінченних циклів, depsправильно встановивши Хук та / або умовно.


▶ 2. Скажімо, у мене є якийсь стан, який залежить від якогось іншого стану. Чи доречно створити гачок, який спостерігає за А та встановлює В всередині гачка useEffect?

Ви тільки що описав в класичний випадок використання для useReducer:

useReducerзазвичай переважніше, useStateколи у вас є складна логіка стану, яка включає кілька підзначень, або коли наступний стан залежить від попереднього. ( React docs )

Коли встановлення змінної стану залежить від поточного значення іншої змінної стану , ви можете спробувати замінити їх обидва на useReducer. [...] Коли ви виявляєте, що пишете setSomething(something => ...) , настав час подумати про використання замість цього редуктора. ( Ден Абрамов, блог, що перереагував )


▶ 3. Чи ефекти каскадуватимуться так, що коли я натисну кнопку, перший ефект спрацює, що призведе до зміни b, спричиняючи спрацювання другого ефекту, до наступного відтворення?

useEffectзавжди запускається після завершення візуалізації та застосування змін DOM. Перший ефект спрацьовує, змінюється bта викликає рендеринг. Після завершення цього візуалізації другий ефект буде діяти через bзміни.


▶ 4. Чи є такі мінуси у продуктивності такого кодування структури?

Так. Обертаючи зміну стану bв окрему useEffectдля a, браузер має додаткову фазу макета / розфарбовування - ці ефекти потенційно можуть бути видимі для користувача. Якщо немає можливості useReducerспробувати, ви можете змінити bстан разом із a:

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.