深海鱼的博客 深海鱼的博客
首页
  • 《ES6 教程》
  • 《TypeScript》
  • 《Vue》
  • 《React》
  • 《Git》
  • Javascript
  • CSS
手写系列
  • React项目实战
  • Vue3项目实战
  • 服务端项目实战
  • 鸿蒙项目实战
  • 小小实践
  • Vue全家桶

    • Vue2.0
    • Vue3.0
    • VueRouter
    • Vuex
  • React

    • React
  • Axios
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

深海鱼

尽人事,知天命,知行合一。
首页
  • 《ES6 教程》
  • 《TypeScript》
  • 《Vue》
  • 《React》
  • 《Git》
  • Javascript
  • CSS
手写系列
  • React项目实战
  • Vue3项目实战
  • 服务端项目实战
  • 鸿蒙项目实战
  • 小小实践
  • Vue全家桶

    • Vue2.0
    • Vue3.0
    • VueRouter
    • Vuex
  • React

    • React
  • Axios
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 导读
  • vue2

  • vuex

    • 前言
    • 调式环境准备
    • 源码基本结构与install函数
    • ModuleCollection与Module类
    • Store实例化
    • Store实例方法
      • 访问器属性
      • replaceState
      • commit
      • dispatch
      • subscribe 和 subscribeAction
      • watch
      • _withCommit
      • registerModule
      • unregisterModule
      • hasModule
      • hotUpdate
    • 辅助函数
    • 内置插件
    • 总结与常见问题
  • vue-router

  • vue3

  • react

  • Axios

  • 源码解读
  • vuex
深海鱼
2024-06-28
目录

Store实例方法

Vuex.Store实例为我们提供一些可以操作状态的方法,本篇文章就对这些方法逐个讲解。

# 访问器属性

get state() {
  return this._vm._data.$$state
}

// 根state不能直接修改,需要使用replaceState方法来替换
set state(v) {
  if (process.env.NODE_ENV !== 'production') {
    assert(false, `Use store.replaceState() to explicit replace store state.`)
  }
}
1
2
3
4
5
6
7
8
9
10

此段代码比较简单,当我们访问store.state时,实际上访问的是store._vm._data.$$state,其中$$state是根据被转化过的具有嵌套的结构的state对象。在Vuex中,根state不能直接修改,需要使用replaceState方法来替换。

# replaceState

方法声明: store.replaceState(state: Object)

方法定义:

replaceState(state) {
  this._withCommit(() => {
    this._vm._data.$$state = state
  })
}
1
2
3
4
5

用于替换store的根状态,这个通常用于状态合并或时光旅行调试。

# commit

commit(_type, _payload, _options) {
  // check object-style commit
  // 统一调用风格
  const {
    type,
    payload,
    options
  } = unifyObjectStyle(_type, _payload, _options)

  const mutation = { type, payload }
  const entry = this._mutations[type] // 获取对应的mutation处理函数
  if (!entry) { // 未设置的mutation
    if (process.env.NODE_ENV !== 'production') {
      console.error(`[vuex] unknown mutation type: ${type}`)
    }
    return
  }
  // 设置_committing为true
  this._withCommit(() => {
    // 遍历所有的mutation执行
    entry.forEach(function commitIterator(handler) {
      handler(payload)
    })
  })
  // 执行mutation订阅列表
  this._subscribers.forEach(sub => sub(mutation, this.state))

  // vue-devtools上silent选项废弃的提醒
  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'
    )
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

commit方法用于提交mutation。commit方法有两种调用方式

  • commit(type: string, payload?: any, options?: object)
  • commit(mutation: Object, options?: object)

两种调用方式都会调用unifyObjectStyle方法,将参数统一为{type: string, payload: any, options: object}。然后根据type获取对应的mutation处理函数,执行以下操作:

  • 如果函数不存在则输出警告信息
  • 如果存在,则将store._committing设置为true,遍历得到的mutation处理函数,传入payload执行(由上一篇文章分析,我们知道,mutation处理函数最终存放在一个数组里)
  • 遍历mutation订阅列表(store._subscribers),传入当前的mutation信息(type和payload)以及改变后的state执行

unifyObjectStyle定义和说明如下:

