9. useState 和 useEffect 的执行时机和细节?

45 阅读2分钟

9. useState 和 useEffect 的执行时机和细节?

答案:

useState 的执行时机:

1. 初始化阶段

function MyComponent() {
  // 组件首次渲染时执行
  const [count, setCount] = useState(0) // 初始化:count = 0
  const [name, setName] = useState('React') // 初始化:name = 'React'

  return <div>{count}</div>
}

2. 更新阶段

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

  const handleClick = () => {
    setCount(count + 1) // 触发重新渲染
    setCount(count + 1) // 不会立即更新,仍然是旧值
  }

  return <button onClick={handleClick}>{count}</button>
}

3. 函数式更新

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

  const handleClick = () => {
    setCount((prevCount) => prevCount + 1) // 使用函数式更新
    setCount((prevCount) => prevCount + 1) // 基于最新值更新
  }

  return <button onClick={handleClick}>{count}</button>
}

useEffect 的执行时机:

1. 组件挂载后

function MyComponent() {
  const [data, setData] = useState(null)

  useEffect(() => {
    // 组件挂载后执行
    console.log('组件已挂载')
    fetchData().then(setData)
  }, []) // 空依赖数组,只在挂载时执行

  return <div>{data ? data.title : 'Loading...'}</div>
}

2. 每次渲染后

function MyComponent({ userId }) {
  const [user, setUser] = useState(null)

  useEffect(() => {
    // 每次渲染后都会执行
    console.log('组件已渲染')
    fetchUser(userId).then(setUser)
  }) // 没有依赖数组,每次渲染都执行

  return <div>{user ? user.name : 'Loading...'}</div>
}

3. 依赖项变化时

function MyComponent({ userId }) {
  const [user, setUser] = useState(null)

  useEffect(() => {
    // 只有当 userId 变化时才执行
    console.log('userId 变化了:', userId)
    fetchUser(userId).then(setUser)
  }, [userId]) // 依赖 userId

  return <div>{user ? user.name : 'Loading...'}</div>
}

执行顺序:

1. 组件渲染顺序

function MyComponent() {
  console.log('1. 组件开始渲染')

  const [count, setCount] = useState(0)
  console.log('2. useState 执行')

  useEffect(() => {
    console.log('4. useEffect 执行(渲染后)')
  })

  console.log('3. 组件渲染完成')

  return <div>{count}</div>
}

// 输出顺序:
// 1. 组件开始渲染
// 2. useState 执行
// 3. 组件渲染完成
// 4. useEffect 执行(渲染后)

2. 多个 useEffect 的执行顺序

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

  useEffect(() => {
    console.log('useEffect 1')
  })

  useEffect(() => {
    console.log('useEffect 2')
  }, [])

  useEffect(() => {
    console.log('useEffect 3')
  }, [count])

  // 执行顺序:按照声明顺序执行
}

清理函数:

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

  useEffect(() => {
    const timer = setInterval(() => {
      setCount((c) => c + 1)
    }, 1000)

    // 清理函数:组件卸载或依赖变化前执行
    return () => {
      clearInterval(timer)
      console.log('定时器已清理')
    }
  }, [])

  return <div>{count}</div>
}

异步操作:

function MyComponent() {
  const [data, setData] = useState(null)
  const [loading, setLoading] = useState(true)

  useEffect(() => {
    let cancelled = false

    const fetchData = async () => {
      try {
        setLoading(true)
        const result = await api.getData()

        // 检查组件是否已卸载
        if (!cancelled) {
          setData(result)
        }
      } catch (error) {
        if (!cancelled) {
          console.error('获取数据失败:', error)
        }
      } finally {
        if (!cancelled) {
          setLoading(false)
        }
      }
    }

    fetchData()

    return () => {
      cancelled = true // 取消请求
    }
  }, [])

  return <div>{loading ? 'Loading...' : data?.title}</div>
}

性能优化:

function MyComponent({ items }) {
  const [filteredItems, setFilteredItems] = useState([])

  // 使用 useMemo 避免不必要的计算
  const expensiveValue = useMemo(() => {
    return items.reduce((sum, item) => sum + item.value, 0)
  }, [items])

  // 使用 useCallback 避免不必要的重新创建
  const handleFilter = useCallback(
    (filter) => {
      setFilteredItems(items.filter((item) => item.category === filter))
    },
    [items]
  )

  useEffect(() => {
    // 只在 items 变化时执行
    setFilteredItems(items)
  }, [items])

  return <div>{filteredItems.length} items</div>
}