React组件数据分为两种,prop和state,无论prop或state改变,都可能引发组件的重新渲染,那么,设计一个组件的时候,什么时候选择用prop什么时候选择用state呢?其实原则很简单,prop是组件的对外接口,state是组件的内部状态,对外用prop,内部用state。
一、React中的prop
在React中,prop是从外部传递给组件的数据,一个React组件通过定义自己能够接受的prop就定义了自己的对外公共接口。
每个React组件都是独立存在的模块,组件之外的一切都是外部世界,外部世界就是通过prop来和组件对话的。
当外部世界要传递一些数据给React组件,一个最直接的方式就是通过prop;同样,React组件要反馈数据到外部世界,也可以使用prop,因为prop的类型不限于纯数据,也可以是函数,函数类型的prop等于让父组件交给了子组件一个回调函数,子组件在恰当的时候调用函数类型的prop,可以带上必要的参数,这样就可以反过来把信息传递给外部世界。
为了演示属性的使用,我们构造一个应用包含两个组件,Counter组件和ControlPanel组件,其中ControlPanel组件是父组件,包含若干个Counter组件。
1、给prop赋值
我们先从外部世界来看,即CounterPanel是如何用prop传递信息给Counter的,代码如下:
class ControlPanel extends Component {
render() {
console.log('enter ControlPanel render');
return (
<div style={style}>
<Counter caption="First"/>
<Counter caption="Second" initValue={10} />
<Counter caption="Third" initValue={20} />
<button onClick={ () => this.forceUpdate() }>
Click me to re-render!
</button>
</div>
);
}
}
CounterPanel组件包含三个Counter组件实例,在CounterPanel的render函数中将这三个子组件实例用div包起来,因为React要求render函数只能返回一个元素。
在每个Counter组件实例中,都使用了caption和initValue两个prop。通过名为caption的prop,CounterPanel传递给Counter组件实例说明文字。通过名为initValue的prop传递给Counter组件一个初始的计数值。
2、读取prop
再来看Counter组件内部是如何接受传入的prop的,首先是构造函数,代码如下:
constructor(props) {
console.log('enter constructor: ' + props.caption);
super(props);
this.onClickIncrementButton = this.onClickIncrementButton.bind(this);//绑定当前this的执行环境
this.onClickDecrementButton = this.onClickDecrementButton.bind(this);
this.state = {
count: props.initValue
}
}
如果一个组件需要定义自己的构造函数,一定要记得在构造函数的第一行通过super调用父类也就是React.Component的构造函数。如果在构造函数中没有调用super(props),那么组件实例被构造之后,类实例的所有成员函数就无法通过this.props访问到父组件传递过来的props值。
读取传入prop的方法:在构造函数中可以通过参数props获得传入prop值,在其他函数中则可以通过this.props访问传入prop的值。比如,在Counter组件的render函数中,就是通过this.props获得传入的caption,代码如下:
render() {
console.log('enter render ' + this.props.caption);
const {caption} = this.props;
return (
<div>
<button style={buttonStyle} onClick={this.onClickIncrementButton}>+</button>
<button style={buttonStyle} onClick={this.onClickDecrementButton}>-</button>
<span>{caption} count: {this.state.count}</span>
</div>
);
}
3、propTypes检查
用来让组件声明自己的接口规范,可以根据propTypes判断外部世界是否正确地使用了组件的属性。可以规范以下方面:
- 这个组件支持哪些prop;
- 每个prop应该是什么样的格式。
比如,对于Counter组件的propTypes定义代码如下:
Counter.propTypes = {
caption: PropTypes.string.isRequired,
initValue: PropTypes.number
};
其中,要求caption必须是string类型,initValue必须是number类型,还有一个区别就是:caption带上了isRequired,这表示使用Counter组件必须要指定caption,而initValue因为没有isRequired,表示如果没有也没关系。
注意:没有propTypes定义,组件依然能够正常工作,而且,即使在上面propTypes检查出错的情况下,组件依旧能工作。也就是说propTypes检查只是一个辅助开发的功能,并不会改变组件的行为。
二、React中的state
state代表组件的内部状态,由于React组件不能修改传入的prop,所以需要记录自身数据变化,就要使用state。
在Counter组件中,最初显示初始计数,可以通过initValue这个prop来定制,在Counter已经被显示之后,用户会点击”+“和”-“按钮改变这个计数,这个变化的数据就要Counter组件自己通过state来存储了。
1、初始化state
通常在组件类的构造函数结尾处初始化state,在Counter构造函数中,通过对this.state的赋值完成对组件state的初始化:
constructor(props) {
console.log('enter constructor: ' + props.caption);
super(props);
……
this.state = {
count: props.initValue || 0 //initValue 是可选的props,考虑到如果父组件没有指定这个props值的情况,就是用默认值0
}
}
注意:组件的state必须是一个javascript对象,不能是string或者number这样的简单数据类型。
2、读取和更新state
通过给button的onClick属性挂载点击事件处理函数,我们可以改变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里新的值出现在界面上。
如果直接修改state,就会出现当你连续点击”+“按钮三次,this.state中的count字段值已经被增加到了3,但是没有重新渲染,这时候点击一次”-“按钮,触发的onClickDecrementButton函数依然是用this.setState改变组件状态,这个函数调用首先把this.state中的count值从3减少为2,然后触发重新渲染,于是我们看到界面上的数字一下子从0跳跃到2.
三、prop和state对比
总结一下prop和state的区别:
- prop用于定义外部接口,state用于记录内部状态;
- prop的赋值在外部世界使用组件时,state的赋值在组件内部;
- 组件不应该改变prop的值,而state存在的目的就是让组件来改变的。