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

Posted

技术标签:

【中文标题】redux计算数据重新渲染如何修复【英文标题】:redux calculated data re-rendering how to fix 【发布时间】: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 端而不是在组件端组合这些数据?

【问题讨论】:

【参考方案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记忆计算。

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

【讨论】:

以上是关于redux计算数据重新渲染如何修复的主要内容,如果未能解决你的问题,请参考以下文章

redux 连接的组件如何知道何时重新渲染?

如何使用 redux 让根组件在 react-native 中重新渲染(开源项目)

如何防止扩展的 React-Table 行在重新渲染时折叠?

Material-UI 和 Redux-form,点击 select 时重新渲染选项,如何防止?

当在 ReactJs React-Redux 中仅创建或更新列表中的一个项目时,如何停止重新渲染整个项目列表?

ReactJS表单应用程序:如何阻止父组件重新渲染? /使用redux共享状态的正确方法