React学习笔记
来自 《React 引领未来的用户界面开发框架》一书
JSX
JavaScript XML,一种在React组建内部构建标签的类XML语法,实际会被转换成JavaScript函数。JSX的表达能力很强,也很适合组件化的React开发。
范例
//编写一个Divider
var Divider = React.createClass({
render: function(){
<div className="divider">
<h2>{this.props.children}</h2><hr/>
</div>
}
})
//Divider调用
<Divider>Questions</Divider>非DOM属性
key可选的唯一标志符,便于React渲染时重用ref表示父子组建之间的引用dangerouslySetInnerHTML在需要将HTML内容设置为字符串时使用
事件
事件已经被规范化,并同统一用驼峰形式表示。
handleClick: function(event){...},
render: function(){
return <div onClick={this.handleClick}>...</div>
}实际上,如CSS的表示也是用这样的形式。
组建生命周期
首次使用组件时
getDefaultProps可以为实例设置默认的props值getInitialState初始化每个实例初始化state值componentWillMount在render之间最后修改state值的机会render创建虚拟DOM,是组件中唯一必要的方法componentDidMount可以在该方法中通过调用this.getDOMNode()来访问真实的DOM,可以在这里调用如JQuery等工具。如果React运行在服务端,该方法不会被调用
之后使用该组件时
getInitialStatecomponentWillMountrendercomponentDidMount
应用状态改变时
componentWillReceivProps组件的props可以通过父辈组件来修改,此时该方法将被调用shouldComponentUpdate在渲染前判断是否需要更新,做到精确的优化,不要草率的使用该方法componentWillUpdate不可以在该方法中更新state或者props值rendercomponentDidUpdate和componentDidMount类似
销毁时
调用componentWillUnmount,可以在这个方法中做一些清理工作,比如创建的定时器或者添加的事件监听器
数据流
React中数据流是单向的,从父节点传递到子节点,因而组件是简单且易于把握的,组件只需要从父节点获取props渲染即可。如果顶层组件的某个prop改变了,React会递归地向下遍历整棵组件树,重新渲染所有使用这个属性的组件。
Props
Props即为properties,能够把任意类型的数据传递给组件。
//挂载数据时设置组件的props
var components=[{title:'test_0'},{title:'test_1'}];
<ListComponents components={components}/>可以通过this.props来访问组件的props,但不能通过这种方式修改它,一个组件绝对不可以自己修改自己的props。props可以是字符串,对象,事件处理器等。可以使用PropTypes来验证props的类型,推荐使用。
State
每一个React组件都可以拥有自己的state,其与props的区别是,state只存在于组件内部
state的值应该通过this.setState()修改,只要该状态被修改,render方法就会被调用,然后导致之后的一系列变化。状态总是让组件变得更加复杂,把状态针对不同的组件独立开来,能更有利于调试。
Props和State存放数据
state中不要保存计算出的值,而应该保存最简单的数据,即为组件工作时的必要数据。而props用来作为数据源,保证数据的单向流动。
事件处理
绑定方式实际上和JavaScript并没太大差别,例如:
var Demo = React.createClass({
render: function(){
<div onClick={this.handleClick}>Click here</div>
}
handleClick: function(event){
this.setState({isClicked : true});
}
});setState
合并对象的方式来更新组件的状态
replaceState
将state整个替换为新的
组件的复合
React的一个突出特点是构建组件的能力,实际上React代码主要就是构建组件,就像编写HTML文档时使用元素一样。
// 封装一个radio input作为子组件
var AnswerRadioInput = React.createClass({
propTypes : {
id: React.PropTypes.string,
name: React.PropTypes.string.isRequired,
label: React.PropTypes.string.isRequired,
value: React.PropTypes.string.isRequired,
checked: React.PropTypes.bool
},
getDefaultProps : function(){
retrun{
id: null,
checked:false
}
},
getInitialState : function(){
var id = this.props.id? this.props.id : uniqueId('radio-');
return {
checked : this.props.checked,
id : id,
name : id
}
},
render : function(){
return (
<div className="radio">
<!-- htmlFor表示该Label为哪个控件服务,点击该label时,所比绑定的元素将获得焦点 -->
<label htmlFor={this.props.id}>
<input type="radio"
name={this.props.name}
id={this.props.id}
value={this.props.value}
checked={this.state.checked}/>
{this.props.label}
</label>
</div>
)
}
});以上构建了一个AnswerRadioInput组件,之后构建一个AnswerMultipleChoiceQuestion来组合这一组件。
// 父组件
var AnswerMultipleChoiceQuestion = React.createClass({
propTyes: {
value: React.PropTypes.string,
choices: React.PropTypes.array.isRequired,
onCompleted: React.PropTyes.func.isRequired
},
getInitialState: function(){
return {
id: uniqueId('multiple-choice-'),
value: this.props.value
}
},
renderChoices: function(){
return this.props.choices.map(function(choice,i){
return AnswerRadioInput({
id: "choice-"+i,
name: this.state.id,
label: choice,
value: choice,
checked: this.state.value === choice
});
}.bind(this))
},
render: function(){
return (
<div className="form-group">
<label className="survey-item-label" htmlFor={this.state.id}>
{this.props.label}
</label>
<div className="survey-item-content">
{this.renderChoices()}
</div>
</div>
);
}
});此时存在一个问题,子组件没办法把自身的变化通知给父组件,因此,父组件需要关联子组件才能知道其更新。方法实际上就是回调,父组件通过向子组件中传入一个回调函数,子组件在需要时调用。
//父组件传入回调函数
var AnswerMultipleChoiceQuestion = React.createClass({
...
handleChanged: function(value){
this.setState({value:value});
this.props.onCompleted(value);
},
renderChoices: function(){
return this.props.choices.map(function(choice, i){
return AnswerRadioInput({
...
onChanged: this.handleChanged
});
}.bind(this));
},
});
//子组件调用回调
var AnswerRadioInpu = React.createClass({
propTypes: {
...
onChanged: React.PropTypes.func.isRequired
},
handleChanged: function(event){
//event.target 找到事件的触发对象input
var checked = event.target.checked;
this.setState({checked: checked});
if(checked){
this.props.onChange(this.props.value);
}
},
render: function(){
return(
<div className="radio">
<label htmlFor={this.state.id}>
<input type="radio"
...
onChange={this.handleChanged}/>
{this.props.label}
</label>
</div>
)
}
})Mixin
mixin允许我们定义可以在多个组件中共用的方法
// 定义IntervalMixin
var IntervalMixin = {
setInterval: function(callback, interval){
var token = setInterval(callback, interval);
this.__intervals.push(token);
return token;
},
componentDidMount: function(){
this.__intervals=[];
},
componentWillUnmount: function(){
this.__intervals.map(clearInterval);
}
};
//定义组件
var Since2014 = React.createClass({
//实际相当于把mixins中的内容解构了出来,放在了Since2014中
mixins: [IntervalMixin],
componentDidMount: function(){
this.setInterval(this.forceUpdate.bind(this),1000);
},
render: function() {
var from = Number(new Date(2014, 0, 1));
var to =Date.now();
return (
<div>{Math.round((to-from)/1000)}</div>
);
}
});DOM操作
在某些情况下,为了实现某些需求就不得不去操作底层的DOM。最常见的场景包括:需要与一个没有使用React的第三方类库进行整合,或者执行一个React没有原生支持的操作。
var DoodleArea = React.createClass({
render: function(){
//通过设定一个ref属性,之后可以通过this.refs.mainCanvas来访问到<canvas>组件
return <canvas ref="mainCanvas"/>;
},
componentDisMount: function(){
//在这里通过getDOMNode()方法来访问到底层的DOM节点,也可以在事件处理器中调用
var canvasNode= this.refs.mainCanvas.getDOMNode();
}
});表单
无约束组件
组件本身控制其值
var MyForm = React.createClass({
submitHandler: function(event){
event.preventDefault();
var text=this.refs.text.getDOMNode().value;
alert(helloTo);
},
render: function(){
return (
<form onSubmit={this.submitHandler}>
<input ref="text" type="text" defaultValue="Hello world!"/>
<br/>
<button type="submit">Submit</button>
</form>
);
}
});约束组件
约束组件的值被存储在state中,由React控制
var MyForm = React.createClass({
getInitialState: function(){
return {
text: 'hello world!'
}
},
…
render: function(){
return (
<form onSubmit={this.submitHandler}>
<input type="text" value={this.state.text}
onChange={this.handleChange}/>
...
</form>
);
}
});对于如handleChange 方法,还可以用bind方法传递更多参数,例如this.handleChange.bind(this, "name")。
React Router
React的介绍即讲过其非常适合于单页面应用,页面之间的切换需要Router的帮助。
React-router完全由React Component构成,路由被定义为了组件,路由的处理器也是组件
//react router的写法如下
var appRouter=(
<Routes location="history">
<Route title="SurveryBuilder" handler={App}>
<Route name="list" path="/" handler={ListSurveys}/>
<Route title="Add survey to surveyBuilder" name="add"
path="/add_survey" handler={AddSurvey}/>
<NotFound title="Page Not Found" handler={NotFoundHandler}/>
</Route>
</Routes>
);每一个处理器就对应着特定页面的组件,需要将上面的路由作为最顶层的组件渲染来启动。
使用react-router Link组件来进行导航。