Подтвердить что ты не робот

Реагировать на регрессирование табличных действий и редукторов

У меня есть данные, управляемые сервером, которые отображаются в таблицах на нескольких страницах.

В настоящее время у меня есть следующие действия таблицы

pageChange
pageSizeChange
sort
load
loaded

У меня есть фильтры на некоторых страницах, которые должны запускать загрузку.

У меня есть несколько объектов, которые используют эту функциональность, которая будет разделять большинство вышеперечисленных логических функций, но для этого потребуется отдельная функция определения нагрузки.

Моя мысль заключалась в том, чтобы иметь действия, которые принимают идентификатор таблицы как параметр, а затем имеют функцию createTableReducer, которая также принимает этот идентификатор и будет монтировать узлы таблицы внутри объектов, аналогично createModelReducer в react-redux-form

Как я могу инициировать действия загрузки конкретных объектов из моих общих действий, не используя что-то вроде саги о редуксе?

Мне было интересно создать компонент более высокого порядка и передать ему функцию загрузки, но я не думаю, что это решило бы мою проблему. Я мог бы также вызвать как действие изменения, так и действие загрузки из самого компонента таблицы, но это не похоже на приятное решение.

4b9b3361

Ответ 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*.