React总结篇之二_设计高质量的React组件

一、易于维护组件的设计要素
1.组件划分的原则:高内聚低耦合
(1)高内聚:将逻辑紧密相关的内容放在一个组件内。React可以将展示内容的JSX、定义行为的JavaScript代码、甚至定义样式的css,都可以放在一个JavaScript文件中,因此React天生具有高内聚的特点。
(2)低耦合:不同组件之间的依赖关系要尽量弱化,也就是每个组件要尽量独立。

创新互联服务项目包括台州网站建设、台州网站制作、台州网页制作以及台州网络营销策划等。多年来,我们专注于互联网行业,利用自身积累的技术优势、行业经验、深度合作伙伴关系等,向广大中小型企业、政府机构等提供互联网行业的解决方案,台州网站推广取得了明显的社会效益与经济效益。目前,我们服务的客户以成都为中心已经辐射到台州省份的部分城市,未来相信会继续扩大服务区域并继续获得客户的支持与信任!


二、React组件的数据
1.数据分类:
React组件的数据分为两种:prop和state。无论prop或者state改变,都可能引发React的重新渲染。那在设计一个组件时,什么时候选择prop什么时候选择state呢?原则很简单:prop是组件的对外接口,state是组件的内部状态,对外用prop,内部用state。

2.Prop:property的缩写,是从外部传递给组件的数据,一个React组件通过定义自己能够接受的prop就定义了自己的对外公共接口。我们先从外部世界看prop是如何使用的:
上面创建了名为SampleButton的组件实例,使用了名字分别为id、borderWidth、onClick和style的prop。此处注意:HTML组件属性的值都是字符串类型,即使是内嵌JavaScript,也依然是字符串形式表示代码。但React组件的prop所能支持的类型除了字符串、可以是任何一种JavaScript语句支持的数据类型,如:数字类型、函数类型、style的值是一个包含color字段的对象,当prop的类型不是字符串类型时,在JSX中必须用花括号{}将prop值包住,所以style的值有两层花括号,外层花括号代表的是JSX的语法,内层花括号表示这是一个对象常量。
Prop要反馈数据给外部世界,使用函数类型的prop,这等于父组件给了子组件一个回调函数,子组件在恰当的时机调用函数类型的prop,可以带上必要额参数,这样就把数据传递给外部世界。

3.React要求render函数只能返回一个元素!

4.组件内部接收传入的prop:
(1)首先是构造函数,如下:
class Counter extends Component {
constructor(props){
super(props);
this.onClickIncrementButon = this.onClickIncrementButon.bind(this);
this.onClickDecrementButon = this.onClickDecrementButon.bind(this);
this.state = {
count : props.initValue || 0
}
}
}
注意:组件定义自己的构造函数,一定要在构造函数的第一行通过super调用父类也就是React.Component的构造函数;如果未调用,那么组件实例被构造之后,类实例的所有成员函数就无法通过this.props访问到父组件传过来的props值。很明显,给this.props赋值是React.Component构造函数的工作之一。在Counter的构造函数中还给两个成员函数绑定了当前this的执行环境,因为ES6方法创建的React类并不自动给我们绑定this到当前实例对象。

我的理解:
Component是React内的一个基类,用于继承和创建React自定义组件。ES6规范下的面向对象实现起来非常精简,class关键字可以快速创建一个类,而Component类内的所有属性和方法均可以通过this访问。换而言之,在Component内的任意方法内,可以通过this.xxx的方式调用该Component的其他属性和方法。
constructor:类的默认方法,通过new命令生成对象实例时,自动调用该方法。一个类必须有constructor方法,如果没有显示定义,一个空的constructor方法会被默认添加;
super:子类必须在constructor方法中调用super方法,否则新建实例会报错。这是因为子类自己的this对象,必须先通过父类的构造函数完成塑造,得到与父类同样的属性和方法,然后再对其加工,加上子类自己的实例属性和方法。如果不调用super方法,子类就得不到this对象(子类是没有自己的 this 对象的,它只能继承自父类的 this 对象,然后对其进行加工,而super( )就是将父类中的this对象继承给子类的。没有 super,子类就得不到 this 对象);
super(props):在constructor中可以使用this.props;
this:在ES6语法下,类的每个成员函数在执行时的this并不是和类实例自动绑定的。而在构造函数中,this就是当前组件实例。所以,为了方便将来的调用,往往在构造函数中将这个实例的特定函数绑定this为当前实例。this.onClickIncrementButon = this.onClickIncrementButon.bind(this),就是通过bind方法让当前实例中onClickIncrementButon函数被调用时,this始终指向当前组件实例

