ReactHooks函数useEffect最佳实践

React Hooks函数中useState及useEffect出场率算是很高了,今天聊一下useEffect使用的最佳实践。

使用方法及调用规则

  • 每一次渲染后都执行的副作用:传入回调函数,不传依赖数组。
useEffect(callBack)
  • 仅在挂载阶段执行一次的副作用:传入回调函数,且这个函数的返回值不是一个函数,同时传入一个空数组。
useEffect(()=>{
  // 这里是业务逻辑 
}, [])
  • 仅在挂载阶段和卸载阶段执行的副作用:传入回调函数,且这个函数的返回值是一个函数,同时传入一个空数组。假如回调函数本身记为 A, 返回的函数记为 B,那么将在挂载阶段执行 A,卸载阶段执行 B。
useEffect(()=>{
  // 这里是 A 的业务逻辑

  // 返回一个函数记为 B
  return ()=>{

  }
}, [])

这里需要注意,这种调用方式之所以会在卸载阶段去触发 B 函数的逻辑,是由 useEffect 的执行规则决定的:useEffect 回调中返回的函数被称为“清除函数”,当 React 识别到清除函数时,会在调用新的 effect 逻辑之前执行清除函数内部的逻辑。这个规律不会受第二个参数或者其他因素的影响,只要你在 useEffect 回调中返回了一个函数,它就会被作为清除函数来处理。

  • 每一次渲染都触发,且卸载阶段也会被触发的副作用:传入回调函数,且这个函数的返回值是一个函数,同时不传第二个参数.
useEffect(()=>{
  // 这里是 A 的业务逻辑
  // 返回一个函数记为 B
  return ()=>{

  }
})
  • 根据一定的依赖条件来触发的副作用:传入回调函数,同时传入一个非空的数组,
useEffect(()=>{

  // 这是回调函数的业务逻辑 
  // 若 xxx 是一个函数,则 xxx 会在组件每次因 num1、num2、num3 的改变而重新渲染时被触发
  return xxx

}, [num1, num2, num3])

若数组不为空,那么 React 就会在新的一次渲染后去对比前后两次的渲染,查看数组内是否有变量发生了更新(只要有一个数组元素变了,就会被认为更新发生了),并在有更新的前提下去触发 useEffect 中定义的副作用逻辑。

最佳实践

1. 指定依赖项

使用 useEffect 时,指定依赖项非常重要。这样可以确保只有在这些依赖项发生变化时才运行 effect。如果不指定依赖项,effect 将在每次渲染时都运行,这可能会导致性能问题。

以下是指定依赖项的示例:

import { useEffect, useState } from 'react';

function MyComponent(props) {
  const [count, setCount] = useState(0);

  useEffect(() => {
    // 这个 effect 只会在 `count` 发生变化时运行
    console.log(`Count changed to ${count}`);
  }, [count]);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}

在这个示例中,effect 只会在 count 状态发生变化时运行。

2. 清理 effect

如果你的 effect 创建了任何资源,例如事件监听器或定时器,那么在组件卸载时清理它们非常重要。这可以防止内存泄漏和其他问题。

以下是清理 effect 的示例:

import { useEffect } from 'react';

function MyComponent(props) {
  useEffect(() => {
    const intervalId = setInterval(() => {
      console.log('Tick');
    }, 1000);

    return () => {
      clearInterval(intervalId);
    };
  }, []);

  return <div>...</div>;
}

在这个示例中,effect 创建了一个每秒记录 “Tick” 的间隔计时器。effect 还返回了一个清理函数,该函数在组件卸载时清除了间隔计时器。

3. 避免在渲染中执行副作用

当使用 useEffect 时,应该避免在渲染中执行副作用。这是因为渲染阶段应该专注于渲染 UI,而执行副作用可能会导致不必要的重新渲染和其他问题。相反,应该在 useEffect 钩子中执行副作用。这样可以确保它们只在其依赖项发生更改时运行,而不是在每次渲染时都运行。
以下是在 useEffect 中执行副作用的示例:

