TypeScript集成 #
安装 #
Recoil 自带 TypeScript 类型定义:
bash
npm install recoil
基本类型定义 #
Atom 类型 #
tsx
import { atom } from 'recoil';
const countState = atom<number>({
key: 'countState',
default: 0,
});
const textState = atom<string>({
key: 'textState',
default: '',
});
const isActiveState = atom<boolean>({
key: 'isActiveState',
default: false,
});
对象类型 #
tsx
interface User {
id: number;
name: string;
email: string;
role: 'admin' | 'user' | 'guest';
}
const userState = atom<User | null>({
key: 'userState',
default: null,
});
数组类型 #
tsx
interface Todo {
id: number;
text: string;
completed: boolean;
createdAt: Date;
}
const todoListState = atom<Todo[]>({
key: 'todoListState',
default: [],
});
Selector 类型 #
只读 Selector #
tsx
import { selector } from 'recoil';
const userNameState = selector<string>({
key: 'userNameState',
get: ({ get }) => {
const user = get(userState);
return user?.name ?? 'Guest';
},
});
可写 Selector #
tsx
import { selector, DefaultValue } from 'recoil';
const tempCelsiusState = atom<number>({
key: 'tempCelsius',
default: 25,
});
const tempFahrenheitState = selector<number>({
key: 'tempFahrenheit',
get: ({ get }) => {
const celsius = get(tempCelsiusState);
return celsius * 9 / 5 + 32;
},
set: ({ set }, newValue) => {
if (newValue instanceof DefaultValue) {
set(tempCelsiusState, 25);
return;
}
set(tempCelsiusState, (newValue - 32) * 5 / 9);
},
});
复杂返回类型 #
tsx
interface TodoStats {
total: number;
completed: number;
active: number;
percentComplete: number;
}
const todoStatsState = selector<TodoStats>({
key: 'todoStatsState',
get: ({ get }) => {
const list = get(todoListState);
const total = list.length;
const completed = list.filter(t => t.completed).length;
return {
total,
completed,
active: total - completed,
percentComplete: total === 0 ? 0 : (completed / total) * 100,
};
},
});
atomFamily 类型 #
tsx
import { atomFamily } from 'recoil';
interface Todo {
id: number;
text: string;
completed: boolean;
}
const todoFamily = atomFamily<Todo, number>({
key: 'todoFamily',
default: (id) => ({
id,
text: '',
completed: false,
}),
});
对象参数类型 #
tsx
interface QueryParams {
category: string;
page: number;
sortBy: string;
}
const filterFamily = atomFamily<QueryParams, QueryParams>({
key: 'filterFamily',
default: (params) => params,
});
selectorFamily 类型 #
tsx
import { selectorFamily } from 'recoil';
interface User {
id: number;
name: string;
email: string;
}
const userQueryState = selectorFamily<User, number>({
key: 'userQueryState',
get: (userId) => async () => {
const response = await fetch(`/api/users/${userId}`);
return response.json();
},
});
多参数类型 #
tsx
interface SearchParams {
query: string;
category: string;
page: number;
}
interface SearchResult {
items: Item[];
total: number;
hasMore: boolean;
}
const searchQueryState = selectorFamily<SearchResult, SearchParams>({
key: 'searchQueryState',
get: (params) => async () => {
const query = new URLSearchParams({
q: params.query,
category: params.category,
page: params.page.toString(),
});
const response = await fetch(`/api/search?${query}`);
return response.json();
},
});
Hooks 类型 #
useRecoilState #
tsx
import { useRecoilState } from 'recoil';
function Counter() {
const [count, setCount] = useRecoilState(countState);
const increment = () => setCount(c => c + 1);
return <button onClick={increment}>{count}</button>;
}
useRecoilValue #
tsx
import { useRecoilValue } from 'recoil';
function UserName() {
const name = useRecoilValue(userNameState);
return <span>{name}</span>;
}
useSetRecoilState #
tsx
import { useSetRecoilState } from 'recoil';
function UpdateButton() {
const setUser = useSetRecoilState(userState);
const handleClick = () => {
setUser({ id: 1, name: 'John', email: 'john@example.com' });
};
return <button onClick={handleClick}>Update</button>;
}
useRecoilValueLoadable #
tsx
import { useRecoilValueLoadable } from 'recoil';
function UserProfile({ userId }: { userId: number }) {
const userLoadable = useRecoilValueLoadable(userQueryState(userId));
switch (userLoadable.state) {
case 'loading':
return <div>Loading...</div>;
case 'hasError':
return <div>Error: {userLoadable.contents.message}</div>;
case 'hasValue':
return <UserCard user={userLoadable.contents} />;
}
}
自定义 Hooks #
tsx
import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil';
interface UseTodoReturn {
todos: Todo[];
addTodo: (text: string) => void;
toggleTodo: (id: number) => void;
deleteTodo: (id: number) => void;
stats: TodoStats;
}
function useTodo(): UseTodoReturn {
const [todos, setTodos] = useRecoilState(todoListState);
const stats = useRecoilValue(todoStatsState);
const addTodo = (text: string) => {
setTodos(prev => [
...prev,
{ id: Date.now(), text, completed: false, createdAt: new Date() },
]);
};
const toggleTodo = (id: number) => {
setTodos(prev => prev.map(todo =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo
));
};
const deleteTodo = (id: number) => {
setTodos(prev => prev.filter(todo => todo.id !== id));
};
return { todos, addTodo, toggleTodo, deleteTodo, stats };
}
Effects 类型 #
tsx
import { AtomEffect } from 'recoil';
const localStorageEffect = <T>(key: string): AtomEffect<T> => ({
setSelf,
onSet,
}) => {
const savedValue = localStorage.getItem(key);
if (savedValue != null) {
setSelf(JSON.parse(savedValue) as T);
}
onSet((newValue, _, isReset) => {
isReset
? localStorage.removeItem(key)
: localStorage.setItem(key, JSON.stringify(newValue));
});
};
const todoListState = atom<Todo[]>({
key: 'todoListState',
default: [],
effects: [localStorageEffect<Todo[]>('todos')],
});
类型工具 #
创建类型安全的 Atom 工厂 #
tsx
import { atom, RecoilState } from 'recoil';
function createAtom<T>(key: string, defaultValue: T): RecoilState<T> {
return atom<T>({
key,
default: defaultValue,
});
}
const countState = createAtom('countState', 0);
const textState = createAtom('textState', '');
类型安全的 Selector 工厂 #
tsx
import { selector, RecoilValueReadOnly } from 'recoil';
function createSelector<T, R>(
key: string,
dep: RecoilValueReadOnly<T>,
transform: (value: T) => R
): RecoilValueReadOnly<R> {
return selector<R>({
key,
get: ({ get }) => transform(get(dep)),
});
}
const doubleCountState = createSelector('doubleCount', countState, c => c * 2);
完整示例 #
tsx
import { atom, selector, useRecoilState, useRecoilValue } from 'recoil';
interface User {
id: number;
name: string;
email: string;
}
interface UserState {
currentUser: User | null;
isLoading: boolean;
error: string | null;
}
const userState = atom<UserState>({
key: 'userState',
default: {
currentUser: null,
isLoading: false,
error: null,
},
});
const isLoggedInState = selector<boolean>({
key: 'isLoggedInState',
get: ({ get }) => {
const { currentUser } = get(userState);
return currentUser !== null;
},
});
const userNameState = selector<string>({
key: 'userNameState',
get: ({ get }) => {
const { currentUser } = get(userState);
return currentUser?.name ?? 'Guest';
},
});
function useAuth() {
const [state, setState] = useRecoilState(userState);
const isLoggedIn = useRecoilValue(isLoggedInState);
const login = async (email: string, password: string) => {
setState(prev => ({ ...prev, isLoading: true, error: null }));
try {
const response = await fetch('/api/login', {
method: 'POST',
body: JSON.stringify({ email, password }),
});
if (!response.ok) {
throw new Error('Login failed');
}
const user = await response.json();
setState({ currentUser: user, isLoading: false, error: null });
} catch (error) {
setState(prev => ({
...prev,
isLoading: false,
error: error instanceof Error ? error.message : 'Unknown error',
}));
}
};
const logout = () => {
setState({ currentUser: null, isLoading: false, error: null });
};
return {
user: state.currentUser,
isLoading: state.isLoading,
error: state.error,
isLoggedIn,
login,
logout,
};
}
总结 #
TypeScript 集成的核心要点:
| 要点 | 说明 |
|---|---|
| Atom 类型 | atom<T>({ ... }) |
| Selector 类型 | selector<T>({ ... }) |
| atomFamily 类型 | atomFamily<T, P>({ ... }) |
| selectorFamily 类型 | selectorFamily<T, P>({ ... }) |
| Effects 类型 | AtomEffect<T> |
下一步,让我们学习 测试策略,了解 Recoil 应用的测试方法。
最后更新:2026-03-28