ReactDOM.render(<Infoname={10}/>,255);">
虽然有修改props的方法,但不建议对props进行修改,如果要修改,就使用state吧
2. 状态(state)
状态是React中定义之后可改变的数据,只能在组件内部定义
getInitialState:function(){return{
age:10
};
}
在需要修改状态的时候,调用this.setState()方法即可(注意不能直接设置this.state = newObj)
this.setState({
age:this.state.age+1});
注意必须初始化state对象,即初始化时至少要返回一个空的state对象,age属性的初始化是不必要的,只是为了便于管理
React的setState方法是异步的,在其中取state.age可能取不到预期的值(不过目前还没遇到过)
这里的异步包含了两个概念
2.1 调用的时机异步
React的组件有生命周期,在componentWillUpdate与render这两个时期之间才会调用
2.2 调用之后的异步
setState实际上是一个异步方法,可带两个参数
this.setState({
age:this.state.age+1
},function(){
});
更好的做法是直接在第一个参数使用函数,如此便保证了函数内部能取到正确的值,在大型复杂的组件中推荐如此
this.setState(function(prevState,props){return{
age:prevState.age+1
};
});
四、组件的三种定义方式
React推荐将大部件划分为一个个小部件,解耦。而组件的定义,常见的有三种方式
1. 函数式定义
使用函数的方式定义,它的特点是无状态,实际上它并没有被实例化,所以无法访问this对象,不能管理生命周期
多用于纯展示的组件
functionInfo(props){return<p>{props.name}</p>}
ReactDOM.render(<Infoname="Jack"/>,document.getElementById('box'));
函数组件接受一个属性参数,可直接获取
2. React.createClass方式定义
这种方式看起来像是ES5的形式,较普遍,根据官方说明,将被类形式取代

varInfo=React.createClass({
getInitialState:function(){return{
name:'myName'
};
},render:function(){return<p>{this.state.name}</p>}
});
在其中也可以使用ES6的语法,为了和类形式的做些区别,代码多写了点

letInfo=React.createClass({
getInitialState(){return{
name:this.props.name||'myName'
};
},getDefaultProps(){return{
year:newDate().getFullYear()
};
},showYear(e){
console.log(this);
letelem=ReactDOM.findDOMNode(e.target);
console.log('year'+elem.getAttribute('data-year'));
},render(){return<ponClick={this.showYear}data-year={this.props.year}>{this.state.name}</p>}
});
绑定了点击事件,在点击函数处理中可以直接取到该组件的this对象

3. extends React.Component方式定义
extends一看就是ES6的类形式了,比较推荐使用

classInfoextendsReact.Component{
constructor(props){
super(props);this.state={
name:this.props.name||'myName'
};
}
showYear(e){
console.log(this);
letelem=ReactDOM.findDOMNode(e.target);
console.log('year'+elem.getAttribute('data-year'));
}
render(){return<ponClick={this.showYear}data-year={this.props.year}>{this.state.name}</p>}
}
Info.defaultProps={
year:newDate().getFullYear()
};
ReactDOM.render(<Info/>,document.getElementById('box'));
可以看到一些区别,初始化props与state的方式不一样
ES5形式中是直接在函数中return的方式,ES6形式的state是在构造函数中直接初始化this.state,而props初始化则需要在外部进行
再看看点击事件,会发现输出的this为null,因在ES6的类形式中,React并不会自动绑定函数方法的this对象,需要自行绑定
一般来说,有三种绑定方式
3.1 直接在构造函数中统一绑定

constructor(props){
super(props);this.state={
name:this.props.name||'myName'
};this.showYear=this.showYear.bind(this);
}
3.2 直接在onClick中绑定
相对在构造函数中绑定来说,这种方法会更有针对性,不过多个统一绑定就会显得代码冗余
render(){return<ponClick={this.showYear.bind(this)}data-year={this.props.year}>{this.state.name}</p>
}
3.3 在onClick绑定中使用回调函数调用
render(){return<ponClick={(e)=>this.showYear(e)}data-year={this.props.year}>{this.state.name}</p>
}
这种方式需要手动传入event参数,而上述两种不需要
五、组件的生命周期
图片引自:组件的生命周期

