所以,这是一个非常开放的问题,实现它的可能性几乎是无限的......但是,无论如何我都会试一试。这个答案不是教条,它只是一种可能的方法。
让我们首先希望我们可以随心所欲地摆桌子。为了区分一个表和另一个表,我们所要做的就是传递一个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;
如果你只从这篇文章中学到一件事,那就这样吧:
这里要注意的是mapStateToProps 和mapDispatchToProps 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(以及您需要的任何其他参数)。