preface
TypeScript 4.1 has released a new feature for TypeScript 4.1 called “string templates.” TypeScript 4.1 has a new feature for TypeScript 4.1 called “string templates.” .
This article uses this feature to simply implement Vuex’s dispatch string type inference in the case of modules nesting. Let’s take a look at the effect.
const store = Vuex({
mutations: {
root(){},},modules: {
cart: {
mutations: {
add() {},
remove(){},}},user: {
mutations: {
login(){},},modules: {
admin: {
mutations: {
login(){},},},},},},},})Copy the code
To do this, the optional action string type should be prompted at dispatch:
store.dispatch('root')
store.dispatch('cart/add')
store.dispatch('user/login')
store.dispatch('user/admin/login')
Copy the code
implementation
Defining function skeleton
First, define the Vuex function, and use two generics to derive mutations and modules backwards:
type Store<Mutations, Modules> = {
// This Action type will be implemented later
dispatch(action: Action<Mutations, Modules>): void
}
type VuexOptions<Mutations, Modules> = {
mutations: Mutations
modules: Modules
}
declare function Vuex<Mutations.Modules> (
options: VuexOptions<Mutations, Modules>
) :Store<Mutations.Modules>
Copy the code
To implement the Action
So the next key is to implement dispatch(action: Action
): Void in Action, our goal is to he concluded that as a ‘root’ | ‘cart/add’ | ‘user/login’ | ‘user/admin/login’ this type of joint, so that users in the call dispatch, You can do it intelligently.
In Action, keyof Mutations can be obtained simply first, because Mutations under root store do not need to do any stitching.
The main thing is that we need to use Modules as a generic, or correspondence structure:
modules: {
cart: {
mutations: {
add(){},remove(){}}},user: {
mutations: {
login(){}},modules: {
admin: {
mutations: {
login(){}}}}}}Copy the code
To get all the concatenated keys in modules.
Inference Modules Keys
Sync up with everyone in advance.
Modules
On behalf of{ cart: { modules: {} }, user: { modules: {} }
More than thisModule
Composite object structure.Module
Represents a single submodule, such ascart
。
using
type Values<Modules> = {
[K in keyof Modules]: Modules[K]
}[keyof Modules]
Copy the code
This way, you can easily expand all the value types in an object, such as
type Obj = {
a: 'foo'
b: 'bar'
}
type T = Values<Obj> // 'foo' | 'bar'
Copy the code
Since we want the key extracted from the cart and user values,
So using that knowledge, we’ll write GetModulesMutationKeys to get all the keys in Modules:
type GetModulesMutationKeys<Modules> = {
[K in keyof Modules]: GetModuleMutationKeys<Modules[K], K>
}[keyof Modules]
Copy the code
We first get all the keys using K in Keyof Modules, so we can get a single Module like CART and User, and pass it to GetModuleMutationKeys, K too. Because we need to concatenate the resulting type with keys like CART and User.
Infer individual Module Keys
Next we implement GetModuleMutationKeys to break down the requirements. First the individual Module looks like this:
cart: {
mutations: {
add(){},remove(){}}},Copy the code
So, after getting Mutations, we only need to join cart/add and CART /remove, so how to get Mutations in an object type?
We can infer from:
type GetMutations<Module> = Module extends { mutations: infer M } ? M : never
Copy the code
Then through keyof GetMutations < Module >, you can easily get the ‘add’ | ‘remove’ this type, we will implement a joining together the Key type, attention here with the TS 4.1 string template type
type AddPrefix<Prefix, Keys> = `${Prefix}/${Keys}`
Copy the code
Here will automatically spread out the joint types and distribution, ${} ‘cart’ / ${‘ add ‘|’ remove ‘} will be concluded into ‘cart/add’ | ‘cart/remove. But due to our incoming keyof GetMutations < Module > it also may be symbol | number type, so use Keys & string to choose one type string, This technique is also mentioned by Pops in Template String types MR:
Above, a keyof T & string intersection is required because keyof T could contain symbol types that cannot be transformed using template string types.
type AddPrefix<Prefix, Keys> = `${Prefix & string}/${Keys & string}`
Copy the code
Mutations
> can easily join the mutations under the CART Module using AddPrefix
>.
Infer nested Module Keys
There may be other Modules under the CART module, such as this:
cart: {
mutations: {
add(){},remove(){}}modules: {
subCart: {
mutations: {
add(){},}}}},Copy the code
GetModulesMutationKeys = ‘Keys’; GetModulesMutationKeys =’ Keys’; GetModulesMutationKeys = ‘Keys’;
type GetModuleMutationKeys<Module, Key> =
// add key/mutation
| AddPrefix<Key, keyof GetMutations<Module>>
// Here we extract keys from the child modules
| GetSubModuleKeys<Module, Key>
Copy the code
Extends is used to judge type structures, and the structure of modules that do not exist is directly returned never. Infer is used to extract the structure of modules. And concatenate the previous module’s key before the result just returned by GetModulesMutationKeys:
type GetSubModuleKeys<Module, Key> = Module extends { modules: infer SubModules }
? AddPrefix<Key, GetModulesMutationKeys<SubModules>>
: never
Copy the code
Taking the CART module as an example, break down the results for each tool type:
cart: {
mutations: {
add(){},remove(){}}modules: {
subCart: {
mutations: {
add(){},}}}},type GetModuleMutationKeys<Module, Key> =
// 'cart/add' | 'cart | remove'
AddPrefix<Key, keyof GetMutations<Module>> |
// 'cart/subCart/add'
GetSubModuleKeys<Module, Key>
type GetSubModuleKeys<Module, Key> = Module extends { modules: infer SubModules }
? AddPrefix<
// 'cart'
Key,
// 'subCart/add'
GetModulesMutationKeys<SubModules>
>
: never
Copy the code
In this way, infinite levels of Modules are concatenated by clever recursion.
The complete code
type GetMutations<Module> = Module extends { mutations: infer M } ? M : never
type AddPrefix<Prefix, Keys> = `${Prefix & string}/${Keys & string}`
type GetSubModuleKeys<Module, Key> = Module extends { modules: infer SubModules }
? AddPrefix<Key, GetModulesMutationKeys<SubModules>>
: never
type GetModuleMutationKeys<Module, Key> = AddPrefix<Key, keyof GetMutations<Module>> | GetSubModuleKeys<Module, Key>
type GetModulesMutationKeys<Modules> = {
[K in keyof Modules]: GetModuleMutationKeys<Modules[K], K>
}[keyof Modules]
type Action<Mutations, Modules> = keyof Mutations | GetModulesMutationKeys<Modules>
type Store<Mutations, Modules> = {
dispatch(action: Action<Mutations, Modules>): void
}
type VuexOptions<Mutations, Modules> = {
mutations: Mutations,
modules: Modules
}
declare function Vuex<Mutations.Modules> (options: VuexOptions<Mutations, Modules>) :Store<Mutations.Modules>
const store = Vuex({ mutations: { root() { }, }, modules: { cart: { mutations: { add() { }, remove() { } } }, user: { mutations: { login() { } }, modules: { admin: { mutations: { login() { } }, } } } } })
store.dispatch("root")
store.dispatch("cart/add")
store.dispatch("user/login")
store.dispatch("user/admin/login")
Copy the code
Head over to TypeScript Playground for a taste.
conclusion
This new feature opens up endless possibilities for the authors of TS library development. Some use it to implement URL Parser and HTML Parser, some use it to implement JSON parse, and some even use it to implement simple regex. This feature allows fans of type gymnastics and framework library authors can further display their skills, expect them to write a more powerful type library to facilitate business development of children’s shoes ~
Thank you for your
Pay attention to the public account “front-end from advanced to admission”, pay attention to the first-hand dry goods information, but also get “advanced front-end advanced guide” and “front-end algorithm zero-based advanced guide”.