想象一个List
组件,它会呈现一个项目列表,并可选择突出显示其中一个项目。
List
有以下道具:
items
- 要显示的项目highlightedItemIndex
- 突出显示的项目的索引(或null
,如果没有突出显示)itemProps
- 任意道具对象传递给每个项目 List
的{{1}}方法如下所示:
render()
问题是,每次<ul>
{
items.map((item, index) => {
const allItemProps = {
...itemProps,
className: index === highlightedItemIndex ? 'highlighted-item' : ''
};
return (
<Item item={item} itemProps={allItemProps} key={index} />
);
})
}
</ul>
List
被调用时,render()
都会获得导致Item
itemProps
Item
{{}}}的新对象1}}被调用,这在大多数情况下是不必要的,因为render()
没有真正改变(它深度等于前一个itemProps
,而不是itemProps
)。
想象一下上面的图片有1000个项目,当您悬停项目时,突出显示的项目会相应更改。每次突出显示的项目发生变化时,===
的{{1}}应该被调用两次,而不是1000次!
添加:
Item
解决了这个问题,但我觉得render()
方法可能非常昂贵。
Here is a playground来证明这个问题。 (我在列表中有5个项目而不是1000个,但你明白了。)
当prop是一个对象(在这种情况下是shouldComponentUpdate(nextProps) {
return JSON.stringify(nextProps) !== JSON.stringify(this.props);
}
)时,优化渲染的“React方式”是什么,或者对象道具不是一个好习惯?
答案 0 :(得分:1)
感谢您分享CodePen - 这对答案有很大帮助。
我已经更新了你的笔,所以它只在必要时呈现:http://codepen.io/amann/pen/dXZpyz?editors=0010
来自顶部的PureRenderMixin的一些复制粘贴代码,请向下滚动,直到看到Relevant code starts here
。
以下是最相关的部分:
class Item extends Component {
constructor(props) {
super(props);
this.onMouseEnter = this.onMouseEnter.bind(this);
this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this);
}
onMouseEnter() {
this.props.onMouseEnter(this.props.index);
}
render() {
const { item, isHighlighted } = this.props;
itemRenderCounter++;
let className = 'item';
if (isHighlighted) className += ' highlighted-item';
return (
<li className={className} onMouseEnter={this.onMouseEnter}>
{item}
</li>
);
}
}
class List extends Component {
static defaultProps = {
items: []
};
constructor(props) {
super(props);
this.onMouseLeave = this.onMouseLeave.bind(this);
this.onItemMouseEnter = this.onItemMouseEnter.bind(this);
}
onMouseLeave() {
this.props.onHighlightedItemChange(null);
}
onItemMouseEnter(index) {
this.props.onHighlightedItemChange(index);
}
render() {
const { items, highlightedItemIndex, onHighlightedItemChange} = this.props;
return (
<ul className="list-container" onMouseLeave={this.onMouseLeave}>
{items.map((item, index) =>
<Item
key={index}
index={index}
item={item}
isHighlighted={index === highlightedItemIndex}
onMouseEnter={this.onItemMouseEnter}
/>
)}
</ul>
);
}
}
class App extends Component {
constructor(props) {
super(props);
this.state = {highlightedItemIndex: 1};
this.onHighlightedItemChange = this.onHighlightedItemChange.bind(this);
}
onHighlightedItemChange(index) {
this.setState({highlightedItemIndex: index});
}
render() {
const { highlightedItemIndex } = this.state;
return (
<div>
<List
items={fruits}
highlightedItemIndex={highlightedItemIndex}
onHighlightedItemChange={this.onHighlightedItemChange}
/>
<div className="counter">
Item render() counter: {itemRenderCounter}
</div>
</div>
);
}
}
我认为您的初始问题可以通过使用扩展运算符<Item {...itemProps} .../>
来解决。这样就可以在Item级别进行浅层比较。但是,我认为还有其他一些事情可以改进,所以我改变了一点。
一些注意事项:
static defaultProps = {…}
而不是默认赋值更好。App
组件可能不应该关注鼠标事件,而是提供一些更多的语义回调,如onHighlightedItemChange
。根据您的使用案例,您还可以考虑将列表项突出显示的状态移至List
。Item
管理自己的类名是有意义的,只要父组件不覆盖某些样式。因此,我从语义属性isHighlighted
计算了它的类名。也许isHighlighted
也可以用于其他一些差异,将来可以独立于班级名称。<Item onMouseEnter={this.props.onHighlightedItemChange.bind(null, index)} />
的呈现方法中执行List
。这有点常见,因为子组件不必知道其index
。但是在你的情况下,由于性能似乎是一个问题,最好不要这样,因为bind
调用将在每个渲染上创建一个新函数,因此总是需要在比较中重新渲染shouldComponentUpdate
。希望这有帮助!
如果您要使用我的代码,请务必使用react-addons-pure-render-mixin的npm包而不是我的复制粘贴代码。顺便说一句。可能很快就会成为一个React.PureComponent
默认情况下会shouldComponentUpdate
- 这对你的情况会派上用场。
答案 1 :(得分:0)
你有正确的想法。只需定位真正定义此情况的值
shouldComponentUpdate(nextProps) {
return nextProps.itemProps.className !== this.props.itemProps.className;
}