使用 keyof 限定对象的属性值参数
如果参数中包含对象的属性,吾辈一般会使用 string
或者 PropertyKey
,但实际上 ts 里在这种场景下有更合适的方式: keyof
。
function get<T extends object>(obj: T, k: PropertyKey): any {
return obj[k]
}
const i = get({ name: '', age: 17 }, 'age') as number
console.log(i - 1)
优化一下
function get<T extends object>(obj: T, k: keyof T): T[keyof T] {
return obj[k]
}
// 这里的第二个参数会有类型约束
const i = get({ name: '', age: 17 }, 'age') as number
console.log(i - 1)
将对象的所有值进行映射
ts 内部实现了一个 Partial
类型就是这样
/**
* Make all properties in T optional
*/
type Partial<T> = {
[P in keyof T]?: T[P]
}
下面是一个使用示例,将对象中所有属性的值映射为函数。
type MapValueToFunc<T> = { [P in keyof T]: () => T[P] }
function mapToComputed<T extends object>(obj: T): MapValueToFunc<T> {
return Object.keys(obj).reduce((res, k) => {
res[k] = obj[k]
return res
}, {}) as any
}
测试一下
const res = mapToComputed({
name: 'rx',
age: 17,
sex: false,
})
infer 解构
可以将复杂类型进行解构,以得到复杂类型中的部分类型。
下面是几个应用场景
- 取出泛型类。例如从
Promise<T>
中取出T
- 取出函数的参数类型列表/返回值。例如从
(...args: P): R
中取出P
和R
- 获取到构造函数参数列表
取出泛型类
// 一个用于
type PromiseDeconstruct<T extends Promise<any>> = T extends Promise<infer R>
? R
: never
const res = Promise.resolve(1)
// 解构 Promsie 中的泛型类
const i: PromiseDeconstruct<typeof res> = 1
获取函数的类型
type FuncParam<T extends (...args: any[]) => any> = T extends (
...args: infer P // 声明一个变量以进行解构部分类型
) => any
? P // 这个值其实永远不会到
: never
function add(a: number, b: string): string {
return a + b
}
type AddParam = FuncParam<typeof add>
const arr: AddParam = [1, '2']
typeof 获取变量的类型
typeof 在 JS 中原本只是获取变量的类型,而且除了基本类型和 Function
之外,其它的所有类型都会得到 object
。而在 TS 种,该关键字的功能得到了增强,它真的变成了可以获取到变量类型,并且参与类型运算了。
如果是
const
声明的基本类型,则会被认为是字面量类型const s = '' type CustomString = typeof s // '' const str1: CustomString = '' const str2: CustomString = '1' // Type '"1"' is not assignable to type '""'.ts(2322)
如果是
let/var
声明的基本类型变量,则会被正常认为是基本类型let s = '' type CustomString = typeof s // string const str1: CustomString = '' const str2: CustomString = '1'
如果是对象,则会被认为是对象的真实类型而非
object
const user = { name: '', age: 17 } /** * { * name: string; * age: number; * } */ type User = typeof user const user2: User = { name: 'rx', age: 1 }
注:虽然着 TypeScript 的类型运算中是这样的,但实际上使用 console.log(typeof new Date())
打印的还是 object
而非 Date
,请记住:TypeScript 只在编译期生效,运行时所有类型都会被擦除。
as const 声明常量
使用 as const
可以声明一个变量为常量
基本类型:将之变为字面量类型
对象:将之所有的属性变为只读
数组:将之变为元组
const i = 1 as const const str = '1' as const const bool = false as const const tuple = [1, 2] as const const obj = { name: 'rx', age: 0, } as const
泛型中指定类型必须拥有某个字段
export function treeMap<
T extends object,
C extends { id: keyof T; children: keyof T },
R extends { [C['children']]: R[] },
>(node: T, fn: (t: T, parentPath: T[C['id']][]) => R, options: C): R
动态根据对象的值进行过滤(Pick/Omit 是静态的)
参考:https://github.com/microsoft/TypeScript/issues/23199#issuecomment-379323872
type FilteredKeys<T, U> = {
[P in keyof T]: T[P] extends U ? P : never
}[keyof T]
// 过滤所有值不为 object 的字段
type PickObject<T extends object> = {
[P in FilteredKeys<T, object>]: T[P]
}
type S = PickObject<{
name: string
age: number
info: {
age: []
}
}> // { info: { age: []; }; }
rocess.env
node.js 在 TypeScript 中使用 pTypeScript 中定义 p
使用以下定义可破
// src/@types/environment.d.ts
declare namespace NodeJS {
interface ProcessEnv {
GITHUB_AUTH_TOKEN: string
NODE_ENV: 'development' | 'production'
PORT?: string
PWD: string
}
}
// If this file has no import/export statements (i.e. is a script)
// convert it into a module by adding an empty export statement.
export {}
参考: https://stackoverflow.com/questions/45194598/using-process-env-in-typescript
如果使用的是 i
的写法,则需要修改成下面这样
// src/env.d.ts
interface ImportMeta {
env: {
NODE_ENV: 'development' | 'production'
}
}
自动推断包含一般值与函数的情况
type GetValue<T> = T | (() => T)
type GetValueType<T> = T extends () => any
? ReturnType<Extract<T, () => any>> | Exclude<T, () => any>
: T
declare function f<T extends any>(
map: [probability: number, value: T][],
): () => GetValueType<T>
f<GetValue<number>>([
[1, 1],
[1, () => 1],
])
f([
[1, 1],
[1, () => 1], // The type is not correctly inferred
])
f 的类型定义修改如下
declare function f<T extends any>(
map: [probability: number, value: T | (() => T)][],
): () => GetValueType<T>
参考答案:https://segmentfault.com/q/1010000040072586/a-1020000040073003
如何增加新的全局变量
// 这行是必不可少的
export {}
declare global {
interface Global {
config: MyConfigType
}
}
如何为第三方包定义类型
在使用某些 npm 模块时,你可能发现 @types/ 下面并没有社区维护的类型定义,这时候你需要自己维护一个类型定义。
大致上有三种方式
- 在项目的
src/@types/<module>.d.ts
中编写类型定义 - 在 monorepo 项目中创建
types-<module>
模块 - 为社区项目 DefinitelyTyped 做贡献
现在只说一下第一、第二种方式,假设我们要为名字为 a
的模块定义类型
在项目的 src/@types/a.d.ts
中编写类型定义
// a.d.ts
declare module 'a' {
// 注意:import 必须卸载 declare module 内部
import { Plugin } from 'vite'
export function hello(name: string): Plugin
}
// 使用
import { hello } from 'a'
hello()
在 monorepo 项目中创建 types-a
模块
// a.d.ts
import { Plugin } from 'vite'
export function hello(name: string): Plugin
配置 package.json 导出
{
"name": "types-a",
"types": "./a.d.ts"
}
然后在需要的模块安装它即可
强制断言类型
有时候我们知道某些类型应该是什么类型,但 ts 不忍,所以需要绕过去。
type Expect<T, E> = T extends E ? T : never
type Func = (...args: any[]) => any
function f<T, K extends keyof T>(
o: T,
k: K,
...args: Parameters<Expect<T[K], Func>>
): ReturnType<Expect<T[K], Func>> {
throw new Error('')
}
const obj = {
hello(name: string): string {
return `hello ${name}`
},
}
f(obj, 'hello', 'liuli')