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 让根组件在 react-native 中重新渲染(开源项目)
如何防止扩展的 React-Table 行在重新渲染时折叠?
Material-UI 和 Redux-form,点击 select 时重新渲染选项,如何防止?