React中的Hooks---useEffect

简介

什么是React Hooks

React Hooks是在React 16.8版本中引入的一项重大特性,旨在解决函数组件在复杂场景下的状态管理和生命周期问题。

它们允许在不编写类组件的情况下使用state、生命周期方法等功能,使得函数组件更加简洁、易于理解和复用。

作为React Hooks的核心成员之一,useEffect在函数组件中扮演着处理副作用的重要角色。

它提供了统一、便捷的方式来组织和管理那些原本分散在生命周期方法中的非纯函数行为,极大地提升了代码的可读性和可维护性。

什么是 useEffect

在React中,副作用是指那些在渲染过程中不应直接执行,但与UI状态相关的操作。常见的副作用包括但不限于:

  • 数据获取:从服务器或缓存中获取数据。
  • 订阅与取消订阅:监听外部数据源(如WebSocket、Redux store等)的变化。
  • 更新DOM:直接操作DOM元素,如聚焦输入框、调整滚动位置等。
  • 设置与清除定时器:定期执行某些任务,如轮询检查数据更新、定时显示提示信息等。
  • 添加与移除事件监听器:监听窗口大小变化、键盘输入等事件,并在适当时候进行响应。

useEffect Hook就是用来封装这些副作用操作的。

它接受一个 “效应函数”(effect function)作为参数,在特定时机执行该函数以处理副作用。

此外,还可以通过第二个参数——依赖数组,控制useEffect的执行时机和清理机制。

使用 useEffect

基本结构

useEffect(() => {
  // 效应函数:在此处执行副作用操作
  // ...

  return () => {
    // 清除函数:在此处进行清理工作,如取消订阅、清除定时器等
    // ...
  };
}, [/* 依赖数组(可选) */]);
  • 效应函数:负责执行副作用操作。React会在组件渲染完成后(浏览器微任务队列清空后)调用此函数。

  • 清除函数(返回值):用于清理副作用,如取消订阅、清除定时器等。React会在下次 useEffect 执行前(或组件卸载时)调用此函数。

  • 依赖数组:一个可选的数组,指定 useEffect 所依赖的React状态变量或其他值。当数组中的某个值发生变化时,React将重新执行 useEffect 及其对应的清除函数。若省略此参数,则 useEffect 只会在组件挂载和卸载时各执行一次;若传入一个空数组 [],则只在DOM挂载的时候执行一次

useEffect 的执行时机

可以把 useEffect 看做 componentDidMount , componentDidUpdate , componentWillUnmount 这三个函数的组合。

当做 componentDidMount 和 componentDidUpdate 的时候

function App() {
    const [count,setCount] = useState(0);
    // 组件挂载完成之后 或 组件数据更新完成之后 执行
    useEffect(() => {
        console.log('组件挂载完成之后 或 组件数据更新完成之后 执行');
    })
    return (
        <div>
            {count}
            <button onClick={() => setCount(count + 1)}>+1</button>
        </div>
    )
}

仅当做 comonentDidMount 的时候

useEffect(() => {
    console.log('仅当做componentDidMount');
},[])

当做 componentWillunmount 的时候

注意:这里不是仅当做componentWillunmount

useEffect(() => () => {
    console.log('当做componentWillUnmount');
})

useEffect的使用方法示例

为window对象添加滚动事件

挂载完成后绑定事件,卸载组件后解除绑定

function App() {
    function onScroll() {
        console.log('监听到页面发生滚动了');
    }
    useEffect(() => {
        window.addEventListener('scroll',onScroll);
        return () => {
            // 卸载组件时解除对事件的绑定
            window.removeEventListener('scroll',onScroll);
        }
    })
    return (
        <div>
            App 
        </div>
    )
}

设置定时器让count数值每隔一秒增加1

function App() {
    
    const [count,setCount] = useState(0);
    useEffect(() => {
        const timeId = setInterval(() => {
           setCount(count => count + 1); 
        },1000)
        return () => {
            clearInterval(timeId);
        }
    },[])
    return (
        <div>
            <h1>当前求和为:{count}</h1> 
        </div>
    )
}

useEffect的第二个参数

只有指定数据发生变化的时候才触发effect

useEffect(() => {
    document.title = count;
}, [count]) 

如果第二个参数是一个空数组,就表明副效应参数没有任何依赖项。

因此,副效应函数这时只会在组件加载进入 DOM 后执行一次,后面组件重新渲染,就不会再次执行。

这很合理,由于副效应不依赖任何变量,所以那些变量无论怎么变,副效应函数的执行结果都不会改变,所以运行一次就够了。

useEffect结合异步函数

useEffect 中直接使用 asyncawait 是会报错的,推荐使用立即执行函数来包裹异步函数。

function getData() {
    return new Promise(resolve => {
        resolve({msg: 'Hello'})
    })
}
function App() {

    useEffect(() => {
        (async function () {
            const result = await getData();
            console.log(result);
        })()
    },[])
    
    return (
        <div>
            App
        </div>
    )
}

useEffect 的注意点

使用 useEffect() 时,有一点需要注意。如果有多个副效应,应该调用多个 useEffect() ,而不应该合并写在一起。

function App() {
  const [varA, setVarA] = useState(0);
  const [varB, setVarB] = useState(0);
  useEffect(() => {
    const timeoutA = setTimeout(() => setVarA(varA + 1), 1000);
    const timeoutB = setTimeout(() => setVarB(varB + 2), 2000);

    return () => {
      clearTimeout(timeoutA);
      clearTimeout(timeoutB);
    };
  }, [varA, varB]);

  return <span>{varA}, {varB}</span>;
}

上面的例子是错误的写法,副效应函数里面有两个定时器,它们之间并没有关系,其实是两个不相关的副效应,不应该写在一起。正确的写法是将它们分开写成两个 useEffect()

function App() {
  const [varA, setVarA] = useState(0);
  const [varB, setVarB] = useState(0);

  useEffect(() => {
    const timeout = setTimeout(() => setVarA(varA + 1), 1000);
    return () => clearTimeout(timeout);
  }, [varA]);

  useEffect(() => {
    const timeout = setTimeout(() => setVarB(varB + 2), 2000);

    return () => clearTimeout(timeout);
  }, [varB]);

  return <span>{varA}, {varB}</span>;
}
posted @ 2024-04-08 11:28  厚礼蝎  阅读(6)  评论(0编辑  收藏  举报