React.js 组件的 props vs state

Stella981
• 阅读 496

看到一篇无比蛋疼的文章:https://www.oschina.net/translate/exploring-reacts-state-propagation。我不针对翻译,只是针对这篇文章想提出的概念,说了跟没说一样,傻傻的。

要搞清楚React.js的props和state,必须要结合组件(组件类)从初始化到装载(mount)的过程来说。

组件类

假定我们通过以下的代码,创建了一个组件:

var SimpleComponent = React.createClass({
    getDefaultProps: function() {
        return {
            name: ''
        };
    },
    getInitialState: function() {
        return {
        };
    },
    render: function() {
        return <div>A simple component named {this.props.name}</div>;
    }
});

这里SimpleComponent是一个组件类。

组件虚拟对象

当我们要使用这个组件类的时候,可以写这样的代码:

<SimpleComponent name="abc"/>

实际上上述代码会转移成下面的js代码:

React.createElement(SimpleComponent, null)

通过React.createElement函数创建的,并不是SimpleComponent的组件类实例(即不是new SimpleComponent),而是一个不可写入的静态对象(Object),并且这个静态对象,具有props,没错,就是SimpleComponent组件类定义的getDefaultProps所返回的值。

我将这个静态对象称为组件虚拟对象,他持有了SimpleComponent的初始化所需的props,并且我有理由怀疑他是全局唯一的(当然我没精力去验证了)。

所以,要知道,当我们在jsx文件中,写了大量看似html标签的结构,他实际上并没有逐个创建对应的组件实例,取而代之的,仅仅是构造一大堆组件虚拟对象(静态Object)和传入参数而已。

注意,这个时候组件虚拟对象是没有state的。

组件实体(实例)

当一个组件虚拟对象被装载(mount)后——这里所谓装载,就是将一个或一大段组件虚拟对象插入到实际的DOM节点中时,才会创建出真实的组件实体(实例)。

创建组件的实体的时候,才会真正将调用参数传入(比如上面的name=abc),并且混入组件虚拟对象上的props。

当组件被实体化后,才真正的拥有state。

而我们实际在编写SimpleComponent这个组件类的方法时,都是假定针对的是组件实体的状态下进行的。

最后,总结为一个流程图来说明的话:

React.js 组件的 props vs state

props只读、不可变?

我只能说,这是一个误区。首先,假定你拥有React Development tools的话,通过修改React.js的组件props,也能促使组件发生变化。就好像这样:

React.js 组件的 props vs state

React.js 组件的 props vs state

这个问题,仔细想想就能明白是别人给你挖了个坑(不是React.js,而是这些所谓的教程)。假定我们设计两个组件:

var AComponent = React.createClass({
    getDefaultProps: function() {
        return {
            name: ''
        };
    },
    getInitialState: function() {
        return {
            name: this.props.name
        };
    },
    render: function() {
        return <div>I'm {this.props.name}</div>;
    }
});

var BComponent = React.createClass({
    getDefaultProps: function() {
        return {
            name: ''
        };
    },
    getInitialState: function() {
        return {
            name: this.props.name
        };
    },
    render: function() {
        return <div>
            <AComponent name={this.state.name} />
            <button type="button" onClick={e => this.setState({name: 'Jack'})}>change name</button>
        </div>;
    }
});

在B组件内,是使用state.name作为A组件的props.name传入的,如果是只读、不可变的话,那么B组件的按钮被点击后,B组件的state.name改变了,永远也不可能触发到A组件state更新了。

所以,严格来说,props不可变,只在组件虚拟对象这个阶段,因为那是一个不可写入的静态对象。

更进一步,从组件类到组件实例,实际上是完成了从props将数据传递给state。这是一个必然性的数据流向,无论多少次组件嵌套,这种数据流向都不会改变。所以一般而言,很少会去动(注意这里只是说动,但没有说不去读)一个组件实例的props,而直接操作state。

传统的OOP编程,并没有严格的数据流向,到底是属性到方法,还是方法到属性,还是属性到属性,完全由开发者自己掌控,你爱怎么写,就怎么写。这在前端组件的源码中,形成了很大的伪装性,当发生错误的时候,你完全不知道该从哪里开始着手,需要花一定的时间去解读、调试,然后才能定位问题(当然有好的规范可以相对的缓解类似的问题)。

而React.js则提供一种简单明确的机制(而且他只提供了这个机制而已),这也是React.js开发过程中会形成的一个思维定式。每当你开始编写一个组件,你就需要设计,这个组件应该具有哪些动态(交互性)特征需要放入state,而这些state又是怎么从props传递过去的。仅此而已,剩下的就是组件的界面逻辑开发而已。

state - 状态

这个应该不用多介绍了吧,React.js的卖点,组件实例的界面和自身的state动态结合,而且性能还相当的好。

不过其实这里也有一个小坑,就是当你在设计一个组件的时候,应该认真的思考哪些特征属于动态的。如果你在state里装载了大量的数据,要监控这些state的更新,还是相当的时间(其实真正很大的数据,大概会有3-5ms的延迟,但这个数据真的很大了)。所以,特别注意:

setState方法是异步的!!!

setState方法是异步的!!!

setState方法是异步的!!!

重要的事情说三次,是的,所以如果你这样写,可能会导致你的界面发生不可预料的后果:

this.setState(bigData);
// ...干点别的事情

正确的写法应该这样:

this.setState(bigData, function() {
    // ...干点别的事情
});

当你使用React.js开发的时间长了的话,慢慢的也会调整自己的思维方式,更加集中的设计state。这个下面还会提到。

es6 or typescript

如果可能的话,如果改用es6或者ts,上面的代码,将会简单很多很多:

class SimpleComponentES6 extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            name: this.props.name || ''
        }
    }
    
    render() {
        return <div>A simple component named {this.props.name}</div>;
    }
}

代码是不是顿时清晰了很多?

ts现在已经迈进2.0时代了,各方面都比过去完善了很多。假如你的项目,超过1000行JS,最好能换成ts,因为ts的语言特性,会让你的代码的自动提示得更加明确,而不像js或者es6,有的没的相似的不是的方法、属性提示你一大堆。

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
6个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Easter79 Easter79
3年前
SVG跟随父级DIV自适应
后台返回过来的是这样的SVG标签<svgwidth"100%"height"100%"version"1.1"xmlns"http://www.w3.org/2000/svg"<gtransform"translate(00)"
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
3年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Stella981 Stella981
3年前
Docker 部署SpringBoot项目不香吗?
  公众号改版后文章乱序推荐,希望你可以点击上方“Java进阶架构师”,点击右上角,将我们设为★“星标”!这样才不会错过每日进阶架构文章呀。  !(http://dingyue.ws.126.net/2020/0920/b00fbfc7j00qgy5xy002kd200qo00hsg00it00cj.jpg)  2
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
11个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这