React组件的数据分两种,prop和state,prop或state的改变都会引发组件的重新渲染。prop是组件的对外接口,state是组件的内部状态,对外用prop,对内用state。
React的prop
当外部世界要传递数据给React组件,最直接的方式就是通过prop;同样,React组件要反馈数据给外部,也可以通过prop。prop的类型不限于纯数据,也可以是函数,函数类型的prop等于父组件传递给子组件一个回调函数,子组件可以对这个回调函数传参,这样就可以把信息传给外部世界。
// ./lesson2/chapter1/ControlPanel.js
class ControlPanel extends Component {
constructor() {
super(...arguments);
this.handleClick = this.handleClick.bind(this);
}
handleClick(name) {
alert(name)
}
render() {
console.log('enter ControlPanel render');
return (
<div style={style}>
<Counter caption="First" handleClick={this.handleClick}/>
</div>
);
}
}
// ./lesson2/chapter1/Counter.js
class Counter extends Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.props.handleClick('点我啊');
}
render() {
const {caption} = this.props; // ES6对象结构的写法
return (
<div>
<button onClick={this.handleClick}>+</button>
<span>caption:{caption}</span>
</div>
);
}
}
读取prop值
从上面例子可以看到ControlPanel.js中组件Counter传递一个叫作caption的prop,其值为First,又传递了一个叫作handleClick的prop,其值是一个函数。在Counter.js中,通过this.props.caption即可获得caption的值。在Counter组件中,我们给一个button绑定点击事件,调用从父组件传递过来的handleClick函数,并传递一个参数给父组件。
先回顾一下,在ES6的课程中,关于CLASS的super我们说过以下几点:
- super()方法可访问基类的构造函数
- super()只可在派生类的构造函数中调用
- super()负责初始化this,在构造函数中访问this前要调用super()
在Counter组件中,我们可以看到:一个组件需要定义自己的构造函数,需要在第一行调用super(props) [constructor中可能还会传入其他参数,也可这样写super(...arguments)];给super()传入props作为参数,则可在构造函数中访问this.props。在构造函数中还给成员函数绑定了当前this的执行环境,ES6方法创造的React组件类并不会自动给我们绑定this到当前对象。
propTypes检查
既然prop是组件的对外接口,那么就应该有某种方式让组件申明自己的接口规范。ES6定义的组件类中,可以通过增加类的propTypes属性来定义prop规格。
比如,对Counter组件的propTypes定义如下代码:
Counter.propTypes = {
caption: PropTypes.string.isRequired,
initValue: PropTypes.number
}
其中要求caption必须是string类型,initValue必须是number,并且caption的值还是必须存在的。为了验证,可将ControlPanel.js中的render函数增加如下代码
<Counter caption={123}/>
控制台中可以看到一个红色的提示警告。
propTypes虽然能够在开发阶段发现代码问题,但在产品环境中并不需要。因为propTypes检查也是要消耗CPU计算资源的。所以最好的方式是,开发环境定义propTypes,生产环境通过babel-react-optimize优化去掉propTypes。关于propTypes具体用法请参见官方文档。
React中的state
驱动组件渲染过程的除了prop,还有state,state代表组件的内部状态。由于React不能修改传入的prop,所以只能通过修改state来记录自身数据的变化。
// ./lesson2/chapter2/Counter.js
class Counter extends Component {
constructor(props) {
... ...
this.state = {
count: props.initValue
}
}
onClickIncrementButton() {
this.setState({count: this.state.count + 1});
}
onClickDecrementButton() {
this.setState({count: this.state.count - 1});
}
render() {
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>
);
}
}
Counter.defaultProps = {
initValue: 0
};
export default Counter;
初始化state
在Counter组件中,我们通过this.state完成对组件state的初始化,代码如下:
constructor(props) {
... ...
this.state = {
count: props.initValue || 0
}
}
组件的state必须是一个javascript对象。通常情况,我们不会通过state设置初始值,而是通过React的defaultProps的功能。
constructor(props) {
... ...
this.state = {
count: props.initValue
}
}
Counter.defaultProps = {
initValue: 0
};
读取和更新state
在button上绑定点击事件,通过触发点击的回调函数来改变state值。代码如下:
onClickIncrementButton() {
this.setState({count: this.state.count + 1});
}
<button style={buttonStyle} onClick={this.onClickIncrementButton}>+</button>
Tips: 改变state必须通过this.setState(),直接去修改this.state并不会让页面重新渲染。 直接修改this.state的值,虽然改变了组件的内部状态,但只是野蛮的修改state,却没有驱动组件进行重新渲染,既然组件没有重新渲染,当然不会反应this.state值的变化;而this.setState()函数所做的事情,首先是改变this.state的值,然后驱动组件更新,这样才有机会让this.state的新值出现在界面上。
prop和state的对比
- prop用于定义外部接口,state用于记录内部状态
- prop的值通过外部组件传入,state的值在组件内部定义
- 组件不应该修改prop的值,state存在的目的就是让组件来改变的