vault backup: 2026-04-29 22:19:51
This commit is contained in:
@@ -0,0 +1,213 @@
|
||||
---
|
||||
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
|
||||
```
|
||||
|
||||
## 关联笔记
|
||||
Reference in New Issue
Block a user