Я читал, что useEffect
является асинхронным, поэтому React сначала рендерит и монтирует страницу и только после обратного вызова в useEffect срабатывает, поэтому иногда мы видим очень быстрое изменение значения в некоторых компонентах.
Но когда я попытался использовать самодельный «сон на 3 секунды» в обратном вызове useEffect, React ждет, пока не пройдет 3 секунды, и только после этого рендерит и монтирует страницу, поэтому я вижу пустую страницу в течение 3 секунд. Также мы можем заметить быстро меняющееся значение после задержки в 3 секунды. Я не понимаю, почему это происходит, так как обратный вызов useEffect должен запускаться после рендеринга и монтирования страницы.
Не могли бы вы объяснить, почему это происходит именно так?
Песочница кода:Связь
Код:
import { useEffect, useState } from "react";
import "./styles.css";
export default function App() {
const [state, setState] = useState(1200000000);
useEffect(() => {
let start = new Date().getTime();
let end = start;
while (end < start + 3000) {
end = new Date().getTime();
}
setState(3);
}, []);
return (
<div className = "App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<div>Value is : {state}</div>
</div>
);
}
I've read, that
useEffect
is asynchronous, so React firstly renders and mounts page and only after callback inuseEffect
fires, so sometimes we see value changing really fast in some components.
Верный. Это задокументировано здесь:
The function passed to useEffect will run after the render is committed to the screen.
и здесь
Timing of effects
Unlike
componentDidMount
andcomponentDidUpdate
, the function passed touseEffect
fires after layout and paint, during a deferred event.
(их ударение)
Продолжая свой вопрос:
But when I tried to use self-created "sleep-for-3-seconds" in useEffect callback, React waits until 3 seconds pass, and only after that renders and mounts page, so I see blank page for 3 seconds.
Странно, я не вижу такого поведения:
const { useState, useEffect} = React;
function App() {
const [state, setState] = useState(1200000000);
useEffect(() => {
let start = new Date().getTime();
let end = start;
while (end < start + 3000) {
end = new Date().getTime();
}
setState(3);
}, []);
return (
<div className = "App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<div>Value is : {state}</div>
</div>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
<div id = "root"></div>
<script src = "https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.development.js"></script>
<script src = "https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.development.js"></script>
Когда я запускаю это, я вижу страницу, отображаемую с начальным значением состояния 1200000000
, а через три секунды я вижу, что она обновляется с измененным значением состояния.
Я также не вижу этого в этом более простом примере:
const {useEffect} = React;
const Example = () => {
useEffect(() => {
const stop = Date.now() + 3000;
while (Date.now() < stop) {
// Wait (NEVER DO THIS IN REAL CODE)
}
console.info("Done waiting");
}, []);
return <div>x</div>;
};
ReactDOM.render(<Example />, document.getElementById("root"));
<div id = "root"></div>
<script src = "https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.development.js"></script>
<script src = "https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.development.js"></script>
Когда я запускаю это, я вижу x
, отображаемый компонентом, а через три секунды я вижу console.info
, что ожидание завершено.
Но, если вы не видите то, что описываете, это может быть просто потому, что у браузера не было возможности фактически отрисовать страницу до вызова useEffect
, несмотря на то, что код React пытается убедиться, что это так. И поскольку ваш код занят ожиданием, основной поток связан с этим циклом, и браузер не может обновить отображение страницы.
Но опять же, я не вижу этого с useEffect
(вы бы видели с useLayoutEffect
).
Спасибо за обстоятельный ответ! Я вижу, что в вашем фрагменте кода useEffect
работает прилично, но в песочнице (Ссылка на песочницу) он ждет 3 секунды и только после этого отрисовывает страницу. После некоторого обновления страницы (не локальной страницы, отображаемой с кодом, а всей страницы песочницы) она работает редко, как ожидалось, но в большинстве случаев useEffect
работает так, как я написал в вопросе :(
@MichaelLearner - Вероятно, это проблема CodeSandbox, там много движущихся частей, браузер может быть не в состоянии полностью обновить пользовательский интерфейс. :-)
То, что вы видите пустой экран, соответствует коду.
Поскольку фрагмент кода, который вы используете, является сценарием ожидания занятости и делает другие ваши коды javascript остановленными, а пользовательский интерфейс замороженным.
А это нехорошо с точки зрения пользовательского опыта и производительности.
let start = new Date().getTime();
let end = start;
while (end < start + 3000) {
end = new Date().getTime();
}
Таким образом, вы можете сделать это следующим образом:
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
useEffect(() => {
delay(3000).then(res => {
setState(3);
});
}, []);
На самом деле, согласно документация, React по крайней мере приложил усилия, чтобы позволить браузеру отображать компонент перед вызовом обратного вызова useEffect
.
Да, React визуализирует ваше тело, а затем запускает ваш useEffect
хук после того, как DOM готов, потому что ваш хук имеет зависимость от пустого массива []
. Но в этом хуке выполняется сценарий занятости-ожидания (блокировки). Во время этого цикла все другие пользовательские интерфейсы, которые должны отображаться после этого компонента, будут остановлены.
Я не уверен, что вы имеете в виду. С кодом OP они должны видеть нарисованную страницу, потому что React предположительно фиксирует и ждет отрисовки перед вызовом useEffect
(и, следовательно, перед ожиданием занятости). Я не могу воспроизвести то, что описывает OP, для меня он всегда отображает страницу (затем застревает в режиме ожидания). Хотя, по сути, в любом случае проблема заключается в напряженном ожидании.
ну, ваше «занятое ожидание» полностью блокирует браузер от каких-либо действий в течение 3 секунд, включая обновление DOM. Хотя
useEffect
запускается после рендеринга React, я думаю, ваш пример доказывает, что фактический DOM еще не обновлен. Не уверен, что это имеет значение, потому что (надеюсь!) вы явно никогда не поместите такой синхронный блокирующий код в реальное приложение!