JotaiJotai

Jotai
React 原始而灵活的状态管理

Devtools

安装

jotai-devtools 安装到你的项目中以开始使用。

npm i jotai-devtools

注意事项

  • <DevTools/> 已优化为生产构建的 tree-shakable,仅在非生产环境中工作
  • Hooks 是仅开发者使用的,设计为在非生产环境中工作
  • 欢迎反馈,如有问题请在 Jotai DevTools GitHub Repo 提出

UI DevTools

使用基于 UI 的 Jotai DevTool 提升你的开发体验。

Babel 插件设置 - (可选但强烈推荐)

使用 Jotai babel 插件以获得最佳的调试体验。在 babel 页面和/或 swc 页面找到完整指南。

示例:

{
"presets": [
// 预设包含两个插件:
// - jotai/babel/plugin-react-refresh 以启用 atoms 的热重载
// - jotai/babel/plugin-debug-label 以自动向 atoms 添加调试标签
'jotai/babel/preset'
]
}

Vite + React 项目的示例:

// vite.config.ts
export default defineConfig({
plugins: [
react({
babel: {
presets: ['jotai/babel/preset'],
},
}),
],
})

Next JS 设置

如果你不使用 Next.js,你可以跳过这一部分。

启用 transpilePackages 以正确地转译 UI CSS 和组件。

// next.config.ts
const nextConfig = {
// 在这里了解更多 - https://nextjs.org/docs/advanced-features/compiler#module-transpilation
// 为了正确地转译 UI css,这是必需的 👇
transpilePackages: ['jotai-devtools'],
}
module.exports = nextConfig

可用的 props

type DevToolsProps = {
// 默认为 false
isInitialOpen?: boolean
// 传递一个自定义的 store
store?: Store
// 默认为 light
theme?: 'dark' | 'light'
// 设置触发按钮的位置
// 默认为 `bottom-left`
position?: 'top-right' | 'top-left' | 'bottom-right' | 'bottom-left'
// 自定义 nonce 以通过 CSP 允许 jotai-devtools 特定的内联样式
nonce?: string
options?: {
// 私有 atoms 在 atom creators(如 `atomWithStorage` 或 `atomWithLocation` 等)内部使用,以管理内部状态。
// 默认为 `false`
shouldShowPrivateAtoms?: boolean
// 在 Atom Viewer 标签、Timeline 标签等的初始渲染上展开 JSON 树视图。
// 默认为 `false`
shouldExpandJsonTreeViewInitially?: boolean
// 时间旅行回放的每一步之间的间隔(以毫秒为单位)。
// 默认为 `750ms`
timeTravelPlaybackInterval?: number
// 在历史记录中保留的快照的最大数量。
// 数字越大,消耗的内存就越多。
// 默认为 `Infinity`。推荐:`~30`
snapshotHistoryLimit?: number
}
}

使用

无 Provider

import { DevTools } from 'jotai-devtools'
import 'jotai-devtools/styles.css'
const App = () => {
return (
<>
<DevTools />
{/* 你的应用 */}
</>
)
}

有 Provider

import { createStore } from 'jotai'
import { DevTools } from 'jotai-devtools'
import 'jotai-devtools/styles.css'
const customStore = createStore()
const App = () => {
return (
<Provider store={customStore}>
<DevTools store={customStore} />
{/* 你的应用 */}
</Provider>
)
}

预览

useAtomsDebugValue

useAtomsDebugValue 是一个 React 钩子,它会在 React Devtools 中显示所有的 atom 值。

function useAtomsDebugValue(options?: {
store?: Store
enabled?: boolean
}): void

在内部,它使用 useDebugValue,这只在 DEV 模式下有效。 它会捕获从钩子所在位置可以访问的所有 atoms。

示例

import { useAtomsDebugValue } from 'jotai-devtools/utils'
const textAtom = atom('hello')
textAtom.debugLabel = 'textAtom'
const lenAtom = atom((get) => get(textAtom).length)
lenAtom.debugLabel = 'lenAtom'
const TextBox = () => {
const [text, setText] = useAtom(textAtom)
const [len] = useAtom(lenAtom)
return (
<span>
<input value={text} onChange={(e) => setText(e.target.value)} />({len})
</span>
)
}
const DebugAtoms = () => {
useAtomsDebugValue()
return null
}
const App = () => (
<Provider>
<DebugAtoms />
<TextBox />
</Provider>
)

useAtomDevtools

useAtomDevtools 是一个 React 钩子,它为特定的 atom 管理 ReduxDevTools 扩展。

function useAtomDevtools<Value>(
anAtom: WritableAtom<Value, Value>,
options?: {
store?: Store
name?: string
enabled?: boolean
},
): void

