【问题标题】:What's the best way to use react-redux with list of items?将 react-redux 与项目列表一起使用的最佳方法是什么?
【发布时间】:2020-07-09 05:01:21
【问题描述】:

我在 redux 存储中有一个项目列表(从 API 返回的 JSON 对象)。这个数据目前是标准化的,所以它只是一个数组。它大约有 10-30 个对象,每个对象大约有 10 个属性。

目前我们有一个顶级容器(使用 react-redux connect),它从 store 中读取这个列表,映射数组并渲染一个名为 ListItem 的组件,它基本上需要对象中的 3-4 个字段来渲染用户界面。

我们现在对此没有任何性能问题。但我想知道为每个列表项设置一个 redux 容器组件是否有意义?我认为这需要对数据进行规范化,并且我们需要将每个对象的唯一 id 传递给该容器,然后该容器可以从 redux 存储中读取对象?

这个问题来自 Redux 文档的样式指南 - https://redux.js.org/style-guide/style-guide#connect-more-components-to-read-data-from-the-store

只是想了解在这种情况下推荐使用 react-redux 的方法。

谢谢!

【问题讨论】:

    标签: reactjs redux react-redux


    【解决方案1】:

    我想知道为每个列表项都有一个 redux 容器组件是否有意义?

    除了可能更好的性能之外,还有更好的代码重用。如果在列表中定义了项的逻辑,那么如何重用列表来呈现其他项?

    下面是一个示例,其中 item 是数据和编辑的组合,因此如果您在 List 而不是 Item 中创建道具,将为所有项目重新创建 item 的道具。

    List 也不能​​用作将 id 传递给 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>

    如果您的应用程序具有重复逻辑,您可能需要考虑将组件拆分为容器和表示(容器在 redux 中也称为连接组件)。您可以重复使用容器,但可以更改呈现方式。

    【讨论】:

      猜你喜欢
      • 2021-09-03
      • 2018-12-16
      • 2012-04-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-02-08
      • 1970-01-01
      相关资源
      最近更新 更多