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