Skip to content

Commit

Permalink
feat: 调整 vuex 增加 helpers 概念
Browse files Browse the repository at this point in the history
  • Loading branch information
imsunhao committed Jul 24, 2019
1 parent 90b9e93 commit 4c9aadd
Show file tree
Hide file tree
Showing 7 changed files with 198 additions and 127 deletions.
66 changes: 30 additions & 36 deletions doc/@types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,58 +35,52 @@ declare namespace Doc {
*/
interface state {
/**
* 全局
* 是否 使用 移动设备 访问
* * 来自服务器端
* * 这是一个例子
*/
global: {
/**
* 是否 使用 移动设备 访问
* * 来自服务器端
* * 这是一个例子
*/
isMobile: boolean
isMobile: boolean

/**
* 是否 使用 移动设备 访问
* * 来自 中间件-服务器端
* * 这是一个例子
*/
hello: string
/**
* 是否 使用 移动设备 访问
* * 来自 中间件-服务器端
* * 这是一个例子
*/
hello: string

/**
* 测试 热加载 vuex
* * 来自 客户端
* * 这是一个例子
*/
testHotLoadingVuex: number
/**
* 测试 热加载 vuex
* * 来自 客户端
* * 这是一个例子
*/
testHotLoadingVuex: number

/**
* 初始化 跳转访问 URL
* * 来自服务器端
* * 这是一个例子
*/
initialReplaceStateUrl: string
}
/**
* 初始化 跳转访问 URL
* * 来自服务器端
* * 这是一个例子
*/
initialReplaceStateUrl: string

/**
* 编辑器
*/
editor: {
editor?: {
test: string
deepTest: {
test1: string
test2: number
}
}
}

/**
* vuex Mutation-Payload-tree
*/
interface MutationPayloads {
/**
* 全局
*/
global: {
SET_IS_MOBILE: boolean
SET_HELLO: { hello: string }
SET_testHotLoadingVuex: { number: number }
}
SET_IS_MOBILE: boolean
SET_HELLO: { hello: string }
SET_testHotLoadingVuex: { number: number }

/**
* 编辑器
Expand Down
2 changes: 1 addition & 1 deletion doc/src/envs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export interface HostGlobal extends Window {
/**
* vuex 实例
*/
store: Store<Tstore.state['global']>
store: Store<Tstore.state>

/**
* vue router 实例
Expand Down
27 changes: 27 additions & 0 deletions doc/src/store/helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { Tstore } from '@types'
import { makeWrapper } from 'src/store/utils'

export const globalHelper = makeWrapper<Tstore.state>()
export const editorHelper = makeWrapper<Tstore.state['editor']>('editor')

/**
* 例子 1
*/
// const store = this.store

// const getState = globalHelper.createGetState()

// const deepTest = getState(store, 'editor', 'deepTest')
// const test1 = getState(store, 'editor', 'deepTest', 'test1')
// const test2 = getState(store, 'editor', 'deepTest').test2

/**
* 例子 2
*/
// const store = this.store
// const getEditorState = editorHelper.createGetState()

// const deepTest = getEditorState(store, 'deepTest')
// const test1 = getEditorState(store, 'deepTest', 'test1')
// const test2 = getEditorState(store, 'deepTest').test2

88 changes: 6 additions & 82 deletions doc/src/store/index.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,16 @@
import { Tstore } from '@types'

import Vue from 'vue'
import Vuex, { ActionContext, Store } from 'vuex'
import Vuex from 'vuex'
import actions from './actions'
import mutations from './mutations'
import mutations, { commit, getState } from './mutations'
import getters from './getters'
import state from './state'

import { Tstore } from '@types'

Vue.use(Vuex)

function isStore(context: Store<any>) {
return 'strict' in context
}

export function createStore() {
return new Vuex.Store<Tstore.state['global']>({
return new Vuex.Store<Tstore.state>({
state: state(),
actions,
mutations,
Expand All @@ -23,76 +19,4 @@ export function createStore() {
})
}

type StoreTypeBounds = { global: any }

function createGetState<T extends StoreTypeBounds>() {
function getState<K extends keyof T['global']>(
context: Store<any>,
state: K,
): T['global'][K]
function getState<NS extends keyof T, K extends keyof T[NS]>(
context: Store<any>,
namespace: NS,
state: K,
): T[NS][K]
function getState(context: Store<any>, ...args: any[]): void {
let namespace: string, state: string
if (args.length === 2) {
namespace = args[0]
state = args[1]
} else if (args.length === 1) {
namespace = 'global'
state = args[0]
}

if (namespace !== 'global') {
return context.state[namespace][state]
}
return context.state[state]
}

return getState
}

export const getState = createGetState<Tstore.state>()


function createCommit<T extends StoreTypeBounds>() {
function commit<K extends keyof T['global']>(
context: Store<any>,
mutation: K,
payload: T['global'][K],
): void
function commit<NS extends keyof T, K extends keyof T[NS]>(
context: Store<any>,
namespace: NS,
mutation: K,
payload: T[NS][K],
): void
function commit(context: Store<any>, ...args: any[]): void {
let namespace: string, mutation: string, payload: any
if (args.length === 3) {
namespace = args[0]
mutation = args[1]
payload = args[2]
} else if (args.length === 2) {
namespace = 'global'
mutation = args[0]
payload = args[1]
}

if (namespace !== 'global' && isStore(context)) {
mutation = `${namespace}/${mutation}`
}
return context.commit(mutation, payload)
}

return commit
}

export const commit = createCommit<Tstore.MutationPayloads>()

// const store = this.store

// commit(store, 'SET_IS_MOBILE', false)
// commit(store, 'editor', 'test', undefined)
export { commit, getState }
22 changes: 15 additions & 7 deletions doc/src/store/mutations.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,24 @@
import { Tstore } from "@types"
import { globalHelper } from 'src/store/helpers'

export const mutations: Tstore.createUtils.ModuleMutations<Tstore.state['global'], Tstore.MutationPayloads['global']> = {
SET_IS_MOBILE: (state, isMobile) => {
export const mutations = globalHelper.makeMutations({
SET_IS_MOBILE: (state, isMobile: boolean) => {
console.log('SET_IS_MOBILE', isMobile)
state.isMobile = isMobile
},
SET_HELLO: (state, { hello }) => {
SET_HELLO: (state, { hello }: { hello: string }) => {
state.hello = hello
},
SET_testHotLoadingVuex: (state, { number }) => {
SET_testHotLoadingVuex: (state, { number }: { number: number }) => {
state.testHotLoadingVuex += number * 1
},
}
})

export default mutations
export default mutations

export const commit = globalHelper.createCommit<typeof mutations>()
export const getState = globalHelper.createGetState()

// const store = this.store

// commit(store, 'SET_IS_MOBILE', true)
// commit(store, 'editor', 'test', undefined)
2 changes: 1 addition & 1 deletion doc/src/store/state.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Tstore } from '@types'

export default function state(): Tstore.state['global'] {
export default function state(): Tstore.state {
return {
isMobile: false,
hello: '',
Expand Down
118 changes: 118 additions & 0 deletions doc/src/store/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import { Tstore } from '@types'
import { ActionContext, Store, MutationTree } from 'vuex'

export type SniffMutationPayload<T> = T extends (state: any, payload: infer P) => any ? P: unknown
export type SniffMutationPayloadTree<S, M extends MutationTree<S>> = {
[K in keyof M]: SniffMutationPayload<M[K]>
}

export function makeWrapper<T>(namespace: keyof Tstore.state = ('' as any)) {
function makeMutations<M extends MutationTree<T>>(mutationTree: M) {
return mutationTree
}

function createCommit<M extends MutationTree<T>>() {
type MutationPayloadTree = SniffMutationPayloadTree<T, M>
function commit<M extends keyof MutationPayloadTree>(
context: Store<any>,
mutation: M,
payload: MutationPayloadTree[M],
): void
function commit<P extends keyof MutationPayloadTree, M extends keyof MutationPayloadTree[P]>(
context: Store<any>,
path: P,
mutation: M,
payload: MutationPayloadTree[P][M],
): void
function commit<P extends keyof MutationPayloadTree, P1 extends keyof MutationPayloadTree[P], M extends keyof MutationPayloadTree[P][P1]>(
context: Store<any>,
path: P,
path1: P1,
mutation: M,
payload: MutationPayloadTree[P][P1][M],
): void
function commit<P extends keyof MutationPayloadTree, P1 extends keyof MutationPayloadTree[P], P2 extends keyof MutationPayloadTree[P][P1], M extends keyof MutationPayloadTree[P][P1][P2]>(
context: Store<any>,
path: P,
path1: P1,
path2: P2,
mutation: M,
payload: MutationPayloadTree[P][P1][P2][M],
): void
function commit(context: Store<any>, ...args: any[]) {
if (!checkStore(context)) return
if (args.length < 2) {
console.error('commit args.length must > 2')
return
}
checkNamespace(namespace, args)
const payload = args.pop()
const paths = args.join('/')
return context.commit(paths, payload)
}

return commit
}

function createGetState() {
function getState<K extends keyof T>(
context: Store<any>,
stateKey: K,
): T[K]
function getState<K extends keyof T, K1 extends keyof T[K]>(
context: Store<any>,
stateKey: K,
state1Key: K1,
): T[K][K1]
function getState<K extends keyof T, K1 extends keyof T[K], K2 extends keyof T[K][K1]>(
context: Store<any>,
stateKey: K,
state1Key: K1,
state2Key: K2,
): T[K][K1][K2]
function getState<K extends keyof T, K1 extends keyof T[K], K2 extends keyof T[K][K1], K3 extends keyof T[K][K1][K2]>(
context: Store<any>,
stateKey: K,
state1Key: K1,
state2Key: K2,
state3Key: K3,
): T[K][K1][K2][K3]
function getState(context: Store<any>, ...args: string[]) {
if (!checkStore(context)) return
checkNamespace(namespace, args)
let result
for (let index = 0; index < args.length; index++) {
const key = args[index];
if (!result) result = context.state[key]
else result = result[key]
if (!result) return
}
return result
}
return getState
}

return {
makeMutations,
createCommit,
createGetState,
}
}

function isStore(context: Store<any>) {
return 'strict' in context
}

function checkStore(context: Store<any>) {
if (!isStore(context)) {
console.error('checkStore context is not a vuex store!', context)
return false
}
return true
}

function checkNamespace(namespace: keyof Tstore.state, args: any[]) {
if (namespace) {
args.unshift(namespace)
}
}

0 comments on commit 4c9aadd

Please sign in to comment.