带有 useEffect 的 ReactJS 不呈现每个状态

ReactJS with useEffect not rendering each state

我正在尝试通过使用 React 的新 useEffecthook”来控制模态进入和退出带有一些动画的 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. 在初始渲染中,animStatenull 所以我们 return null

  2. 当 isOpen 设置为 true 时,将触发效果并满足第一个条件,将 animState 设置为 'entering'。在这种状态下,组件应该 return 一些 DOM,不透明度为 0

  3. 效果再次触发并满足第二个条件,将 animState 设置为 'entered'。使用 1 的不透明度创建 DOM,使用 css 过渡动画。

据我所知,问题在于添加的 DOM 的不透明度为 1,就好像 React 正在批处理多个渲染调用并且只更新 DOM一次。如果你查看 codepen,我会在每次渲染时记录 animState,并且 "closed" 状态正在记录,但它似乎没有在渲染。

难道是这样吗?我如何确保渲染在 运行 效果再次发生之前发生?

我很想知道是否有更好的方法来做这样的事情?但我也很好奇 React 在幕后做了什么,以及为什么我没有得到进入动画。

只为不透明度设置动画似乎有点太复杂,为什么不将不透明度存储在这样的状态变量中:

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

我相信 React 正在做你所期望的。浏览器的行为有点令人惊讶。

来自Using CSS transitions

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 毫秒,它确实 NOT 起作用,并且这种行为的具体情况可能因浏览器而异。

这是修改后的代码笔:https://codepen.io/anon/pen/KbQoZO?editors=0010