避免在 React render 中使用箭头函数和 bind

在平时写 React 组件的时候,我们会习惯于像下面这样去绑定事件

class App extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            users: [
                { id: 1, name: 'Cory' }, 
                { id: 2, name: 'Meg' }, 
                { id: 3, name: 'Bob' }
            ]
        };
    }

    deleteUser = id => {
        this.setState(prevState => {
            return { 
                users: prevState.users.filter( user => user.id !== id)
            }
        })
    }

    render() {
        return (
            <div>
                <h1>Users</h1>
                <ul>
                    { 
                        this.state.users.map(user => {
                            return (
                                <li>
                                    <input 
                                        type="button" 
                                        value="Delete" 
                                        onClick={() => this.deleteUser(user.id)} 
                                    />
                                    {user.name}
                                </li>
                            )
                        })
                    }
                </ul>
            </div>
        );
    }
}

这里的 onDeleteClick 绑定了一个箭头函数,每次 render 的时候,这个函数都会重新创建。假如这个 users 是个很长的列表,有 1 万多项,每次 render 的时候,这个函数就要被创建 1 万多次。如果这里传递的是一个组件

<User
    name={user.name}
    id={user.id}
    onClick={() => this.deleteUser(user.id)}
/>

由于传进去的 onDeleteClick 每次 render 都变化,所以组件 User 即使在相同的 idname 的情况下,每次也都会发生改变。当然用 bind 去绑定也存在上面说的这些问题。

<li>
    <input 
        type="button" 
        value="Delete" 
        onClick={this.deleteUser.bind(this, user.id)} 
    />
    {user.name}
</li>

那要怎么解决呢?下面这两篇文章提到了两种比较巧妙的解决办法

第一种方法,抽象成一个子组件,在子组件里面去传参数

class User extends React.Component {
    onDeleteClick = () => {
        this.props.onDelete(this.props.id); // 在这里传入 id
    };

    render() {
        return (
            <li>
                <input 
                type="button" 
                value="Delete" 
                onClick={this.onDeleteClick} 
                />
                {this.props.name}
            </li>
        );
    }
}

class App extends React.Component {
    // ...

    render() {
        return (
            <div>
                <h1>Users</h1>
                <ul>
                    { 
                        this.state.users.map(user => {
                            return (
                                <User
                                    id={user.id}
                                    name={user.name}
                                    onDelete={this.deleteUser}
                                />
                            )
                        })
                    }
                </ul>
            </div>            
        );
    }
}

第二种方法,把参数加到 html 元素的属性上面

class App extends React.Component {
    // ...

    deleteUser = e => {   
        const id = e.target.value
        this.setState(prevState => {      
            return { 
                users: prevState.users.filter( user => user.id !== id)
            }    
        })  
    } 

    render() {
        return (
            <div>
                <h1>Users</h1>
                <ul>
                    { 
                        this.state.users.map(user => {
                            return (
                                <li>
                                    <input 
                                        type="button" 
                                        value={user.id}
                                        onClick={this.deleteUser} 
                                    />
                                    {user.name}
                                </li>
                            )
                        })
                    }
                </ul>
            </div>            
        );
    }
}

如果你有用 eslint,也可以启用一条规则来检测,No .bind() or Arrow Functions in JSX Props

参考

2017.08.28
Powered by Cubi,  Hosted by Coding Pages