GithubHelp home page GithubHelp logo

redux-analysis's People

Contributors

lubyxu avatar

Stargazers

 avatar

Watchers

 avatar  avatar

redux-analysis's Issues

6. 学习点

utils里的isPlainObject写法:

export default function isPlainObject(obj) {
  if (typeof obj !== 'object' || obj === null) return false

  let proto = obj
  while (Object.getPrototypeOf(proto) !== null) {
    proto = Object.getPrototypeOf(proto)
  }

  return Object.getPrototypeOf(obj) === proto
}

3. applyMiddleware

该函数构造了一个增强版的createStore。之所以叫做增强版,因为标准的createStore里,dispatch(action)时,直接将currentState=currentReucer(currentState , action)。即直接调用reducer函数,处理更新。但是利用applyMiddleware之后,他便可以经过一系列中间件middlewares处理。\

于是乎,怎样通过调用dispatch(action)进入中间件????答案是:redux同过改造标准的dispatch方法来实现。

applyMiddleware的代码并不多,但是理解起来,还是有点绕的。首先看他的调用方:createStore

return enhancer(createStore)(reducer, preloadedState)

带着这个前提,看看applyMiddleware的返回部分:

return createStore => (...args) => {}

由此可见:createStore传递到applyMiddleware是一个标准的store创建函数。而applyMiddleware里的 (...args)部分,对应的就是(reducer, preloadedState)这一部分了。
因此,我们可以说:enhancer(createStore)返回的是一个增强版的createStore

我们再往里看看:
const store = createStore(...args)构建了一个标准的store。

applyMiddleware最关键的部分:

let chain = []
const middlewareAPI = {
    getState: store.getState,
    dispatch: (...args) => dispatch(...args)
}
// POS. A
// middlewares的函数结构:
// function m1 = store => next => action => {}
// 执行完后:
// chain里的middlewares只包含后面两个箭头,即: 
// function c_m1 = next => action => {};
chain = middlewares.map(middleware => middleware(middlewareAPI))

// POS. B
// 将middlewares链组合起来生成新的一个dispatch
dispatch = compose(...chain)(store.dispatch)

