PS:开头的一段废话
想起一个月前还不知道reactjs该如何下手的而今天有点小体会,还是有点小欣慰,不过回望一些走过的坑和开始时的满头浆糊觉得还是有点恐怖的。今天分享一点实践中的小心得给新手朋友们。
reactjs的es6语法形式
其他的就不必多说,只说说两个点:
constructor和super
this.functionName=this.functionName.bind(this)
constructor(props)和super(props)一般是要么不出现,要么同时出现,意义实际上就是继承,这两个东西其实并不是reactjs的,而是es6的,前者就是一个一般意义的构造函数,相当于es5里面的
function FunctionName(props) {
this.props=props;
}
super(props)则是继承父类的属性,(意义相当于es5中的 function Son(name){ Parent.call(this, name) }),并在constructor内部正确拿到this,这个this指向此时的子类。如果在操作this之前(比如console.log(this))没有写super(props)则会报错,意思大概是:不能在super()之前写this!
而有时候看到constructor()和super(),即没有写props好像也没有什么问题,原因自然是没有在constructor内部用到this.props,实际上在constructor内部一般用到的是this.state居多,貌似this.props没怎么写。。。
this.functionName=this.functionName.bind(this)这个是绑定方法到子类上,使得这个this就是当前的子类(所谓的this指向)。还可以直接将.bind(this)写函数定义的地方,比如在render渲染的元素上 onClick={this.handleClick.bind(this)}.
this.state和this.setState({})以及propsName={stateName}
state在reactjs组件状态渲染里面的作用不言而喻,被称为状态渲染机,元素节点的各种属性值及其改变实际上基本是由state提供的状态值及其改变完成的。比如元素的文本改变、表单的value值、checked、disabled甚至css样式都可以做到用state来渲染。以下提供一个简例辅助说明:
import React from 'react';
import ReactDOM from 'react-dom';
import $ from 'jquery';
class Comp extends React.Component {
constructor(){
super();
this.handleChange=this.handleChange.bind(this);
this.handleClick=this.handleClick.bind(this);
this.state={
value: "",
msg: ""
}
}
handleChange(e){
this.setState({
value: e.target.value
})
}
handleClick(){
let {value}=this.state;
$.ajax({
...
data: {
value:value
},
success:(data)=>{
this.setState({
msg: data
})
}
})
}
render(){
let {value,msg}=this.state;
return(
<div>
<input type="text" value={value} onChange={this.handleChange} />
<input type="button" value="提交" onClick={this.handleClick} />
<span>{msg? msg:"此处加载ajax返回的信息"}</span>
</div>
)
}
}
ReactDOM.render( <Comp />, document.body)
以上简例在页面上渲染之后是一个按钮、一个输入框和一个信息提示。由于reactjs提倡单向数据流,在value状态初始为空的情况下,如果直接在输入框中输入而不设置value状态(setState),输入信息无法获取。这个渲染过程是:首先页面加载时根据默认的this.state中的三个状态值生成相应的状态,onChange事件不断改变state的value值,此时state接收到了改变并马上重新渲染页面上输入框内的值,即:只要任何动作改变了state值,页面就会重新渲染相应的地方(关于其他reactjs入门案例demo可以参见本人的github)。
props属性以及传递
注意一下,以上说constructor内如果没有传递props参数,其内部就不能使用this.props。但这并不意味着其他地方比如render内部不能使用。目前只是体会到组件之间传递的props值的初始来源一般都是state值,可能是实践不够的原因 @_@。。。
1.父组件向子组件传值
这是比较常见的父子组件之间通信的方式。子组件上的属性值可以理解为一个参数,而需要父组件传递一个实际值。传值这种方式一般是复用组件所必需的,因为有可能有多个页面的内容的某一个部分高度相似甚至相同,这时只需要对一个组件传递不同的值就可以实现多次利用。如下例:
假定组件文件都在根目录下:
//子组件Son.js
class Son extends React.Component{
render(){
let {title,userValue,pwdValue,handleChange,handleSubmit}=this.props;
return(
<div>
<h3>{title}</h3>
<input type="text" value={userValue} onChange={(e)=>handleChange("user",e.target.value} />
<input type="password" value={pwdValue} onChange={(e)=>handleChange("pwd",e.target.value} />
<input type="button" value="提交" onClick={handleSubmit} />
</div>
)
}
}
//父组件1:Login.js
import Son from './Son.js';
class Login extends React.Component{
constructor(){
super();
this.handleChange=this.handleChange.bind(this);
this.handleSubmit=this.handleSubmit.bind(this);
this.state={
user:{
value:"",
error:false
},
pwd:{
value:"",
error:false
}
}
}
handleChange(field,val){
let newState={value:val, error:true};
switch(field) {
case "user":
if (!val.length){
newState.error=false
} else if (val.length>4) {
newState.error=false
}
this.setState({
user:newState
})
break;
case "pwd":
if (!val.length){
newState.error=false;
} else if (val.length>6) {
newState.error=false
}
this.setState({
pwd:newState
})
break;
}
}
handleSubmit() {
let {user, pwd}=this.state;
if (!user.error || !pwd.error) {
alert("请重试!");
return;
} else {
$.ajax({
...
data: {
username: user.value,
password: pwd.value
},
...
})
}
render(){
let {user, pwd}=this.state;
return(
<Son title="请登录" userValue={user.value} pwdValue={pwd.value} handleChange={this.handleChange} handleSubmit={this.handleSubmit} />
)
}
}
相信各位看得出来,这是个登录页面的雏形,其中仅限原理说明,先不要纠结规范什么的哈。。。一般来说,既然有登录,也就有注册,注册页面基本上也是这个道理。需要说明的是:子组件上的属性值和方法都是this.props的,这里之所以没有写this.handleChange或者this.handleSubmit是因为子组件本身没有这个方法,因此如果此时在子组件上的方法前加上this.xxx就会报错大概说这个方法不是个函数!属性值也相似;同时利用拆包表达式写this.props表示这些属性和方法都是从父组件上继承而来,只要父组件上定义了方法,因此子组件上此时可以不用写constructor()和super()。
2.父组件的父组件子组件传值
这个场景主要用于组件嵌套比较多的时候。比如,我们发现在子组件Son.js中,有三个输入框分别是用户名、密码,但貌似有点不伦不类,那么我们就完善一点这个表单:
新建组件Form.js
class Form extends React.Component{
render(){
let {handleSubmit, userValue, pwdValue, handleChange}=this.props;
return(
<form onSubmit={handleSubmit} method="post" action="/submit">
<input type="text" name="user" value={userValue} onChange={(e)=>handleChange("user",e.target.value} />
<input type="password" name="pwd" value={pwdValue} onChange={(e)=>handleChange("pwd",e.target.value} />
<input type="submit" />
</form>
)
}
}
有了这个组件之后,关于表单的Son.js组件可以改造成(同样假定所有组件都在根目录下):
//Son.js
import Form from './Form.js';
class Son extends React.Component{
render(){
let {title,userValue,pwdValue,handleChange,handleSubmit}=this.props;
return(
<div>
<h3>{title}</h3>
<Form
userValue={userValue}
pwdValue={pwdValue}
handleChange={handleChange}
handleSubmit={handleSubmit}
/>
</div>
)
}
}
也许新手童鞋们不太明白Son.js组件上的属性和方法命名到底是不是一定要和孙子级组件上的一样,因为本人在学这个地方的时候的确纠结过,不过请注意:只要onChange或者onSubmit这类元素上原生方法后指定的一个函数(Form.js),比如onChange={handleChange},那么这个handleChange在传递的时候,上级组件(Son.js)上的handleChange={handleChange}中,左边是上级组件的方法,可以新命名比如footChange。。。而右边则是传递给下级组件的方法handleChange,所以,下级组件Form.js中onChange的右边函数名必须对应之。
此外,我们还可以根据Son.js做个简单的欢迎页:
//Welcome.js
class Welcome extends React.Component{
render(){
return(
<h1>欢迎您!</h1>
)
}
}
//SonWelcome.js
import Welcome from './Welcome.js';
class SonWelcome extends React.Component{
render(){
return(
<div>
<h3>{this.props.title}</h3>
<Welcome />
</div>
)
}
}
当然,SonWelcom.js太过简单了。。。同时title的值依赖于上一级组件传值确定,兄弟们大开脑洞吧。
3.非上下级关系和非兄弟的组件之间如何传值
相信这个情况是比较少的,不过这是个值得研究的,有一些师兄和大神们肯定知道如何解决。所谓的兄弟组件就是具有共同的父组件或者“爷组件”的多个组件,这种情况和以上差不多,主要把兄弟组件之间需要的共同的值挂载到上级组件即可。而非上下级和非兄弟组件这种情况的应用场景有点类似(注意,仅仅是类似)session或者cookie,比如有多个页面需要根据用户是否登录或者是否是管理员超级用户来对页面进行不同的渲染。
先说说可以解决的途径吧,首先推荐的是一个js库:PubSubJs,详情可以去了解一下(实际我也没用过。。。)。其次,说说本人的想法吧。正如上所言,假设这个属性或者方法在渲染组件的时候并不涉及安全性问题,那么何不将其设置为cookie或者session呢?哪个组件需要这个值就将其提出来。如下例两个组件:
暂且写如下,先不管自定义flag从何而来,同时,.active的样式是:display:none;
//CompOne.js
class CompOne extends React.Component{
render(){
return(
<div>
<p className={flag? "":"active"}>这个第一个组件</p>
</div>
)
}
}
//CompTwo.js
class CompTwo extends React.Component{
render(){
return(
<div>
<p className={flag? "active":""}>这个第二个组件</p>
</div>
)
}
}
假设以上两个组件并没有关系,且都带有一个共同的自定义属性flag,这两个组件根据flag的意义进行隐藏或展示。此时,可以建一个组件Cookie内部封装一个构造函数HandleCookie,内部封装三个方法setCookie(name, value, time),getCookie(name),clearCookie(name, value, time)。假设,name是cookie名称,value是cookie的值,time参数是参照当前时间的cookie的保存时间长度,设置为1表示一天后过期,设置-1则表示过期失效。
照此假定,以上两个组件可以直接在渲染页面的时候取到cookie的flag名称对应的值,并根据值渲染页面。比如在项目的某个文件加载完毕(componentDidMount)的时候设置一个cookie:{flag:”1″},CompOne.js:
//CompOne.js
import Cookie from './Cookie.js';
class CompOne extends React.Component{
render(){
let flag=Cookie.getCookie("flag");
return(
<div>
<p className={flag? "":"active"}>这个第一个组件</p>
</div>
)
}
}
照此安排,这个组件CompOne渲染后就会展示,而CompTwo则会隐藏。不过,这种方式明显不是一种规范的方法,仅仅是想法而已,望轻喷!
写在结尾的话
以上若有不当之处,还请指正一下,给小弟一个改过自新的机会吧。。。如果童鞋还对其他没有写彻底的地方感兴趣,可以参见本人的一个留言本小项目,乃是本国reactjs、webpack配置、nodejs和express入门的最好材料之一,觉得好用就star一下吧,哎,求star真不好意思^_^