react中的useEffect的使用

之前我们已经掌握了useState的使用,在 class 中,我们通过在构造函数中设置 this.state{ count: 0 } 来初始化 count state 为 0

 

class Example extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    };
  }

在函数组件中,我们没有 this,所以我们不能分配或读取 this.state。我们直接在组件中调用 useState Hook:

 

import React, { useState } from 'react';

function Example() {
  // 声明一个叫 “count” 的 state 变量
  const [count, setCount] = useState(0);

既然我们知道了 useState 的作用,那么掌握 useEffect 就更容易,函数组件中没有生命周期,那么可以使用 useEffect 来替代。如果你熟悉 React class 的生命周期函数,你可以把 useEffect Hook 看做 componentDidMountcomponentDidUpdatecomponentWillUnmount 这三个函数的组合。

 

class Example extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    };
  }
  componentDidMount() {
    document.title = `You clicked ${this.state.count} times`;
  }
  componentDidUpdate() {
    document.title = `You clicked ${this.state.count} times`;
  }
  render() {
    return (
      <div>
        <p>You clicked {this.state.count} times</p>
        <button onClick={() => this.setState({ count: this.state.count + 1 })}>
          Click me
        </button>
      </div>
    );
  }
}

使用 Hook 的示例

 

import React, { useState, useEffect } from 'react';

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

  useEffect(() => {
    document.title = `You clicked ${count} times`;
  });

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

默认情况下,它在第一次渲染之后和每次更新之后都会执行。你可能会更容易接受 effect 发生在“渲染之后”这种概念,不用再去考虑“挂载”还是“更新”。React 保证了每次运行 effect 的同时,DOM 都已经更新完毕。

数据获取,设置订阅以及手动更改 React 组件中的 DOM 都属于副作用。有些副作用可能需要清除,所以需要返回一个函数,比如挂载时设置定时器,卸载时取消定时器。

 

class Example extends Component {
  constructor (props) {
    super(props);
    this.state = {
      count: 0
    }
  }
  componentDidMount() {
    this.id = setInterval(() => {
      this.setState({count: this.state.count + 1})
    }, 1000);
  }
  componentWillUnmount() {
    clearInterval(this.id)
  }
  render() { 
    return <h1>{this.state.count}</h1>;
  }
}

使用 Hook 的示例

 

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

  useEffect(() => {
    const id = setInterval(() => {
      setCount(c => c + 1);
    }, 1000);
    return () => clearInterval(id);
  }, []);

  return <h1>{count}</h1>
}

你可以通知 React 跳过对 effect 的调用,只要传递数组作为 useEffect 的第二个可选参数即可,如果想执行只运行一次的 effect(仅在组件挂载和卸载时执行),可以传递一个空数组([])作为第二个参数。这就告诉 React 你的 effect 不依赖于 props 或 state 中的任何值,所以它永远都不需要重复执行。

通过跳过 Effect 进行性能优化,在某些情况下,每次渲染后都执行清理或者执行 effect 可能会导致性能问题。在 class 组件中,我们可以通过在 componentDidUpdate 中添加对 prevPropsprevState 的比较逻辑解决:

 

componentDidUpdate(prevProps, prevState) {
  if (prevState.count !== this.state.count) {
    document.title = `You clicked ${this.state.count} times`;
  }
}

这是很常见的需求,所以它被内置到了 useEffect 的 Hook API 中。如果某些特定值在两次重渲染之间没有发生变化,你可以通知 React 跳过对 effect 的调用,只要传递数组作为 useEffect 的第二个可选参数即可:

 

useEffect(() => {
  document.title = `You clicked ${count} times`;
}, [count]); // 仅在 count 更改时更新

你已经学习了 State Hook 和 Effect Hook,将它们结合起来你可以做很多事情了。它们涵盖了大多数使用 class 的用例。

### 处理引用类型作为依赖项 在 React 中,`useEffect` 钩子用于处理副作用逻辑。当涉及到引用类型的对象或数组作为 `useEffect` 的依赖项时,需要注意这些引用类型的比较方式[^3]。 由于 JavaScript 对象和数组是按引用传递的,即使两个变量指向相同的结构化内容,它们也可能具有不同的内存地址。因此,在每次渲染期间创建新的对象或数组实例会导致不必要的副作用重新运行,因为 React 认为依赖已更改。 #### 正确的方法 为了防止这种情况发生,可以采取以下措施: - **使用稳定的引用** 如果可能的话,尝试保持引用稳定。这意味着不在组件内部定义新对象或数组,而是从外部传入或通过其他手段确保其引用不变。 - **利用 useMemo 或 useCallback** 当确实需要动态生成复杂的数据结构时,可以通过 `useMemo` 来缓存计算结果,或者对于函数来说,可以用 `useCallback` 缓存函数引用,以此减少不必要的更新[^1]。 ```javascript import React, { useState, useEffect, useMemo } from 'react'; function MyComponent({ initialData }) { const [data, setData] = useState(initialData); // 使用 useMemo 创建一个不会频繁变化的对象 const memoizedObject = useMemo(() => ({ key: data }), [data]); useEffect(() => { console.log('Effect triggered by changes to the object'); }, [memoizedObject]); // 只有当 memoizedObject 发生实际改变才会触发 effect return ( <div> {/* Component JSX */} </div> ); } ``` - **自定义相等性检查** 在某些情况下,如果默认的行为仍然不够理想,则可以在 `useEffect` 的第二个参数中提供自定义的浅层对比逻辑来决定是否应该重新执行效果。不过这种方法较为少见且容易引入 bug,通常推荐先考虑前两种方案。 #### 注意事项 - 不要直接将复杂的嵌套对象放入依赖列表中,除非绝对必要,并且已经确认这样做不会引起性能问题。 - 考虑到闭包捕获的问题,有时即使依赖看起来没变,但由于闭包环境的变化也会导致意外的结果。此时应仔细审查代码逻辑以排除潜在隐患[^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值