跳至正文
来两杯美式
返回

前端核心(四):React 必备 TS 语法速查

By 来两杯美式
发布于

写 React 用 TypeScript,不是为了写更多代码,是为了让编辑器替你找 bug。这篇文档只覆盖你写 React 每天都会遇到的 TS 语法,按出现频率排序。

前置:先读完 React 必备 JS 语法速查,这篇在其基础上补 TS 的部分。

一、类型注解

给变量加一个”标签”,告诉 TS 它是什么类型。

1.1 基本类型

const name: string = "yi";
const age: number = 25;
const isActive: boolean = true;
const items: string[] = ["a", "b"]; // 字符串数组
const nums: number[] = [1, 2, 3]; // 数字数组
const tuple: [string, number] = ["yi", 25]; // 固定长度+类型的数组(元组)

后端出身的话,这跟 Java 的 String name = "yi" 或 Python 的类型注解 name: str = "yi" 一样——声明类型,编译器帮你检查。

1.2 对象类型:内联写法

const user: { name: string; age: number } = {
  name: "yi",
  age: 25,
};

用分号 ; 分隔字段(在类型定义里),跟 JS 对象用逗号不同。

问题:对象类型写长了很丑,而且同一个类型可能多处复用 → 用接口。

二、接口 Interface

定义对象的”形状”——有哪些字段,每个字段什么类型。

2.1 基本用法

interface User {
  name: string;
  age: number;
  email: string;
}

const user: User = { name: "yi", age: 25, email: "yi@test.com" };

比内联写法清晰,而且可以复用。

2.2 React 里的样子——Props 类型

对 Props 本身还不熟的话,先看博客中的《Props 完全指南》。

interface TodoListProps {
  items: Todo[];
  onDelete: (id: number) => void;
}

function TodoList({ items, onDelete }: TodoListProps) {
  return (
    <ul>
      {items.map(item => (
        <li key={item.id}>
          {item.name}
          <button onClick={() => onDelete(item.id)}>删除</button>
        </li>
      ))}
    </ul>
  );
}

每个组件的 Props 都用 interface 定义,这是 React + TS 的标准写法。

拆解:函数签名是三件事叠在一起

function TodoList({ items, onDelete }: TodoListProps);
步骤写法对应概念
1. 接收一个对象参数propsJS 函数参数
2. 解构出需要的属性{ items, onDelete }JS 解构赋值
3. 告诉 TS 这个对象什么形状: TodoListPropsTS 类型注解

Props 只有两种东西:数据 + 回调

interface TodoListProps {
  items: Todo[]; // 数据:Todo 对象的数组
  onDelete: (id: number) => void; // 回调:点删除时调用的函数
}

后端类比:items: Todo[]List<Todo> items,就是传入数据;onDeleteConsumer<Integer> onDelete,就是传入回调函数(Java 的函数式接口 / Go 的 func 参数)。

事件处理:传函数,不传调用结果

// ❌ 错误:立即执行,一渲染就调用了
<button onClick={onDelete(item.id)}>

// ✅ 正确:包一层箭头函数,点击时才执行
<button onClick={() => onDelete(item.id)}>

跟 JS 速查里讲的同一个道理——事件处理函数必须传函数本身,不能传函数调用的结果。

2.3 React 里的样子——API 响应类型

interface ApiResponse {
  code: number;
  data: User[];
  message: string;
}

// 后端接口返回的数据,用接口约束
async function fetchUsers(): Promise<ApiResponse> {
  const res = await fetch("/api/users");
  return res.json();
}

跟后端定义 DTO/VO 一个道理——前后端对齐数据结构。

2.4 interface vs type——哪个?

// interface:定义对象形状
interface User {
  name: string;
}

// type:也能定义对象形状
type User = {
  name: string;
};

90% 的情况用哪个都行。实际区别:

interfacetype
可以继承extends& 交叉
可以声明合并✅ 同名自动合并❌ 重复定义报错
能写联合类型type A = B | C
能写基本类型别名type ID = string

React 项目惯例:对象用 interface,其他用 type。不用纠结,团队统一即可。

三、可选属性 ?

属性名后面加 ?,表示”这个字段可以没有”。

3.1 定义

