Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Redux与Vuex源码笔记(一) #9

Open
Cyrilszq opened this issue Mar 21, 2017 · 0 comments
Open

Redux与Vuex源码笔记(一) #9

Cyrilszq opened this issue Mar 21, 2017 · 0 comments

Comments

@Cyrilszq
Copy link
Owner

Cyrilszq commented Mar 21, 2017

虽然网上能搜到各种关于vuex与redux的源码分析文章,而且有很多写得很详细,但我还是决定按照我的思路写几篇博客,重点将放在整体的工作流程以及vuex与redux的一些对比。

Redux与React-redux工作流程梳理

第一篇是关于React-redux(5.0.3)的源码分析笔记,因为重在流程整理,所以其中只截取了部分源码

首先看看通常的用法:

const firstState =(state = {}, action){
  switch (action.type) {
    case 'ACTION_ONE':
      return {
        // 返回一个新的state
      }
    default:
      return {
        // 返回一个默认的state
      }
  }
}
// ...secondState类似

// 合并
const rootReducer = combineReducers({
    firstState,
    secondState
})

let store = createStore(
    rootReducer,
    // 关于中间件放到第二篇
    applyMiddleware(thunk)
);

<Provider store={store}>
  // 放一些自己的组件,如:
  <MyComponent></MyComponent>
</Provider>

store如何生成暂时先跳过,暂时只需要知道它是这样的结构:

store = {
    dispatch, // 即常用的dispatch函数
    subscribe,
    getState, // 通过它可以获取整个state
    replaceReducer
}

Provider开始:

export default class Provider extends Component {
  // react为实现跨级组件通信提供的api,从这一层往下的子组件都可以通过context.store直接拿到store,类似于一个全局变量
  getChildContext() {
    return { store: this.store, storeSubscription: null }
  }

  constructor(props, context) {
    super(props, context)
    // this.store来自于props
    this.store = props.store
  }

  render() {
    return Children.only(this.props.children)
  }
}

Provider的功能就是将store传给子孙组件。下面进入子组件MyComponent:

export default class MyComponent extends Component{
  // ...省略
  render() {
    // 这里this.props已经包含firstState
  }
}

// 这里传入的state是通过store.getState()获取到的值,即整个应用的state
function mapStateToProps(state) {
  // 在这里可以从整个state中提取一些在该组件需要用到的属性,比如这里的firstState
    const {firstState} = state
    // 通常需要返回一个对象,对象中的属性将通过下面的connect函数注入到组件的props中,
    return {
        firstState
    }
}

export default connect(mapStateToProps)(MyComponent)

关于connect我在看文档和使用的时候就挺好奇的,是怎么通过connect就可以把mapStateToProps里返回的对象注入到组件中的?关于这一段的流程我专门写了个最小化Demo打断点调试了一下,得出结果如下:

执行connect(mapStateToProps)对应的源码是:

function connect(
    mapStateToProps,
    mapDispatchToProps,
    mergeProps
  ) {
    // match函数中对mapStateToProps做一些处理,
    // 定义了mapStateToProps缺失或者mapStateToProps是一个函数时如何处理,
    // 如果mapStateToProps是一个函数,将对它进行一个包装,返回
    // function initProxySelector(dispatch, { displayName }) {...}
    // 即initMapStateToProps指向function initProxySelector(dispatch, { displayName }) {...}
    // 至于为什么这么做我还没搞懂
    const initMapStateToProps = match(mapStateToProps, mapStateToPropsFactories, 'mapStateToProps')
    const initMapDispatchToProps = match(mapDispatchToProps, mapDispatchToPropsFactories, 'mapDispatchToProps')
    const initMergeProps = match(mergeProps, mergePropsFactories, 'mergeProps')
    // 这里返回connectHOC()的执行结果,传递了一系列参数包括mapStateToProps,
    // 这个函数其实就是 /src/components/connectAdvanced.js中的connectAdvanced()
    // 在这个connectAdvanced里定义了一些contextTypes(猜测是为了从context上获取store)
    // 然后返回了wrapWithConnect(WrappedComponent){}函数
    // 至此connect(mapStateToProps)就执行完了,返回wrapWithConnect(WrappedComponent){}函数
    return connectHOC(selectorFactory, {
      initMapStateToProps,
      initMapDispatchToProps,
      initMergeProps,
    })
  }

