Я хочу наблюдать, когда размер окна перестанет изменяться, потому что в настоящее время, если я слушаю событие resize
в window
, я могу только слушать изменение размера change
, но не знаю, когда изменение размера прекращается. Я проверил параметр event
в обратном вызове изменения размера, но не нашел полезной информации.
Ниже приведен код, который я пытаюсь выполнить:
import React, { useState, useEffect } from "react";
export default function App() {
const [windowResizing, setWindowResizing] = useState(false);
const handleWindowResize = e => {
setWindowResizing(true);
};
useEffect(() => {
window.addEventListener("resize", handleWindowResize);
return () => window.removeEventListener("resize", handleWindowResize);
}, []);
return <div>{JSON.stringify({ windowResizing })}</div>;
}
Но это не работает, так как windowResizing
будет оставаться true
после начала изменения размера, даже если я уже перестал изменять размер.
Итак, есть ли способ наблюдать, когда размер окна перестает изменяться, и я могу затем вызвать setState
на основе этого?
В браузере нет реального «состояния изменения размера»; происходит событие resize
, а затем оно заканчивается.
Вы можете эмулировать собственное состояние изменения размера, установив для переменной состояния значение true
для каждого события resize
и запустив новый тайм-аут, который сбросит его обратно на false
через небольшой промежуток времени.
Пример
import React, { useState, useEffect } from "react";
export default function App() {
const [windowResizing, setWindowResizing] = useState(false);
useEffect(() => {
let timeout;
const handleResize = () => {
clearTimeout(timeout);
setWindowResizing(true);
timeout = setTimeout(() => {
setWindowResizing(false);
}, 200);
}
window.addEventListener("resize", handleResize);
return () => window.removeEventListener("resize", handleResize);
}, []);
return <div>{JSON.stringify({ windowResizing })}</div>;
}
@Лимбур Отлично! На самом деле это просто вопрос предпочтений. Поскольку переменная тайм-аута была нужна только в эффекте, я поместил ее как обычную переменную области видимости. Если бы вы хотели иметь возможность очистить тайм-аут, например. обработчик событий, ref был бы более подходящим.
Спасибо за ваше подробное объяснение. Последний вопрос: я вижу, вы вставили handleResize()
внутрь useEffect
, есть ли какая-то особая причина для этого? Или это просто вопрос предпочтения кодирования? Обычно я предпочитаю помещать функцию или метод независимо друг от друга внутри компонента, чтобы иметь возможность ссылаться на него в других местах.
@Limboer Это тоже в основном предпочтение. Поскольку мы передаем пустой массив в качестве второго аргумента эффекта, он будет запущен только один раз после первоначального рендеринга. Поместив handleResize
в эффект, становится ясно, что одна и та же функция будет использоваться для addEventListener
и removeEventListener
. Та же функция будет использоваться, даже если мы поместим ее вне эффекта, но это не так очевидно. Кратко это обсуждается в FAQ в документации, поэтому я и поставил функции в эффект.
Небольшое обновление к ответу Толле
чтобы включить размер и состояние изменения размера
import { useState, useLayoutEffect } from 'react'
// https://stackoverflow.com/a/63010184
function debounce(fn, ms) {
let timer;
return _ => {
clearTimeout(timer);
timer = setTimeout(_ => {
timer = null;
fn.apply(this, arguments);
}, ms);
};
}
const useWindowSize = () => {
const [size, setSize] = useState([0, 0, false]);
useLayoutEffect(() => {
let timeout;
clearTimeout(timeout);
function updateSize() {
clearTimeout(timeout);
setSize([window.innerWidth, window.innerHeight, true]);
timeout = setTimeout(() => {
setSize([window.innerWidth, window.innerHeight, false]);
}, 800);
}
const debouncedResizeHandler = debounce(() => updateSize())
window.addEventListener('resize', debouncedResizeHandler);
setSize([window.innerWidth, window.innerHeight, false]);
return () => window.removeEventListener('resize', debouncedResizeHandler);
}, []);
return size;
}
export default useWindowSize
Ура, это именно то, что я хочу, однако у меня есть один небольшой вопрос: в чем разница между созданием
timeout
в качестве ссылки и просто помещением его в качестве локальной переменной вuseEffect
?