简介
最近又重新学了一遍redux,以前感觉模糊的知识都清晰了许多。
Redux 是 JavaScript 状态容器,提供可预测化的状态管理。redux经常和react搭配使用,用以解决react组件多且状态难以维护、组件间通信困难的痛点,你可以将项目一些公用的、常用的状态存储在redux的仓库store
里,并在需要使用时通过connect
高阶函数封装你的组件,就可以在组件中用props
获取、操作这些状态。只不过与react常规操作setState
稍稍不同,组件不能直接更改状态,需要发送一个通知action
,告诉仓库store
,说我想改变状态,仓库收到这个通知后,并不直接修改状态,而是将它收到的通知委托给一个处理人去进行处理,这个处理人就是reducer
,处理人处理完毕后,返回一个新的状态给仓库让它改变状态。
编写代码
下面通过一个官网上点击+1的例子来演示这个过程,首先编写Count.js
import React, { Component } from "react";
class Count extends Component {
constructor(props) {
super(props);
this.state = {
count: 0,
};
}
add = () => {
this.setState({
count: this.state.count + 1
})
}
render() {
return (
<div>
<p>你点击了 {this.state.count} 次</p>
<button onClick={this.add}>点我+1</button>
</div>
);
}
}
export default Count;
现在要将状态count保存在redux仓库里,就必须创建一个redux仓库,
前已说到,store仓库不直接处理通知,所以创建仓库的同时,还要指定处理通知的人reducer。
store.js
import { createStore} from 'redux'
import countReducer from './countReducer'
export default createStore(countReducer);
countReducer.js
const initCount = 10;
export default (preState = initCount,action) => {
const { type } = action;
switch(type){
case 'add':
return preState + 1;
default:
return preState;
}
}
可以看到,处理人reducer默认给count赋了一个默认值10,传给了store仓库,
函数体里的switch暂且不用管,目前仓库里只有一个值,state=10。
现在仓库和处理人都有了,Count组件该如何使用这个托管的状态count呢?
在redux中传递保存的状态,主要是通过Provider
组件store属性传递整个store,
在子组件使用connect
封装子组件本身就可以在props
中接收到仓库的状态了。
所以我们对项目入口文件index.js和组件count.js修改
index.js
import React from "react";
import ReactDOM from "react-dom";
import { Provider } from "react-redux";
import store from "./store";
import Count from './count'
ReactDOM.render(
<Provider store={store}>
<Count/>
</Provider>,
document.getElementById("root")
);
count.js
import React, { Component } from "react";
import { connect } from 'react-redux'
class Count extends Component {
constructor(props) {
super(props);
this.state = {};
}
add = () => {}
render() {
return (
<div>
<p>你点击了 {this.props.count} 次</p>
<button onClick={this.add}>点我+1</button>
</div>
);
}
}
function mapStateToProps(state){
return { count: state }
}
export default connect(mapStateToProps,null)(Count);
如此一来,Count组件就接收到了store存储的状态。
connect
前两个参数十分重要,第一个参数是mapStateToProps
,第二个是mapDispatchToProps
,这两个参数均为函数,返回值均为一个对象,所以也可以写成下面的形式。
export default connect({},{})(Count);
这两个函数的意思很容易猜出,map意思是映射,第一个参数mapStateToProps
就是:映射状态给props,第二个参数就是:映射Dispatch给props,这个Dispatch下面会说到。
上面的代码就是给Count组件映射仓库保存的状态值,并命名为count,通过this.props.count
使用。
必须要强调的一点是,connect
高阶函数是给store
调用的,当它在调用时,会给默认给第一个参数mapStateToProps
函数传递总状态state,我这里的例子太简单只有一个值,实际项目往往是一个对象,要用到解构;默认给第二个参数mapDispatchToProps
函数传递发送通知的方法dispatch,所以我们编写这两个函数的时候会带上参数。
经过上面的流程,我们已经知道如何获取到store的状态了,
那么要改变状态,使得count +1 该如何实现?
在redux中,改变状态要发送通知给store,通知就是action
,通知的格式形如:
Flux标准action
{
type: 'add',
data: data
}
就是一个JSON对象,新建一个action文件用以创建action对象,
action.js
export const createAddAction = () => ({type: 'add'});
有了这个对象,就可以通过dispatch
发送通知了,继续修改count.js
count.js
import React, { Component } from "react";
import { connect } from 'react-redux'
import { createAddAction } from './action'
class Count extends Component {
constructor(props) {
super(props);
this.state = {};
}
add = () => {
this.props.increment();
}
render() {
return (
<div>
<p>你点击了 {this.props.count} 次</p>
<button onClick={this.add}>点我+1</button>
</div>
);
}
}
function mapStateToProps(state){
return { count: state }
}
function mapDispatchToProps(dispatch){
return {
increment: () => dispatch(createAddAction())
}
}
export default connect(mapStateToProps,mapDispatchToProps)(Count);
mapDispatchToProps
返回一个包含多个方法的对象,将要改变状态的意图发送给store,store再交由reducer进行处理,reducer发现action的type是add,于是匹配到reducer的第一个选项,将新的状态返回了。
switch(type){
case 'add':
return preState + 1;
default:
return preState;
}
总结
- redux运行的大体流程就是这样,难点在于
connect
高阶函数的理解,我这里在思否看到一篇写的很好的文章:React实践心得:react-redux 之 connect 方法详解,有兴趣可以去看看 - 在redux中,reducer必须是一个纯函数,它接收两个参数,第一个参数为store的前一次状态,第二个参数是传递的action对象
- redux还有其他很常用的api,比如用于组合多个reducer的
combineReducers
、用于增加中间件的applyMiddleware
等等,还有用于创建action,处理action的库redux-actions
,其中的方法能大大提高工作效率 - 本文内容为个人理解,如有错误欢迎指出。
拓展
组合多个reducer,并且添加中间件的store.js文件编写
import { createStore,applyMiddleware,combineReducers } from 'redux'
import countReducer from './reducer/count'
import personReducer from './reducer/person'
//引入redux-thunk,用于支持异步action
import thunk from 'redux-thunk'
const allReducer = combineReducers({
countReducer,
personReducer,
})
export default createStore(allReducer,applyMiddleware(thunk));