[Front-end dictionary] The process of Vuex injecting Vue life cycle

Preface

This article is the 13th article in the [Front-end Dictionary] series of articles. I will focus on Vue for the next 9 articles. I hope that these 9 articles can deepen your understanding of Vue. Of course, the premise of these articles is that you have a certain foundation of Vue by default. If you don't have any basics, it is recommended to read the official documentation first.

In the first article, I will combine part of the source code of Vue and Vuex to illustrate the process of injecting Vuex into the Vue life cycle.

When it comes to source code, it's not as difficult as you might think. It's almost the same as we usually write business code, it's all method calls. But the call tree of the source code will be much more complicated.

Why use Vuex

With Vue, we will inevitably encounter data or state shared between components. The business code of the application becomes more and more complicated, and the disadvantages of communication methods such as props, events, and event buses will become more obvious. At this time we need Vuex. Vuex is a state management tool specially designed for Vue.

State management is an important means of decoupling Vue components.

It draws on the basic ideas of Flux and redux, extracts the state to the whole world, and forms a store.

[Front-end dictionary] The process of Vuex injecting Vue life cycle

Vuex does not limit your code structure, but you need to follow some rules:

  1. Application-level state should be concentrated in a single store object

  2. Submitting a mutation is the only way to change the state, and this process is synchronous

  3. Asynchronous logic should be encapsulated in action

The process of injecting Vuex into the Vue life cycle

When we install the plug-in, we always use Vue.use() to load the plug-in like the following, but what does Vue.use() do?

import Vue from 'vue';

import Vuex from 'vuex';

Vue.use(Vuex);

What does Vue.use() do

Install the Vue.js plugin. If the plug-in is an object, the install method must be provided. If the plugin is a function, it will be used as the install method. When the install method is called, Vue will be passed in as a parameter.

The above is the explanation of the official document.

Next, let's take a look at what Vue.use() does from the source code part.

The Vue source code calls the initUse(Vue) method in the initGlobalAPI entry method. This method defines what Vue.use() needs to do.

function initGlobalAPI (Vue) {

  ......

  initUse(Vue);

  initMixin$1(Vue); // 下面讲 Vue.mixin 会提到

  ......

}

function initUse (Vue) {

  Vue.use = function (plugin) {

    var installedPlugins = (this._installedPlugins || (this._installedPlugins = []));

    /* 判断过这个插件是否已经安装 */

    if (installedPlugins.indexOf(plugin) > -1) {

      return this

    }

    var args = toArray(arguments, 1);

    args.unshift(this);

    /* 判断插件是否有 install 方法 */

    if (typeof plugin.install === 'function') {

      plugin.install.apply(plugin, args);

    } else if (typeof plugin === 'function') {

      plugin.apply(null, args);

    }

    installedPlugins.push(plugin);

    return this

  };

}

This code mainly does two things:

  1. One is to prevent repeated installation of the same plugin

  2. The other is to initialize the plugin

The install method of the plugin

After reading the above source code, we know that the plug-in (Vuex) needs to provide an install method. So let's see if there is this method in the Vuex source code. The result is of course:

/* 暴露给外部的 install 方法 */

function install (_Vue) {

  /* 避免重复安装(Vue.use 内部也会检测一次是否重复安装同一个插件)*/

  if (Vue && _Vue === Vue) {

    {

      console.error(

        '[vuex] already installed. Vue.use(Vuex) should be called only once.'

      );

    }

    return

  }

  Vue = _Vue;

  /* 将 vuexInit 混淆进 Vue 的 beforeCreate(Vue2.0) 或 _init 方法(Vue1.0) */

  applyMixin(Vue);

}

This code mainly does two things:

  1. One is to prevent Vuex from being repeatedly installed

  2. The other is to execute applyMixin, the purpose is to execute the vuexInit method to initialize Vuex

Next we look at the source code of applyMixin (Vue):

/* 将 vuexInit 混淆进 Vue 的 beforeCreate */

function applyMixin (Vue) {

  var version = Number(Vue.version.split('.')[0]);

  if (version >= 2) {

    Vue.mixin({ beforeCreate: vuexInit });

  } else {

    /* Vue1.0 的处理逻辑,此处省略 */

    ......

  }

  function vuexInit () {

    ......

  }

}

From the above source code, we can see that the Vue.mixin method confuses the vuexInit method into the beforeCreate hook. Because of this operation, each vm instance will call the vuexInit method. So what does vuexInit do?

vuexInit ()

When we use Vuex, we need to pass store to the Vue instance.

new Vue({

  el: '#app',

  store

});

But we can access the store in every vm, this requires vuexInit.

  function vuexInit () {

    const options = this.$options

    if (options.store) {

      /* 根节点存在 stroe 时 */

      this.$store = typeof options.store === 'function'

        ? options.store()

        : options.store

    } else if (options.parent && options.parent.$store) {

      /* 子组件直接从父组件中获取 $store,这样就保证了所有组件都公用了全局的同一份 store*/

      this.$store = options.parent.$store

    }

  }

When the root node exists stroe, directly assign options.store to this.$store. Otherwise, it is not the root node, and it is obtained from the $store of the parent node.

Through this step, we can access the Store instance through this.$store in any vm. Next, let's talk about Vue.mixin() in reverse.

Vue.mixin()

