redux 计算数据重新渲染如何修复

问题描述

我有一张使用状态树的卡片列表。 我有一个选择器获取作业列表,然后两个选择器使用该选择来映射和组合一个对象以传递到卡片中。

function ProductionJobs(props) {
    const jobData = useSelector(getDataForProductionJobs);
    const dataData = useSelector(getDataForProduction(jobData.map(x=>x.jobsessionkey)));
    const matData =  useSelector(getMatsForProduction(jobData.map(x=>x.jobsessionkey)));
    console.count("renders");
    const combined = jobData.map(x=> {
        const foundData = dataData.find(y=>y.attachedJobKey===x.jobsessionkey);
        const foundMaterial = matData.filter(z=>z.attachedJobkey===x.jobsessionkey);
        const obj = {...x}
        if(foundData) obj.foundData = foundData;
        if(foundMaterial)  obj.material = foundMaterial;      
        return obj;
    });
    const productionCards = combined.map(x=><ProductionJobCard key={x.jobsessionkey} props={x} />)
    return <div className="ProductionJobs">{productionCards}</div>  
}

问题是 - 这会不必要地重新渲染。有没有更好的方法在 reducer 端而不是在组件端组合这些数据?

解决方法

您可以为 ProductionJobCard 创建一个容器,并在过滤 matData 项时使用shallowEqual 作为第二个参数选择该容器中的组合项。

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

const initialState = {
  productionJobs: [
    { jobSessionKey: 1 },{ jobSessionKey: 2 },{ jobSessionKey: 3 },{ jobSessionKey: 4 },],data: [{ id: 1,attachedJobKey: 1 }],mat: [
    { id: 1,attachedJobKey: 1 },{ id: 2,{ id: 3,attachedJobKey: 2 },};
//action types
const TOGGLE_MAT_ITEM = 'TOGGLE_MAT_ITEM';
const TOGGLE_DATA_ITEM = 'TOGGLE_DATA_ITEM';
const TOGGLE_JOB = 'TOGGLE_JOB';
//action creators
const toggleMatItem = () => ({ type: TOGGLE_MAT_ITEM });
const toggleDataItem = () => ({ type: TOGGLE_DATA_ITEM });
const toggleJob = () => ({ type: TOGGLE_JOB });
const reducer = (state,{ type }) => {
  if (type === TOGGLE_MAT_ITEM) {
    //toggles matItem with id of 3 between job 1 or 2
    return {
      ...state,mat: state.mat.map((matItem) =>
        matItem.id === 3
          ? {
              ...matItem,attachedJobKey:
                matItem.attachedJobKey === 2 ? 1 : 2,}
          : matItem
      ),};
  }
  if (type === TOGGLE_DATA_ITEM) {
    //toggles data between job 1 or 3
    const attachedJobKey =
      state.data[0].attachedJobKey === 1 ? 3 : 1;
    return {
      ...state,attachedJobKey }],};
  }
  if (type === TOGGLE_JOB) {
    //adds or removes 4th job
    const productionJobs =
      state.productionJobs.length === 3
        ? state.productionJobs.concat({ jobSessionKey: 4 })
        : state.productionJobs.slice(0,3);
    return { ...state,productionJobs };
  }
  return state;
};
//selectors
const selectDataForProductionJobs = (state) =>
  state.productionJobs;
const selectData = (state) => state.data;
const selectMat = (state) => state.mat;
const selectDataByAttachedJobKey = (attachedJobKey) =>
  createSelector([selectData],(data) =>
    data.find((d) => d.attachedJobKey === attachedJobKey)
  );
const selectMatByAttachedJobKey = (attachedJobKey) =>
  createSelector([selectMat],(mat) =>
    mat.filter((m) => m.attachedJobKey === attachedJobKey)
  );
//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 ProductionJobCard = (props) => (
  <li><pre>{JSON.stringify(props,undefined,2)}</pre></li>
);
const ProductionJobCardContainer = React.memo(
  function ProductionJobCardContainer({ jobSessionKey }) {
    //only one item,no need to shallow compare
    const dataItem = useSelector(
      selectDataByAttachedJobKey(jobSessionKey)
    );
    //shallow compare because filter always returns a new array
    //  only re render if items in the array change
    const matItems = useSelector(
      selectMatByAttachedJobKey(jobSessionKey),shallowEqual
    );
    console.log('rendering:',jobSessionKey);
    return (
      <ProductionJobCard
        dataItem={dataItem}
        matItems={matItems}
        jobSessionKey={jobSessionKey}
      />
    );
  }
);
const ProductionJobs = () => {
  const jobData = useSelector(selectDataForProductionJobs);
  const dispatch = useDispatch();
  return (
    <div>
      <button onClick={() => dispatch(toggleMatItem())}>
        toggle mat
      </button>
      <button onClick={() => dispatch(toggleDataItem())}>
        toggle data
      </button>
      <button onClick={() => dispatch(toggleJob())}>
        toggle job
      </button>
      <ul>
        {jobData.map(({ jobSessionKey }) => (
          <ProductionJobCardContainer
            key={jobSessionKey}
            jobSessionKey={jobSessionKey}
          />
        ))}
      </ul>
    </div>
  );
};

ReactDOM.render(
  <Provider store={store}>
    <ProductionJobs />
  </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>

你不应该在reducer上合并数据,因为你本质上是复制数据(合并数据本质上是你已经拥有的数据的副本)。组合数据是派生值,此类值不应存储在状态中,而应在选择器中计算,需要时使用记忆重新计算(此处未完成)但如果您有兴趣,可以查看 here 我如何使用重新选择以进行记忆计算。

目前对每个项目运行过滤器和查找,但由于结果相同,组件不会重新呈现。