#reactjs #react-hooks #css-transitions
#reactjs #реагирующие хуки #css-переходы
Вопрос:
Я пытаюсь создать оболочку простого перехода css в React, где логическое свойство управляет классом HTML, который переключает свойства css, для которых установлено значение transition. Для рассматриваемого варианта использования мы также хотим, чтобы компонент был размонтирован ( return null
) перед переходом на входе и после перехода на выходе.
Для этого я использую две логические переменные состояния: одну, которая управляет монтированием, и другую, которая управляет классом HTML. Когда props.in
переходит от false
к true
, я устанавливаю mounted
значение true
. Теперь хитрость: если для класса немедленно установлено "in"
значение при первом рендеринге, переход не происходит. Нам нужно, чтобы компонент сначала отображался с помощью class "out"
, а затем менял класс на "in"
.
A setTimeout
работает, но довольно произвольно и не привязано строго к жизненному циклу React. Я обнаружил, что даже тайм-аут в 10 мс иногда может не дать эффекта. Это дерьмо.
Я думал, что использование useEffect
with mounted
в качестве зависимости будет работать, потому что компонент будет отображаться, и эффект произойдет после:
useEffect(if (mounted) { () => setClass("in"); }, [mounted]);
(смотрите Полный код в контексте ниже)
но это не приводит к переходу. Я полагаю, это связано с тем, что React выполняет пакетные операции и выбирает, когда выполнять рендеринг в реальном DOM, и в большинстве случаев не делает этого до тех пор, пока не произойдет эффект.
Как я могу гарантировать, что значение моего класса изменится только после, но сразу после того, как компонент будет отображаться после mounted
того, как будет установлено значение true
?
Упрощенный компонент React:
function Transition(props) {
const [inStyle, setInStyle] = useState(props.in);
const [mounted, setMounted] = useState(props.in);
function transitionAfterMount() {
// // This can work if React happens to render after mounted get set but before
// // the effect; but this is inconsistent. How to wait until after render?
setInStyle(true);
// // this works, but is arbitrary, pits UI delay against robustness, and is not
// // tied to the React lifecycle
// setTimeout(() => setInStyle(true), 35);
}
function unmountAfterTransition() {
setTimeout(() => setMounted(false), props.duration);
}
// mount on props.in, or start exit transition on !props.in
useEffect(() => {
props.in ? setMounted(true) : setInStyle(false);
}, [props.in]);
// initiate transition after mount
useEffect(() => {
if (mounted) { transitionAfterMount(); }
}, [mounted]);
// unmount after transition
useEffect(() => {
if (!props.in) { unmountAfterTransition(); }
}, [props.in]);
if (!mounted) { return false; }
return (
<div className={"transition " inStyle ? "in" : "out"}>
{props.children}
</div>
)
}
Примеры стилей:
.in: {
opacity: 1;
}
.out: {
opacity: 0;
}
.transition {
transition-property: opacity;
transition-duration: 1s;
}
И использование
function Main() {
const [show, setShow] = useState(false);
return (
<>
<div onClick={() => setShow(!show)}>Toggle</div>
<Transition in={show} duration={1000}>
Hello, world.
</Transition>
<div>This helps us see when the above component is unmounted</div>
</>
);
}
Ответ №1:
Найдено решение, выходящее за рамки React. Использование window.requestAnimationFrame
позволяет выполнить действие после следующего рисования DOM.
function transitionAfterMount() {
// hack: setTimeout(() => setInStyle(true), 35);
// not hack:
window.requestAnimationFrame(() => setInStyle(true));
}