- React有props和state:
props意味著父級分發下來的屬性
state意味著組件內部可以自行管理的狀態,并且整個React沒有數據向上回溯的能力,這就是react的單向數據流。這就意味著如果是一個數據狀態非常復雜的應用,更多的時候發現React根本無法讓兩個組件互相交流,使用對方的數據,react的通過層級傳遞數據的這種方法是非常難受的,這個時候,迫切需要一個機制,把所有的state集中到組件頂部,能夠靈活的將所有state各取所需的分發給所有的組件,這就是redux - Redux的誕生是為了給 React 應用提供「可預測化的狀態管理」機制。
- Redux會將整個應用狀態(其實也就是數據)存儲到到一個地方,稱為store(就是一個數據池,一個應用只有一個),這個store里面保存一棵狀態樹(state tree),組件改變state的唯一方法是通過調用store的dispatch方法,觸發一個action,這個action被對應的reducer(reducer就是改變state的處理層,它接收action和state,通過處理action來返回新的state)處理,于是state完成更新
組件可以派發(dispatch)行為(action)給store,而不是直接通知其它組件,其它組件可以通過訂閱store中的狀態(state)來刷新自己的視圖 -
使用步驟
1.創建reducer
可以使用單獨的一個reducer,也可以將多個reducer合并為一個reducer,即:combineReducers()
action發出命令后將state放入reducer加工函數中,返回新的state,對state進行加工處理
2.創建action
用戶是接觸不到state的,只能由view觸發,action可以理解為指令,需要發出多少動作就有多少指令,action是一個對象,其中的type屬性是必須的,定義action類型,其他屬性可以自由設置
3.創建store,使用createStore方法。store 可以理解為有多個加工機器的總工廠,提供subscribe,dispatch,getState這些方法。
- 如果把store直接集成到React應用的頂層props里面,只要各個子組件能訪問到頂層props就可以了,比如這樣:
<頂層組件 store={store}>
<App />
</頂層組件>
這就是 react-redux,是為了讓redux更好的適用于react而生的一個庫。react-redux將組件區分為容器組件 和 UI 組件,前者會處理邏輯,后者只負責顯示和交互,內部不處理邏輯,狀態完全由外部掌控。
- 兩個核心
Provider
一般我們都將頂層組件包裹在Provider組件之中,這樣,所有組件就都可以在react-redux的控制之下了,但是store必須作為參數放到Provider組件中。
<Provider store = {store}>
<App />
<Provider>
// 這樣,所有組件都能夠訪問到Redux中的數據
connect
這個方法可以從UI組件生成容器組件,但容器組件的定位是處理數據、響應行為,因此,需要對UI組件添加額外的東西,即mapStateToProps和mapDispatchToProps,也就是在組件外加了一層state,用法如下:
connect(mapStateToProps, mapDispatchToProps)(MyComponent)
mapStateToProps
把Redux中的數據映射到React中的props中去,舉例:
const mapStateToProps = (state) => {
return {
foo: state.bar
}
}
然后渲染的時候就可以使用this.props.foo
class Foo extends Component {
constructor(props){
super(props);
}
render(){
return(
<div>this.props.foo</div>
)
}
}
Foo = connect()(Foo);
export default Foo;
然后這樣就可以完成渲染了
mapDispatchToProps
把各種dispatch也變成了可以直接使用的props,舉例:
const mapDispatchToProps = (dispatch) => {
return {
onClick: () => {
dispatch({type: 'increatment'});
}
}
}
class Foo extends Component {
constructor(props){
super(props);
}
render(){
return(
<button onClick = {this.props.onClick}>click increase</button>
)
}
}
Foo = connect()(Foo);
export default Foo;
組件可以直接通過this.props.onClick來調用dispatch,就不需要在代碼中來進行store.dispatch了
- 如果按照原始的redux工作流程,當組件中產生一個action后會直接觸發reducer修改state,reducer又是一個純函數,也就是不能在reducer中進行異步操作;而往往實際中,組件中發生的action在進入reducer之前需要完成一個異步任務(比如發送ajax請求),拿到數據后再進入reducer,這個時候就需要一個中間件來處理這種業務場景,目前最優雅的處理方式是redux-saga中間件,它通過 Generator 函數來創建,可以用同步的方式寫異步的代碼,目的是更好、更易地解決異步操作(把所有異步請求集中處理)。
redux-saga提供了一些輔助函數,用來在一些特定的action 被發起到Store時派生任務,我們先來看兩個輔助函數:takeEvery 和 takeLatest
takeEvery
takeEvery,同一個action多次觸發,每個都會執行
例如:每次點擊按鈕去Fetch數據時,我們發起一個 FETCH_REQUESTED 的 action,想通過啟動一個任務從服務器獲取一些數據,來處理這個action
首先創建一個將執行異步 action 的任務
// put:你就認為put就等于 dispatch就可以了
// call:可以理解為實行一個異步函數,是阻塞型的,只有運行完后面的函數,才會繼續往下
import { call, put } from 'redux-saga/effects'
export function* fetchData(action) {
try {
const apiAjax = (params) => fetch(url, params);
const data = yield call(apiAjax);
yield put({type: "FETCH_SUCCEEDED", data});
} catch (error) {
yield put({type: "FETCH_FAILED", error});
}
}
然后在每次 FETCH_REQUESTED action 被發起時啟動上面的任務,也就相當于每次觸發一個名字為 FETCH_REQUESTED 的action就會執行上邊的任務,代碼如下
import { takeEvery } from 'redux-saga'
function* watchFetchData() {
yield* takeEvery("FETCH_REQUESTED", fetchData)
}
takeLatest
在上面的例子中,takeEvery 允許多個 fetchData 實例同時啟動,在某個特定時刻,我們可以啟動一個新的 fetchData 任務, 盡管之前還有一個或多個 fetchData 尚未結束,如果我們只想得到最新那個請求的響應(例如,始終顯示最新版本的數據),我們可以使用 takeLatest
import { takeLatest } from 'redux-saga'
function* watchFetchData() {
yield* takeLatest('FETCH_REQUESTED', fetchData)
}
和takeEvery不同,在任何時刻 takeLatest 只允許執行一個 fetchData 任務,并且這個任務是最后被啟動的那個,如果之前已經有一個任務在執行,那之前的這個任務會自動被取消
Effect Creators
redux-saga框架提供了很多創建effect的函數,下面我們就來簡單的介紹下開發中最常用的幾種
- take(pattern)
- put(action)
- call(fn, ...args)
- fork(fn, ...args)
- select(selector, ...args)
take(pattern)
take函數可以理解為監聽未來的action,它創建了一個命令對象,告訴middleware等待一個特定的action,Generator會暫停,直到一個與pattern匹配的action被發起,才會繼續執行下面的語句,也就是說,take是一個阻塞的 effect,用法:
function* watchFetchData() {
while(true) {
// 監聽一個type為 'FETCH_REQUESTED' 的action的執行,直到等到這個Action被觸發,才會接著執行下面的 yield fork(fetchData) 語句
yield take('FETCH_REQUESTED');
yield fork(fetchData);
}
}
put(action)
put函數是用來發送action的 effect,你可以簡單的把它理解成為redux框架中的dispatch函數,當put一個action后,reducer中就會計算新的state并返回,put 也是阻塞 effect,用法:
export function* toggleItemFlow() {
let list = []
// 發送一個type為 'UPDATE_DATA' 的Action,用來更新數據,參數為 `data:list`
yield put({
type: actionTypes.UPDATE_DATA,
data: list
})
}
call(fn, ...args)
call函數簡單的理解為可以調用其他函數的函數,它命令 middleware 來調用fn 函數,args為函數的參數,注意:fn 函數可以是一個 Generator 函數,也可以是一個返回 Promise 的普通函數,call 函數也是阻塞 effect,用法:
export const delay = ms => new Promise(resolve => setTimeout(resolve, ms))
export function* removeItem() {
try {
// 這里call 函數就調用了 delay 函數,delay 函數為一個返回promise 的函數
return yield call(delay, 500)
} catch (err) {
yield put({type: actionTypes.ERROR})
}
}
fork(fn, ...args)
fork 函數和 call 函數很像,都是用來調用其他函數的,但是fork函數是非阻塞函數,也就是說,程序執行完 yield fork(fn, args) 這一行代碼后,會立即接著執行下一行代碼語句,而不會等待fn函數返回結果后再執行下面的語句,用法:
import { fork } from 'redux-saga/effects'
export default function* rootSaga() {
// 下面的四個 Generator 函數會一次執行,不會阻塞執行
yield fork(addItemFlow)
yield fork(removeItemFlow)
yield fork(toggleItemFlow)
yield fork(modifyItem)
}
select(selector, ...args)
select 函數是用來指示 middleware調用提供的選擇器獲取Store上的state數據,可以簡單的把它理解為redux框架中獲取store上的 state數據一樣的功能 :store.getState(),用法:
export function* toggleItemFlow() {
// 通過 select effect 來獲取 全局 state上的 `getTodoList` 中的 list
let tempList = yield select(state => state.getTodoList.list)
}
一個具體的實例
- index.js
import React from 'react';
import ReactDOM from 'react-dom';
import {createStore, applyMiddleware} from 'redux'
import createSagaMiddleware from 'redux-saga'
import rootSaga from './sagas'
import Counter from './Counter'
import rootReducer from './reducers'
const sagaMiddleware = createSagaMiddleware() // 創建了一個saga中間件實例
// 下邊這句話和下邊的兩行代碼創建store的方式是一樣的
// const store = createStore(reducers,applyMiddlecare(middlewares))
const createStoreWithMiddleware = applyMiddleware(middlewares)(createStore)
const store = createStoreWithMiddleware(rootReducer)
sagaMiddleware.run(rootSaga)
const action = type => store.dispatch({ type })
function render() {
ReactDOM.render(
<Counter
value={store.getState()}
onIncrement={() => action('INCREMENT')}
onDecrement={() => action('DECREMENT')}
onIncrementAsync={() => action('INCREMENT_ASYNC')} />,
document.getElementById('root')
)
}
render()
store.subscribe(render)
- sagas.js
import { put, call, take,fork } from 'redux-saga/effects';
import { takeEvery, takeLatest } from 'redux-saga'
export const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
function* incrementAsync() {
// 延遲 1s 在執行 + 1操作
yield call(delay, 1000);
yield put({ type: 'INCREMENT' });
}
export default function* rootSaga() {
// while(true){
// yield take('INCREMENT_ASYNC');
// yield fork(incrementAsync);
// }
// 下面的寫法與上面的寫法上等效
yield* takeEvery("INCREMENT_ASYNC", incrementAsync)
}
- reducer.js
export default function counter(state = 0, action) {
switch (action.type) {
case 'INCREMENT':
return state + 1
case 'DECREMENT':
return state - 1
case 'INCREMENT_ASYNC':
return state
default:
return state
}
}
redux-saga基本用法總結:
- 使用 createSagaMiddleware 方法創建 saga 的 Middleware ,然后在創建的 redux 的 store 時,使用 applyMiddleware 函數將創建的 saga Middleware 實例綁定到 store 上,最后可以調用 saga Middleware 的 run 函數來執行某個或者某些 Middleware 。
- 在 saga 的 Middleware 中,可以使用 takeEvery 或者 takeLatest 等 API 來監聽某個 action ,當某個 action 觸發后, saga 可以使用 call 發起異步操作,操作完成后使用 put 函數觸發 action ,同步更新 state ,從而完成整個 State 的更新。
- ui->action1(異步請求)->redux-saga->action2->reducer(更新state)