【问题标题】:react redux architecting table actions and reducers反应 redux 架构表动作和减速器
【发布时间】:2016-03-07 10:38:59
【问题描述】:

我将服务器驱动的数据显示在多个页面的表格中。

我目前有以下表格动作

pageChange
pageSizeChange
sort
load
loaded

我在一些需要触发加载的页面上有过滤器。

我有多个使用此功能的实体,它们将共享上述大部分逻辑,但需要单独定义加载功能。

我的想法是有一个将表 ID 作为参数的操作,然后有一个 createTableReducer 函数,该函数也将采用此 ID 并将表节点安装在实体中,类似于 react-redux-form 中的 createModelReducer

如何在不使用 redux saga 之类的东西的情况下从我的通用操作中触发实体特定的加载操作?

我想知道创建一个高阶组件并将其传递给加载函数,但我认为这不会解决我的问题。我也可以从表格组件本身调用更改操作和加载操作,但这似乎不是一个好的解决方案。

【问题讨论】:

    标签: javascript reactjs redux react-redux


    【解决方案1】:

    所以,这是一个非常开放的问题,实现它的可能性几乎是无限的......但是,无论如何我都会试一试。这个答案不是教条,它只是一种可能的方法。

    让我们首先希望我们可以随心所欲地摆桌子。为了区分一个表和另一个表,我们所要做的就是传递一个tableId 属性,它将Table 链接到存储中的相应数据。

    <Table tableId='customers' />
    <Table tableId='products' />
    <Table tableId='orders' />
    <Table tableId='transactions' />
    

    接下来,让我们定义一个要使用的状态形状。每个顶级键都匹配我们希望为其存储数据的tableId。你可以让这个形状成为你想要的任何形状,但我提供了这个,所以当我在后面的代码中引用东西时,你必须做更少的心理可视化。您也可以随意命名顶级 tableId 键,只要您一致地引用它们即可。

    {
       customers: {
         page: 0,
         sortBy: 'id',
         cols: [
           {id: 'id',         name: 'name'},
           {id: 'username',   name: 'Username'},
           {id: 'first_name', name: 'First Name'},
           {id: 'last_name',  name: 'Last Name'},
           ...
         ],
         rows: [
           {id: 1, username: 'bob', first_name: 'Bob', last_name: 'Smith', ...},
           ...
         ]
      },
      products: {
        ...
      },
      orders: {
        ...
      }
      ...
    }
    

    现在,让我们把困难的部分弄清楚:Table 容器。一次消化的内容很多,但别担心,我会逐个分解重要的部分。

    containers/Table.js

    import React from 'react';
    import {connect} from 'react-redux';
    import actions as * from './actions/';
    
    const _Table = ({cols, rows, onClickSort, onClickNextPage}) => (
      <div>
        <table>
          <thead>
            <tr>
             {cols.map((col,key) => (
               <Th key={key} onClickSort={onClickSort(col.id)}>{col.name}</Th>
             )}
            </tr>
          </thead>
          <tbody>
            {rows.map((row,key) => ...}
          </tbody>
        </table>
        <button onClick={onClickNextPage}>Next Page</button>
      </div>
    );
    
    const Table = connect(
      ({table}, {tableId}) => ({    // example: if tableId = "customers" ...
        cols: table[tableId].cols,  // state.table.customers.cols
        rows: table[tableId].rows   // state.table.customers.rows
      }),
      (dispatch, {tableId}) => ({
        onClickSort: columnId => event => {
          dispatch(actions.tableSortColumn(tableId, columnId));
          // example: if user clicks 'last_name' column in customers table
          // dispatch(actions.tableSortColumn('customers', 'last_name'));
        },
        onClickNextPage: event => {
          dispatch(actions.tableNextPage(tableId))
        }
      })
    )(_Table);
    
    export default Table;
    

    如果你只从这篇文章中学到一件事,那就这样吧:

    这里要注意的是mapStateToPropsmapDispatchToProps accepts a second argument 称为ownProps

    // did you know you can pass a second arg to these functions ?
    const MyContainer = connect({
      (state, ownProps) => ...
      (dispatch, ownProps) => ...
    })(...);
    

    在我上面写的容器中,我正在解构我们关心的每个变量。

    // Remember how I used the container ?
    // here ownProps = {tableId: "customers"}
    <Table tableId="customers" />
    

    现在看看我是如何使用connect

    const Table = connect(
      // mapStateToProps
      ({table}, {tableId}) => ...
        // table = state.table
        // tableId = ownProps.tableId = "customers"
    
    
      // mapDispatchToProps
      (dispatch, {tableId}) => ...
        // dispatch = dispatch
        // tableId = ownProps.tableId = "customers"
    )(_Table);
    

    这样,当我们为底层组件 (_Table) 创建调度处理程序时,我们将在处理程序中使用 tableId。如果您不希望 _Table 组件本身甚至不必关心 tableId 属性,这实际上还不错。

    接下来注意onClickSort 函数的定义方式。

    onClickSort: columnId => event => {
      dispatch(actions.tableSortColumn(tableId, columnId));
    }
    

    _Table 组件将此函数传递给 Th 使用

    <Th key={key} onClickSort={onClickSort(col.id)}>{col.name}</Th>
    

    看看它是如何columnId 发送到这里的处理程序的?接下来,我们将看到Th 是如何发送event 的,它最终会调度操作。

    components/Table/Th.js

    import React from 'react';
    
    const Th = ({onClickSort, children}) => (
      <th>
        <a href="#sort" onClickSort={event => {
          event.preventDefault();
          onClickSort(event);
        }}>{children}</a>
      </th>
    );
    
    export default Th;
    

    如果您不想要 event,则不必保留它,但我想我会向您展示如何附加它,以防您想将其用于某事。

    继续前进……

    actions/index.js

    您的操作文件看起来很标准。请注意,我们在每个表格操作中都提供了对 tableId 的访问权限。

    export const TABLE_SORT_COLUMN = 'TABLE_SORT_COLUMN';
    export const TABLE_NEXT_PAGE = 'TABLE_NEXT_PAGE';
    
    export const tableSortColumn = (tableId, columnId) => ({
      type: TABLE_SORT_COLUMN, payload: {tableId, columnId}
    });
    
    export const tableNextPage = (tableId) => ({
      type: TABLE_NEXT_PAGE, payload: {tableId}
    });
    
    ...
    

    最后,您的tableReducer 可能看起来像这样。同样,这里没有什么特别的。您将对操作类型执行常规switch,然后相应地更新状态。您可以使用action.payload.tableId 影响状态的适当部分。只要记住我提出的状态形状。如果您选择不同的状态形状,则必须更改此代码以匹配

    const defaultState = {
      customers: {
        page: 0,
        sortBy: null,
        cols: [],
        rows: []
      }
    };
    
    // deep object assignment for nested objects
    const tableAssign = (state, tableId, data) =>
      Object.assign({}, state, {
        [tableId]: Object.assign({}, state[tableId], data)
      });
    
    const tableReducer = (state=defaultState, {type, payload}) => {
      switch (type) {
        case TABLE_SORT_COLUMN:
          return tableAssign(state, payload.tableId, {
            sortBy: payload.columnId
          });
        case TABLE_NEXT_PAGE:
          return tableAssign(state, payload.tableId, {
            page: state[payload.tableId].page + 1
          });
        default:
          return state;
      }
    };
    

    备注:

    我不打算讨论表数据的异步加载。 Redux 文档中已经对这样的工作流程进行了详细介绍:Async Actions。您选择 redux-thunk、redux-promise 或 redux-saga 取决于您。选择你最了解的!正确实现TABLE_DATA_FETCH 的关键是确保像我在其他onClick* 处理程序中那样调度tableId(以及您需要的任何其他参数)。

    【讨论】:

    • 嗨,谢谢你的回答,上面有一些很好的概念;)我一直在尝试找出最好的方法之一是一旦触发了 tableSort 操作,如何然后触发加载操作,以及该加载操作应在何处/如何插入到表中
    • @Tom,您特别说“不使用 redux-saga 之类的东西”,但是在学习了 redux-saga 之后,我不确定我是否知道如何提出任何其他建议。我最近在为我的应用程序建模控制流时遇到了麻烦,gaearon 自己回答了我的问题,敦促我查看 redux-saga。我真的很高兴我花时间去理解它,因为它极大地清理了我的应用程序。
    • 当然,我很喜欢它,并希望尽快完全采用它,虽然目前我使用它只是为了处理表格操作的副作用,并且它只是在这个地方使用我认为这很令人困惑,因此正在寻找替代方法。
    • 我现在在想我也许可以将加载动作作为道具传入,然后在 mapDispatchToProps 中调度后调用此动作..
    • 一切都好。我可能会建议你从这篇文章中尽你所能,并用它运行一段时间。尽量使用actions.tablefetchData(tableId, itemsPerPage, page) 实现获取代码。 redux-thunk 或 redux-promise 应该可以帮助您完成大部分工作。至少,如果您遇到额外的麻烦,您的下一个问题会更加具体。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-11-06
    • 2017-06-14
    • 1970-01-01
    • 2022-01-04
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多