Jotai 教程

1. 项目概述

Jotai 是一个基于原子的状态管理库,为 React 设计,它提供了一种简单、直观的方式来管理应用状态。与传统的状态管理库不同,Jotai 采用了原子化的思想,将状态分解为细小的、可组合的单元,从而实现更细粒度的状态管理和更新。

1.1 主要特性

  • 原子化状态:将状态分解为细小的、可组合的原子
  • 细粒度更新:只更新依赖于变化原子的组件
  • 无需 Provider:不需要在应用顶层包裹 Provider
  • 异步支持:内置支持异步原子
  • TypeScript 支持:内置类型定义
  • 与 React 生态系统集成:支持 React 18 的并发特性和 Suspense
  • 可组合性:原子可以相互依赖和组合
  • 性能优异:避免不必要的渲染
  • 轻量化:核心代码小巧,无依赖
  • 可扩展:支持中间件和持久化

1.2 适用场景

  • 中小型 React 应用
  • 需要细粒度状态控制的项目
  • 对性能有要求的应用
  • 不希望使用复杂状态管理库的项目
  • 需要与其他状态管理库共存的项目
  • 使用 React 18 并发特性的项目

2. 安装与设置

2.1 环境要求

  • Node.js 14.0 或更高版本
  • npm 或 yarn 包管理器
  • React 16.8 或更高版本(支持 Hooks)

2.2 安装步骤

# 使用 npm 安装
npm install jotai

# 使用 yarn 安装
yarn add jotai

# 使用 pnpm 安装
pnpm add jotai

2.3 基本项目结构

my-jotai-app/
├── src/
│   ├── atoms/           # 原子定义
│   │   ├── counterAtom.js     # 计数器原子
│   │   └── userAtom.js        # 用户原子
│   ├── components/      # 组件
│   │   ├── Counter.js         # 计数器组件
│   │   └── UserProfile.js     # 用户信息组件
│   ├── App.js           # 根组件
│   └── index.js         # 应用入口
├── package.json         # 项目配置
└── package-lock.json    # 依赖锁定

3. 核心概念

3.1 原子 (Atom)

原子是 Jotai 的核心概念,它是一个状态的容器,具有唯一的值。

// src/atoms/counterAtom.js
import { atom } from 'jotai';

// 创建一个基本原子
export const countAtom = atom(0);

// 创建一个派生原子
export const doubleCountAtom = atom((get) => get(countAtom) * 2);

// 创建一个可写的派生原子
export const incrementAtom = atom(
  (get) => get(countAtom),
  (get, set, amount = 1) => set(countAtom, get(countAtom) + amount)
);

3.2 基本原子

基本原子是最基础的原子类型,它存储一个简单的值。

// 创建一个基本原子
const countAtom = atom(0);

// 创建一个存储对象的原子
const userAtom = atom({
  name: 'John Doe',
  email: 'john@example.com'
});

3.3 派生原子

派生原子是基于其他原子计算出来的原子。

// 创建一个只读派生原子
const doubleCountAtom = atom((get) => get(countAtom) * 2);

// 创建一个可写派生原子
const updateCountAtom = atom(
  (get) => get(countAtom), // 读取函数
  (get, set, newValue) => set(countAtom, newValue) // 写入函数
);

3.4 异步原子

异步原子是用于处理异步操作的原子。

// 创建一个异步原子
const fetchUserAtom = atom(async () => {
  const response = await fetch('https://jsonplaceholder.typicode.com/users/1');
  const user = await response.json();
  return user;
});

3.5 使用原子

在组件中使用原子需要使用 useAtom hook。

import { useAtom } from 'jotai';
import { countAtom } from '../atoms/counterAtom';

function Counter() {
  const [count, setCount] = useAtom(countAtom);
  
  return (
    <div>
      <h1>Count: {count}</h1>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}

4. 基本使用

4.1 创建原子

基本原子

// src/atoms/counterAtom.js
import { atom } from 'jotai';

// 创建一个计数器原子
export const countAtom = atom(0);

// 创建一个增加计数的原子
export const incrementAtom = atom(
  (get) => get(countAtom),
  (get, set) => set(countAtom, get(countAtom) + 1)
);

// 创建一个减少计数的原子
export const decrementAtom = atom(
  (get) => get(countAtom),
  (get, set) => set(countAtom, get(countAtom) - 1)
);

// 创建一个重置计数的原子
export const resetAtom = atom(
  null, // 这个原子不存储值,只提供操作
  (_, set) => set(countAtom, 0)
);

用户原子

// src/atoms/userAtom.js
import { atom } from 'jotai';

// 创建一个用户原子
export const userAtom = atom(null);

// 创建一个登录原子
export const loginAtom = atom(
  null,
  async (_, set, { email, password }) => {
    // 模拟登录 API 调用
    await new Promise((resolve) => setTimeout(resolve, 1000));
    const user = {
      id: 1,
      name: 'John Doe',
      email: email
    };
    set(userAtom, user);
    return user;
  }
);

// 创建一个登出原子
export const logoutAtom = atom(
  null,
  (_, set) => set(userAtom, null)
);

4.2 在组件中使用

基本用法

// src/components/Counter.js
import React from 'react';
import { useAtom } from 'jotai';
import { countAtom, incrementAtom, decrementAtom, resetAtom } from '../atoms/counterAtom';

function Counter() {
  const [count] = useAtom(countAtom);
  const [, increment] = useAtom(incrementAtom);
  const [, decrement] = useAtom(decrementAtom);
  const [, reset] = useAtom(resetAtom);

  return (
    <div>
      <h1>Count: {count}</h1>
      <button onClick={increment}>Increment</button>
      <button onClick={decrement}>Decrement</button>
      <button onClick={reset}>Reset</button>
    </div>
  );
}

export default Counter;

使用派生原子

// src/components/DoubleCounter.js
import React from 'react';
import { useAtom } from 'jotai';
import { countAtom, doubleCountAtom } from '../atoms/counterAtom';

function DoubleCounter() {
  const [count, setCount] = useAtom(countAtom);
  const [doubleCount] = useAtom(doubleCountAtom);

  return (
    <div>
      <h1>Count: {count}</h1>
      <h2>Double Count: {doubleCount}</h2>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}

export default DoubleCounter;

使用异步原子

// src/components/UserProfile.js
import React from 'react';
import { useAtom } from 'jotai';
import { userAtom, loginAtom, logoutAtom } from '../atoms/userAtom';

function UserProfile() {
  const [user] = useAtom(userAtom);
  const [, login] = useAtom(loginAtom);
  const [, logout] = useAtom(logoutAtom);

  const handleLogin = async () => {
    await login({ email: 'john@example.com', password: 'password' });
  };

  if (!user) {
    return (
      <div>
        <h1>Please login</h1>
        <button onClick={handleLogin}>Login</button>
      </div>
    );
  }

  return (
    <div>
      <h1>User Profile</h1>
      <p>Name: {user.name}</p>
      <p>Email: {user.email}</p>
      <button onClick={logout}>Logout</button>
    </div>
  );
}

export default UserProfile;

4.3 原子组合

创建组合原子

// src/atoms/todoAtom.js
import { atom } from 'jotai';

// 创建一个 todos 原子
export const todosAtom = atom([]);

// 创建一个添加 todo 的原子
export const addTodoAtom = atom(
  null,
  (get, set, text) => {
    const todos = get(todosAtom);
    const newTodo = {
      id: Date.now(),
      text,
      completed: false
    };
    set(todosAtom, [...todos, newTodo]);
  }
);

// 创建一个切换 todo 状态的原子
export const toggleTodoAtom = atom(
  null,
  (get, set, id) => {
    const todos = get(todosAtom);
    set(todosAtom, todos.map(todo =>
      todo.id === id ? { ...todo, completed: !todo.completed } : todo
    ));
  }
);

// 创建一个删除 todo 的原子
export const removeTodoAtom = atom(
  null,
  (get, set, id) => {
    const todos = get(todosAtom);
    set(todosAtom, todos.filter(todo => todo.id !== id));
  }
);

// 创建一个完成的 todos 数量原子
export const completedTodosCountAtom = atom(
  (get) => get(todosAtom).filter(todo => todo.completed).length
);

在组件中使用组合原子

// src/components/TodoList.js
import React, { useState } from 'react';
import { useAtom } from 'jotai';
import { 
  todosAtom, 
  addTodoAtom, 
  toggleTodoAtom, 
  removeTodoAtom,
  completedTodosCountAtom 
} from '../atoms/todoAtom';

function TodoList() {
  const [todos] = useAtom(todosAtom);
  const [completedCount] = useAtom(completedTodosCountAtom);
  const [, addTodo] = useAtom(addTodoAtom);
  const [, toggleTodo] = useAtom(toggleTodoAtom);
  const [, removeTodo] = useAtom(removeTodoAtom);
  const [inputText, setInputText] = useState('');

  const handleAddTodo = () => {
    if (inputText.trim()) {
      addTodo(inputText);
      setInputText('');
    }
  };

  return (
    <div>
      <h1>Todo List</h1>
      <h2>Completed: {completedCount} / {todos.length}</h2>
      
      <div>
        <input
          type="text"
          value={inputText}
          onChange={(e) => setInputText(e.target.value)}
          placeholder="Add a todo"
        />
        <button onClick={handleAddTodo}>Add</button>
      </div>

      <ul>
        {todos.map((todo) => (
          <li key={todo.id}>
            <input
              type="checkbox"
              checked={todo.completed}
              onChange={() => toggleTodo(todo.id)}
            />
            <span style={{
              textDecoration: todo.completed ? 'line-through' : 'none'
            }}>
              {todo.text}
            </span>
            <button onClick={() => removeTodo(todo.id)}>Remove</button>
          </li>
        ))}
      </ul>
    </div>
  );
}

export default TodoList;

5. 高级功能

5.1 异步原子

基本异步原子

// src/atoms/apiAtom.js
import { atom } from 'jotai';

// 创建一个获取用户数据的异步原子
export const fetchUserAtom = atom(async () => {
  const response = await fetch('https://jsonplaceholder.typicode.com/users/1');
  if (!response.ok) {
    throw new Error('Failed to fetch user');
  }
  return response.json();
});

// 创建一个带参数的异步原子
export const fetchUserByIdAtom = atom(
  null,
  async (_, set, userId) => {
    const response = await fetch(`https://jsonplaceholder.typicode.com/users/${userId}`);
    if (!response.ok) {
      throw new Error('Failed to fetch user');
    }
    return response.json();
  }
);

与 Suspense 一起使用

// src/components/AsyncUser.js
import React, { Suspense } from 'react';
import { useAtom } from 'jotai';
import { fetchUserAtom } from '../atoms/apiAtom';

function UserContent() {
  const [user] = useAtom(fetchUserAtom);

  return (
    <div>
      <h1>User Profile</h1>
      <p>Name: {user.name}</p>
      <p>Email: {user.email}</p>
      <p>Phone: {user.phone}</p>
    </div>
  );
}

function AsyncUser() {
  return (
    <Suspense fallback={<div>Loading user data...</div>}>
      <UserContent />
    </Suspense>
  );
}

export default AsyncUser;

5.2 持久化原子

安装持久化插件

npm install jotai/utils

创建持久化原子

// src/atoms/persistAtom.js
import { atom } from 'jotai';
import { atomWithStorage } from 'jotai/utils';

// 创建一个持久化的计数器原子
export const persistedCountAtom = atomWithStorage('count', 0);

// 创建一个持久化的用户原子
export const persistedUserAtom = atomWithStorage('user', null);

在组件中使用

// src/components/PersistedCounter.js
import React from 'react';
import { useAtom } from 'jotai';
import { persistedCountAtom } from '../atoms/persistAtom';

function PersistedCounter() {
  const [count, setCount] = useAtom(persistedCountAtom);

  return (
    <div>
      <h1>Persisted Count: {count}</h1>
      <button onClick={() => setCount(count + 1)}>Increment</button>
      <button onClick={() => setCount(0)}>Reset</button>
    </div>
  );
}

export default PersistedCounter;

5.3 原子家族

创建原子家族

原子家族是用于创建一系列相关原子的工具。

// src/atoms/userFamilyAtom.js
import { atom } from 'jotai';

// 创建一个用户原子家族
const createUserAtom = (userId) => atom(async () => {
  const response = await fetch(`https://jsonplaceholder.typicode.com/users/${userId}`);
  return response.json();
});

// 使用映射存储创建的原子
const userAtoms = new Map();

export const useUserAtom = (userId) => {
  if (!userAtoms.has(userId)) {
    userAtoms.set(userId, createUserAtom(userId));
  }
  return userAtoms.get(userId);
};

在组件中使用原子家族

// src/components/UserList.js
import React, { Suspense } from 'react';
import { useAtom } from 'jotai';
import { useUserAtom } from '../atoms/userFamilyAtom';

function UserItem({ userId }) {
  const userAtom = useUserAtom(userId);
  const [user] = useAtom(userAtom);

  return (
    <div>
      <h3>{user.name}</h3>
      <p>{user.email}</p>
    </div>
  );
}

function UserList() {
  const userIds = [1, 2, 3, 4, 5];

  return (
    <div>
      <h1>User List</h1>
      {userIds.map((userId) => (
        <Suspense key={userId} fallback={<div>Loading user {userId}...</div>}>
          <UserItem userId={userId} />
        </Suspense>
      ))}
    </div>
  );
}

export default UserList;

5.4 TypeScript 支持

使用 TypeScript 创建原子

// src/atoms/counterAtom.ts
import { atom } from 'jotai';

// 创建一个计数器原子
export const countAtom = atom<number>(0);

// 创建一个派生原子
export const doubleCountAtom = atom<number>((get) => get(countAtom) * 2);

// 创建一个可写的派生原子
export const incrementAtom = atom(
  (get) => get(countAtom),
  (get, set, amount: number = 1) => set(countAtom, get(countAtom) + amount)
);

复杂类型

// src/atoms/userAtom.ts
import { atom } from 'jotai';

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

// 创建一个用户原子
export const userAtom = atom<User | null>(null);

// 创建一个登录原子
export const loginAtom = atom(
  null,
  async (_, set, credentials: { email: string; password: string }): Promise<User> => {
    // 模拟登录 API 调用
    await new Promise((resolve) => setTimeout(resolve, 1000));
    const user: User = {
      id: 1,
      name: 'John Doe',
      email: credentials.email
    };
    set(userAtom, user);
    return user;
  }
);

5.5 中间件

创建自定义中间件

// src/middleware/loggerMiddleware.js
import { atom } from 'jotai';

const loggerAtom = (get, set, update) => {
  console.log('Before update');
  const result = update(get, set);
  console.log('After update');
  return result;
};

// 创建一个带日志中间件的原子
const createLoggedAtom = (initialValue) => {
  const baseAtom = atom(initialValue);
  return atom(
    (get) => get(baseAtom),
    (get, set, arg) => {
      console.log('Updating atom with:', arg);
      return set(baseAtom, arg);
    }
  );
};

export const loggedCountAtom = createLoggedAtom(0);

在组件中使用

// src/components/LoggedCounter.js
import React from 'react';
import { useAtom } from 'jotai';
import { loggedCountAtom } from '../middleware/loggerMiddleware';

function LoggedCounter() {
  const [count, setCount] = useAtom(loggedCountAtom);

  return (
    <div>
      <h1>Logged Count: {count}</h1>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}

export default LoggedCounter;

6. 实用案例

6.1 购物车应用

创建购物车原子

// src/atoms/cartAtom.js
import { atom } from 'jotai';
import { atomWithStorage } from 'jotai/utils';

// 创建一个持久化的购物车原子
export const cartAtom = atomWithStorage('cart', []);

// 创建一个添加商品到购物车的原子
export const addToCartAtom = atom(
  null,
  (get, set, product) => {
    const cart = get(cartAtom);
    const existingItem = cart.find((item) => item.id === product.id);
    
    if (existingItem) {
      // 如果商品已存在,增加数量
      set(cartAtom, cart.map((item) =>
        item.id === product.id
          ? { ...item, quantity: item.quantity + 1 }
          : item
      ));
    } else {
      // 如果商品不存在,添加到购物车
      set(cartAtom, [...cart, { ...product, quantity: 1 }]);
    }
  }
);

// 创建一个从购物车移除商品的原子
export const removeFromCartAtom = atom(
  null,
  (get, set, productId) => {
    const cart = get(cartAtom);
    set(cartAtom, cart.filter((item) => item.id !== productId));
  }
);

// 创建一个更新商品数量的原子
export const updateQuantityAtom = atom(
  null,
  (get, set, { productId, quantity }) => {
    const cart = get(cartAtom);
    set(cartAtom, cart.map((item) =>
      item.id === productId ? { ...item, quantity } : item
    ));
  }
);

// 创建一个清空购物车的原子
export const clearCartAtom = atom(
  null,
  (_, set) => set(cartAtom, [])
);

// 创建一个计算购物车总商品数的原子
export const cartItemCountAtom = atom(
  (get) => get(cartAtom).reduce((total, item) => total + item.quantity, 0)
);

// 创建一个计算购物车总价格的原子
export const cartTotalPriceAtom = atom(
  (get) => get(cartAtom).reduce((total, item) => total + item.price * item.quantity, 0)
);

创建购物车组件

// src/components/Cart.js
import React from 'react';
import { useAtom } from 'jotai';
import {
  cartAtom,
  removeFromCartAtom,
  updateQuantityAtom,
  clearCartAtom,
  cartItemCountAtom,
  cartTotalPriceAtom
} from '../atoms/cartAtom';

function Cart() {
  const [cart] = useAtom(cartAtom);
  const [itemCount] = useAtom(cartItemCountAtom);
  const [totalPrice] = useAtom(cartTotalPriceAtom);
  const [, removeFromCart] = useAtom(removeFromCartAtom);
  const [, updateQuantity] = useAtom(updateQuantityAtom);
  const [, clearCart] = useAtom(clearCartAtom);

  if (cart.length === 0) {
    return <div>Your cart is empty</div>;
  }

  return (
    <div>
      <h1>Shopping Cart</h1>
      <button onClick={clearCart}>Clear Cart</button>
      <h2>Total Items: {itemCount}</h2>
      <h2>Total Price: ${totalPrice.toFixed(2)}</h2>
      <ul>
        {cart.map((item) => (
          <li key={item.id}>
            <h3>{item.name}</h3>
            <p>Price: ${item.price.toFixed(2)}</p>
            <div>
              <button
                onClick={() => updateQuantity({ productId: item.id, quantity: Math.max(1, item.quantity - 1) })
                }>
                -
              </button>
              <span>Quantity: {item.quantity}</span>
              <button
                onClick={() => updateQuantity({ productId: item.id, quantity: item.quantity + 1 })
                }>
                +
              </button>
            </div>
            <button onClick={() => removeFromCart(item.id)}>Remove</button>
          </li>
        ))}
      </ul>
    </div>
  );
}

export default Cart;

创建产品列表组件

// src/components/ProductList.js
import React from 'react';
import { useAtom } from 'jotai';
import { addToCartAtom } from '../atoms/cartAtom';

// 模拟产品数据
const products = [
  { id: 1, name: 'Product 1', price: 10.99 },
  { id: 2, name: 'Product 2', price: 19.99 },
  { id: 3, name: 'Product 3', price: 5.99 },
  { id: 4, name: 'Product 4', price: 29.99 },
];

function ProductList() {
  const [, addToCart] = useAtom(addToCartAtom);

  return (
    <div>
      <h1>Products</h1>
      <div style={{ display: 'flex', gap: '20px', flexWrap: 'wrap' }}>
        {products.map((product) => (
          <div key={product.id} style={{ border: '1px solid #ccc', padding: '10px', width: '200px' }}>
            <h2>{product.name}</h2>
            <p>Price: ${product.price.toFixed(2)}</p>
            <button onClick={() => addToCart(product)}>Add to Cart</button>
          </div>
        ))}
      </div>
    </div>
  );
}

export default ProductList;

6.2 主题切换应用

创建主题原子

// src/atoms/themeAtom.js
import { atom } from 'jotai';
import { atomWithStorage } from 'jotai/utils';

// 创建一个持久化的主题原子
export const themeAtom = atomWithStorage('theme', 'light');

// 创建一个切换主题的原子
export const toggleThemeAtom = atom(
  (get) => get(themeAtom),
  (get, set) => set(themeAtom, get(themeAtom) === 'light' ? 'dark' : 'light')
);

// 创建一个主题样式原子
export const themeStylesAtom = atom((get) => {
  const theme = get(themeAtom);
  return theme === 'light' ? {
    backgroundColor: '#fff',
    color: '#333',
  } : {
    backgroundColor: '#333',
    color: '#fff',
  };
});

创建主题切换组件

// src/components/ThemeToggle.js
import React from 'react';
import { useAtom } from 'jotai';
import { themeAtom, toggleThemeAtom, themeStylesAtom } from '../atoms/themeAtom';

function ThemeToggle() {
  const [theme] = useAtom(themeAtom);
  const [, toggleTheme] = useAtom(toggleThemeAtom);
  const [styles] = useAtom(themeStylesAtom);

  return (
    <div style={{
      ...styles,
      padding: '20px',
      minHeight: '200px'
    }}>
      <h1>Current Theme: {theme}</h1>
      <p>This component uses the current theme.</p>
      <button 
        onClick={toggleTheme}
        style={{
          backgroundColor: theme === 'light' ? '#333' : '#fff',
          color: theme === 'light' ? '#fff' : '#333',
          padding: '10px',
          border: 'none',
          cursor: 'pointer'
        }}
      >
        Toggle Theme
      </button>
    </div>
  );
}

export default ThemeToggle;

7. 性能优化

7.1 细粒度更新

Jotai 的核心优势之一是细粒度更新,它只会重新渲染依赖于变化原子的组件。

// 创建多个独立的原子
const countAtom = atom(0);
const userAtom = atom({ name: 'John' });

// 组件 A 只依赖于 countAtom
function ComponentA() {
  const [count] = useAtom(countAtom);
  console.log('Component A rendered');
  return <div>Count: {count}</div>;
}

// 组件 B 只依赖于 userAtom
function ComponentB() {
  const [user] = useAtom(userAtom);
  console.log('Component B rendered');
  return <div>User: {user.name}</div>;
}

// 当 countAtom 变化时,只有 Component A 会重新渲染
// 当 userAtom 变化时,只有 Component B 会重新渲染

7.2 使用 memo 优化

对于复杂的组件,可以使用 React.memo 进一步优化性能。

// src/components/OptimizedComponent.js
import React, { memo } from 'react';
import { useAtom } from 'jotai';
import { countAtom } from '../atoms/counterAtom';

const ExpensiveComponent = memo(() => {
  const [count] = useAtom(countAtom);
  console.log('Expensive component rendered');
  
  // 模拟昂贵的计算
  for (let i = 0; i < 100000000; i++) {
    // 计算
  }
  
  return <div>Count: {count}</div>;
});

function OptimizedComponent() {
  return (
    <div>
      <h1>Optimized Component</h1>
      <ExpensiveComponent />
    </div>
  );
}

export default OptimizedComponent;

7.3 批量更新

Jotai 会自动批量处理多个原子的更新,减少渲染次数。

// 批量更新多个原子
const updateMultipleAtoms = atom(
  null,
  (get, set) => {
    // 这些更新会被批量处理,只触发一次渲染
    set(countAtom, get(countAtom) + 1);
    set(userAtom, { ...get(userAtom), name: 'Updated Name' });
    set(todoAtom, [...get(todoAtom), { id: Date.now(), text: 'New Todo' }]);
  }
);

7.4 避免在渲染中创建原子

避免在组件渲染过程中创建新的原子,这会导致每次渲染都创建新的原子实例。

// 不好的做法
function BadComponent() {
  // 每次渲染都会创建新的原子
  const [count, setCount] = useAtom(atom(0));
  return <div>Count: {count}</div>;
}

// 好的做法:在组件外部创建原子
const countAtom = atom(0);

function GoodComponent() {
  const [count, setCount] = useAtom(countAtom);
  return <div>Count: {count}</div>;
}

8. 最佳实践

8.1 项目结构

  • 按功能组织原子:将相关的原子放在一起
  • 使用模块化导出:通过 index.js 导出所有原子
  • 分离业务逻辑:将复杂的业务逻辑从组件中移到原子中
  • 使用原子组合:通过组合原子来构建复杂状态

8.2 原子设计

  • 保持原子简洁:每个原子只负责一个简单的状态
  • 使用派生原子:通过派生原子来计算复杂状态
  • 使用异步原子:处理异步操作
  • 使用持久化原子:对于需要持久化的状态

8.3 组件使用

  • 只使用需要的原子:在组件中只使用必要的原子
  • 与 Suspense 一起使用:对于异步原子,使用 Suspense 处理加载状态
  • 使用 memo:对于复杂组件,使用 React.memo 优化性能
  • 避免在渲染中创建原子:在组件外部创建原子

8.4 性能优化

  • 利用细粒度更新:将状态分解为小的原子
  • 批量更新:将多个更新放在一个派生原子中
  • 使用缓存:对于昂贵的计算,使用缓存
  • 避免不必要的依赖:只依赖必要的原子

8.5 测试

  • 测试原子逻辑:测试原子的计算和更新逻辑
  • 测试组件:测试组件与原子的交互
  • 测试异步原子:测试异步操作和错误处理

9. 常见问题与解决方案

9.1 原子不更新

问题:更新原子后,组件没有重新渲染

解决方案

  • 检查是否正确使用了 useAtom hook
  • 确保在更新对象或数组时返回新的引用
  • 检查组件是否被 React.memo 包裹,导致没有重新渲染

9.2 异步原子错误

问题:异步原子失败或不执行

解决方案

  • 检查异步函数是否正确使用 async/await
  • 确保在异步原子中正确处理错误
  • 对于需要在组件中处理错误的情况,使用 error boundaries

9.3 持久化不工作

问题:使用 atomWithStorage 后状态没有持久化

解决方案

  • 检查存储键名是否正确
  • 确保浏览器支持 localStorage
  • 检查状态是否可序列化

9.4 组件渲染次数过多

问题:组件在原子更新时渲染次数过多

解决方案

  • 检查是否依赖了不必要的原子
  • 考虑将多个原子组合成一个派生原子
  • 使用 React.memo 包裹组件
  • 确保原子更新时返回新的引用,而不是修改原引用

9.5 与其他状态管理库集成

问题:需要在 Jotai 和其他状态管理库之间共享状态

解决方案

  • 使用 atom 包装其他状态管理库的状态
  • 使用派生原子从其他状态管理库中获取状态
  • 考虑将共享状态移到 Jotai 中管理

10. 参考资源

10.1 官方文档

10.2 学习资源

10.3 工具与库

10.4 相关库

  • Zustand - 轻量级状态管理库
  • Valtio - 基于代理的状态管理库
  • Redux - 传统状态管理库

11. 总结

Jotai 是一个基于原子的状态管理库,它提供了一种简单、直观的方式来管理 React 应用的状态。通过本教程,你应该已经掌握了 Jotai 的基本使用方法和高级功能,包括:

  • 核心概念(原子、派生原子、异步原子)
  • 基本使用
  • 原子组合
  • 异步原子和 Suspense
  • 持久化原子
  • 原子家族
  • TypeScript 支持
  • 性能优化
  • 最佳实践

Jotai 的设计理念是 "原子化状态管理",它将状态分解为细小的、可组合的原子,从而实现更细粒度的状态管理和更新。这种方法不仅使得状态管理更加灵活和可组合,还能提高应用的性能,避免不必要的渲染。

Jotai 适合从简单的个人项目到复杂的企业级应用的各种场景。它的灵活性和可扩展性使其成为 React 生态系统中一个重要的状态管理解决方案。

随着你对 Jotai 的深入了解,你会发现它不仅是一个状态管理库,更是一种开发理念:通过原子化的思想,让状态管理变得更加简单、可预测和高效。希望本教程对你的学习和开发有所帮助!

« 上一篇 Zustand 教程 - 轻量级状态管理库 下一篇 » Recoil 教程 - Facebook 开发的 React 状态管理库