Appearance
Hooks类型定义
概述
React Hooks在TypeScript中需要正确的类型定义才能充分发挥类型系统的优势。本文将全面介绍各种Hooks的类型定义方式、泛型使用和最佳实践。
useState类型定义
基本类型
typescript
import { useState } from 'react';
// 自动推断类型
const [count, setCount] = useState(0); // number
const [name, setName] = useState('Alice'); // string
const [isActive, setActive] = useState(true); // boolean
// 显式指定类型
const [count2, setCount2] = useState<number>(0);
const [name2, setName2] = useState<string>('');对象和数组类型
typescript
// 对象类型
interface User {
id: string;
name: string;
email: string;
}
const [user, setUser] = useState<User>({
id: '1',
name: 'Alice',
email: 'alice@example.com',
});
// 数组类型
const [users, setUsers] = useState<User[]>([]);
// 更新对象
setUser((prev) => ({
...prev,
name: 'Bob',
}));
// 更新数组
setUsers((prev) => [...prev, newUser]);可空类型
typescript
// null或undefined
const [user, setUser] = useState<User | null>(null);
const [data, setData] = useState<string | undefined>(undefined);
// 使用时需要检查
if (user) {
console.log(user.name); // ✅ 类型安全
}
// 或使用可选链
console.log(user?.name);联合类型
typescript
// 字符串字面量联合
const [status, setStatus] = useState<'idle' | 'loading' | 'success' | 'error'>('idle');
// 复杂联合类型
type LoadingState =
| { status: 'idle' }
| { status: 'loading' }
| { status: 'success'; data: any }
| { status: 'error'; error: Error };
const [state, setState] = useState<LoadingState>({ status: 'idle' });
// 类型守卫
if (state.status === 'success') {
console.log(state.data); // ✅ 可以访问data
}函数式更新
typescript
// 函数式更新的类型推断
const [count, setCount] = useState(0);
// 参数类型自动推断为number
setCount((prev) => prev + 1);
// 对象更新
interface FormState {
name: string;
email: string;
}
const [form, setForm] = useState<FormState>({
name: '',
email: '',
});
// 参数类型自动推断为FormState
setForm((prev) => ({
...prev,
name: 'Alice',
}));useEffect类型定义
基本用法
typescript
import { useEffect } from 'react';
// 无依赖
useEffect(() => {
console.log('Component mounted');
}, []);
// 有依赖
useEffect(() => {
console.log('Count changed:', count);
}, [count]);
// 返回清理函数
useEffect(() => {
const timer = setTimeout(() => {
console.log('Delayed');
}, 1000);
return () => {
clearTimeout(timer);
};
}, []);异步Effect
typescript
// ❌ 错误 - useEffect不能直接使用async
useEffect(async () => {
const data = await fetch('/api/data');
}, []);
// ✅ 正确 - 在内部定义async函数
useEffect(() => {
const fetchData = async () => {
try {
const response = await fetch('/api/data');
const data = await response.json();
setData(data);
} catch (error) {
console.error(error);
}
};
fetchData();
}, []);
// ✅ 使用IIFE
useEffect(() => {
(async () => {
const data = await fetchData();
setData(data);
})();
}, []);依赖数组类型
typescript
// 依赖必须是不可变的值
const [count, setCount] = useState(0);
const [user, setUser] = useState<User | null>(null);
useEffect(() => {
console.log(count, user);
}, [count, user]); // ✅
// ❌ 错误 - 对象字面量每次都是新的引用
useEffect(() => {
console.log('Effect');
}, [{ id: 1 }]);
// ✅ 正确 - 使用useMemo
const config = useMemo(() => ({ id: 1 }), []);
useEffect(() => {
console.log('Effect');
}, [config]);useRef类型定义
DOM引用
typescript
import { useRef, useEffect } from 'react';
// HTML元素引用
const inputRef = useRef<HTMLInputElement>(null);
const divRef = useRef<HTMLDivElement>(null);
const buttonRef = useRef<HTMLButtonElement>(null);
useEffect(() => {
// 需要检查null
inputRef.current?.focus();
if (divRef.current) {
divRef.current.scrollTop = 0;
}
}, []);
return (
<div>
<input ref={inputRef} />
<div ref={divRef}>Content</div>
<button ref={buttonRef}>Click</button>
</div>
);可变值引用
typescript
// 存储任意可变值
const countRef = useRef<number>(0);
const timerRef = useRef<NodeJS.Timeout | null>(null);
const prevValueRef = useRef<string>('');
// 不需要检查null
countRef.current += 1;
// 清理定时器
useEffect(() => {
timerRef.current = setTimeout(() => {
console.log('Delayed');
}, 1000);
return () => {
if (timerRef.current) {
clearTimeout(timerRef.current);
}
};
}, []);
// 保存前一个值
useEffect(() => {
prevValueRef.current = value;
});回调Ref
typescript
// 使用回调形式的ref
const [height, setHeight] = useState(0);
const measureRef = useCallback((node: HTMLDivElement | null) => {
if (node !== null) {
setHeight(node.getBoundingClientRect().height);
}
}, []);
return <div ref={measureRef}>Content</div>;useContext类型定义
基本Context
typescript
import { createContext, useContext } from 'react';
// 定义Context类型
interface ThemeContextType {
theme: 'light' | 'dark';
toggleTheme: () => void;
}
// 创建Context(可以设置默认值)
const ThemeContext = createContext<ThemeContextType | undefined>(undefined);
// Provider
export const ThemeProvider = ({ children }: { children: React.ReactNode }) => {
const [theme, setTheme] = useState<'light' | 'dark'>('light');
const toggleTheme = () => {
setTheme((prev) => (prev === 'light' ? 'dark' : 'light'));
};
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
};
// 自定义Hook
export const useTheme = () => {
const context = useContext(ThemeContext);
if (context === undefined) {
throw new Error('useTheme must be used within a ThemeProvider');
}
return context;
};
// 使用
const Component = () => {
const { theme, toggleTheme } = useTheme();
return <div>Theme: {theme}</div>;
};泛型Context
typescript
// 泛型Context
interface DataContextType<T> {
data: T;
setData: (data: T) => void;
}
function createDataContext<T>() {
return createContext<DataContextType<T> | undefined>(undefined);
}
// 使用
interface User {
id: string;
name: string;
}
const UserContext = createDataContext<User>();
const UserProvider = ({ children }: { children: React.ReactNode }) => {
const [data, setData] = useState<User>({ id: '1', name: 'Alice' });
return (
<UserContext.Provider value={{ data, setData }}>
{children}
</UserContext.Provider>
);
};useReducer类型定义
基本用法
typescript
import { useReducer } from 'react';
// State类型
interface State {
count: number;
error: string | null;
}
// Action类型
type Action =
| { type: 'increment' }
| { type: 'decrement' }
| { type: 'set'; payload: number }
| { type: 'error'; payload: string };
// Reducer函数
const reducer = (state: State, action: Action): State => {
switch (action.type) {
case 'increment':
return { ...state, count: state.count + 1 };
case 'decrement':
return { ...state, count: state.count - 1 };
case 'set':
return { ...state, count: action.payload };
case 'error':
return { ...state, error: action.payload };
default:
return state;
}
};
// 使用
const Counter = () => {
const [state, dispatch] = useReducer(reducer, { count: 0, error: null });
return (
<div>
<p>Count: {state.count}</p>
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
<button onClick={() => dispatch({ type: 'set', payload: 10 })}>Set to 10</button>
</div>
);
};复杂Reducer
typescript
// 复杂State
interface User {
id: string;
name: string;
email: string;
}
interface AppState {
user: User | null;
isLoading: boolean;
error: Error | null;
data: any[];
}
// 复杂Action
type AppAction =
| { type: 'FETCH_START' }
| { type: 'FETCH_SUCCESS'; payload: { user: User; data: any[] } }
| { type: 'FETCH_ERROR'; payload: Error }
| { type: 'LOGOUT' }
| { type: 'UPDATE_USER'; payload: Partial<User> };
// Reducer
const appReducer = (state: AppState, action: AppAction): AppState => {
switch (action.type) {
case 'FETCH_START':
return { ...state, isLoading: true, error: null };
case 'FETCH_SUCCESS':
return {
...state,
isLoading: false,
user: action.payload.user,
data: action.payload.data,
};
case 'FETCH_ERROR':
return {
...state,
isLoading: false,
error: action.payload,
};
case 'LOGOUT':
return {
...state,
user: null,
data: [],
};
case 'UPDATE_USER':
return {
...state,
user: state.user ? { ...state.user, ...action.payload } : null,
};
default:
return state;
}
};useMemo和useCallback类型定义
useMemo
typescript
import { useMemo } from 'react';
// 基本用法 - 类型自动推断
const expensiveValue = useMemo(() => {
return computeExpensiveValue(a, b);
}, [a, b]);
// 显式指定返回类型
const value = useMemo<number>(() => {
return a + b;
}, [a, b]);
// 复杂类型
interface ProcessedData {
items: string[];
total: number;
}
const processedData = useMemo<ProcessedData>(() => {
return {
items: data.map((item) => item.name),
total: data.length,
};
}, [data]);
// 返回React元素
const memoizedElement = useMemo(() => {
return <ExpensiveComponent data={data} />;
}, [data]);useCallback
typescript
import { useCallback } from 'react';
// 基本用法 - 参数和返回值类型自动推断
const handleClick = useCallback(() => {
console.log('Clicked');
}, []);
// 带参数的回调
const handleItemClick = useCallback((id: string) => {
console.log('Item clicked:', id);
}, []);
// 异步回调
const fetchData = useCallback(async (id: string): Promise<Data> => {
const response = await fetch(`/api/data/${id}`);
return response.json();
}, []);
// 复杂回调类型
interface FormData {
name: string;
email: string;
}
const handleSubmit = useCallback(
(data: FormData): Promise<boolean> => {
return submitForm(data);
},
[submitForm]
);
// 事件处理器
const handleChange = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
setValue(e.target.value);
},
[setValue]
);自定义Hooks类型定义
基本自定义Hook
typescript
// 返回值类型
function useCounter(initialValue: number): [number, () => void, () => void] {
const [count, setCount] = useState(initialValue);
const increment = useCallback(() => {
setCount((c) => c + 1);
}, []);
const decrement = useCallback(() => {
setCount((c) => c - 1);
}, []);
return [count, increment, decrement];
}
// 使用
const [count, increment, decrement] = useCounter(0);对象返回值
typescript
// 返回对象(更灵活)
interface UseCounterReturn {
count: number;
increment: () => void;
decrement: () => void;
reset: () => void;
}
function useCounter(initialValue: number): UseCounterReturn {
const [count, setCount] = useState(initialValue);
const increment = useCallback(() => {
setCount((c) => c + 1);
}, []);
const decrement = useCallback(() => {
setCount((c) => c - 1);
}, []);
const reset = useCallback(() => {
setCount(initialValue);
}, [initialValue]);
return { count, increment, decrement, reset };
}
// 使用
const { count, increment, decrement, reset } = useCounter(0);泛型自定义Hook
typescript
// 泛型Hook
interface UseAsyncReturn<T> {
data: T | null;
error: Error | null;
isLoading: boolean;
execute: () => Promise<void>;
}
function useAsync<T>(
asyncFunction: () => Promise<T>
): UseAsyncReturn<T> {
const [data, setData] = useState<T | null>(null);
const [error, setError] = useState<Error | null>(null);
const [isLoading, setIsLoading] = useState(false);
const execute = useCallback(async () => {
setIsLoading(true);
setError(null);
try {
const result = await asyncFunction();
setData(result);
} catch (err) {
setError(err as Error);
} finally {
setIsLoading(false);
}
}, [asyncFunction]);
return { data, error, isLoading, execute };
}
// 使用
interface User {
id: string;
name: string;
}
const fetchUser = async (): Promise<User> => {
const response = await fetch('/api/user');
return response.json();
};
const { data, error, isLoading, execute } = useAsync<User>(fetchUser);复杂自定义Hook
typescript
// useForm Hook
interface UseFormOptions<T> {
initialValues: T;
validate?: (values: T) => Partial<Record<keyof T, string>>;
onSubmit: (values: T) => void | Promise<void>;
}
interface UseFormReturn<T> {
values: T;
errors: Partial<Record<keyof T, string>>;
touched: Partial<Record<keyof T, boolean>>;
isSubmitting: boolean;
handleChange: (field: keyof T) => (e: React.ChangeEvent<HTMLInputElement>) => void;
handleBlur: (field: keyof T) => () => void;
handleSubmit: (e: React.FormEvent) => void;
setFieldValue: (field: keyof T, value: T[keyof T]) => void;
resetForm: () => void;
}
function useForm<T extends Record<string, any>>({
initialValues,
validate,
onSubmit,
}: UseFormOptions<T>): UseFormReturn<T> {
const [values, setValues] = useState<T>(initialValues);
const [errors, setErrors] = useState<Partial<Record<keyof T, string>>>({});
const [touched, setTouched] = useState<Partial<Record<keyof T, boolean>>>({});
const [isSubmitting, setIsSubmitting] = useState(false);
const handleChange = useCallback(
(field: keyof T) => (e: React.ChangeEvent<HTMLInputElement>) => {
setValues((prev) => ({
...prev,
[field]: e.target.value,
}));
},
[]
);
const handleBlur = useCallback((field: keyof T) => () => {
setTouched((prev) => ({
...prev,
[field]: true,
}));
}, []);
const handleSubmit = useCallback(
async (e: React.FormEvent) => {
e.preventDefault();
if (validate) {
const validationErrors = validate(values);
setErrors(validationErrors);
if (Object.keys(validationErrors).length > 0) {
return;
}
}
setIsSubmitting(true);
try {
await onSubmit(values);
} finally {
setIsSubmitting(false);
}
},
[values, validate, onSubmit]
);
const setFieldValue = useCallback((field: keyof T, value: T[keyof T]) => {
setValues((prev) => ({
...prev,
[field]: value,
}));
}, []);
const resetForm = useCallback(() => {
setValues(initialValues);
setErrors({});
setTouched({});
}, [initialValues]);
return {
values,
errors,
touched,
isSubmitting,
handleChange,
handleBlur,
handleSubmit,
setFieldValue,
resetForm,
};
}
// 使用
interface LoginForm {
email: string;
password: string;
}
const LoginComponent = () => {
const form = useForm<LoginForm>({
initialValues: {
email: '',
password: '',
},
validate: (values) => {
const errors: Partial<Record<keyof LoginForm, string>> = {};
if (!values.email) {
errors.email = 'Email is required';
}
if (!values.password) {
errors.password = 'Password is required';
}
return errors;
},
onSubmit: async (values) => {
await login(values);
},
});
return (
<form onSubmit={form.handleSubmit}>
<input
value={form.values.email}
onChange={form.handleChange('email')}
onBlur={form.handleBlur('email')}
/>
{form.touched.email && form.errors.email && (
<span>{form.errors.email}</span>
)}
<input
type="password"
value={form.values.password}
onChange={form.handleChange('password')}
onBlur={form.handleBlur('password')}
/>
{form.touched.password && form.errors.password && (
<span>{form.errors.password}</span>
)}
<button type="submit" disabled={form.isSubmitting}>
Login
</button>
</form>
);
};最佳实践
1. 优先类型推断
typescript
// ✅ 好 - 让TypeScript推断
const [count, setCount] = useState(0);
// ❌ 不必要 - 显式指定明显的类型
const [count, setCount] = useState<number>(0);2. 处理可空值
typescript
// ✅ 好 - 明确处理null
const [user, setUser] = useState<User | null>(null);
if (user) {
console.log(user.name);
}
// ❌ 不好 - 强制断言
const [user, setUser] = useState<User>(null!);
console.log(user.name); // 可能出错3. 使用const断言
typescript
// ✅ 好 - 使用as const
const [status, setStatus] = useState<'idle' | 'loading'>('idle');
// ✅ 更好 - 使用enum或const对象
const Status = {
Idle: 'idle',
Loading: 'loading',
} as const;
type StatusType = typeof Status[keyof typeof Status];
const [status, setStatus] = useState<StatusType>(Status.Idle);4. 抽取类型定义
typescript
// ✅ 好 - 复用类型定义
interface User {
id: string;
name: string;
}
const [user, setUser] = useState<User | null>(null);
const [users, setUsers] = useState<User[]>([]);Hooks的类型定义是React TypeScript开发的核心,正确的类型定义能够提供类型安全和良好的开发体验。