请问,为什么没有 Transition ?

import { useEffect, useLayoutEffect, useState } from "react";
import "./styles.css";

export default function App() {
  const [color, setColor] = useState("red");

  useLayoutEffect(() => {
    setColor("green");
  }, []);

  useEffect(() => {
    setColor("blue");
  }, []);

  return (
    <div className="App">
      <h1
        style={{
          color,
          transition: "color 300ms ease-in-out"
        }}
      >
        Hello World
      </h1>
    </div>
  );
}

如题,这段代码为什么没有transition?

阅读 2k
1 个回答
注意以下所有代码都是基于react 17.0.2,由于不同版本之间代码有所区别,请确保你使用的是该版本以便于找到代码出处

首次渲染时在commit阶段useLayoutEffect的create函数(也就是他的第一个参数)会被同步执行,由于其中使用色setColor,他会重新调度一次performSyncWorkOnRoot被调度的performSyncWorkOnRoot会在该轮更新完毕后被执行,同时在commit阶段中useEffect的create函数会被当作一个宏任务调度,你可以理解为useEffect的create函数会被setTimeout延时调用
该轮更新完毕后,开始执行前面被调度的performSyncWorkOnRoot开始新一轮的更新,在开始执行的过程中发现前一轮更新还有遗留的useEffect需要执行,在这里就不会等setTimeout(实际上使用的是postMessage延时调用的)去执行他了,而是把他同步执行了,由于在useEffect中使用了setColor("blue")所以又会重新调度一次performSyncWorkOnRoot,但是这一次调度有所不同,因为已经存在一个useLayoutEffect调度的任务而且他们的优先级还是一样的,所以不会再重复调度了,而是复用之前的任务,而由于每次setColor时都会将update加到更新链表上,
所以此时的更新链表就为 green ---> blue待会在updateReducer中他们会被reduce成blue所以虽然执行了两次setColor,但是到最后只设置了blue颜色,我们在组件里打印一下color就能清除的看到这个过程

export default function App() {
  const [color, setColor] = useState('red')

  useLayoutEffect(() => {
    setColor('green')
  }, [])

  useEffect(() => {
    setColor('blue')
  }, [])

  console.log(color)

  ...
}

打印结果

red //首次渲染时的初始state
blue //green ---> blue reduce而来

知道了原理之后要想出现transition效果就很简单了,只要上useEffect中调度的任务不复用useLayoutEffect调度的任务就行了所以我们可以把代码改成这样,就能出现transition效果了

export default function App() {
  const [color, setColor] = useState('red')

  useLayoutEffect(() => {
    setColor('green')
  }, [])

  useEffect(() => {
    setTimeout(() => {
      setColor('blue')
    })
  }, [])

  console.log(color)

  return (
    <div className="App">
      <h1
        style={{
          color,
          transition: 'color 1000ms ease-in-out',
        }}
      >
        Hello World
      </h1>
    </div>
  )
}

此时的打印结果

red
green
blue //没有复用前一个任务,green和blue都会单独产生一轮更新
推荐问题