interface User {
  name: string;
  age: number;
  email?: string; // 可选,可以不传
  phone?: string; // 可选,可以不传
}

// ✅ 不传 email 和 phone 也行
const user: User = { name: "yi", age: 25 };

跟 JS 解构默认值的关系

// TS 层面:email 是可选的(可以不传)
interface User {
  email?: string;
}

// JS 层面:解构时给默认值(没传就用默认值)
const { email = "none" } = user;

3.2 React 里的样子——可选 Props

interface ButtonProps {
  children: React.ReactNode;
  variant?: "primary" | "secondary"; // 可选,默认 'primary'
  size?: "sm" | "md" | "lg"; // 可选,默认 'md'
  onClick?: () => void; // 可选回调
}

function Button({
  children,
  variant = "primary",
  size = "md",
  onClick,
}: ButtonProps) {
  // ...
}

可选 Prop + 解构默认值,是 React 组件的标准模式。

四、联合类型 |

“这个值可以是 A,也可以是 B”。

4.1 基本用法

// 变量可以是多种类型
let id: string | number = "abc";
id = 123; // ✅ 也行

// 函数参数可以是多种类型
function formatId(id: string | number): string {
  return String(id);
}

后端类比:Java 的泛型上界 <? extends A> 或 Go 的 interface{} + 类型断言,但 TS 的联合类型更精确——你明确列出所有可能的类型。

4.2 字面量联合——最实用的模式

限定值为几个固定选项:

type Status = "loading" | "success" | "error";
type Method = "GET" | "POST" | "PUT" | "DELETE";
type Theme = "light" | "dark";

const status: Status = "loading"; // ✅
const status2: Status = "pending"; // ❌ 不是允许的值

后端类比:枚举。但 TS 的字面量联合更轻量,不需要定义 enum 关键字,直接写字面值就行。

4.3 React 里的样子

// 组件的 variant 只允许这几个值
interface AlertProps {
  variant: "info" | "warning" | "error";
  message: string;
}

function Alert({ variant, message }: AlertProps) {
  // TS 知道 variant 只能是这三个值,编辑器有自动补全
  const colors = {
    info: "blue",
    warning: "yellow",
    error: "red",
  };
  return <div style={{ color: colors[variant] }}>{message}</div>;
}

五、类型推断——不写类型也行

TS 很聪明,很多时候不用你写类型,它自己能猜到

5.1 自动推断

// TS 推断出 name 是 string
const name = "yi";

// TS 推断出 age 是 number
const age = 25;

// TS 推断出 items 是 string[]
const items = ["a", "b", "c"];

// TS 推断出返回值是 number
function double(n: number) {
  return n * 2; // 不用写 : number,TS 知道
}

5.2 什么时候必须写类型?

// ❌ 不写类型,TS 推断为 any(等于没类型)
let result;
result = fetchUser();

// ✅ 必须写类型
let result: User | null = null;

// ❌ 函数参数无法推断,必须写
function greet(name) {} // 报错:参数隐式有 'any' 类型

// ✅ 参数必须写类型
function greet(name: string) {}

原则:能推断就不写,推断不了或推断为 any 就必须写。函数参数永远要写。

5.3 React 里的样子

// useState 的初始值能推断类型,不用手写
const [count, setCount] = useState(0); // 推断为 number
const [name, setName] = useState(""); // 推断为 string
const [isOpen, setIsOpen] = useState(false); // 推断为 boolean

// 初始值是 null 或 undefined 时,必须手写类型
const [user, setUser] = useState<User | null>(null);
//                              ^^^^^^^^^^^^^ 必须写,否则推断为 null(永远赋不了别的值)

六、泛型 <T>

“类型的参数”——写一次类型定义,用在多种具体类型上。

6.1 为什么要泛型?

// 不用泛型:每种类型写一个函数
function firstString(arr: string[]): string {
  return arr[0];
}
function firstNumber(arr: number[]): number {
  return arr[0];
}

// 用泛型:写一次,所有类型都能用
function first<T>(arr: T[]): T {
  return arr[0];
}

first<string>(["a", "b"]); // → string
first<number>([1, 2, 3]); // → number
// 大多数时候不用手写 <string>,TS 能推断
first(["a", "b"]); // TS 自动推断 T = string

