介绍🌟
随着 React 16.8 中 hooks 的引入,编写函数式组件的景观发生了巨大变化。 Among 这些,useEffect
和 useLayoutEffect
尤为重要,用于处理代码中的副作用。乍看之下,它们可能看起来很相似,但两者之间存在一些重要区别。在本博文中,我们将以清晰而直接的方式来看这些差异。我们将帮助您理解何时使用 useEffect
以及何时 useLayoutEffect
可能是 React 项目的更好选择。
让我们开始这个实用的探索,深入了解这两个必不可少的 React hooks。
useEffect 是什么?🔍
useEffect
是 React 的一个钩子,允许您在函数组件中执行副作用。副作用本质上是指影响执行函数范围之外的操作。这可能包括数据获取、订阅、手动更改 DOM 等。
记住,99% 的时候,
useEffect
是您想要使用的钩子。
**如何使用 useEffect:**🚀
当您使用 useEffect
时,您告诉 React 在渲染后做一些事情。React 会记住您传递的函数,并在执行 DOM 更新后调用它。
useEffect 的语法:
useEffect(() => {
// 您的副作用代码放在这里。
}, [dependencies]);
第二个参数 [dependencies]
是依赖关系数组。React 只会在依赖项之一发生变化时重新运行副作用。如果您传递一个空数组([]
),副作用会在初始渲染后只运行一次,这与 class 组件中的 componentDidMount
类似。
useLayoutEffect 是什么?🤔
useLayoutEffect
是与 useEffect
类似的另一个 React 钩子,但在其执行时机上有一个关键区别。 它用于需要与 DOM 同步的操作,比如直接操作 DOM 或在浏览器绘制前调整布局。
**何时使用 useLayoutEffect:**📐
如果您需要在屏幕更新前进行 DOM 更改,则应使用 useLayoutEffect
。 在您想要避免操作 DOM 后可能发生的视觉抖动(如闪烁)的情况下,这一点至关重要。
useLayoutEffect 的语法:
useLayoutEffect(() => {
// 与 DOM 交互的代码
}, [dependencies]);
与 useEffect
一样,它也将依赖关系数组作为第二个参数。
比较 useEffect 和 useLayoutEffect 的行为⚖️
在分别探讨了 useEffect
和 useLayoutEffect
之后,在同一个组件中看到它们的行动是很有帮助的。 这种比较将使我们更清楚地了解它们的执行顺序和行为。
让我们创建一个同时包含 useEffect
和 useLayoutEffect
的单个函数组件。 我们将在每个钩子中添加控制台日志,以观察它们执行的顺序。
import React, { useEffect, useLayoutEffect } from 'react';
const Home = () => {
useLayoutEffect(() => {
console.log('useLayoutEffect - 首先运行,但在 DOM 变更后');
}, []);
useEffect(() => {
console.log('useEffect - 其次运行,浏览器绘制后');
}, []);
return (
<div>Hello, React Hooks!</div>
);
};
export default Home;
在此组件中,我们同时具有依赖关系数组均为空的 useLayoutEffect
和 useEffect
,这意味着它们应该在初始渲染后运行。
useEffect 对比 useLayoutEffect
- useLayoutEffect: 此钩子首先运行。它在所有 DOM 变更完成后但浏览器有机会绘制之前触发。 这使得它非常适合需要在 DOM 更新后但用户看到任何内容之前发生的任何 DOM 操纵或计算。
useLayoutEffect
内的控制台日志将是首先出现的。 - useEffect: 此钩子在
useLayoutEffect
之后运行。它在组件渲染周期完成并且屏幕更新后触发。 这种行为意味着useEffect
最适合不需要立即同步更新 DOM 的任务,比如 API 调用或设置订阅。useEffect
内的控制台日志将出现在来自useLayoutEffect
的日志之后。
理论上,由于其异步性质(在浏览器绘制后执行),
useEffect
可能会导致闪烁效果,但在实践中,观察到明显的闪烁可能很困难。 从视觉上看,useEffect
和useLayoutEffect
的行为通常相似,尤其是对于简单的 DOM 更新。
实际例子:useEffect 和 useLayoutEffect 的行动💫
为了更好地掌握 useEffect
和 useLayoutEffect
的细微差别,让我们深入一些实际例子。 这些将说明每个 hook 如何在不同情况下被有效利用。
重要的是要注意,在许多情况下,尤其是在简单的场景或使用现代高性能浏览器的情况下,这些差异可能不会在视觉上明显。
1️⃣ useEffect 用于数据获取🌐📈
数据获取是 useEffect
的常见用例。 它允许您在组件渲染时从 API 请求和加载数据。
这是一个完整的 React 组件示例,它使用 useEffect
从 JSONPlaceholder
API(一个免费的假在线 REST API)获取数据:
import React, { useState, useEffect } from 'react';
const App = () => {
const [data, setData] = useState([]);
useEffect(() => {
const fetchData = async () => {
try {
const response = await fetch('https://jsonplaceholder.typicode.com/posts');
const jsonData = await response.json();
setData(jsonData);
} catch (error) {
console.error('Error fetching data: ', error);
}
};
fetchData();
}, []);
return (
<div>
<h1>从 JSONPlaceholder API 获取的数据</h1>
<ul>
{data.map(item => (
<li key={item.id}>{item.title}</li>
))}
</ul>
</div>
);
};
export default App;
该组件渲染从 API 获取的标题列表。 useEffect
中依赖关系列表中的空数组确保数据获取仅运行一次,类似于 class 组件中的 componentDidMount
。
2️⃣ useLayoutEffect 用于 DOM 操作:📜
让我们考虑一种情况,您想要在一些状态更改后立即将列表的滚动位置调整为顶部。 用 useEffect
做这件事可能会导致明显的闪烁,因为调整是在屏幕更新后发生的。 useLayoutEffect
通过确保调整在屏幕绘制之前进行来解决这个问题。
这是一个例子:
import React, { useState, useLayoutEffect, useRef } from 'react';
const App = () => {
const [items, setItems] = useState([]);
const listRef = useRef(null);
const addItems = () => {
const newItems = [...Array(5).keys()].map(i => `Item ${i + items.length}`);
setItems([...items, ...newItems]);
};
useLayoutEffect(() => {
// 在屏幕更新之前调整滚动位置
if (listRef.current) {
listRef.current.scrollTop = 0;
}
}, [items]); // 依赖于“items”状态
return (
<div>
<button onClick={addItems}>添加项目</button>
<ul ref={listRef}>
{items.map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
</div>
);
};
export default App;
在这个组件中:
- 我们使用
useState
创建一个items
状态来存储项目列表。 - 提供了一个按钮来向列表中添加新项目。
- 使用
useLayoutEffect
钩子在添加新项目时将列表滚动到顶部。这是在浏览器绘制更新的 UI 之前完成的,防止滚动位置的任何闪烁或跳转。 - 使用
ref
属性(listRef
)来引用列表 DOM 元素以操纵其滚动位置。
这个例子展示了如何使用 useLayoutEffect
在响应 React 组件中的状态更改时平滑地、无闪烁地调整 DOM。
结论✅
在 useEffect
和 useLayoutEffect
之间的选择通常取决于正在执行的 DOM 操作的具体要求。 尽管视觉差异不总是明显,但理解这些钩子的内部行为对于优化 React 应用程序的性能和确保顺畅的用户体验至关重要。
对于大多数副作用,特别是那些不需要与 DOM 立即交互的副作用(如数据获取或设置订阅),useEffect
是您的首选钩子。 其对状态和道具更改做出反应的能力使其对广泛的用例非常通用。
另一方面,当您需要根据 DOM 更改进行同步更新时,useLayoutEffect
是必不可少的。 当操作 DOM 时,它是防止视觉故障并确保顺畅用户体验的完美工具。
编码快乐!