React系列之 Redux 架構模式

原文地址: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...

這個例子來加深下理解,以及目錄結構的分工,固然有興趣多瞭解一些例子就更好了

呼,這篇到這裏終於算是寫完了,最後你們都加油吧!

相關文章
相關標籤/搜索