后端类比:Java 的 List<T>、Go 的泛型 func First[T any](arr []T) T,一个意思。

6.2 React 里的样子

// useState 是泛型函数
const [count, setCount] = useState<number>(0)
// useState 的签名大概是:function useState<T>(initial: T): [T, (v: T) => void]

// useQuery 是泛型函数——告诉它返回的数据是什么类型
const { data } = useQuery<User>({
  queryKey: ['user'],
  queryFn: () => fetch('/api/user').then(res => res.json())
})
// 现在 data 的类型是 User | undefined,编辑器能补全 user.name、user.age

// 不写泛型 → data 类型是 unknown,什么属性都点不出来
const { data } = useQuery({ ... })
// data 是 unknown ❌

关键useQuery<User> 就是告诉 TS “这个接口返回的数据是 User 类型”,之后 data 就有完整的类型提示了。这跟你后端定义接口返回类型一个道理。

七、类型断言 as

“我比你(TS)更清楚这个值的类型”。

7.1 基本用法

// fetch 返回 any,你知道它是 User
const data = res.json() as User;

// 事件对象的类型
const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
  const value = e.target.value; // TS 知道 value 是 string
};

7.2 什么时候用 as

// ✅ 你确实比 TS 知道更多信息时
const data = res.json() as User[];

// ✅ DOM 操作时
const input = document.getElementById("myInput") as HTMLInputElement;
input.value; // 没有 as 的话,TS 不知道这是 input 元素

// ❌ 用来绕过类型错误(跟 as any 一样危险)
const user = {} as User; // 骗过编译器,运行时可能出错

注意as 不做任何运行时转换,只是编译期的类型标注。as User 不会把数据变成 User,只是告诉 TS “请当它是 User”。如果实际数据不是 User,运行时照样报错。

7.3 as 和后端类型转换的区别

// Java:强转,运行时检查,类型不对抛 ClassCastException
User user = (User) obj;

// TS:编译期标注,运行时完全消失,不做检查
const user = obj as User

TS 的 as 更像是”信任我”,不是”帮我转换”。

7.4 React 里的样子

// 事件处理——最常见的 as 场景
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
  setValue(e.target.value);
};

// 不用 as 的写法(更推荐)——直接在参数上标注类型
<input onChange={e => setValue(e.target.value)} />;
// TS 自动推断 e 的类型,不需要 as

八、工具类型

TS 内置的类型转换函数——把一个类型变成另一个类型。

8.1 Partial<T>——所有属性变可选

interface User {
  name: string;
  age: number;
  email: string;
}

type PartialUser = Partial<User>;
// 等价于:
// {
//   name?: string
//   age?: number
//   email?: string
// }

React 场景——更新对象时,只传要改的字段:

function updateUser(id: number, updates: Partial<User>) {
  // 只需要传要改的字段,不用传整个 User
}
updateUser(1, { age: 26 }); // ✅ 只改 age

8.2 Pick<T, K>——只取几个属性

type UserPreview = Pick<User, "name" | "age">;
// 等价于:
// {
//   name: string
//   age: number
// }

React 场景——组件只需要用部分字段:

interface UserCardProps {
  user: Pick<User, "name" | "email">;
}

8.3 Omit<T, K>——排除几个属性

type UserWithoutEmail = Omit<User, "email">;
// 等价于:
// {
//   name: string
//   age: number
// }

React 场景——基于已有类型但去掉某些字段:

// 基础 Props,但不需要 children
type ButtonBaseProps = Omit<
  React.ButtonHTMLAttributes<HTMLButtonElement>,
  "children"
>;

8.4 Record<K, V>——键值对对象

// key 是 string,value 是 number
const scores: Record<string, number> = {
  math: 90,
  english: 85,
};

// 后端类比:Map<String, Integer>

React 场景——路由映射、主题配置:

const themeColors: Record<"light" | "dark", string> = {
  light: "#ffffff",
  dark: "#000000",
};

8.5 速查

工具类型作用后端类比
Partial<T>所有属性变可选更新 DTO(字段均可选)
Pick<T, K>只取某几个属性视图对象(只暴露部分字段)
Omit<T, K>排除某几个属性创建 DTO(去掉 id/timestamp)
Record<K, V>键值对映射Map<K, V>

