解读redux工作原理

欢迎访问个人博客的其他文章

1. 前言

随着WEB应用变得越来越复杂,再加上node前后端分离越来越流行,那么对数据流动的控制就显得越发重要。redux是在flux的基础上产生的,基本思想是保证数据的单向流动,同时便于控制、使用、测试。

redux不依赖于任意框架(库),只要subscribe相应框架(库)的内部方法,就可以使用该应用框架保证数据流动的一致性。

那么如何使用redux呢?下面一步步进行解析,并带有源码说明,不仅做到知其然,还要做知其所以然

2. 主干逻辑介绍(createStore)

2.1 简单demo入门

先来一个直观的认识:

// 首先定义一个改变数据的plain函数,成为reducer
function 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: 2016
store.dispatch(action2); // 'the year is: 2017
store.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 year
function 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

// 高阶函数,最后返回一个reducer
export 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 源码解析

// 隐式实现dispatch
function 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可以认实现dispatch
function add() {
    return { tyle: 'add' }
}
dispatch(add());

// 异步的action creator,因为异步完成的时间不确定,只能手工dispatch
function 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);

// 定制化的dispatch
var applyMiddleware = require('redux').applyMiddleware;
// 实现action异步的middleware
var thunk = requre('redux-thunk');
var store = applyMiddleware([thunk])(createStore);
// 经过处理的dispatch方法
console.log(store.dispatch);

5.2 源码解析

// next: 其实就是createStore
export 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里面的上一个dispatch
function 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 为上层的Component
class 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传给真正的Component

  • componentDidMount时,添加事件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关系图

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


react 中的高阶组件主要是对于 hooks 之前的类组件来说的,如果组件之中有复用的代码,需要重新创建一个父类,父类中存储公共代码,返回子类,同时把公用属性...
我们上一节了解了组件的更新机制,但是只是停留在表层上,例如我们的 setState 函数式同步执行的,我们的事件处理直接绑定在了 dom 元素上,这些都跟 re...
我们上一节了解了 react 的虚拟 dom 的格式,如何把虚拟 dom 转为真实 dom 进行挂载。其实函数是组件和类组件也是在这个基础上包裹了一层,一个是调...
react 本身提供了克隆组件的方法,但是平时开发中可能很少使用,可能是不了解。我公司的项目就没有使用,但是在很多三方库中都有使用。本小节我们来学习下如果使用该...
mobx 是一个简单可扩展的状态管理库,中文官网链接。小编在接触 react 就一直使用 mobx 库,上手简单不复杂。
我们在平常的开发中不可避免的会有很多列表渲染逻辑,在 pc 端可以使用分页进行渲染数限制,在移动端可以使用下拉加载更多。但是对于大量的列表渲染,特别像有实时数据...
本小节开始前,我们先答复下一个同学的问题。上一小节发布后,有小伙伴后台来信问到:‘小编你只讲了类组件中怎么使用 ref,那在函数式组件中怎么使用呢?’。确实我们...
上一小节我们了解了固定高度的滚动列表实现,因为是固定高度所以容器总高度和每个元素的 size、offset 很容易得到,这种场景也适合我们常见的大部分场景,例如...
上一小节我们处理了 setState 的批量更新机制,但是我们有两个遗漏点,一个是源码中的 setState 可以传入函数,同时 setState 可以传入第二...
我们知道 react 进行页面渲染或者刷新的时候,会从根节点到子节点全部执行一遍,即使子组件中没有状态的改变,也会执行。这就造成了性能不必要的浪费。之前我们了解...
在平时工作中的某些场景下,你可能想在整个组件树中传递数据,但却不想手动地通过 props 属性在每一层传递属性,contextAPI 应用而生。
楼主最近入职新单位了,恰好新单位使用的技术栈是 react,因为之前一直进行的是 vue2/vue3 和小程序开发,对于这些技术栈实现机制也有一些了解,最少面试...
我们上一节了了解了函数式组件和类组件的处理方式,本质就是处理基于 babel 处理后的 type 类型,最后还是要处理虚拟 dom。本小节我们学习下组件的更新机...
前面几节我们学习了解了 react 的渲染机制和生命周期,本节我们正式进入基本面试必考的核心地带 -- diff 算法,了解如何优化和复用 dom 操作的,还有...
我们在之前已经学习过 react 生命周期,但是在 16 版本中 will 类的生命周期进行了废除,虽然依然可以用,但是需要加上 UNSAFE 开头,表示是不安...
上一小节我们学习了 react 中类组件的优化方式,对于 hooks 为主流的函数式编程,react 也提供了优化方式 memo 方法,本小节我们来了解下它的用...
开源不易,感谢你的支持,❤ star me if you like concent ^_^
hel-micro,模块联邦sdk化,免构建、热更新、工具链无关的微模块方案 ,欢迎关注与了解
本文主题围绕concent的setup和react的五把钩子来展开,既然提到了setup就离不开composition api这个关键词,准确的说setup是由...
ReactsetState的执行是异步还是同步官方文档是这么说的setState()doesnotalwaysimmediatelyupdatethecomponent.Itmaybatchordefertheupdateuntillater.Thismakesreadingthis.staterightaftercallingsetState()apotentialpitfall.Instead,usecom