React深入 - 手寫redux api
阿新 • • 發佈:2018-11-04
簡介: 手寫實現redux基礎api
createStore( )和store相關方法
api回顧:
createStore(reducer, [preloadedState], enhancer)
建立一個 Redux store 來以存放應用中所有的 state
reducer (Function): 接收兩個引數,當前的 state 樹/要處理的 action,返回新的 state 樹
preloadedState: 初始時的 state
enhancer (Function): store creator 的高階函式,返回一個新的強化過的 store creator
Store 方法
getState() 返回應用當前的 state 樹
dispatch(action) 分發 action。這是觸發 state 變化的惟一途徑
subscribe(listener) 新增一個變化監聽器。每當 dispatch action 的時候就會執行,state 樹中的一部分可能已經變化
replaceReducer(nextReducer) 替換 store 當前用來計算 state 的 reducer(高階不常用,不作實現)實現 Redux 熱載入機制會用到
原始碼實現:
./self-redux.js export function createStore(reducer, enhancer) { if(enhancer) { return enhancer(createStore)(reducer) } let currentState = {} let currentListeners = [] function getState() { return currentState } function subscribe(listeners) { currentListeners.push(listener) } function dispatch(action) { currentState = reducer(currentState, action) currentListeners.forEach(v => v()) return action } dispatch({ type: '@rainie/init-store' }) return { getState, subscribe, dispatch } }
demo:驗證正確性
// import { createStore } from 'redux' // 將redux檔案替換成自己實現的redux檔案 import { createStore } from './self-redux.js' // 這就是reducer處理函式,引數是狀態和新的action function counter(state=0, action) { // let state = state||0 switch (action.type) { case '加機關槍': return state + 1 case '減機關槍': return state - 1 default: return 10 } } // 新建store const store = createStore(counter) const init = store.getState() console.log(`一開始有機槍${init}把`) function listener(){ const current = store.getState() console.log(`現在有機槍${current}把`) } // 訂閱,每次state修改,都會執行listener store.subscribe(listener) // 提交狀態變更的申請 store.dispatch({ type: '加機關槍' })
combineReducers(reducers)
api簡介
把一個由多個不同 reducer 函式作為 value 的 object,合併成一個最終的 reducer 函式
實現 Redux 熱載入機制會用到
import { combineReducers } from 'redux'
import todos from './todos'
import counter from './counter'
export default combineReducers({
todos,
counter
})
實現:
實質就是返回一個大的function 接受state,action,然後根據key用不同的reducer
注:combinedReducer的key跟state的key一樣
const reducer = combineReducers({
a: doSomethingWithA,
b: processB,
c: c
})
function reducer(state = {}, action) {
return {
a: doSomethingWithA(state.a, action),
b: processB(state.b, action),
c: c(state.c, action)
}
}
function combindReducer(reducers) {
// 第一個只是先過濾一遍 把非function的reducer過濾掉
const reducerKeys = Object.keys(reducers)
const finalReducers = {}
reducerKeys.forEach((key) => {
if(typeof reducers[key] === 'function') {
finalReducers[key] = reducers[key]
}
})
const finalReducersKeys = Object.keys(finalReducers)
// 第二步比較重要 就是將所有reducer合在一起
// 根據key呼叫每個reducer,將他們的值合併在一起
let hasChange = false;
const nextState = {};
return function combind(state={}, action) {
finalReducersKeys.forEach((key) => {
const previousValue = state[key];
const nextValue = reducers[key](previousValue, action);
nextState[key] = nextValue;
hasChange = hasChange || previousValue !== nextValue
})
return hasChange ? nextState : state;
}
}
applyMiddleware(...middleware)
使用包含自定義功能的 middleware 來擴充套件 Redux 是
...middleware (arguments): 遵循 Redux middleware API 的函式。
每個 middleware 接受 Store 的 dispatch 和 getState 函式作為命名引數,並返回一個函式。
該函式會被傳入 被稱為 next 的下一個 middleware 的 dispatch 方法,並返回一個接收 action 的新函式,這個函式可以直接呼叫 next(action),或者在其他需要的時刻呼叫,甚至根本不去呼叫它。
呼叫鏈中最後一個 middleware 會接受真實的 store 的 dispatch 方法作為 next 引數,並藉此結束呼叫鏈。
所以,middleware 的函式簽名是 ({ getState, dispatch }) => next => action
import { createStore, combineReducers, applyMiddleware } from 'redux'
import thunk from 'redux-thunk'
import * as reducers from './reducers'
let reducer = combineReducers(reducers)
// applyMiddleware 為 createStore 注入了 middleware:
let store = createStore(reducer, applyMiddleware(thunk))
中介軟體機制applyMiddleware的實現
中介軟體機制圖
實現步驟
1.擴充套件createStore,使其接受第二個引數(中介軟體其實就是對createStore方法的一次擴充套件)
2.實現applyMiddleware,對store的disptach進行處理
3.實現一箇中間件
正常呼叫
import React from 'react'
import ReactDOM from 'react-dom'
// import { createStore, applyMiddleware} from 'redux'
import { createStore, applyMiddleware} from './self-redux'
// import thunk from 'redux-thunk'
import thunk from './self-redux-thunk'
import { counter } from './index.redux'
import { Provider } from './self-react-redux';
import App from './App'
const store = createStore(counter, applyMiddleware(thunk))
ReactDOM.render(
(
<Provider store={store}>
<App />
</Provider>
),
document.getElementById('root'))
// 便於理解:函式柯利化例子
function add(x) {
return function(y) {
return x+y
}
}
add(1)(2) //3
applymiddleware
// ehancer(createStore)(reducer)
// createStore(counter, applyMiddleware(thunk))
// applyMiddleware(thunk)(createStore)(reducer)
// 寫法函式柯利化
export function applyMiddleware(middleware) {
return function (createStore) {
return function(...args) {
// ...
}
}
}
// 只處理一個 middleware 時
export function applyMiddleware(middleware) {
return createStore => (...args) => {
const store = createStore(...args)
let dispatch = store.dispatch
const midApi = {
getState: store.getState,
dispatch: (...args) => dispatch(...args)
}
// 經過中介軟體處理,返回新的dispatch覆蓋舊的
dispatch = middleware(midApi)(store.dispatch)
// 正常中介軟體呼叫:middleware(midApi)(store.dispatch)(action)
return {
...store,
dispatch
}
}
}
// 處理多個middleware時
// 多個 compose
export function applyMiddleware(...middlewares) {
return createStore => (...args) => {
const store = createStore(...args)
let dispatch = store.dispatch
const midApi = {
getState: store.getState,
dispatch: (...args) => dispatch(...args)
}
const middlewareChain = middlewares.map(middleware => middleware(midApi))
dispatch => compose(...middlewareChain(store.dispatch))
// dispatch = middleware(midApi)(store.dispatch)
// middleware(midApi)(store.dispatch)(action)
return {
...store,
dispatch
}
}
}
手寫redux-thunk非同步中介軟體實現
// middleware(midApi)(store.dispatch)(action)
const thunk = ({ dispatch, getState }) => next => action => {
// next就是store.dispatch函式
// 如果是函式,執行以下,引數dispatch和getState
if (typeof action == 'function') {
return action(dispatch, getState)
}
// 預設 什麼都不幹
return next(action)
}
export default thunk
處理非同步action
export function addGunAsync() {
// thunk外掛作用,這裡可以返回函式
return dispatch => {
setTimeout(() => {
// 非同步結束後,手動執行dispatch
dispatch(addGun())
}, 2000)
}
}
趁熱打鐵,再實現一箇中間件: dispatch接受一個數組,一次處理多個action
export arrayThunk = ({ dispatch, getState }) => next => action => {
if(Array.isArray(action)) {
return action.forEach(v => dispatch(v))
}
return next(action)
}
這類action會被處理
export function addTimes() {
return [{ type: ADD_GUN },{ type: ADD_GUN },{ type: ADD_GUN }]
}
bindActionCreators的實現
在react-redux connect mapDispatchToProps中使用到了該方法,可以去看那篇blog,有詳解~
api: bindActionCreators(actionCreators, dispatch)
把 action creators 轉成擁有同名 keys 的物件,但使用 dispatch 把每個 action creator 包圍起來,這樣可以直接呼叫它們
實現:
function bindActionCreator(creator, dispatch) {
return (...args) => dispatch(creator(...args))
}
export function bindActionCreators(creators, dispatch) {
let bound = {}
Object.keys(creators).forEach( v => {
let creator = creators[v]
bound[v] = bindActionCreator(creator, dispatch)
})
return bound
}
// 簡寫
export function bindActionCreators(creators, dispatch) {
return Object.keys(creators).reduce((ret, item) => {
ret[item] = bindActionCreator(creators[item], dispatch)
return ret
}, {})
}
compose的實現
api: compose(...functions)
從右到左來組合多個函式。
當需要把多個 store 增強器 依次執行的時候,需要用到它
import { createStore, applyMiddleware, compose } from 'redux'
import thunk from 'redux-thunk'
import DevTools from './containers/DevTools'
import reducer from '../reducers'
const store = createStore(
reducer,
compose(
applyMiddleware(thunk),
DevTools.instrument()
)
)
實現:
compose(fn1, fn2, fn3)
fn1(fn2(fn3))
export function compose(...funcs) {
if(funcs.length == 0) {
return arg => arg
}
if(funcs.length == 1) {
return funcs[0]
}
return funcs.reduce((ret,item) => (...args) => ret(item(...args)))
}
原文地址: