问题
I have a React / Redux app that renders a grid of PureComponents. I want the components to only re-render when a prop value has changed, but in fact all components re-render on any update to store. The issue appears to be caused by an array property.
I've reproduced the issue in this sandbox. In this minimal example, two instances of the Cell component are rendered. Each displays a value that is separately recorded in the store, and can be incremented or decremented separately with a button.
Cell.js
import React, { PureComponent } from "react";
import { connect } from "react-redux";
import { incrementAction, decreaseAction } from "./actions";
class Cell extends PureComponent {
render() {
const { index, value, incrementAction, decreaseAction } = this.props;
console.log("render cell with index", index);
return (
<div>
<h1>{value}</h1>
<button onClick={incrementAction}>increment</button>
<button onClick={decreaseAction}>decrease</button>
</div>
);
}
}
const mapStateToProps = (state, ownProps) => ({
value: ownProps.index === 1 ? state.value1 : state.value2,
myArray: [0, 1, 2]
});
const mapDispatchToProps = (dispatch, ownProps) => ({
incrementAction: () => dispatch(incrementAction(ownProps.index)),
decreaseAction: () => dispatch(decreaseAction(ownProps.index))
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(Cell);
If you check the console, you should see that when you click one button, both Cells re-render.
If you comment out the myArray prop in mapStateToProps, the behaviour changes so that only the cell you've clicked re-renders.
So it appears that PureComponent is re-rendering on any change to the store, because of the static array property.
In my real app the array would come from the store also, and Cell should re-render if an array value changes, but the sandbox example shows how even a static array property triggers re-render.
Is there any way to provide an array prop to a PureComponent and have it only re-render when a prop changes? Thank you.
Edit: I've updated the sandbox to move myArray into the store as suggested by Domino987 and to add a function to calculate the sub-array required by the Cell component - this is what my real app does. I've added memoization with reselect and re-reselect, and I've made it a functional component instead of PureComponent. As far as I can see, this is now working - only one Cell re-renders when you click a button. Yay!
In actions.js:
import createCachedSelector from "re-reselect";
export function getMyArray(state, index) {
console.log("getMyArrayCached for index", index);
return state.myArray;
}
export function getIndex(state, index) {
return index;
}
export const getMyArrayCached = createCachedSelector(
getMyArray,
getIndex,
(myArray, index) =>
myArray.map(elm => {
return elm[index - 1];
})
)((_state_, index) => index);
in reducer.js:
const initialState = {
value1: 0,
value2: 0,
myArray: [[1, 2], [1, 2], [1, 2]]
};
in Cell.js:
const mapStateToProps = (state, ownProps) => {
const value = ownProps.index === 1 ? state.value1 : state.value2;
return {
value,
myArray: getMyArrayCached(state, ownProps.index)
}};
回答1:
You are creating a new array on every mapStateToProps call with myArray: [0, 1, 2].
Because of this, mapStateToProps always returns an object with a new instance of an array.
Redux than does a shallow comparison of the previous props. The references of myArray changed, since oyu created a new one during the call and all cells get udpated. By removing the myArray: [0, 1, 2] line, it works as expected. Move that array into redux, so that you do not generate a new one of every call.
The pure component is also useless, since redux already does a prop comparison and the pure component does the same thing again, so that is wasted since it always has the same result for redux and the pure component and is just more work.
Update: You are still creating a new array on every mapSateToPropsCall with the map function in getMyArray, since map returns a new array. Why do you not make the array static or memoize that, so that you only generate a new one, if needed?
Why do you not change how you save the data so that state.myArray[index] returns the needed array?
And you still can transform the PureCompoennt to Component.
来源:https://stackoverflow.com/questions/59641706/react-redux-pure-component-with-an-array-property-re-renders-when-the-array