React的组件有从产生到消亡,有个生命周期。宏观来讲有三个时期
1. 实例化期(Mounting)
实例化这个时期主要是组件的初始实例化阶段,如图

主要包括属性和状态的初始化阶段、组件即将加载(componentWillMount)阶段、组件渲染(render)阶段、组件加载完成(componentDidMount)阶段
除了render可在存在期的时候再次进行组件渲染之外,其他阶段只会发生一次

classInfoextendsReact.Component{
constructor(props){
super(props);this.state={
name:this.props.name,age:0
};
}//组件将加载componentWillMount(){
console.log('componentWillMount:',this.state.age)
}//组件加载完成componentDidMount(){
console.log('componentDidMount:',this.state.age)
}//渲染render(){
console.log('Inforender:',this.state.age);return<p>{this.state.name}{this.state.age}</p>}
}
ReactDOM.render(<Infoname="Jack"/>,255);">
2. 存在期间(Updating)
组件实例化之后,在组件存在的时期,随着与用户的交互,属性或状态的改变,组件可发生一些更新,如图

componentWillReceiveProps(nextProps)
组件接收到属性(通常是父级传来的),带一个参数,即为该属性对象
shouldComponentUpdate(nextProps,nextState)
组件是否应该更新,true|false,默认返回true,带两个参数,将要更新的属性对象和状态对象
需要注意的是,如果自定义了这个方法,就会直接覆盖默认的方法(若定义之后不返回则表示返回了false)
componentWillUpdate(nextProps,255);">组件将更新,带两个参数,将要更新的属性对象和状态对象
render
再次进入渲染阶段
componentDidUpdate(prevProps,prevState)
组件更新完成,带两个参数,之前(已经)更新的属性对象和状态对象
在这个时期,各个阶段按照流程不断地进行着,举个栗子
这里定义一个父组件InfoWrap和子组件Info
在实际开发中,为了防止JS阻塞HTML结构的渲染,初始异步获取数据时一般会放到componentDidMount中

classInfoWrapextendsReact.Component{
constructor(props){
super(props);this.state={
name:'defaultName'
};
}
componentDidMount(){
setTimeout(()=>{this.setState({
name:'Jack'
});
},1000);
setTimeout(()=>{this.setState({
name:'Jack'
});
},3000);
}
render(){
console.log('InfoWraprender');return<Infoname={this.state.name}/>}
}
ReactDOM.render(<InfoWrap/>,255);">通过setTimeout模拟异步,一段时间后改变状态state中的name值,通过属性name传入子Info组件中
这里要注意的是,两次setState的name值相同,
基于React依照state状态的diff来判断是否需要重新渲染数据,在InfoWrap中不会更新两次HTML,但还是会向子Info中传入两次属性props

由上图,子Info被渲染了三次,而实际上第三次name并未改变,其实是不需要渲染的
在实际开发中,为了防止无意义的渲染,通常会在shouldComponentUpdate添加判断,自定义是否需要更新
将其中的return nextProps.name !== this.state.name;取消注释,则不再被更新渲染

细心点可以看到,Info组件中的setState是放在了componentWillReceiveProps中
为什么不直接在shouldComponentUpdate中判断是否需要更新后再更新状态呢?
根据上方的流程图,如果在这里更新,就会再次触发state改变,导致又多循环执行了一次
所以一般的做法是在componentWillReceiveProps中根据条件判断是否需要更新状态,然后在shouldComponentUpdate中再根据条件判断是否需要更新渲染组件
同理,千万不要在render的时候setState更新状态,这更危险,会出现死循环,不注意的话可以直接把浏览器搞崩了

以上是子组件从父组件获取数据后更新的情况,下面来看看在子组件中的自我更新(increaseAge方法)
假设现在点击一次age属性值自增一次,在age不等于3的时候才更新页面

可以看到,在render和componentDidUpdate阶段,state的值才被实实在在地更新了,所以在之前的阶段取setState之后的新值,仍为旧的值
3. 销毁期(Unmounting)
销毁期发生在组件被移除的时候,用于如果卸载组件后需要做一些特殊操作时,一般很少用

