Как синхронизировать пропсы с состоянием с помощью хуков React: setState()

Я пытаюсь установить состояние с помощью React hook setState(), используя реквизиты, которые получает компонент. Я пробовал использовать следующий код:

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

const Persons = (props) =>  {

    // console.info(props.name);

   const [nameState , setNameState] = useState(props)

   console.info(nameState.name);
   console.info(props.name);

   return (
            <div>
                <p>My name is {props.name} and my age is {props.age}</p>
                <p>My profession is {props.profession}</p>
            </div>
        )

}

export default Persons;

Проблема заключается в том, что состояние устанавливается при загрузке компонента. Но когда он получает новые реквизиты, состояние не обновляется. Как обновить состояние в этом случае? Заранее спасибо.

Можете ли вы добавить код в setNameState.

ManavM 11.02.2019 08:41

Привет, Манав, не можем ли мы синхронизировать реквизиты с состоянием, используя useState каждый раз, когда реквизиты обновляются..?

METALHEAD 11.02.2019 08:51

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

ManavM 11.02.2019 08:53
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Навигация по приложениям React: Исчерпывающее руководство по React Router
Навигация по приложениям React: Исчерпывающее руководство по React Router
React Router стала незаменимой библиотекой для создания одностраничных приложений с навигацией в React. В этой статье блога мы подробно рассмотрим...
Массив зависимостей в React
Массив зависимостей в React
Все о массиве Dependency и его связи с useEffect.
105
3
72 947
7
Перейти к ответу Данный вопрос помечен как решенный

Ответы 7

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

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

const Persons = props => {
  // console.info(props.name);

  const [nameState, setNameState] = useState(props);

  console.info(nameState.name);
  console.info(props.name);
  useEffect(
    () => {
      if (nameState !== props.name) {
        setNameState(props.name);
      }
    },
    [nameState]
  );
  return (
    <div>
      <p>
        My name is {props.name} and my age is {props.age}
      </p>
      <p>My profession is {props.profession}</p>
    </div>
  );
};

export default Persons;

Демо

Такой подход выглядит великолепно, но разве этот компонент не рендерит дважды? Потому что мы используем setState() в useEffect. Разве это не удар по производительности?

METALHEAD 04.02.2020 14:21

@METALHEAD: он не будет вызываться дважды, потому что мы также проверили, изменился ли nameState, тогда должен выполняться только useEffect, см. второй аргумент useEffect

Dhaval Patel 05.02.2020 08:57

@DhavalPatel Изменение реквизита вызвало рендеринг, а setNameState() запускает другой. Мне кажется дважды.

Drazen Bjelovuk 23.03.2020 00:42
Ответ принят как подходящий

Аргумент функции перехватчиков useState используется только один раз, а не каждый раз при изменении реквизита. Вы должны использовать хуки useEffect для реализации того, что вы бы назвали функциональностью componentWillReceiveProps/getDerivedStateFromProps.

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

const Persons = (props) =>  {
   const [nameState , setNameState] = useState(props)

   useEffect(() => {
       setNameState(props);
   }, [props])

   return (
            <div>
                <p>My name is {props.name} and my age is {props.age}</p>
                <p>My profession is {props.profession}</p>
            </div>
        )

}

export default Persons;

Были обсуждения инициализации состояния из реквизита в антишаблоне, но поскольку у нас есть хуки, я думаю, что-то изменилось. Но я не уверен. Что вы думаете?

Imdadul Huq Naim 08.05.2019 19:11

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

Shubham Khatri 09.05.2019 07:58

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

METALHEAD 27.07.2019 05:18

Почему вы используете props.name и props.age и т. д. в возврате вместо nameState.name и nameState.age и т. д.?

sutherlandahoy 29.08.2019 09:49

Такой подход выглядит великолепно, но разве этот компонент не рендерит дважды? Потому что мы используем setState() в useEffect. Разве это не удар по производительности?

METALHEAD 04.02.2020 14:20

Неправильный ответ, это приведет к расточительному, призрачному повторному рендерингу. useEffect здесь не используется. Самый простой способ сделать это: http://reactjs.org/docs/… В некоторых случаях применимы более продвинутые методы, например, кодирование хука useComputedState. Некоторые другие предложения: reactjs.org/blog/2018/06/07/… Это неприятный побочный продукт реактивных хуков.

AlexG 10.05.2020 01:52

@AlexG То, что вы предложили, также устанавливает состояние и вызывает повторную визуализацию. В документах указано: React will re-run the component with updated state immediately after exiting the first render so it wouldn’t be expensive..

supra28 29.09.2020 13:38

@ supra28 Да, но на самом деле это не коснется DOM, как в этом ответе.

