`” 那我就返回类型 `P`,否则返回 `never`”,这句话用 TS 描述就是:`T extends [infer P, ...infer Rest] ? P : never`。
+
+### [Length of Tuple](https://github.com/type-challenges/type-challenges/blob/main/questions/00018-easy-tuple-length/README.md)
+
+实现类型 `Length : P
+ : never
+```
+
+如果 `Promise ` 取到的 `P` 还形如 `Promise `。这里提到了递归,也就是 TS 类型处理可以是递归的,所以才有了后面版本做尾递归优化。
+
+### [If](https://github.com/type-challenges/type-challenges/blob/main/questions/00268-easy-if/README.md)
+
+实现类型 `If `,将两个数组类型连起来:
+
+```ts
+type Result = Concat<[1], [2]> // expected to be [1, 2]
+```
+
+由于 TS 支持数组解构语法,所以可以大胆的尝试这么写:
+
+```ts
+type Concat = [...P, ...Q]
+```
+
+考虑到 `Concat` 函数应该也能接收非数组类型,所以做一个判断,为了方便书写,把 `extends` 从泛型定义位置挪到 TS 类型推断的运行时:
+
+```ts
+// 本题答案
+type Concat = [
+ ...P extends any[] ? P : [P],
+ ...Q extends any[] ? Q : [Q],
+]
+```
+
+解决这题需要信念,相信 TS 可以像 JS 一样写逻辑。这些能力都是版本升级时渐进式提供的,所以需要不断阅读最新 TS 特性,快速将其理解为固化知识,其实还是有一定难度的。
+
+### [Includes](https://github.com/type-challenges/type-challenges/blob/main/questions/00898-easy-includes/README.md)
+
+用类型系统实现 `Includes `,将对象 `P` 中类型为 `Q` 的 key 保留:
+
+```ts
+type OnlyBoolean = PickByType<
+ {
+ name: string
+ count: number
+ isReadonly: boolean
+ isEnable: boolean
+ },
+ boolean
+> // { isReadonly: boolean; isEnable: boolean; }
+```
+
+本题很简单,因为之前碰到 Remove Index Signature 题目时,我们用了 `K in keyof P as xxx` 来对 Key 位置进行进一步判断,所以只要 `P[K] extends Q` 就保留,否则返回 `never` 即可:
+
+```ts
+// 本题答案
+type PickByType = {
+ [K in keyof P as P[K] extends Q ? K : never]: P[K]
+}
+```
+
+### [StartsWith](https://github.com/type-challenges/type-challenges/blob/main/questions/02688-medium-startswith/README.md)
+
+实现 `StartsWith
+
+> 版权声明:自由转载-非商用-非衍生-保持署名([创意共享 3.0 许可证](https://creativecommons.org/licenses/by-nc-nd/3.0/deed.zh))
+
+
diff --git "a/TS \347\261\273\345\236\213\344\275\223\346\223\215/244.\347\262\276\350\257\273\343\200\212Get return type, Omit, ReadOnly...\343\200\213.md" "b/TS \347\261\273\345\236\213\344\275\223\346\223\215/244.\347\262\276\350\257\273\343\200\212Get return type, Omit, ReadOnly...\343\200\213.md"
new file mode 100644
index 00000000..e0a92efa
--- /dev/null
+++ "b/TS \347\261\273\345\236\213\344\275\223\346\223\215/244.\347\262\276\350\257\273\343\200\212Get return type, Omit, ReadOnly...\343\200\213.md"
@@ -0,0 +1,354 @@
+解决 TS 问题的最好办法就是多练,这次解读 [type-challenges](https://github.com/type-challenges/type-challenges) Medium 难度 1~8 题。
+
+## 精读
+
+### [Get Return Type](https://github.com/type-challenges/type-challenges/blob/main/questions/00002-medium-return-type/README.md)
+
+实现非常经典的 `ReturnType
+
+> 版权声明:自由转载-非商用-非衍生-保持署名([创意共享 3.0 许可证](https://creativecommons.org/licenses/by-nc-nd/3.0/deed.zh))
+
+
diff --git "a/TS \347\261\273\345\236\213\344\275\223\346\223\215/245.\347\262\276\350\257\273\343\200\212Promise.all, Replace, Type Lookup...\343\200\213.md" "b/TS \347\261\273\345\236\213\344\275\223\346\223\215/245.\347\262\276\350\257\273\343\200\212Promise.all, Replace, Type Lookup...\343\200\213.md"
new file mode 100644
index 00000000..6569e1f3
--- /dev/null
+++ "b/TS \347\261\273\345\236\213\344\275\223\346\223\215/245.\347\262\276\350\257\273\343\200\212Promise.all, Replace, Type Lookup...\343\200\213.md"
@@ -0,0 +1,206 @@
+解决 TS 问题的最好办法就是多练,这次解读 [type-challenges](https://github.com/type-challenges/type-challenges) Medium 难度 9~16 题。
+
+## 精读
+
+### [Promise.all](https://github.com/type-challenges/type-challenges/blob/main/questions/00020-medium-promise-all/README.md)
+
+实现函数 `PromiseAll`,输入 PromiseLike,输出 `Promise
`,将字符串 `From` 替换为 `To`:
+
+```ts
+type replaced = Replace<'types are fun!', 'fun', 'awesome'> // expected to be 'types are awesome!'
+```
+
+把 `From` 夹在字符串中间,前后用两个 `infer` 推导,最后输出时前后不变,把 `From` 换成 `To` 就行了:
+
+```ts
+// 本题答案
+type Replace =
+ S extends `${infer A}${From}${infer B}` ? `${A}${To}${B}` : S
+```
+
+### [ReplaceAll](https://github.com/type-challenges/type-challenges/blob/main/questions/00119-medium-replaceall/README.md)
+
+实现 `ReplaceAll`,将字符串 `From` 替换为 `To`:
+
+```ts
+type replaced = ReplaceAll<'t y p e s', ' ', ''> // expected to be 'types'
+```
+
+该题与上题不同之处在于替换全部,解法肯定是递归,关键是何时递归的判断条件是什么。经过一番思考,如果 `infer From` 能匹配到不就说明还可以递归吗?所以加一层三元判断 `From extends ''` 即可:
+
+```ts
+// 本题答案
+type ReplaceAll =
+ From extends '' ? S : (
+ S extends `${infer A}${From}${infer B}` ? (
+ From extends '' ? `${A}${To}${B}` : `${A}${To}${ReplaceAll}`
+ ) : S
+ )
+```
+
+补充一些细节:
+
+1. 如果替换文本为空字符串需要跳过,否则会匹配第二个任意字符。
+2. 为了防止替换完后结果可以再度匹配,对递归形式做一下调整,下次递归直接从剩余部分开始。
+
+### [Append Argument](https://github.com/type-challenges/type-challenges/blob/main/questions/00191-medium-append-argument/README.md)
+
+实现类型 `AppendArgument
+
+> 版权声明:自由转载-非商用-非衍生-保持署名([创意共享 3.0 许可证](https://creativecommons.org/licenses/by-nc-nd/3.0/deed.zh))
+
+
diff --git "a/TS \347\261\273\345\236\213\344\275\223\346\223\215/246.\347\262\276\350\257\273\343\200\212Permutation, Flatten, Absolute...\343\200\213.md" "b/TS \347\261\273\345\236\213\344\275\223\346\223\215/246.\347\262\276\350\257\273\343\200\212Permutation, Flatten, Absolute...\343\200\213.md"
new file mode 100644
index 00000000..7a83efde
--- /dev/null
+++ "b/TS \347\261\273\345\236\213\344\275\223\346\223\215/246.\347\262\276\350\257\273\343\200\212Permutation, Flatten, Absolute...\343\200\213.md"
@@ -0,0 +1,270 @@
+解决 TS 问题的最好办法就是多练,这次解读 [type-challenges](https://github.com/type-challenges/type-challenges) Medium 难度 17~24 题。
+
+## 精读
+
+### [Permutation](https://github.com/type-challenges/type-challenges/blob/main/questions/00296-medium-permutation/README.md)
+
+实现 `Permutation` 类型,将联合类型替换为可能的全排列:
+
+```ts
+type perm = Permutation<'A' | 'B' | 'C'>; // ['A', 'B', 'C'] | ['A', 'C', 'B'] | ['B', 'A', 'C'] | ['B', 'C', 'A'] | ['C', 'A', 'B'] | ['C', 'B', 'A']
+```
+
+看到这题立马联想到 TS 对多个联合类型泛型处理是采用分配律的,在第一次做到 `Exclude` 题目时遇到过:
+
+```ts
+Exclude<'a' | 'b', 'a' | 'c'>
+// 等价于
+Exclude<'a', 'a' | 'c'> | Exclude<'b', 'a' | 'c'>
+```
+
+所以这题如果能 “递归触发联合类型分配率”,就有戏解决啊。但触发的条件必须存在两个泛型,而题目传入的只有一个,我们只好创造第二个泛型,使其默认值等于第一个:
+
+```ts
+type Permutation
= S extends `${infer S}${infer E}` ? LengthOfString = S extends `${infer F}${infer R}` ? (
+ Lowercase = S extends `-${infer Rest}` ? Rest : S
+```
+
+分开写就非常容易懂了,首先 `KebabCase` 每次递归取第一个字符,如何判断这个字符是大写呢?只要小写不等于原始值就是大写,所以判断条件就是 `Lowercase
+
+> 版权声明:自由转载-非商用-非衍生-保持署名([创意共享 3.0 许可证](https://creativecommons.org/licenses/by-nc-nd/3.0/deed.zh))
+
+
diff --git "a/TS \347\261\273\345\236\213\344\275\223\346\223\215/247.\347\262\276\350\257\273\343\200\212Diff, AnyOf, IsUnion...\343\200\213.md" "b/TS \347\261\273\345\236\213\344\275\223\346\223\215/247.\347\262\276\350\257\273\343\200\212Diff, AnyOf, IsUnion...\343\200\213.md"
new file mode 100644
index 00000000..3e9fa0c5
--- /dev/null
+++ "b/TS \347\261\273\345\236\213\344\275\223\346\223\215/247.\347\262\276\350\257\273\343\200\212Diff, AnyOf, IsUnion...\343\200\213.md"
@@ -0,0 +1,296 @@
+解决 TS 问题的最好办法就是多练,这次解读 [type-challenges](https://github.com/type-challenges/type-challenges) Medium 难度 25~32 题。
+
+## 精读
+
+### [Diff](https://github.com/type-challenges/type-challenges/blob/main/questions/00645-medium-diff/README.md)
+
+实现 `Diff`,返回一个新对象,类型为两个对象类型的 Diff:
+
+```ts
+type Foo = {
+ name: string
+ age: string
+}
+type Bar = {
+ name: string
+ age: string
+ gender: number
+}
+
+Equal
= S extends `${infer A}${C}${infer B}` ?
+ `${A}${DropChar}` : S
+```
+
+## 总结
+
+写到这,越发觉得 TS 虽然具备图灵完备性,但在逻辑处理上还是不如 JS 方便,很多设计计算逻辑的题目的解法都不是很优雅。
+
+但是解决这类题目有助于强化对 TS 基础能力组合的理解与综合运用,在解决实际类型问题时又是必不可少的。
+
+> 讨论地址是:[精读《Diff, AnyOf, IsUnion...》· Issue #429 · dt-fe/weekly](https://github.com/dt-fe/weekly/issues/429)
+
+**如果你想参与讨论,请 [点击这里](https://github.com/dt-fe/weekly),每周都有新的主题,周末或周一发布。前端精读 - 帮你筛选靠谱的内容。**
+
+> 关注 **前端精读微信公众号**
+
+
+
+> 版权声明:自由转载-非商用-非衍生-保持署名([创意共享 3.0 许可证](https://creativecommons.org/licenses/by-nc-nd/3.0/deed.zh))
+
+
diff --git "a/TS \347\261\273\345\236\213\344\275\223\346\223\215/248.\347\262\276\350\257\273\343\200\212MinusOne, PickByType, StartsWith...\343\200\213.md" "b/TS \347\261\273\345\236\213\344\275\223\346\223\215/248.\347\262\276\350\257\273\343\200\212MinusOne, PickByType, StartsWith...\343\200\213.md"
new file mode 100644
index 00000000..d8848efb
--- /dev/null
+++ "b/TS \347\261\273\345\236\213\344\275\223\346\223\215/248.\347\262\276\350\257\273\343\200\212MinusOne, PickByType, StartsWith...\343\200\213.md"
@@ -0,0 +1,486 @@
+解决 TS 问题的最好办法就是多练,这次解读 [type-challenges](https://github.com/type-challenges/type-challenges) Medium 难度 33~40 题。
+
+## 精读
+
+### [MinusOne](https://github.com/type-challenges/type-challenges/blob/main/questions/02257-medium-minusone/README.md)
+
+用 TS 实现 `MinusOne` 将一个数字减一:
+
+```ts
+type Zero = MinusOne<1> // 0
+type FiftyFour = MinusOne<55> // 54
+```
+
+TS 没有 “普通” 的运算能力,但涉及数字却有一条生路,即 TS 可通过 `['length']` 访问数组长度,几乎所有数字计算都是通过它推导出来的。
+
+这道题,我们只要构造一个长度为泛型长度 -1 的数组,获取其 `['length']` 属性即可,但该方案有一个硬伤,无法计算负值,因为数组长度不可能小于 0:
+
+```ts
+// 本题答案
+type MinusOne