import { useEffect } from 'react';

function MyComponent(props) {
  useEffect(() => {
    // This effect will only run when the component mounts
    console.log('Component mounted');

    // This effect will also run when `props.someProp` changes
    console.log(`Some prop changed to ${props.someProp}`);

    // This effect will also run when `props.anotherProp` changes
    console.log(`Another prop changed to ${props.anotherProp}`);

    // This effect will only run when the component unmounts
    return () => {
      console.log('Component unmounted');
    };
  }, [props.someProp, props.anotherProp]);

  return <div>...</div>;
}

在这个示例中,useEffect 钩子只在组件挂载时运行一次,并在组件卸载时清理。它还在 props.someProp 和 props.anotherProp 更改时运行。这确保了副作用只在其依赖项发生更改时运行,而不是在每次渲染时都运行。

4.将 effect 拆分成多个

如果你的 effect 包含多个不同的逻辑,可以将它们拆分成多个 effect。这样可以更好地组织代码,并使每个 effect 更加专注于单个任务。

以下是将 effect 拆分成多个的示例:

import { useEffect, useState } from 'react';

function MyComponent(props) {
  const [count, setCount] = useState(0);

  useEffect(() => {
    // This effect will only run when `count` changes
    console.log(`Count changed to ${count}`);
  }, [count]);

  useEffect(() => {
    // This effect will only run when `props.someProp` changes
    console.log(`Some prop changed to ${props.someProp}`);
  }, [props.someProp]);

  useEffect(() => {
    // This effect will only run when `props.anotherProp` changes
    console.log(`Another prop changed to ${props.anotherProp}`);
  }, [props.anotherProp]);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}

5. 使用 useCallback 和 useMemo 优化性能

如果你的 effect 依赖于回调函数或计算结果,可以使用 useCallback 和 useMemo 优化性能。这些钩子可以缓存回调函数和计算结果,以避免不必要的重新计算和重新渲染。
以下是使用 useCallback 和 useMemo 的示例:

import { useEffect, useState, useCallback, useMemo } from 'react';

function MyComponent(props) {
  const [count, setCount] = useState(0);

  const handleClick = useCallback(() => {
    setCount(count + 1);
  }, [count]);

  const expensiveValue = useMemo(() => {
  // Some expensive computation
}, [dependency1, dependency2]);
}

在上面的示例代码中,我们使用了 useCallback 和 useMemo 来优化性能。useCallback 可以缓存回调函数,避免不必要的重新渲染,而 useMemo 可以缓存计算结果,避免不必要的重新计算。

在 handleClick 回调函数中,我们使用了 useCallback 来缓存回调函数,以避免在每次重新渲染时都创建一个新的回调函数。这样可以提高性能,因为 React 可以比较新旧回调函数是否相等,从而避免不必要的重新渲染。

const handleClick = useCallback(() => {
  setCount(count + 1);
}, [count]);

在 expensiveValue 中,我们使用了 useMemo 来缓存计算结果,以避免在每次重新渲染时都重新计算。这样可以提高性能,因为计算结果只需要在依赖项发生变化时才重新计算。

const expensiveValue = useMemo(() => {
  // Some expensive computation
}, [dependency1, dependency2]);

需要注意的是,useCallback 和 useMemo 都需要传入一个依赖项数组,用于指定哪些变量的变化会导致回调函数或计算结果的重新计算。如果依赖项数组中的任何一个变量发生变化,那么回调函数或计算结果都会被重新计算。如果依赖项数组为空,那么回调函数或计算结果只会在组件挂载和卸载时被计算一次。

原文链接:https://juejin.cn/post/7218392277918711864 作者:wayne214

(0)
上一篇 2023年4月5日 下午5:03
下一篇 2023年4月6日 上午10:00

相关推荐

发表回复

登录后才能评论