乐趣区

关于vue.js:看了-vuex4-源码后vuex4-和-provideinject-原来就是妙用了原型链

1. 前言

你好,我是若川,微信搜寻「若川视线」关注我,专一前端技术分享,一个愿景是帮忙 5 年内前端开阔视野走向前列的公众号。欢送加我微信ruochuan12,长期交流学习。

这是 学习源码整体架构系列 之 vuex4 源码(第十篇)。学习源码整体架构系列文章(有哪些必看的 JS 库):jQuery、underscore、lodash、sentry、vuex、axios、koa、redux、vue-devtools 间接关上文件性能揭秘。

10 篇源码系列文章小成就达成,从 19 年 7 月开始写,19 年写了 6 篇,20 年写了 2 篇,往年写了 2 篇。算是一个完结吧。短时间内应该临时不更新这个系列了。次要是投入的工夫和精力比拟多,看的人很少,失去的反馈也比拟少。之后先写其余文章吧。欢送继续关注我(若川)。

本文仓库地址:git clone https://github.com/lxchuan12/vuex4-analysis.git,本文最佳浏览形式,克隆仓库本人入手调试,容易排汇消化。

要是有人说到怎么读源码,正在读文章的你能举荐我的源码系列文章,那真是无以为报啊

我的文章,尽量写得让想看源码又不晓得怎么看的读者能看懂。我都是举荐应用 搭建环境断点调试源码学习 哪里不会点哪里 边调试边看,而不是硬看 。正所谓: 授人与鱼不如授人予渔

浏览本文后你将学到:

    1. git subtree 治理子仓库
    1. 如何学习 Vuex 4 源码、了解 Vuex 原理
    1. Vuex 4Vuex 3 的异同
    1. Vuex 4 composition API 如何应用
    1. Vue.provide / Vue.inject API 应用和原理
    1. 如何写一个 Vue3 插件
  • 等等

如果对于谷歌浏览器调试还不是很相熟的读者,能够看这篇文章 chrome devtools source 面板,写的很具体。顺带提一下,我关上的设置,source 面板中反对开展搜寻代码块(默认不反对),一图胜千言。

谷歌浏览器是咱们前端罕用的工具,所以倡议大家深刻学习,毕竟 工欲善其事,必先利其器

之前写过 Vuex 3 的源码文章学习 vuex 源码整体架构,打造属于本人的状态治理库、若川的博客 Vuex 源码,仓库有很具体的正文和看源码办法,所以本文不会过多赘述与 Vuex 3 源码雷同的中央。

1.1 本文浏览最佳形式

把我的 vuex4 源码仓库 git clone https://github.com/lxchuan12/vuex4-analysis.git克隆下来,顺便 star 一下我的 vuex4 源码学习仓库 ^_^。跟着文章节奏调试和示例代码调试,用 chrome 入手调试印象更加粗浅。文章长段代码不必细看,能够调试时再细看。看这类源码文章百遍,可能不如本人多调试几遍,大胆猜想,小心求证。也欢送加我微信交换ruochuan12

2. Vuex 原理简述

论断后行 Vuex 原理能够拆解为三个关键点。
第一点、其实就是每个组件实例里都注入了 Store 实例。
第二点、Store实例中的各种办法都是为 Store 中的属性服务的。
第三点、Store中的属性变更触发视图更新。

本文次要解说第一点。第二点在我的上一篇文章学习 vuex 源码整体架构,打造属于本人的状态治理库具体讲了,本文就不赘述了。第三点两篇文章都没有具体讲述。

以下是一段简短的代码阐明 Vuex 原理的。

// 简版
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('我被批改了');
// store Store {_state: "我被批改了"}

// rootInstance、parentInstance、childInstance1、childInstance2 这些对象中的 provides.store 都改了。// 因为共享着同一个 store 对象。

看了下面的官网文档中的图,大略晓得是用 provide 父级组件中提供 Store 实例,用 inject 来获取到 Store 实例。

那么接下来,带着问题:

1、为什么批改了实例 store 里的属性,变更后会触发视图更新。

2、Vuex4作为 Vue 的插件如何实现和 Vue 联合的。

3、provideinject的如何实现的,每个组件如何获取到组件实例中的 Store 的。

