
use hook。现在,您可以在 React 之外 使用 store interface 了。Jotai 采用 Atom 风格的方式进行全局的 React 状态管理。
通过组合 atom 来构建状态,并根据 atom 的依赖关系自动优化渲染。 这解决了 React 上下文的额外重新渲染问题, 消除了对 memoization 的需求,并且 保持声明式编程模型的同时,提供了与信号(signals)类似的 开发体验。
它可以从简单的 useState 替代品扩展到具有复杂需求的企业级 TypeScript 应用程序。 此外,还有大量实用工具和扩展程序可以 为你提供帮助!
Jotai 已被应用于这些创新型公司的生产环境中。
接下来将指导您创建一个简单的 Jotai 应用程序。首先是安装 Jotai,然后探索 核心 API 的基础知识,最后在一个 React 框架中进行服务器端 渲染。
安装
首先将 Jotai 作为依赖项添加到 React 项目中。
# npmnpm install jotai# yarnyarn add jotai# pnpmpnpm 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'])export 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'import { animeAtom } from './atoms'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 的值为只允许读或写时,请使用 useAtomValue 和 useSetAtom 这两个 hooks 来优化重新渲染的效率。
import { useAtomValue, useSetAtom } from 'jotai'import { animeAtom } from './atoms'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.jsimport { 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.jsimport { 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.jsimport { Providers } from '../components/providers'export default async function RootLayout({ children }) {return (<Providers>{children}</Providers>)}
核心
Jotai 的 API 很精简,并且是面向 TypeScript 的。它与 React 内置的 useState hook 一样简单易用,并且 所有状态都是全局可访问的,派生状态也易于实现, 并且自动消除了不必要的重新渲染。
import { atom, useAtom } from 'jotai'// Create your atoms and derivativesconst textAtom = atom('hello')const uppercaseAtom = atom((get) => get(textAtom).toUpperCase())// Use them anywhere in your appconst 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 componentsconst 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 valueconst darkModeAtom = atomWithStorage('darkMode', false)const Page = () => {// Consume persisted state like any other atomconst [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 类型提供了双向数据绑定函数,例如 atomWithLocation 或 atomWithHash。
import { useAtom } from 'jotai'import { atomWithImmer } from 'jotai-immer'// Create a new atom with an immer-based write functionconst countAtom = atomWithImmer(0)const Counter = () => {const [count] = useAtom(countAtom)return (<div>count: {count}</div>)}const Controls = () => {// setCount === update: (draft: Draft<Value>) => voidconst [, setCount] = useAtom(countAtom)const increment = () => setCount((c) => (c = c + 1))return (<button onClick={increment}>+1</button>)}
