美文网首页前端文章翻译
ReactJS组件state的最佳实践

ReactJS组件state的最佳实践

作者: mervynyang | 来源:发表于2016-08-10 15:33 被阅读122次

    当我们写React应用的时候,知道在组件中何时使用state何时不使用state,是非常重要的。在这篇文章中,我将回顾我所认为的使用state的最佳实践:

    1. 如果component没有自己的数据,那么其他数据便不应该影响它的state。
    2. 用于描述组件的state尽可能简单。
    3. 运算和条件判断移动到render函数。

    这些规则如果有特殊情况,应该在适当的时候违反。不过如果你能够一直都遵循它们的话,你会发现你的component更容易解耦,测试更容易写,而且整个应用的bug也很少。下面让我们仔细看看这些规则:

    1. 如果component没有自己的数据,那么其他数据便不应该影响它的state

    第一,可能是最重要的一点,component的state不应该依赖于props传递。当然props可能向子组件传递state,例如,在一个普通的input组件中,为了禁用input的文字输入,我可能选择一个disabled的prop。但是当我说'state'的时候,我是明确的指component的state属性。所以,当state开始依赖于它的props的时候,你可能会发现这是一段不好的代码。看看以下代码片段:

        import React from 'react';
    
        class UserWidget extends React.Component {
    
        // BAD: 通过props接收到的值设置this.state.fullName
            constructor (props) {
                this.state = {
                    fullName: `${props.firstName} ${props.lastName}`
                };
            }
    
            render () {
                var fullName = this.state.fullName;
                var picture = this.props.picture;
    
                return (
                    <div>
                        <img src={picture} />
                        <h2>{fullName}</h2>
                    </div>
                );
            }
        }
    

    以上代码有什么问题?一开始可能不是很明显,但是假如firstName或者lastName改变了,UserWidget组件的视图将不会改变。构造函数在组件初始化渲染执行之后只会调用一次,因此fullName的值永远是第一次渲染时候的值。React新手可能经常会犯这样的错误,因为setState是更新组件视图的最简单且最明显的方式。

    你应该问问你自己,该组件是否拥有这些数据,内部的firstName和lastName创建了吗?如果没有,那么state不应该依赖便不应该依赖于这些数据。那么最好的避免这个问题的方式是什么呢?在render函数里面计算fullName的值。

        render () {
            var fullName = `${this.props.firstName} ${this.props.lastName}`;
            // ...
        }
    

    把fullName移动到render函数里面之后,我们将不用再关心fullName的值是否更新了。当props改变的时候,React会运行一个钩子函数--componentWillReceiveProps,然而我还是会考虑这种反模式,因为它不需要增加项目的复杂性。

    当然,如果你在组件初始化之后不关心props,那么这条规则将不会适用。

    当使用React.createClass代替extends React.Component时候,则用getInitialState代替constructor。
    有时候,"state"将需要设置一些值,在flux模式中,可能是根控制器组件监听不同的stores。

    2. 用于描述组件的state尽可能简单

    你应该尽可能的简单的去描述一个组件的状态。在很多种情况下,这意味着用布尔值是更好的方式。

    思考下面的例子,我们有一些组件,它们在state里面的class属性是基于clicked和hovered事件改变的。(不管你信不信,我看到过很多这样的例子)

        import React from 'react';
        var cx = React.addons.classSet;
    
        class ArbitraryWidget extends React.Component {
    
            constructor() {
                this.state = {
                    classes: []
                };
            }
    
            // BAD: 当鼠标滑过的时候,把'hover'push到this.state.classes
            handleMouseOver() {
                var classes = this.state.classes;
                classes.push('hover');
    
                this.setState({ classes: classes });
            }
    
            // BAD: 当鼠标离开的时候,从this.state.classes移除'hover'
            handleMouseOut() {
                var classes = this.state.classes;
                var index = classes.indexOf('hover');
                classes.splice(index, 1);
    
                this.setState({ classes: classes });
            }
    
            // BAD: 被点击的时候,在this.state.classes切换'active'
            handleClick() {
                var classes = this.state.classes;
                var index = classes.indexOf('active');
    
                if (index != -1) {
                    classes.splice(index, 1);
                } else {
                    classes.push('active');
                }
    
                this.setState({ classes: classes });
            }
    
            render() {
                var classes = this.state.classes;
    
                return ( 
                    <div className={cx(classes)}
                        onClick={this.handleClick.bind(this)}
                        onMouseOver={this.handleMouseOver.bind(this)}
                        onMouseOut={this.handleMouseOut.bind(this)}
                    />
                )
            }
        }
    

    这个组件可以运行,但是我持保留意见。它现在的state是一个存着字符串类型的数组,this.state.classes = ['active', 'hover'],不仅代码的可读性很差,而且改变起来特别麻烦。假如有其他组件依赖于我的这个class的数组,那么查看这个数组是否包含hover肯定比查看hover的布尔值是什么的难度要大。我们需要重构这段代码,用布尔值代表组件是否应该有这些class,例如isHovering === true意味着我是否应该使用hover这个class。

        import React from 'react';
        var cx = React.addons.classSet;
    
        class ArbitraryWidget extends React.Component {
    
            constructor() {
                this.state = {
                    isHovering: false,
                    isActive: false
                };
            }
    
            // GOOD: 当鼠标滑过的时候,this.state.isHovering设置为true
            handleMouseOver() {
                this.setState({ isHovering: true });
            }
    
            // GOOD: 当鼠标离开的时候,this.state.isHovering设置为false
            handleMouseOut() {
                this.setState({ isHovering: false });
            }
    
            // GOOD: 被点击的时候,改变this.state.active
            handleClick() {
                var active = !this.state.isActive;
                this.setState({ isActive: active });
            }
    
            render() {
                // use the classSet addon to concat an array of class names together
                var classes = cx([
                    this.state.isHovering && 'hover',
                    this.state.isActive && 'active'
                ]);
    
                return ( 
                    <div className={cx(classes)}
                        onClick={this.handleClick.bind(this)}
                        onMouseOver={this.handleMouseOver.bind(this)}
                        onMouseOut={this.handleMouseOut.bind(this)}
                    />
                );
            }
        }
    
    

    为了使用这些state的布尔值,我们必须在render函数里面计算class数组。但是,我们增强了代码的可读性,this.state.isHovering远比this.state.classes.indexOf('hover') != -1更能代表组件实际的状态。这个组件更容易扩展和测试,因为我们不需要考虑数组的构建。

    我想再说一遍,你应该始终以用最简单的方式表示state为目标。这并不一定意味着你只能存储布尔值,有可能是深层嵌套的对象,也可能是数字、字符串或者函数。
    想象一下作为其他人,试图观察组件返回的一个class的数组的状态,这个数组对于你是否有用呢?当然没有。相比之下布尔值isActive是更为可行的。我希望你明白我的意思。

    3. 运算和条件判移动到render函数

    在前面的两条规则中,这一条其实已经提到了。然而,它仍然是值得注意的。尽可能的在render函数中进行最后一步运算。虽然这样也许会略慢于其他方法,但它能确保最少的重定向组件,在轻微的性能提升之前,我们应该更注重代码的可读性和扩展性。

    我需要连接prop中的firstName和lastName?把它移动到render函数。我的组件需要使用哪个class?在render函数中做决定。如果我的todo列表没有任何项目,我应该显示在text框中显示一个placeholder?在render函数中做决定。我需要格式化电话号码?在render函数中做决定。我该如何呈现出子组件?在render函数中做决定。我今天要吃午饭吗?在render函数中做决定。

    当然,你不要把所有代码都放在一个函数里面。相反,最好把它们分割成合适的helper函数(用一个好的名字),关键是你用render函数做太多的事情的话,应该减少它的复杂性。你可以用一个前缀来表示helper函数。例如:

        // GOOD: Helper function to render fullName
        renderFullName () {
            return `${this.props.firstName} ${this.props.lastName}`;
        }
    
        render () {
            var fullName = this.renderFullName();
            // ...
        }
    

    CPU密集运算

    因为我建议你把所有的东西都推迟到render函数中,它会导致CPU密集运算也会推迟。为了避免重复复杂的渲染,考虑memoization的功能。

    不要把变量存储到component实例上

    不要像下面这样做:

        class ArbitraryWidget extends React.Component {
    
            constructor () {
                this.derp = 'something';
            }
    
            handleClick () {
                this.derp = 'somethingElse';
            }
    
            render () {
                var something = this.derp;
            }
        }
    

    这是非常不好的,不仅是因为你没有遵守用this.state存储值的约定,而且this.derp改变的时候,不会自动触发render。

    原文地址
    Best Practices for Component State in React.js

    相关文章

      网友评论

        本文标题:ReactJS组件state的最佳实践

        本文链接:https://www.haomeiwen.com/subject/pssbjttx.html