prop 与 state 的对比
- prop 用于定义外部接口,state用于记录内部状态;
- prop的赋值在外部世界使用组件时,state的赋值在组件内部;
- 组件不应该改变 prop 的值,而 state 存在的目的就是让组件来改变的;
关于 prop
设置 props 的方式
1)可以在组件挂载时设置props:
<script type="text/babel">
var HelloWorld = React.createClass({
render: function(){
return <p>Hello, {this.props.name ? this.props.name : "World"}</p>
}
});
React.render(<HelloWorld name="Sam" />, document.body);
</script>
2)也可以通过调用组件实例的setProps()
方法来设置props(在ES6中将被禁用,这个方法不支持ES6类组件React.Component扩展。)
<script type="text/babel">
var HelloWorld = React.createClass({
render: function(){
return <p>Hello, {this.props.name ? this.props.name : "World"}</p>
}
});
var instance = React.render(<HelloWorld/>, document.body);
instance.setProps({name: 'Tim'});
</script>
prop 支持的数据类型
我们先从外部世界来看,prop是如何使用的,在下面的JSX代码片段中,就使用了prop:
<SampleButton id="sample" borderWidth={2} onClick={onButtonClick} style={{color: "red"}} />
在上面的例子中,创建了名为 SampleButton 的组件实例,使用了名字分别为id、borderWidth、onClick和style的 prop,看起来,React组件的 prop 很像是HTML元素的属性,不过,HTML组件属性的值都是字符串类型,即使是内嵌的JavaScript,也依然是字符串形式表示代码。React组件的 prop 所能支持的类型则丰富得多,可以是任何一种JavaScript语言支持的数据类型。
比如在上面的SampleButton中,borderWidth就是数字类型,onClick是函数类型,style的值是一个包含color字段的对象,当 prop 的类型不是字符串类型时,在JSX中必须用花括号{}把 prop 值包住,所以style的值有两层花括号,外层花括号代表是JSX的语法,内层的花括号代表这是一个对象常量。
当外部世界要传递一些数据给React组件,一个最直接的方式就是通过 prop;同样,React组件要反馈数据给外部世界,也可以用 prop,因为 prop 的类型不限于纯数据,也可以是函数,函数类型的 prop 等于让父组件交给了子组件一个回调函数,子组件在恰当的实际调用函数类型的 prop,可以带上必要的参数,这样就可以反过来把信息传递给外部世界。
修改 props 是不好的
如果你不知道要设置哪些 Props,那么现在最好不要设置它:
var component = <Component />;
component.props.foo = x; // 不好
component.props.bar = y; // 同样不好
这样是反模式,因为 React 不能帮你检查属性类型(propTypes)。这样即使你的属性类型有错误也不能得到清晰的错误提示。
Props 应该被当作禁止修改的。修改 props 对象可能会导致预料之外的结果,所以最好不要去修改 props 对象。
展开属性(Spread Attributes)
现在你可以使用 JSX 的新特性 - 展开属性:
var props = {};
props.foo = x;
props.bar = y;
var component = <Component {...props} />;
传入对象的属性会被复制到组件内。
React中的map遍历
var names = ['Alice', 'Emily', 'Kate'];
React.render(
<div>
{
names.map(function (name) {
return <div>Hello, {name}!</div>
})
}
</div>,
document.getElementById('example')
);
this.props.children 遍历所有子节点
this.props 对象的属性与组件的属性一一对应,但是有一个例外,就是 this.props.children 属性。它表示组件的所有子节点
var NotesList = React.createClass({
render: function() {
return (
<ol>
{
React.Children.map(this.props.children, function (child) {
return <li>{child}</li>;
})
}
</ol>
);
}
});
ReactDOM.render(
<NotesList>
<span>hello</span>
<span>world</span>
</NotesList>,
document.body
);
这里需要注意, this.props.children
的值有三种可能:如果当前组件没有子节点,它就是 undefined ; 如果有一个子节点,数据类型是 object ;如果有多个子节点,数据类型就是 array 。所以,处理 this.props.children 的时候要小心。
React 提供一个工具方法 React.Children
来处理 this.props.children
。我们可以用 React.Children.map 来遍历子节点,而不用担心 this.props.children 的数据类型是 undefined 还是 object。
关于super(props)
class Counter extends Component{
constructor(props){
super(props);
this.onClickIncrementButton = this.onClickIncrementButton.bind(this);
this.onClickDecrementButton = this.onClickDecrementButton.bind(this);
this.state = {
count: props.initValue || 0
}
}
}
如果一个组件需要定义自己的构造函数,一定要记得在构造函数的第一行通过 super 调用父类也就是React.Component的构造函数。如果在构造函数中没有调用super(props),那么组件实例被构造之后,类实例的所有成员函数就无法通过 this.props 访问到父组件传递过来的 props 值。
在Counter的构造函数中还给两个成员函数绑定了当前this的执行环境,因为ES6方法创造的React组件类并不自动给我们绑定this到当前实例对象。
关于 state
this.state 与 this.setState
在代码中,通过 this.state
可以读取到组件的当前 state。值得注意的是,我们改变组件 state 必须要使用 this.setState
函数,而不能直接去修改 this.state
。
直接修改 this.state
的值,虽然事实上改变了组件的内部状态,但只是野蛮地修改了 state,却没有驱动组件进行重新渲染,既然组件没有重新渲染,当然不会反应 this.state
值的变化;而 this.setState()
函数所做的事情,首先是改变 this.state
的值,然后驱动组件经历更新过程,这样才有机会让 this.state
里新的值出现在界面上。
什么时候需要组件的构造函数 constructor
要注意,并不是每个组件都需要定义自己的构造函数。无状态的React组件往往就不需要定义构造函数,一个React组件需要构造函数,往往是为了下面的目的:
- 初始化 state,因为组件生命周期中任何函数都可能要访问 state,那么整个生命周期中第一个被调用的构造函数自然是初始化 state 最理想的地方;
- 绑定成员函数的 this 环境;
在ES6语法下,类的每个成员函数在执行时的 this 并不是和类实例自动绑定的。而在构造函数中,this 就是当前组件实例,所以,为了方便将来的调用,往往在构造函数中将这个实例的特定函数绑定 this 为当前实例。
以 Counter 组件为例,我们的构造函数有这样如下的代码:
this.onClickIncrementButton = this.onClickIncrementButton.bind(this);
this.onClickDecrementButton = this.onClickDecrementButton.bind(this);
这两条语句的作用,就是通过 bind 方法让当前实例中的 onClickIncrementButton 和 onClickDecrementButton 函数被调用时,this 始终是指向当前组件实例。
初始化 state 的区别(ES5与ES6)
const Sample = React.createClass({
getInitialState: function(){ // 初始化组件的 this.state
return {foo: 'bar'};
},
getDefaultProps: function(){ // 设置组件 props 的初始值
return {sampleProp: 0}
}
});
class Sample extends React.Component {
constructor(props){ // 初始化组件的 this.state
super(props);
this.state = {foo: 'bar'};
}
};
Sample.defaultProps = {sampleProp: 0}; // 设置组件 props 的初始值
React.createClass 已经被Facebook官方逐渐废弃,但是在互联网上还能搜索到很多使用 React.createClass 的教材,虽然强烈建议不再要使用 React.createClass ,但是如果读者你真的要用的话,需要注意关于 getInitialState 只出现在装载过程中,也就是说在一个组件的整个生命周期过程中,这个函数只被调用一次,不要在里面放置预期会被多次执行的代码。
render 函数
通常一个组件要发挥作用,总是要渲染一些东西,render 函数并不做实际的渲染动作,它只是返回一个JSX描述的结构,最终由React来操作渲染过程。(render函数并不往DOM树上渲染或者装载内容,它只是返回一个JSX表示的对象,然后由React库来根据返回对象决定如何渲染。而React库肯定是要把所有组件返回的结果综合起来,才能知道该如何产生对应的DOM修改。)
当然,某些特殊组件的作用不是渲染界面,或者,组件在某些情况下选择没有东西可画,那就让render函数返回一个null或者false,等于告诉React,这个组件这次不需要渲染任何DOM元素。
需要注意,render 函数应该是一个纯函数,完全根据 this.state 和this.props 来决定返回的结果,而且不要产生任何副作用。在 render 函数中去调用 this.setState 毫无疑问是错误的,因为一个纯函数不应该引起状态的改变。