Я пытаюсь понять новый useEffect «крюк» React, используя его для управления модальным входом и выходом из DOM с некоторой анимацией.
Вот рабочий код:
https://codepen.io/nicholasstephan/pen/roprgw
и упрощенная версия:
const isOpen = props.isOpen;
const [ animState, setAnimState ] = useState(null);
useEffect(() => {
if (isOpen === true && animState === null) {
setAnimState('entering');
}
if (isOpen === true && animState === 'entering') {
setAnimState('entered');
}
}, [isOpen, animState]);
if (animState === null) {
return null;
}
return (
<div
style = {{
opacity: animState === 'entered' ? 1 : 0,
transition: 'opacity 200ms ease-in-out'
}}>
...
</div>
);
Я думаю:
При первоначальном рендеринге animState - это null, поэтому мы возвращаем null.
Если для isOpen установлено значение true, эффект срабатывает и выполняется первое условие, устанавливающее для animState значение «вход». В этом состоянии компонент должен вернуть некий DOM с непрозрачностью 0.
Эффект срабатывает снова, и второе условие выполняется, устанавливая для animState значение «введено». Создание модели DOM с непрозрачностью 1, анимированной с переходом css.
Проблема, насколько я могу судить, в том, что DOM добавляется с непрозрачностью 1, как будто React выполняет пакетную обработку нескольких вызовов рендеринга и обновляет DOM только один раз. Если вы посмотрите код, я регистрирую animState при каждом рендеринге, и "закрытое" состояние регистрируется, но, похоже, это не рендеринг.
Может ли это быть так? Как мне убедиться, что рендеринг произошел, прежде чем снова запустить эффект?
Мне было бы интересно услышать, есть ли лучший способ сделать что-то вроде этого? Но мне также любопытно, что React делает здесь за кулисами и почему я не получаю анимацию входа.
@RyanCogswell Я добавил код, демонстрирующий проблему.



![Безумие обратных вызовов в javascript [JS]](https://i.imgur.com/WsjO6zJb.png)


Это кажется слишком сложным для анимации непрозрачности, почему бы не сохранить непрозрачность в переменной состояния, например:
const isOpen = props.isOpen;
const [ opacity, setOpacity ] = useState(0);
useEffect(() => {
setOpacity(isOpen ? 1 : 0);
}, [isOpen]);
return (
<div
style = {{
opacity,
transition: 'opacity 200ms ease-in-out'
}}>
...
</div>
);
Вы также можете попробовать ловушку useRef для обновления прозрачности вручную.
const isOpen = props.isOpen;
const divEl = useRef(null);
useEffect(() => {
if (divEl) {
divEl.current.style.opacity = 1;
}
}, [divEl]);
if (!isOpen) {
return null;
}
return (
<div
ref = {divEl}
style = {{
opacity: 0,
transition: 'opacity 200ms ease-in-out'
}}>
...
</div>
);
Да, я ограничился прозрачностью, чтобы упростить пример. Разница также в том, что в вашем примере DOM всегда отображается, но я хочу, чтобы он возвращал null, пока isOpen не станет истинным.
Это работает, но не обрабатывает выход из анимации. Как только для isOpen установлено значение false, DOM немедленно удаляется. Я не показал этого, поскольку эта часть моего теста действительно работает нормально, но я обновил код, чтобы включить ее.
Посмотреть реф с useEffect - интересная идея. Я не думал об этом. Но не будет ли установка непрозрачности императивно означать, что div будет повторно отрисован с непрозрачностью 0, если изменится какое-либо другое свойство?
вы правы, вы можете использовать переменную состояния для хранения непрозрачности и использовать setOpacity(1) вместо divEl.current.style.opacity = 1;
Я обновил свой первый пример, чтобы обработать выход из анимации, когда isOpen имеет значение false.
Я добавил код, чтобы лучше показать, что происходит. То, что я пытаюсь сделать, в значительной степени представляет собой комбинацию ваших двух примеров, для этого необходимо: 1) отобразить null, если нечего отображать, и 2) поддержать анимацию входа и выхода. Но более того, мне любопытно, что делает реакция. Кажется, что цикл рендеринга пропускается, и я не знаю почему.
Я считаю, что React делает именно то, что вы ожидаете. Это браузер, который ведет себя несколько неожиданно.
От Использование переходов CSS:
Care should be taken when using a transition immediately after:
- adding the element to the DOM using .appendChild()
- removing an element's display: none; property.
This is treated as if the initial state had never occurred and the element was always in its final state. The easy way to overcome this limitation is to apply a window.setTimeout() of a handful of milliseconds before changing the CSS property you intend to transition to.
Если я изменю:
if (status === true && animState === 'closed') {
setAnimState('open');
}
к:
if (status === true && animState === 'closed') {
setTimeout(()=>setAnimState('open'), 25);
}
вроде нормально работает. Для меня НЕТ работал, если я использовал тайм-аут менее 8 миллисекунд, и особенности этого поведения, вероятно, варьируются от браузера к браузеру.
Вот измененный код: https://codepen.io/anon/pen/KbQoZO?editors=0010
Будет полезно, если вы сможете поделиться примером в CodeSandbox (или аналогичном), который демонстрирует проблему. Это значительно упростит проверку потенциальных решений.