Ответ 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
. Это много, чтобы переварить все сразу, но не волнуйтесь, я разбиваю важные биты индивидуально.
<сильные > контейнеры /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
принимает второй аргумент под названием 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
, который, наконец, отправляет действие.
компоненты/Таблица/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
, если вы этого не хотите, но я решил, что покажу вам, как прикрепить его, если вы хотите использовать его для чего-то.
Перемещение по...
действия /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. Ваш выбор редукции, редукции-обещания или сокращения-саги зависит от вас. Выберите то, что вы понимаете лучше всего! Ключом к реализации TABLE_DATA_FETCH
должным образом является то, что вы отправляете tableId
(вместе с любыми другими параметрами, которые вам нужны), как это было в других обработчиках onClick*
.