Это мой компонент:
function Test() {
const [data, setData]=useState<Array<string>>([]);
console.info('Hello:', data);
useEffect(()=>{
console.info('data: ', data)
}, [data])
const test1 = () => {
setData(data.concat('a'));
}
const test2 = () => {
setData(data);
}
return (
<>
<button onClick = {test1}>Button one</button>
<button onClick = {test2}>Button two</button>
</>
);
}
Все работает нормально при нажатии Button one. Компонент выполняет повторный рендеринг, и эффект запускается. Однако то, что происходит с Button two, я не могу объяснить:
Button two
сразу после Button one
, компонент перерендерится, но эффект не запустится. Это не имеет смысла, поскольку React использует сравнение Object.is
для принятия решения о том, следует ли повторно отображать/запускать эффект. Как это сравнение дает разные результаты среди useState
и useEffect
? Сначала он решает перерендериться, что означает, что значение состояния data
изменилось. Как это верно с setData(data)
? Затем он решает не запускать эффект, что означает отсутствие изменений в зависимости data
. Очевидно, что между двумя вышеуказанными решениями существует противоречие...Button two
во второй раз (после нажатия Button one
), то ничего не произойдет, что абсолютно понятно.Может ли кто-нибудь объяснить вышеуказанное поведение?
Если новое состояние совпадает с текущим состоянием, как вы упомянули Object.is, React пропустит повторный рендеринг, но, как указано в документах React useState, React все еще может вызывать компонент.
Хотя в некоторых случаях React все еще может потребоваться вызвать ваш компонент, прежде чем пропустить дочерние элементы, это не должно повлиять на ваш код.
Это приводит к запуску console.info со значениями "Hello: ", data.
Итак, React на самом деле не перерисовывает компонент.
Мы можем видеть это с useEffect без массива зависимостей, который, согласно документам React useEffect, должен запускаться при каждом повторном рендеринге.
Эффект будет повторно запускаться после каждого повторного рендеринга компонента.
useEffect(() => {
console.warn("Render");
});
Как видите, это не так.
const {useState, useEffect, Fragment} = React;
function Test() {
const [data, setData] = useState([]);
console.info("Hello:", data);
useEffect(() => {
console.warn("Render");
});
useEffect(() => {
console.info("data: ", data);
}, [data]);
const test1 = () => {
setData(data.concat("a"));
};
const test2 = () => {
setData(data);
};
return (
<Fragment>
<button onClick = {test1}>Button one</button>
<button onClick = {test2}>Button two</button>
</Fragment>
);
}
ReactDOM.createRoot(
document.getElementById("root")
).render(
<Test />
);
<div id = "root"></div>
<script src = "https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.development.js"></script>
<script src = "https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.development.js"></script>
Что произошло, так это когда вы используете setData(data); повторное рендеринг компонента независимо от того, действительно ли данные изменились или нет, но когда дело доходит до useEffect способа сравнения значений, он берет старые значения и сравнивает их с новыми, если значения изменились. изменился, он выполнит то, что внутри него, иначе этого не произойдет, это поведение также произойдет, если данные являются объектом, он проверит каждое значение внутри него, чтобы решить, изменился ли объект или нет