(2)读取prop值:
在构造函数中可以通过props获得传入的prop值,在其他函数中可通过this.props访问传入prop的值。
const {caption} = this.props;
以上,我们使用了ES6的解构赋值语法,从this.props中获得了名为caption的prop值。

5.propTypes检查:
组件支持哪些prop;
每个prop应该是什么样的格式;
如:对于Counter组件的propTypes定义代码如下:
Counter.propTypes = {
caption : propTypes.string.isRquired,
initVlue : propTypes.number
}
其中要求caption必须是string类型,initVlue必须是number类型,另外,caption带上了isRquired,表示使用Counter组件必须指定caption,而initVlue如果没有也没关系。
propTypes虽然能在开发阶段发现代码中的问题,但是放在产品环境就不大合适了:首先,占用一些代码空间,耗CPU计算资源;其次,在产品环境下做propTypes检查没有什么帮助,在最终用户的浏览器Console中输出这些错误信息没什么意义。所以,最好的方式是,开发者在代码中定义propTypes,但在发布产品代码时,用一种自动的方式将propTypes去掉。现有的babel-react-optimize具有这个功能,可以通过npm安装,但是应该确保只在发布产品代码时使用它。

6.初始化state:
通常在组件类的构造函数结尾处初始化state,如下:
constructor(props){
......
this.state = {
count:props.initValue || 0
}
}
因为initValue是一个可选的props,考虑到父组件没有指定这个props值的情况,我们优先使用传入属性的initVlue,如果没有,就使用默认值0。
组件的state必须是一个JavaScript对象!!!
以上,可使用React的defaultProps功能,如下:
Counter.defaultProps = {0}

7.改变组件的state,如下:
onClickIncrementButton () {
this.setState({count : this.state.count + 1})
}
在代码中,this.state可以读取到组件当前的state。注意:改变组件state必须要使用this.setState函数而不能直接去修改this.state。直接修改this.state的值,虽然事实上改变了组件的内部状态,但只是野蛮的修改了state,并没有驱动组件进行重新渲染,这样就无法反应this.state值的变化;而this.setState()函数所做的事情,首先是改变this.state的值,然后驱动组件经历更新过程,这样才有机会让this.state里新的值出现在界面上。

8.prop和state的对比

  • prop用于定义外部接口,state用于记录内部状态
  • prop的赋值在外部世界使用组件时,state的赋值在组件内部
  • 组件不应该改变prop的值,而state存在的目的就是让组件来改变的
    组件的state存在就是为了被修改,每一次通过this.setState函数修改state就改变了组件的状态,然后通过渲染过程把这种变化体现出来;
    但是,组件是绝不应该去修改传入的props值的,假如父组件包含多个子组件,然后把一个JavaScript对象作为props传给这几个子组件,而某个组组件竟然改变了这个对象的内部值,那么,接下来其他子组件读取这个对象会得到什么值呢?当时读取了修改过的值,但是其他子组件是每次渲染都读取这个props的值呢?还是只读一次就用哪个最初的值呢?都有可能。也就是说一个子组件去修改props中的值,可能让程序陷入一团混乱,违背了React设计的初衷
    UI = render(data)
    React组件扮演的是render函数的角色,应该是一个没有副作用的纯函数,修改props的值,是一个副作用,组件应该避免。
    严格来说,React并没有办法阻止你去修改传入的props对象,所以,每个开发者就把这当做一个规定。

三、组件的生命周期
生命周期可能会经历3个过程:

  • 装载过程(Mount):组件第一次在DOM数中渲染的过程;
  • 更新过程(Update):组件被重新渲染的过程;
  • 卸载过程(Unmount):组件从DOM中删除的过程。

1.装载过程,依次调用的函数如下:

  • constructor
  • getInitialState
  • getDefaultProps
  • componentWillMount
  • render
  • componentDidMount

1.1 constructor
是ES6中每个类的构造函数,要创建一个组件类的实例,当然会调用对应的构造函数;
注意:无状态的React组件不需要定义构造函数,一个组件需要构造函数,往往为下面的目的:
(1)初始化state,因为组件生命周期中任何函数都可能会访问state,那整个生命周期中第一个被调用的构造函数自然是初始化state最理想的地方;
(2)绑定成员函数的this环境

1.2 getInitialState和getDefaultProps
待完成........

