1. Introduction

Hello, I am Ruochuan, WeChat search “Ruochuan vision” follow me, focus on front-end technology sharing, a vision is to help the front-end broaden the vision to the forefront of the public account within 5 years. Welcome to join me at WeChat ruochuan12, long-term communication and learning.

This is to learn the overall source architecture series of VUEX4 source code (10). JQuery, underscore, lodash, sentry, vuex, axios, koa, redux, vue-devtools open files directly.

10 source series article small achievement, from July 19 began to write, 19 years to write 6, 20 years to write 2, this year to write 2. It’s kind of an end. This series should not be updated for a while. The main reason is that it takes a lot of time and effort to see it, with fewer people watching it and less feedback. Write some other articles after that. Welcome to continue to follow me (Wakawa).

In this paper, the warehouse address: git clone https://github.com/lxchuan12/vuex4-analysis.git, best way to read in this paper, cloning warehouse do-it-yourself debug, easy to digest.

If someone is talking about how to read the source code, and you are reading the article, you can recommend my source code series, it would be really ungrateful.

My article, try to write so that want to see the source code and do not know how to read the reader can understand. I recommend the use of build environment breakpoint debugging source code learning, where not point where, while debugging to see, rather than hard to see. Just say: give a man and fish than give a man to fish.

What you will learn after reading this article:

    1. git subtreeManage the sub-warehouse
    1. How to learnVuex 4Source code, understandingVuexThe principle of
    1. Vuex 4Vuex 3The similarities and differences
    1. Vuex 4 composition APIHow to use
    1. Vue.provide / Vue.injectAPI usage and principles
    1. How to write aVue3The plug-in
  • , etc.

For those of you who are not familiar with debugging the Google browser, read this articleThe Chrome DevTools Source paneIt’s very detailed. By the way, when I opened the Settings, the Source panel supports expanding the search code block (it doesn’t by default). A picture is worth a thousand words.

Google browser is our front-end commonly used tools, so I recommend you to study in depth, after all, the work to do a good job, must first advantage its tool.

Wakawa’s blog VUEX source code. The repository has a very detailed annotation and the way to look at the source code, so this article will not repeat the same with VUEX 3 source code too much.

1.1 Best way to read this article

My vuex4 source warehouse git clone https://github.com/lxchuan12/vuex4-analysis.git clone, star, by the way my warehouse ^_^ vuex4 source study. Follow the rhythm of the article and debug the sample code, using Chrome to debug more impressive. The article long code need not look closely, you can look carefully when debugging. See this kind of source article one hundred times, may not be as good as their own debugging a few times, bold guess, careful proof. Also welcome to add me WeChat communication ruochuan12.

2. Brief introduction of Vuex principle

Conclusion first: the Vuex principle can be broken down into three key points. The first is that each component instance is injected with a Store instance. Second, the various methods in the Store instance serve properties in the Store. Third, property changes in the Store trigger view updates.

This article focuses on the first point. The second point in my last article to learn the overall VUEX source architecture, to build their own state management library in detail, this article will not repeat. The third point is not discussed in detail in either article.

Here is a short piece of code that illustrates how Vuex works.

Class Store{constructor(){this._state = 'Store '; } dispatch(val){ this.__state = val; } commit(){} const store = new store (); var rootInstance = { parent: null, provides: { store: store, }, }; var parentInstance = { parent: rootInstance, provides: { store: store, } }; var childInstance1 = { parent: parentInstance, provides: { store: store, } }; var childInstance2 = { parent: parentInstance, provides: { store: store, } }; Store. Dispatch (' I've been modified '); ChildInstance1 provides. Store; childInstance2 provides. Store; childInstance2 provides. // Because the same Store object is shared.

Inject is used to retrieve the Store instances in the provide parent component. Inject is used to retrieve the Store instances.

So then, with the question:

1. Why did you modify the property in the instance store? The change will trigger the view update.

2, VUEX4 as a VUE plug-in implementation and VUE combination.

3. How to implement provide and inject, and how to get the Store of each component instance.

4. Why is there a Store instance in every component object (render component object)?

5. Why the data provided by the provide written in the component can be retrieved by the quilt-level component.

3. Major changes to Vuex 4

Before we look at the source code, let’s take a look at the Vuex 4 release and the major change mentioned in the official documentation migration, Vuex 4 Release.

Migrate from 3.x to 4.0

Vuex 4 is all about compatibility. Vuex 4 supports development with Vue 3 and provides directly the same API as Vuex 3, so users can reuse existing Vuex code in Vue 3 projects.

Compared to Vuex 3 version. The major changes are as follows (the others are in the links above) :

3.1 Installation process

Vuex 3 is a Vue. Use (Vuex)

Vuex 4 is app.use(store)

import { createStore } from 'vuex'

export const store = createStore({
  state() {
    return {
      count: 1
    }
  }
})
import { createApp } from 'vue'
import { store } from './store'
import App from './App.vue'

const app = createApp(App)

app.use(store)

app.mount('#app')

3.2 The core module is exportedcreateLoggerfunction

import { createLogger } from 'vuex'

Let’s look at these major changes from a source point of view.

4. Vuex 4 major changes from a source point of view

4.1 Chrome debug Vuex 4 source code preparation work

Git subtree add -- prefix = vuex https://github.com/vuejs/vuex.git 4.0

This method preserves the Git records of the Vuex4 repository. See this article for more information on how to use a Git subtree to synchronize subprojects in both directions across multiple Git projects.

As readers friend of you, need to clone my Vuex source warehouse 4 https://github.com/lxchuan12/vuex4-analysis.git, welcome to star, too.

The vuex/examples/webpack. Config. Js, add a devtool: ‘the source – the map, so you can open sourcemap debugging the source code.

We debug with the shopping cart example in the project throughout this article.

Git clone https://github.com/lxchuan12/vuex4-analysis.git CD vuex NPM NPM I run dev # # open http://localhost:8080/ option Examples of composition shopping cart shopping cart - # open http://localhost:8080/composition/shopping-cart/ # opening on F12 debugging tools, Source panel => Page => Webpack :// =>.

It is said that a picture is worth a thousand words, then simply cut a debugging figure.

Locate the createStore function and hit the breakpoint.

// webpack:///./examples/composition/shopping-cart/store/index.js import { createStore, createLogger } from 'vuex' import cart from './modules/cart' import products from './modules/products' const debug = process.env.NODE_ENV ! == 'production' export default createStore({ modules: { cart, products }, strict: debug, plugins: debug ? [createLogger()] : [] })

Find the app.js entry and put breakpoints in app.use(store), app.mount(‘#app’), etc.

// webpack:///./examples/composition/shopping-cart/app.js
import { createApp } from 'vue'
import App from './components/App.vue'
import store from './store'
import { currency } from './currency'

const app = createApp(App)

app.use(store)

app.mount('#app')

Next, we’ll branch out from createApp({}) and app.use(Store).

4.2 Vuex createStore function

Compared to Vuex 3, the new Vuex.Store is actually the same. Vuex 4 adds an extra createStore function to keep it in line with Vue 3.

export function createStore (options) { return new Store(options) } class Store{ constructor (options = {}){ // Omit some code... this._modules = new ModuleCollection(options) const state = this._modules.root.state resetStoreState(this, State) // omit some code... }} function resetStoreState (store, state, hot) { Store._state = reactive({data: state}) // Reactive code }

Monitoring data

Unlike Vuex 3, it is no longer using new Vue(), but the reactive method provided by Vue 3.

Reactive function method is not discussed in this article. Because by extension, I can write a new article. Just know that the main function is to monitor data changes and change views.

So that’s the answer to our first question.

Following the breakpoint we move on to the app.use() method, the plug-in mechanism that Vue provides.

4.3 app. Use () method

What Use does is simple enough to say that it adds passed plug-ins to the collection of plug-ins to prevent duplication.

Execute the plug-in. If it is an object and install is a function, then pass the parameter app and other parameters to the install function for execution. If the function is executed directly.

// webpack:///./node_modules/@vue/runtime-core/dist/runtime-core.esm-bundler.js function createAppAPI(render, Hydrate) {return function createApp(RootComponent, Rootprops = null) {const instAlledPlugins = new Set(); const app = (context.app = { use(plugin, ... Options) {// Warning if the plugin is already available and not in production. if (installedPlugins.has(plugin)) { (process.env.NODE_ENV ! == 'production') && warn(`Plugin has already been applied to target app.`); Else if (plugin && isFunction(plugin.install)) {instAlledPlugins.add (plugin); // plugin.install(app... options); Else if (isFunction(plugin)) {instAlledPlugins.add (plugin); plugin(app, ... options); } else if (process.env.node_env! == 'production')) { warn(`A plugin must either be a function or an object with an "install" ` + `function.`); } // Support chained calls to return app; }, provide(){// Else... More on this later); }}

In the above code, break the line plugin.install(app,… options);

Follow the breakpoint to the next step, the install function.

4.4 install function

Export class Store{// install (app, InjectKey) {/ / for use in composition API / / can pass in injectKey if didn't pass the default storeKey is store app. Dojo.provide (injectKey | | storeKey, This) / / option for apis used in the app. Config. GlobalProperties $store = this} / / omit some code... }

The install function in Vuex4 is relatively simpler than it was in Vuex3. The first sentence is provided to the Composition API. Injection into the root instance object. The second sentence is provided for the Option API.

Following the breakpoint, press F11 to see the app.provide implementation.

4.4.1 app. Dojo.provide

The store = store instance is added to the context’s provides property.

Provide (key, value) {// Warn if ((process.env.node_env! == 'production') && key in context.provides) { warn(`App already provides property with key "${String(key)}". ` + `It will be overwritten with the new value.`); } // TypeScript doesn't allow symbols as index type // https://github.com/Microsoft/TypeScript/issues/24587 context.provides[key] = value; return app; }

Then search for context from the code above, and you’ll find this code:

const context = createAppContext();

Now let’s look at the function createAppContext. Context = context

// webpack:///./node_modules/@vue/runtime-core/dist/runtime-core.esm-bundler.js
function createAppContext() {
    return {
        app: null,
        config: {
            isNativeTag: NO,
            performance: false,
            globalProperties: {},
            optionMergeStrategies: {},
            isCustomElement: NO,
            errorHandler: undefined,
            warnHandler: undefined
        },
        mixins: [],
        components: {},
        directives: {},
        provides: Object.create(null)
    };
}

Vue3 document application configuration (app.config)

4.4.2 app. Config. GlobalProperties

App. Config. GlobalProperties official documentation

Usage:

app.config.globalProperties.$store = {}

app.component('child-component', {
  mounted() {
    console.log(this.$store) // '{}'
  }
})

This explains why each component can use this.$store. XXX to access methods and properties in Vuex.

AppContext. provides is a Store instance injected into AppContext. provides. This is the equivalent of the root component instance and config global configuration GlobalProperties has the Store instance object.

So that’s it. CreateStore (Store) and App.Use (Store).

App. Provide is actually used by the composition API.

But that’s just what the documentation says, so let’s dig deeper into the rationale for why each component instance should be accessible.

Next, let’s look at the source code implementation and why it is available in each component instance.

Before I do that, let’s look at how we use Vuex4 in our composite API, that’s the clue.

4.5 How does Vuex 4 work with Composition API

Then we find the following file, useStore, which is the object of our breakpoint.

// webpack:///./examples/composition/shopping-cart/components/ShoppingCart.vue import { computed } from 'vue' import { useStore } from 'vuex' import { currency } from '.. /currency' export default {setup () {const store = useStore() // window.shoppingCartStore = store; // window.shoppingCartStore = store; // This is not the case.

Next, press the breakpoint F11, step through, and you will see that the vue.inject method is finally used.

4.5.1 Vuex.useStore source code implementation

// vuex/src/injectKey.js import { inject } from 'vue' export const storeKey = 'store' export function useStore (key = null) { return inject(key ! == null ? key : storeKey) }

4.5.2 Vue.inject source code implementation

Inject is a simple function to find the value that we provide with provide.

If there is no parent, which is the root instance, he took the vnode instance objects. The appContext. Provides. Otherwise, it takes the value of InstanceParent. Provides in the parent.

In the VUEX4 source code, it is a: Store instance object.

// webpack:///./node_modules/@vue/runtime-core/dist/runtime-core.esm-bundler.js function inject(key, defaultValue, treatDefaultAsFactory = false) { // fallback to `currentRenderingInstance` so that this can be called in // a functional Component / / if the is a functional component calls take currentRenderingInstance const instance = currentInstance | | currentRenderingInstance; if (instance) { // #2400 // to support `app.use` plugins, // fallback to appContext's `provides` if the intance is at root const provides = instance.parent == null ? instance.vnode.appContext && instance.vnode.appContext.provides : instance.parent.provides; if (provides && key in provides) { // TS doesn't allow symbol as index type return provides[key]; } // If the number of arguments is greater than one, the second argument is the default, the third argument is true, and the second argument is a function, the function is executed. else if (arguments.length > 1) { return treatDefaultAsFactory && isFunction(defaultValue) ? defaultValue() : defaultValue; Else if ((process.env.node_env! == 'production')) { warn(`injection "${String(key)}" not found.`); }} // If there is no current instance, the description will give a warning. // Inject must be called in SetUp or else if ((process.env.node_env! == 'production')) { warn(`inject() can only be used inside setup() or functional components.`); }}

Next we move on to provide the inject.

4.5.3 Vue.provide source implementation

Provide provides the key/value of the provided object property to the component instance. Provide provides the key/value of the provided object to the component instance.

2. If a component has a nickel-link, it will provide a nickel-link between the component and its parent. If a component has a nickel-link, it will provide a nickel-link between the component and its parent.

// webpack:///./node_modules/@vue/runtime-core/dist/runtime-core.esm-bundler.js function provide(key, value) { if (! currentInstance) { if ((process.env.NODE_ENV ! == 'production')) { warn(`provide() can only be used inside setup().`); } } else { let provides = currentInstance.provides; // by default an instance inherits its parent's provides object // but when it needs to provide values of its own, it creates its // own provides object using parent provides object as prototype. // this way in `inject` we can simply look up injections from direct // parent and let the prototype chain do the work. const parentProvides = currentInstance.parent && currentInstance.parent.provides; if (parentProvides === provides) { provides = currentInstance.provides = Object.create(parentProvides); } // TS doesn't allow symbol as index type provides[key] = value; }}

This section of the provide function may not be as easy to understand.

if (parentProvides === provides) {
    provides = currentInstance.provides = Object.create(parentProvides);
}

Let’s take an example and digest it.

Var currentInstance = {Provides: {store: {__state: 'store'}}; var provides = currentInstance.provides; // If the object is the same as the object, the object will be equal to the object. var parentProvides = provides; if(parentProvides === provides){ provides = currentInstance.provides = Object.create(parentProvides); }

After executing this once, currentInstance will look like this.

{provides: {// __proto__ : {store: {__state: 'store instance'}}}}

When executed the second time, currentInstance would be:

__proto__: {store: {__state: 'store instance'}}}}}. If this is the case, __proto__: {store instance: 'store instance'}}}}}

By the same token, the more times provide is executed, the longer the prototype chain becomes.

The inject and provide functions above both have a currentInstance currentInstance, so where does the currentInstance come from?

Why every component has access to the idea of Dependency Injection. A neat way to do this is to search for “Provides” in the file run-time -core.esm-bundler.js, and you’ll find the CreateComponentInstance function

Now let’s see how the CreateComponentInstance function creates a component instance.

4.6 CreateComponentInstance creates the component instance

You can disable other breakpoints, such as: const AppContext = (parent? parent.appContext : vnode.appContext) || emptyAppContext; Let’s look at the implementation.

// webpack:///./node_modules/@vue/runtime-core/dist/runtime-core.esm-bundler.js
const emptyAppContext = createAppContext();
let uid$1 = 0;
function createComponentInstance(vnode, parent, suspense) {
    const type = vnode.type;
    const appContext = (parent ? parent.appContext : vnode.appContext) || emptyAppContext;
    const instance = {
        uid: uid$1++,
        vnode,
        type,
        parent,
        appContext,
        root: null,
        next: null,
        subTree: null,
        // ...
        provides: parent ? parent.provides : Object.create(appContext.provides),
        // ...
    }
    instance.root = parent ? parent.root : instance;
    // ...
    return instance;
}

When the root component instance is generated, the vnode has already been generated. As for when the vnode was generated, I have compiled the following simplified version.

Mount (rootContainer, isHydrate) {if (! isMounted) { const vnode = createVNode(rootComponent, rootProps); // store app context on the root VNode. // this will be set on the root instance on initial mount. vnode.appContext = context; }},

Object.create is all about prototyping relationships. A picture is worth a thousand words.

From teacher huang yi pull hook column, I wanted to draw a picture by myself, but I think this one is very good.

4.6.1 Component instances are generated, so how to combine them

In the runtime-core.esm-bundler.js file, search for provide(you can find the following code:

If the user provides the object or function return value, it will provide the object. If the user provides the object or function return value, it will provide the object.

{// If the parent is __proto__: {// If the parent is __proto__: {// If the parent is __proto__: {// If the parent is __proto__: { {__state: 'Store instance '}}}}}
// webpack:///./node_modules/@vue/runtime-core/dist/runtime-core.esm-bundler.js function applyOptions(instance, options, deferredData = [], deferredWatch = [], deferredProvide = [], asMixin = false) { // ... if (provideOptions) { deferredProvide.push(provideOptions); } if (! AsMipin && DeferredProviding. Length) {DeferredProviding. Foreach (DeferredOptions => {// The write provides an object or function const provides = isFunction(provideOptions) ? provideOptions.call(publicThis) : provideOptions; Reflect.ownKeys(provides).forEach(key => { provide(key, provides[key]); }); }); } / /... }

This way, objects provided by app.provide, from top to bottom, are injected into each component instance. The component provides a “Provides” instance. The component provides a “Provides” instance.

Then let’s follow the project to verify the statement above. A look at the VUE3 documentation reveals that there is an API to get the current component instance.

4.7 GetCurrentInstance Gets the current instance object

GetCurrentInstance supports access to internal component instances for higher-order usage or library development.

import { getCurrentInstance } from 'vue' const MyComponent = { setup() { const internalInstance = getCurrentInstance() InternalInstance. AppContext. Config. GlobalProperties / / access globalProperties}}

With this API in mind, we can add some code to the code for the shopping cart example. It’s easy for us to understand.

// vuex/examples/composition/shopping-cart/components/App.vue import { getCurrentInstance, provide } from 'vue' import { useStore } from 'vuex'; Setup () {const store = useStore() provide('ruochuan12', 'WeChat search "Wakaguan view" ') window.AppStore = store; window.AppCurrentInstance = getCurrentInstance(); },
// vuex/examples/composition/shopping-cart/components/ProductList.vue setup(){ const store = useStore() // Start window. productListStore = store; window.ProductListCurrentInstance = getCurrentInstance(); provide('weixin-2', 'ruochuan12'); provide('weixin-3', 'ruochuan12'); provide('weixin-4', 'ruochuan12'); const mp = inject('ruochuan12'); Console. log(mp, 'introduction-productlist '); // WeChat search "Ruochuan vision" follow me, focus on front-end technology sharing. // end}; // end}
// vuex/examples/composition/shopping-cart/components/ShoppingCart.vue setup () { const store = useStore() // Start window. shoppingCartStore = store; window.ShoppingCartCurrentInstance = getCurrentInstance(); provide('weixin', 'ruochuan12'); provide('weixin1', 'ruochuan12'); provide('weixin2', 'ruochuan12'); const mp = inject('ruochuan12'); Console. log(mp, 'introduction-shoppinglist '); // WeChat search "Ruochuan vision" follow me, focus on front-end technology sharing. // Wakawa added debug code --start}

Output these values at the console

AppCurrentInstance AppCurrentInstance.provides ShoppingCartCurrentInstance.parent === AppCurrentInstance // true ShoppingCartCurrentInstance.provides ShoppingCartStore === AppStore // true ProductListStore === AppStore // true AppStore // Store instance object

Look at the console screenshot output example, actually with the text is similar. If you injects a provide(‘store’: ’empty string ‘), you will find the store that the user wrote first, and Vuex will not be able to use it properly.

Of course, Vuex4 provides that the injected key can be written in a way other than store, so there is no conflict with the user.

Export class Store{// install (app, InjectKey) {/ / for use in composition API / / can pass in injectKey if didn't pass the default storeKey is store app. Dojo.provide (injectKey | | storeKey, This) / / option for apis used in the app. Config. GlobalProperties $store = this} / / omit some code... }
export function useStore (key = null) { return inject(key ! == null ? key : storeKey) }

5. Answer the first five questions

Answer the following five questions:

1. Why did you modify the property in the instance store? The change will trigger the view update.

A: Use the reactive method in VUE to monitor changes in data.

Class Store{constructor (constructor (options = {}){//... this._modules = new ModuleCollection(options) const state = this._modules.root.state resetStoreState(this, State) // omit some code... }} function resetStoreState (store, state, hot) { Store._state = reactive({data: state}) // Reactive code }

2, VUEX4 as a VUE plug-in implementation and VUE combination.

A: App. Use (store) executes the install method in store, which is used in the composition API to provide the store instance object to the root instance. A sentence is injected into the root instance’s global property and is used in the Option API. They are injected into each component instance when the component is generated.

Export class Store{// install (app, InjectKey) {/ / for use in composition API / / can pass in injectKey if didn't pass the default storeKey is store app. Dojo.provide (injectKey | | storeKey, This) / / option for apis used in the app. Config. GlobalProperties $store = this} / / omit some code... }

3. How to implement provide and inject, and how to get the Store of each component instance.

5. Why the data provided by the provide written in the component can be retrieved by the quilt-level component.

Answer: The provide function creates a prototype chain that distinguishes the user-written properties of the component instance from the system-injected properties. Inject finds the properties of the Provides object in the parent instance through the prototype chain.

Function provide(){let provides = currentInstance. Provides; const parentProvides = currentInstance.parent && currentInstance.parent.provides; if (parentProvides === provides) { provides = currentInstance.provides = Object.create(parentProvides); } provides[key] = value; }
Function inject(){const provides = instance.parent == null? instance.vnode.appContext && instance.vnode.appContext.provides : instance.parent.provides; if (provides && key in provides) { return provides[key]; }}

Examples like this:

{// If the parent is __proto__: {// If the parent is __proto__: {// If the parent is __proto__: {// If the parent is __proto__: { {__state: 'Store instance '}}}}}

4. Why is there a Store instance in every component object (render component object)?

A: When rendering a component instance, createComponentInstance is called and injected into the provider of the component instance.

function createComponentInstance(vnode, parent, suspense) {
    const type = vnode.type;
    const appContext = (parent ? parent.appContext : vnode.appContext) || emptyAppContext;
    const instance = {
        parent,
        appContext,
        // ...
        provides: parent ? parent.provides : Object.create(appContext.provides),
        // ...
    }
    // ...
    return instance;
}
  1. How do you know so much

Answer: because the community someone wrote the VUE4 source article.

6. Summary

This article mainly describes the principle of Vuex4 to inject Store instances into the various components, expand on the Vuex4 and Vuex3 installation changes vuex.createStore, app.use(Store), vuex.createStore, In-depth source code analysis of Vue.inject, Vue.provide implementation principle.

Vuex4 is basically the same as Vuex3.x, except for the way it is installed and the way it monitors changes in data using Vue.reactive.

Finally, a review of the picture at the beginning of the article can be said to be a clever use of the prototype chain.

Do not feel suddenly enlightened.

Vuex is also a plugin to Vue, so if you know the principle of Vuex, you will be able to write a plugin to Vue.

If you find something wrong or can be improved, or if you don’t make it clear, please feel free to point it out in the comments, or add my WeChat
ruochuan12Communication. In addition, I think it is well written and helpful to you. I can thumb up, comment, forward and share. It is also a kind of support for me. If you can pay attention to my front-end public account:
“Wakawa Vision”, even better.

about

Hello, speaking
If sichuan, WeChat search
“Wakawa Vision”Follow me, focus on front-end technology sharing, a vision is to help the front-end broaden the vision to the forefront of the public account in 5 years. Welcome to add me WeChat
ruochuan12, long-term communication and learning.


There are mainly the following series of articles:
Learn the overall architecture of the source code series,
Annual summary,
JS Basic Series

Refer to the link

The GitHub repository provides Inject source code to test Vuex 4 official Chinese documentation