一幅圖明白React-Redux的原理

前言

最近在學 React-Native,卡在 React-Redux 上了,費了些時間和功夫,對其原理和數據流向瞭解了一點兒,畫了幅圖,解釋下。但願看這篇文章的人最好對 Redux 有些瞭解,假如不瞭解,能夠去看下阮一峯的文章。有些解釋是我的理解,不是很嚴謹,假若有錯誤的地方,煩請指正。html

數據流向圖

如下是來自阮一峯博客上的代碼,通過一些修改以適配 React-Nativereact

root.js

// 建立一個store全局管理state和操做
const store = createStore(reducer);

// Provider在根組件<App>外面包了一層,App的全部子組件就默認均可以拿到store,經過組件的props傳遞
export default class Root extends Component {
    render() {
        return (
            <Provider store={store}>
                <App/>
            </Provider>
        )
    }
}
複製代碼
app.js

// view提供UI組件
class Counter extends Component {
  render() {
    // View的狀態來自於props,props傳遞的是store中的state
    const { value, onIncreaseClick } = this.props
    return (
      <div>
        <span>{value}</span>
        <button onClick={onIncreaseClick}>Increase</button>
      </div>
    )
  }
}

// 將UI組件和容器組件鏈接起來
const App = connect(
 state =>({
    value: state.count   //輸入,將store中的state經過props輸入
 }),
 dispatch =>({    // 輸出,將action做爲props綁定到View上,用戶操做類型在此分發出去
    onIncreaseClick: () => dispatch(increaseAction.increase())
  })
)(Counter)

export default App
複製代碼
increaseAction.js

// 定義action的類型
export const INCREMENT = 'INCREMENT';

// action 建立函數只是簡單的返回一個 action
export function increase(params) {
    return {
        type: INCREMENT,
        data:data
        };
}
複製代碼
counterReducer.js

// 提供一個初始的狀態
initState={
 count: 0 
}

// 經過判斷Action的類型,返回新的數據改變後的state對象,即便沒有任何狀態的改變,也要返回一個對象
export default function counter(state = initState, action) {
  const count = state.count
  switch (action.type) {
    case INCREMENT:
      return { count: count + 1 }
    default:
      return state
  }
}
複製代碼

數據的流向能夠看下圖,先以一個View爲例子,待會兒講多個,這幅圖看懂了,React-Redux也就明白的差很少了。git

咱們尋本溯源,一點點來看這幅圖。經過問題來解疑答惑:

一、在這個例子中,View組件只提供UI,沒有本身的state和操做,那麼什麼致使了界面的變化?github

View自己的props,咱們知道組件的初始狀態由props決定,雖然沒有了本身的state,假如他的props發生改變,界面也會發生變化。redux

二、Viewprops內容由什麼提供,多個View中的props如何區分?bash

由應用中全局惟一store中的state提供,全部的狀態,保存在一個對象裏面,經過key區分。<Provider store={store}> <App/> </Provider> 這行代碼實現了爲應用綁定惟一的storeapp

三、store是怎麼來的?ide

經過 store = createStore(reducer) 建立,reducer返回的正好是變化後的state對象。函數

前三個問題解釋了上圖左半部分的數據流向,reducer——(store/state)——provider——(state/props)——viewui

四、action是如何與reducer綁定的,或者說,reducer(state,action)這個函數中的action是怎麼來的?

store.dispatch(action)內部處理的,先看下createStore(reducer)這個函數,簡略代碼以下:

function createStore = ( reducer ) => {
  let currentState; // 內部的狀態
  let listeners = []; //全部的監聽者
  
  const getState = () => currentState;  // 獲取store中的state
  
  // dispatch的操做就是內部執行reducer()函數,action和reducer在這兒產生交集,而且通知全部的監聽者
  const dispatch = ( action ) => {
    currentState = reducer(state, action); // 更新state
    listeners.forEach(listener => listener());
  }
  
  // 訂閱事件
  const subscribe = ( listener ) => {
    listeners.push(listener);
    return ()=>{
      listeners = listeners.filter(l => l !== listener)
    }
  }

  return {
    getState,
    dispatch,
    subscribe
  }
}
複製代碼

