原文地址:https://gmiam.com/post/react-...html
沒想到這篇文章這麼晚纔出,最近發生了太多的事情,已致於心態全無,最終也離開了如今的公司,沒想到是這麼的狼狽react
一我的的光芒能夠放到很大也能夠小到微乎其微,若是不能好好的規劃最終也只能默默的承受git
世上沒有相同的感同身受,感覺真實才能真正的認清一切github
好了,下面步入正題redux
前面看到 Flux 架構相對來講仍是比較繁瑣,同時社區也涌現了不少第三方的框架模式,而 Redux 則脫穎而出架構
React 以組件的形式維護了一顆 UI 樹,可是對狀態數據沒有作更多的處理,Redux 則把狀態數據也抽象成了一棵樹來維護mvc
它自己與 React 沒有直接關係,能夠與其餘框架配合使用,也能夠很好的與 React 配合使用app
Redux 的代碼量很是短小,核心只提供了 5 個 API框架
createStoredom
combineReducers
bindActionCreators
applyMiddleware
compose
下面先來直觀的感覺下 Redux
import { createStore } from 'redux'; function counter(state = 0, action) { switch (action.type) { case 'INCREMENT': return state + 1; case 'DECREMENT': return state - 1; default: return state; } } let store = createStore(counter); store.subscribe(() => console.log(store.getState()) ); store.dispatch({ type: 'INCREMENT' }); // 1 store.dispatch({ type: 'INCREMENT' }); // 2 store.dispatch({ type: 'DECREMENT' }); // 1
表象能夠看出入口是 createStore,接收一個函數(這裏叫作 reducer),這個函數接收 state 與 action 倆個參數,而後 dispatch 一個對象(這裏叫 action ,要包含一個 type 屬性標明行爲),reducer 函數就會被觸發執行來操做狀態,同時也會觸發 subscribe 訂閱的回調,回調能夠經過 store.getState() 獲取當前狀態數據
到這裏都很簡單,那麼若是咱們須要處理的數據和狀態愈來愈多 reducer 函數就會愈來愈大致使難以維護,因此 Redux 提供了 combineReducers 來處理這種狀況,它把這個大的 reducer 分解成一個個小的 reducer ,每一個小 reducer 維護本身的狀態數據,這樣就分解出了一個狀態樹
作下變種
reducers/todos.js
export default function todos(state = [], action) { switch (action.type) { case 'ADD_TODO': return state.concat([action.text]) default: return state } }
reducers/counter.js
export default function counter(state = 0, action) { switch (action.type) { case 'INCREMENT': return state + 1 case 'DECREMENT': return state - 1 default: return state } }
reducers/index.js
import { combineReducers } from 'redux' import todos from './todos' import counter from './counter' export default combineReducers({ todos, counter })
App.js
import { createStore } from 'redux' import reducer from './reducers/index' let store = createStore(reducer) console.log(store.getState()) // { // counter: 0, // todos: [] // } store.dispatch({ type: 'ADD_TODO', text: 'Use Redux' }) console.log(store.getState()) // { // counter: 0, // todos: [ 'Use Redux' ] // }
能夠看到咱們利用 combineReducers 把 reducer 作了拆分,combineReducers 部分精簡源碼
export default function combineReducers(reducers) { var reducerKeys = Object.keys(reducers) var finalReducers = {} for (var i = 0; i < reducerKeys.length; i++) { var key = reducerKeys[i] if (typeof reducers[key] === 'function') { finalReducers[key] = reducers[key] } } var finalReducerKeys = Object.keys(finalReducers) return function combination(state = {}, action) { var hasChanged = false var nextState = {} for (var i = 0; i < finalReducerKeys.length; i++) { var key = finalReducerKeys[i] var reducer = finalReducers[key] var previousStateForKey = state[key] var nextStateForKey = reducer(previousStateForKey, action) nextState[key] = nextStateForKey hasChanged = hasChanged || nextStateForKey !== previousStateForKey } return hasChanged ? nextState : state } }
能夠看到就是把對象中的 reducer 所有執行一遍,把上次的狀態傳入進去,最新的狀態返回回來,固然你也能夠提供本身的
combineReducers 方法
前面咱們注意到 store.dispatch 都是一個純對象,也就是說咱們的觸發都是同步的,如何支持異步?
下面咱們來引入 Redux 中間件來加強下
import { createStore, applyMiddleware } from 'redux'; import thunk from 'redux-thunk'; import rootReducer from './reducers/index'; function increment() { return { type: 'INCREMENT_COUNTER' } } function incrementAsync() { return dispatch => { setTimeout(() => { dispatch(increment()); }, 1000) } } const store = createStore( rootReducer, applyMiddleware(thunk) ) store.dispatch(increment()) // 同步 store.dispatch(incrementAsync()) // 異步
同步方式的觸發跟之前是同樣的,這裏的異步支持就是靠 Redux 的 applyMiddleware 中間件模式與 thunk 中間件作加強支持的
來看下 applyMiddleware 與部分 createStore 源碼
export default function applyMiddleware(...middlewares) { return (createStore) => (reducer, preloadedState, enhancer) => { var store = createStore(reducer, preloadedState, enhancer) var dispatch = store.dispatch var chain = [] var middlewareAPI = { getState: store.getState, dispatch: (action) => dispatch(action) } chain = middlewares.map(middleware => middleware(middlewareAPI)) dispatch = compose(...chain)(store.dispatch) return { ...store, dispatch } } }
export default function createStore(reducer, preloadedState, enhancer) { if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') { enhancer = preloadedState preloadedState = undefined } if (typeof enhancer !== 'undefined') { return enhancer(createStore)(reducer, preloadedState) } .... return { dispatch, subscribe, getState, replaceReducer, [$$observable]: observable } }
createStore 裏所謂的加強就是 applyMiddleware 一些中間件,
const store = createStore( rootReducer, applyMiddleware(thunk) )
與下面寫法是等效的
const store = applyMiddleware(thunk)(createStore)(rootReducer)
看上面 applyMiddleware 的源碼能夠知道會先用 createStore 建立原始 store,而後把 getState 與 dispatch 傳給中間件,中間件處理完後返回擴展後的 store
看下 thunk 中間件源碼
function createThunkMiddleware(extraArgument) { return ({ dispatch, getState }) => next => action => { if (typeof action === 'function') { return action(dispatch, getState, extraArgument); } return next(action); }; } const thunk = createThunkMiddleware(); thunk.withExtraArgument = createThunkMiddleware; export default thunk;
很簡單傳入 dispatch, getState 後返回 next => action = > {...},而後傳入 store.dispatch 返回 action => {...} 即擴展後的 dispatch
這個新的 dispatch 也是接受 action,若是是對象用原始 store.dispatch 直接觸發,若是是函數則把 dispatch 傳進函數體,把控制權交給函數內部
注意後面執行用到的 dispatch 已經是擴展後的能處理函數的 dispatch
回過頭來在說下 compose API,applyMiddleware 能夠接受一系列中間件,內部調用 compose 來作處理
compose(...chain) 等同於 (...args) => f(g(h(...args)))
也就是說傳入一組函數,它會倒序執行,把前一個的執行結果傳給下一個,達到漸進加強效果
說到這裏 Redux 和 它的 API 終於介紹差很少了,至於 bindActionCreators 後面介紹
說了這麼多能夠看到 Redux 本身就能夠跑,那如何與 React 結合起來?那就須要 react-redux 這個中間橋樑了
react-redux 提供了倆個 API
Provider store
connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])
Provider 就是一個 React 組件,它接收一個 store 屬性,把 store 掛在 React 的 Context 上,這樣它的子組件不須要顯示的傳遞 store 就能夠獲取到
看個例子
import { Provider } from 'react-redux' const store = createStore(reducer) render( <Provider store={store}> <App /> </Provider>, document.getElementById('root') )
那麼問題來了,能夠獲取到 store 後呢,如何作交互以及 React 與 Redux 的溝通,這時候 connect API 就派上用場了
仍是繼續看個例子
import { bindActionCreators } from 'redux' const App = ({todos, actions}) => ( <div> <Header addTodo={actions.addTodo} /> <MainSection todos={todos} actions={actions} /> </div> ) const mapStateToProps = state => ({ todos: state.todos }) const mapDispatchToProps = dispatch => ({ actions: bindActionCreators(TodoActions, dispatch) }) export default connect( mapStateToProps, mapDispatchToProps )(App)
connect 的源碼執行大概是這樣
export default function connect(mapStateToProps, mapDispatchToProps, mergeProps, options = {}) { return function wrapWithConnect(WrappedComponent) { class Connect extends Component { constructor(props, context) { this.store = props.store || context.store } render() { const mappedState = mapStateToProps(store.getState(), this.props) const mappedDispatch = mapDispatchToProps(store.dispatch, this.props) const mergedProps = { mappedState, mappedDispatch } this.renderedElement = createElement(WrappedComponent,mergedProps) return this.renderedElement } } } }
這裏作了適當的簡化,從這能夠看出 connect 返回了一個 Connect 組件獲取到 store,而後把 store.getState() 與 store.dispatch
傳遞給咱們的 mapStateToProps 與 mapDispatchToProps 函數,返回相應的數據與方法經過 props 傳遞給 React 組件,這樣 React 組件就能夠獲取到相應數據展現,同時也能夠經過 dispatch 觸發 Redux store 的數據變更,Connect 組件在根據數據對比看是否須要從新渲染~
connect 實際的代碼比這複雜的多,內部作了細緻的淺數據對比以提高性能
對於 react-redux 這裏還有一個潛規則,那就是展現組件與容器組件相分離,就是說只有容器組件處理數據與狀態與 Redux 溝通,
展現組件只作正常的 UI 渲染,能夠從這裏瞭解更多 http://redux.js.org/docs/basi...
再看下上面的
const mapDispatchToProps = dispatch => ({ actions: bindActionCreators(TodoActions, dispatch) })
會把傳入的函數或對象的每個方法作下面的變形
function bindActionCreator(actionCreator, dispatch) { return (...args) => dispatch(actionCreator(...args)) }
這樣 React 組件調用對應的 action 時就能夠 dispatch 這個 actionCreator 產生的數據
最終無論有沒有明白均可以看下 https://github.com/reactjs/re...
這個例子來加深下理解,以及目錄結構的分工,固然有興趣多瞭解一些例子就更好了
呼,這篇到這裏終於算是寫完了,最後你們都加油吧!