Jotai:React輕量級原子化狀態管理,告別重渲染困擾

簡介

Jotai 是一個為 React 提供的原子化狀態管理庫,采用自下而上的方法來進行狀態管理。Jotai 受 Recoil 啟發,通過組合原子來構建狀態,并且渲染基于原子依賴性進行優化。這解決了 React 上下文的額外重新渲染問題,并消除了對 memoization 技術的需要。

核心特性

  • 原子化狀態?– 狀態被分解為原子單元,可以獨立管理和組合
  • 零配置?– 無需像 Redux 那樣的復雜配置和樣板代碼
  • 類型安全?– 完全支持 TypeScript,提供良好的類型推斷
  • 高性能?– 自動優化渲染,避免不必要的組件重渲染
  • 輕量級?– 核心包僅 2.4kB,API 簡潔易用
  • 靈活性?– 支持同步和異步狀態,易于派生和組合

快速開始

安裝

npm install jotai
# 或
yarn add jotai
# 或
pnpm add jotai

基礎用法

import { atom, useAtom } from 'jotai'// 創建一個原子狀態
const countAtom = atom(0)function Counter() {// 使用原子狀態,類似于 useStateconst [count, setCount] = useAtom(countAtom)return (<div className="flex flex-col items-center justify-center min-h-[300px] p-6"><h1 className="text-3xl font-medium mb-6 text-gray-800">Count: <span className="text-4xl font-bold">{count}</span></h1><div className="flex gap-3"><button onClick={() => setCount(count + 1)}className="px-4 py-2 bg-blue-500 text-white font-medium rounded hover:bg-blue-600">Increment</button><button onClick={() => setCount(count - 1)}className="px-4 py-2 bg-red-500 text-white font-medium rounded hover:bg-red-600">Decrement</button></div></div>)
}export default Counter

原子類型

基礎原子

基礎原子是最簡單的狀態單元,可以存儲任何類型的值。

import { atom } from 'jotai'// 基礎類型
const boolAtom = atom(true)
const numberAtom = atom(42)
const stringAtom = atom('hello')// 復雜類型
const objectAtom = atom({ name: 'John', age: 30 })
const arrayAtom = atom(['apple', 'banana', 'orange'])

派生原子

派生原子可以基于其他原子計算出新的狀態,類似于 Vue 的計算屬性或 MobX 的計算值。

import { atom } from 'jotai'const countAtom = atom(0)// 只讀派生原子
const doubleCountAtom = atom((get) => get(countAtom) * 2)// 可讀寫派生原子
const countryAtom = atom('Japan')
const citiesAtom = atom(['Tokyo', 'Kyoto', 'Osaka'])const derivedAtom = atom((get) => ({country: get(countryAtom),cities: get(citiesAtom),}),(get, set, newValue) => {// 可以同時更新多個原子set(countryAtom, newValue.country)set(citiesAtom, newValue.cities)}
)

使用原子

Jotai 提供了幾種使用原子的方式,根據不同的使用場景選擇合適的 Hook。

useAtom

最基本的 Hook,類似于 React 的?useState,用于讀取和更新原子狀態。

import { atom, useAtom } from 'jotai'const textAtom = atom('hello')function TextInput() {const [text, setText] = useAtom(textAtom)return (<input value={text} onChange={(e) => setText(e.target.value)} />)
}

useAtomValue 和 useSetAtom

當組件只需要讀取或只需要寫入原子狀態時,可以使用這兩個 Hook 來優化性能。

import { atom, useAtomValue, useSetAtom } from 'jotai'const countAtom = atom(0)// 只讀組件
function DisplayCount() {const count = useAtomValue(countAtom)return <div>Count: {count}</div>
}// 只寫組件
function Controls() {const setCount = useSetAtom(countAtom)return (<div><button onClick={() => setCount(c => c + 1)}>+1</button><button onClick={() => setCount(c => c - 1)}>-1</button><button onClick={() => setCount(0)}>Reset</button></div>)
}

高級用法

異步原子

Jotai 支持異步原子,可以處理異步數據獲取和更新。

import { atom, useAtom } from 'jotai'// 異步讀取原子
const userAtom = atom(async () => {const response = await fetch('https://api.example.com/user')return response.json()
})// 異步寫入原子
const postAtom = atom(null,async (get, set, newPost) => {const response = await fetch('https://api.example.com/posts', {method: 'POST',body: JSON.stringify(newPost),})const result = await response.json()// 可以更新其他原子set(postsAtom, [...get(postsAtom), result])return result}
)function AsyncComponent() {const [user, setUser] = useAtom(userAtom)const [, createPost] = useAtom(postAtom)// 使用 React Suspense 處理加載狀態return (<div><h1>Welcome, {user.name}</h1><button onClick={() => createPost({ title: 'New Post' })}>Create Post</button></div>)
}

持久化

Jotai 提供了?atomWithStorage?工具函數,可以輕松實現狀態的持久化。

import { useAtom } from 'jotai'
import { atomWithStorage } from 'jotai/utils'// 自動保存到 localStorage
const darkModeAtom = atomWithStorage('darkMode', false)function ThemeToggle() {const [darkMode, setDarkMode] = useAtom(darkModeAtom)return (<div><h1>Current theme: {darkMode ? 'Dark' : 'Light'}</h1><button onClick={() => setDarkMode(!darkMode)}>Toggle theme</button></div>)
}

原子族 (atomFamily)

原子族用于創建一組相關的原子,每個原子都有自己的狀態,但共享相同的行為。

import { useAtom } from 'jotai'
import { atomFamily } from 'jotai/utils'// 創建一個原子族,每個 ID 對應一個原子
const todoAtomFamily = atomFamily((id) => atom({ id, text: '', completed: false }),(a, b) => a === b
)function TodoItem({ id }) {const [todo, setTodo] = useAtom(todoAtomFamily(id))return (<div><inputtype="checkbox"checked={todo.completed}onChange={() => setTodo({ ...todo, completed: !todo.completed })}/><inputvalue={todo.text}onChange={(e) => setTodo({ ...todo, text: e.target.value })}/></div>)
}

實際應用示例

import { atom, useAtom } from 'jotai';
import { atomWithStorage } from 'jotai/utils';// 創建原子狀態
const todosAtom = atomWithStorage('todos', []); // 存儲所有待辦事項,使用 atomWithStorage 自動持久化到 localStorage
const todoInputAtom = atom(''); // 存儲輸入框的值// 過濾類型:全部、已完成、未完成
const filterTypeAtom = atomWithStorage('filterType', 'all');// 派生狀態 - 計算已完成和未完成的任務數量
const todoStatsAtom = atom((get) => {const todos = get(todosAtom);const total = todos.length;const completed = todos.filter(todo => todo.completed).length;const uncompleted = total - completed;return { total, completed, uncompleted };
});// 派生狀態 - 根據過濾類型篩選任務
const filteredTodosAtom = atom((get) => {const todos = get(todosAtom);const filterType = get(filterTypeAtom);switch (filterType) {case 'completed':return todos.filter(todo => todo.completed);case 'active':return todos.filter(todo => !todo.completed);default:return todos;}
});export default function CssDemo() {const [todos, setTodos] = useAtom(todosAtom);const [filteredTodos] = useAtom(filteredTodosAtom);const [todoInput, setTodoInput] = useAtom(todoInputAtom);const [todoStats] = useAtom(todoStatsAtom);const [filterType, setFilterType] = useAtom(filterTypeAtom);// 添加新的待辦事項const addTodo = () => {if (todoInput.trim() === '') return;const newTodo = {id: Date.now(),text: todoInput,completed: false};setTodos([...todos, newTodo]);setTodoInput('');};// 切換待辦事項的完成狀態const toggleTodo = (id) => {setTodos(todos.map(todo =>todo.id === id ? { ...todo, completed: !todo.completed } : todo));};// 刪除待辦事項const deleteTodo = (id) => {setTodos(todos.filter(todo => todo.id !== id));};// 清除所有已完成的任務const clearCompleted = () => {setTodos(todos.filter(todo => !todo.completed));};// 全部標記為已完成/未完成const markAllAs = (completed) => {setTodos(todos.map(todo => ({ ...todo, completed })));};return (<div className="max-w-md mx-auto p-6 bg-white rounded-lg shadow-sm"><h1 className="text-2xl font-bold text-gray-800 mb-4 text-center">Todo List</h1>{/* 任務統計信息 */}<div className="flex justify-between text-sm text-gray-500 mb-5 bg-gray-50 p-2 rounded"><span className="px-2 py-1 bg-white rounded shadow-sm">總計: {todoStats.total}</span><span className="px-2 py-1 bg-white rounded shadow-sm">已完成: {todoStats.completed}</span><span className="px-2 py-1 bg-white rounded shadow-sm">未完成: {todoStats.uncompleted}</span></div>{/* 添加待辦事項表單 */}<div className="flex mb-5"><inputtype="text"value={todoInput}onChange={(e) => setTodoInput(e.target.value)}onKeyPress={(e) => e.key === 'Enter' && addTodo()}placeholder="添加新的待辦事項..."className="flex-1 px-4 py-2 border border-gray-300 rounded-l focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"/><buttononClick={addTodo}className="px-4 py-2 bg-blue-500 text-white font-medium rounded-r hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-opacity-50 transition-colors">添加</button></div>{/* 過濾選項 */}<div className="flex justify-center space-x-2 mb-4"><buttononClick={() => setFilterType('all')}className={`px-4 py-1.5 text-sm rounded-full transition-colors ${filterType === 'all' ? 'bg-blue-500 text-white' : 'bg-gray-100 text-gray-700 hover:bg-gray-200'}`}>全部</button><buttononClick={() => setFilterType('active')}className={`px-4 py-1.5 text-sm rounded-full transition-colors ${filterType === 'active' ? 'bg-blue-500 text-white' : 'bg-gray-100 text-gray-700 hover:bg-gray-200'}`}>未完成</button><buttononClick={() => setFilterType('completed')}className={`px-4 py-1.5 text-sm rounded-full transition-colors ${filterType === 'completed' ? 'bg-blue-500 text-white' : 'bg-gray-100 text-gray-700 hover:bg-gray-200'}`}>已完成</button></div>{/* 待辦事項列表 */}<ul className="space-y-2 mb-4 max-h-60 overflow-y-auto pr-1">{filteredTodos.length === 0 ? (<li className="text-gray-500 text-center py-6 border border-dashed border-gray-200 rounded-lg bg-gray-50">{todos.length === 0 ? '暫無待辦事項' : '沒有符合條件的待辦事項'}</li>) : (filteredTodos.map(todo => (<li key={todo.id} className="flex items-center justify-between p-3 border border-gray-200 rounded-lg hover:bg-gray-50 transition-colors"><div className="flex items-center flex-1 min-w-0"><inputtype="checkbox"checked={todo.completed}onChange={() => toggleTodo(todo.id)}className="h-5 w-5 text-blue-500 rounded focus:ring-blue-500"/><span className={`ml-3 truncate ${todo.completed ? 'line-through text-gray-400' : 'text-gray-800'}`}>{todo.text}</span></div><buttononClick={() => deleteTodo(todo.id)}className="text-red-500 hover:text-red-700 focus:outline-none ml-2 flex-shrink-0"><svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" viewBox="0 0 20 20" fill="currentColor"><path fillRule="evenodd" d="M9 2a1 1 0 00-.894.553L7.382 4H4a1 1 0 000 2v10a2 2 0 002 2h8a2 2 0 002-2V6a1 1 0 100-2h-3.382l-.724-1.447A1 1 0 0011 2H9zM7 8a1 1 0 012 0v6a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v6a1 1 0 102 0V8a1 1 0 00-1-1z" clipRule="evenodd" /></svg></button></li>)))}</ul>{/* 底部操作欄 */}{todos.length > 0 && (<div className="flex justify-between pt-4 border-t border-gray-200"><buttononClick={() => markAllAs(true)}className="text-sm text-blue-500 hover:text-blue-700 focus:outline-none transition-colors">全部完成</button><buttononClick={() => markAllAs(false)}className="text-sm text-blue-500 hover:text-blue-700 focus:outline-none transition-colors">全部取消</button><buttononClick={clearCompleted}className={`text-sm ${todoStats.completed === 0 ? 'text-gray-400 cursor-not-allowed' : 'text-red-500 hover:text-red-700'} focus:outline-none transition-colors`}disabled={todoStats.completed === 0}>清除已完成</button></div>)}</div>);
}

與其他狀態管理庫的比較

Jotai vs Redux

  • 復雜度: Jotai 更簡單,沒有 actions、reducers、middleware 等概念
  • 樣板代碼: Jotai 幾乎沒有樣板代碼,而 Redux 需要大量樣板代碼
  • 學習曲線: Jotai 的學習曲線更平緩,API 更接近 React 原生 hooks
  • 適用場景: Jotai 適合中小型應用,Redux 適合大型、復雜的應用

Jotai vs Recoil

  • API: Jotai 的 API 更簡潔,不需要 key 字符串
  • 大小: Jotai 更小巧 (2.4kB vs Recoil 的 ~20kB)
  • 配置: Jotai 不需要 Provider 包裹(雖然 SSR 時推薦使用)
  • TypeScript: Jotai 對 TypeScript 的支持更好

Jotai vs Zustand

  • 模型: Jotai 是原子模型,Zustand 是單一 store 模型
  • 集成: Jotai 與 React 集成更緊密,Zustand 可以在 React 外使用
  • 選擇: 如果喜歡原子化狀態,選 Jotai;如果喜歡單一 store,選 Zustand

最佳實踐

  1. 原子粒度: 保持原子粒度適中,既不要過大也不要過小
  2. 原子組織: 將相關原子放在同一個文件中,便于管理
  3. 派生優先: 盡量使用派生原子而不是手動同步狀態
  4. Hook 選擇: 根據需要選擇合適的 Hook (useAtom/useAtomValue/useSetAtom)
  5. 異步處理: 對于異步操作,使用 React Suspense 和 ErrorBoundary

總結

Jotai 是一個輕量級、高性能的 React 狀態管理庫,采用原子化的方式管理狀態。它簡化了全局狀態管理,提供了優秀的開發體驗和運行時性能。特別適合:

  • 中小型 React 應用
  • 需要簡單狀態管理的項目
  • 對性能有要求的應用
  • 喜歡函數式和原子化思想的開發者

通過原子化的狀態管理方式,Jotai 既保持了使用的簡單性,又提供了強大的狀態組合能力,是 React 應用狀態管理的絕佳選擇。

?Jotai:React輕量級原子化狀態管理,告別重渲染困擾 - 高質量源碼分享平臺-免費下載各類網站源碼與模板及前沿技術分享

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/91972.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/91972.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/91972.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

C語言數據結構(7)貪吃蛇項目2.貪吃蛇項目實現

8. 核心邏輯實現分析 8.1 游戲主邏輯 程序開始就設置程序支持本地模式&#xff0c;然后進入程序的主邏輯。 主邏輯分為3個過程&#xff1a; ? 游戲開始&#xff08;GameStart&#xff09;完成游戲的初始化。 ? 游戲運行&#xff08;GameRun&#xff09;完成游戲運行邏輯的…

知識蒸餾 - 最小化KL散度與最小化交叉熵是完全等價的

知識蒸餾 - 最小化KL散度與最小化交叉熵是完全等價的 flyfish KL散度與交叉熵的數學關系 對于兩個概率分布 PPP&#xff08;真實分布&#xff09;和 QQQ&#xff08;模型預測分布&#xff09;&#xff0c;KL散度的定義是&#xff1a; DKL(P∥Q)∑xP(x)log?(P(x)Q(x)) D_{KL}(P…

設計心得——網絡包的處理

一、介紹 在程序的開發中&#xff0c;網絡開發是一個重要的應用場景。畢竟這些年IT行業之所以火&#xff0c;主要還是互聯網&#xff08;移動互聯網&#xff09;帶來的。網絡開發&#xff0c;有各種平臺、框架以及系統和庫提供的API&#xff0c;如果說網絡開發是一個特別復雜和…

sqli-labs通關筆記-第30關GET字符注入(WAF繞過 雙引號閉合 手工注入+腳本注入兩種方法)

目錄 一、源碼分析 1、index.php代碼審計 2、login.php代碼審計 3、java_implimentation函數 4、whitelist函數 5、SQL安全性分析 二、滲透實戰 1、進入靶場 2、WAF探測 &#xff08;1&#xff09;觸發WAF &#xff08;2&#xff09;繞過WAF 3、手工注入 &#xf…

【openlayers框架學習】九:openlayers中的交互類(select和draw)

文章目錄openlayers進階28 openlayers中的事件29 openlayers中select交互類的使用30 openlayers中select常見的配置選項31 openlayers中繪制交互類&#xff08;Draw&#xff09;openlayers進階 28 openlayers中的事件 常用進行事件交互的對象&#xff1a;map\view\source29 o…

Java企業級應用性能優化實戰

在企業級Java應用開發中,性能優化是確保系統穩定運行的關鍵因素。本文將從多個維度深入分析Java應用性能瓶頸,并提供實戰優化方案。 ?? 性能優化核心領域 1. 對象操作性能優化 在企業應用中,對象拷貝是一個高頻操作,特別是在分層架構中的DO、DTO、VO轉換。選擇合適的拷…

LLM Prompt與開源模型資源(3)如何寫一個好的 Prompt

學習材料&#xff1a;https://www.hiascend.com/developer/courses/detail/1935520434893606913 &#xff08;3.5&#xff09;學習時長&#xff1a; 預計 60 分鐘學習目的&#xff1a; 了解提示工程的定義與作用熟悉提示工程的關鍵技術相關概念掌握基于昇騰適配的大模型提示工程…

日志管理工具 ——ELK Stack

一、ELK Stack 概述1.1 核心組件ELK Stack&#xff08;現更名為 Elastic Stack&#xff09;是一套開源的日志收集、存儲、分析和可視化平臺&#xff0c;由三個核心組件構成&#xff1a;Elasticsearch&#xff1a;分布式搜索引擎&#xff0c;負責日志數據的存儲、索引和快速查詢…

SpringAI:AI工程應用框架新選擇

Spring AI 是一個用于 AI 工程的應用框架 Spring AI 是一個用于 AI 工程的應用框架。其目標是將可移植性和模塊化設計等 Spring 生態系統設計原則應用于 AI 領域,并推廣使用 POJO 作為應用程序的構建塊到 AI 領域。 Spring AI 的核心是解決 AI 集成的基本挑戰:將企業數據和…

Kettle 開源ETL數據遷移工具從入門到實戰

ETL&#xff08;Extract, Transform, Load&#xff09;工具是用于數據抽取、轉換和加載的軟件工具&#xff0c;用于支持數據倉庫和數據集成過程。Kettle作為傳統的ETL工具是純 java 開發的開源的 ETL工具&#xff0c;用于數據庫間的數據遷移 。可以在 Linux、windows、unix 中運…

Maven中的bom和父依賴

maven最全避坑指南寫完后&#xff0c;發現自己對于bom和父pom的理解還是不夠深入&#xff0c;特此轉載DeepSeek的回答&#xff0c;和大家一起學習了。 在 Maven 的依賴管理中&#xff0c;父 POM (Parent POM) 和 BOM (Bill of Materials) 都是用于實現集中化管理和控制的核心機…

Python 操作 Word 文檔:主流庫對比與選擇指南

在辦公自動化、報告生成、數據處理等領域&#xff0c;利用 Python 程序化地創建、讀取或修改 Microsoft Word 文檔 (.docx 格式) 是一項非常實用的技能。Python 生態中有多個優秀的庫可以完成這項任務&#xff0c;但它們各有側重和優缺點。選擇哪一個“最好用”&#xff0c;關鍵…

怎么修改論文格式呢?提供一份論文格式模板

注!!!本文內容是作者自己整理的一份模板,僅供參考,各位如何修改,還需要看學校的要求。 一、參考文獻 1、有一定數量的近幾年參考文獻、不宜過多中文文獻 英文期刊模板 [1] Taesoo K, Sooyoung K, Kyunghan L, et al. Special issue on 6G and satellite communication…

MVC 發布

MVC 發布 引言 MVC(Model-View-Controller)模式是一種廣泛應用于軟件開發的架構模式。它將應用程序分為三個主要部分:模型(Model)、視圖(View)和控制器(Controller)。這種模式不僅提高了代碼的可維護性和可擴展性,而且使得開發者可以更加專注于各個組件的開發。本文…

arkui 動畫曲線

參考文檔 https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-curve#curvesinterpolatingspring10 可視化工具網站 https://easingwizard.com/ https://www.desmos.com/calculator/k01p40v0ct?langzh-CN 基本介紹 import { curves } from kit.A…

大語言模型(LLM)技術架構與工程實踐:從原理到部署

在自然語言處理領域,大語言模型(LLM)已成為顛覆性技術。從 GPT 系列到 LLaMA、ChatGLM,這些參數規模動輒百億甚至萬億的模型,不僅實現了流暢的自然語言交互,更在代碼生成、邏輯推理等復雜任務中展現出驚人能力。本文將從技術底層拆解 LLM 的核心架構,分析訓練與推理的關…

python后端之DRF框架(上篇)

一、DRF框架介紹 1、web應用開發模式 1.1、前后端不分離1.2、前后端分離2、RESTful介紹 RESTful是目前最流行的API設計風格 &#xff0c; REST 指的是一組架構約束條件和原則。滿足這些約束條件和原則的應用程序或設計就是 RESTful。 1、每一個URI代表1種資源&#xff1b; 2、客…

信創數據庫-DM(達夢)數據庫安裝教程

官方安裝文檔在這&#xff1a;安裝前準備 | 達夢技術文檔 本文也是基于這個來寫的&#xff0c;微調了一下。 1&#xff0c;下載安裝包 體驗版直接到官方下載即可&#xff1a;產品下載 | 達夢在線服務平臺 如果是有需要商業版等&#xff0c;需要聯系客服申請。 安裝包要選擇CPU…

docker常用命令集(6)

接前一篇文章&#xff1a;docker常用命令集&#xff08;5&#xff09; 本文內容參考&#xff1a; Docker login/logout 命令 | 菜鳥教程 Docker命令_docker login-CSDN博客 特此致謝&#xff01; 9. docker login 簡介 docker login命令用于登錄到docker注冊表&#xff08…

[LINUX操作系統]shell腳本之循環

1.編寫腳本for1.sh,使用for循環創建20賬戶&#xff0c;賬戶名前綴由用戶從鍵盤輸入&#xff0c;賬戶初始密碼由用戶輸入&#xff0c;例如:test1、test2、test3......[rootmaster ~]# vim for1.sh #!/bin/bashread -p "請輸入賬戶名稱前綴&#xff1a;" prefixread -p…