IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> JavaScript知识库 -> Vuex4.0.0 源码解析 -> 正文阅读

[JavaScript知识库]Vuex4.0.0 源码解析

本文章基于以下版本撰写

在 vue 中使用 vuex

import { createApp } from 'vue'
import { createStore } from 'vuex'

// 创建一个新的 store 实例
const store = createStore({
  state () {
    return {
      count: 0
    }
  },
  mutations: {
    increment (state) {
      state.count++
    }
  }
})

const app = createApp({ /* 根组件 */ })

// 将 store 实例作为插件安装
app.use(store)

从 createStore 引入讲起

让我们先看一下 vuex/src 目录,引入 createStore 就是从 index.js 文件中引入
在这里插入图片描述

// src/index.js

??import { Store, createStore } from './store'
import { storeKey, useStore } from './injectKey'
import { mapState, mapMutations, mapGetters, mapActions, createNamespacedHelpers } from './helpers'
import { createLogger } from './plugins/logger'

export default {
  version: '__VERSION__',
  Store,
  storeKey,
  ??createStore,
  useStore,
  mapState,
  mapMutations,
  mapGetters,
  mapActions,
  createNamespacedHelpers,
  createLogger
}

export {
  Store,
  storeKey,
  createStore,
  useStore,
  mapState,
  mapMutations,
  mapGetters,
  mapActions,
  createNamespacedHelpers,
  createLogger
}

可以看到 createStore 是从 store 文件中引用,那么下面我们来看下 store 文件

// store.js

export function createStore(options) {
  return new Store(options)
}

直接返回了一个 Store 的实例,所以我们知道 Store 肯定是一个 class,下面我们来看下 Store 类

// store.js
export class Store {
  constructor(options = {}) {
    if (__DEV__) {
      assert(
        typeof Promise !== 'undefined',
        `vuex requires a Promise polyfill in this browser.`
      )
      assert(
        this instanceof Store,
        `store must be called with the new operator.`
      )
    }

    const { plugins = [], strict = false, devtools } = options

    // 存储内部状态
    this._committing = false
    this._actions = Object.create(null)
    this._actionSubscribers = []
    this._mutations = Object.create(null)
    this._wrappedGetters = Object.create(null)
    ??this._modules = new ModuleCollection(options)
    this._modulesNamespaceMap = Object.create(null)
    this._subscribers = []
    this._makeLocalGettersCache = Object.create(null)

    this._scope = null

    this._devtools = devtools

    const store = this
    // 在实例自身身上挂两个方法分别是原型上的dispatch 、commit方法,并将函数内部的this指针强行指向当前创建的store对象。
    const { dispatch, commit } = this
    this.dispatch = function boundDispatch(type, payload) {
      return dispatch.call(store, type, payload)
    }
    this.commit = function boundCommit(type, payload, options) {
      return commit.call(store, type, payload, options)
    }

    // strict mode
    this.strict = strict

    const state = this._modules.root.state

    // 初始化根Moudule
    ????installModule(this, state, [], this._modules.root)

    // 对State进行响应式处理
    ??????resetStoreState(this, state)

    // 应用插件
    plugins.forEach((plugin) => plugin(this))
  }
  
  ????????install (app, injectKey) {
    app.provide(injectKey || storeKey, this)
    app.config.globalProperties.$store = this
  }
}

我们先看下 constructor 部分,我们知道通过 new 命令生成对象实例时,自动调用该方法。
1. 所以首先是存储内部状态
2. 在实例自身身上挂两个方法分别是原型上的dispatch 、commit方法,并将函数内部的this指针强行指向当前创建的store对象。
3. 初始化根Moudule
4. 对State进行响应式处理
5. 应用插件
6. 全局注册

?? ModuleCollection 模块处理

我们从 this._modules = new ModuleCollection(options) 创建实例对象讲起,从字面意思可知是一个 module 收集的过程

// src/module/module-collection.js

export default class ModuleCollection {
  constructor (rawRootModule) {
    // register root module (Vuex.Store options)
    this.register([], rawRootModule, false)
  }

可以看到是调用 register 方法

// src/module/module-collection.js

