1.基本计数器
功能需求:点击+按钮使得计数加一,点击-按钮使得计数减一,实时显示计数值。功能分析:根据功能,所以我们可以分析出只需要一个state,这个state代表着计数值。很简单(性能优化方面暂时搁下),直接上代码。
class Counter extends Component{
constructor(props){
super(props)
this.incrementClick = this.incrementClick.bind(this)
this.decrementClick = this.decrementClick.bind(this)
this.state = {
"times": 0
}
}
incrementClick(ev){
this.setState({"times": this.state.times+1})
}
decrementClick(ev){
let value = (this.state.times-1) >= 0 ? (this.state.times-1) : 0;
this.setState({"times": value})
}
render(){
return (
<div>
<button style={buttonStyle} onClick={this.incrementClick}>+</button>
<span style={contentStyle}>{this.state.times}</span>
<button style={buttonStyle} onClick={this.decrementClick}>-</button>
</div>
)
}
}
2.基本计数器组
现在实现另一个功能,那就是实现计数器组,有多个计数器,每个计数器可以增加也可以减少,并且实时显示计数值。而且需要实时显示所有得计数值之和。功能分析:有多少个计数器,应该就需要有多少个state,下面可以看看具体实现:
class Counter extends Component{
constructor(props){
super(props)
this.state = {
"first": 1,
"second": 2,
"third": 3
}
}
handleClick(obj){
console.log(obj)
return function(){
this.setState(obj)
}.bind(this)//位置1
}
render(){
let summary = 0;
//注意一定要不能省略变量定义关键字,否则会出错,大概react的源代码开启了严格模式
for(var key in this.state){
if(this.state.hasOwnProperty(key)){
summary += this.state[key]
}
}
return (
<div>
<div style={divStyle}>
<button style={btnStyle} onClick={this.handleClick({"first": this.state.first+1})()}>+</button>
<span style={spanStyle}>{this.state.first}</span>
<button style={btnStyle} onClick={this.handleClick({"first": (this.state.first-1)>=0 ? (this.state.first-1): 0})()}>-</button>
</div>
<div style={divStyle}>
<button style={btnStyle} onClick={this.handleClick({"second": this.state.second+1})()}>+</button>
<span style={spanStyle}>{this.state.second}</span>
<button style={btnStyle} onClick={this.handleClick({"second": (this.state.second-1)>=0 ? (this.state.second-1) : 0})()}>-</button>
</div>
<div style={divStyle}>
<button style={btnStyle} onClick={this.handleClick({"third": this.state.third+1})()}>+</button>
<span style={spanStyle}>{this.state.third}</span>
<button style={btnStyle} onClick={this.handleClick({"third": (this.state.third-1)>=0 ? (this.state.third-1) : 0})()}>-</button>
</div>
<div style={sumStyle}>
<span style={spanStyle}>{summary}</span>
</div>
</div>
)
}
}
总结:需要注意的地方,我们不能再react组件中使用for(key in obj),必须得利用变量定义符声明这个变量,原因是ES2015引入的class语法规定了在class body里面使用严格模式("use strict")。从这里应该学习到:尽管在JavaScript中for语句块并不是一个块作用域,但是仍旧不要养成了不使用变量声明标识符得习惯。同时,这个项目还涉及到了JavaScript中一个很重要得知识点:环境对象,在位置一处,我们必须bind(this),否则返回的这个函数的环境对象将不会是react组件对象,但是我们的合成事件handleClick就无需绑定环境对象为react组件对象,因为在handleClick函数内部并没有使用任何与react component相关逻辑,所以无需绑定。
由于需要要计算计数值的总和,所以我们在这里实现的基本计数器组将所有的逻辑都挤在一个组件中,但是这样做的缺点也很明显,这样的组件太过杂乱,同时也不利于复用。那么问题来了,有更好的方法吗?别的方法我也不知道,下面我们来直接试试利用redux库吧。。
3.利用redux库构建计数器组
由于代码比较多,所以这里就不贴代码了。
关于使用redux结合react来构建应用所需要注意的地方是:明白redux库的核心概念:数据单向流动,纯函数。对于每个文件,redux都提供了范式的,比如说对于actionTypes文件,我们所需要做的就是定义应用的交互事件;对于actions文件来说,我们需要做的就是根据每个交互事件来定义相应的actioncreater函数,这个函数的参数是自定义的,在日后dispatch的时候传入即可,actioncreater函数返回一个对象,这个对象必须包含type字段以供reducer函数识别交互动作的类型,当然这个对象也可以包括其他数据,以协助reducer函数迭代store所维护的状态。对于reducer文件来说,它的格式更是固定的,reducer函数接受两个参数,第一个参数是previousState,第二个参数是actionCreater所返回的交互动作对象。reducer函数所完成的逻辑就是根据action的类型来迭代store中的相关状态,要注意的是,我们的reducer函数是一个纯函数。至于store文件,如果我们的整个应用就只有一个reducer函数的话,那么就无需使用combineReducer函数将众多reducer组合成一个高阶函数,那么此时的核心就是createStore函数了,这个函数的第一个参数是reducer函数,第二个参数是默认的初始化state,如果缺省这个参数的话,那么在reducer函数的previousState参数应该会有默认值。
关于组件的组织,我们习惯利用容器组件来与store进行交互,他负责从store取得所需要的相应初始状态,负责更改store所维护的状态,负责让组件所需要的数据与store所维护的相应状态保持同步变化。具体细节就是分别是:store.getState;在事件处理函数中内部调用store.dispatch(actionCreater(此时传入参数));在componentWillMount函数里面增添store.subscribe()来订阅store所维护得状态得变化,一旦发生变化,那么将会调用subscribe的callback,这个calback应该完成的逻辑就是调用setState,重新从store中取得所需要的state。
我们的容器组件负责与store进行交互,对于视图组件来说,它的功能就是展示数据了。而且容器组件和视图组件是父子组件的关系,视图组件所需要展示的数据均是通过父组件所传入进来的props实现的,因此,展示组件一般没有必要具有state,所以一般的我们一般都将展示组件利用无状态函数来实现,这样做同时也可以提高性能。
4.进一步使用react-redux进行抽象
在react项目中使用redux的话,那么顺便使用react-redux将会是一个非常不错的选择,因为react-redux包提供了许多便捷的方法,同时它们也带来了抽象感。使用react-redux包的话,那么我们不在需要花费大量的功夫为视图组件定义一个容器组件来与store交互了,利用react-redux提供的connect方法返回的函数,我们可以利用视图组件来生成一个容器组件。所以对于react-redux来说,核心就是理解它的connect方法部分,下面看看connect方法的函数签名:
connect(mapStateToProps, mapDispatchToProps) => function
在这里我们只需要理解一下mapStateToProps和mapDispatchToProps的用法,它们各自的函数签名如下:
mapStateToProps(storeState, ownProps)
mapDispatchToProps(dispatch, ownProps)
要想搞清上面两个方法的作用,首先我们需要先明白容器组件的功能:当用户做出响应时,容器组件需要dispatch一个action,具体做法就是dispatch(actionCreater(param)),为什么要dispatch动作?因为要迭代store嘛,dispatch了之后,redux的reducer就会根据action的类型对store所维护的state做出相应的更改;当store所维护的组件被更改后,我们的容器组件还得保持自己的数据和store里的状态保持一致,要如何实现呢?答案就是利用subscribe订阅修改事件,一旦状态发生改变就会调用传递给subscribe的callback,我们在这个callback里面所需要完成的逻辑就是从store中取出我们所需要的state,接着在setState即可。
理解了容器组件所完成的大致功能后,我们现在再回过头来看看mapStateToProps以及mapDispatchToProps,首先,这两个方法都是返回一个对象。前者方法所返回的对象应该包括我们的视图组件所需要渲染的数据,所以这个对象中应该具有视图组件所接受到的关于数据展示的props这个字段。理解一下它的参数,storeState参数即是store.getState()所返回的对象,ownProps即我们这个容器组件所接收到的来自父组件的内容。所以对于这个函数来说,他完成的功能就是保持视图组件所需要的数据与store所维护的状态保持同步。
再来看看mapDispatchToProps方法,他接受两个参数,第一个参数就是dispatch,他就是store.dispatch,ownProps就是容器组件所接受到的来自父组件的数据。他也是返回一个对象,这个对象所包括的内容就是我们在容器组件所自定义的合成事件,因此这个方法实现的就是迭代store中所维护的state。