事件绑定中使用的函数.bind将始终重新呈现,因为它不是纯函数



在React上处理渲染性能时,想知道解决此性能问题的最佳方法是什么。(为了清晰起见,代码过于简化)

var TodoList = React.createClass({
    getInitialState: function () {
        return { todos: Immutable.List(['Buy milk', 'Buy eggs']) };
    },
    onTodoChange: function (index, newValue) {
        this.setState({
            todos: this.state.todos.set(index, newValue)
        });
    },
    render: function () {
        return (
            this.state.todos.map((todo, index) =>
                <TodoItem
                    value={todo}
                    onChange={this.onTodoChange.bind(null, index)} />
            )
        );
    }
});

假设只更改了一个待办事项。首先,将调用TodoList.render()并再次开始重新渲染列表。由于TodoItem.onChange通过bind绑定到事件处理程序,因此,每次调用TodoList.render()时,TodoItem.onChange都会有一个新值。这意味着,即使我们将React.addons.PureRenderMixin应用于TodoItem.mixins,即使它们的value没有改变,也不会重新渲染一个而是所有的TodoItem

我相信有多种方法可以解决这个问题,我正在寻找一种优雅的方法来解决这个问题。

在React中循环使用UI组件时,需要使用key属性。这样可以进行同类比较。您可能会在控制台中看到以下警告。

警告:数组或迭代器中的每个子级都应该有一个唯一的"键"道具。

使用index属性作为键是很诱人的,如果列表是静态的,这可能是一个不错的选择(如果只是为了消除警告的话)。但是,如果列表是动态的,则需要更好的密钥。在这种情况下,我会选择todo项本身的值。

render: function () {
    return (
        this.state.todos.map((todo, index) => (
            <TodoItem
                key={todo}
                value={todo}
                onChange={this.onTodoChange.bind(null, index)}
            />
        ))
    );
}

最后,我认为你关于onChange性质的猜想是错误的。是的,每次渲染时都是不同的特性。但该属性本身没有渲染效果,因此在虚拟DOM比较中没有发挥作用。

更新

(此答案已根据以下对话更新。)

虽然对onChange这样的非基于渲染的道具的更改确实不会触发重新渲染,但它会触发虚拟DOM比较。根据应用程序的大小,这可能很昂贵,也可能很琐碎。

如果有必要避免这种比较,则需要实现组件的shouldComponentUpdate方法,以忽略对非基于渲染的道具的任何更改。例如

shouldComponentUpdate(nextProps) {
    const ignoreProps = [ 'onChange' ];
    const keys = Object.keys(this.props)
        .filter((k) => ignoreProps.indexOf(k) === -1);
    const keysNext = Object.keys(nextProps)
        .filter((k) => ignoreProps.indexOf(k) === -1);
    return keysNext.length !== keys.length ||
        keysNext.some((k) => nextProps[k] !== this.props[k]);
}

如果使用state,还需要比较nextStatethis.state

最新更新