代码中compose的作用将输入的middlewares链[m1, m2, m3],输出返回(...args) => m1(m2(m3(...args))。因此,这一块是从最后一个开始调用执行的。

那么这里为什么compose(...chain)(store.dispatch)之后就返回了一个新的dispatch呢?举个例子:

// 两个中间件。m1和m2
const m1 = store => next => action => {
    console.log('i am m1 before');
    next(action)
    console.log('i am m1 after');
}
const m2 = store => next => action => {
    console.log('i am m2 before');
    next(action)
    console.log('i am m2 after');
}

// 执行完POS. A之后变成
const c_m1 = next => action => {
    console.log('i am m1 before');
    next(action)
    console.log('i am m1 after');
}
const c_m2 = next => action => {
    console.log('i am m2 before');
    next(action)
    console.log('i am m2 after');
}
chain = [c_m1, c_m2];

// 执行完POS. B之后
dispatch = c_m1(c_m2(store.dispatch));
// 我们将等式右侧从里到外拆开。
// const dispatch1 = c_m2(store.dispach) = action => {
//    console.log('i am m2 before');
//    store.dispatch(action);
//    console.log('i am m2 after');
// }
// 上面dispatch1的作用和标准的dispatch基本一样,
// 无非多了一个个性化的console.log('i am m2');
// 因此我们可以说dispatch1是标准dispatch的一个增强版。
// ---------------------------------------------------
// 带着dispatch1,继续执行c_m1(dispatch1)。这个时候如下:
// const dispatch2 = c_m1(dispatch1) = action => {
//    console.log('i am m1 before');
//    dispatch1(action);
//    console.log('i am m1 after');
// }
// 从上述结果来看,dispatch2在dispatch1的基础上又增强了。
// 同时,我们也可以理解到参数next,其实表示的是next dispatch
// 的意思。
// ---------------------------------------------------
// 因此 c_m1(c_m2(store.dispatch))返回的是一个新的dispatch。

接下来,我们再看看用这个增强的dispatch发出一个action会怎么样:

disaptch({type: 'REQUEST_DATA'});
// 当发出一个action时,会先经过c_m1,
// 打出console.log('i am m1 before');之后,
// 再将同样的action传递给c_m2

这就解释了:dispatch是从中间件最右侧开始构建的,但是中间件调用的顺序还是从第一个来的。 (本人之前一直的疑惑)

关于问题:

1、关于这个函数还有一点要说的就是这个middlewareAPI:

// 为什么getState用的是标准的store.getState
// 为什么dispatch用的是新的,而不是store.dispatch
const middlewareAPI = {
    getState: store.getState,
    dispatch: (...args) => dispatch(...args)
}

其实applyMiddleware的目的就是改造dispatch,那么如果只写使用store.dispatch会有什么问题呢?

试想一下我们的多个中间件相当与下面这幅图(来自网上随便找的)。
中间件
嗯,类似于一个圈中圈娱乐城,最中心的就是我们标准的dispatch。试想当middlewareAPI中的dispatch直接调用了store.dispatch。那中间件m1里的store.dispatch就不会经过这些next了,而直接到达了中心,那么这一切的改造就没有了意义。

const m1 = store => next => action => {
    console.log('i am m1');
    store.dispatch({type: 'xxx'});
}

2、middlewareAPI中的dispatch为什么匿名包裹。
middlewareAPIdispatch的实现来看,他其实是一个闭包的匿名包裹。这样就可以实现,当下面dispatch被更新的时候,闭包里的disaptch页得到了相应的更新。如果用的是下面这种方式的话

const middlewareAPI = {
    getState: store.getState,
    dispatch
}

那么middlewareAPI.dispatch将一直都dispatch的初始化,即代码中的:

let dispatch = () => {
    throw new Error(
    `Dispatching while constructing your middleware is not allowed. ` +
    `Other middleware would not be applied to this dispatch.`
    )
}

1. 前言

redux最核心的API:createStore,创建store对象,这个store对象并不是我们在用react-redux中的connect第一个参数mapStoreToState中的store。确切的说mapStoreToState中的store只是createStore中的store.getState返回的对象。

先来看看redux(4.0.0-beta.2)中的src目录

├── applyMiddleware.js        // 中间件函数
├── bindActionCreators.js     // 
├── combineReducers.js        // 合并reducer
├── compose.js
├── createStore.js            // 创建store对象
├── index.js
└── utils
    ├── actionTypes.js
    ├── isPlainObject.js
    └── warning.js

目录呈现的即使redux相关的API。

5. bindActionCreators

这个函数的主要目的是方便。。。他将拥有actions的对象与dispatch绑定到了一起。这样调用的时候就不需要

import {requestData, successData} from 'actions.js';
store.dispatch(requestData())
store.dispatch(successData())

而直接是:

import getData from 'actions.js';
const bindedCreator = bindActionCreators(getData, store.dispatch)
bindedCreator.requestData()
bindedCreator.successData()

看起来很方便。但我很少用。。。

PS:作为一个点,虽然还没开始做同构:http://cn.redux.js.org/docs/api/bindActionCreators.html里的【小贴士】

4. combineReducers

为什么会有这个东西的存在呢?在业务中,我们经常会创建不同的reducer,比如权限相关的,路由相关的。而createStore只接受一个reducer。那怎么办呢?这就有了combineReducers,他将多个reducer合并撑了一个reducer,并返回和reducer同样参数的函数。

export default function combineReducers(reducers) {
    // 将多个reducer按照key值合并
    return function combination(state = {}, action) {
        // 把action分发给每个reducer。
    }
}

从返回上来看,其实combineReducers返回的是一个大的reducer,也可以说combineReducers(reducers)他是一个高阶的reducer。
他的主要工作是:
1、根据key值合并reducer。
2、返回一个新的reducer:combination。\

那么combination到底是做什么的呢?
1、把action分发给所有的reducer。生成新的state。
2、如果有因为action导致状态改变的话,就返回nextState,否则返回state

从上述,我们看到了一个不合理的地方,就是combination把action分发给了所有的reducer。即使我们知道某个action是不可能发到其他reducer的。这样无疑造成了一定的性能损耗问题。因此,针对这个现象,也提出了解决方案:specialActions.

react-redux

redux帮助我们创建了一个store,但是如何关联到react组件上?这里用到了另外一个库:react-redux
那么:我们就会有三个问题:

  1. 如何将store关联到组件上
  2. 如何将store的内容绑定到组件上
  3. 当store内容发生变化的时候,怎样做到重新渲染组件。

针对以上三个问题,我们需要了解react-redux中的Providerconnect。当前react-redux版本:4.4.5

如何将store关联到组件上

react-redux利用组件Provider将store关联到react组件上。实际上利用的是react中的context属性。将传给props的store属性赋值给了context,这样Provider下的所有子组件通过context.store方式都能访问到store。

  • 需要注意的是:Providerstore字段在4.4.5版本是写死的(5.0.7版本可自定义)。
constructor(props, context) {
  super(props, context)
  this.store = props.store
}
// Right
// <Provider store={store}>
// ......
// </Provider>

// Wrong
// <Provider customStore={store}>
// ......
// </Provider>
  • Provider.prototype.render
    Children.only这个函数很少用到,作用是判断子节点只有一个,且返回子节点。
render() {
  return Children.only(this.props.children)
}

如何将store的内容绑定到组件上

在调用react-reduxconnect时,我们知道,第一个参数就是将store绑定到数组的属性上。那怎么做到的呢?这里我们采用倒推的方式。先看connect返回render里的两句话:

if (withRef) {
  this.renderedElement = createElement(WrappedComponent, {
    ...this.mergedProps,
    ref: 'wrappedInstance'
  })
}
else {
  this.renderedElement = createElement(WrappedComponent,
    this.mergedProps
  )
}

react-redux利用React.createElementmergedProps绑定到组件上。回溯mergedProps来源。

  • 直接定义在组件上的parentProps
  • store上关联来的stateProps
  • connect第二个参数dispatchProps

store上关联来的stateProps

第一次store关联来的stateProps来源于configureFinalMapState计算,之后都来源于computeStateProps计算。
首先看看configureFinalMapState,代码不多,直接贴出来:

configureFinalMapState(store, props) {
  // mapState 来源于connect第一个参数mapStateToProps
  const mappedState = mapState(store.getState(), props)
  const isFactory = typeof mappedState === 'function'

 // finalMapStateToProps:最终的mapStateToProp函数。
  this.finalMapStateToProps = isFactory ? mappedState : mapState
  this.doStatePropsDependOnOwnProps = this.finalMapStateToProps.length !== 1
  if (isFactory) {
    return this.computeStateProps(store, props)
  }
  return mappedState
}
  • 从上述来看,connect允许第一个参数mapStateToProps函数返回函数的。即如下两种方法都是可行的。因为有了这个的条件,reselect才可以利用闭包的特性,减少一些不必要的计算。
// right
const mapStateToProps = (state, props) => {
  return {
    auth: state.get('auth')
  };
}
// right
const mapStateToProps = (state, props) => {
  return (state, props) => {
    return {
      auth: state.get('auth')
    }
  };
}

因此,我们可以得出finalMapStateToProps的意思表示最终的finalMapStateToProps函数。当得到这个最终的函数之后,我们就可以计算他的stateProps

  • configureFinalMapState有一个this.doStatePropsDependOnOwnProps的赋值。意思表示的是,stateProps是以来props(第二个参数)。方法是查看函数的形参个数:Function.length
this.finalMapStateToProps.length !== 1

connect第二个参数dispatchProps

dispatchProps的方式与stateProps结构可以说是几乎完全一样。这里可以自己查看。

mergedProps

最后mergedPros将stateProps,dispatchProps和this.props合并,赋值给this.mergedProps。默认采用简单的扩展:

const defaultMergeProps = (stateProps, dispatchProps, parentProps) => ({
  ...parentProps,
  ...stateProps,
  ...dispatchProps
})

总结

上述反推倒过来之后结合render,就变成了如下:
第一次render的时候会调用updateStatePropsIfNeeded,这个时候回根据finalMapDispatchToProps计算stateProps。同时也会调用updateDispatchPropsIfNeeded,根据finalMapDispatchToProps计算dispatchProps,最后将statePropsdispatchPropsthis.props合并,生成this.mergedProps。最后利用React.createElementmergedProps班谷底功能到组件上。

当store内容发生变化的时候,怎样做到重新渲染组件

redux 调用监听事件

还记得redux中是如何调用监听时间的吗?
每次dispatch(action)的时候,redux都会同过reducer计算出nextState,然后强制调用nextListeners里的所有listener。

redux 注册监听事件

那么上面的监听事件从何而来?
nextListeners里的listener,通过store.subscribe(listener),将要注册的事件push到nextListeners中。

react-redux 如何响应监听

当redux dispatch(action)之后,redux会调用所有的listener。那么react-redux需要做的事,就是把这个响应事件放到listener中。react-redux做法如下:

// 在组件挂载之后,注册handleChange事件,同时强制调用一次
componentDidMount() {
  this.trySubscribe()
}
trySubscribe() {
  if (shouldSubscribe && !this.unsubscribe) {
    this.unsubscribe = this.store.subscribe(this.handleChange.bind(this))
    this.handleChange()
  }
}

这里解释下为什么在注册完监听事件之后,还要强制调用一下handleChange。
connect返回的是一个包裹了目标组件的组件:CON_COMA = connect(COMA)。因此他的声明周期顺序如下:

// constructor CON_COMA     ---> 生成this.state.storeState
  // render CON_COMA
  // constructor COMA
  // render COMA
  // componentDidMount COMA    ----> 阶段A
// componentDidMount CON_COMA ---> 阶段B

假如我们在【阶段A】dispatch(action)更新了Store,那么这个时候的store会与阶段B中的this.state.storeState不一致。因此我们需要强制调用handleChange同步store

碎片知识

  • 如何connect中的mapStateToProps没传入的话,即使store改变,也不会造成render的重新渲染,因为handleChange没有被注册
  • option.pure = false,表示不需要计算他的stateProps,dispatchProps,this.props的改变,因为shouldComponentUpdate都返回true。任何改变,都会重新调用render。

2. redux最核心API

说到redux最核心的API,不得不提createStore。

2.1 createStore内部变量

currentReducer
currentState
currentListeners
nextListeners
isDispatching

从变量上看,我们知道createStore工作:处理reducer、store、和响应事件之间的关系。那么我们接下来看看creatStore是怎么处理的。
变量中的nextListenerscurrentListeners基本一样。不一样的是我们操作的永远是nextListeners这个变量。可能有个疑惑:在dispatch函数中有const listeners = (currentListeners = nextListeners)这么一句话,因为nextListeners是数组,所以操作nextListeners的时候,其实currentListeners也相应发生了变化。其实不然:createStore中有这么一个函数ensureCanMutateNextListeners。具体实现方式如下:

function ensureCanMutateNextListeners() {
    if (nextListeners === currentListeners) {
      nextListeners = currentListeners.slice()
    }
  }

通过array.prototype.slicecurrentListeners拷贝给了nextListeners。因此nextListenerscurrentListeners只是数值一样,引用不一样的不同数组。

2.2 createStore对外暴露的函数:

getState // 获取当前状态currentState
subscribe //订阅响应函数
dispatch
replaceReducer  // 替换当前reducer
observable  // 类似一个Observable类型,观察state的变化

2.2.1 getState

不处在dispatch的时候,返回当前状态currentState,如果是初始的时候,currentState是reducer的初始值:preloadedState

2.2.2 replaceReducer

1、重新设置currentReducer。
2、dispatch替换通知:ActionTypes.REPLACE

2.2.3 dispatch

不处在dispatch的时候currentReducer处理currentState,得到新的状态。最后,强制调用nextListeners里所有的listener

函数过程中涉及一些isDispatching处理。

2.2.4 observable

该函数返回的是一个轻量级的observable对象。具体可参见:https://github.com/tc39/proposal-observable。他是TC委员会在2017年1月提出的提案。动机是为了解决异步数据流的问题。

2.2.5 subscribe

注册监听事件,将监听事件入队到nextListeners
返回注销监听事件函数:事件从nextListeners删除。用到函数:array.prototype.splice

2.3 dispatch({ type: ActionTypes.INIT })

每次调用createStore,都会自动分发出一个type: ActionTypes.INIT通知,从【2.2.3: dispatch】知道,这个动作是调用currentReducer,生成一个新的currentState状态,去初始化状态数:state tree。

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.