React实战-深入源码了解Redux用法之Connect
Redux结构并不复杂,用法也较之Flux更为简单,使得Redux成为ReactJS的标配。虽然我并不赞成用个windows就得搞清楚windows内存/线程怎么管的,用个算法函数就要知道算法怎么写的,但是如果不去看看Redux的源码,不先了解Flux的用法,在使用Redux的时候,你会感到迷惑。再者,Redux源码并不太多,但蕴含了巧妙的设计思想,使得我们在使用Redux编码时,更加简单,比Flux的代码量少了很多,另外我们常用的Redux函数并不太多,主要有Action,Reducers,store,provider,conner等(微信:react-javascript)。
即使之前用过Flux,在使用Redux时,也会有不明就理的感觉。因为Redux为我们做了很多事情。在Redux里面,我们很少用到ReactJs component的那些事件了,连componentDidMount、componentUnMount这些主要的事件都少见了,还有涉及数据的更像操作与事件,涉及state的操作,store中require('events').EventEmitter的事件绑定与触发等等都不知所踪,但是无论怎么变,这个数据流方式是不可改变的,这也就成为我们在学习和使用Redux过程中的雾水,让我们只知其然,不知其所以然了。
只能硬着头皮去看看Redux源码的处理方式,会让我们知道它的实现方式,写起代码来不再那么晕头晕脑了。首先是connect,为什么是它,因为这是一个将操作和数据与我们的componet进行关联的函数,成为理解Redux的一个至关重要和迷惑的函数,但是不知什么原因,在Redux的官网中并没有做为头号重心来介绍,甚至在主目录中都没有出现。
首先,看看在传统Rejeact component编码中的实现方式。
1.ReactJS + Flux中组件数据更新方式。
store中的事件如下:
addChangedListener(callback)
{
this.on(CHANGE_EVENT, callback);
},
removeChangedListener(callback){
this.removeListener(CHANGE_EVENT, callback);
}
component中的事件与数据更新方式:
var PersionList= React.createClass({
getInitialState: function () {
return getPersions();
},
componentDidMount(){
PersonStore.addChangedListener(this._onChange);
},
componentWillUnmount(){
PersonStore.removeChangedListener(this._onChange);
},
onSearch()
{
PersonAction.searchByName(this.refs.organizeName.getValue());
},
_onChange(){
this.setState(getPersons());
},
render()
{
return (
<div >
<div className="search">
<TextField name="personName" ref="personName"/>
<RaisedButton label="search" primary={true} onClick={this.onSearch}/>
</div>
<div className="list">
<PersonList organizes={this.state.persons}/>
</div>
</div>
);
}
});
2.在Redux中的实现方式
let AddTodo = ({ dispatch }) => {
let input
return (
<div>
<form onSubmit={e => {
e.preventDefault()
if (!input.value.trim()) {
return
}
dispatch(addTodo(input.value))
input.value = ''
}}>
<input ref={node => {
input = node
}} />
<button type="submit">
Add Todo
</button>
</form>
</div>
)
}
AddTodo = connect()(AddTodo)
以上可以看出采用connect方式省去了很多代码和操作,数据更新不需要自己主动触发了,事件处理不需要自己去绑定和解除绑定了。但是任何事都有利有弊,如同我们采用java编码,一上来就来个eclipse,自动生成一大堆的文件,顿时蒙了,不如自己写个helloworld.java清晰。
上面的connect更是莫名其妙了,数据、事件全没有,componnet怎么实现事件提交、数据更新的?!
3.Redux中connect源码分析
还是看看一般的connect的写法吧。
function mapStateToProps(state) {
return { todos: state.todos }
}
function mapDispatchToProps(dispatch) {
return { actions: bindActionCreators(actionCreators, dispatch) }
}
export default connect(mapStateToProps, mapDispatchToProps)(TodoApp)
扩展的参数就暂时不说了,主要是mapStateToProps,mapDispatchToProps。
mapStateToProps:负责将state中的有用数据,外加组件自生的props传人关联组件。
mapDispatchToProps:主要负责传人组件关联的事件,这里暂时也不讲reduce事件了。
理解这两点,好像有点明白了,对于一个ReactJS组件,能获得组件的数据,绑定事件函数,似乎就足够了。但是问题来了,在传统方法中,我们还会绑定数据的更新操作,这里没有reactJS的state数据变化引起的组件重绘,更奇怪的是代码2中connect参数为空?!
还是看看connect的源码实现吧:
export default function connect(mapStateToProps, mapDispatchToProps, mergeProps, options = {}) {
.......
return function wrapWithConnect(WrappedComponent) {
.......
}
}
以上是connect的函数主题结构。可以看出connect是采用了闭包的方式实现了两次操作。
a.第一次:实现对mapStateToProps, mapDispatchToProps, mergeProps, options = {}等输入参数的处理。
mapStateToProps的处理方式:
进一步往里看,我们可以到对mapStateToProps参数的处理
const defaultMapStateToProps = state => ({});
const mapState = mapStateToProps || defaultMapStateToProps;
这里可以看出对mapStateToProps是有默认值处理的,如果mapStateToProps为空或者null,则默认为{}了。
mapDispatchToProps的处理方式:
const defaultMapDispatchToProps = dispatch => ({ dispatch })
mapDispatch = defaultMapDispatchToProps
这里可以看出对mapDispatchToProps是有默认值处理的,如果mapDispatchToProps为空或者null,则默认为{ dispatch }了。
其它参数类似处理了,看到这里,我们至少明白了代码2中为什么为空了。
因为componet只是添加操作,不需要数据绑定,所有参数为{},事件为submit中执行dispatch(addTodo(input.value)),参数应为{dispatch}。如果采用完整的方式,应该为:
AddTodo = connect({},{dispatch})(AddTodo)才对。
而看看connect 源码知道,为null和不为null的结果是一样的。因此采用AddTodo = connect()(AddTodo)的方式了。
b.第二步才是真正建立commponent与数据源以及事件的关联。
在采用Redux的代码中我们看不到事件绑定与解除绑定,根本原因是connect已经我们的component自己生成了新的组件,已经不再是以前你定义的组件了,在新的组件中增加了事件操作等。看看connect源码:
return function wrapWithConnect(WrappedComponent) {
......
class Connect extends Component {
shouldComponentUpdate() {
return !pure || this.haveOwnPropsChanged || this.hasStoreStateChanged
}
........
componentDidMount() {
this.trySubscribe()
}
componentWillUnmount() {
this.tryUnsubscribe()
this.clearCache()
}
}
}
从上面的代码可以看出,新创建的component中这些事件操作全了。
迷雾是不是少了很多,更多细节需要我们更加深入了解,跟随函数调用顺序,深入代码中,你会对框架的结构更加清楚。