AlexG 04.11.2020 23:48
import React, { useState, useEffect } from "react";

const Persons = props => {
  // console.info(props.name);

  const [nameState, setNameState] = useState(props);

  console.info(nameState.name);
  console.info(props.name);
  useEffect(
    () => {
      if (nameState !== props) {
        setNameState(props);
      }
    },
    [nameState]
  );
  return (
    <div>
      <p>
        My name is {props.name} and my age is {props.age}
      </p>
      <p>My profession is {props.profession}</p>
    </div>
  );
};

export default Persons;

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

Эту общую идею можно поместить на крючок:

export function useStateFromProp(initialValue) {
  const [value, setValue] = useState(initialValue);

  useEffect(() => setValue(initialValue), [initialValue]);

  return [value, setValue];
}


function MyComponent({ value: initialValue }) {
  const [value, setValue] = useStateFromProp(initialValue);

  return (...);
}

Для машинописного текста понадобится немного неуклюжая типизированная версия: useEffect(() => setValue(initialValue), [initialValue]); вернуть [значение, установить значение]; }

chrismarx 26.05.2021 14:29

Значение props в useState(props) используется только во время начального рендера, дальнейшее обновление состояния производится сеттером setNameState.

Кроме того, нет необходимости в useEffect при обновлении производное состояние:

const Person = props => {
  const [nameState, setNameState] = useState(props.name);
  // update derived state conditionally without useEffect
  if (props.name !== nameState) setNameState(props.name);
  // ... other render code
};

Из Реагировать документы:

[...] you can update the state right during rendering. React will re-run the component with updated state immediately after exiting the first render so it wouldn’t be expensive.

[...] an update during rendering is exactly what getDerivedStateFromProps has always been like conceptually.

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

Рабочий пример

Это надуманный пример, иллюстрирующий приведенный выше шаблон - в реальном коде вы бы читали props.name напрямую. См. Реагировать на сообщение в блоге для более подходящих вариантов использования производного состояния.

const Person = props => {
  const [nameState, setNameState] = React.useState(props.name);
  // Here, we update derived state without useEffect
  if (props.name !== nameState) setNameState(props.name);

  return (
    <p>
      <h3>Person</h3>
      <div>{nameState} (from derived state)</div>
      <div>{props.name} (from props)</div>
      <p>Note: Derived state is synchronized/contains same value as props.name</p>
    </p>
  );
};

const App = () => {
  const [personName, setPersonName] = React.useState("Lui");
  const changeName = () => setPersonName(personName === "Lukas" ? "Lui" : "Lukas");

  return (
    <div>
      <Person name = {personName} />
      <button onClick = {changeName}>Change props</button>
    </div>
  );
};

ReactDOM.render(<App />, 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>

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

METALHEAD 21.05.2020 21:17

Не уверен, что вы пытаетесь сделать. Хотя вам придется написать логику глубокого сравнения вручную, как в приведенном выше коде: if (props.name.first !== nameState.first). Или используйте помощник для глубокого сравнения

ford04 21.05.2020 22:00

Жаль, что я не увидел этот ответ утром... Я только что потратил целый день на эту проблему.

aztack 27.03.2021 17:11

Я считаю, что проблема указывает на попытку использовать одну переменную концептуальный или набор переменных для выполнения двух разных задач. Например, попытка заставить props.name и name делать то же самое.

Так что если

const [name, setName] = useState(props.name)

недостаточно, и вы обнаружите, что пытаетесь принудительно перевести props.name в переменную состояния name позже в функции, тогда, возможно, name перегружен. Попробуйте настроить другую переменную состояния, например. updatedName и посмотрите, будет ли работать лучше.

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

Если бы const [name, setName] = useState(props.name) обновлялся при каждом повторном рендеринге, не было бы смысла иметь переменную состояния name, поскольку она всегда была бы такой же, как props.name (и дальнейшие попытки ее изменить вызовут повторный рендеринг).

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

Ross Attrill 24.06.2021 00:12

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

export function useStateFromProp(propValue) {
  const [value,          setValue         ] = useState(propValue);
  const [propValueState, setPropValueState] = useState(propValue);

  if (propValueState != propValue) {
    setPropValueState(propValue);
    setValue(propValue);
  }

  return [value, setValue];
}


function MyComponent({ value: propValue }) {
  const [value, setValue] = useStateFromProp(propValue);

  return (...);
}

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

Отказ от ответственности: я еще не тестировал это. Я немного погуглил и нашел эту вспомогательную статью: https://pretagteam.com/question/in-react-hooks-when-calling-setstate-directly-during-render-is-the-rerender-guaranteed-to-run-before-the-render-of-children

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