六、组件间的通信
组件一多起来,就涉及到不同组件之间的数据交流,主要有三种类型
1. 父子通信
React是单向的数据流动
父组件向子组件传递数据,其实就是通过props属性传递的方式,父组件的数据更新,通过props数据的流动,子组件也得到更新
2. 子父通信
子组件与父组件通信,不同于Angular.js的数据双向绑定,在React中默认支持子同步父的数据
若想实现父同步子的数据,则需要在子数据发生改变的时候,调用执行父props传来的回调,从而达到父的同步更新

classInputItemextendsReact.Component{
constructor(props){
super(props);this.state={};
}
inputChange(e){this.props.inputChange(e.target.value);
}
render(){return<ptitle={this.props.title}>
[InputItem]-input:<inputtype="text"onChange={this.inputChange.bind(this)}/>
</p>}
}
classPageextendsReact.Component{
constructor(props){
super(props);this.state={
inputValue:''
};
}
inputChange(inputValue){this.setState({
inputValue,});
}
render(){return(<div>
<p>[Page]-input:<inputtype="input"value={this.state.inputValue}/></p>
<InputItemtitle="myInput"inputChange={this.inputChange.bind(this)}/>
<InputItemtitle="myInput"inputChange={this.inputChange.bind(this)}/>
</div>)
}
}
ReactDOM.render(<Page/>,255);">这里定义了一个父组件Page,子组件InputItem
在父组件中<InputItem title="myInput" ... />其实就有了父与子的通信(props传递)
Page向InputItem传递了一个回调属性,InputItem数据改变后调用此回调,数据得到更新

3. 兄弟通信
上述是父同步子的数据,如果要实现兄弟之间(或者两个没什么关系的组件)的数据同步,就得结合父与子、子与父的方式

classInputItemextendsReact.Component{
constructor(props){
super(props);this.state={};
}
inputChange(e){this.props.inputChange(e.target.value);
}
render(){return<ptitle={this.props.title}>
[InputItem]-input:<inputtype="text"onChange={this.inputChange.bind(this)}value={this.props.inputValue}/>
</p>}
}
classPageextendsReact.Component{
constructor(props){
super(props);this.state={
inputValue:''
};
}
inputChange(inputValue){this.setState({
inputValue,});
}
render(){return(<div>
<p>[Page]-input:<inputtype="input"value={this.state.inputValue}/></p>
<InputItemtitle="myInput"inputChange={this.inputChange.bind(this)}inputValue={this.state.inputValue}/>
<InputItemtitle="myInput"inputChange={this.inputChange.bind(this)}inputValue={this.state.inputValue}/>
</div>)
}
}
ReactDOM.render(<Page/>,255);">子InputItem更新后,调用父Page的回调,在父Page中将更新后的数据通过props传至子InputItem
不同组件之间数据得到同步

4. 事件发布/订阅
这个还没用过 不清楚..
七、受控组件与非受控组件
在React中的表单Form系统中,有受控组件与非受控组件一说
1. 非受控组件
非受控,即表单项的value不受React的控制,不设初始value值,我们可以随意更改
但不便于统一使用React进行管理,也不便于设置初始值

classPageextendsReact.Component{
constructor(props){
super(props);this.state={
inputValue:''
};
}
inputChange(e){
console.log(e.target.value)
}
render(){return(<div>
<p><inputtype="input"onChange={this.inputChange.bind(this)}/></p>
</div>)
}
}
ReactDOM.render(<Page/>,255);">可以看到,此input项目似乎与React没什么关系,想获取它的值就必须通过DOM获取到该元素,不方便管理
2. 受控组件
受控组件,是为了更好地管理表单项的值
但要注意的是,一旦设置了value,将不能通过直接在表单项输入就能改变value值
因为value已经被React控制,要更新value值,就得更新相应的state状态值
对于受控组件,又有初始值和值两种之分
2.1 初始值(defaultValue) -- 注:其实defaultValue应该是属于非受控组件的
defaultValue这里指的是input,select,0);">textarea等,相应的checkboxradio是defaultChecked
初始值只是初始的一个值,在第一次设置定义之后就不可改变
在实际开发中,数据的获取经常是异步的,大部分情况下会先初始设置input表单值为空,获取到数据后再放到input中(如编辑页面)
便会有以下代码