个人感觉connect(mapStateToProps)就是对mapStateToProps的包装和利用闭包保存一些变量引用还有初始化一些变量。 上面注释中提到connect(mapStateToProps)返回的结果是wrapWithConnect(WrappedComponent){}函数,所以connect(mapStateToProps)(MyComponent)相当于执行wrapWithConnect(MyComponent)

wrapWithConnect源码:

// 只截取了部分

// 重点关注run方法
function makeSelectorStateful(sourceSelector, store) {
  // wrap the selector in an object that tracks its results between runs.
  const selector = {
    // 下面会多次调用run方法,将在这里比较新旧props,并设置 selector.shouldComponentUpdate
    run: function runComponentSelector(props) {
      try {
        const nextProps = sourceSelector(store.getState(), props)
        if (nextProps !== selector.props || selector.error) {
          selector.shouldComponentUpdate = true
          selector.props = nextProps
          selector.error = null
        }
      } catch (error) {
        selector.shouldComponentUpdate = true
        selector.error = error
      }
    }
  }

  return selector
}

function wrapWithConnect(WrappedComponent) {
    // 高阶组件的通常用法,创建一个Connect组件,用来包装我们传入的WrappedComponent
    class Connect extends Component {
      constructor(props, context) {
        super(props, context)
        // 获取到Provider中的store
        this.store = props[storeKey] || context[storeKey]

        this.initSelector()
        // 初始化Subscription,用于发布订阅模式
        this.initSubscription()
      }

      componentDidMount() {
        if (!shouldHandleStateChanges) return

        // 订阅store,一个发布订阅模式,当store发生变化时调用onStateChange
        this.subscription.trySubscribe()
      }

      componentWillReceiveProps(nextProps) {
        this.selector.run(nextProps)
      }

      shouldComponentUpdate() {
        // 通过this.selector.shouldComponentUpdate属性避免不必要的更新,
        // 所以使用了redux之后是不需要自己通过shouldComponentUpdate优化的
        return this.selector.shouldComponentUpdate
      }

      initSelector() {
        const sourceSelector = selectorFactory(this.store.dispatch, selectorFactoryOptions)

        this.selector = makeSelectorStateful(sourceSelector, this.store)
        // 将在run方法中调用mapStateToProps,返回值赋给this.selector.props
        this.selector.run(this.props)
      }

      initSubscription() {
        if (!shouldHandleStateChanges) return

        const parentSub = (this.propsMode ? this.props : this.context)[subscriptionKey]
        this.subscription = new Subscription(this.store, parentSub, this.onStateChange.bind(this))
      }
      // store发生变化时调用
      onStateChange() {
        this.selector.run(this.props)
        // setState触发react的更新,这里dummyState是一个空对象,
        // 只是为了触发react的更新机制,在更新机制中会从通过store.getState()获取最新的state
        this.setState(dummyState)
      }


      render() {
        // 在这里将额外的props(包括dispatch和mapStateToProps返回值)合并到WrappedComponent(即这里的MyComponent组件)
        return createElement(WrappedComponent, this.addExtraProps(selector.props))
      }
    }

    return hoistStatics(Connect, WrappedComponent)
  }
}

现在可以做个总结:

  1. connect内部调用了我们定义的mapStateToProps(),返回值将作为props注入组件
  2. 注入的方式类似于高阶组件
  3. connect中调用store.subscribe(这个方法在redux中定义,详情可以看第二篇)传入onStateChange作为回调,使得state变化时能够执行onStateChange触发DOM更新

参考链接

探索react-redux的小秘密
Redux入坑进阶-源码解析
解读redux工作原理

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant