一幅图明白React-Redux的原理
Posted shaoshuai0305
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了一幅图明白React-Redux的原理相关的知识,希望对你有一定的参考价值。
前言
最近在学 React
,卡在 React-Redux
上了,费了些时间和功夫,对其原理和数据流向了解了一点儿,画了幅图,解释下。希望看这篇文章的人最好对 Redux
有些了解,假如不了解,可以去看下阮一峰的文章。有些解释是个人理解,不是很严谨,假如有错误的地方,烦请指正。
数据流向图
以下是来自阮一峰博客上的代码,经过一些修改
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
也就明白的差不多了。
1、在这个例子中,View
组件只提供UI,没有自己的state
和操作,那么什么导致了界面的变化?
是View
本身的props
,我们知道组件的初始状态由props
决定,虽然没有了自己的state
,假如他的props
发生改变,界面也会发生变化。
2、View
的props
内容由什么提供,多个View
中的props
如何区分?
由应用中全局唯一store
中的state
提供,所有的状态,保存在一个对象里面,通过key区分。<Provider store={store}> <App/> </Provider>
这行代码实现了为应用绑定唯一的store
。
3、store
是怎么来的?
通过 store = createStore(reducer)
创建,reducer
返回的正好是变化后的state对象。
前三个问题解释了上图左半部分的数据流向,reducer——(store/state)——provider——(state/props)——view
4、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-Redux
中 connect
和 Provider
的功劳了,假如不用他们,上面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> ) } }
5、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
,从而所有订阅相应state
的View
都会发生变化。所以上面问题的答案就是:会。 最后再放一个图,也就是多个reducer
和action
时的数据流向图。
图上可以看出,store
,state
,reducer
,action
其实最后都只有一个,我们只是为了代码逻辑将其分为多个,层次分明,便于开发和阅读。
总结
View
负责UI界面,redux
将View
中的state
和操作集中起来在store
中管理,然后通过props
将修改后的state
内容传递给View
,界面发生变化。用户操作界面,View
通过dispatch
执行相关操作,然后将ActionType
和Data
交由reducer
函数,根据ActionType
和Data
修改state
。
以上是关于一幅图明白React-Redux的原理的主要内容,如果未能解决你的问题,请参考以下文章