classInputItemextendsReact.Component{
constructor(props){
super(props);this.state={
inputValue:this.props.inputValue||''
};
}
componentWillReceiveProps(nextProps){this.setState({
inputValue:nextProps.inputValue
});
}
inputChange(e){
letinputValue=e.target.value;
console.log(inputValue);//this.setState({
//inputValue
//});}
render(){return<p><inputtype="input"onChange={this.inputChange.bind(this)}defaultValue={this.state.inputValue}/></p>
}
}
classPageextendsReact.Component{
constructor(props){
super(props);this.state={
inputValue:''
};
}
componentDidMount(){
setTimeout(()=>{this.setState({
inputValue:'myValue'
});
},1000);
}
render(){return<InputIteminputValue={this.state.inputValue}/>}
}
ReactDOM.render(<Page/>,255);">初始在InputItem中设置了defaultValue为空,一段时间后获取到父Page传来的新值inputValue,然而InputItem中的defaultValue并不会更新
这种情况,就不适用与defaultValue了,换成用状态控制的value即可
2.2 值(value)
render(){return<p><inputtype="input"onChange={this.inputChange.bind(this)}value={this.state.inputValue}/></p>
}
获取到异步的数据后,通过componentWillReceiveProps中更新状态值
加入onChange事件,在输入的时候更新状态值
而对于onChange事件的调用更新state,也有点点小技巧
假如input项目太多,为每个input定义一个change回调并不实际
这时可以在bind中指定参数,指定是某个input项,或者直接在input项中添加属性区分,调用的时候再获取

classInputItemextendsReact.Component{
constructor(props){
super(props);this.state={
userName:this.props.userName||'',age:this.props.age||''
};
}
componentWillReceiveProps(nextProps){this.setState({
userName:nextProps.userName,age:nextProps.age
});
}
inputChange(name,e){this.setState({
[name]:e.target.value
});
}//inputChange(e){
//this.setState({
//[e.target.getAttribute('name')]:e.target.value
//});
//}
render(){return(<div>
<p><inputtype="input"name="userName"onChange={this.inputChange.bind(this,'userName')}value={this.state.userName}/></p>
<p><inputtype="input"name="age"onChange={this.inputChange.bind(this,'age')}value={this.state.age}/></p>
</div>)
}
}
classPageextendsReact.Component{
constructor(props){
super(props);this.state={
userName:'',age:''
};
}
componentDidMount(){
setTimeout(()=>{this.setState({
userName:'Jack',age:10
});
},1000);
}
render(){return<InputItemuserName={this.state.userName}age={this.state.age}/>}
}
ReactDOM.render(<Page/>,255);">默认情况下,如果bind中不填第二个参数,在回调中第一个参数就是触发的event对象
如果有第二个参数,回调中的第一个参数就是该参数,后续的参数才是触发的event对象
上述两个inputChange方法调用之后结果一样,这里也利用了ES6支持对象属性名为变量的新特性
另外,由于设置了value值之后的React组件表单项不能直接更改value值,需要修改state相应值。
在使用一些插件的时候可能会遇到问题,如日期插件bootstrap-datepicker

classDatePickerextendsReact.Component{
constructor(props){
super(props);this.state={
timeFrom:'',timeEnd:''
};
}
combindDate(date){
letyear=date.getFullYear(),month=date.getMonth()+1,day=date.getDate();
month=month<10?'0'+month:month;
day=day<10?'0'+day:day;return[year,month,day].join('-');
}
componentDidMount(){
let$timeFrom=$(this.refs.timeFrom);
$timeFrom.datepicker({
format:'yyyy-mm-dd',autoclose:true,language:'zh-CN'
}).on('changeDate',(ev)=>{
letday=ev.date.getDate();if(day>15){
$timeFrom.datepicker('update','');//this.setState({
//timeFrom:''
//});
}else{//this.setState({
//timeFrom:this.combindDate(ev.date)
//});}
});
}
render(){return(<div>
<p>timeFrom:<inputtype="input"ref="timeFrom"value={this.state.timeFrom}/></p>
<p>timeEnd:<inputtype="input"ref="timeEnd"value={this.state.timeEnd}/></p>
</div>)
}
}
ReactDOM.render(<DatePicker/>,255);">且看看这个timeFrom,假设现在的需求是选择的日期不能大于15号
正常情况下,直接调用.datepicker('update','');清空即可
但在React受控组件中,这关乎状态state值,所以要同时进行显示地setState(包括选成功的赋值与选失败的清空,即注释部分)
八、组件的复制
组件的复制也是一块知识,不过我这里应该不算是复制吧,其实只是一个具体的栗子
1. 弹窗中的组件并不是在弹窗之后才加载,其实是初始就加载
想象一下有这么一个需求:
有很多道题,每道题会有一些附加的文件,需要有个文件的轮播,另外点击文件还有弹窗预览,弹窗中下方是文件轮播,上方是文件的预览轮播
所以一个页面会出现多个相似的轮播,点击轮播中的文件可弹窗预览该文件,在弹窗中下方还有这个相似的轮播
所以要做的其实就是三个组件,页面组件,文件轮播组件,弹窗预览组件(该组件中使用一个文件轮播组件)
思路很清晰,不过在实现过程中发现,并不是想象的样子,弹窗中的文件轮播组件并不是在弹窗之后才加载,其实是页面加载出来就加载了。
那例子太复杂,用几个input项模拟一下吧
Page组件是页面组件,InputItem是共享的,BoxBanner是弹窗组件

classInputItemextendsReact.Component{
constructor(props){
super(props);this.state={
inputIndex:this.props.inputIndex||0,inputValue:this.props.inputValue||''
};
}
componentWillReceiveProps(nextProps){this.setState({
inputIndex:nextProps.inputIndex,inputValue:nextProps.inputValue
});
}
componentDidMount(){
console.log('componentDidMount',this.state.inputIndex);
}
inputChange(e){this.setState({
inputValue:e.target.value
});
}
inputClick(){
console.log('inputClick');
}
render(){return<pdata-first="1"className="check-first">{this.state.inputIndex}、<input
type="input"
onChange={this.inputChange.bind(this)}
onClick={this.inputClick.bind(this)}
value={this.state.inputValue}
style={{'margin':'10px'}}/>
</p>}
}
classBoxBannerextendsReact.Component{
constructor(props){
super(props);this.state={
inputIndex:0,inputValue:''
};
}
openBox(e){
letelem=e.target;if(elem.tagName!=='BUTTON'){return;
}this.setState({
inputIndex:elem.getAttribute('data-index'),inputValue:elem.getAttribute('title')
});
layer.open({
type:1,title:false,shadeClose:true,//content:$('.template-box').html(),content:$('.template-box'),//content:$(this.refs.templateBox),success:function(layero){
let$first=$(layero).find('.check-first');
console.log('isFirst:',$first.attr('data-first'));
$first.attr('data-first','0');
}.bind(this),end:function(layero){//$('.check-first').attr('data-first','1');}
});
}
render(){return(<div>
<ponClick={this.openBox.bind(this)}>
<buttondata-index="1"title="box1">box1</button>
<buttondata-index="2"title="box1">box2</button>
<buttondata-index="3"title="box1">box3</button>
</p>
<divclassName="template-box"ref="templateBox"style={{display:'none'}}>
<InputIteminputIndex={this.state.inputIndex}inputValue={this.state.title}/>
</div>
</div>)
}
}
classPageextendsReact.Component{
constructor(props){
super(props);
}
render(){return(<div>
<BoxBanner/>
</div>)
}
}
ReactDOM.render(<Page/>,255);">这里有个要求是,判断是否是首次弹窗进来,初始设置data-first属性为1,弹窗后即更新为0

在BoxBanner组件中引入了一个InputItem组件,但InputItem组件被共享,只在页面开始加载是被加载了
传递到layer中的content似乎只是加载后的结果,可以看到isFirst值不是预想的
在layer的content中指定InputItem组件明显是不可行的,毕竟这是JSX
所以,就得在弹窗关闭之后恢复相关的值,即end回调中的注释部分
上述的代码中
//content:$('.template-box').html(),
最开始用的是第一种方法,但这将只会传递html,其中的事件将不被执行
换成第二种,事件的传递得到解决,但在React中过多的DOM操作并不推荐,且如果存在多个.template-box时,基于弹窗中组件不会重新加载的问题,组件的获取就不正确
建议是换成第三种,取该组件的ref映射
Page组件中加多一项

render(){return(<div>
<BoxBanner/>
<BoxBanner/>
</div>)
}

(编辑:李大同)
【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!