Register a mixin globally, affecting every Vue instance created after registration. Plug-in authors can use mixins to inject custom behaviors into components. Not recommended for use in application code.

The initMixin$1(Vue) method is called in the initGlobalAPI entry method of vue:

function initMixin$1 (Vue) {

  Vue.mixin = function (mixin) {

    this.options = mergeOptions(this.options, mixin);

    return this

  };

}

The process of Vuex injecting the Vue life cycle is probably like this. If you are interested, you can directly take a look at the source code of Vuex, and then let's talk about Store.

Store

We mentioned above that vuexInit will get Store from options. So how does the Store come from?

When we use Vuex, we will define a Store instance similar to the following.

import Vue from 'vue'

import Vuex from 'vuex'

import mutations from './mutations'

Vue.use(Vuex)

const state = {

    showState: 0,                             

}

export default new Vuex.Store({

    strict: true,

    state,

    getters,

})

Do not enable strict mode in the publishing environment. Strict mode will deeply monitor the state tree to detect non-compliant state changes-please make sure to turn off strict mode in the release environment to avoid performance loss.

Responsive state

Do you care how responsive state is? This is mainly achieved through the resetStoreVM(this,state) method called in the Store constructor.

This method is mainly to reset a private _vm (an instance of Vue) object. This _vm object will keep our state tree, and store getters of store by way of calculating attributes. Now let’s take a look at its implementation process.

/* 使用 Vue 内部的响应式注册 state */

function resetStoreVM (store, state, hot) {

  /* 存放之前的vm对象 */

  const oldVm = store._vm 

  store.getters = {}

  const wrappedGetters = store._wrappedGetters

  const computed = {}

  /* 通过 Object.defineProperty 方法为 store.getters 定义了 get 方法。当在组件中调用 this.$store.getters.xxx 这个方法的时候,会访问 store._vm[xxx]*/

  forEachValue(wrappedGetters, (fn, key) => {

    computed[key] = partial(fn, store)

    Object.defineProperty(store.getters, key, {

      get: () => store._vm[key],

      enumerable: true // for local getters

    })

  })

  const silent = Vue.config.silent

  /* 设置 silent 为 true 的目的是为了取消 _vm 的所有日志和警告 */

  Vue.config.silent = true

  /*  这里new了一个Vue对象,运用Vue内部的响应式实现注册state以及computed*/

  store._vm = new Vue({

    data: {

      $$state: state

    },

    computed

  })

  Vue.config.silent = silent

  /* 使能严格模式,Vuex 中对 state 的修改只能在 mutation 的回调函数里 */

  if (store.strict) {

    enableStrictMode(store)

  }

  if (oldVm) {

    /* 解除旧 vm 的 state 的引用,并销毁这个旧的 _vm 对象 */

    if (hot) {

      store._withCommit(() => {

        oldVm._data.$$state = null

      })

    }

    Vue.nextTick(() => oldVm.$destroy())

  }

}

The responsive state of state is probably implemented in this way, which is the process of initializing the resetStoreVM method.

Look at the commit method of Store

We know that the commit method is used to trigger the mutation.

commit (_type, _payload, _options) {

  /* unifyObjectStyle 方法校参 */

  const {

    type,

    payload,

    options

  } = unifyObjectStyle(_type, _payload, _options)

  const mutation = { type, payload }

  /* 找到相应的 mutation 方法 */

  const entry = this._mutations[type]

  if (!entry) {

    if (process.env.NODE_ENV !== 'production') {

      console.error(`[vuex] unknown mutation type: ${type}`)

    }

    return

  }

  /* 执行 mutation 中的方法 */

  this._withCommit(() => {

    entry.forEach(function commitIterator (handler) {

      handler(payload)

    })

  })

  /* 通知所有订阅者,传入当前的 mutation 对象和当前的 state */

  this._subscribers.forEach(sub => sub(mutation, this.state))

  if (

    process.env.NODE_ENV !== 'production' &&

    options && options.silent

  ) {

    console.warn(

      `[vuex] mutation type: ${type}. Silent option has been removed. ` +

      'Use the filter functionality in the vue-devtools'

    )

  }

}

This method first performs parameter style verification, and then uses the _withCommit method to execute this batch trigger mutation processing function. After the execution is complete, notify all _subscribers (subscription functions) of the mutation object of this operation and the current state state.

Vue related articles output plan

Recently, friends have always asked me about Vue-related questions, so I will output 9 Vue-related articles next, hoping to help you. I will keep an update in 7 to 10 days.

  1. [Front-end dictionary] The process of Vuex injecting Vue life cycle

  2. [Front-end Dictionary] Analysis of Vue Responsive Principle

  3. [Front-end dictionary] The process of patching new and old VNodes

  4. [Front-end dictionary] How to develop functional components and upload npm

  5. [Front-end dictionary] Optimize your Vue project from these aspects

  6. [Front-end Dictionary] Talk about the development of front-end routing from the Vue-Router design

  7. [Front-end dictionary] How to use Webpack correctly in the project

  8. [Front-end dictionary] Vue server rendering

  9. [Front-end dictionary] How to choose between Axios and Fetch

I suggest you pay attention to my official account, you can receive the latest articles as soon as possible.

[Front-end dictionary] The process of Vuex injecting Vue life cycle

If you want to join group communication, you can also add my WeChat wqhhsd.

Guess you like

Origin blog.51cto.com/15077552/2596466