useAtomDevtools 钩子接受一个泛型类型参数(反映存储在 atom 中的类型)。此外,钩子接受两个调用参数,anAtomnameanAtom 是将附加到 devtools 实例的 atom。name 是一个可选参数,定义 devtools 实例的调试标签。如果 name 未定义,将使用 atom.debugLabel

示例

import { useAtomDevtools } from 'jotai-devtools/utils'
// 存储在 atom 中的类型的接口。
export interface Task {
label: string
complete: boolean
}
// 要调试的 atom。
export const tasksAtom = atom<Task[]>([])
// 如果 useAtomDevtools 的 name 参数未定义,将使用此值。
tasksAtom.debugLabel = 'Tasks'
export const useTasksDevtools = () => {
// 只需传递一个用于调试的 atom 即可调用钩子。
useAtomDevtools(tasksAtom)
// 指定一个自定义类型参数
useAtomDevtools<Task[]>(tasksAtom)
// 你可以将两个 devtools 实例附加到同一个 atom,并使用自定义名称区分它们。
useAtomDevtools(tasksAtom, 'Tasks (Instance 1)')
useAtomDevtools(tasksAtom, 'Tasks (Instance 2)')
}

useAtomsDevtools

⚠️ 注意:此钩子处于实验阶段(欢迎反馈),并且只在 process.env.NODE_ENV !== 'production' 环境中工作。

useAtomsDevtoolsuseAtomDevtools 的全捕获版本,它显示存储中的所有 atoms,而不是显示特定的一个。

function useAtomsDevtools(
name: string,
options?: {
store?: Store
enabled?: boolean
},
): void

它需要一个 name 参数,用于命名 Redux devtools 实例,以及一个 store 参数。

作为此 API 的限制,我们需要将 useAtomsDevtools 放在一个组件中,该组件中消耗的 atoms 应位于 React 树的该组件(下面示例中的 AtomsDevtools)下方。 AtomsDevtools 组件可以被视为我们应用的最佳实践。

示例

const countAtom = atom(0);
const doubleCountAtom = atom((get) => get(countAtom) * 2);
function Counter() {
const [count, setCount] = useAtom(countAtom);
const [doubleCount] = useAtom(doubleCountAtom);
...
}
const AtomsDevtools = ({ children }) => {
useAtomsDevtools('demo')
return children
}
export default function App() {
return (
<AtomsDevtools>
<Counter />
</AtomsDevtools>
)
}

useAtomsSnapshot

⚠️ 注意:此钩子只在 process.env.NODE_ENV !== 'production' 环境中工作。在生产环境中,它返回一个静态的空值。

useAtomsSnapshot 会对当前挂载的 atoms 及其状态进行快照。

function useAtomsSnapshot(options?: { store?: Store }): AtomsSnapshot

它接受一个 store 参数,并将返回一个 AtomsSnapshot,基本上是一个 Map<AnyAtom, unknown>。你可以使用 Map API 来遍历 atoms 及其状态。 这个钩子主要用于调试和 devtools 的用例。

使用这个钩子时要小心,因为它会导致组件在所有状态变化时重新渲染。

示例

import { Provider } from 'jotai'
import { useAtomsSnapshot } from 'jotai-devtools/utils'
const RegisteredAtoms = () => {
const atoms = useAtomsSnapshot()
return (
<div>
<p>Atom count: {atoms.size}</p>
<div>
{Array.from(atoms).map(([atom, atomValue]) => (
<p key={`${atom}`}>{`${atom.debugLabel}: ${atomValue}`}</p>
))}
</div>
</div>
)
}
const App = () => (
<Provider>
<RegisteredAtoms />
</Provider>
)

useGotoAtomsSnapshot

⚠️ 注意:此钩子只在 process.env.NODE_ENV !== 'production' 环境中工作。在生产环境中,它像一个空函数一样工作。

useGotoAtomsSnapshot 将更新当前的 Jotai 状态以匹配传递的快照。

function useGotoAtomsSnapshot(options?: {
store?: Store
}): (values: Iterable<readonly [AnyAtom, unknown]>) => void

此钩子返回一个回调,该回调接受来自 useAtomsSnapshot 钩子的 snapshot 并将更新 Jotai 状态。它接受一个 store 参数。 此钩子主要用于调试和 devtools 的用例。

示例

import { Provider } from 'jotai'
import { useAtomsSnapshot, useGotoAtomsSnapshot } from 'jotai-devtools/utils'
const petAtom = atom('cat')
const colorAtom = atom('blue')
const UpdateSnapshot = () => {
const snapshot = useAtomsSnapshot()
const goToSnapshot = useGotoAtomsSnapshot()
return (
<button
onClick={() => {
const newSnapshot = new Map(snapshot)
newSnapshot.set(petAtom, 'dog')
newSnapshot.set(colorAtom, 'green')
goToSnapshot(newSnapshot)
}}
>
Go to snapshot
</button>
)
}