React useState 和 useEffect 使用坑点注意总结

发布于:2025-02-21 ⋅ 阅读:(21) ⋅ 点赞:(0)

React Hooks 使用注意事项

Area: Hooks
Date: February 10, 2025
Important: 🌟🌟🌟

React Hooks 注意事项

要点:

  • useState 的初始化值 只在第一次渲染时计算,并且这个值不会随着组件重新渲染而更新。
  • useEffect 可能出现死循环,比如处理请求接口时,依赖项绑定对象类型数据,会死循环。


useState 的初始值问题

Case:useState 的初始化值 只在第一次渲染时计算,并且这个值不会随着组件重新渲染时更新。

理解:useState 的初始化值只在组件的第一次渲染时生效,不会随着 propsstate 的变化而更新。

2025-02-16 23.03.54.gif

import React, { useState } from 'react'

// 子组件
function Child({ userInfo }) {
  // render: 初始化 state
  // re-render: 只恢复初始化的 state 值,不会再重新设置新的值
  //            只能用 setName 修改
  const [name, setName] = useState(userInfo.name)

  return (
    <div>
      <p>Child, props name: {userInfo.name}</p>
      <p>Child, state name: {name}</p>
    </div>
  )
}

function App() {
  const [name, setName] = useState('双越')
  const userInfo = { name }

  return (
    <div>
      <div>
        Parent &nbsp;
        <button onClick={() => setName('慕课网')}>setName</button>
      </div>
      <Child userInfo={userInfo} />
    </div>
  )
}

export default App

解决方案:使用 useEffect 来监听 userInfo 的变化,并在变化时更新 name

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

function Child({ userInfo }) {
  const [name, setName] = useState(userInfo.name)

  // 使用 useEffect 来监听 userInfo.name 的变化并更新 name
  useEffect(() => {
    setName(userInfo.name)
  }, [userInfo.name])  // 当 userInfo.name 发生变化时更新 name

  return (
    <div>
      <h1>Child, Props name: {userInfo.name}</h1>
      <h1>Child, state name: {name}</h1>
    </div>
  )
}

function App() {
  const [name, setName] = useState('Nathan')
  const userInfo = {
    name,
  }
  return (
    <div>
      <h1>UserName</h1>
      <button onClick={() => setName('XXXX')}>Change Name</button>
      <Child userInfo={userInfo} />
    </div>
  )
}

export default App



useEffect 的死循环问题

Case:useEffect 死循环

理解:处理请求接口时,依赖项绑定对象类型数据,会死循环。

2025-02-16 23.24.16.gif

import { useState, useEffect } from 'react'
import axios from 'axios'

// 传入 config,类型为对象
function useAxios(url, config = {}) {
  const [loading, setLoading] = useState(false)
  const [data, setData] = useState()
  const [error, setError] = useState()

  useEffect(() => {
    setLoading(true)
    axios
      .get(url) // 发送一个 get 请求
      .then(res => setData(res))
      .catch(err => setError(err))
      .finally(() => setLoading(false))
  }, [url, config])

  return [loading, data, error]
}

export default useAxios

分析:

1-useEffect 和依赖项的关系:

useEffect 中,[url, config] 是依赖项数组。这意味着当 urlconfig 发生变化时,useEffect 会重新执行。

2-对象 config 的变化:

在 JavaScript 中,对象(如 config)是引用类型。如果每次重新渲染时,config 的引用发生了变化,即使它的内容相同,React 也会认为它发生了变化,从而触发 useEffect 的重新执行。

这就导致了一个问题:每次组件重新渲染时,config 对象的引用可能会发生变化,即使它的值没有改变。这会导致 useEffect 被反复调用,进而每次发起请求。

注:这里 React 底层使用的是 Object.is() 进行比较

image.png

总结无限请求的原因:

  • 每次组件渲染时,config 会被认为是新的对象(即使它的内容没有变化),从而触发 useEffect 重新执行。
  • useEffect 执行时会发起请求,且请求可能会导致 config 的变化(如果在请求完成后更新了 config,即使没有显式更新也可能影响父组件中的 config)。
  • 因此,useEffect 会再次被触发,导致请求不断发起,形成无限请求的循环。

解决方案:

基础方案:

useEffect 依赖项不要放置对象类型。可在之前将对象解构,useEffect依赖项中放置基本类型值

另一种方案:

如果你希望在 config 变化时也重新发送请求,你可以使用 useMemo 来确保只有在 config 内容变化时才触发请求:

import { useState, useEffect, useMemo } from 'react'
import axios from 'axios'

function useAxios(url, config = {}) {
  const [loading, setLoading] = useState(false)
  const [data, setData] = useState()
  const [error, setError] = useState()

  // 使用 useMemo 来确保 config 引用只有在实际变化时才会更新
  const memoizedConfig = useMemo(() => config, [JSON.stringify(config)])

  useEffect(() => {
    setLoading(true)
    axios
      .get(url, memoizedConfig) // 使用 memoizedConfig 而非原始 config
      .then(res => setData(res))
      .catch(err => setError(err))
      .finally(() => setLoading(false))
  }, [url, memoizedConfig])

  return [loading, data, error]
}

export default useAxios


网站公告

今日签到

点亮在社区的每一天
去签到