Я пытаюсь установить состояние с помощью 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;
Проблема заключается в том, что состояние устанавливается при загрузке компонента. Но когда он получает новые реквизиты, состояние не обновляется. Как обновить состояние в этом случае? Заранее спасибо.
Привет, Манав, не можем ли мы синхронизировать реквизиты с состоянием, используя useState каждый раз, когда реквизиты обновляются..?
нет, эта строка только инициализирует ваше состояние. если вы хотите каждый раз обновлять свое состояние с помощью реквизита, вам придется использовать что-то вроде useEffect.





Для этого вам нужно использовать 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: он не будет вызываться дважды, потому что мы также проверили, изменился ли nameState, тогда должен выполняться только useEffect, см. второй аргумент useEffect
@DhavalPatel Изменение реквизита вызвало рендеринг, а setNameState() запускает другой. Мне кажется дважды.
Аргумент функции перехватчиков 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;
Были обсуждения инициализации состояния из реквизита в антишаблоне, но поскольку у нас есть хуки, я думаю, что-то изменилось. Но я не уверен. Что вы думаете?
@ImdadulHuqNaim Нет, все осталось прежним, изменился только способ их реализации. Это зависит от того, нужно ли вам получать состояние из реквизита или нет. Например, может быть случай, когда начальное значение ввода предоставляется реквизитами, но оно все равно изменяется локально и обновляется в родительском элементе, скажем, при отправке.
Но это снова правильно отображает компонент. Это было не так в getDerivedStateFromProps, когда мы синхронизируем свойства с состоянием.
Почему вы используете props.name и props.age и т. д. в возврате вместо nameState.name и nameState.age и т. д.?
Такой подход выглядит великолепно, но разве этот компонент не рендерит дважды? Потому что мы используем setState() в useEffect. Разве это не удар по производительности?
Неправильный ответ, это приведет к расточительному, призрачному повторному рендерингу. useEffect здесь не используется. Самый простой способ сделать это: http://reactjs.org/docs/… В некоторых случаях применимы более продвинутые методы, например, кодирование хука useComputedState. Некоторые другие предложения: reactjs.org/blog/2018/06/07/… Это неприятный побочный продукт реактивных хуков.
@AlexG То, что вы предложили, также устанавливает состояние и вызывает повторную визуализацию. В документах указано: React will re-run the component with updated state immediately after exiting the first render so it wouldn’t be expensive..
@ supra28 Да, но на самом деле это не коснется DOM, как в этом ответе.
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]); вернуть [значение, установить значение]; }
Значение 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
getDerivedStateFromPropshas 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>Но хорошо ли это, если мы получаем совершенно новый объект в качестве реквизита и нам нужно синхронизировать его с состоянием. Как мы можем сделать глубокий поиск внутри объекта, чтобы проверить, что изменилось, а что нет?
Не уверен, что вы пытаетесь сделать. Хотя вам придется написать логику глубокого сравнения вручную, как в приведенном выше коде: if (props.name.first !== nameState.first). Или используйте помощник для глубокого сравнения
Жаль, что я не увидел этот ответ утром... Я только что потратил целый день на эту проблему.
Я считаю, что проблема указывает на попытку использовать одну переменную концептуальный или набор переменных для выполнения двух разных задач. Например, попытка заставить props.name и name делать то же самое.
Так что если
const [name, setName] = useState(props.name)
недостаточно, и вы обнаружите, что пытаетесь принудительно перевести props.name в переменную состояния name позже в функции, тогда, возможно, name перегружен. Попробуйте настроить другую переменную состояния, например. updatedName и посмотрите, будет ли работать лучше.
Исходный пример не демонстрирует эту проблему, поскольку переменные состояния никогда не используются, кроме как в операторах журнала.
Если бы const [name, setName] = useState(props.name) обновлялся при каждом повторном рендеринге, не было бы смысла иметь переменную состояния name, поскольку она всегда была бы такой же, как props.name (и дальнейшие попытки ее изменить вызовут повторный рендеринг).
Я считаю, что мой ответ полезен, особенно тем, что он указывает на то, что в вопросе нет варианта использования, поскольку переменные состояния используются только при ведении журнала. Я хотел бы знать, почему за это проголосовали.
Я придумал альтернативное решение, избегающее 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
Можете ли вы добавить код в
setNameState.