> : {
[_ in CombineStringKey]:T[K]
}
}[StringKeyof]
```
我们将 `ChainKeys` 函数拆开来看,首先是它的参数,接受两个类型参数,第一个是类型T,第二个则是用于拼接的**前缀字符串**,其值可选。我们使用 `in` 语法遍历 `T` 的所有 key 值,也就是 `[K in StringKeyof]`,这里我们使用了前面封装好的方法 `StringKeyof` 来代替 `keyof` 关键字。对对应的 key 的值,也就是 `T[K]`,我们使用 `extends` 进行前置判断,如果 `T[K]` 是一个**复杂类型**,我们则将当前的字符串前缀 `P` 和当前的 `K` 进行拼接,递归调用 `ChainKeys` 函数进行处理。如果 `T[K]` 为**基本类型**,我们直接使用 `Record` 方法组装拼接好的key(`CombineStringKey`)和value(`T[K]`),为了方便在编辑器里面查看结果,这里将`Record, T[K]>` 替换为 `{ [_ in CombineStringKey]:T[K] }`。最后将所有结果打平成联合类型,也就是最后的一个取值操作:`{...}[StringKeyof]`,这和 `[1,2,3][number] // 3 | 1 | 2` 的处理方式一致。
接下来我们在编辑器中查看一下调用结果:

如红框中所示,发现链式key已经和其类型一一对应起来了。
但是这还有一些问题:
一、`"b"` 以及 `"b.e"` 没有出现在枚举当中。
二、返回结果是一个**联合类型**,如何把这个结果运用到函数当中呢?
第一个问题其实很好解决,当我们在递归处理的同时,将当前的链式key与对应值也进行处理就可以,即:
```ts
type ChainKeys = {
[K in StringKeyof]: T[K] extends Record
? Record, T[K]> & ChainKeys>
: {
[_ in CombineStringKey]:T[K]
}
}[StringKeyof]
```
同样为了方便编辑器中查看结果我们把 `Record, T[K]>` 替换成 `{ [_ in CombineStringKey]:T[K] }`:

可以看到 `b` 以及 `"b.e"` 这样的key都被取出来并放到了结果当中。其实这里还是有一些问题,如果你实际去代码编辑器里面查看结果的话,会发现有很多重复的 `b` ,这是因为后续使用交叉类型进行拼接的 `ChainKeys` 的返回值是一个联合类型,如此一来,`b` 会被分配到联合类型的**每一项**上,在后面我们会解决这个问题。
第二个问题是结果为联合类型,我们如何才能将其运用至函数当中呢?
大家都知道,对联合类型使用 `keyof` 操作,返回的类型为 `never`,即无法取出联合类型的所有key值:
```ts
type T = { a: number } | { b: string }
type K = keyof T // never
// 如何使K的类型为 a | b 呢?
```
这里我们引入另一个工具函数:`UnionToIntersection`,顾名思义,该函数的作用就是将联合类型转换为交叉类型,至于**为什么**它能将联合类型转换为交叉类型,我们后面会讲:
```ts
type UnionToIntersection = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never;
```
我们将其运用到ChainKeys的推导当中去:
```ts
type ChainKeys = UnionToIntersection<{
[K in StringKeyof]: T[K] extends Record
? Record, T[K]> & ChainKeys>
: {
[_ in CombineStringKey]:T[K]
}
}[StringKeyof]>
```
再经过一些润色得到一个比较精简的函数:
```ts
type ChainKeys = UnionToIntersection<{
[K in StringKeyof]: Record, T[K]> & (T[K] extends Record ? ChainKeys> : {})
}[StringKeyof]>
```
尝试调用该方法最终得到的结果为:

最终,我们以此为基础,编写 `getValueByPath` 函数的定义:
```ts
declare function getValueByPath, K extends keyof ChainKeys>(object: T, prop: K): ChainKeys[K]
let a = getValueByPath({ a: { b: { c: 1 } } }, 'a') // { b: { c: number } }
let b = getValueByPath({ a: { b: { c: 1 } } }, 'a.b') // { c: number }
let c = getValueByPath({ a: { b: { c: 1 } } }, 'a.b.c') // number
```
到此,我们算是完成了 `getValueByPath` 函数的类型定义以及推导。
## 深层次Actions的dispatch函数TS实现
其实在推导 `getValueByPath` 的过程中,我们已经基本上完成了所有实现该 `dispatch` 所需要的条件,与 `getValueByPath` 不同的是,我们只需要遍历出函数所对应的链式key即可,改写一下 `ChainKeys` 方法:
```ts
interface DeeperActions {
[k: string]: ((...args: any[]) => any) | DeeperActions
}
type ActionChainKeys = UnionToIntersection<{
[K in StringKeyof]: T[K] extends DeeperActions
? ActionChainKeys>
: Record, T[K]>
}[StringKeyof]>
```
我们除去了对对象的混入,同时将判断类型改为了 `DeeperActions` ,以此来判断该 `action` 对象是否有更深层次的actions。
调用结果:

再看我们最开始提出的问题:
> 编写一个 dispatch 方法,使得:
>
> `let result = dispatch('deeperActions.anotherAction') // 返回类型为: Promise`
>
> `let final = dispatch('deeperActions.otherActions.finalAction', 'hello world') // 返回类型为:Promise`
我们开始定义 `dispatch` 方法:
```ts
declare function dispatch(key: K, ...args: Parameters): ReturnType
```
这里我们分别使用了 `Parameters` 和 `ReturnType` 函数来获取对应 `action` 的参数以及返回值,用来填充至 `dispatch` 方法中,实际编写调用代码:


考虑到我们的 `actions` 无论如何都会返回一个 `Promise`,上述定义其实还是稍微有一些问题,但是改动起来也很方便,只需要在 `ReturnType` 外层使用 `Promise` 包装一下就可以了:
```ts
type Promisify = T extends Promise ? T : Promise
declare function dispatch(key: K, ...args: Parameters): Promisify>
let result = dispatch('deeperActions.anotherAction') // Promise
let final = dispatch('deeperActions.otherActions.finalAction', 'hello world') // Promise
```
至此,我们 `dispatch` 的TS实现也已经完成。
最终结果:

* 若图片加载失败请访问: http://cdn.qiniu.archerk.com.cn/QQ20210128-162139-HD.gif
## 结尾
其实 `getValueByPath` 这一部分的内容是有一些缺陷的,因为TS本身**限制了递归的次数**,当数据层级达到一定程度时,上文的推导会失效。在Mpx的建设中我们还对此做了一些优化,尽可能的**减少**了递归次数。
这里也给出**另一个方案**做链式key推导,这种方式**性能**上会好很多,因为不用解析出所有的链式key,而是根据链式key**反向解析**前面的对象,因此该方案可以解析出正确的类型,但是会丧失一部分代码提示:
```ts
type CutChainKeys = T extends `${infer A}.${infer B}` ? [A, B] : [T, never]
type GetValueByPath, K extends string> = CutChainKeys extends [string, never]
? T[CutChainKeys