You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何useEffect调用Zustand的Axios数据获取函数多次触发?如何解决?

问题分析与解决方案

问题现象

我在Zustand Store中通过Axios实现了数据获取逻辑:

import create from 'zustand'
import axios from "axios";

export const useStore = create((set) => ({
    pokemons: [],
    eachPokemon: {},
    getPokemons: async () => {
        const response = await axios.get('https://pokeapi.co/api/v2/pokemon')
        set({ pokemons: await response.data.results })
    },
    getPokemon: async(pokemonURL) => {
        const response = await axios.get(pokemonURL)
        set({ eachPokemon: await response.data})
    }
}))

在React组件的useEffect中调用getPokemons时:

import React, {useEffect} from 'react'
import { useStore } from '../../store/store'

import ItemDetails from './ItemDetails'

function Items() {
  const {pokemons, getPokemons} = useStore((state) => ({
    pokemons: state.pokemons,
    getPokemons: state.getPokemons
  }))

  useEffect(() => {
    getPokemons();
    console.log(pokemons)
  }, [getPokemons])


  return (
    <>
        Some code
    </>
  )
}

export default Items

发现useEffect里的console.log执行了两次;如果把console.log放到useEffect外:

import React, {useEffect} from 'react'
import { useStore } from '../../store/store'

import ItemDetails from './ItemDetails'

function Items() {
  const {pokemons, getPokemons} = useStore((state) => ({
    pokemons: state.pokemons,
    getPokemons: state.getPokemons
  }))

  useEffect(() => {
    getPokemons();
  }, [getPokemons])

  console.log(pokemons)


  return (
    <>
       Some code
    </>
  )
}

export default Items

控制台会多次输出pokemons数组:先输出空数组,之后多次打印包含20条数据的数组。想知道原因,以及如何实现只获取一次数据。

原因解析

  1. React Strict Mode的开发环境行为:开发环境下,React Strict Mode会强制执行两次useEffect等生命周期钩子,目的是检测未清理的副作用、重复渲染等潜在问题,这是useEffect内日志执行两次的直接原因。
  2. 组件重渲染与依赖不稳定:
    • 调用getPokemons后,Zustand更新pokemons状态,组件会因状态变化触发重渲染,每次重渲染都会执行组件内的console.log,所以会多次输出数组。
    • 当前的Zustand selector每次返回新对象,导致getPokemons的引用在组件每次渲染时都不一样,useEffect会因为依赖项变化额外触发执行。

解决方法

1. 稳定getPokemons的引用,避免不必要的useEffect触发

Zustand的selector如果返回新对象,会导致依赖项引用变化,可通过两种方式解决:

// 方法一:拆分selector,单独获取每个状态/方法
const pokemons = useStore(state => state.pokemons)
const getPokemons = useStore(state => state.getPokemons)

// 方法二:使用shallow比较,避免新对象引用
import { shallow } from 'zustand/shallow'

const {pokemons, getPokemons} = useStore(state => ({
  pokemons: state.pokemons,
  getPokemons: state.getPokemons
}), shallow)

2. 确保只发起一次数据请求

方案一:组件层控制请求时机

在useEffect中判断数据是否已存在,避免重复请求:

useEffect(() => {
  if (pokemons.length === 0) {
    getPokemons()
  }
}, [getPokemons, pokemons])

方案二:Store层控制(更推荐)

在Store的getPokemons方法内添加判断,只要数据已存在就不再发起请求:

export const useStore = create((set, get) => ({
    pokemons: [],
    eachPokemon: {},
    getPokemons: async () => {
        const currentPokemons = get().pokemons
        // 已有数据直接返回,避免重复请求
        if (currentPokemons.length > 0) return
        
        const response = await axios.get('https://pokeapi.co/api/v2/pokemon')
        set({ pokemons: response.data.results })
    },
    getPokemon: async(pokemonURL) => {
        const response = await axios.get(pokemonURL)
        set({ eachPokemon: response.data})
    }
}))

3. 关于开发环境的两次执行

生产环境下React Strict Mode不会触发两次执行,上线后useEffect只会执行一次。开发环境中如果不想看到两次执行,可以暂时关闭Strict Mode,但不建议这么做——它能帮你提前发现潜在的代码问题。

内容的提问来源于stack exchange,提问作者Young Jun Joo

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.23 12:27:16