Говорят, что этот хук может имитировать три жизненных цикла компонента класса.
предисловие
Официальный сайт уже представил его, поэтому я повторю его здесь.useEffectЭто хук, используемый для выполнения побочных эффектов. Первый параметр передается в функцию. После каждого рендеринга выполняется побочный эффект, а последний побочный эффект очищается. Возвращаемое значение функции — функция очистки. Второй параметр — это массив, который передается в зависимостях, необходимых для выполнения функции побочного эффекта.Когда одну из этих зависимостей необходимо обновить, новый побочный эффект будет регенерирован в эффекте, и побочный эффект будет выполнен. Если обновления нет, оно не будет выполнено. Если второй параметр не передан, то он не указывает, есть ли у него какие-либо зависимости, то есть функциональный компонент выполняется каждый раз при выполнении рендера.
Это понятно,useEffectПервый параметр может имитироватьdidmount,didupdate, его возвращаемое значение может имитироватьwillunmount
моделирование жизненного цикла компонентов класса
"Имитируя жизненный цикл, второй параметр useEffect передает пустой массив, который не имеет зависимостей и выполняется только один раз, что эквивалентно didmount. Если вы хотите выделить жизненный цикл, не передавая второй параметр, он будет выполняться каждый раз, что эквивалентно didupdate. Добавьте отметку на монтирование и используйте его, чтобы судить о нем, что позволяет добиться эффекта имитации жизненного цикла"
Многие люди подумают об этом методе моделирования, поэтому давайте попробуем его:
let mount;
function useForceUpdate() {
const [_, forceUpdate] = useState(0);
return () => forceUpdate(x => x + 1);
}
function UnmountTest() {
useEffect(() => {
if (!mount) {
mount = true;
console.log('did mount')
} else {
console.log('did update')
}
return () => {
mount = false;
console.log('unmount')
}
})
const forceUpdate = useForceUpdate();
return (<div>
我是随时被抛弃的
<button onClick={forceUpdate}>强制更新</button>
</div>);
}
function State() {
const [count, setCount] = useState(20);
const handleCount = useCallback(() => {
setCount(count => count + 1)
}, [])
return (
<div>
{count}
<button onClick={handleCount}>count+1</button>
{(count % 2) && <UnmountTest />}
</div>
)
}
Если количество нечетное, то отображатьUnmountTest, компонент также имеет метод для обновления компонента. Согласно логике,useEffectНе передавайте второй параметр, чтобы обеспечить выполнение каждого рендеринга. Затем добавьте метку, чтобы отметить первое крепление. Так что запустите волну, чтобы увидеть
- Нажмите count+1, отобразите компонент, напечатайте didmount
- Снова щелкните количество, удалите компонент и распечатайте размонтирование.
Как и ожидалось, 😊
- Нажмите count+1, отобразите компонент, напечатайте didmount
- Нажмите, чтобы принудительно обновить, распечатайте размонтирование, размонтируйте, нажмите еще раз, все то же самое
🤔️, какого черта, не оправдал ожиданий
useEffect используется для выполнения побочных эффектов. Каждый раз, когда вы выполняете рендеринг, он очищает предыдущий побочный эффект и выполняет этот побочный эффект (если есть зависимость или не передан массив зависимостей). Этот хук основан на побочном эффекте и Конечно, его можно использовать несколько раз.
Таким образом, каждый раз происходит размонтирование и размонтирование, что действительно соответствует этой логике, которая немного отличается от жизненного цикла симуляции «принять как должное». Таким образом, мы можем решить проблему, разделив ее на два вызова useEffect:
function UnmountTest() {
useEffect(() => {
if (mount) {
console.log('did update')
}
});
useEffect(() => {
if (!mount) {
console.log('did mount')
mount = true;
}
return () => {
console.log('unmount')
mount = false;
}
}, []);
const forceUpdate = useForceUpdate();
return (<div>
我是随时被抛弃的
<button onClick={forceUpdate}>强制更新</button>
</div>);
}
В этот раз все соответствует ожиданиям, просто ojbk😊
Разница между useEffect и useLayoutEffect
useEffect является асинхронным, useLayoutEffect является синхронным
Давайте посмотрим на время, когда компонент происходит от монтирования до повторного рендеринга:
Временная шкала представлена слева направо, красный — асинхронный, красный прямоугольник — синхронный, а выполнение — сверху вниз.useEffectЯвляется асинхронным, так называемый асинхронный использоватьrequestIdleCallback, который выполняет входящий обратный вызов, когда браузер бездействует. В большинстве случаев один и тот же используется, если побочный эффект относительно длинный, например, большое количество вычислений, если онuseLayoutEffectЭто приведет к блокировке рендеринга. Это просто случай, мы можем посмотреть на этот волшебный таймер:
Нажмите Начните, чтобы начать время, нажмите Паузу, чтобы сделать паузу. Нажмите, чтобы очистить до 0, пауза и очистить номер
function LYE() {
const [lapse, setLapse] = React.useState(0)
const [running, setRunning] = React.useState(false)
useEffect(
() => {
if (running) {
const startTime = Date.now() - lapse
const intervalId = setInterval(() => {
setLapse(Date.now() - startTime)
}, 2)
console.log(intervalId)
return () => clearInterval(intervalId)
}
},
[running],
)
function handleRunClick() {
setRunning(r => !r)
}
function handleClearClick() {
setRunning(false)
setLapse(0)
}
return (
<div>
<label>{lapse}ms</label>
<button onClick={handleRunClick}>
{running ? '暂停' : '开始'}
</button>
<button onClick={handleClearClick}>
暂停并清0
</button>
</div>
)
}
Итак, нажатие на очистку не сбрасывает 0, а останавливает, а нажатие на запуск также продолжает запускаться. Просто измените его наuseLayoutEffectВот и все, нажмите на 0, чтобы сразу превратиться в 0 и остановиться. Кроме того, при использованииuseEffectЗатем измените время интервала, чтобы оно было больше 16, есть вероятность, что оно будет успешно очищено до 0, а если оно больше, оно будет очищено полностью. сказатьuseEffectявляется асинхронным, то проблема, скорее всего, возникнет здесь в асинхронном режиме.
useLayoutEffectЭто синхронно, поэтому весь процесс именно такой, как мы ожидали, и все под контролем. На основании двух пунктов:useEffectЗадержка интервала внутри слишком мала и не очищает результат синхронизации,useEffectРешить проблему можно после настройки интервала задержки больше 16. Начнем с этих двух пунктов и разберемсяuseEffectВремя исполнения:
При этом результат таймера не очищается, обратите внимание на средний кусок: interval1="render="clean useEffect1. Перед очисткой useEffect1 снова запустите interval1, и interval1 запускает рендеринг, показывая текущий результат синхронизации. Предыдущая операция остановки,setRunning(false)а такжеsetLapse(0)Он запустился, но interval1 устанавливает текущий результат синхронизации, поэтомуsetLapse(0)Это просто ничего.
Увеличьте интервальную задержку
Это нормально, и, очевидно, все в пределах наших ожиданий. После многих тестов критическая точка задержки составляет 16 мс.
Почему 16 мс?
Есть проблема, естественно думать об асинхронии, и когда дело доходит до асинхронности, я снова думаю об этом.requestIdleCallback, эта функция предназначена для выполнения обратного вызова, когда браузер бездействует. похожий наrequestAnimationFrame,ТолькоrequestIdleCallbackПонизьте приоритет. Говоря оrequestAnimationFrameЯ думал о средней скорости 60 кадров в секунду, а затем 1000/60 — это 16,66666, поэтому интервал между каждым кадром составляет около 16 мс. Наконец, таким образом выявляется источник проблемы.Когда интервал интервала больше, чем экранное время одного кадра, используйтеuseEffectС этим таймером проблем не будет, иначе интервал будет выполняться еще раз перед useEffect, что вызовет проблемы.
Если статья была вам полезна,github , НаггетсВы можете следовать за волной