美文网首页前端文章翻译
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的最佳实践

    当我们写React应用的时候,知道在组件中何时使用state何时不使用state,是非常重要的。在这篇文章中,我将...

  • Flink State 最佳实践

    本文主要分享与交流 Flink 状态使用过程中的一些经验与心得,当然标题取了“最佳实践”之名,希望文章内容能给读者...

  • 菜鸡ReactJs-03 组件状态state

    1:getInitialState:function( ){ } 这个是设置一个默认值。 2:this.state...

  • 前端系列(三)react获取子组件属性的方法

    组件组合有几种情况 父组件不保存任何state,子组件A和子组件B各自保存state 父组件保存state,子组件...

  • React内部状态state

    state   React组件的数据分为两种:props和state,props是组件的对外接口,state是组件...

  • 第四章 深入理解组件

    4.1 组件state 4.1.1 如何设计state 组件的state是一个组件的UI呈现的完整状态集合。(UI...

  • React 使用State Hook

    本文摘自 https://zh-hans.reactjs.org/docs/hooks-state.html Ho...

  • React进阶-State&生命周期

    state state是指组件的当前状态。组件根据状态state呈现不同的UI展示。 一旦状态(数据)更改,组件就...

  • state和props

    state state 是私有的,并且完全受控于当前组件。class组件才有state。 将函数组件转化成clas...

  • react之剖析setState

    react是通过state管理组件,state的变化会导致组件的更新,而state的变化则是通过setState(...

网友评论

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

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