Typescript FAQ
caution
- 部分类型参数推导 #26242
- monorepo 类型问题 TypeScript#25376
- 目前就 npm monorepo 工作相对正常
- interface 定义 static
abstract static
TypeScript#34516- specifying interface implements clauses #33892
- typescript 没有 ESM
null
用
undefined
. 不要用null
.-- Coding Guidelines for Contributors to TypeScript.
- undefined
- 无值,不能被序列化
- not been assigned a value
- null
- 有值,值为 null,能序列化
- 可以保持 object 的 key 存在
- intentional absence
- resource not ready
foo == null
- 会匹配
null
和undefined
- 会匹配
- 参考
tsconfig.tsbuildinfo
--incremental
type vs interface
建议优先 interface
- 更直观 - 写法和功能上
- 对熟悉其他语言的人来说更好理解
- 弱于
type
- 避免太复杂
type
- Type alias
- 可以表示任意类型
- 支持 union、mapped type、conditional type、tuple
- 隐含
Record<PropertyKey, unknown>
- type 定义后就不可变了
interface
- 可以 extends - 会比 type
&
快一点 - 同名会做合并
- 通常是不希望的结果
- 可以作为扩展点,库提供定义类型的能力
- 可以 extends - 会比 type
- https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#differences-between-type-aliases-and-interfaces
- 建议优先 interface
- https://www.totaltypescript.com/type-vs-interface-which-should-you-use
- Stop Using TypeScript Interfaces
enum vs union
- enum
- 有类型,不能直接赋值
- union
// 可以遍历的 ENUM
const permissions = ['read', 'write', 'execute'] as const;
type Permission = (typeof permissions)[number];
// 使用 ENUM 且可以遍历的方式
enum Permission {
Read = 'r',
Write = 'w',
Execute = 'x',
}
const Permission = {
Read: 'r',
Write: 'w',
Execute: 'x',
} as const;
type Permission = (typeof Permission)[keyof typeof Permission];
- jsonschema 也有这个问题
- enum 是数组,不能有 title
- oneOf/anyOf 可以有 title - union
- https://stackoverflow.com/a/60041791/1870054
为每个方法添加一个参数
Expected 3 type arguments, but got 1
- TS 5.0 不支持部分参数推导
- https://stackoverflow.com/a/55754981/1870054
- #10571 Allow skipping some generics when calling a function with multiple generics
- #26242 Partial Type Argument Inference
箭头函数使用泛型参数
arrow function use generic type parameter
const identity = <T>(value: T): T[] => {
return [value];
};
// TSX
const identity = <T,>(arg: T): T => arg;
Path alias
- 推荐
@/*
->src/*
- 简单好配置
- 适用于大多场景
- 尽量保持使用相同的 alias 位置,让代码夸项目粘贴复制方便
tsconfig.json
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
}
}
vite.config.ts
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
alias: {
'@/': new URL('./src/', import.meta.url).pathname,
},
},
});
.swcrc
{
"jsc": {
"baseUrl": "./",
"paths": {
"@/*": ["@/*"]
}
}
}
- https://www.npmjs.com/package/module-alias
- self link
- PNPM
- https://pnpm.io/aliases
- https://github.com/pnpm/pnpm/issues/6316
- dependencies
"src": "workspace:web@^"
- https://yarnpkg.com/configuration/yarnrc#nmSelfReferences
- PNPM
- nextjs
ts-node
npm add -D tsconfig-paths
{
"ts-node": {
"require": ["tsconfig-paths/register"]
}
}
I 接口命名前缀
- 觉得需要用的时候就用
- 类和接口同时存在
- 如果只有接口没有实现时,不要加前缀
// user.ts
// IUserService 仅提供给使用方
export class UserService implements IUserService {}
export interface IUserService {}
// index.ts
// 对外只暴露接口,避免 import 的时候导入源码增加 bundle
export { type IUserService } from 'user.ts';
// client.ts
import type { IUserService } from 'server';
// 客户端不需要 bundle 源码
const svc = getService<IUserService>('user');
DEV
types.d.ts
declare var __DEV__: boolean;
使用 pnpm 安装,Typescript 报类型错误
- 尝试 preserveSymlinks
{
"compilerOptions": {
"preserveSymlinks": true
}
}
Types of property 'propTypes' are incompatible
method-signature-style
interface Example {
// method shorthand syntax
func(arg: string): number;
// regular property with function type
func: (arg: string) => number;
}
- method args - bivariant
- function args - contravariant
- https://github.com/microsoft/TypeScript/pull/18654
as const
- const assertions
- 不会让类型变宽 -
'hello'
不会变为string
- 都是 readyonly
- https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-4.html#const-assertions
bundle/rollup d.ts
- rollup-plugin-dts
- 不支持 path
- rollup-plugin-ts
- timocov/dts-bundle-generator
- https://github.com/Microsoft/TypeScript/issues/4433
- bun bundle 逻辑
The inferred type of '' cannot be named without a reference to sequelize
# tsconfig 可以调整为 include 需要的内容
pnpm tsc -p ./tsconfig.json --noEmit false --emitDeclarationOnly true --declaration true --outDir ./dist/types
# 直接生成
pnpm tsc --target ESNext --jsx preserve --declaration --strict --pretty --out ./dist/server.js --module system --moduleResolution node --emitDeclarationOnly ./src/server/routers/_app.ts
Module 'wechat4u' resolves to an untyped module at , which cannot be augmented.
将所有的类型定义都放在 declare module 里
TS6133: 'React' is declared but its value is never read.
SyntaxError: This experimental syntax requires enabling one of the following parser plugin(s): "decorators", "decorators-legacy".
export default {
importOrderParserPlugins: ['typescript', 'decorators-legacy'],
};
mixin
Polymorphic React Props
import * as React from "react";
type BoxProps<E extends ElementType> = Omit<ComponentProps<E>, 'as'> & {
as?: E;
};
const Box = <E extends ElementType = 'div'>({ as, ...props }: BoxProps<E>) => {
const TagName = as || 'div';
return <TagName {...props} />;
};
type PolymorphicProps<P extends { as?: T }, T extends React.ElementType> = P &
Omit<React.ComponentPropsWithoutRef<T>, keyof P>;
type ButtonProps<T extends React.ElementType> = PolymorphicProps<
{
as?: T;
icon?: React.ReactNode;
children?: React.ReactNode;
},
T
>;
function Button<T extends React.ElementType = "button">({
as,
...props
}: ButtonProps<T>
) {
const Component = as || "button";
return <Component {...props} />;
}
NullableOptional
type RequiredKeys<T> = {
[K in keyof T]-?: {} extends { [P in K]: T[K] } ? never : K;
}[keyof T];
type OptionalKeys<T> = {
[K in keyof T]-?: {} extends { [P in K]: T[K] } ? K : never;
}[keyof T];
type PickRequired<T> = Pick<T, RequiredKeys<T>>;
type PickOptional<T> = Pick<T, OptionalKeys<T>>;
type Nullable<T> = {
[P in keyof T]: T[P] | null;
};
type NullableOptional<T> = PickRequired<T> & Nullable<PickOptional<T>>;