JotaiJotai

状態
Primitive and flexible state management for React
Atomikku, the Jotai mascot
v2
欢迎使用 Jotai v2!
Jotai 完全兼容 React 18 和即将推出的 use hook。 现在,您可以在 React 之外使用 store interface 了。
请从以下全新的 “入门” 章节开始吧!
简介

Jotai 采用 Atom 风格的方式进行全局的 React 状态管理。

通过组合 atom 来构建状态,并根据 atom 的依赖关系自动优化渲染。 这解决了 React 上下文的额外重新渲染问题,消除了 对 memoization 的需求,并在保持声明式编程模型的同时, 提供了与信号(signals)类似的开发体验。

它可以从简单的 useState 替代品扩展到 具有复杂需求的企业级 TypeScript 应用程序。此外,还有大量实用工具和 扩展程序可以为你提供帮助!

Jotai 已被应用于这些创新型公司的生产环境中。

candycode alternative graphic design web development agency
Adobe
Ping Labs
TokTok
Uniswap
入门

接下来将指导您创建一个简单的 Jotai 应用程序。首先是 安装 Jotai,然后探索核心 API 的基础知识,最后在应用于 React 框架中进行服务器端渲染。

安装

首先将 Jotai 作为依赖项添加到 React 项目中。

# npm
npm i jotai
# yarn
yarn add jotai
# pnpm
pnpm add jotai

创建 atom

首先创建原始和派生类型的 atom 来构建状态。

原始类型的 atom

原始类型的 atom 可以是后面列出的任何类型:booleans、numbers、strings、objects、arrays、sets、maps、 等。

import { atom } from 'jotai'
const countAtom = atom(0)
const countryAtom = atom('Japan')
const citiesAtom = atom(['Tokyo', 'Kyoto', 'Osaka'])
const animeAtom = atom([
{
title: 'Ghost in the Shell',
year: 1995,
watched: true
},
{
title: 'Serial Experiments Lain',
year: 1998,
watched: false
}
])

派生类型的 atom

派生类型的 atom 在返回自身的值之前,可以读取其它 atom 的值。

const progressAtom = atom((get) => {
const anime = get(animeAtom)
return anime.filter((item) => item.watched).length / anime.length
})

使用 atom

在 React 组件中使用 atom 来读取或写入状态。

从同一个组件中读取和写入

在同一个组件中读取和写入 atom 时,为简便起见,请使用合并后的 useAtom hook。

import { useAtom } from 'jotai'
const AnimeApp = () => {
const [anime, setAnime] = useAtom(animeAtom)
return (
<>
<ul>
{anime.map((item) => (
<li key={item.title}>{item.title}</li>
))}
</ul>
<button onClick={() => {
setAnime((anime) => [
...anime,
{
title: 'Cowboy Bebop',
year: 1998,
watched: false
}
])
}}>
Add Cowboy Bebop
</button>
<>
)
}

从不同的组件中读取和写入

当 atom 的值为只允许读或写时,请使用 useAtomValueuseSetAtom 这两个 hook 来 优化重新渲染的效率。

import { useAtomValue, useSetAtom } from 'jotai'
const AnimeList = () => {
const anime = useAtomValue(animeAtom)
return (
<ul>
{anime.map((item) => (
<li key={item.title}>{item.title}</li>
))}
</ul>
)
}
const AddAnime = () => {
const setAnime = useSetAtom(animeAtom)
return (
<button onClick={() => {
setAnime((anime) => [
...anime,
{
title: 'Cowboy Bebop',
year: 1998,
watched: false
}
])
}}>
Add Cowboy Bebop
</button>
)
}
const ProgressTracker = () => {
const progress = useAtomValue(progressAtom)
return (
<div>{Math.trunc(progress * 100)}% watched</div>
)
}
const AnimeApp = () => {
return (
<>
<AnimeList />
<AddAnime />
<ProgressTracker />
</>
)
}

服务器端渲染

如果服务器端渲染使用的是类似 Next.js Waku 之类的框架时,请确保在 root 节点下 添加一个 Jotai Provider。

