本篇主要是redux基础使用篇,如果你已经掌握了redux+redux-saga基础,可以直接看下一篇redux最佳实践-前世今生2
本篇理论实际应用于antd-custom框架
大纲:
1.Redux基础知识
redux只有一个store和一个根级reduce(root reducer)函数,通常将reducer拆分成多个小的reducers,就像一个react应用只有一个根级的组件,这个根组件又由多个小组件构成。
reducer管理整个应用的state,每个reducers分别独立操作state tree 的不同部分
redux 三大原则
1)单一数据源:整个应用的state被存储在一棵object tree中,并且这个object tree只存唯一一个store中
2)state是只读:唯一改变state的方法就是触发action,action是一个用于描述已发生事件的普通对象。
3)使用纯函数来执行修改:改变state你需要编写(Reducer),(oldState, action) => newState,每次更改总是返回一个新的 State
tips:纯函数就是输出只依赖于输入,输入值确定后,每次输出值必然相同,纯函数内部不会依赖其他外部变量
redux数据流
2)reducer会修改state状态,即(oldState, action) => newState
3)state状态发生改变后,view层对应的状态也会发生改变
tips:redux本身只是一个状态管理工具,与react并无关联,要想与react联系起来还需要一个中间件react-redux。
react-redux中间件
react-redux主要提供两大功能
1)connect():高阶函数,利用subscribe()和Context原理监听订阅模式传递state
2)Provider:利用ChildContext原理向子组件传递store
具体可看react-redux源码,很短,约百来行代码
2.Redux实例
本例子是redux+redux-saga配置,至于redux-saga是什么,后面会介绍
基础配置
// app.jsx
import React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'
import store from './redux/store'
import Home from './home'
const App = (
<Provider store={store}>
<Home />
</Provider>
)
render(App, document.getElementById('root'))
// store.js
import createSagaMiddleware from 'redux-saga'
import { createStore, applyMiddleware } from 'redux'
import reducer from './rootReducer'
import sagas from './rootSaga'
const sagaMiddleware = createSagaMiddleware()
const store = createStore(
reducer,
applyMiddleware(sagaMiddleware)
)
sagaMiddleware.run(sagas)
export default store
// rootReducer.js
import { combineReducers } from 'redux'
import incrementReducer from './increment.reducer'
const rootReducer = combineReducers({
incrementReducer,
})
export default rootReducer
// 处理同步不需要saga,这里先返回一个空函数 // rootSaga.js
import { fork, all } from 'redux-saga/effects'
export default function *rootSaga() {
}
以上是redux+redux-saga最基础的配置
1)Redux处理同步
i)redux最原始处理同步,dispatch->action->reducer
// home.jsx
import React from 'react'
import { Button } from 'antd'
import { connect } from 'react-redux'
import { incrementAction } from './redux/increment.action'
const Home = (props) => {
const onIncrement = (e) => {
props.dispatch(incrementAction(1))
}
return (
<aside>
<Button type='primary' onClick={onIncrement}>+1</Button>
<h4>count: { props.count }</h4>
</aside>
)
}
const mapDispatchToProp = (dispatch) => {
return { dispatch }
}
const mapStateToProp = state => {
const { incrementReducer } = state
return incrementReducer
}
export default connect(mapStateToProp, mapDispatchToProp)(Home)
// increment.action.js
const incrementAction = (payload) => {
return {
type: 'INCREMENT',
payload,
}
}
export default incrementAction
// increment.reducer.js
const incrementReducer = (state = {
count: 0,
loading: false,
}, action) => {
const { type } = action
switch(type) {
case 'INCREMENT':
return {
...state,
count: state.count + 1,
}
case 'INCREMENT_ASYNC':
return {
...state,
loading: true,
}
case 'INCREMENT_ASYNC_SUCCESS':
const { payload } = action
return {
...state,
count: state.count + payload,
loading: false,
}
default:
return state
}
}
export default incrementReducer
其中,涉及3个文件home.jsx,increment.action.js,increment.reducer.js,即在view层发起dispatch->action->reducer来改变state,state状态发生变化后react-redux监听state状态进而更新view层,以上是redux最原始处理同步行为
效果图:
ii)redux处理同步提取action省略写action,dispatch->reducer
import React from 'react'
import { Button } from 'antd'
import { connect } from 'react-redux'
// import { incrementAction } from '../../redux/increment.action'
const Home = (props) => {
const onIncrement = (e) => {
props.dispatch({
type: 'INCREMENT',
payload: 1,
})
}
// ...
上述合并action文件和操作,只剩下home.jsx,increment.reducer.js,流程不变,只是简化了aciton 变为dipatch(action)->reducer来改变state,state状态发生变化后react-redux监听state状态进而更新view层,以上是redux进阶处理同步行为
2)Redux处理异步
redux本身只能处理同步,不能处理异步,要处理异步需要引入其他中间件,这里用redux-saga
i)redux异步处理,引入中间件redux-saga处理异步
// rootSaga.js
import { fork, all } from 'redux-saga/effects'
import { watchIncrementAsync } from './increment.saga'
export default function *rootSaga() {
yield all([
watchIncrementAsync()
])
}
// increment.saga.js
import { put, takeEvery, call, takeLatest } from 'redux-saga/effects'
import { increment } from '../api/increment.api'
export function* incrementAsync({ type, payload }) {
const resp = yield call(increment, payload)
console.log("resp", resp)
yield put({ type: 'INCREMENT_ASYNC_SUCCESS', payload: resp.data })
}
export function* watchIncrementAsync() {
yield takeLatest('INCREMENT_ASYNC', incrementAsync)
}
export function* watchIncrementAsync() {
yield takeEvery('INCREMENT_ASYNC', incrementAsync)
}
// increment.api.js
export async function increment(payload) {
const { data } = payload || {}
// fetch, axios向后端发起请求,这里用setTimeout模拟异步
return new Promise(resolve => {
if (payload) {
console.log('payload', payload)
setTimeout(() => {
resolve(payload)
}, 1000)
}
})
}
// home.jsx
//...
const Home = (props) => {
//...
const onIncrementAsync = (e) => {
props.dispatch({
type: 'INCREMENT_ASYNC',
payload: {
data: 10,
},
})
}
return (
<aside>
<Button type='primary' onClick={onIncrement}>+1</Button>
<Button type='primary' onClick={onIncrementAsync} loading={props.loading}>+10</Button>
<h4>count: { props.count }</h4>
</aside>
)
}
//...
异步处理流程:
在view层发起dispatch(action),redux-saga监听action,sagaMiddleware中间件处理yield,redux-saga调用call进行异步处理,堵塞调用返回api结果,获取返回结果后,sagaMiddleware再次触发yield操作,调用redux-saga的put操作,将action传递给reducer,进而改变state,state状态发生变化后react-redux监听state状态进而更新view层
异步流程:view -> dispatch(action) -> redux-saga -> reducer(state) -> view
同步流程:view -> dispatch(action) -> reducer(state) -> view
整体流程:
效果图:
3.总结 本篇只是简单阐述了redux基础应用,为Redux最佳实践的前世,当项目小一些还好,但当项目慢慢变大,需要在*.reducer.js和*.saga.js文件中来回切换,并不是我们想要的,下一篇将重点讲述redux最佳实践的今生,redux最佳实践-前世今生2
上述例子可正常运行,本篇理论实际应用于antd-custom框架
tips:redux-saga几个重要概念
1)yield作用 Effect 是一个 javascript 对象,里面包含描述副作用的信息,可以通过 yield 传达给 sagaMiddleware 执行
在 redux-saga 世界里,所有的 Effect 都必须被 yield 才会执行,所以有人写了 eslint-plugin-redux-saga 来检查是否每个 Effect 都被 yield。并且原则上来说,所有的 yield 后面也只能跟Effect,以保证代码的易测性。
2)阻塞与非阻塞调用 redux-saga 可以用 fork 和 call 来调用子 saga ,其中 fork 是无阻塞型调用,call 是阻塞型调用
想了解更多redux-saga请点击这里