繁体   English   中英

如何在 useReducer() 和 useContext() state 管理设置中使用 useEffect() 从 api 获取数据?

[英]How to use useEffect() to fetch data from an api in a useReducer() and useContext() state management setup?

reducer.js

export const reducer = (state, action) => {
  switch(action.type) {
    case SET_HEROES:
      return { ...state, heroes: action.heroes }
  }
}

AppContext.js

export const AppContext = React.createContext()

export const AppProvider = (props) => {
  const initialState = { 
    heroes: [] 
  }

  const [appState, dispatch] = useReducer(reducer, initialState)

  const setHeroes = async () => {
    const result = await getHeroes()
    dispatch({ type: SET_HEROES, heroes: result })
  }

  return <AppContext.Provider
    values={{ heroes: appState.heroes, setHeroes }}
  >
    {props.children}
  </AppContext.Provider>
}

HeroesScreen.js

const HeroesScreen = () => {
  const { heroes, setHeroes } = useContext(AppContext)

  useEffect(() => {
    setHeroes()
  }, [])

  return <>
  // iterate hero list
  </>
}

export default HeroesScreen

以上是使用 reducer + context 作为 state 管理的简单组件设置。 英雄正在屏幕上显示,一切正常,但我收到警告Reach Hook useEffect has a missing dependency: 'setHeroes' 但是,如果我将它作为依赖项添加,它会使我的应用程序崩溃,并Maximum depth update exceeded

一直在搜索,但我看到的只是将 function fetch 调用放在useEffect()中。 我想要的是提取 function 并按照 SRP 原则将其放在单独的文件中

编辑:

建议使用useCallback()

AppContext.js

const setHeroes = useCallback(() => {
    getHeroes().then(result => dispatch({ type: SET_HEROES, heroes: result }))
  }, [dispatch, getHeroes])

HeroesScreen.js

useEffect(() => {
  setHeroes()
}, [setHeroes])

添加getHeroes作为对useCallback显示不必要的依赖

如果您查看AppProvider中的代码,您会在每次呈现时创建一个新的setHeroes function。 因此,如果您将setHeroes作为依赖项添加到useEffect ,则代码将执行如下操作:

  1. AppProvider渲染, setHeroes被创建,state 是初始 state
  2. HeroesScreen渲染的组件层次结构中的某处。 调用useEffect进而调用setHeroes
  3. getHeroes并发送一个动作
  4. reducer 更改了导致AppProvider重新渲染的 state
  5. AppProvider渲染, setHeroes是从头创建的
  6. 由于setHeroes发生了变化, useEffect再次执行,整个循环永远重复!

要解决此问题,您确实需要将setHeroes作为依赖项添加到useEffect ,然后使用useCallback将其包装:

const setHeroes = useCallback(async () => {
  const result = await getHeroes();
  dispatch({ type: SET_HEROES, heroes: result });
}, [getHeroes, dispatch]);

好问题,我也有这个问题,这是我的解决方案。 我正在使用 Typescript 但它也只能与 JS 一起使用。

UserProvider.tsx

import * as React from 'react'
import { useReducer, useEffect } from 'react'
import UserContext from './UserContext'
import { getUser } from './actions/profile'
import userReducer, { SET_USER } from './UserReducer'

export default ({ children }: { children?: React.ReactNode }) => {
  const [state, dispatch] = useReducer(userReducer, {})

  const getUserData = async () => {
    const { user } = await getUser()
    dispatch({ type: SET_USER, payload: { ...user } })
  }

  useEffect(() => {
    getUserData()
  }, [])

  return (
    <UserContext.Provider value={{ user: state }}>
      {children}
    </UserContext.Provider>
  )
}

然后用提供者包装你的应用程序

index.tsx

<UserProvider>
  <App />
</UserProvider>

然后使用上下文消费者我这样做

AnyComponent.tsx

<UserConsumer>
   {({ user }) => {
     ...
   }} 
</UserConsumer

或者你也可以像这样使用它

const { user } = useContext(UserContext)

让我知道它是否有效。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM