useRecoilCallback #

什么是 useRecoilCallback? #

useRecoilCallback 是一个 Hook,用于在回调函数中命令式地访问 Recoil 状态。它允许你在不订阅状态的情况下读取和写入状态,非常适合用于事件处理、副作用操作和批量更新。

jsx
const callback = useRecoilCallback((params) => (args) => {
  
}, deps);

基本用法 #

读取状态 #

jsx
import { atom, useRecoilCallback } from 'recoil';

const countState = atom({
  key: 'countState',
  default: 0,
});

function LogButton() {
  const logCount = useRecoilCallback(({ snapshot }) => () => {
    const count = snapshot.getLoadable(countState).contents;
    console.log('Current count:', count);
  });
  
  return <button onClick={logCount}>Log Count</button>;
}

写入状态 #

jsx
import { atom, useRecoilCallback } from 'recoil';

const countState = atom({
  key: 'countState',
  default: 0,
});

function BatchUpdateButton() {
  const incrementMultiple = useRecoilCallback(({ set }) => () => {
    for (let i = 0; i < 10; i++) {
      set(countState, prev => prev + 1);
    }
  });
  
  return <button onClick={incrementMultiple}>Increment 10 times</button>;
}

Callback 参数 #

jsx
useRecoilCallback(({ snapshot, set, reset, refresh, gotoSnapshot, transact_UNSTABLE }) => {
  
}, deps);
参数 说明
snapshot 只读状态快照
set 设置状态值
reset 重置状态到默认值
refresh 刷新 Selector 缓存
gotoSnapshot 恢复到指定快照
transact_UNSTABLE 批量事务更新

Snapshot API #

getLoadable #

同步获取状态值:

jsx
const logUser = useRecoilCallback(({ snapshot }) => () => {
  const userLoadable = snapshot.getLoadable(userState);
  
  if (userLoadable.state === 'hasValue') {
    console.log('User:', userLoadable.contents);
  }
});

getPromise #

异步获取状态值:

jsx
const fetchUser = useRecoilCallback(({ snapshot }) => async () => {
  const user = await snapshot.getPromise(userState);
  console.log('User:', user);
});

getNodes #

获取所有状态节点:

jsx
const logAllStates = useRecoilCallback(({ snapshot }) => () => {
  const nodes = snapshot.getNodes_UNSTABLE();
  for (const node of nodes) {
    console.log(node.key, snapshot.getLoadable(node).contents);
  }
});

实战示例:批量操作 #

jsx
import { atom, useRecoilCallback, useRecoilValue } from 'recoil';

const todoListState = atom({
  key: 'todoList',
  default: [],
});

function TodoActions() {
  const todos = useRecoilValue(todoListState);
  
  const markAllComplete = useRecoilCallback(({ set }) => () => {
    set(todoListState, todos.map(todo => ({ ...todo, completed: true })));
  }, [todos]);
  
  const deleteCompleted = useRecoilCallback(({ set }) => () => {
    set(todoListState, todos.filter(todo => !todo.completed));
  }, [todos]);
  
  const clearAll = useRecoilCallback(({ reset }) => () => {
    reset(todoListState);
  });
  
  return (
    <div>
      <button onClick={markAllComplete}>Mark All Complete</button>
      <button onClick={deleteCompleted}>Delete Completed</button>
      <button onClick={clearAll}>Clear All</button>
    </div>
  );
}

实战示例:表单提交 #

jsx
import { atom, useRecoilCallback, useRecoilValue } from 'recoil';

const formState = atom({
  key: 'form',
  default: {
    username: '',
    email: '',
    password: '',
  },
});

const isSubmittingState = atom({
  key: 'isSubmitting',
  default: false,
});

function SubmitButton() {
  const submitForm = useRecoilCallback(({ set, reset, snapshot }) => async () => {
    set(isSubmittingState, true);
    
    try {
      const form = await snapshot.getPromise(formState);
      const response = await fetch('/api/submit', {
        method: 'POST',
        body: JSON.stringify(form),
      });
      
      if (response.ok) {
        reset(formState);
        alert('Form submitted successfully!');
      }
    } catch (error) {
      console.error('Submit failed:', error);
    } finally {
      set(isSubmittingState, false);
    }
  });
  
  return <button onClick={submitForm}>Submit</button>;
}

实战示例:数据同步 #

jsx
import { atom, useRecoilCallback } from 'recoil';

const userState = atom({
  key: 'user',
  default: null,
});

const lastSyncState = atom({
  key: 'lastSync',
  default: null,
});

function SyncButton() {
  const syncData = useRecoilCallback(({ set, snapshot }) => async () => {
    const user = await snapshot.getPromise(userState);
    
    if (!user) return;
    
    const response = await fetch(`/api/sync/${user.id}`);
    const data = await response.json();
    
    set(userState, data.user);
    set(lastSyncState, new Date().toISOString());
  });
  
  return <button onClick={syncData}>Sync</button>;
}

实战示例:撤销/重做 #

jsx
import { atom, useRecoilCallback, useRecoilValue } from 'recoil';

