Skip to main content

Typescript FAQ

caution
  • 部分类型参数推导 #26242
  • monorepo 类型问题 TypeScript#25376
    • 目前就 npm monorepo 工作相对正常
  • interface 定义 static
  • typescript 没有 ESM
    • 直接 import 会有问题, 多一层 default
    • Provide TypeScript as an ESM #32949
    • Use ESM for our executables #51440

null

undefined . 不要用 null .

-- Coding Guidelines for Contributors to TypeScript.

tsconfig.tsbuildinfo

  • --incremental

type vs interface

建议优先 interface

  • 更直观 - 写法和功能上
  • 对熟悉其他语言的人来说更好理解
  • 弱于 type - 避免太复杂

  • type
    • Type alias
    • 可以表示任意类型
    • 支持 union、mapped type、conditional type、tuple
    • 隐含 Record<PropertyKey, unknown>
    • type 定义后就不可变了
  • interface
    • 可以 extends - 会比 type & 快一点
    • 同名会做合并
      • 通常是不希望的结果
      • 可以作为扩展点,库提供定义类型的能力

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];

为每个方法添加一个参数

Expected 3 type arguments, but got 1

箭头函数使用泛型参数

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": {
"@/*": ["@/*"]
}
}
}

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;
}

as const

bundle/rollup d.ts

The inferred type of '' cannot be named without a reference to sequelize

TypeScript#42873

# 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>>;