ReactJS с useEffect не отображает каждое состояние

Я пытаюсь понять новый 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>
);

Я думаю:

  1. При первоначальном рендеринге animState - это null, поэтому мы возвращаем null.

  2. Если для isOpen установлено значение true, эффект срабатывает и выполняется первое условие, устанавливающее для animState значение «вход». В этом состоянии компонент должен вернуть некий DOM с непрозрачностью 0.

  3. Эффект срабатывает снова, и второе условие выполняется, устанавливая для animState значение «введено». Создание модели DOM с непрозрачностью 1, анимированной с переходом css.

Проблема, насколько я могу судить, в том, что DOM добавляется с непрозрачностью 1, как будто React выполняет пакетную обработку нескольких вызовов рендеринга и обновляет DOM только один раз. Если вы посмотрите код, я регистрирую animState при каждом рендеринге, и "закрытое" состояние регистрируется, но, похоже, это не рендеринг.

Может ли это быть так? Как мне убедиться, что рендеринг произошел, прежде чем снова запустить эффект?

Мне было бы интересно услышать, есть ли лучший способ сделать что-то вроде этого? Но мне также любопытно, что React делает здесь за кулисами и почему я не получаю анимацию входа.

Будет полезно, если вы сможете поделиться примером в CodeSandbox (или аналогичном), который демонстрирует проблему. Это значительно упростит проверку потенциальных решений.

Ryan Cogswell 02.01.2019 01:40

@RyanCogswell Я добавил код, демонстрирующий проблему.

nicholas 03.01.2019 23:21
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
0
2
1 280
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Это кажется слишком сложным для анимации непрозрачности, почему бы не сохранить непрозрачность в переменной состояния, например:

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 не станет истинным.

nicholas 02.01.2019 01:17

Это работает, но не обрабатывает выход из анимации. Как только для isOpen установлено значение false, DOM немедленно удаляется. Я не показал этого, поскольку эта часть моего теста действительно работает нормально, но я обновил код, чтобы включить ее.

nicholas 02.01.2019 19:15

Посмотреть реф с useEffect - интересная идея. Я не думал об этом. Но не будет ли установка непрозрачности императивно означать, что div будет повторно отрисован с непрозрачностью 0, если изменится какое-либо другое свойство?

nicholas 02.01.2019 19:17

вы правы, вы можете использовать переменную состояния для хранения непрозрачности и использовать setOpacity(1) вместо divEl.current.style.opacity = 1;

Olivier Boissé 02.01.2019 19:21

Я обновил свой первый пример, чтобы обработать выход из анимации, когда isOpen имеет значение false.

Olivier Boissé 02.01.2019 19:26

Я добавил код, чтобы лучше показать, что происходит. То, что я пытаюсь сделать, в значительной степени представляет собой комбинацию ваших двух примеров, для этого необходимо: 1) отобразить null, если нечего отображать, и 2) поддержать анимацию входа и выхода. Но более того, мне любопытно, что делает реакция. Кажется, что цикл рендеринга пропускается, и я не знаю почему.

nicholas 02.01.2019 22:33
Ответ принят как подходящий

Я считаю, что 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

Другие вопросы по теме