九、函数类型

给函数参数和返回值标注类型。

9.1 基本写法

// 参数类型 + 返回值类型
function add(a: number, b: number): number {
  return a + b;
}

// 箭头函数
const add = (a: number, b: number): number => a + b;

// 返回值通常可以省略,TS 能推断
const add = (a: number, b: number) => a + b; // TS 知道返回 number

9.2 回调函数类型

React 里到处都是回调,需要知道怎么标注类型:

// 无参数无返回值
const onClick: () => void = () => {};

// 有参数
const onChange: (value: string) => void = value => {};

// 作为 Props 的一部分
interface ButtonProps {
  onClick: (e: React.MouseEvent<HTMLButtonElement>) => void;
  children: React.ReactNode;
}

9.3 React 常用事件类型

不用背,编辑器会提示,知道有这些就行:

事件类型
点击React.MouseEvent<HTMLButtonElement>
输入变化React.ChangeEvent<HTMLInputElement>
表单提交React.FormEvent<HTMLFormElement>
键盘React.KeyboardEvent<HTMLInputElement>

实际写法——让 TS 自动推断,不用手写:

// ✅ 推荐:TS 自动推断 e 的类型
<button onClick={(e) => console.log(e.currentTarget)}>Click</button>

// ❌ 多此一举:手动标注
<button onClick={(e: React.MouseEvent<HTMLButtonElement>) => { }}>Click</button>

十、字面量类型 & as const

10.1 字面量类型——比 string 更精确

// 普通类型:太宽泛
let direction: string = "left";
direction = "any string"; // ✅ 不会报错,但可能是 typo

// 字面量类型:精确到某个值
let direction: "left" | "right" | "up" | "down" = "left";
direction = "right"; // ✅
direction = "diagonal"; // ❌ 不是允许的值

联合字面量类型 = 轻量级枚举。React 里比 enum 常用得多。

10.2 as const——让 TS 把值当作字面量

// 普通 const,TS 推断为 string[]
const ROLES = ["admin", "editor", "viewer"];
// ROLES 的类型是 string[]

// 加 as const,TS 推断为只读字面量元组
const ROLES = ["admin", "editor", "viewer"] as const;
// ROLES 的类型是 readonly ['admin', 'editor', 'viewer']

// 好处:可以从 ROLES 提取联合类型
type Role = (typeof ROLES)[number];
// 'admin' | 'editor' | 'viewer'

10.3 React 里的样子

// 定义状态机的所有状态
const STATUSES = ["idle", "loading", "success", "error"] as const;
type Status = (typeof STATUSES)[number];

interface State {
  status: Status; // 只能是上面四个值之一
}

// 定义路由配置
const ROUTES = {
  home: "/",
  login: "/login",
  dashboard: "/dashboard",
} as const;
// 每个值都是字面量类型,不是 string

速查表

语法一句话React 里怎么用
: 类型给变量加类型标签const [user, setUser] = useState<User | null>(null)
interface定义对象形状Props 类型、API 响应类型
? 可选属性这个字段可以没有可选 Props、可选回调
| 联合类型可以是 A 也可以是 Bvariant: 'primary' | 'secondary'
类型推断TS 自己猜,不用你写useState(0) 自动推断 number
<T> 泛型类型的参数useQuery<User>() 告诉 TS 数据类型
as 断言我比 TS 更清楚类型res.json() as User
Partial<T>全部变可选更新函数的参数
Pick<T, K>只取几个属性组件只需要部分字段
Omit<T, K>排除几个属性去掉不需要的字段
as const值当字面量状态常量、路由配置

分享这篇文章:
通过邮件分享这篇文章✓ 链接已复制
查看系列全部文章
  1. 01.前端核心(一):DOM 入门
  2. 02.前端核心(二):React 必备 JS 语法速查
  3. 03.前端核心(三):Promise 与异步编程详解
  4. 04.前端核心(四):React 必备 TS 语法速查

上一篇
身份安全三要素(AAA):认证、授权与审计
下一篇
前端核心(三):Promise 与异步编程详解