Jotai: 速查指南

常用的 jotai 用法
更新于: 2025-12-20 13:34:55

安装

pnpm add jotai

cheatsheet

用法代码
定义 Atom
// atoms/index.ts
export const countAtom = atom(0);
rw: 使用 set/get Atom
// component
const nameAtom = atom('John');

const upperNameAtom = atom(
  (get) => get(nameAtom).toUpperCase(), // 只读
  (get, set, newName: string) => set(nameAtom, newName) // 写回原始 atom
);
read: get Atom
-
write: set Atom(null)
const writeOnlyAtom = atom(
  null, // it's a convention to pass `null` for the first argument
  (get, set, update) => {
    // `update` is any single value we receive for updating this atom
    set(priceAtom, get(priceAtom) - update.discount)
    // or we can pass a function as the second parameter
    // the function will be invoked,
    //  receiving the atom's current value as its first parameter
    set(priceAtom, (price) => price - update.discount)
  },
)
useHydrateAtoms SSR
防止状态不一致的闪烁问题
import { atom, useAtom } from 'jotai'
import { useHydrateAtoms } from 'jotai/utils'

const countAtom = atom(0)
const CounterPage = ({ countFromServer }) => {
  useHydrateAtoms([[countAtom, countFromServer]])
  const [count] = useAtom(countAtom)
  // count would be the value of `countFromServer`, not 0.
}
使用 localStorage
import { useAtom } from 'jotai';
import { atomWithStorage } from 'jotai/utils';

const theme = atomWithStorage('dark', false);

常规使用 atom,可以多模块复用,链接: https://tutorial.jotai.org/quick-start/intro

import { atom, useAtom } from 'jotai';

const counter = atom(0);

export default function Page() {
  const [count, setCounter] = useAtom(counter);
  const onClick = () => setCounter(prev => prev + 1);
  return (
    <div>
      <h1>{count}</h1>
      <button onClick={onClick}>Click</button>
    </div>
  )
}

使用 localStorage

import { useAtom } from 'jotai';
import { atomWithStorage } from 'jotai/utils';

const theme = atomWithStorage('dark', false);

export default function Page() {
  const [appTheme, setAppTheme] = useAtom(theme);
  const handleClick = () => setAppTheme(!appTheme);
  return (
    <div className={appTheme? 'dark': 'light'}>
      <h1>This is a theme switcher</h1>
      <button onClick={handleClick}>{appTheme? 'DARK': 'LIGHT'}</button>
    </div>
  )
}

read: useAtomValue: 只读

write: useSetAtom: 只写

链接: https://chat.qwen.ai/c/28ab834e-c680-4247-ae3a-447ea8a9b6de

const countAtom = atom(0)

const Counter = () => {
  const setCount = useSetAtom(countAtom)
  const count = useAtomValue(countAtom)

  return (
    <>
      <div>count: {count}</div>
      <button onClick={() => setCount(count + 1)}>+1</button>
    </>
  )
}

Write Only

不是特别理解这种用法…

const switchAtom = atom(false)

const SetTrueButton = () => {
  const setCount = useSetAtom(switchAtom)
  const setTrue = () => setCount(true)

  return (
    <div>
      <button onClick={setTrue}>Set True</button>
    </div>
  )
}

const SetFalseButton = () => {
  const setCount = useSetAtom(switchAtom)
  const setFalse = () => setCount(false)

  return (
    <div>
      <button onClick={setFalse}>Set False</button>
    </div>
  )
}

export default function App() {
  const state = useAtomValue(switchAtom)

  return (
    <div>
      State: <b>{state.toString()}</b>
      <SetTrueButton />
      <SetFalseButton />
    </div>
  )
}

读/写分离的意义

  • 读写职责分离(Separation of Concerns)
    • 读 atom(read-only):只提供 get 函数,用于派生状态。
    • 写 atom(write-only):只提供 set 函数,常用于触发副作用或命令。
    • 读写 atom(read-write):同时提供 getset

这种分离让开发者能更清晰地表达意图:

  • 如果一个 atom 只用于计算派生值(如 fullName = firstName + lastName),就不应该暴露 set
  • 如果一个 atom 仅用于触发动作(如日志记录、导航),就不需要 get。这增强了代码的可读性可维护性

useAtom/useAtomValue

React 生态推崇 “最小权限原则”:

  • 如果组件不需要修改状态,就不应获得修改能力。
  • 类似于 useContext + useReducer 中,将 dispatch 单独传递给需要的组件。
const fullNameAtom = atom((get) => get(firstNameAtom) + ' ' + get(lastNameAtom));

✅ useAtomValue(fullNameAtom):✅ 类型安全,正常工作。
❌ useAtom(fullNameAtom):
TypeScript 会报错(因为该 atom 不可写),或运行时报错(取决于 Jotai 版本)。
所以 useAtomValue 是消费只读 atom 的唯一正确方式。

useHydrateAtoms

import { atom, useAtom } from 'jotai'
import { useHydrateAtoms } from 'jotai/utils'

const countAtom = atom(0)
const CounterPage = ({ countFromServer }) => {
  useHydrateAtoms([[countAtom, countFromServer]])
  const [count] = useAtom(countAtom)
  // count would be the value of `countFromServer`, not 0.
}