从 “跳戏的文本框” 到无缝切换:useEffect 与 useLayoutEffect 的另一个故事📝

58 阅读3分钟

故事的开端:一个 “调皮” 的文本框😜

小王最近在做一个 React 项目,有个需求很简单:页面加载时,先显示一段英文文本,然后立刻切换成中文文本,同时把文本框的高度固定为 200px。他信心满满地写下了代码,用 useEffect 来处理这个切换逻辑:

function TextBox() {
  const [content, setContent] = useState("Once upon a time, true love stood before me...");
  const ref = useRef();

  useEffect(() => {
    // 切换文本并设置高度
    setContent('曾经有一份真诚的爱情放在我面前...');
    ref.current.style.height = '200px';
  }, [])

  return <div ref={ref} style={{ border: '1px solid #ccc', padding: '10px' }}>{content}</div>
}

可运行后,小王发现了问题:页面加载时,文本框先显示英文,高度跟着内容 “自由舒展”,过了一瞬间,才突然变成中文,高度也猛地缩成 200px。就像看电影时突然切了镜头,用户明显能感觉到 “跳戏”,体验很不流畅。

“明明是同一时间执行的操作,怎么会有延迟呢?” 小王挠了挠头。

揪出原因:两个 Hook 的 “工作节奏” 不同⏳

为了搞清楚问题,小王翻了 React 官方文档,终于明白了关键 ——useEffect 和 useLayoutEffect 的 “工作节奏” 完全不一样。

useEffect:“等渲染完再干活” 的拖延症患者

useEffect 就像个有拖延症的同事:浏览器先把组件 “画” 在页面上(用户已经能看到英文文本和自然高度了),它才慢悠悠地开始执行里面的代码。这时候再修改文本和高度,相当于让用户先看 “初稿”,再眼睁睁看着它改成 “终稿”,自然会觉得 “跳戏”。

useLayoutEffect:“渲染前必须搞定” 的急性子

而 useLayoutEffect 是个急性子,它会在浏览器 “动笔渲染” 之前就冲过来干活,而且不把事情做完绝不罢休(会阻塞渲染)。就像打印文件前,先在电脑里把内容改好、格式调好,再点 “打印”—— 用户拿到的直接是最终版,看不到修改过程。

问题解决:让文本框 “无缝切换”✨

小王恍然大悟,赶紧把 useEffect 换成了 useLayoutEffect

function TextBox() {
  const [content, setContent] = useState("Once upon a time, true love stood before me...");
  const ref = useRef();

  useLayoutEffect(() => {
    // 切换文本并设置高度(在渲染前完成)
    setContent('曾经有一份真诚的爱情放在我面前...');
    ref.current.style.height = '200px';
  }, [])

  return <div ref={ref} style={{ border: '1px solid #ccc', padding: '10px' }}>{content}</div>
}

这次运行后,奇迹发生了:页面加载时,文本框直接显示中文,高度稳稳地固定在 200px,没有一丝 “跳戏” 的痕迹。用户看到的就像 “一步到位” 的效果,完全不知道背后还藏着 “英文变中文” 的切换过程。

深入原理:为什么 useLayoutEffect 能 “无缝切换”?🔑

关键在于它的 “执行时机” 和 “阻塞性”:

  • 当组件初始化时,React 先准备好 DOM(比如创建文本框元素,初始内容是英文)

  • 此时 useLayoutEffect 立刻执行,在浏览器开始渲染前就完成了 “改中文、设高度” 的操作

  • 浏览器最终渲染到页面上的,是已经修改完毕的 “终稿”,用户自然看不到中间的切换过程

而 useEffect 是在渲染后执行,相当于让用户先看 “初稿”,再看 “终稿”,中间的差异就成了刺眼的 “跳戏”。

故事的结局:合适的工具,让体验更丝滑🌟

小王的文本框问题解决了,他也总结出了新的经验:

  • 当需要处理 “用户不能看到中间状态” 的 DOM 操作(比如文本切换、样式调整)时,用 useLayoutEffect,它能让变化 “悄无声息”
  • 当处理 “用户看不到的副作用”(比如数据请求、事件监听)时,用 useEffect 更合适,它不阻塞渲染,性能更好

就像故事里的文本框,当需要 “无缝切换” 时,useLayoutEffect 就是那个能 “藏起过程、只给结果” 的好帮手。而大多数时候,useEffect 已经能胜任啦~

最后,小王看着屏幕上 “一步到位” 的中文文本框,满意地笑了😊。