github:https://github.com/vuejs/vuex 下载调试
基础
示例
// src/index.js
import {createApp} from 'vue'
import App from './App.vue'
import store from './store'
// vue.use(store)插件的用法,会默认调用store中的install方法
createApp(App).use(store, 'my').mount('#app') // 给store加标识,用于创建多个store
// store/index.js
import {createStore} from 'vuex'
export default createStore({
state:{ count:0 },
getter:{ // vuex4没有实现计算属性
double(state){
return state.count * 2
}
},
mutations:{ // 可以更改状态,必须是同步更改的
add(state, payload){
state.count +=payload
}
},
actions: { // 可以调用其他action,或者调用mutation
asyncAdd({commit}, payload){
setTimeout(()=>{
commit('add', payload)
}, 1000)
}
},
modules: {
}
})
// App.vue
import {computed} from 'vue'
import {useStore} from 'vuex'
export default{
setup(){
const store = useStore('my')
function add(){
store.commit('add', 1)
}
function asyncAdd(){
store.dispatch('asyncAdd', 1)
}
return{
count: computed(()=> store.state.count),
double: computed(()=> store.getters.double),
add,
asyncAdd
}
}
}
分析
- 创建一个Store类
- 把Store类实例挂载到全局上下文ctx和全局provide上
createStore方法(new Store,把实例挂到全局上下文ctx)
useStore(inject(storeKey),把实例挂载到全局provide上,供子组件通信使用)
- 把各种方法挂载到Store实例上
// index.js
import { inject, reactive } from 'vue'
// 对象的数据拼接完再给fn函数
function forEachValue(obj, fn){
Object.keys(obj).forEach(key=>fn(obj[key], key))
}
// forEachValue({a:1, b:2}, function(value, key){
// console.log(value, key)
// })
const storeKey = 'store'
class Store{
constructor(options){
const store = this
// 拿到createStore的参数,分别挂载到当前组件的store上
store._state = reactive({data: options.state})
const _getters = options.getters
store.getters = {}
forEachValue(_getters, function(fn, key){
Object.defineProperty(store.getters, key, {
get:()=> fn(store.state)
})
})
store._mutations = Object.create(null)
store._actions = Object.create(null)
const _mutations = options.mutations
const _actions = options.actions
// 遍历对象,把对象都挂载到store._mutations对象上
// mutation相当于 store.commit('add', 1) commit执行mutations里的同步操作
forEachValue(_mutations, function(mutation, key){
store._mutations[key] = (payload)=>{
mutation.call(store, store.state, payload)
}
})
// action相当于 store.dispatch('asyncAdd', 1) dispatch执行actions里的同步操作
forEachValue(_actions, function(action, key){
store._actions[key] = (payload)=>{
action.call(store, store, payload)
}
})
}
get state(){
return this._state.data
}
install (app, injectKey) {
// 把store挂载到上下文的provide上,全局都可以访问
app.provide(injectKey || storeKey, this)
// 配置全局属性和方法,在页面可以直接使用$store.getters.xxx
app.config.globalProperties.$store = this
}
commit=(type, payload)=>{
this._mutations[type](payload)
}
dispatch=(type, payload)=>{
this._actions[type](payload)
}
}
function createStore (options) {
console.log(options)
return new Store(options)
}
function useStore (key = null) {
return inject(key !== null ? key : storeKey)
}
export {
useStore,
createStore
}
命名空间modules
示例
// store.js
import { createStore } from 'vuex'
const state = {
count: 0
}
const mutations = {
increment (state) {
state.count++
},
}
const actions = {
increment: ({ commit }) => commit('increment'),
}
const getters = {
evenOrOdd: state => state.count % 2 === 0 ? 'even' : 'odd'
}
const modules = {
aCount: {
namespaced: true,
state:{count:0},
mutations:{
increment(state, payload){
state.count +=payload
}
},
actions:{
increment: ({ commit }, payload) => commit('increment', payload),
}
},
bCount: {
namespaced: true,
state:{count:0},
mutations:{
increment(state, payload){
state.count +=payload
}
},
actions:{
increment: ({ commit }, payload) => commit('increment', payload),
}
}
}
export default createStore({
state,
getters,
actions,
mutations,
modules
})
分析
1)从结构可以看出,_modules为ModuleCollection类的实例,这个实例的root为Module类的实例
2)_children上挂载的是每个命名空间对应的Module实例
3)分别把各个命名空间的mutation、action挂载到Store的_mutations、_actions上,且key拼接上对应的命名空间名称
// src/module.js
import { forEachValue } from './utils'
class Module{
constructor(rawModule){
this._rawModule = rawModule,
this.state = rawModule.state,
this._children = {}
}
get namespaced () {
return !!this._rawModule.namespaced
}
addChild(key, module){
this._children[key] = module
}
getChild(key){
return this._children[key]
}
forEachChild (fn) {
forEachValue(this._children, fn)
}
forEachGetter (fn) {
if (this._rawModule.getters) {
forEachValue(this._rawModule.getters, fn)
}
}
forEachAction (fn) {
if (this._rawModule.actions) {
forEachValue(this._rawModule.actions, fn)
}
}
forEachMutation (fn) {
console.log('_rawModule',this._rawModule )
if (this._rawModule.mutations) {
forEachValue(this._rawModule.mutations, fn)
}
}
}
export class ModuleCollection{
constructor(rootModule){
this.root = null
// 把属性挂载到组件上
this.register(rootModule, [])
}
register(rawModule, path){
const newModule = new Module(rawModule)
// 根模块
if(path.length===0){
this.root = newModule
}else{ // [a] [b] [a,c] this.root._children.a._children.c
const parent = path.slice(0,-1).reduce((module, current)=>{
return module.getChild(current)
}, this.root)
parent.addChild(path[path.length-1], newModule)
}
if(rawModule.modules){
forEachValue(rawModule.modules, (rawChildModule, key) =>{
this.register(rawChildModule, path.concat(key))
})
}
}
getNamespace (path) {
let module = this.root
console.log('path', path)
return path.reduce((namespace, key) => {
module = module.getChild(key)
console.log('module', module)
return namespace + (module.namespaced ? key + '/' : '')
}, '')
}
}
// src/store-utils.js
export function installModule(store, rootState, path, module){
let isRoot = !path.length
const namespace = store._modules.getNamespace(path)
// 如果有命名空间
if (module.namespaced) {
store._modulesNamespaceMap[namespace] = module
}
if(!isRoot){
let parentState = path.slice(0,-1).reduce((state,key)=>state[key], rootState)
parentState[path[path.length-1]] = module.state
}
const local = module.context = makeLocalContext(store, namespace, path)
module.forEachChild((child, key) => {
installModule(store, rootState, path.concat(key), child)
})
/* 把_rawModule的mutations数据copy到组件实例的_mutations上
_mutations:{
// aCount/increment: [fn, fn] // 源码里是数组形式处理,这里只对应一个函数
aCount/increment: fn
}
*/
module.forEachMutation((mutation, key) => {
const namespacedType = namespace + key
store._mutations[namespacedType] = (payload)=>{
mutation.call(store, local.state, payload)
}
})
module.forEachAction((action, key) => {
const type = action.root ? key : namespace + key
const handler = action.handler || action
console.log('type', type)
console.log('handler', handler)
store._actions[type] = (payload)=>{
handler.call(store, local, payload)
}
})
module.forEachGetter((getter, key) => {
const namespacedType = namespace + key
store.getters = {}
Object.defineProperty(store.getters, namespacedType, {
get: namespace === ''
? () => getter(store.state) : () => getter(local.state)
})
})
}
function makeLocalContext (store, namespace, path) {
const noNamespace = namespace === ''
const local = {
dispatch: noNamespace ? store.dispatch : (_type, _payload, _options) => {
const args = unifyObjectStyle(_type, _payload, _options)
const { payload, options } = args
let { type } = args
if (!options || !options.root) {
type = namespace + type
}
return store.dispatch(type, payload)
},
commit: noNamespace ? store.commit : (_type, _payload, _options) => {
const args = unifyObjectStyle(_type, _payload, _options)
const { payload, options } = args
let { type } = args
if (!options || !options.root) {
type = namespace + type
}
store.commit(type, payload, options)
}
}
Object.defineProperties(local, {
getters: {
get: noNamespace
? () => store.getters
: () => makeLocalGetters(store, namespace)
},
state: {
get: () => getNestedState(store.state, path)
}
})
return local
}
function getNestedState (state, path) {
return path.reduce((state, key) => state[key], state)
}
function unifyObjectStyle (type, payload, options) {
if (type.type) {
options = payload
payload = type
type = type.type
}
return { type, payload, options }
}