Обеспечить входной переход css с помощью компонента React при рендеринге

#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));
  }