Как лучше всего использовать react-redux со списком элементов? - PullRequest
1 голос
/ 09 июля 2020

У меня есть список элементов (JSON объектов, возвращаемых API) в хранилище redux. В настоящее время эти данные нормализованы, поэтому это просто массив. В нем будет примерно 10-30 объектов, и каждый объект будет иметь около 10 свойств.

В настоящее время у нас есть контейнер верхнего уровня (использует соединение response-redux), который считывает этот список из магазина и отображает массив и визуализирует компонент с именем ListItem, которому в основном требуется 3-4 поля объекта для визуализации пользовательского интерфейса.

Сейчас у нас нет проблем с производительностью. Но мне интересно, имеет ли смысл иметь компонент-контейнер redux для каждого элемента списка? Я думаю, что для этого потребуется нормализовать данные, и нам понадобится уникальный идентификатор каждого объекта, который должен быть передан в этот контейнер, который затем может читать объект из хранилища redux?

Этот вопрос возникает из документации Redux ' Руководство по стилю - https://redux.js.org/style-guide/style-guide#connect -more-components-to-read-data-from-the-store

Просто пытаюсь понять, какой способ рекомендуется использовать в этом сценарий.

Спасибо!

1 Ответ

0 голосов
/ 09 июля 2020

Интересно, имеет ли смысл иметь компонент-контейнер redux для каждого элемента списка?

Помимо, возможно, лучшей производительности, есть также лучшее повторное использование кода. Если logi c того, что элемент является определенным в списке, то как вы можете повторно использовать список для визуализации других элементов? item будет воссоздан для всех элементов, если вы создадите реквизиты в List вместо Item.

Список также нельзя использовать в качестве общего списка, который передает идентификатор в компонент Item.

const { Provider, useDispatch, useSelector } = ReactRedux;
const { createStore, applyMiddleware, compose } = Redux;
const { useMemo } = React;
const { createSelector } = Reselect;
const { produce } = immer;

const initialState = {
  people: {
    data: {
      1: { id: 1, name: 'Jon' },
      2: { id: 2, name: 'Marie' },
    },
    edit: {},
  },
  places: {
    data: {
      1: { id: 1, name: 'Rome' },
      2: { id: 2, name: 'Paris' },
    },
    edit: {},
  },
};
//action types
const SET_EDIT = 'SET_EDIT';
const CANCEL_EDIT = 'CANCEL_EDIT';
const SAVE = 'SAVE';
const CHANGE_TEXT = 'CHANGE_TEXT';
//action creators
const setEdit = (dataType, id) => ({
  type: SET_EDIT,
  payload: { dataType, id },
});
const cancelEdit = (dataType, id) => ({
  type: CANCEL_EDIT,
  payload: { dataType, id },
});
const save = (dataType, item) => ({
  type: SAVE,
  payload: { dataType, item },
});
const changeText = (dataType, id, field, value) => ({
  type: CHANGE_TEXT,
  payload: { dataType, id, field, value },
});
const reducer = (state, { type, payload }) => {
  if (type === SET_EDIT) {
    const { dataType, id } = payload;
    return produce(state, (draft) => {
      draft[dataType].edit[id] = draft[dataType].data[id];
    });
  }
  if (type === CANCEL_EDIT) {
    const { dataType, id } = payload;
    return produce(state, (draft) => {
      delete draft[dataType].edit[id];
    });
  }
  if (type === CHANGE_TEXT) {
    const { dataType, id, field, value } = payload;
    return produce(state, (draft) => {
      draft[dataType].edit[id][field] = value;
    });
  }
  if (type === SAVE) {
    const { dataType, item } = payload;
    return produce(state, (draft) => {
      const newItem = { ...item };
      delete newItem.edit;
      draft[dataType].data[item.id] = newItem;
      delete draft[dataType].edit[item.id];
    });
  }
  return state;
};
//selectors
const createSelectData = (dataType) => (state) =>
  state[dataType];
const createSelectDataList = (dataType) =>
  createSelector([createSelectData(dataType)], (result) =>
    Object.values(result.data)
  );
const createSelectDataById = (dataType, itemId) =>
  createSelector(
    [createSelectData(dataType)],
    (dataResult) => dataResult.data[itemId]
  );
const createSelectEditById = (dataType, itemId) =>
  createSelector(
    [createSelectData(dataType)],
    (dataResult) => (dataResult.edit || {})[itemId]
  );
const createSelectItemById = (dataType, itemId) =>
  createSelector(
    [
      createSelectDataById(dataType, itemId),
      createSelectEditById(dataType, itemId),
    ],
    (item, edit) => ({
      ...item,
      ...edit,
      edit: Boolean(edit),
    })
  );
//creating store with redux dev tools
const composeEnhancers =
  window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(
  reducer,
  initialState,
  composeEnhancers(
    applyMiddleware(() => (next) => (action) =>
      next(action)
    )
  )
);
const Item = ({ item, dataType }) => {
  const dispatch = useDispatch();
  return (
    <li>
      {item.edit ? (
        <React.Fragment>
          <input
            type="text"
            value={item.name}
            onChange={(e) =>
              dispatch(
                changeText(
                  dataType,
                  item.id,
                  'name',
                  e.target.value
                )
              )
            }
          />
          <button
            onClick={() =>
              dispatch(cancelEdit(dataType, item.id))
            }
          >
            cancel
          </button>
          <button
            onClick={() => dispatch(save(dataType, item))}
          >
            save
          </button>
        </React.Fragment>
      ) : (
        <React.Fragment>
          {item.name}
          <button
            onClick={() =>
              dispatch(setEdit(dataType, item.id))
            }
          >
            edit
          </button>
        </React.Fragment>
      )}
    </li>
  );
};
const createItem = (dataType) =>
  React.memo(function ItemContainer({ id }) {
    const selectItem = useMemo(
      () => createSelectItemById(dataType, id),
      [id]
    );
    const item = useSelector(selectItem);
    return <Item item={item} dataType={dataType} />;
  });

const Person = createItem('people');
const Location = createItem('places');
const List = React.memo(function List({ items, Item }) {
  return (
    <ul>
      {items.map(({ id }) => (
        <Item key={id} id={id} />
      ))}
    </ul>
  );
});
const App = () => {
  const [selectPeople, selectPlaces] = useMemo(
    () => [
      createSelectDataList('people'),
      createSelectDataList('places'),
    ],
    []
  );
  const people = useSelector(selectPeople);
  const places = useSelector(selectPlaces);
  return (
    <div>
      <List items={people} Item={Person} />
      <List items={places} Item={Location} />
    </div>
  );
};

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.0.5/redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/7.2.0/react-redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/reselect/4.0.0/reselect.min.js"></script>
<script src="https://unpkg.com/immer@7.0.5/dist/immer.umd.production.min.js"></script>
<div id="root"></div>

Если ваше приложение имеет повторяющиеся logi c, вы можете подумать о разделении компонента на контейнер и презентацию (контейнер также называется подключенным компонентом в redux) . Вы можете повторно использовать контейнер, но изменить его представление.

...