首页 > 解决方案 > 当 Redux 存储发生变化时,阻止整个组件重新渲染

问题描述

我有一个功能组件,可以呈现带有行的表格。每一行都填充了作为数组存储在 Redux 存储中的数据。每行也可以被删除,如果在给定的时间范围内(通过吐司),用户也可以撤消删除。

我面临的问题是,当从 Redux 存储数组中删除 1 行的数据时,整个表都会重新渲染。撤消删除后将其添加回数组时也是如此。

我知道在每次删除/撤消时,都会创建一个新数组,该数组会保留在商店中,因此会重新渲染。但是有没有办法可以优化它,这样整个表就不会每次都重新渲染?

标签: reactjsreduxreact-redux

解决方案


您可以选择行组件中的每个项目,如果您重新创建所有项目,因为您重新获取它们然后添加比较功能:

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

const initialState = {
  items: [
    { id: 1, name: 'one' },
    { id: 2, name: 'two' },
    { id: 3, name: 'three' },
  ],
};
//action types
const REMOVE = 'REMOVE';
//action creators
const remove = (itemId) => ({
  type: REMOVE,
  payload: { itemId },
});
const reducer = (state, { type, payload }) => {
  if (type === REMOVE) {
    const { itemId } = payload;
    return {
      ...state,
      items: state.items.filter(({ id }) => id !== itemId),
    };
  }
  return state;
};
//selectors
const selectItems = (state) => state.items;
const createSelectItemById = (itemId) =>
  createSelector([selectItems], (items) =>
    items.find(({ id }) => id === itemId)
  );
//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 = React.memo(function Item({ itemId }) {
  console.log('creating jsx for item:', itemId);
  const selectItemById = React.useMemo(
    () => createSelectItemById(itemId),
    [itemId]
  );
  const item = useSelector(selectItemById);
  const dispatch = useDispatch();
  return (
    <li>
      <pre>{JSON.stringify(item)}</pre>
      <button onClick={() => dispatch(remove(itemId))}>
        remove
      </button>
    </li>
  );
});
const App = () => {
  const items = useSelector(selectItems);
  return (
    <ul>
      {items.map(({ id }) => (
        <Item key={id} itemId={id} />
      ))}
    </ul>
  );
};

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>

<div id="root"></div>

如果这对您不起作用,因为所有项目都已重新创建,那么您可以在 createSelectItemById 中构建内存:

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

const initialState = {
  items: JSON.stringify([
    { id: 1, name: 'one' },
    { id: 2, name: 'two' },
    { id: 3, name: 'three' },
  ]),
};
//action types
const REMOVE = 'REMOVE';
//action creators
const remove = (itemId) => ({
  type: REMOVE,
  payload: { itemId },
});
const reducer = (state, { type, payload }) => {
  if (type === REMOVE) {
    const { itemId } = payload;
    return {
      ...state,
      items: JSON.stringify(
        JSON.parse(state.items).filter(
          ({ id }) => id !== itemId
        )
      ),
    };
  }
  return state;
};
//selectors
// re creating items every time
const selectItems = (state) => JSON.parse(state.items);
const createSelectItemById = (itemId) => {
  //comparing last to current and returning last
  //  if items are the same
  let lastItem = {};
  return createSelector([selectItems], (items) => {
    const currentItem = items.find(
      ({ id }) => id === itemId
    );
    //only need to compare name, you may need more comparisons
    if (currentItem.name === lastItem.name) {
      return lastItem;
    }
    lastItem = currentItem;
    return currentItem;
  });
};
//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 = React.memo(function Item({ itemId }) {
  console.log('creating jsx for item:', itemId);
  const selectItemById = React.useMemo(
    () => createSelectItemById(itemId),
    [itemId]
  );
  const item = useSelector(selectItemById);
  const dispatch = useDispatch();
  return (
    <li>
      <pre>{JSON.stringify(item)}</pre>
      <button onClick={() => dispatch(remove(itemId))}>
        remove
      </button>
    </li>
  );
});
const App = () => {
  const items = useSelector(selectItems);
  return (
    <ul>
      {items.map(({ id }) => (
        <Item key={id} itemId={id} />
      ))}
    </ul>
  );
};

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>

<div id="root"></div>


推荐阅读