解读redux工作原理
来源:互联网 发布:95后嫩模网络直播 编辑:程序博客网 时间:2024/06/06 14:29
原文地址:https://segmentfault.com/a/1190000004236064?utm_source=Weibo
1. 前言
随着WEB应用变得越来越复杂,再加上node前后端分离越来越流行,那么对数据流动的控制就显得越发重要。redux是在flux的基础上产生的,基本思想是保证数据的单向流动,同时便于控制、使用、测试。
redux不依赖于任意框架(库),只要subscribe相应框架(库)的内部方法,就可以使用该应用框架保证数据流动的一致性。
那么如何使用redux呢?下面一步步进行解析,并带有源码说明,不仅做到知其然,还要做到知其所以然。
2. 主干逻辑介绍(createStore)
2.1 简单demo入门
先来一个直观的认识:
// 首先定义一个改变数据的plain函数,成为reducerfunction count (state, action) { var defaultState = { year: 2015, }; state = state || defaultState; switch (action.type) { case 'add': return { year: state.year + 1 }; case 'sub': return { year: state.year - 1 } default : return state; }}// store的创建var createStore = require('redux').createStore;var store = createStore(count);// store里面的数据发生改变时,触发的回调函数store.subscribe(function () { console.log('the year is: ', store.getState().year);});// action: 触发state改变的唯一方法(按照redux的设计思路)var action1 = { type: 'add' };var action2 = { type: 'add' };var action3 = { type: 'sub' };// 改变store里面的方法store.dispatch(action1); // 'the year is: 2016store.dispatch(action2); // 'the year is: 2017store.dispatch(action3); // 'the year is: 2016
2.2 挖掘createStore实现
为了说明主要问题,仅列出其中的关键代码,全部代码,可以点击这里阅读。
a 首先看createStore到底都返回的内容:
export default function createStore(reducer, initialState) { ... return { dispatch, subscribe, getState, replaceReducer }}
每个属性的含义是:
dispatch: 用于action的分发,改变store里面的state
subscribe: 注册listener,store里面state发生改变后,执行该listener
getState: 读取store里面的state
replaceReducer: 替换reducer,改变state修改的逻辑
b 关键代码解析
export default function createStore(reducer, initialState) { // 这些都是闭包变量 var currentReducer = reducer var currentState = initialState var listeners = [] var isDispatching = false; // 返回当前的state function getState() { return currentState } // 注册listener,同时返回一个取消事件注册的方法 function subscribe(listener) { listeners.push(listener) var isSubscribed = true return function unsubscribe() { if (!isSubscribed) { return } isSubscribed = false var index = listeners.indexOf(listener) listeners.splice(index, 1) } } // 通过action该改变state,然后执行subscribe注册的方法 function dispatch(action) { try { isDispatching = true currentState = currentReducer(currentState, action) } finally { isDispatching = false } listeners.slice().forEach(listener => listener()) return action } // 替换reducer,修改state变化的逻辑 function replaceReducer(nextReducer) { currentReducer = nextReducer dispatch({ type: ActionTypes.INIT }) } // 初始化时,执行内部一个dispatch,得到初始state dispatch({ type: ActionTypes.INIT })}
如果还按照2.1的方式进行开发,那跟flux没有什么大的区别,需要手动解决很多问题,那redux如何将整个流程模板化(Boilerplate)呢?
3. 保证store的唯一性
随着应用越来越大,一方面,不能把所有的数据都放到一个reducer里面,另一方面,为每个reducer创建一个store,后续store的维护就显得比较麻烦。如何将二者统一起来呢?
3.1 demo入手
通过combineReducers将多个reducer合并成一个rootReducer:
// 创建两个reducer: count yearfunction count (state, action) { state = state || {count: 1} switch (action.type) { default: return state; }}function year (state, action) { state = state || {year: 2015} switch (action.type) { default: return state; }}// 将多个reducer合并成一个var combineReducers = require('./').combineReducers;var rootReducer = combineReducers({ count: count, year: year,});// 创建store,跟2.1没有任何区别var createStore = require('./').createStore;var store = createStore(rootReducer);var util = require('util');console.log(util.inspect(store));//输出的结果,跟2.1的store在结构上不存在区别// { dispatch: [Function: dispatch],// subscribe: [Function: subscribe],// getState: [Function: getState],// replaceReducer: [Function: replaceReducer]// }
3.2 源码解析combineReducers
// 高阶函数,最后返回一个reducerexport default function combineReducers(reducers) { // 提出不合法的reducers, finalReducers就是一个闭包变量 var finalReducers = pick(reducers, (val) => typeof val === 'function') // 将各个reducer的初始state均设置为undefined var defaultState = mapValues(finalReducers, () => undefined) // 一个总reducer,内部包含子reducer return function combination(state = defaultState, action) { var finalState = mapValues(finalReducers, (reducer, key) => { var previousStateForKey = state[key] var nextStateForKey = reducer(previousStateForKey, action) hasChanged = hasChanged || nextStateForKey !== previousStateForKey return nextStateForKey } } return hasChanged ? finalState : state}
4. 自动实现dispatch
4.1 demo介绍
在2.1中,要执行state的改变,需要手动dispatch:
var action = { type: '***', payload: '***'};dispatch(action);
手动dispatch就显得啰嗦了,那么如何自动完成呢?
var bindActionCreators = require('redux').bindActionCreators;// 可以在具体的应用框架隐式进行该过程(例如react-redux的connect组件中)bindActionCreators(action)
4.2 源码解析
// 隐式实现dispatchfunction bindActionCreator(actionCreator, dispatch) { return (...args) => dispatch(actionCreator(...args))}export default function bindActionCreators(actionCreators, dispatch) { if (typeof actionCreators === 'function') { return bindActionCreator(actionCreators, dispatch) } return mapValues(actionCreators, actionCreator => bindAQctionCreator(actionCreator, dispatch) )}
5. 支持插件 - 对dispatch的改造
5.1 插件使用demo
一个action可以是同步的,也可能是异步的,这是两种不同的情况, dispatch执行的时机是不一样的:
// 同步的action creator, store可以默认实现dispatchfunction add() { return { tyle: 'add' }}dispatch(add());// 异步的action creator,因为异步完成的时间不确定,只能手工dispatchfunction fetchDataAsync() { return function (dispatch) { requst(url).end(function (err, res) { if (err) return dispatch({ type: 'SET_ERR', payload: err}); if (res.status === 'success') { dispatch({ type: 'FETCH_SUCCESS', payload: res.data }); } }) }}
下面的问题就变成了,如何根据实际情况实现不同的dispatch方法,也即是根据需要实现不同的moddleware:
// 普通的dispatch创建方法var store = createStore(reducer, initialState);console.log(store.dispatch);// 定制化的dispatchvar applyMiddleware = require('redux').applyMiddleware;// 实现action异步的middlewarevar thunk = requre('redux-thunk');var store = applyMiddleware([thunk])(createStore);// 经过处理的dispatch方法console.log(store.dispatch);
5.2 源码解析
// next: 其实就是createStoreexport default function applyMiddleware(...middlewares) { return (next) => (reducer, initialState) => { var store = next(reducer, initialState) 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 // 实现新的dispatch方法 } }}// 再看看redux-thunk的实现, next就是store里面的上一个dispatchfunction thunkMiddleware({ dispatch, getState }) { return function(next) { return function(action) { typeof action === 'function' ? action(dispatch, getState) : next(action); } } return next => action => typeof action === 'function' ? action(dispatch, getState) : next(action);}
6. 与react框架的结合
6.1 基本使用
目前已经有现成的工具react-redux
来实现二者的结合:
var rootReducers = combineReducers(reducers);var store = createStore(rootReducers);var Provider = require('react-redux').Provider;// App 为上层的Componentclass App extend React.Component{ render() { return ( <Provier store={store}> <Container /> </Provider> ); }}// Container作用: 1. 获取store中的数据; 2.将dispatch与actionCreator结合起来var connect = require('react-redux').connect;var actionCreators = require('...');// MyComponent是与redux无关的组件var MyComponent = require('...');function select(state) { return { count: state.count }}export default connect(select, actionCreators)(MyComponent)
6.2 Provider -- 提供store
React通过Context属性,可以将属性(props)直接给子孙component,无须通过props层层传递, Provider仅仅起到获得store,然后将其传递给子孙元素而已:
export default class Provider extends Component { getChildContext() { // getChildContext: 将store传递给子孙component return { store: this.store } } constructor(props, context) { super(props, context) this.store = props.store } componentWillReceiveProps(nextProps) { const { store } = this const { store: nextStore } = nextProps if (store !== nextStore) { warnAboutReceivingStore() } } render() { let { children } = this.props return Children.only(children) }}
6.3 connect -- 获得store及dispatch(actionCreator)
connect是一个高阶函数,首先传入mapStateToProps、mapDispatchToProps,然后返回一个生产Component
的函数(wrapWithConnect),然后再将真正的Component作为参数传入wrapWithConnect(MyComponent),这样就生产出一个经过包裹的Connect组件,该组件具有如下特点:
通过this.context获取祖先Component的store
props包括stateProps、dispatchProps、parentProps,合并在一起得到
nextState
,作为props传给真正的ComponentcomponentDidMount时,添加事件this.store.subscribe(this.handleChange),实现页面交互
shouldComponentUpdate时判断是否有避免进行渲染,提升页面性能,并得到nextState
componentWillUnmount时移除注册的事件this.handleChange
在非生产环境下,带有热重载功能
// 主要的代码逻辑
export default function connect(mapStateToProps, mapDispatchToProps, mergeProps, options = {}) {return function wrapWithConnect(WrappedComponent) { class Connect extends Component { constructor(props, context) { // 从祖先Component处获得store this.store = props.store || context.store this.stateProps = computeStateProps(this.store, props) this.dispatchProps = computeDispatchProps(this.store, props) this.state = { storeState: null } // 对stateProps、dispatchProps、parentProps进行合并 this.updateState() } shouldComponentUpdate(nextProps, nextState) { // 进行判断,当数据发生改变时,Component重新渲染 if (propsChanged || mapStateProducedChange || dispatchPropsChanged) { this.updateState(nextProps) return true } } componentDidMount() { // 改变Component的state this.store.subscribe(() = { this.setState({ storeState: this.store.getState() }) }) } render() { // 生成包裹组件Connect return ( <WrappedComponent {...this.nextState} /> ) } } Connect.contextTypes = { store: storeShape } return Connect; }
}
7. redux与react-redux关系图
- 解读redux工作原理
- 解读redux工作原理
- 解读 Redux 中间件的原理
- SpringMVC工作原理解读
- 解读Android虚拟机工作原理
- 【读书笔记】HashMap工作原理解读
- 解读Android虚拟机工作原理
- redux文档解读
- redux-applyMiddleware源码解读
- redux-applyMiddleware源码解读
- redux-applyMiddleware源码解读
- redux-applyMiddleware源码解读
- redux-applyMiddleware源码解读
- redux-applyMiddleware源码解读
- 显卡结构与工作原理解读
- 全面解读Java NIO工作原理
- NRF24L01——工作原理解读
- 全面解读Java NIO工作原理(1)
- mxl 数据
- Leetcode 399. Evaluate Division 除法推理 解题报告
- 20160912之一
- 使用eclipse打包app以及AndroidStudio和Eclipse中app签名修改等问题(SH1和MD5)
- NEFU OJ14 蟠桃记
- 解读redux工作原理
- poj 1961kmp next的再理解
- windows XP 安装Sql Server 2000企业管理器无法打开(MMC)的解决方法(亲试,可用)
- CInternetFile怎么获取文件大小?!
- Java 日期时间 Date类型,long类型,String类型表现形式的转换
- 麻烦的素数
- 联合体union 枚举enum
- 题目10:二进制中的1的个数
- 【用例】安装&卸载测试用例