4、为什么每个组件对象里都有 Store 实例对象了(渲染组件对象过程)。

5、为什么在组件中写的 provide 提供的数据,能被子级组件获取到。

TODO:
那么每个组件如何获取组件实例中的 Store 实例,composition API中实质上则是应用 inject 函数。

全局的 Store 实例对象。通过Vue.reactive() 监测数据。

3. Vuex 4 重大扭转

在看源码之前,先来看下 Vuex 4 公布的 release 和官网文档迁徙提到的重大扭转,Vuex 4 release。

从 3.x 迁徙到 4.0

Vuex 4的重点是兼容性。Vuex 4反对应用 Vue 3 开发,并且间接提供了和 Vuex 3 完全相同的 API,因而用户能够在Vue 3 我的项目中复用现有的 Vuex 代码。

相比 Vuex 3 版本。次要有如下重大扭转(其余的在上方链接中):

3.1 装置过程

Vuex 3Vue.use(Vuex)

Vuex 4则是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 外围模块导出了 createLogger 函数

import {createLogger} from 'vuex'

接下来咱们从源码的角度来看这些重大扭转

4. 从源码角度看 Vuex 4 重大变动

4.1 chrome 调试 Vuex 4 源码筹备工作

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

这种形式保留了 vuex4 仓库的 git 记录信息。更多 git subtree 应用形式能够查看这篇文章用 Git Subtree 在多个 Git 我的项目间双向同步子项目,附扼要使用手册。

作为读者敌人的你,只需克隆我的 Vuex 4 源码仓库 https://github.com/lxchuan12/vuex4-analysis.git 即可,也欢送 star 一下。

vuex/examples/webpack.config.js,加个devtool: 'source-map',这样就能开启sourcemap 调试源码了。

咱们应用我的项目中的购物车的例子调试,贯通全文。

git clone https://github.com/lxchuan12/vuex4-analysis.git
cd vuex
npm i
npm run dev
# 关上 http://localhost:8080/
# 抉择 composition  购物车的例子 shopping-cart
# 关上 http://localhost:8080/composition/shopping-cart/
# 按 F12 关上调试工具,source 面板 => page => webpack:// => .

据说一图胜千言,这时简略截个调试的图。

找到 createStore函数打上断点。

// 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()] : []})

找到 app.js 入口,在 app.use(store)app.mount('#app') 等打上断点。

// 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')

接下来,咱们从 createApp({})app.use(Store) 两个方面发散开来解说。

4.2 Vuex.createStore 函数

相比 Vuex 3 中,new Vuex.Store,其实是一样的。只不过为了和Vue 3 对立,Vuex 4 额定多了一个 createStore 函数。

export function createStore (options) {return new Store(options)
}
class Store{constructor (options = {}){
    // 省略若干代码...
    this._modules = new ModuleCollection(options)
    const state = this._modules.root.state
    resetStoreState(this, state)
    // 省略若干代码...
  }
}
function resetStoreState (store, state, hot) {
  // 省略若干代码...
  store._state = reactive({data: state})
  // 省略若干代码...
}

监测数据

Vuex 3 不同的是,监听数据不再是用 new Vue(),而是Vue 3 提供的 reactive 办法。

Vue.reactive 函数办法,本文就不开展解说了。因为开展来讲,又能够写篇新的文章了。只须要晓得次要性能是监测数据扭转,变更视图即可。

这也就算解答了结尾提出的第一个问题。

跟着断点咱们持续看 app.use() 办法,Vue提供的插件机制。

4.3 app.use() 办法

use做的事件说起来也算简略,把传递过去的插件增加插件汇合中,到避免反复。

执行插件,如果是对象,install是函数,则把参数 app 和其余参数传递给 install 函数执行。如果是函数间接执行。

