问题描述
我有一张使用状态树的卡片列表。 我有一个选择器获取作业列表,然后两个选择器使用该选择来映射和组合一个对象以传递到卡片中。
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 我如何使用重新选择以进行记忆计算。
目前对每个项目运行过滤器和查找,但由于结果相同,组件不会重新呈现。