Redux并不是只能在React應(yīng)用中使用,而是可以在一般的應(yīng)用中使用。
一、首先來看一下redux的工作流:

ReactComponents可以看做'借書人',ActionCreators可以看做‘我要借本書’,Store可以看做圖書管理員,Reducers可以看做'電腦/手冊(cè)'
- store是唯一的,只有store能夠改變自己的內(nèi)容。唯一改變state的方法是觸發(fā)
action - 借書人(ReactComponents)通過觸發(fā)action告訴圖書管理員(Store)我要借本書(ActionCreators)。圖書管理員(Store)收到消息后通過查閱電腦/手冊(cè)(Reducers),將書(state)借給借書人(ReactComponents)
- action實(shí)質(zhì)上是一個(gè)對(duì)象。我們通過ActionCreators統(tǒng)一創(chuàng)建action(定義很多個(gè)方法,每個(gè)方法都導(dǎo)出這個(gè)action對(duì)象)
const action = {
type: INPUT_CHANGE,
value: e.target.value
}
// store.dispatch(action)
// actionCreators.js
export const onInputChange = (value) => ({
type: INPUT_CHANGE,
value
})
// store.dispatch(onInputChange(e.target.value))
- store.dispatch(action) 當(dāng)store接收到action后,會(huì)將previousState和action自動(dòng)轉(zhuǎn)發(fā)給reducer, reducer處理完成相應(yīng)的邏輯后將新修改的newState返回給store。store接收到reducer傳來的新的state后會(huì)替換掉原來舊的state,從而實(shí)現(xiàn)store里的state的更新
- reducer可以接收state,但絕不能修改state。所以要將從store接收到的state深拷貝一份,返回新更改的state
- reducer必須是純函數(shù)。純函數(shù)指的是,給定固定的輸入,就一定會(huì)有固定的輸出。同時(shí)不會(huì)有任何副作用。reducer里不能有new Date()、setTimeOut、 ajax請(qǐng)求等
const defaultStore = {
inputValue: ''
}
export default (state = defaultStore, action) => {
if (action.type === INPUT_CHANGE) {
const newState = JSON.parse(JSON.stringify(state))
newState.inputValue = action.value
return newState
}
return state
}
- store.subscribe()這個(gè)方法是在組件里訂閱store.只要store一改變,subscribe里的方法就會(huì)自動(dòng)執(zhí)行
constructor (props) {
super(props)
this.state = store.getState()
store.subscribe(this.handelStoreChange)
}
handelStoreChange () {
this.setState(store.getState())
}
二、store中的方法
Redux中的store是一個(gè)保存整個(gè)應(yīng)用state對(duì)象樹的對(duì)象,其中包含了幾個(gè)方法,它的原型如下:
type Store = {
dispatch: Dispatch
getState: () => State
subscribe: (listener: () => void) => () => void
replaceReducer: (reducer: Reducer) => void
}
- dispatch 用于發(fā)送action(動(dòng)作)使用的的方法
- getState 取得目前state的方法
- subscribe 注冊(cè)一個(gè)回調(diào)函數(shù),當(dāng)state有更動(dòng)時(shí)會(huì)調(diào)用它
- replaceReducer 高級(jí)API,用于動(dòng)態(tài)加載其它的reducer,一般情況不會(huì)用到
三、使用redux-thunk中間件實(shí)現(xiàn)ajax請(qǐng)求
- redux-thunks是redux的中間件。我們?cè)趧?chuàng)建store的時(shí)候,就使用中間件。
import {createStore, applyMiddleware, compose} from 'redux'
import thunk from 'redux-thunk'
import reducer from './reducer'
const Combine = compose(applyMiddleware(thunk), window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__())
var store = createStore(reducer, Combine)
export default store
- 使用了redux-thunk之后,actionCreater可以return出去一個(gè)函數(shù)(正常情況下return一個(gè)對(duì)象)
export const initListData = (list) => ({
type: INIT_LIST,
list
})
export const getListData = () => {
return dispatch => {
axios.get('/api/todolist')
.then((res) => {
const data = res.data
dispatch(initListData(data))
})
}
}
- 只有使用了thunk這個(gè)中間件,action才能是個(gè)函數(shù),當(dāng)store發(fā)現(xiàn)action是個(gè)函數(shù)時(shí),會(huì)自動(dòng)執(zhí)行這個(gè)函數(shù)
componentDidMount () {
// axios.get('/api/todolist')
// .then((res) => {
// console.log(res.data);
// store.dispatch(initListData(res.data))
// })
store.dispatch(getListData())
}
當(dāng)我們把異步請(qǐng)求放在組件里的生命周期里時(shí),隨著代碼量的增加,這個(gè)生命周期函數(shù)可能會(huì)變得越來越復(fù)雜,組件變得越來越大。所以建議還是將這種復(fù)雜的業(yè)務(wù)邏輯異步代碼拆分到一個(gè)地方去管理。
現(xiàn)在借助redux-thunk 將異步請(qǐng)求放在actionCreator中去管理。
放在這里又帶來一個(gè)好處,就是自動(dòng)化測(cè)試變得很簡(jiǎn)單,比測(cè)試組件的生命周期函數(shù)簡(jiǎn)單的多。
- redux中間件指的是action和store之間。action通過dispatch方法被傳遞給store。實(shí)際上中間件就是對(duì)dispatch方法的一個(gè)封裝。
d7f70ee27ee955877023b3ec74db848.png
最原始的dispatch方法,接收到一個(gè)對(duì)象之后會(huì)把這個(gè)對(duì)象傳遞給store.當(dāng)有了中間件(對(duì)dispatch方法進(jìn)行升級(jí)之后),當(dāng)傳遞一個(gè)函數(shù)時(shí),dispatch不會(huì)把這個(gè)函數(shù)直接傳給store,而是先執(zhí)行這個(gè)函數(shù).所以dispatch會(huì)根據(jù)參數(shù)的不同做不同的事情。
四、使用redux-saga中間件實(shí)現(xiàn)ajax請(qǐng)求
- redux-thunk中間件是把異步操作放在action里,redux-suga是單獨(dú)把異步邏輯拆分出來放到一個(gè)文件里去管理.
- saga里面都是generator函數(shù)
- saga.js中會(huì)接收到每一次派發(fā)出的action
- takeEvery函數(shù)接收到相應(yīng)的action類型,會(huì)執(zhí)行第二個(gè)參數(shù)的方法,將相應(yīng)的異步邏輯寫在第二個(gè)參數(shù)的方法中。建議寫成generator函數(shù)。
import {createStore, applyMiddleware, compose} from 'redux'
import createSagaMiddleware from 'redux-saga'
import reducer from './reducer'
import todoSaga from './saga'
const sagaMiddleware = createSagaMiddleware()
const enhancers = compose(applyMiddleware(sagaMiddleware),window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__())
var store = createStore(reducer, enhancers)
sagaMiddleware.run(todoSaga)
export default store
componentDidMount () {
store.dispatch(getListData())
}
export const initListData = (list) => ({
type: INIT_LIST,
list
})
export const getListData = () => ({
type: GRT_INIT
})
import {put, takeEvery} from 'redux-saga/effects'
import {GRT_INIT} from './actiontypes'
import {initListData} from './actionCreators'
import axios from 'axios'
function* asyncGetList () {
try {
const res = yield axios.get('/api/todolist')
const action = initListData(res.data)
yield put(action)
} catch (err) {
console.error(err)
}
}
function* todoSaga () {
yield takeEvery(GRT_INIT, asyncGetList)
}
export default todoSaga
- takeEvery和takeLatest的區(qū)別:
takeEvery每次觸發(fā)都會(huì)執(zhí)行,不會(huì)中斷;
takeLatest連續(xù)觸發(fā),只執(zhí)行最后一次觸發(fā)的結(jié)果(比如點(diǎn)擊提交按鈕,避免提交多次) - 調(diào)用promise的地方用call語(yǔ)句包裹起來
function* fetchUser() {
const user = yield call(axios.get, 'https://jsonplaceholder.typicode.com/users');
console.log(user)
}
-
并發(fā)請(qǐng)求可以使用
yeild all([ ])把多個(gè)請(qǐng)求包起來
image.png -
當(dāng)saga函數(shù)特別多時(shí)如何組織文件?
方法一:
image.png
使用fork關(guān)鍵字運(yùn)行函數(shù),上面例子相當(dāng)于:

因?yàn)閡serSagas打印出來是個(gè)對(duì)象,值是watchFetchUser函數(shù)
方法二:在各自文件入口維護(hù)好sage并導(dǎo)出來

然后在根saga文件中使用
yeild all [ ]
五、react-redux的使用
- provider將store提供給內(nèi)部所有的組件。渲染根組件時(shí)使用即可。
<Provider />是由 React Redux 提供的高階組件,用來讓你將 Redux 綁定到 React
import React from 'react'
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux'
import { createStore } from 'redux'
import App from './App';
let store = createStore(todoApp)
ReactDOM .render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
- 在組件里,使用connect,讓組件和store做連接。
- connect接收3個(gè)參數(shù),第三個(gè)參數(shù)是要和store連接的組件。mapStateToProps這個(gè)映射規(guī)則,是將store里的state映射為組件里的props。mapDispatchToProps將store.dispatch方法掛載到props上。


