使用 Signia 實現 React 狀態管理
原文作者:Kapeel Kokane
原文地址:https://blog.logrocket.com/implement-react-state-management-signia/
翻譯:一川
寫在前面
如果你在最近的過去開發過任何具有相當複雜程度的 React 應用程序,你可能已經瞭解狀態管理如何很快成爲一個主要問題。React 提供的原生工具,如 useState
和 useContext
,在嘗試實現常見的設計模式時被證明是不夠的,比如由多個組件使用和更新的中央共享狀態。
Redux
是幫助解決這個問題的最受歡迎的庫; 它運行了幾年,爲了克服它存在的小差距,一個偉大的生態系統以Reselect
和Redux-Saga
等庫的形式圍繞它發展起來。最近,MobX
,Zustand
和Jotai
等其他替代品越來越受歡迎。在本文中,我們將瞭解 Signia
,這是一個使用信號來解決相同問題的狀態管理庫。
什麼是 Signia?
正如tldraw
團隊在公告博客文章中提到的,“Signia
是一個原始庫,用於處理細粒度的反應值,稱爲signals
,使用基於邏輯時鐘的新惰性反應模型”。
簡單來說,Signia
使用稱爲signals
的基元進行狀態管理,它可以通過執行增量計算來有效地計算計算值。此外,藉助爲整個事務的回滾提供支持的內部時鐘,如果需要,它們可以實現事務的概念。
雖然核心庫與框架無關,但 tldraw
團隊還發布了一組 React
綁定,這使得將 Signia
集成到 React
應用程序中變得輕而易舉。
signals
到底是什麼?
在進入 Signia
的功能之前,讓我們先了解一下信號的概念是什麼。根據官方文件,“signals
是一個隨時間變化的值,其變化事件會引發副作用”。換句話說,signals
是一個純粹的、無功的值,可以觀察到變化。然後,信號庫負責觀察這些變化,通知訂閱者,並觸發所需的副作用。
從理論上講,signals
有點類似於RxJS
庫提供的可觀察量的概念,但有一些根本區別。其中之一是需要創建訂閱並傳遞迴調函數來訪問可觀察量的值。
Signia
核心概念
讓我們回顧一下理解 Signia
所必需的一些概念。
Atom 原子
Signia
中的 Atom
表示對應於根狀態的signals
,即應用的真實來源。可以讀取和更新其值,也可以在此基礎上構建以創建計算值。
創建原子
要創建 Atom
,Signia
庫提供了 atom
函數:
import { atom } from 'signia'
const fruit = atom('fruit', 'Apple');
上面的代碼創建一個名爲 fruit
的信號,值爲Apple
。還將fruit
作爲第一個參數傳遞給 atom
函數,因爲它有助於調試目的。
更新原子
爲了更新 Atom
,使用 set
函數,如下所示:
fruit.set('Banana');
console.log(fruit.value); // Banana
fruit.set((current) => current + 's');
console.log(fruit.value); // Bananas
與 React setState
函數類似,有一個接受函數作爲參數的set
的替代版本。然後,它使用信號的當前值調用該函數並計算更新的值。
計算信號
計算信號來自原子,因此依賴於它們;每當它們所依賴的原子發生變化時,它們的值就會重新計算。
創建computed
信號
您可以使用 computed
函數創建計算信號,如下所示:
import { computed, atom } from 'signia'
const fruits = atom('fruits', 'Apples')
const numberOf = atom('numberOf', 10)
const display = computed('display', () => {
return `${numberOf.value} ${fruits.value}`
})
console.log(display.value) // 10 Apples
更新computed
信號
沒有更新計算信號的直接方法。但是,更新其任何根原子都會自動更新計算出的信號:
fruits.set('Bananas')
console.log(display.value) // 10 Bananas
如上所示,計算信號的值被更新以反映在fruits
的根原子上設置的最新值。
React 綁定 Signia
到目前爲止,我們回顧的代碼示例是通用的,使用 Signia
核心庫。但是,如前所述,tldraw
團隊還發布了一組 React
綁定,可以更輕鬆地將 Signia
集成到React
應用程序中。官方的 React
綁定以兩個包的形式提供,即 signia-react
和 signia-react-jsx
。
signia-react
提供了像 useAtom
和useComputed
這樣的鉤子,它們有助於管理React
組件中的本地狀態。signia-react
還提供了track
和 useValue
等實用程序,您可以使用它們爲組件提供反應性,但如果使用 signia-react-jsx
庫,則不需要。
signia-react-jsx
提供導致所有功能組件變得跟蹤和響應的配置選項。它還解包每個信號,因此不需要將信號包裝在 useValue
中。現在,創建一個使用Signia
進行狀態管理的 React
待辦事項列表應用程序。
上手體驗 Signia
Signia
對Vite
有開箱即用的支持,所以將使用 Vite 作爲打包器。若要創建新的 Vite
項目,請運行以下命令:
npm create vite@latest
當界面出現時,爲新項目提供一個名稱,選擇 React 作爲框架,然後選擇 TypeScript 作爲語言。創建項目時,應看到類似於以下內容的內容:
我們需要在創建項目的目錄中工作,在例子中是 todo-list-signia
目錄。
設置 Signia
現在,讓我們安裝特定於Signia
的庫:
npm install --save signia-react signia-react-jsx
我們將爲組件設置響應式,這樣就不需要手動將每個組件包裝在 track
函數中。爲了進行設置,在新創建的樣板 Vite
項目中打開 tsconfig.json
文件,並將以下代碼添加到 compilerOptions
對象:
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "signia-react-jsx"
}
現在,我們可以開始在樣板中使用 Signia
。
設置 Chakra UI
我們還安裝 Chakra UI
組件庫,將使用它來構建 UI
組件,使它們看起來乾淨有序。要安裝 Chakra UI
及其對等依賴項,請運行以下命令:
npm i @chakra-ui/react @emotion/react @emotion/styled framer-motion react-icons
在 App.tsx
中進行以下更改:
import { ChakraProvider } from '@chakra-ui/react'
function App() {
return (
<ChakraProvider>
<div class>
<Heading>Testing Vite!</Heading>
<Button colorScheme='blue'>Button</Button>
</div>
</ChakraProvider>
)
}
接下來,使用以下命令運行本地開發服務器:
npm run dev
可以看到該應用程序在 localhost
上啓動並運行,顯示以下內容:
使用 Signia
測試響應式
在創建實際應用程序之前,測試一下是否正確設置了所有內容。將創建一個簡單的計數器應用,該應用使用 Signia
進行狀態管理。我們將創建一個 useAtom
的局部狀態變量,該變量將保存計數的值,並在每次單擊按鈕時添加一個增量函數:
import { useAtom } from 'signia-react'
...
function App() {
const count = useAtom('count', 0);
const onButtonClick = () => {
count.set(count.value + 1);
}
return (
<ChakraProvider>
<div class>
<Heading>Counter value: {count.value}</Heading>
<Button colorScheme='blue' onClick={() => onButtonClick()}>Increment</Button>
</div>
</ChakraProvider>
)
}
當我們單擊按鈕時,可以看到計數器值已正確更新。因此,設置按預期工作:
設計狀態
現在可以將簡單值存儲爲 Signia atom
,繼續下一步,給待辦事項列表應用程序設計狀態。要求是存儲兩個實體,即項列表和列表標題。可以使用 Signia
團隊推薦的基於類的設計,並創建兩個單獨的 Atom 來存儲這些實體。該類將如下所示:
class Todo {
metadata = atom('metadata', {
title: 'Groceries',
})
items = atom('items', {
1: {
id: 1,
text: 'Milk',
completed: false,
}
})
}
請注意,items class
屬性是一個包含與各個項目對應的其他對象的對象,這將有助於我們有效地更新狀態。不需要遍歷項目來尋找的項目,可以在項目上使用 spread
運算符並只更新感興趣的項目。
class Todo {
...
addItem(todoText: string) {
const listItem = {
id: Date.now(),
text: todoText,
completed: false,
}
this.items.update((items) => ({ ...items, [listItem.id]: listItem }))
}
markItemAsDone(itemId) {
const updatedItem = { ...this.items.value[itemId], completed: true }
this.items.update((items) => ({ ...items, [itemId]: updatedItem }))
}
setTitle(title: string) {
this.metadata.update((metadata) => ({ ...metadata, title }))
}
}
上面的代碼實現UX
所需的所有最小功能。
創建用戶界面
對於待辦事項列表應用的 UI
,將在頂部顯示標題。若要實現重命名列表的功能,只需提供一個 edit
按鈕並調用已在狀態類中定義的 setTitle
函數。
在 Title
下方,可以將 input box
與button
一起使用,您可以使用它向列表中添加項目。使用 Chakra UI
,標題的代碼以及輸入框如下所示:
<Heading>Todo Title</Heading>
<InputGroup size='md' mt='2rem'>
<Input
pr='4.5rem'
type={'text'}
value={todoText.value}
onChange={onTodoItemChange}
placeholder='Enter item to add'
/>
<InputRightElement width='4.5rem'>
<Button h='1.75rem' size='sm' onClick={onAddClick}>
Add
</Button>
</InputRightElement>
</InputGroup>
爲了掌握 React
組件內部的狀態,我們必須實例化 Todo
類。爲此,使用 useMemo Hook
創建狀態的記憶版本,如下所示:
const useNewTodo = () => useMemo(() => new Todo(), [])
We can now use this custom hook inside of the App component:
function App() {
const todo = useNewTodo()
...
}
還需要創建一個本地狀態變量,該變量將跟蹤在 input
內鍵入的文本。可以利用 useAtom 來實現此目的:
const todoText = useAtom('todoText', '');
const onTodoItemChange = (e) => {
todoText.set(e.target.value);
}
還需要兩個處理程序,一個用於處理 todo
項的添加,另一個用於將其標記爲完成:
const onAddClick = (e) => {
todo.addItem(todoText.value);
todoText.set('');
}
const onDoneClick = (id) => {
todo.markItemAsDone(id);
}
單擊添加按鈕時,在實例化的狀態類上調用 addItem
方法。選中該複選框後,使用 ID
調用 markItemAsDone
方法。
還剩下一件事。
循環訪問待辦事項列表並在 UI
中顯示它們。爲此,將使用List
和 ListItem
組件以及 Object.values
幫助程序來迭代對象值:
<List spacing={3} textAlign={'left'} mt='2rem'>
{Object.values(todo.items.value).map((item) => (
<ListItem key={item.id} alignItems={'center'}>
<Checkbox disabled={item.completed} checked={item.completed} mt={'4px'} mr={2} onChange={() => onDoneClick(item.id)} />
<Text as={item.completed ? 's' : 'b'}>{item.text}</Text>
</ListItem>
))}
</List>
這樣就完成了最小待辦事項列表應用正常工作所需的所有代碼更改。您可以檢查完整的代碼更改集,甚至可以通過克隆此 GitHub
(https://github.com/kokanek/todo-list-with-signia) 存儲庫自行運行它。
測試 UI
讓我們測試一下代碼更改。當第一次運行應用程序時,可以看到待辦事項列表中存在的 Milk
項,因爲在以下狀態下對其進行了硬編碼:
UI
按預期工作,可以根據需要添加更多任務。
在 React 組件之間共享狀態
需要探索的最後一件事是在不同的 React
組件之間共享狀態。在本教程中構建的示例在同一文件中具有狀態類以及該狀態的使用者。
但是,在現實生活中的用例中,狀態和消費的存儲點相距甚遠。在這些場景中,如何管理共享狀態?Signia
建議使用 React.context
。首先使用狀態類創建一個上下文,然後將整個應用程序包裝在該上下文提供程序中,將實例化的狀態類作爲值傳遞:
const TodoContext = React.createContext<Todo | null>(null)
class TodoHelper {
static useNewTodo = () => {
const todoState = useMemo(() => new Todo(), [])
return todoState
}
}
const App = () => {
const todo = TodoHelper.useNewTodo()
return (
<TodoContext.Provider value={todo}>
...other components get access to the state
</TodoContext.Provider>
)
}
在示例中進行這些更改並進行測試。爲此,在 App 組件中進行上述更改。然後,創建一個名爲 TodoList.jsx
的新文件,並複製代碼以呈現其中的列表項。還將代碼用於從此文件的上下文中使用狀態對象:
import { TodoContext } from './App';
const useTodoFromContext = () => {
const doc = useContext(TodoContext)
if (!doc) throw new Error('No document found in context')
return doc
}
export function TodoList() {
const todo = useTodoFromContext();
return (
<List spacing={3} textAlign={'left'} mt='2rem'>
{Object.values(todo.items.value).map((item) => (
<ListItem key={item.id} alignItems={'center'}>
<Checkbox disabled={item.completed} checked={item.completed} mt={'4px'} mr={2} />
<Text as={item.completed ? 's' : 'b'}>{item.text}</Text>
</ListItem>
))}
</List>
)
}
useTodoFromContext
幫助程序負責獲取上下文並將狀態的最新實例化返回給此組件。現在,將這個組件放在 App.tsx
文件中的藍色 <div>
中。可以將其放置在 UX 中的任何位置,甚至可以在新路線上。
現在,當添加新的待辦事項時,看到從上下文中讀取此狀態的 TodoList
組件也顯示添加到列表中的最新項:
在上面的演示中,正在讀取 TodoList
組件中的列表項。因爲可以從上下文訪問 todo
對象,所以也可以調用 addItem
和 markItemAsDone
方法,它會反映在兩個列表中。因此,我們有效地實現了來自中央來源的狀態共享。
寫在最後
在本文中,我們構建了一個使用 Signia
庫及其 React
幫助程序來管理狀態的應用程序。 useAtom Hook
提供了 useState
的替代方案,而以原子作爲類屬性的基於類的體系結構提供了一種構造更復雜的狀態的方法。
還探索了一種在 React.createContext
和 useContext
的不同組件之間共享公共狀態的方法,所有這些都沒有反應性的初始設置和 Redux
等庫所期望的樣板。因此,Signia
可能是您下次構建 React
應用程序時用於狀態管理的庫。
歡迎關注筆者公衆號「宇宙一碼平川」,助你技術路上一碼平川。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/LLoUNaBgeF_A_pJkf2lkYw