Next.js (使用 app 目录)

在单独的客户端组件中创建 Jotai provider。然后将新建的 provider 导入(import)到根目录下的 layout.js 服务器端组件中。

// ./components/providers.js
'use client'
import { Provider } from 'jotai'
export const Providers = ({ children }) => {
return (
<Provider>
{children}
</Provider>
)
}
// ./app/layout.js
import { Providers } from '../components/providers'
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>
<Providers>
{children}
</Providers>
</body>
</html>
)
}

Next.js (使用 pages 目录)

_app.js 中添加 Jotai provider。

// ./pages/_app.js
import { Provider } from 'jotai'
export default function App({ Component, pageProps }) {
return (
<Provider>
<Component {...pageProps} />
</Provider>
)
}

Waku

在单独的客户端组件中创建 Jotai provider。然后将新建的 provider 导入(import)到根 布局文件中。

// ./src/components/providers.js
'use client'
import { Provider } from 'jotai'
export const Providers = ({ children }) => {
return (
<Provider>
{children}
</Provider>
)
}
// ./src/pages/_layout.js
import { Providers } from '../components/providers'
export default async function RootLayout({ children }) {
return (
<Providers>
{children}
</Providers>
)
}
API 概述

核心

Jotai 的 API 很精简,并且是面向 TypeScript 的。它与 React 内置的 useState hook 一样简单易用,并且所有状态都是全局可访问的, 派生状态也易于实现,并且自动消除了不必要的重新渲染。

HELLO
import { atom, useAtom } from 'jotai'
// Create your atoms and derivatives
const textAtom = atom('hello')
const uppercaseAtom = atom(
(get) => get(textAtom).toUpperCase()
)
// Use them anywhere in your app
const Input = () => {
const [text, setText] = useAtom(textAtom)
const handleChange = (e) => setText(e.target.value)
return (
<input value={text} onChange={handleChange} />
)
}
const Uppercase = () => {
const [uppercase] = useAtom(uppercaseAtom)
return (
<div>Uppercase: {uppercase}</div>
)
}
// Now you have the components
const App = () => {
return (
<>
<Input />
<Uppercase />
</>
)
}

实用工具

Jotai 软件包还包含了一个 jotai/utils 包。这些额外的 函数提供了在 localStorage 中持久化 atom、在服务器端渲染时hydrating an atom、 使用类似 Redux 的 reducers 和 action types 创建 atom 等功能的 支持。

import { useAtom } from 'jotai'
import { atomWithStorage } from 'jotai/utils'
// Set the string key and the initial value
const darkModeAtom = atomWithStorage('darkMode', false)
const Page = () => {
// Consume persisted state like any other atom
const [darkMode, setDarkMode] = useAtom(darkModeAtom)
const toggleDarkMode = () => setDarkMode(!darkMode)
return (
<>
<h1>Welcome to {darkMode ? 'dark' : 'light'} mode!</h1>
<button onClick={toggleDarkMode}>toggle theme</button>
</>
)
}

扩展

每个官方扩展都是独立的软件包:tRPC、Immer、Query、XState、 URQL、Optics、Relay、location、molecules、cache 等。

某些扩展提供了新的 atom 类型并自带写入函数,例如 atomWithImmer (Immer) 或 atomWithMachine (XState)。

其它新的 atom 类型提供了双向数据绑定函数,例如 atomWithLocationatomWithHash

0
import { useAtom } from 'jotai'
import { atomWithImmer } from 'jotai-immer'
// Create a new atom with an immer-based write function
const countAtom = atomWithImmer(0)
const Counter = () => {
const [count] = useAtom(countAtom)
return (
<div>count: {count}</div>
)
}
const Controls = () => {
// setCount === update: (draft: Draft<Value>) => void
const [, setCount] = useAtom(countAtom)
const increment = () => setCount((c) => (c = c + 1))
return (
<button onClick={increment}>+1</button>
)
}
了解更多

观看由 Jotai 的创建者 Daishi 在 Egghead 上推出的免费课程。

Jotai course