本篇主要將react全家桶的產品很是精煉的提取了核心內容,精華程度堪比精油。各位大人,既然來了,客官您坐,來人,給客官看茶~~javascript
首先,本篇文章要求您對js,react等知識有必定的瞭解,若是未曾瞭解,建議您先看一下:React精髓!一篇全歸納(急速) java
React有props和state:react
這就意味着若是是一個數據狀態很是複雜的應用,更多的時候發現React根本沒法讓兩個組件互相交流,使用對方的數據,react的經過層級傳遞數據的這種方法是很是難受的,這個時候,迫切須要一個機制,把全部的state集中到組件頂部,可以靈活的將全部state各取所需的分發給全部的組件,是的,這就是reduxajax
combineReducers()
上述步驟,對應的序號,我會在相關代碼標出npm
npm install redux -S // 安裝
import { createStore } from 'redux' // 引入
const reducer = (state = {count: 0}, action) => {----------> ⑴
switch (action.type){
case 'INCREASE': return {count: state.count + 1};
case 'DECREASE': return {count: state.count - 1};
default: return state;
}
}
const actions = {---------->⑵
increase: () => ({type: 'INCREASE'}),
decrease: () => ({type: 'DECREASE'})
}
const store = createStore(reducer);---------->⑶
store.subscribe(() =>
console.log(store.getState())
);
store.dispatch(actions.increase()) // {count: 1}
store.dispatch(actions.increase()) // {count: 2}
store.dispatch(actions.increase()) // {count: 3}
複製代碼
本身畫了一張很是簡陋的流程圖,方便理解redux的工做流程redux
剛開始就說了,若是把store直接集成到React應用的頂層props裏面,只要各個子組件能訪問到頂層props就好了,好比這樣:api
<頂層組件 store={store}>
<App />
</頂層組件>
複製代碼
不就ok了嗎?這就是 react-redux。Redux 官方提供的 React 綁定庫。 具備高效且靈活的特性。promise
看我上邊那個代碼的頂層組件4個字。對,你沒有猜錯。這個頂級組件就是Provider,通常咱們都將頂層組件包裹在Provider組件之中,這樣的話,全部組件就均可以在react-redux的控制之下了,可是store必須做爲參數放到Provider組件中去bash
<Provider store = {store}>
<App />
<Provider>
複製代碼
這個組件的目的是讓全部組件都可以訪問到Redux中的數據。
複製代碼
這個纔是react-redux中比較難的部分,咱們詳細解釋一下服務器
首先,先記住下邊的這行代碼:
connect(mapStateToProps, mapDispatchToProps)(MyComponent)
複製代碼
這個單詞翻譯過來就是把state映射到props中去 ,其實也就是把Redux中的數據映射到React中的props中去。
舉個栗子:
const mapStateToProps = (state) => {
return {
// prop : state.xxx | 意思是將state中的某個數據映射到props中
foo: state.bar
}
}
複製代碼
而後渲染的時候就可使用this.props.foo
class Foo extends Component {
constructor(props){
super(props);
}
render(){
return(
// 這樣子渲染的其實就是state.bar的數據了
<div>this.props.foo</div>
)
}
}
Foo = connect()(Foo);
export default Foo;
複製代碼
而後這樣就能夠完成渲染了
這個單詞翻譯過來就是就是把各類dispatch也變成了props讓你能夠直接使用
const mapDispatchToProps = (dispatch) => { // 默認傳遞參數就是dispatch
return {
onClick: () => {
dispatch({
type: 'increatment'
});
}
};
}
複製代碼
class Foo extends Component {
constructor(props){
super(props);
}
render(){
return(
<button onClick = {this.props.onClick}>點擊increase</button>
)
}
}
Foo = connect()(Foo);
export default Foo;
複製代碼
組件也就改爲了上邊這樣,能夠直接經過this.props.onClick,來調用dispatch,這樣子就不須要在代碼中來進行store.dispatch了
react-redux的基本介紹就到這裏了
若是按照原始的redux工做流程,當組件中產生一個action後會直接觸發reducer修改state,reducer又是一個純函數,也就是不能再reducer中進行異步操做;
而每每實際中,組件中發生的action後,在進入reducer以前須要完成一個異步任務,好比發送ajax請求後拿到數據後,再進入reducer,顯然原生的redux是不支持這種操做的
這個時候急需一箇中間件來處理這種業務場景,目前最優雅的處理方式天然就是redux-saga
redux-saga提供了一些輔助函數,用來在一些特定的action 被髮起到Store時派生任務,下面我先來說解兩個輔助函數:takeEvery
和 takeLatest
takeEvery就像一個流水線的洗碗工,過來一個髒盤子就直接執行後面的洗碗函數,一旦你請了這個洗碗工他會一直執行這個工做,絕對不會中止接盤子的監聽過程和觸發洗盤子函數
例如:每次點擊 按鈕去Fetch獲取數據時時,咱們發起一個 FETCH_REQUESTED 的 action。 咱們想經過啓動一個任務從服務器獲取一些數據,來處理這個action,相似於
window.addEventLister('xxx',fn)
複製代碼
當dispatch xxx的時候,就會執行fn方法,
首先咱們建立一個將執行異步 action 的任務(也就是上邊的fn):
// put:你就認爲put就等於 dispatch就能夠了;
// call:能夠理解爲實行一個異步函數,是阻塞型的,只有運行完後面的函數,纔會繼續往下;
// 在這裏能夠片面的理解爲async中的await!但寫法直觀多了!
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)
}
複製代碼
注意:上面的 takeEvery 函數可使用下面的寫法替換
function* watchFetchData() {
while(true){
yield take('FETCH_REQUESTED');
yield fork(fetchData);
}
}
複製代碼
在上面的例子中,takeEvery 容許多個 fetchData 實例同時啓動,在某個特定時刻,咱們能夠啓動一個新的 fetchData 任務, 儘管以前還有一個或多個 fetchData 還沒有結束
若是咱們只想獲得最新那個請求的響應(例如,始終顯示最新版本的數據),咱們可使用 takeLatest 輔助函數
import { takeLatest } from 'redux-saga'
function* watchFetchData() {
yield* takeLatest('FETCH_REQUESTED', fetchData)
}
複製代碼
和takeEvery不一樣,在任什麼時候刻 takeLatest 只容許執行一個 fetchData 任務,而且這個任務是最後被啓動的那個,若是以前已經有一個任務在執行,那以前的這個任務會自動被取消
redux-saga框架提供了不少建立effect的函數,下面咱們就來簡單的介紹下開發中最經常使用的幾種
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的 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函數你能夠把它簡單的理解爲就是能夠調用其餘函數的函數,它命令 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 函數和 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 函數是用來指示 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的使用方式仍是比較簡單的,相比較以前的redux框架的CounterApp,多了一個sagas的文件,reducers文件仍是以前的使用方式
ok,故事到這裏就接近尾聲了,以上主要介紹了redux,react-redux和redux-saga目前redux全家桶主流的一些產品,接下來,主要會產出一下根據源碼,手寫一下redux和react-redux的輪子