一幅图明白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我。

相关文章
相关标签/搜索