  register (path, rawModule, runtime = true) {
    if (__DEV__) {
      assertRawModule(path, rawModule)
    }

    const newModule = new Module(rawModule, runtime)
    if (path.length === 0) {
      this.root = newModule
    } else {
      const parent = this.get(path.slice(0, -1))
      parent.addChild(path[path.length - 1], newModule)
    }

    // register nested modules
    if (rawModule.modules) {
      forEachValue(rawModule.modules, (rawChildModule, key) => {
        this.register(path.concat(key), rawChildModule, runtime)
      })
    }
  }

创建 Module 实例,先挂在到 root 属性上,然后看有没有 modules 属性,有的话就递归,给每个模块都创建一个 Module 实例对象,

// src/module/module.js

export default class Module {
  constructor (rawModule, runtime) {
    this.runtime = runtime
    // Store some children item
    this._children = Object.create(null)
    // Store the origin module object which passed by programmer
    this._rawModule = rawModule
    const rawState = rawModule.state

    // Store the origin module's state
    this.state = (typeof rawState === 'function' ? rawState() : rawState) || {}
  }
  }

这样每个模块就有自己单独的 state,实现独立管理自己模块的状态

???? 初始化根 Moudle installModule

// src/store-util.js

export function installModule (store, rootState, path, module, hot) {
  const isRoot = !path.length
  const namespace = store._modules.getNamespace(path)

  // register in namespace map
  ??if (module.namespaced) {
    if (store._modulesNamespaceMap[namespace] && __DEV__) {
      console.error(`[vuex] duplicate namespace ${namespace} for the namespaced module ${path.join('/')}`)
    }
    store._modulesNamespaceMap[namespace] = module
  }

  // set state
  ????if (!isRoot && !hot) {
    const parentState = getNestedState(rootState, path.slice(0, -1))
    const moduleName = path[path.length - 1]
    store._withCommit(() => {
      if (__DEV__) {
        if (moduleName in parentState) {
          console.warn(
            `[vuex] state field "${moduleName}" was overridden by a module with the same name at "${path.join('.')}"`
          )
        }
      }
      parentState[moduleName] = module.state
    })
  }

  ??????const local = module.context = makeLocalContext(store, namespace, path)

  ????????module.forEachMutation((mutation, key) => {
    const namespacedType = namespace + key
    registerMutation(store, namespacedType, mutation, local)
  })

  ????????module.forEachAction((action, key) => {
    const type = action.root ? key : namespace + key
    const handler = action.handler || action
    registerAction(store, type, handler, local)
  })

  ????????module.forEachGetter((getter, key) => {
    const namespacedType = namespace + key
    registerGetter(store, namespacedType, getter, local)
  })

  ??????????module.forEachChild((child, key) => {
    installModule(store, rootState, path.concat(key), child, hot)
  })
}

??:
若module.namespaced = true : 此Module将被加入store._modulesNamespaceMap内,其key为Module嵌套的路径。

????:
非root Module时:子Module.state注入到父节点的state对象里。

??????:
对store进行局部化,这里主要对module.namespaced= true 的module进行另外处理,其内部的成员都需要进行namespace路径处理处理。

????????:
对 mutation、action、getter 进行封装,放在 Store 对应的 _mutations、_actions、_wrappedGetters 里

??????????:
若当前module含有子module时,遍历当前model的_children属性,迭代执行installModule。

??????对State进行响应式处理 resetStoreState

// src/store-util.js

export function resetStoreState (store, state, hot) {
  const oldState = store._state
  const oldScope = store._scope

  // bind store public getters
  store.getters = {}
  // reset local getters cache
  store._makeLocalGettersCache = Object.create(null)
  const wrappedGetters = store._wrappedGetters
  const computedObj = {}
  const computedCache = {}

  // create a new effect scope and create computed object inside it to avoid
  // getters (computed) getting destroyed on component unmount.
  const scope = effectScope(true)

  ??scope.run(() => {
    forEachValue(wrappedGetters, (fn, key) => {
      // use computed to leverage its lazy-caching mechanism
      // direct inline function use will lead to closure preserving oldState.
      // using partial to return function with only arguments preserved in closure environment.
      computedObj[key] = partial(fn, store)
      computedCache[key] = computed(() => computedObj[key]())
      Object.defineProperty(store.getters, key, {
        get: () => computedCache[key].value,
        enumerable: true // for local getters
      })
    })
  })

  ????store._state = reactive({
    data: state
  })

  // register the newly created effect scope to the store so that we can
  // dispose the effects when this method runs again in the future.
  store._scope = scope

  // enable strict mode for new state
  if (store.strict) {
    enableStrictMode(store)
  }

  if (oldState) {
    if (hot) {
      // dispatch changes in all subscribed watchers
      // to force getter re-evaluation for hot reloading.
      store._withCommit(() => {
        oldState.data = null
      })
    }
  }

  // dispose previously registered effect scope if there is one.
  if (oldScope) {
    oldScope.stop()
  }
}

??:
将 getter 注册为计算属性

????:
让 state 变为响应式对象

???????? Store 全局注册

我们知道当我们应用插件 app.use(store) 时候,会自动调用 install 方法

4.0版本 vuex 使用的是 provide / inject 来实现全局注册,因为 vue3 已经不支持 $ api

  install (app, injectKey) {
    app.provide(injectKey || storeKey, this)
    app.config.globalProperties.$store = this
  }

除了通过 app.config.globalProperties 设置全局属性$store,还provide了一个storeKey,这显然是为 useStore() 做准备。

// src/injectKey.js

import { inject } from 'vue'

export const storeKey = 'store'

export function useStore (key = null) {
  return inject(key !== null ? key : storeKey)
}
  JavaScript知识库 最新文章
ES6的相关知识点
react 函数式组件 & react其他一些总结
Vue基础超详细
前端JS也可以连点成线(Vue中运用 AntVG6)
Vue事件处理的基本使用
Vue后台项目的记录 (一)
前后端分离vue跨域,devServer配置proxy代理
TypeScript
初识vuex
vue项目安装包指令收集
上一篇文章      下一篇文章      查看所有文章
加:2022-12-25 10:59:18  更:2022-12-25 11:02:02 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年4日历 -2024/4/26 5:00:04-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码