爲何沒有顯示的寫出調用的是store中dispatch,這個全都是React-ReduxconnectProvider 的功勞了,假如不用他們,上面app.js 中的代碼應該以下:

class Counter extends Component{

  componentWillMount(){
      // 訂閱狀態變化
    store.subscribe((state)=>this.setState(state))
  }

  render() {
    return (
      <div>
        <span>{value}</span>
        // 點擊後dispatch事件類型
        <button onClick={()=>store.dispatch(increaseAction.increase())}>Increase</button>
      </div>
    )
  }
}
複製代碼

五、reducer()函數執行以後,是如何更改state的?

見問題4中createStore的代碼,簡化的能夠寫成:

function createStore = ( reducer ) => {

  let currentState; // 內部的狀態

  const getState = () => currentState;  // 獲取store中的state
  
  // 更新state
  const dispatch = ( action ) => {
    currentState = reducer(state, action); 
  }
  
  return {
    getState,
    dispatch,
  }
}
複製代碼

以上兩個問題解決了解釋了上圖右半部分的數據流向,view——(action)——dispatch——(action)——reducer,兩個數據循環合在一塊兒,就是一個圓,完成了生命的大和諧。以下圖:

多個Reducer

看完上面的分析,咱們再拓展下,上圖中只有一個reducer,正常的app中有不少View,天然有不少相對應的reducer,那麼一個界面的action是如何與其對應的reducer綁定的呢?

假如上面的項目中添加了一個loginReducer.js文件,代碼以下:

loginReducer.js

// 提供一個初始的狀態
initState={
 login: false
}

// 經過判斷Action的類型,返回新的數據改變後的state對象,即便沒有任何狀態的改變,也要返回一個對象
export default function login(state = initState, action) {
  const login = state.login
  switch (action.type) {
    case INCREMENT:
      return { login: !login }
    default:
      return state
  }
}
複製代碼

這個reducer就執行一個操做,收到 INCREMENT 這個操做類型,登陸狀態反轉一次。假如我再點擊那個按鈕,count這個數字增長的同時,登陸狀態會不會發生變化呢?答案是!那爲何呢?

的前提是:你用到了下面的代碼:

const rootReducer = combineReducers({
    counter: counter,
    login:login
});

store=createStore(rootReducer);
複製代碼

combineReducers 顧名思義就是合併reducer,所謂的合併,就是把reducer函數對象整合爲單一reducer函數,它會遍歷全部的子reducer成員,並把結果整合進單一狀態樹,因此最後只有一個reducer,重複一遍,最後只有一個reducer函數!combineReducers粗略的代碼以下:

export default function combineReducers(reducers) {

    var reducerKeys = Object.keys(reducers)
    var finalReducers = {}

    //提取reducers中value值爲function的部分
    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 = {}
        /**
         * 遍歷訪問finalReducers
         */
        for (var i = 0; i < finalReducerKeys.length; i++) {
            var key = finalReducerKeys[i]
            var reducer = finalReducers[key]
            /**
             *將state按照reducer的名字分離
             * 每一個key都對應着state
             */
            var previousStateForKey = state[key];
            var nextStateForKey = reducer(previousStateForKey, action)
  
            nextState[key] = nextStateForKey
            hasChanged = hasChanged || nextStateForKey !== previousStateForKey
        }
        return hasChanged ? nextState : state
    }
}
複製代碼

上面的代碼能夠看出,當dispatch一個action,全部的reducer函數都會執行一遍,經過action.type修改對應的state,從而全部訂閱相應stateView都會發生變化。因此上面問題的答案就是:。 最後再放一個圖,也就是多個reduceraction時的數據流向圖。

圖上能夠看出, storestatereduceraction其實最後都只有一個,咱們只是爲了代碼邏輯將其分爲多個,井井有條,便於開發和閱讀。

總結

一句話總結,View負責UI界面,reduxView中的state和操做集中起來在store中管理,而後經過props將修改後的state內容傳遞給View,界面發生變化。用戶操做界面,View經過dispatch執行相關操做,而後將ActionTypeData交由reducer函數,根據ActionTypeData修改state

致謝

最後

喜歡的請點贊和關注。

歡迎進入個人GitHub主頁,喜歡的能夠follow我。

相關文章
相關標籤/搜索