React Hooks — обновление родительского компонента без запуска бесконечного цикла

Скажем, у меня есть родительский компонент, который отображает дочерний компонент, и все, что делает дочерний компонент, — это сохраняет некоторое состояние и запускает обработчик onChange, если его собственное состояние изменяется. См. приведенный ниже код или CodeSandbox.

Пример CodeSandbox

Этот код попадает в бесконечный цикл, и я хочу избавиться от этого поведения.

Возможные решения, но те, которые я не предпочитаю:

1: Я мог бы поместить все состояние в родительский компонент и управлять дочерним компонентом с помощью родителя, но это не совсем то, чего я хочу. В реальной жизни мой дочерний компонент заботится не только о простом счетчике, и я хочу легко использовать его из родительского компонента. Внутри дочернего компонента происходит сложное поведение, и я хочу сообщить о некоторых простых изменениях родительскому компоненту. Или это абсолютно запрещено в React? (чтобы сохранить состояние в дочернем, а также инициировать изменение состояния при обновлении родительского) Я бы сказал, что это не обязательно?

2: я также мог бы вызвать обработчик onChange из обработчика handleInternalChange. К тому же не то, что я хочу. В реальной жизни мой дочерний компонент будет обновляться из нескольких отдельных мест в самом компоненте, поэтому изменение состояния — это самая элегантная вещь, которую нужно наблюдать и запускать родитель onChange.

3: я мог бы опустить зависимость onChange в массиве зависимостей useEffect. Это не рекомендуется, сообщество React ссылается на это объяснение. Как я понимаю, только я чувствую, что эта ситуация будет исключением?** Кроме того, я использую CRA, который поставляется с отличными линтерами и т. д. из коробки, и линтер жалуется на это, если я удаляю обработчик onChange из зависимостей, и я не предпочитаю начинать варить свои собственные правила лайнера. Для такого простого варианта использования, как мой, линтеры, установленные сообществом, должны работать нормально.

Что я думаю, что происходит Я думаю, что происходит то, что родитель обновляется, затем перерисовывает все это, и каким-то образом обработчик onChange также «меняется». Функция на самом деле не меняется, насколько я бы сказал, но React думает (или знает), что это так, поэтому он снова запускает вызов useEffect в дочернем компоненте, а затем рождается бесконечный цикл.

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

import React, { useState, useEffect } from "react";

const Comp = ({ onChange }) => {
  const [internalState, setInternalState] = useState(0);
  const handleChangeEvent = () => {
    setInternalState(internalState + 1);
  };

  useEffect(() => {
    const result = internalState.toString();
    onChange(result);
  }, [internalState, onChange]);

  return (
    <div onClick = {handleChangeEvent}>
      CLICK ME
      <div>{`INTERNAL NUM: ${internalState}`}</div>
    </div>
  );
};

export default function App() {
  const [state, setState] = useState("");
  const handleChangeEvent = () => setState(state + 1);
  return (
    <div className = "App">
      <h3>{`STATE: ${state}`}</h3>
      <Comp onChange = {handleChangeEvent} />
    </div>
  );
}

** Через некоторое время, в других случаях, свойство onChange, конечно, может быть изменено, просто назначив ему другую функцию. Так что это правило мне совершенно ясно. Только (как сказано в последнем абзаце), почему он ведет себя так, как будто он меняется в этом сценарии? Так как моя функция совершенно не меняется.

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

Keith 15.12.2020 15:02
Since my function does not change at all. Он меняется каждый раз, когда вы визуализируете родителя.
Keith 15.12.2020 15:08

Ваши реквизиты onChange постоянно меняются, потому что каждый раз, когда родительский компонент изменяется, он повторно создает экземпляр handleChangeEvent, который никогда не будет таким же, как предыдущий. Я думаю, вам следует выбрать решение 1: сохранить состояние в родительском компоненте и передать его в качестве опоры дочернему компоненту; это никоим образом не должно ограничивать вас, так как ваш дочерний компонент все еще может иметь свое собственное локальное состояние и выполнять все сложные операции, которые он должен выполнять.

secan 15.12.2020 15:08

Возможно, вы захотите проверить хук useCallback

secan 15.12.2020 15:11
Поведение ключевого слова "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) для оценки ваших знаний,...
2
4
2 348
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

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

Вы должны обернуть метод handleChangeEvent хуками useCallback, чтобы он был создан один раз.

const handleChangeEvent = useCallback(() => setState(state + 1),[]);

Бесконечный цикл возникает из-за того, что вы добавили метод onChange в качестве зависимости для useEffect в компоненте <Comp />.

useEffect принимает массив зависимостей и запускает эффект при изменении одной из зависимостей.

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

Процесс рендеринга компонента будет таким:

  1. <App /> компонент создает handleChangeEvent метод и передает его <Comp />
  2. Useffect в <Comp /> будет вызываться после первоначального рендеринга, а оттуда будет вызываться метод handleChangeEvent компонента <App />.
  3. состояние изменяется в компоненте <App /> и повторно отображает его. При повторном рендеринге создается новый экземпляр handleChangeEvent и передается свойство onChange компоненту <Comp />.
  4. Поскольку значение в предыдущем и новом свойствах onChange различается, сработает useEffect, который снова обновит состояние родительского компонента, и этот цикл продолжится.

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

{} === {}  // false
[] === []  // false
(() => {}) == (() => {})  //false

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