This repository has been archived on 2026-05-24. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
all-in-kingsoft/hhs/REACT/3. 生态工具篇/10-TS + React.md
T
2026-04-29 22:19:51 +08:00

214 lines
5.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
tags: [React, TypeScript, Frontend]
create time: 2026-04-29 22:09
---
# TS + React
## 概述
TypeScript 为 React 提供编译时类型检查和智能提示。本文档系统梳理 Props、State、Hook、Ref 等核心场景的类型定义方式,以及进阶的泛型推导模式。
## Props 类型定义
### 基础方式
```tsx
// 方式1:interface(推荐,可 extend)
interface ButtonProps {
label: string;
onClick?: () => void;
}
const Button = ({ label, onClick }: ButtonProps) => <button onClick={onClick}>{label}</button>;
// 方式2:type alias
type ButtonProps = { label: string; onClick?: () => void };
// ⚠️ 避免:函数参数解构后不再标注
// 这样会导致每个参数无法被单独推断
function Component({ a, b }) { ... } // any!
```
### 合成事件类型
```tsx
// ❌ 不要用 HTML 原生的 Event
const handleChange = (e: Event) => {};
// ✅ 用 React 的合成事件类型
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
e.target.value; // string | number | string[]
};
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
};
const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
console.log(e.button); // 鼠标按键
};
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === "Enter") submit();
};
```
### Children 类型
```tsx
// 通用 children 类型
function Card({ children }: { children: React.ReactNode }) {}
// 严格类型 children(限制允许的子节点类型)
interface TabsProps {
children: React.ReactElement<TabProps>; // 只能是 Tab 组件
}
// 数组形式
interface ListProps<T> {
children: React.ReactElement<{ item: T }> [];
}
```
## State 类型推导
```tsx
interface User { name: string; role: "admin" | "user"; age: number }
// 完整泛型
const [user, setUser] = useState<User>({ name: "", role: "user", age: 0 });
// 可选初始值
const [user, setUser] = useState<User | null>(null);
// 使用时需判空
user?.name; // 安全
(user as User).name; // or
user!?.name; // non-null assertion
// useReducer 类型推导
interface State { items: Item[]; filter: string }
type Action = { type: "SET_FILTER"; payload: string } | { type: "ADD_ITEM"; payload: Item };
function reducer(state: State, action: Action): State {
switch (action.type) {
case "SET_FILTER": return { ...state, filter: action.payload };
case "ADD_ITEM": return { ...state, items: [...state.items, action.payload] };
}
}
const [state, dispatch] = useReducer(reducer, initialState);
```
## Ref 类型定义
```tsx
// 元素 ref
const inputRef = useRef<HTMLInputElement>(null);
// mutable value ref(不触发 re-render)
const timerIdRef = useRef<ReturnType<typeof setInterval>>(undefined);
timerIdRef.current = setInterval(() => {}, 1000);
// class component style ref object(用于挂载子组件引用)
const childRef = useRef<ComponentInstance>(null);
// 需要 useImperativeHandle 暴露方法给父级
// forwardRef 中 ref 的正确用法
const FancyInput = forwardRef<HTMLInputElement, { label: string }>(function FancyInput(props, ref) {
const innerRef = useRef<HTMLInputElement>(null);
useImperativeHandle(ref, () => ({
focus: () => innerRef.current?.focus(),
blur: () => innerRef.current?.blur(),
}));
return <input ref={innerRef} label={props.label} />;
});
```
## Hook 类型推导
```tsx
// 自定义 Hook 返回值类型
interface UseCountReturn {
count: number;
increment: () => void;
decrement: () => void;
}
function useCount(initial = 0): UseCountReturn {
const [count, setCount] = useState(initial);
return {
count,
increment: () => setCount(c => c + 1),
decrement: () => setCount(c => c - 1),
};
}
// generic hook —— 最强大的类型推导场景
function useAsync<T, Args extends any[]>(
asyncFn: (...args: Args) => Promise<T>,
deps: DependencyList
): { data: T | null; loading: boolean; error: Error | null; invoke: (...args: Args) => void } {
const [data, setData] = useState<T | null>(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<Error | null>(null);
const invoke = useCallback(async (...args: Args) => {
setLoading(true);
setError(null);
try {
const result = await asyncFn(...args);
setData(result);
} catch (err) {
setError(err as Error);
} finally {
setLoading(false);
}
}, [asyncFn, ...deps]);
useEffect(() => { invoke(); }, [invoke]);
return { data, loading, error, invoke };
}
// 使用:T 自动推导!
const { data: users } = useAsync(fetchUsers, []); // data: User[] | null
const { data: user } = useAsync(fetchUserById, [id]); // data: User | null
```
## discriminated Union(区分联合类型)
```tsx
// 类型守卫 —— React 状态管理中最常用的类型模式
interface SuccessAction { type: "success"; data: User[] }
interface ErrorAction { type: "error"; error: string }
interface LoadingAction { type: "loading" }
type Action = SuccessAction | ErrorAction | LoadingAction;
function reducer(state: State, action: Action): State {
switch (action.type) {
case "success":
return { ...state, data: action.data, status: "loaded" };
// ✅ TS 自动推断 action.data 是 User[]
case "error":
return { ...state, error: action.error };
// ✅ TS 自动推断 action.error 是 string
case "loading":
return { ...state, status: "loading" };
}
}
```
```mermaid
graph LR
A["Union Type"] -->|"switch / if"| B["Type Narrowing"]
B --> C["discriminated union: type field"]
B --> D["typeof check"]
B --> E["in operator"]
style C fill:#61DAFB,color:#000
```
## 关联笔记