function unifyObjectStyle(type, payload, options) {
  // type是一个包含type属性的对象
  if (isObject(type) && type.type) {
    options = payload // 第二个参数作为options
    payload = type // 第一个参数作为完整的payload
    type = type.type // type.type做为type
  }

  // type只能是string类型
  if (process.env.NODE_ENV !== 'production') {
    assert(typeof type === 'string', `Expects string as the type, but found ${typeof type}.`)
  }

  // 返回规范化后的参数对象
  return { type, payload, options }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# dispatch

dispatch(_type, _payload) {
  // check object-style dispatch
  // 统一调用风格
  const {
    type,
    payload
  } = unifyObjectStyle(_type, _payload)

  const action = { type, payload }
  const entry = this._actions[type] // 获取action处理函数
  if (!entry) { // 未定义的action
    if (process.env.NODE_ENV !== 'production') {
      console.error(`[vuex] unknown action type: ${type}`)
    }
    return
  }

  // 执行action订阅列表
  this._actionSubscribers.forEach(sub => sub(action, this.state))

  // 多个action处理函数返回一个Promise,否则返回处理结果(也可能是Promise)
  return entry.length > 1
    ? Promise.all(entry.map(handler => handler(payload)))
    : entry[0](payload)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

dispatch用于分发action。dispatch方法同样有两种调用方式:

  • dispatch(type: string, payload?: any, options?: Object): Promise<any>
  • dispatch(action: Object, options?: Object): Promise<any>

参数的处理方式与commit的处理方式一致,根据type获取对应的action处理函数后,执行以下操作:

  • 如果函数不存在则输出警告信息
  • 遍历action订阅列表(store._actionSubscribers),传入当前的action信息(type和payload)以及改变后的state执行
  • 以payload为参数调用所有的action处理函数,如果处理函数的长度大于1则使用Promise.all组合所有的处理函数并返回,否则返回第一个处理函数的执行结果(也是Promise)

提示

在注册action时,所有的action已经被包装并转为了Promise,点击这里查看

# subscribe 和 subscribeAction

方法声明:

subscribe(handler: Function, options?: Object): Function subscribeAction(handler: Function, options?: Object): Function

subscribe和subscribeAction分别用于订阅mutation和action,通常是给Vuex插件使用的。

// 订阅mutation
subscribe(fn) {
  return genericSubscribe(fn, this._subscribers)
}

// 订阅actions
subscribeAction(fn) {
  return genericSubscribe(fn, this._actionSubscribers)
}

1
2
3
4
5
6
7
8
9
10

两个方法都调用了通用的订阅函数genericSubscribe,该函数定义如下:

// 通用的订阅函数
function genericSubscribe(fn, subs) {
  if (subs.indexOf(fn) < 0) { // 同一个订阅函数只订阅一次
    subs.push(fn)
  }
  // 返回一个取消订阅的新函数
  return () => {
    const i = subs.indexOf(fn)
    if (i > -1) {
      subs.splice(i, 1)
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13

函数很简单,就是给定的fn不在订阅列表subs中,则添加到订阅列表中,也就是说,同一个订阅函数只订阅一次。最后返回一个取消订阅的新函数,该函数会从订阅列表subs中移除给定的fn。

所以两个订阅方法,最终就是分别将订阅函数添加到_subscribers和_actionSubscribers中,并返回一个取消订阅的函数。

# watch

方法声明: watch(fn: Function, callback: Function, options?: Object): Function

watch方法用于响应式地侦听fn的返回值,当值改变时调用回调函数。

watch(getter, cb, options) {
  if (process.env.NODE_ENV !== 'production') { // 只接收函数
    assert(typeof getter === 'function', `store.watch only accepts a function.`)
  }
  return this._watcherVM.$watch(() => getter(this.state, this.getters), cb, options)
}
1
2
3
4
5
6

watch函数的第一个参数getter仅接收函数类型并分别以store的state作为其第一个参数,getter作为第二个参数。options选项则为传递给Vue的vm.$watch方法的选项参数。watch执行后会返回一个可以取消监听的函数。

# _withCommit

_withCommit是一个私有方法,用于修改store._committing的值,从而按约束来修改state。该外部不应该调用:

_withCommit(fn) {
  const committing = this._committing
  this._committing = true
  fn()
  this._committing = committing
}
1
2
3
4
5
6

其核心的逻辑就是执行fn函数前,先将_committing的值设置为true,执行完fn函数后,将_committing恢复为原来的值。

# registerModule

方法声明: registerModule(path: Array<string> | string, rawModule: Module, options?: Object)

registerModule方法用于动态注册一个模块,使用动态模块注册可以给Vuex带来更大的灵活性,一般Vuex插件就可以通过该方法来使用Vuex的状态管理,比如vuex-router-sync (opens new window)插件就是通过动态注册模块将vue-router和vuex结合在一起,实现应用的路由状态管理。其定义如下:

registerModule(path, rawModule, options = {}) {
  if (typeof path === 'string') path = [path]

  // 不能用于动态注册根模块
  if (process.env.NODE_ENV !== 'production') {
    assert(Array.isArray(path), `module path must be a string or an Array.`)
    assert(path.length > 0, 'cannot register the root module by using registerModule.')
  }

  // 动态注册模块
  this._modules.register(path, rawModule)
  // 安装模块
  installModule(this, this.state, path, this._modules.get(path), options.preserveState)

  // reset store to update getters...
  // 重置vm实例
  resetStoreVM(this, this.state)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

在注册动态模块时,首先会保证注册的不是根模块,即path.length > 0(因为Vuex是将根模块设置成不可变的),所以该方法只能用于注册子模块。注册步骤如下:

  • 将rawModule添加到_modules中,此过程从this._modules.root中查找到其父模块,然后添加到父模块的_children中,并添加到_rawModule中。
  • 安装当前模块,即注册mutations、actions和getters,安装时会递归安装子模块
  • 重置vm实例,即重新更新getters和state的响应式数据

与实例化Store时安装模块不同,注册模块时,此处调用installModule最后一个参数会设置为option.preserveState,该参数默认为false,当将其设置为true时表示保留之前的state。在installModule函数中有如下代码:

function installModule(store, rootState, path, module, hot) {
  // ....
  if (!isRoot && !hot) {  // 非根模块且非热更新时,设置模块状态
    const parentState = getNestedState(rootState, path.slice(0, -1))
    const moduleName = path[path.length - 1]
    store._withCommit(() => {
      Vue.set(parentState, moduleName, module.state)
    })
  }
  // ...
}

1
2
3
4
5
6
7
8
9
10
11
12

此时,action、mutation、getter将会被添加到store中而store.state将不会被替换。一般在服务端渲染中,某些需要保留旧state的场景就会将该值设置为true。

# unregisterModule

方法声明: unregisterModule(path: string | Array<string>)

unregisterModule方法用于卸载一个动态模块,其定义如下:

unregisterModule(path) {
  if (typeof path === 'string') path = [path]

  if (process.env.NODE_ENV !== 'production') {
    assert(Array.isArray(path), `module path must be a string or an Array.`)
  }

  this._modules.unregister(path) // 卸载
  this._withCommit(() => {
    const parentState = getNestedState(this.state, path.slice(0, -1))
    Vue.delete(parentState, path[path.length - 1])
  })
  // 重置vm实例
  resetStore(this)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

根据传入的path,找到_modules中的对应模块,然后调用_modules.unregister方法卸载该模块,同时会删除state中对应的属性。在调用this._modules.unregister(path)时会判断模块的runtime属性是否为true(动态注册),只有为true时才会执行卸载操作。

# hasModule

方法声明: hasModule(path: Array<string> | string): boolean

这个是Vuex 3.2.0新增的方法,用于判断是否注册了某个模块。通过查看3.2.0对应的源码,其定义如下:

// store.js
hasModule (path) {
  if (typeof path === 'string') path = [path]

  if (process.env.NODE_ENV !== 'production') {
    assert(Array.isArray(path), `module path must be a string or an Array.`)
  }

  return this._modules.isRegistered(path)
}
1
2
3
4
5
6
7
8
9
10
// module/module-collection.js
isRegistered (path) {
  const parent = this.get(path.slice(0, -1))
  const key = path[path.length - 1]

  return parent.hasChild(key)
}

1
2
3
4
5
6
7
8
// module/module.js
hasChild (key) {
  return key in this._children
}
1
2
3
4

代码非常简单,就是先根据给定的path,找到父模块,然后判断父模块的_children属性中是否有对应的子模块。

# hotUpdate

方法声明: hotUpdate(newOptions: Object)

hotUpdate方法用于热更新,其执行后会热替换新的action、mutation、module和getter。例子查看这里 (opens new window)。定义如下:

hotUpdate(newOptions) {
  this._modules.update(newOptions)
  resetStore(this, true)
}
1
2
3
4
  • 首先会执行this._modules.update(newOptions),此操作会递归更新模块,更新时只会更新actions、mutation和getters,不会更新state。
  • 然后调用resetStore方法,第二个参数为true会透传给installModule和resetStoreVm,从而会触发hot相关逻辑。

以上就是所有的Store实例方法,所有列表如下:

vuex-store-method

最近更新: 2024/07/01, 18:59
Store实例化
辅助函数

← Store实例化 辅助函数→

最近更新
01
Axios源码解读
07-29
02
基于React16的Webpack升级与构建速度优化
07-09
03
Vue-Router源码解读
07-09
更多文章>
Theme by Vdoing | Copyright © 2024-2024 深海鱼 | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式