// 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) {
          // 曾经有插件,并且 不是生产环境,报正告。if (installedPlugins.has(plugin)) {(process.env.NODE_ENV !== 'production') && warn(`Plugin has already been applied to target app.`);
            }
            // 插件的 install 是函数,则增加插件,并执行 install 函数
            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.`);
            }
            // 反对链式调用
            return app;
        },
        provide(){// 省略... 后文再讲}
      });
    }
}

下面代码中,断点这行plugin.install(app, ...options);

跟着断点走到下一步,install函数。

4.4 install 函数

export class Store{
    // 省略若干代码...
    install (app, injectKey) {
        // 为 composition API 中应用
        //  能够传入 injectKey  如果没传取默认的 storeKey 也就是 store
        app.provide(injectKey || storeKey, this)
        // 为 option API 中应用
        app.config.globalProperties.$store = this
    }
    // 省略若干代码...
}

Vuex4中的 install 函数绝对比 Vuex3 中简略了许多。
第一句是给 Composition API 提供的。注入到根实例对象中。
第二句则是为 option API 提供的。

接着断点这两句,按 F11 来看 app.provide 实现。

4.4.1 app.provide

简略来说就是给 contextprovides属性中加了store = Store 实例对象

provide(key, value) {
    // 如果曾经有值了正告
    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;
}

接着从上方代码中搜寻context,能够发现这一句代码:

const context = createAppContext();

接着咱们来看函数 createAppContext
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 文档利用配置(app.config)

4.4.2 app.config.globalProperties

app.config.globalProperties 官网文档

用法:

app.config.globalProperties.$store = {}

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

也就能解释为什么每个组件都能够应用 this.$store.xxx 拜访 vuex中的办法和属性了。

也就是说在 appContext.provides 中注入了一个 Store 实例对象。这时也就是相当于根组件实例和config 全局配置 globalProperties 中有了Store 实例对象

至此咱们就看完,createStore(store)app.use(store)两个API

app.provide 其实是用于 composition API 应用的。

但这只是文档中这样说的,为什么就每个组件实例都能拜访的呢,咱们持续深刻探索下原理。

接下来,咱们看下源码具体实现,为什么每个组件实例中都能获取到的。

这之前先来看下组合式 API 中,咱们如何应用Vuex4,这是线索。

4.5 composition API 中如何应用 Vuex 4

接着咱们找到如下文件,useStore是咱们断点的对象。

// 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;
    // 省略了若干代码
  }
}

接着断点按 F11,单步调试,会发现最终是应用了Vue.inject 办法。

4.5.1 Vuex.useStore 源码实现

// 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 源码实现

接着看 inject 函数,看着代码很多,其实原理很简略,就是要找到咱们用 provide 提供的值。

如果没有父级,也就是根实例,就取实例对象中的 vnode.appContext.provides
否则就取父级中的 instance.parent.provides 的值。

Vuex4 源码里则是:Store实例对象。

// 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
    // 如果是被一个函数式组件调用则取 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];
        }
        // 如果参数大于 1 个 第二个则是默认值,第三个参数是 true,并且第二个值是函数则执行函数。else if (arguments.length > 1) {return treatDefaultAsFactory && isFunction(defaultValue)
                ? defaultValue()
                : defaultValue;
        }
        // 正告没找到
        else if ((process.env.NODE_ENV !== 'production')) {warn(`injection "${String(key)}" not found.`);
        }
    }
    // 如果没有以后实例则阐明则报正告。// 也就是是说 inject 必须在 setup 中调用或者在函数式组件中应用
    else if ((process.env.NODE_ENV !== 'production')) {warn(`inject() can only be used inside setup() or functional components.`);
    }
}

接着咱们持续来看 inject 的绝对应的provide

4.5.3 Vue.provide 源码实现

provide函数作用其实也算简略,1、也就是给以后组件实例上的 provides 对象属性,增加键值对key/value

2、还有一个作用是当以后组件和父级组件的 provides 雷同时,在以后组件实例中的 provides 对象和父级,则建设链接,也就是原型[[prototype]],(__proto__)。

// 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;
    }
}

provide函数中的这段,可能不是那么好了解。

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

咱们来举个例子消化一下。

var currentInstance = {provides: { store: { __state: 'Store 实例'}  } };
var provides = currentInstance.provides;
// 这句是我手动加的,在后文中则是创立实例时就是写的同一个对象,当然就会相等了。var parentProvides = provides;
if(parentProvides === provides){provides =  currentInstance.provides = Object.create(parentProvides);
}

通过一次执行这个后,currentInstance 就变成了这样。

{
  provides: {
    // 能够包容其余属性,比方用户本人写的
    __proto__ : {store: { __state: 'Store 实例'}  }
  }
}

执行第二次时,currentInstance 则是:

{
  provides: {
    // 能够包容其余属性,比方用户本人写的
    __proto__: {
        // 能够包容其余属性,比方用户本人写的
        __proto__ : {store: { __state: 'Store 实例'}  }
    }
  }
}

以此类推,多执行 provide 几次,原型链就越长。

上文 injectprovide 函数中都有个变量 currentInstance 以后实例,那么以后实例对象是怎么来的呢。

为什么每个组件就能拜访到,依赖注入的思维。
有一个讨巧的办法,就是在文件 runtime-core.esm-bundler.js 中搜寻 provides,则能搜寻到createComponentInstance 函数

接下来咱们 createComponentInstance 函数如何创立组件实例。

4.6 createComponentInstance 创立组件实例

能够禁用其余断点,独自断点这里,
比方:const appContext = (parent ? parent.appContext : vnode.appContext) || emptyAppContext;
来看具体实现。

// 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;
}

断点时会发现,根组件实例时 vnode 曾经生成,至于是什么时候生成的,我整顿了下简化版。

// 把上文中的 appContext 赋值给了 `appContext`
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 其实就是建设原型关系。这时放一张图,一图胜千言。

出自黄轶老师拉勾专栏,本想本人画一张图,但感觉这张挺好的。

4.6.1 组件实例生成了,那怎么把它们联合呢

这时,也有一个讨巧的办法,在 runtime-core.esm-bundler.js 文件中,搜寻 provide(能够搜到如下代码:

这段代码其实看起来很简单的款式,实际上次要就是把用户在组件中写的 provides 对象或者函数返回值遍历, 生成相似这样的实例对象:

// 以后组件实例
{
  parent: '父级的实例',
  provides: {
    // 能够包容其余属性,比方用户本人写的
    __proto__: {
        // 能够包容其余属性,比方用户本人写的
        __proto__ : {store: { __state: 'Store 实例'}  }
    }
  }
}
// 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 (!asMixin && deferredProvide.length) {
      deferredProvide.forEach(provideOptions => {
          // 组件中写 provides 能够是对象或者是函数
          const provides = isFunction(provideOptions)
              ? provideOptions.call(publicThis)
              : provideOptions;
          Reflect.ownKeys(provides).forEach(key => {provide(key, provides[key]);
          });
      });
  }
  // ...
}

这样一来就从上到下 app.provide 提供的对象,被注入到每一个组件实例中了。同时组件自身提供的 provides 也被注入到实例中了。

接着咱们跟着我的项目来验证下,上文中的表述。翻看 Vue3 文档能够发现有一个 API 能够获取以后组件实例。

4.7 getCurrentInstance 获取以后实例对象

getCurrentInstance 反对拜访外部组件实例,用于高阶用法或库的开发。

import {getCurrentInstance} from 'vue'

const MyComponent = {setup() {const internalInstance = getCurrentInstance()

    internalInstance.appContext.config.globalProperties // 拜访 globalProperties
  }
}

晓得这个 API 后,咱们能够在购物车例子的代码中增加一些代码。便于咱们了解。

// vuex/examples/composition/shopping-cart/components/App.vue
import {getCurrentInstance, provide} from 'vue'
import {useStore} from 'vuex';
setup () {const store = useStore()
  provide('ruochuan12', '微信搜寻「若川视线」关注我,专一前端技术分享。')

  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, '介绍 -ProductList'); // 微信搜寻「若川视线」关注我,专一前端技术分享。// 若川退出的调试代码 ---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, '介绍 -ShoppingList'); // 微信搜寻「若川视线」关注我,专一前端技术分享。// 若川退出的调试代码 --start
}

在控制台输入这些值

AppCurrentInstance
AppCurrentInstance.provides
ShoppingCartCurrentInstance.parent === AppCurrentInstance // true
ShoppingCartCurrentInstance.provides
ShoppingCartStore === AppStore // true
ProductListStore === AppStore // true
AppStore // store 实例对象

看控制台截图输入的例子,其实跟上文写的相似。这时如果写了棘手本人注入了一个 provide(‘store’: ‘ 空字符串 ’),那么顺着原型链,必定是先找到用户写的store,这时 Vuex 无奈失常应用,就报错了。

当然 vuex4 提供了注入的 key 能够不是 store 的写法,这时就不和用户的抵触了。

export class Store{
    // 省略若干代码...
    install (app, injectKey) {
        // 为 composition API 中应用
        //  能够传入 injectKey  如果没传取默认的 storeKey 也就是 store
        app.provide(injectKey || storeKey, this)
        // 为 option API 中应用
        app.config.globalProperties.$store = this
    }
    // 省略若干代码...
}
export function useStore (key = null) {return inject(key !== null ? key : storeKey)
}

5. 解答下结尾提出的 5 个问题

对立解答下结尾提出的 5 个问题:

1、为什么批改了实例 store 里的属性,变更后会触发视图更新。

答:应用Vue 中的 reactive 办法监测数据变动的。

class Store{constructor (options = {}){
    // 省略若干代码...
    this._modules = new ModuleCollection(options)
    const state = this._modules.root.state
    resetStoreState(this, state)
    // 省略若干代码...
  }
}
function resetStoreState (store, state, hot) {
  // 省略若干代码...
  store._state = reactive({data: state})
  // 省略若干代码...
}

2、Vuex4作为 Vue 的插件如何实现和 Vue 联合的。

答:app.use(store) 时会执行 Store 中的 install 办法,一句是为 composition API 中应用,提供 Store 实例对象到根实例中。一句则是注入到根实例的全局属性中,为 option API 中应用。它们都会在组件生成时,注入到每个组件实例中。

export class Store{
    // 省略若干代码...
    install (app, injectKey) {
        // 为 composition API 中应用
        //  能够传入 injectKey  如果没传取默认的 storeKey 也就是 store
        app.provide(injectKey || storeKey, this)
        // 为 option API 中应用
        app.config.globalProperties.$store = this
    }
    // 省略若干代码...
}

3、provideinject的如何实现的,每个组件如何获取到组件实例中的 Store 的。

5、为什么在组件中写的 provide 提供的数据,能被子级组件获取到。

答:provide函数建设原型链辨别出组件实例用户本人写的属性和零碎注入的属性。inject函数则是通过原型链找父级实例中的 provides 对象中的属性。

// 有删减
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];
    }
}

也就是相似这样的实例:

// 以后组件实例
{
  parent: '父级的实例',
  provides: {
    // 能够包容其余属性,比方用户本人写的
    __proto__: {
        // 能够包容其余属性,比方用户本人写的
        __proto__ : {store: { __state: 'Store 实例'}  }
    }
  }
}

4、为什么每个组件对象里都有 Store 实例对象了(渲染组件对象过程)。

答:渲染生成组件实例时,调用 createComponentInstance,注入到组件实例的provides 中。

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. 你怎么晓得那么多的

答:因为社区有人写了 Vue4 源码文章。

6. 总结

本文次要讲述了 Vuex4Store实例注入到各个组件中的原理,开展讲述了 Vuex4 绝对与 Vuex3 装置形式的扭转 Vuex.createStoreapp.use(store),深刻源码剖析Vue.injectVue.provide 实现原理。

Vuex4 除了装置形式和监测数据变动形式应用了 Vue.reactive,其余根本和Vuex3.x 版本没什么区别。

最初回顾下文章结尾的图,能够说就是原型链的妙用。

是不是感觉恍然大悟。

Vuex其实也是 Vue 的一个插件,通晓了 Vuex 原理,对于本人给 Vue 写插件也是会熟能生巧。

如果读者敌人发现有不妥或可改善之处,再或者哪里没写明确的中央,欢送评论指出,也欢送加我微信 ruochuan12 交换。另外感觉写得不错,对您有些许帮忙,能够点赞、评论、转发分享,也是对我的一种反对,万分感激。如果能关注我的前端公众号:「若川视线」,就更好啦。

对于

你好,我是若川,微信搜寻「若川视线」关注我,专一前端技术分享,一个愿景是帮忙 5 年内前端开阔视野走向前列的公众号。欢送加我微信 ruochuan12,长期交流学习。
次要有以下系列文章:学习源码整体架构系列、年度总结、JS 根底系列

参考链接

官网文档 Provide / Inject

github 仓库 provide/inject 源码

github 仓库 provide/inject 测试

Vuex 4 官网中文文档

退出移动版