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

时间:2020-05-27 09:01:27

标签: javascript reactjs react-hooks

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 +上下文作为状态管理的组件的简单设置。屏幕上显示了英雄,一切正常,但我收到警告Reach Hook useEffect has a missing dependency: 'setHeroes'。但是,如果我将其添加为依赖项,则会使应用Maximum depth update exceeded

崩溃

经过搜索,但我所看到的只是将函数fetch调用放在useEffect()内。我想按照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的依赖项,则linter显示不必要的依赖项

2 个答案:

答案 0 :(得分:3)

如果您查看AppProvider中的代码,则每次渲染时都会创建一个新的setHeroes函数。因此,如果您将setHeroes添加为对useEffect的依赖,则代码将执行以下操作:

  1. AppProvider渲染,创建setHeroes,状态为初始状态
  2. 在组件层次结构HeroesScreen下的某个位置进行渲染。调用useEffect,依次调用setHeroes
  3. getHeroes被调用并调度一个动作
  4. reducer更改导致AppProvider重新渲染的状态
  5. AppProvider渲染,setHeroes从头开始创建
  6. useEffect更改后,setHeroes再次执行,整个循环永远重复!

要解决此问题,您确实确实需要将setHeroes作为对useEffect的依赖项,然后使用useCallback包装它:

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

答案 1 :(得分:1)

很好的问题,我也遇到了这个问题,这是我的解决方案。我正在使用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>

然后使用Context Consumer我这样做

AnyComponent.tsx

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

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

const { user } = useContext(UserContext)

让我知道它是否有效。