反应redux架构表动作和减速器

Tom*_*Tom 14 javascript reactjs redux react-redux

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

我目前有以下表格动作

pageChange
pageSizeChange
sort
load
loaded
Run Code Online (Sandbox Code Playgroud)

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

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

我的想法是让操作以表ID作为参数,然后有一个createTableReducer函数,该函数也将获取此ID并将表节点挂载到实体中,类似于react-redux-form中的 createModelReducer

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

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

Tha*_*you 19

所以,这是一个相当开放的问题,实现这个问题的可能性几乎是无限的...但是,无论如何我会对它进行一次拍摄.这个答案不是学说,只是一种可行的方法.

让我们首先希望我们可以随时随地摆放桌子.要将一个表与另一个表区分开来,我们所要做的就是传递一个tableId属性,该属性将a链接Table到商店中的相应数据.

<Table tableId='customers' />
<Table tableId='products' />
<Table tableId='orders' />
<Table tableId='transactions' />
Run Code Online (Sandbox Code Playgroud)

接下来,让我们定义一个要使用的状态形状.每个顶级键与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: {
    ...
  }
  ...
}
Run Code Online (Sandbox Code Playgroud)

现在,让我们解决困难的问题:Table容器.一次消化所有这一切都很多,但不要担心,我会单独分解重要的部分.

集装箱/ 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;
Run Code Online (Sandbox Code Playgroud)

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

这里要注意的是,mapStateToPropsmapDispatchToProps 接受第二个被称为的参数ownProps.

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

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

// Remember how I used the container ?
// here ownProps = {tableId: "customers"}
<Table tableId="customers" />
Run Code Online (Sandbox Code Playgroud)

现在看看我是如何使用的 connect

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


  // mapDispatchToProps
  (dispatch, {tableId}) => ...
    // dispatch = dispatch
    // tableId = ownProps.tableId = "customers"
)(_Table);
Run Code Online (Sandbox Code Playgroud)

这样,当我们为底层组件(_Table)创建调度处理程序时,我们将tableId在处理程序中为我们提供这些处理程序.如果你不想要的话,_Table组件本身甚至不必关心tableId道具,这实际上是一件好事.

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

onClickSort: columnId => event => {
  dispatch(actions.tableSortColumn(tableId, columnId));
}
Run Code Online (Sandbox Code Playgroud)

_Table分量通过该功能可以Th使用

<Th key={key} onClickSort={onClickSort(col.id)}>{col.name}</Th>
Run Code Online (Sandbox Code Playgroud)

看看它如何发送columnId到这里的处理程序?接下来,我们将看到如何Th发送event,最终发送动作.

组件/表/ 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;
Run Code Online (Sandbox Code Playgroud)

没有必要保留,event如果你不想要它,但我想我会告诉你如何附加它,以防你想利用它的东西.

继续......

动作/ 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}
});

...
Run Code Online (Sandbox Code Playgroud)

最后,你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;
  }
};
Run Code Online (Sandbox Code Playgroud)

备注:

我不打算进入表数据的异步加载.在redux文档中已经很好地详细说明了这样的工作流程:Async Actions.您选择的redux-thunk,redux-promise或redux-saga取决于您.选择你最了解的!TABLE_DATA_FETCH正确实施的关键是确保您tableId像其他onClick*处理程序一样调度(以及您需要的任何其他参数).