面试:React 中的 useEffect 与 useLayoutEffect有什么区别

介绍🌟

随着 React 16.8 中 hooks 的引入,编写函数式组件的景观发生了巨大变化。 Among 这些,useEffectuseLayoutEffect 尤为重要,用于处理代码中的副作用。乍看之下,它们可能看起来很相似,但两者之间存在一些重要区别。在本博文中,我们将以清晰而直接的方式来看这些差异。我们将帮助您理解何时使用 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 的行为⚖️

在分别探讨了 useEffectuseLayoutEffect 之后,在同一个组件中看到它们的行动是很有帮助的。 这种比较将使我们更清楚地了解它们的执行顺序和行为。

让我们创建一个同时包含 useEffectuseLayoutEffect 的单个函数组件。 我们将在每个钩子中添加控制台日志,以观察它们执行的顺序。

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;

在此组件中,我们同时具有依赖关系数组均为空的 useLayoutEffectuseEffect,这意味着它们应该在初始渲染后运行。

useEffect 对比 useLayoutEffect

  1. useLayoutEffect: 此钩子首先运行。它在所有 DOM 变更完成后但浏览器有机会绘制之前触发。 这使得它非常适合需要在 DOM 更新后但用户看到任何内容之前发生的任何 DOM 操纵或计算。 useLayoutEffect 内的控制台日志将是首先出现的。
  2. useEffect: 此钩子在 useLayoutEffect 之后运行。它在组件渲染周期完成并且屏幕更新后触发。 这种行为意味着 useEffect 最适合不需要立即同步更新 DOM 的任务,比如 API 调用或设置订阅。 useEffect 内的控制台日志将出现在来自 useLayoutEffect 的日志之后。

理论上,由于其异步性质(在浏览器绘制后执行),useEffect 可能会导致闪烁效果,但在实践中,观察到明显的闪烁可能很困难。 从视觉上看,useEffectuseLayoutEffect 的行为通常相似,尤其是对于简单的 DOM 更新。

实际例子:useEffect 和 useLayoutEffect 的行动💫

为了更好地掌握 useEffectuseLayoutEffect 的细微差别,让我们深入一些实际例子。 这些将说明每个 hook 如何在不同情况下被有效利用。

重要的是要注意,在许多情况下,尤其是在简单的场景或使用现代高性能浏览器的情况下,这些差异可能不会在视觉上明显。

1️⃣ useEffect 用于数据获取🌐📈

数据获取是 useEffect 的常见用例。 它允许您在组件渲染时从 API 请求和加载数据。

这是一个完整的 React 组件示例,它使用 useEffectJSONPlaceholder 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。

结论✅

useEffectuseLayoutEffect 之间的选择通常取决于正在执行的 DOM 操作的具体要求。 尽管视觉差异不总是明显,但理解这些钩子的内部行为对于优化 React 应用程序的性能和确保顺畅的用户体验至关重要。

对于大多数副作用,特别是那些不需要与 DOM 立即交互的副作用(如数据获取或设置订阅),useEffect 是您的首选钩子。 其对状态和道具更改做出反应的能力使其对广泛的用例非常通用。

另一方面,当您需要根据 DOM 更改进行同步更新时,useLayoutEffect 是必不可少的。 当操作 DOM 时,它是防止视觉故障并确保顺畅用户体验的完美工具。

编码快乐!

ReactuseEffectuseLayoutEffect都是用于处理组件副作用的Hook。它们都可以在组件渲染后执行一些操作,比如获取数据、修改DOM等。它们的主要区别在于执行时机和阻塞UI渲染的能力。具体来说,useEffect会在浏览器渲染完成后异步执行,而useLayoutEffect会在浏览器渲染前同步执行,有阻塞UI渲染的能力。因此,如果要修改DOM并且希望能够同步更新UI,则应该使用useLayoutEffect;否则,使用useEffect即可。 举个例子,假设你需要在组件渲染后通过API获取数据并更新组件状态。则你可以这样使用useEffect: ```javascript import { useState, useEffect } from 'react'; function MyComponent() { const [data, setData] = useState(null); useEffect(() => { fetchData().then((res) => setData(res)); }, []); return ( <div> {data ? ( <ul> {data.map((item) => ( <li key={item.id}>{item.name}</li> ))} </ul> ) : ( <p>Loading...</p> )} </div> ); } ``` 这里的useEffect会在组件渲染后异步执行fetchData函数并更新组件状态。而如果你需要在DOM更新之前计算一些值并更新组件状态,则可以使用useLayoutEffect: ```javascript import { useState, useLayoutEffect } from 'react'; function MyComponent() { const [width, setWidth] = useState(null); useLayoutEffect(() => { function updateWidth() { setWidth(window.innerWidth); } window.addEventListener('resize', updateWidth); updateWidth(); return () => window.removeEventListener('resize', updateWidth); }, []); return <p>Window width: {width}px</p>; } ``` 这里的useLayoutEffect会在组件渲染前同步执行updateWidth函数并更新组件状态,因此可以实时获取窗口宽度并渲染到UI
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

今天也想MK代码

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值