JotaiJotai

Jotai
React 原始而灵活的状态管理
Atomikku, the Jotai mascot
v2
欢迎使用 Jotai v2!
完全兼容 React 18 和即将推出的 use 钩子。 现在有一个可以在 React 外部使用的存储接口。
在下面享受新的“入门”体验!
介绍

Jotai 采用原子化的方法进行全局 React 状态管理。

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

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

Jotai 在这些创新公司的生产环境中得到了信任。

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

这将引导你创建一个简单的 Jotai 应用程序的过程。从安装开始,然后探索核心 API 的基础知识,最后在 React 框架中进行服务器端渲染。

安装

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

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

创建原子

首先创建原始和派生的原子来构建状态。

原始原子

原始原子可以是任何类型:布尔值,数字,字符串,对象,数组,集合,映射等等。

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
}
])

派生原子

派生原子可以在返回其自身的值之前读取其他原子的值。

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

使用原子

然后在 React 组件中使用原子来读取或写入状态。

在同一组件中读取和写入

当在同一组件中同时读取和写入原子时,为了简单起见,使用组合的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>
<>
)
}

从不同的组件读取和写入

当原子值只被读取或写入时,使用单独的useAtomValueuseSetAtom 钩子来 优化重新渲染。

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 或 Gatsby 等框架进行服务器端渲染,请确保至少在根部使用一个 Provider 组件。

import { Provider } from 'jotai'
// Placement is framework-specific (see below)
<Provider>
{...}
</Provider>

Next.js (应用目录)

在一个单独的客户端组件中创建 provider。然后将 provider 导入到根layout.js服务器组件中。

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

Next.js (页面目录)

_app.js 中创建 provider。

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

Gatsby

gatsby-shared.js 文件中创建 provider,以在 gatsby-browser.jsgatsby-ssr.js 之间共享代码。使用 wrapRootElement API 来放置 provider。

// gatsby-shared.js
import { Provider } from 'jotai'
export const wrapRootElement = ({ element }) => {
return (
<Provider>
{element}
</Provider>
)
}
// gatsby-browser.js
export { wrapRootElement } from './gatsby-shared'
// gatsby-ssr.js
export { wrapRootElement } from './gatsby-shared'
API 概览

核心

Jotai的API非常简洁,以TypeScript为主。它的使用就像React的内置useState钩子一样简单,但所有的状态都可以全局访问, 派生状态易于实现,且自动消除了不必要的重新渲染。

HELLO
import { atom, useAtom } from 'jotai'
// 创建你的原子和派生物
const textAtom = atom('hello')
const uppercaseAtom = atom(
(get) => get(textAtom).toUpperCase()
)
// 在你的应用程序的任何地方使用它们
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>
)
}
// 现在你有了组件
const App = () => {
return (
<>
<Input />
<Uppercase />
</>
)
}

实用工具

Jotai包还包括一个jotai/utils包。这些额外的函数增加了在localStorage中持久化原子的支持,服务端渲染时的原子水合, 创建具有Redux-like的reducers和action类型的原子等等。

import { useAtom } from 'jotai'
import { atomWithStorage } from 'jotai/utils'
// 设置字符串键和初始值
const darkModeAtom = atomWithStorage('darkMode', false)
const Page = () => {
// 像使用任何其他原子一样使用持久化状态
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等等。

一些扩展提供了新的原子类型,带有替代的写函数,例如atomWithImmer(Immer)或atomWithMachine(XState)。

其他的提供了新的原子类型,带有双向数据绑定,例如atomWithLocationatomWithHash

0
import { useAtom } from 'jotai'
import { atomWithImmer } from 'jotai-immer'
// 使用基于 immer 的写函数创建一个新的原子
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