【问题标题】:redux calculated data re-rendering how to fixredux计算数据重新渲染如何修复
【发布时间】:2021-06-01 06:53:24
【问题描述】:

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

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 端而不是在组件端组合这些数据?

【问题讨论】:

    标签: reactjs performance redux rerender redux-selector


    【解决方案1】:

    您可以为 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, attachedJobKey: 1 },
        { 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,
          data: [{ id: 1, 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 上合并数据,因为您实际上会复制数据(合并后的数据本质上是您已经拥有的数据的副本)。组合数据是派生值,这些值不应存储在 state 中,而是在选择器中计算,在需要时使用 memoization 重新计算(此处未完成),但如果您有兴趣,可以查看 here 我如何使用 reselect记忆计算。

    此时过滤器和查找在每个项目上运行,但由于结果相同,因此不会重新渲染组件。

    【讨论】:

      猜你喜欢
      • 2023-04-07
      • 1970-01-01
      • 2021-05-03
      • 2021-10-04
      • 2020-11-27
      • 2018-03-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多