const historyState = atom({
  key: 'history',
  default: [],
});

const historyIndexState = atom({
  key: 'historyIndex',
  default: -1,
});

const canvasState = atom({
  key: 'canvas',
  default: null,
});

function useHistory() {
  const undo = useRecoilCallback(({ set, snapshot }) => async () => {
    const history = await snapshot.getPromise(historyState);
    const index = await snapshot.getPromise(historyIndexState);
    
    if (index > 0) {
      set(historyIndexState, index - 1);
      set(canvasState, history[index - 1]);
    }
  });
  
  const redo = useRecoilCallback(({ set, snapshot }) => async () => {
    const history = await snapshot.getPromise(historyState);
    const index = await snapshot.getPromise(historyIndexState);
    
    if (index < history.length - 1) {
      set(historyIndexState, index + 1);
      set(canvasState, history[index + 1]);
    }
  });
  
  const pushHistory = useRecoilCallback(({ set, snapshot }) => async (state) => {
    const history = await snapshot.getPromise(historyState);
    const index = await snapshot.getPromise(historyIndexState);
    
    const newHistory = history.slice(0, index + 1);
    newHistory.push(state);
    
    set(historyState, newHistory);
    set(historyIndexState, newHistory.length - 1);
    set(canvasState, state);
  });
  
  return { undo, redo, pushHistory };
}

实战示例:条件更新 #

jsx
import { atom, useRecoilCallback } from 'recoil';

const balanceState = atom({
  key: 'balance',
  default: 1000,
});

function WithdrawButton({ amount }) {
  const withdraw = useRecoilCallback(({ set, snapshot }) => async () => {
    const balance = await snapshot.getPromise(balanceState);
    
    if (balance < amount) {
      alert('Insufficient funds');
      return;
    }
    
    set(balanceState, balance - amount);
    console.log(`Withdrew $${amount}. New balance: $${balance - amount}`);
  });
  
  return <button onClick={withdraw}>Withdraw ${amount}</button>;
}

事务更新 #

使用 transact_UNSTABLE 进行批量原子更新:

jsx
const batchUpdate = useRecoilCallback(({ transact_UNSTABLE }) => () => {
  transact_UNSTABLE(({ set, get }) => {
    const count = get(countState);
    set(countState, count + 1);
    set(lastUpdatedState, Date.now());
    set(updateCountState, prev => prev + 1);
  });
});

与 useEffect 结合 #

jsx
function DataFetcher({ userId }) {
  const fetchData = useRecoilCallback(({ set, snapshot }) => async () => {
    const response = await fetch(`/api/users/${userId}`);
    const user = await response.json();
    
    set(userState, user);
  }, [userId]);
  
  useEffect(() => {
    fetchData();
  }, [fetchData]);
  
  return null;
}

TypeScript 支持 #

tsx
import { atom, useRecoilCallback, RecoilState } from 'recoil';

interface User {
  id: number;
  name: string;
}

const userState = atom<User | null>({
  key: 'userState',
  default: null,
});

function UserActions() {
  const updateUser = useRecoilCallback(
    ({ set }) => (updates: Partial<User>) => {
      set(userState, (prev) => prev ? { ...prev, ...updates } : null);
    },
    []
  );
  
  return <button onClick={() => updateUser({ name: 'New Name' })}>Update</button>;
}

完整示例:购物车结账 #

jsx
import { atom, useRecoilCallback, useRecoilValue } from 'recoil';

const cartState = atom({
  key: 'cart',
  default: [],
});

const orderState = atom({
  key: 'order',
  default: null,
});

const isProcessingState = atom({
  key: 'isProcessing',
  default: false,
});

function CheckoutButton() {
  const cart = useRecoilValue(cartState);
  
  const checkout = useRecoilCallback(({ set, reset, snapshot }) => async () => {
    if (cart.length === 0) {
      alert('Cart is empty');
      return;
    }
    
    set(isProcessingState, true);
    
    try {
      const total = cart.reduce((sum, item) => sum + item.price * item.quantity, 0);
      
      const response = await fetch('/api/checkout', {
        method: 'POST',
        body: JSON.stringify({ items: cart, total }),
      });
      
      const order = await response.json();
      
      set(orderState, order);
      reset(cartState);
      
      alert(`Order placed! Order ID: ${order.id}`);
    } catch (error) {
      console.error('Checkout failed:', error);
      alert('Checkout failed. Please try again.');
    } finally {
      set(isProcessingState, false);
    }
  }, [cart]);
  
  return (
    <button onClick={checkout} disabled={cart.length === 0}>
      Checkout (${cart.reduce((sum, item) => sum + item.price * item.quantity, 0).toFixed(2)})
    </button>
  );
}

总结 #

useRecoilCallback 的核心要点:

特点 说明
命令式 不订阅状态,按需访问
批量操作 支持批量更新多个状态
异步支持 支持异步操作
事件处理 适合用于事件回调

下一步,让我们学习 useRecoilValueLoadable,了解异步状态处理。

最后更新:2026-03-28