React 中数据获取 Hook 的比较

React Hooks 为在 React 组件中管理副作用提供了高效的方法。 其中,useEffectuseLayoutEffectuseEffectEvent 是最常用的三个 Hook,它们各自具有独特的应用场景。 因此,根据具体任务选择合适的 Hook 至关重要。

useEffect Hook

useEffect Hook 是 React 中一个基础的 Hook,允许你在函数组件中执行诸如 DOM 操作、异步调用和数据获取等副作用。 这个 Hook 接收两个参数:一个效果函数和一个依赖项数组。

效果函数包含了执行副作用的代码,而依赖项数组则决定了效果函数何时运行。 如果依赖项数组为空,效果函数仅在组件初次渲染时执行一次。 反之,只要依赖数组中的任何值发生变化,效果函数就会重新执行。

以下是一个使用 useEffect Hook 获取数据的示例:

 import React from "react";

function App() {
const [data, setData] = React.useState([]);

React.useEffect(() => {
fetch("<https://jsonplaceholder.typicode.com/posts>")
.then((response) => response.json())
.then((data) => setData(data));
}, []);

return (
<div className="app">
{data.map((item) => (
<div key={item.id}>{item.title}</div>
))}
</div>
);
}

export default App;

上述代码展示了一个使用 useEffect Hook 从外部 API 获取数据的 App 组件。 useEffect 的效果函数会从 JSONPlaceholder API 获取示例数据,解析 JSON 响应并将检索到的数据存储到 data 状态中。

随后,App 组件利用 data 状态,渲染状态中每个项目的标题属性。

useEffect Hook 的特点

  • 异步友好:天然支持异步操作,方便进行数据获取。
  • 渲染后执行:useEffect Hook 在组件渲染完成后执行其效果,确保不会阻塞 UI。
  • 清理机制:通过返回一个函数来执行清理操作,特别适用于处理监听器或订阅。

useLayoutEffect Hook

useLayoutEffect Hook 与 useEffect Hook 类似,但它会在所有 DOM 更改后同步执行。 这意味着它会在浏览器绘制屏幕之前运行,因此非常适合需要精确控制 DOM 布局和样式的场景,例如测量元素尺寸、调整元素大小或实现动画效果。

以下是一个使用 useLayoutEffect Hook 修改按钮元素宽度的示例:

 import React from "react";

function App() {
const button = React.useRef();

React.useLayoutEffect(() => {
const { width } = button.current.getBoundingClientRect();

button.current.style.width = `${width + 12}px`;
}, []);

return (
<div className="app">
<button ref={button}>Click Me</button>
</div>
);
}

export default App;

上面的代码块利用 useLayoutEffect Hook 将按钮元素的宽度增加 12 像素。 这样可以确保按钮宽度在显示到屏幕之前完成调整。

useLayoutEffect Hook 的特点

  • 同步执行:它同步执行,如果其中操作耗时较长,可能会阻塞 UI。
  • DOM 读写:最适用于直接读写 DOM,尤其是在浏览器重新绘制之前需要进行更改的情况下。

useEffectEvent Hook

useEffectEvent Hook 是一个 React Hook,旨在解决 useEffect Hook 的依赖项问题。 如果你熟悉 useEffect,你就会知道它的依赖项数组有时可能会很棘手,你可能需要在依赖项数组中添加一些并非绝对必要的值。

例如:

 import React from "react";

function App() {
const connect = (url) => {

};

const logConnection = (message, loginOptions) => {

};

const onConnected = (url, loginOptions) => {
logConnection(`Connected to ${url}`, loginOptions);
};

React.useEffect(() => {
const device = connect(url);
device.onConnected(() => {
onConnected(url);
});

return () => {
device.disconnect();
};
}, [url, onConnected]);

return <div></div>;
}

export default App;

这段代码展示了一个管理与外部服务连接的应用程序组件。 connect 函数连接到指定的 URL,而 logConnection 函数记录连接的详细信息。 最后,当设备成功连接时,onConnected 函数会调用 logConnection 函数来记录连接成功的消息。

useEffect Hook 调用 connect 函数,然后设置一个 onConnected 回调函数,以便在设备触发 onConnected 事件时执行。 这个回调函数会记录连接消息。 它返回一个清理函数,在组件卸载时被激活,负责断开设备连接。

依赖项数组包含 url 变量和 onConnected 函数。 由于每次渲染时都会重新创建 onConnected 函数,这会导致 useEffect 函数循环执行,进而导致 App 组件不断重新渲染。

虽然有多种方法可以解决 useEffect 的循环问题,但最有效的方式是在不向依赖项数组添加更多不必要的值的情况下使用 useEffectEvent Hook。

 import React from "react";

function App() {
const connect = (url) => {

};

const logConnection = (message, loginOptions) => {

};

const onConnected = React.useEffectEvent((url, loginOptions) => {
logConnection(`Connected to ${url}`, loginOptions);
});

React.useEffect(() => {
const device = connect(url);
device.onConnected(() => {
onConnected(url);
});

return () => {
device.disconnect();
};
}, [url]);

return <div></div>;
}
export default App;

通过使用 useEffectEvent Hook 包装 onConnected 函数,useEffectEvent Hook 始终可以读取 messageloginOptions 参数的最新值,然后再将其传递给 useEffect Hook。 这意味着 useEffect 不需要依赖 onConnected 函数或传递给它的值。

useEffect 需要依赖特定的值,即使事件触发效果需要你不希望在 useEffect 中作为依赖项的其他值时,useEffectEvent Hook 就显得非常有用。

useEffectEvent Hook 的特点

  • 它最适合事件驱动的副作用。
  • useEffectEvent Hook 不适用于诸如 onClickonChange 等事件处理程序。

值得注意的是,useEffectEvent Hook 仍处于实验阶段,在 React 18 版本中尚不可用。

何时使用哪个 Hook?

上述各个数据获取 Hook 适用于不同的情况:

  • 获取数据:useEffect 是一个不错的选择。
  • 直接 DOM 操作:如果需要在重绘之前对 DOM 进行同步修改,请选择 useLayoutEffect
  • 轻量级操作:对于没有阻塞 UI 风险的操作,可以自由使用 useEffect
  • 事件驱动的副作用:使用 useEffectEvent Hook 包装事件,并使用 useEffect Hook 来运行副作用。

有效处理副作用

React Hooks 打开了一个充满可能性的世界。 了解 useEffectuseLayoutEffectuseEffectEvent Hook 之间的差异,可以显著影响你处理副作用和 DOM 操作的方式。 为了构建用户友好的应用程序,必须考虑这些 Hook 的具体要求和影响。