1.3 render
一个React组件必须要实现render函数,因为所有React组件的父类React.Component类对除render类之外的生命周期函数都有默认实现。
render函数并不做实际的渲染动作,而是返回一个JSX描述的结构,最终由React来操作渲染过程。某些特殊组件的作用不是渲染界面,或者,组件在某些情况下选择没有东西可画,那就让render函数返回一个null或者false,等于告诉React,这个组件这次不需要渲染任何DOM元素。
注意:render函数应该是一个纯函数,完全根据this.props和this.state来决定返回的结果,而且不要产生任何副作用。在render中调用this.setState是错的,因为一个纯函数不应该引起状态的改变。

1.4 componentWillMount和componentDidMount
在装载过程中,componentWillMount在调用render之前被调用,componentDidMount在调用render之后被调用。
通常不用定义componentWillMount函数,所有可以在componentWillMount中做的事情都可以提前到constructor中去做。
而componentDidMount作用很大!render函数在调用完之后,componentDidMount并不是会被立刻调用,componentDidMount被调用的时候,render函数返回的东西已经引发了渲染,组件已经被装载到了DOM树上。由于render函数只返回一个JSX表示的对象,然后React库根据返回对象来决定如何渲染。而React库肯定要把所有组件返回的结果综合起来,才知道如何产生对应的DOM修改,所以,当有多个组件时,只有React库调用多个组件的render函数之后,才有可能完成装载,这时才会依次调用各个组件的componentDidMount函数作为装载过程的收尾。
两者的区别:componentWillMount可以在服务器端被调用,也可以在浏览器端被调用,而componentDidMount只能在浏览器端被调用!

2.更新过程:
componentWillReceiveProps
shouldComponentUpdate
componentWillUpdate
render
componentDidUpdate
并不是所有的更新过程都会执行全部函数

2.1 componentWillReceiveProps(nextProps)
只要是父组件的render函数被调用,在render函数里面被渲染的子组件就会经历更新过程,不管父组件传给子组件的props有没有改变,都会触发子组件的componentWillReceiveProps。所以,这个函数有必要把传入参数nextProps和this.props做对比,nextProps代表的是这一次渲染传入的props值,this.props代表上一次渲染时的props值,只有两者有变化的时候才调用this.setState更新内部状态,这是提高React性能的重要方式。
注意:通过this.setState方法触发的更新过程不会调用这个函数,这是因为这个函数适合根据新的props值(nextProps)来计算出是不是要更新内部状态state。更新组件内部状态的方法就是this.setState,如果this.setState的调用导致componentWillReceiveProps再一次被调用,那就是一个死循环了。

2.2 shouldComponentUpdate(nextProps,nextState)
render和shouldComponentUpdate是React生命周期函数中唯二两个要求有返回结果的函数。render函数返回的结果用于构造DOM对象,而shouldComponentUpdate返回一个布尔值,告诉React库这个组件在这次更新过程中是否要继续。
在更新过程中,React库首先调用shouldComponentUpdate函数,若返回true,会继续更新过程,接着调用render函数,若是false,停止更新过程。另外,通过this.setState函数引发更新过程,并不是立刻更新组件的state值,在执行到函数shouldComponentUpdate的时候,this.state依然是this.setState函数执行之前的值,因此,在shouldComponentUpdate函数中,可进行比较nextProps、nextState、this.props、this.state是否发生变化,若变化则继续更新,是提高React性能的重要方式。

2.3 componentWillUpdate和componentDidUpdate
如果shouldComponentUpdate返回true,React接下来会依次调用对应组件的componentWillUpdate、render和componentDidUpdate函数。
和装载过程不同的是,当在服务器端使用React渲染时,componentDidUpdate函数并不是只在浏览器端才执行的。无论更新过程发生在服务器端还是浏览器端,该函数都会被调用。

3.卸载过程
componentWillUnmount
当React组件要从DOM树上删除掉之前,对应的componentWillUnmount会被调用,所以该函数适合做一些请理性的工作。
componentWillUnmount往往和componentDidMount有关,比如,在componentDidMount用非React的方法创造了一些DOM元素,如果不管可能会造成内存泄漏,那就需要在componentWillUnmount中将这些创建的DOM元素清理掉。


四、组件向外传递数据
1.React中state和prop的局限
React使用state来存储状态的一个缺点就是:数据的冗余和重复,另外使用prop在多个父子组件中传递数据时也有同样的问题。


文章题目:React总结篇之二_设计高质量的React组件
本文URL:http://scyanting.com/article/gijhdi.html