本文是学习redux时的一些感悟以及对流程的记录, 包含redux的同步及异步处理。
这里就不对redux和react-redux作过多的介绍了。直接结合代码来解释一些遇到的问题和理解。
1.目录结构
该目录是基于create-react-app生成的项目, 添加了redux文件夹存放redux的处理逻辑
├── README.md
├── package.json
├── public
│ ├── favicon.ico
│ └── index.html
├── src
│ ├── redux
│ │ ├── actions // 将每个action按自己的方式拆分成多个, 便于管理
│ │ │ ├── action1.js
│ │ │ └── action2.js
│ │ ├── reducers
│ │ │ ├── modules // 拆分recuder
│ │ │ │ ├── reducer1.js
│ │ │ │ └── reducer2.js
│ │ │ └── index.js // 组合reducers并导出reducer
│ │ └── stores
│ │ ├── initialState.js // 储存初始state
│ │ └── index.js // 导出store
│ ├── views
│ │ └── Home.js
│ ├── App.css
│ ├── App.js
│ ├── App.test.js
│ ├── index.css
│ ├── index.js
│ └── logo.svg
└── yarn.lock
2.用到的插件
主要用到了: redux, redux-thunk, react-redux
安装:
npm i redux redux-thhunk react-redux -S
3. 实现
3.1. store
import {createStore, compose, applyMiddleware} from 'redux'
import thunk from 'redux-thunk' // redux-thunk 用于处理异步操作
import {reducer} from '../reducer' // 合并完的reducer
import initialState from './initialState' // 初始的state
export const store = createStore(
reducer,
initialState,
compose(applyMiddleware(thunk)) // 应用中间件, 如果只有一个中间件, 可以省略compose()直接写:appliMiddleware(middleware)
)
store 就是保存数据的地方,你可以把它看成一个容器。整个应用只能有一个 Store。
redux 提供createStore
这个函数,用来生成 state。
createStore函数有三个参数, 分别是: reducer, preloadedState(也就是初始值), enhancer(也就是中间件), 其中只有reducer是必传的, 其他两个可以不传。同时, 如果第二个参数是函数类型, 会默认传的是enhancer。
3.2. action
// action1.js
// 将type保存到一个对象中, 便于管理
const constant= {
ADD:'ADD',
REDUCE:"REDUCE"
}
export const ADD = {
type:constant.ADD,
}
export const REDUCE = {
type:constant.REDUCE,
}
//action2.js
const constant= {
ADD_MSG: 'ADD_MSG',
DEL_MSG: 'DEL_MSG'
}
// 异步触发action
export const ADD_MSG = msg => async (dispatch, getState) => dispatch({
type: constant.ADD_MSG,
payload: msg
})
export const DEL_MSG = msg => ({
type: constant.DEL_MSG,
payload: msg
})
action 是一个对象。其中的type
属性是必须的,表示 action 的名称。其他属性可以自由设置。
对比上面两个的action, 我们可以发现: 上面的action等于一个对象, 而下面的action是一个返回一个对象的函数。这是因为第一个是不需要做额外操作的, 所以直接把对象传给 reducer ,就可以交给reducer去进行操作了, 而如果需要传递参数, 就需要将参数通过函数返回的对象中带上该参数, 再去交给reducer处理, 具体会在组件中使用时讲到。
3.3. reducer
// 拆分的reducer
// reducer1.js, 命名导出
export const reducer1 = function (state=123, action) {
switch(action.type){
case 'ADD':
return ++state
case 'REDUCE':
return state-1
default :
return state
}
}
// reducer2.js 默认导出
export default function (state=[], action) {
switch(action.type){
case 'ADD_MSG':
return [...state,action.payload]
case 'DEL_MSG':
return state.filter(item=>item.msg!==action.payload.msg)
default :
return state
}
}
reducer 是一个函数,它接受 action 和当前 state 作为参数,返回一个新的 state。
注意: 每一个reducer接受的的state参数必须设置一个默认值, 否则会报错
// 组合拆分的reducers, 导出组合后的reducer
// reducers/index.js
import {combineReducers} from "redux";
import {reducer1} from './modules/reducer1'
import reducer2 from './modules/reducer2'
export const reducer = combineReducers({
reducer1,
msg: reducer2
})
将每个拆分的reducer通过combineReducers函数组合起来。
注意: combineReducer接受一个对象, 对象的键名为创建后的state的键名, 值为每个reducer, 所以最终生成的state应该是这样的:
{
reducer: xxx,
msg:{}
}
换句话就是说: 拆分reducer后, redux最终生成的state的键名与createStore函数中传入的初始值无关, 而是取决于combineReducers时传入对象的键名。
4. redux结合react
// App.js
import React, {Component} from 'react';
import {Provider} from 'react-redux'
import {store} from '@/redux/store'
import Home from './views/Home'
class App extends Component {
render() {
return (
<Provider store={store}> // 通过react-redux的Provider组件让使用connect方法生成的组件能拿到state
<Home/>
</Provider>
)
}
}
export default App;
// Home.js
import React, {Component} from 'react';
import {connect} from 'react-redux'
import {ADD,REDUCE} from '@/redux/actions/action1'
import {ADD_MSG,DEL_MSG} from '@/redux/actions/action2'
class Home extends Component {
state={
msg:'',
}
onInput=(e)=>{
this.setState({
msg:e.target.value
})
}
addMsg = ()=>{
const msg = {
msg:this.state.msg
}
this.props.addMsg(msg)
this.setState({
msg:''
})
}
delMsg = (msg)=>{
this.props.delMsg(msg)
}
render() {
return (
<div>
<p>{this.props.num}</p>
<button onClick={this.props.addNum}>加一</button>
<button onClick={this.props.reduceNum}>减一</button>
{
this.props.msg.map(item=>{
return (
<p onClick={this.props.delMsg.bind(this,item)} key={item.msg}>{item.msg}</p>
)
})
}
<input type="text" value={this.state.msg} onChange={(e)=>this.onInput(e)}/>
<button onClick={this.addMsg}>添加一条消息</button>
</div>
);
}
}
const mapStateToProps = (state,ownProps)=>{
return {
num: state.reducer1,
msg:state.msg
}
}
const mapDispatchToProps = (dispatch,ownProps)=>{
return{
addNum: ()=>dispatch(ADD),
reduceNum: ()=>dispatch(REDUCE),
addMsg: (msg)=>dispatch(ADD_MSG(msg)),
delMsg: (msg)=>dispatch(DEL_MSG(msg))
}
}
export default connect(mapStateToProps,mapDispatchToProps)(Home)
4.1. connect
react-redux 提供connect
方法,用于从 UI 组件生成容器组件。connect
的意思,就是将这两种组件连起来。
如上, connect接收mapStateToProps和mapDispatchToProps两个参数, 生成一个高阶函数, 然后传入UI组件Home, 生成一个新的组件, 该组件可以在props中访问到mapStateToProps中指定的state, 和mapDispatchToProps中指定UI 组件的参数到store.dispatch
方法的映射了。
4.2. mapStateToProps
mapStateToProps
是一个函数,它接受state
作为参数,返回一个对象。这个对象有num和msg两个属性,代表 UI 组件的同名参数。
mapStateToProps
会订阅 Store,每当state
更新的时候,就会自动执行,重新计算 UI 组件的参数,从而触发 UI 组件的重新渲染。
mapStateToProps
的第一个参数总是state
对象,还可以使用第二个参数,代表容器组件的props
对象。
connect
方法可以省略mapStateToProps
参数,那样的话,UI 组件就不会订阅Store,就是说 Store 的更新不会引起 UI 组件的更新。
4.3. mapActionToProps
mapDispatchToProps
是connect
函数的第二个参数,用来建立 UI 组件的参数到store.dispatch
方法的映射。也就是说,它定义了哪些用户的操作应该当作 Action,传给 Store。它可以是一个函数,也可以是一个对象。
如果mapDispatchToProps
是一个函数,会得到dispatch
和ownProps
(容器组件的props
对象)两个参数。
4.4. redux-thunk
总结一句话, thunk就是可以使store.dispatch可以接受函数作为参数。而在函数里我们就可以进行异步操作, 从而实现redux的异步操作。
具体的说明可以参考阮一峰的Redux 入门教程(二):中间件与异步操作
export const ADD_MSG = msg => async (dispatch, getState) => dispatch({
type: constant.ADD_MSG,
payload: msg
})
如上, 我们在本来应该返回对象的地方返回了一个async函数, 函数接收dispatch跟getState两个参数, 然后返回一个dispatch, 发出一个action, 这样就模拟了一个异步操作。
参考资料: