Vuex源码阅读笔记

JuniorBlack 5年前
   <p>俗话说得好,没有无缘无故的爱,也没有无缘无故的恨,更不会无缘无故的去阅读别人的源代码。之所以会去阅读Vuex的源代码,是因为在刚开始接触Vuex时,就在官方文档的Actions部分,看到这么一句:</p>    <pre>  <code class="language-javascript">// the simplest action  function increment (store) {    store.dispatch('INCREMENT')  }    // a action with additional arguments  // with ES2015 argument destructuring  function incrementBy ({ dispatch }, amount) {    dispatch('INCREMENT', amount)  }  </code></pre>    <p>上面的Action还好说,能看懂,但是下面使用ES6写法的Action是什么鬼呀喂(摔!)</p>    <p>虽然知道有解构赋值,但是那个 { dispatch } 又是从哪儿冒出来的呀喂!明明我在调用时,没有传这个参数呀!</p>    <p>之前因为赶项目进度,所以抱着能用就行的态度,也就没管那么多。如今有了空闲时间,必须好好钻研一下呀。</p>    <p>而钻研最好的方式,就是阅读Vuex的源代码。这样就能弄清楚,那个 { dispatch } 到底从哪儿冒出来的。</p>    <h3>Vuex源代码简介</h3>    <p>Vuex的源代码量挺少的,加起来也才600行不到,但是其中大量使用了ES6的语法,且部分功能(如Vuex初始化)使用到了Vue。所以读起来还是有些费劲的。</p>    <p>整个Vuex的源代码,核心内容包括两部分。一部分是Store的构造函数,另一部分则是Vuex的初始化函数。</p>    <p>而刚才问题的答案,就在第二部分。</p>    <h2>问题场景还原</h2>    <p>首先要介绍的,就是Vuex在Vue项目中的初始化。这儿贴一段代码:</p>    <p>首先是Vuex中,我写的Actions源代码:</p>    <pre>  <code class="language-javascript">// global/Vuex/action.js  export const getMe = ({ dispatch }) => {    /**   * 异步操作,获取用户信息,并存入Vuex的state中   */    res.user.get_me()    .then(data => {      dispatch('GET_ME', data)    })    .catch(err => {      console.log(err)    })  }  </code></pre>    <p>这个则是顶层组件,调用store的地方。由于Vuex的特点,store只需要在最顶层的组件声明一次。</p>    <pre>  <code class="language-javascript"><template>    <div id="wrapper">      <router-view></router-view>    </div>  </template>    <script type="text/javascript">   import store from './Vuex/store.js'     export default {   store   }  </script>  </code></pre>    <p>接下来则是组件中,则是实际调用Vuex的代码。</p>    <pre>  <code class="language-javascript">// index.vue  import { getMe } from './../global/Vuex/action'    export default {      vuex: {      actions: {        getMe      },      getters: {        // 从state中获取信息        user: state => state.user      }    },      ready() {      // 开始获取用户信息      this.getMe()    }  }  </code></pre>    <p>在这儿,可以很明显的看出,我在使用 this.getMe() 时,是没有任何参数的。但是在 getMe 函数的定义中,是需要解构赋值出 {dispatch} 的。</p>    <p>就好比说这个:</p>    <pre>  <code class="language-javascript">function getX({ x }) {    console.log(x)  }    getX({ x: 3, y: 5 })  // 3  </code></pre>    <p>你得传入相应的参数,才能进行解构赋值。</p>    <p>同时,我注意到在Vuex的Actions调用,需要在Vue的options的Vuex.actions中先声明,之后才能使用。</p>    <p>那么,一定是Vuex对这个Action动了手脚。(逃)</p>    <p>而动手脚的代码,就存在于Vuex源代码的 override.js 中。这个文件,是用于初始化Vuex的。</p>    <h2>Vuex的初始化</h2>    <p>在 override.js 中,有个 vuexInit 的函数。看名字就知道,这是拿来初始化Vuex的。</p>    <p>在代码开头,有这么一句:</p>    <pre>  <code class="language-javascript">const options = this.$options  const { store, vuex } = options  // 感觉解构赋值真的很棒,这样写能省很多时间。  // 下面的是老写法  // const store = options.store  // const vuex = options.vuex  </code></pre>    <p>在这儿,用于是在Vue中调用,所以this指向Vue,而this.$options则是Vue的配置项。</p>    <p>也就是写Vue组件时的:</p>    <p>export default {……一些配置}</p>    <p>这里,就把Vue配置项的store和vuex抽离出来了。</p>    <h3>搜寻store</h3>    <p>接下来,则看到了Vuex源代码的精妙之处:</p>    <pre>  <code class="language-javascript">// store injection  if (store) {    this.$store = store  } else if (options.parent && options.parent.$store) {    this.$store = options.parent.$store  }  </code></pre>    <p>解构赋值并不是一定成功的,如果store在options中不存在,那么store就会是undefined。但是我们需要找store。</p>    <p>于是Vuex提供了向父级(Vue中的功能)寻找store的功能。不难看出,这儿父级的$store如果不存在,那么其实他也会到自己的父级去寻找。直到找到为止。</p>    <p>就想一条锁链一样,一层一层的连到最顶部store。所以在没有找到时,Vuex会给你报个错误。</p>    <pre>  <code class="language-javascript">// 声明了Vuex但没有找到store时的状况  if (vuex) {    if (!this.$store) {      console.warn(        '[vuex] store not injected. make sure to ' +        'provide the store option in your root component.'      )    }  </code></pre>    <h3>对Vuex声明的内容,进行改造</h3>    <p>接下来,则是对Vuex声明的内容,进行改造。</p>    <p>首先的是获取Vuex对象的内容:</p>    <pre>  <code class="language-javascript">let { state, getters, actions } = vuex  </code></pre>    <p>同时,在这儿还看到了对过时API的处理。感觉算是意料之外的惊喜。</p>    <pre>  <code class="language-javascript">// handle deprecated state option  // 如果使用state而不是getters来获取Store的数据,则会提示你state已经过时的,你需要使用新的api。  // 但是,这儿也做了兼容,确保升级时服务不会挂掉。  if (state && !getters) {    console.warn(      '[vuex] vuex.state option will been deprecated in 1.0. ' +      'Use vuex.getters instead.'    )    getters = state  }  </code></pre>    <p>接下来,则是对getters和actions的处理:</p>    <pre>  <code class="language-javascript">// getters  if (getters) {    options.computed = options.computed || {}    for (let key in getters) {      defineVuexGetter(this, key, getters[key])    }  }  // actions  if (actions) {    options.methods = options.methods || {}    for (let key in actions) {      options.methods[key] = makeBoundAction(this.$store, actions[key], key)    }  }  </code></pre>    <p>可以看出,在这儿对getters和actions都进行了额外处理。在这儿,我们讲述actions的额外处理,至于getters,涉及了过多的Vue,而我不是很熟悉。等我多钻研后,再写吧。</p>    <h2>Actions的改造</h2>    <p>对整个Actions的改造,首先是Vuex的检测:</p>    <pre>  <code class="language-javascript">// actions  if (actions) {    // options.methods是Vue的methods选项    options.methods = options.methods || {}    for (let key in actions) {      options.methods[key] = makeBoundAction(this.$store, actions[key], key)    }  }  </code></pre>    <p>在这儿,我们一点一点的剖析。可以看出,所有的actions,都会被 makeBoundAction 函数处理,并加入Vue的methods选项中。</p>    <p>那么看来, makeBoundAction 函数就是我要找的答案了。</p>    <p>接下来贴出 makeBoundAction 函数的源代码:</p>    <pre>  <code class="language-javascript">/**   * Make a bound-to-store version of a raw action function.   *   * @param {Store} store   * @param {Function} action   * @param {String} key   */    function makeBoundAction(store, action, key) {    if (typeof action !== 'function') {      console.warn(`[vuex] Action bound to key 'vuex.actions.${key}' is not a function.`)    }    return function vuexBoundAction(...args) {      return action.call(this, store, ...args)    }  }  </code></pre>    <p>事情到这儿,其实已经豁然明朗了。</p>    <p>我在Vuex中传入的actions,实际会被处理为 vuexBoundAction ,并加入options.methods中。</p>    <p>在调用这个函数时,实际上的action会使用call,来改变this指向并传入store作为第一个参数。而store是有dispatch这个函数的。</p>    <p>那么,在我传入 {dispatch} 时,自然而然就会解构赋值。</p>    <p>这样的话,也形成了闭包,确保action能访问到store。</p>    <h2>结语</h2>    <p>今天应该算是解决了心中的一个大疑惑,还是那句话:</p>    <p>没有无缘无故的爱,也没有无缘无故的恨,更没有无缘无故冒出来的代码。</p>    <p>整个源代码读下来一遍,虽然有些部分不太理解,但是对ES6和一些代码的使用的理解又加深了一步。比如这回就巩固了我关于ES6解构赋值的知识。而且还收获了很多别的东西。总而言之,收获颇丰~最后的,依然是那句话:前端路漫漫,且行且歌。</p>    <p>来自: <a href="/misc/goto?guid=4959671119931272796" rel="nofollow">http://www.lxxyx.win/2016/04/17/Vuex源码阅读笔记/</a></p>