useRecoilValue #

什么是 useRecoilValue? #

useRecoilValue 是一个只读 Hook,用于获取 Recoil 状态的值。当组件只需要读取状态而不需要修改时,使用这个 Hook 可以获得更好的性能。

jsx
const value = useRecoilValue(state);

基本用法 #

读取 Atom #

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

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

function CountDisplay() {
  const count = useRecoilValue(countState);
  
  return <div>Current count: {count}</div>;
}

读取 Selector #

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

const firstNameState = atom({
  key: 'firstName',
  default: 'John',
});

const lastNameState = atom({
  key: 'lastName',
  default: 'Doe',
});

const fullNameState = selector({
  key: 'fullName',
  get: ({ get }) => {
    return `${get(firstNameState)} ${get(lastNameState)}`;
  },
});

function NameDisplay() {
  const fullName = useRecoilValue(fullNameState);
  
  return <div>Full Name: {fullName}</div>;
}

性能优势 #

细粒度订阅 #

组件只订阅它使用的状态:

jsx
const userState = atom({
  key: 'user',
  default: {
    name: 'John',
    email: 'john@example.com',
    age: 30,
  },
});

function UserName() {
  const user = useRecoilValue(userState);
  return <span>{user.name}</span>;
}

function UserEmail() {
  const user = useRecoilValue(userState);
  return <span>{user.email}</span>;
}

避免不必要的重渲染 #

使用 useRecoilValue 而不是 useRecoilState,当只需要读取时:

jsx
function DisplayOnlyComponent() {
  const count = useRecoilValue(countState);
  return <div>{count}</div>;
}

对比使用 useRecoilState

jsx
function LessOptimizedComponent() {
  const [count, setCount] = useRecoilState(countState);
  
  return <div>{count}</div>;
}

实战示例:统计信息 #

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

const todoListState = atom({
  key: 'todoList',
  default: [
    { id: 1, text: 'Learn Recoil', completed: true },
    { id: 2, text: 'Build an app', completed: false },
    { id: 3, text: 'Write tests', completed: false },
  ],
});

const todoStatsState = selector({
  key: 'todoStats',
  get: ({ get }) => {
    const list = get(todoListState);
    const total = list.length;
    const completed = list.filter(item => item.completed).length;
    const active = total - completed;
    const percentComplete = total === 0 ? 0 : (completed / total) * 100;
    
    return { total, completed, active, percentComplete };
  },
});

function TodoStats() {
  const stats = useRecoilValue(todoStatsState);
  
  return (
    <div>
      <p>Total: {stats.total}</p>
      <p>Active: {stats.active}</p>
      <p>Completed: {stats.completed}</p>
      <p>Progress: {stats.percentComplete.toFixed(1)}%</p>
    </div>
  );
}

实战示例:过滤列表 #

jsx
import { atom, selector, useRecoilValue, useRecoilState } from 'recoil';

const productsState = atom({
  key: 'products',
  default: [
    { id: 1, name: 'Laptop', category: 'electronics', price: 999 },
    { id: 2, name: 'T-Shirt', category: 'clothing', price: 29 },
    { id: 3, name: 'Phone', category: 'electronics', price: 699 },
    { id: 4, name: 'Jeans', category: 'clothing', price: 59 },
  ],
});

const filterState = atom({
  key: 'filter',
  default: 'all',
});

const filteredProductsState = selector({
  key: 'filteredProducts',
  get: ({ get }) => {
    const filter = get(filterState);
    const products = get(productsState);
    
    if (filter === 'all') return products;
    return products.filter(p => p.category === filter);
  },
});

const productCountState = selector({
  key: 'productCount',
  get: ({ get }) => get(filteredProductsState).length,
});

function ProductList() {
  const products = useRecoilValue(filteredProductsState);
  const count = useRecoilValue(productCountState);
  
  return (
    <div>
      <p>Showing {count} products</p>
      <ul>
        {products.map(product => (
          <li key={product.id}>
            {product.name} - ${product.price}
          </li>
        ))}
      </ul>
    </div>
  );
}

function FilterControls() {
  const [filter, setFilter] = useRecoilState(filterState);
  
  return (
    <select value={filter} onChange={(e) => setFilter(e.target.value)}>
      <option value="all">All</option>
      <option value="electronics">Electronics</option>
      <option value="clothing">Clothing</option>
    </select>
  );
}

与其他 Hook 对比 #

Hook 读取 写入 订阅更新
useRecoilState
useRecoilValue
useSetRecoilState
useRecoilValueLoadable

异步状态 #

对于异步 Selector,可以使用 useRecoilValue 配合 Suspense:

jsx
import { Suspense } from 'react';

const userQueryState = selector({
  key: 'userQuery',
  get: async ({ get }) => {
    const userId = get(userIdState);
    const response = await fetch(`/api/users/${userId}`);
    return response.json();
  },
});

function UserProfile() {
  const user = useRecoilValue(userQueryState);
  
  return (
    <div>
      <h2>{user.name}</h2>
      <p>{user.email}</p>
    </div>
  );
}

function App() {
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <UserProfile />
    </Suspense>
  );
}

多个状态读取 #

jsx
function Dashboard() {
  const user = useRecoilValue(userState);
  const notifications = useRecoilValue(notificationsState);
  const settings = useRecoilValue(settingsState);
  
  return (
    <div>
      <h1>Welcome, {user.name}</h1>
      <p>You have {notifications.length} notifications</p>
      <p>Theme: {settings.theme}</p>
    </div>
  );
}

条件渲染 #

jsx
function ConditionalDisplay() {
  const isLoggedIn = useRecoilValue(isLoggedInState);
  
  if (!isLoggedIn) {
    return <div>Please log in</div>;
  }
  
  const user = useRecoilValue(userState);
  
  return <div>Welcome, {user.name}</div>;
}

TypeScript 支持 #

tsx
import { atom, selector, useRecoilValue } from 'recoil';

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

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

const userNameState = selector<string>({
  key: 'userName',
  get: ({ get }) => {
    const user = get(userState);
    return user?.name ?? 'Guest';
  },
});

function UserGreeting() {
  const userName = useRecoilValue(userNameState);
  
  return <h1>Hello, {userName}!</h1>;
}

完整示例:仪表盘 #

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

const salesDataState = atom({
  key: 'salesData',
  default: [
    { month: 'Jan', sales: 4000 },
    { month: 'Feb', sales: 3000 },
    { month: 'Mar', sales: 5000 },
    { month: 'Apr', sales: 4500 },
  ],
});

const totalSalesState = selector({
  key: 'totalSales',
  get: ({ get }) => {
    const data = get(salesDataState);
    return data.reduce((sum, item) => sum + item.sales, 0);
  },
});

const averageSalesState = selector({
  key: 'averageSales',
  get: ({ get }) => {
    const data = get(salesDataState);
    const total = get(totalSalesState);
    return total / data.length;
  },
});

const maxSalesState = selector({
  key: 'maxSales',
  get: ({ get }) => {
    const data = get(salesDataState);
    return Math.max(...data.map(item => item.sales));
  },
});

function DashboardStats() {
  const total = useRecoilValue(totalSalesState);
  const average = useRecoilValue(averageSalesState);
  const max = useRecoilValue(maxSalesState);
  
  return (
    <div className="stats">
      <div className="stat-card">
        <h3>Total Sales</h3>
        <p className="value">${total.toLocaleString()}</p>
      </div>
      <div className="stat-card">
        <h3>Average Sales</h3>
        <p className="value">${average.toLocaleString()}</p>
      </div>
      <div className="stat-card">
        <h3>Best Month</h3>
        <p className="value">${max.toLocaleString()}</p>
      </div>
    </div>
  );
}

总结 #

useRecoilValue 的核心要点:

特点 说明
只读 不能修改状态
订阅 状态变化时组件重渲染
性能 比使用 useRecoilState 更优
适用 只需要读取状态的组件

下一步,让我们学习 useSetRecoilState,了解只写状态的 Hook。

最后更新:2026-03-28