如何在 React Context 中使用 reducer 从数组中添加/删除?

Posted

技术标签:

【中文标题】如何在 React Context 中使用 reducer 从数组中添加/删除?【英文标题】:How do I add/remove from an array with the reducer within React Context? 【发布时间】:2021-07-06 12:20:48 【问题描述】:
import React from 'react';
import useReducer, createContext  from 'react';

let initialState = 
  associatedOrgs:[],
;

const reducer = (state, action) => 
  switch (action.type) 
    case "logout":
      return initialState;
    
    case "set-associated-org":
      return ...state, associatedOrgs: action.payload ;
    
    default:
      return state;
  
;

export const UserContext = createContext(initialState);

const UserContextProvider = (children) => 
  const [state, dispatch] = useReducer(reducer, initialState);

  const logout = () => 
    dispatch(
      type: "logout",
    );
  ;

  const setAssociatedOrg = (org) => 
    dispatch(
      type: "set-associated-org",
      payload: org,
    );
  ;
  
  return(
    <UserContext.Provider
      value=
        associatedOrgs: state.associatedOrgs,
        logout,
        setAssociatedOrg
      
    >
      children
    </UserContext.Provider>
  )
;

export default UserContextProvider;

【问题讨论】:

我希望你不介意,但我修正了你帖子的格式,在这样做的同时我注意到你在渲染部分有一些嵌套错误,在传递给上下文的道具中提供者。你实际上并没有说明你遇到了什么问题,所以我可能不在基地,但这可能是你问题的一部分。 【参考方案1】:

我会这样做:

case "add-associated-org":
  return 
    ...state,
    associatedOrgs: state.associatedOrgs.concat(action.orgToAdd)
  

case "remove-associated-org":
  return 
    ...state,
    associatedOrgs: state.associatedOrgs.filter(org => org !== action.orgToRemove)
  

然后你只需要定义几个动作创建者将这些事件分派给你的reducer; set-associated-orgs 的模式应该可以工作(尽管最好在组件之外定义它们,因为它们可以是纯函数,并且不需要让引擎在每次渲染时重新构造它们)。


编辑

问:如果在外部创建,您将如何将调度传递给动作创建者?

答:(很好的问题。)我会让每个动作创建者成为一个纯函数,然后将它们全部包装在一个函数中,将它们绑定到特定的dispatch。不幸的是,这导致了性能增益的逆转。稍后会详细介绍。

这是一个简单粗暴的初稿:

function getActions( dispatch ) 
  const logout = () => 
    dispatch(
      type: "logout",
    );
  ;
  
  const setAssociatedOrg = (org) => 
    dispatch(
      type: "set-associated-org",
      payload: org,
    );
  ;
  
  return 
    logout,
    setAssociatedOrg
  

然后在我拥有的组件内部:

const [state, dispatch] = useReducer(reducer, initialState);
const actions = getActions(dispatch)

这不会很好地扩展,为了测试目的,每个动作创建者都应该被导出,所以更好的模式应该是这样的:

// src/actions/index.js
export function logout( dispatch )  /* impl */ 
export function setAssociatedOrgs( dispatch, orgs )  /* impl */ 

// create a collection of all action creators so we can process them all
const ACTIONS =  logout, setAssociatedOrgs 

// app code should only ever import this function
// the other functions would be private if not for unit-testing
export default function bindActionsToDispatch( dispatch ) 
  return Object.keys(ACTIONS).reduce(( hash, actionCreatorName ) => (
    ...hash,
    [actionCreatorName]: ACTIONS[actionCreatorName].bind(undefined, dispatch)
  ), )

这有利有弊。

这种方法的一个怪癖是每个动作创建者都定义了一个额外的dispatch arg。聚合器函数使调用代码不可见(因此,您仍然可以使用actions.setAssociatedOrgs(orgs)),但我不知道您的 IDE 语法建议会是什么样子。

就像我说的那样,我假设这将重新引入在每个组件渲染上重新定义所有动作创建者的性能损失,这很糟糕。现在我用一堆额外的代码来做这件事,这更糟。

所以,如果我这样做,我会创建一个基于 useReducer 的自定义 React 钩子;它将创建状态容器,定义根减速器,绑定所有动作创建者。 (此时绑定动作创建者是有意义的,因为商店支持的动作集取决于它所包含的减速器,并且当您将根减速器提供给useReducer 时设置。) '我很确定您可以将所有这些设置为只执行一次的方式,随后的渲染返回相同的商店和商店绑定的动作创建者。

使用新的钩子可能如下所示:

let [ orgState, orgActions ] = useDucksStore(initialOrgState, orgReducers, orgActionCreators)

// and then:
orgActions.setAssociatedOrgs(newOrgs)

退一步说,我认为“动作创建者”模式不适合裸露的useReducer 钩子。 useReducer 很好,因为它是轻量级的,如果您决定每个 dispatch 调用都必须包含在“鸭子”式动作创建器中,那么您就放弃了它。如果 Ducks 是你的菜,你真的应该构建一个自定义钩子来促进并强制执行这些访问模式。否则,您将不得不在每个想要类似地管理状态的组件内完成所有这些工作,更不用说在每个 blit 上重建微型 Ducks 生态系统的性能成本。

【讨论】:

如果在外部创建,您将如何将调度传递给动作创建者?

以上是关于如何在 React Context 中使用 reducer 从数组中添加/删除?的主要内容,如果未能解决你的问题,请参考以下文章

如何在Redux中使用React Context API?

如何在提供者的值中传递数组和 setArray(React、Typescript、Context)

在 React.js 的父组件中使用 react-router 时,如何使用 react context API 将数据从父组件传递到子组件?

如何获取数据并存储在 React Context API 中?

在 Next.js 中,如何使用来自 getServerSideProps 的数据更新 React Context 状态?

React 系列 02❤️ Custom Hooks 中使用 React Context