翻譯自 Zustand: simple, modern state management for React , 本人英語(yǔ)水平一般, 借助了谷歌翻譯和自己的理解, 大概翻譯出了全文, 如有錯(cuò)誤, 請(qǐng)諒解, 下面所有代碼可以查看 codesandbox
狀態(tài)管理一直是現(xiàn)代程序應(yīng)用中的重要組成部分, 在早期, 通過(guò)將數(shù)據(jù)傳遞給各種應(yīng)用程序來(lái)管理狀態(tài), 但隨著應(yīng)用程序復(fù)雜性的增加, 這種方法開(kāi)始變得低效, 為了解決這些, 不同的狀態(tài)管理庫(kù)被開(kāi)發(fā)出來(lái), 他們唯一的目標(biāo)是創(chuàng)建一個(gè)中央的存儲(chǔ)來(lái)管理這些狀態(tài), 在各個(gè)組件分發(fā)數(shù)據(jù), 這樣做的結(jié)果是有了清晰的代碼和更好的數(shù)據(jù)處理和組件之間的共享. 在這個(gè)教程中, 讀者會(huì)學(xué)習(xí)如何使用 Zustand
來(lái)管理 states
, 以在他們的應(yīng)用程序中實(shí)現(xiàn)簡(jiǎn)潔的代碼和更好的數(shù)據(jù)流
Zustand 作為一個(gè)狀態(tài)管理庫(kù)
Zustand 是由 Jotai 和 React springs 的開(kāi)發(fā)人員構(gòu)建的快速且可擴(kuò)展的狀態(tài)管理解決方案, Zustand 以簡(jiǎn)單被大家所知, 它使用 hooks 來(lái)管理狀態(tài)無(wú)需樣板代碼
"Zustand" 只是德語(yǔ)的"state"
有很多的流行 React 狀態(tài)管理工具, 但以下是您更喜歡使用 Zustand
的一些原因
- 更少的樣板代碼
- Zustand 只在 state 的值改變時(shí)渲染組件, 通常可以處理狀態(tài)的改變而無(wú)需渲染代碼
- 狀態(tài)管理通過(guò)簡(jiǎn)單定義的操作進(jìn)行集中和更新, 在這方面和 Redux 類(lèi)似, 但是又和 Redux 不太類(lèi)似, Redux 開(kāi)發(fā)必須創(chuàng)建 reducer气筋、action、dispatch來(lái)處理狀態(tài), Zustand 讓它變得更加容易
- 使用 hooks 來(lái)管理 states, Hooks 在 react 中很流行, 因此是一個(gè)很受歡迎的狀態(tài)管理庫(kù)
- Zustand 使用簡(jiǎn)單使用和簡(jiǎn)單實(shí)現(xiàn)的代碼
- 通過(guò)消除使用
Context Provides
從而使代碼更短、更易讀
啟動(dòng)一個(gè) app
第一步就是創(chuàng)建一個(gè)新的React應(yīng)用并且安裝 Zustand
依賴(lài), 運(yùn)行下的的命令
npx create-react-app zustand
cd zustand
npm install zustand
現(xiàn)在, 在我們的項(xiàng)目文件中安裝了我們的狀態(tài)管理供我們使用, 現(xiàn)在我們需要定義一個(gè) store
希望包含應(yīng)用程序使用的所有狀態(tài)和他們的函數(shù), 我們?cè)?App.js
文件中定義
import create from 'zustand'
// define the store
const useStore = create(set => ({
votes: 0
}))
以上, 我們創(chuàng)建了一個(gè) store 來(lái)跟蹤關(guān)于 votes 的狀態(tài), 初始值是 0, 在這里, 我們的 store 名字是 useStore
. 定義 store, 我們使用了 function create
從 Zustand
引入, 它接受一個(gè)回調(diào)函數(shù)來(lái)創(chuàng)建 store
訪問(wèn) Store
在我們的應(yīng)用中使用這個(gè) state, 我們可以將創(chuàng)建狀態(tài)的值綁定到 DOM 元素
const getVotes = useStore(state => state.votes);
return (
<div className="App">
<h1>{getVotes} people have cast their votes</h1>
</div>
);
在上面的代碼中, 我們有一個(gè)變量 getVotes
, 這個(gè)變量包含了 state 的屬性 votes 的值, 有了這個(gè), 我們可以訪問(wèn)這個(gè)值放入到 h1 DOM
元素中展示這個(gè)值, 現(xiàn)在, 如果我們運(yùn)行我們程序 npm start
, 我們可以在頁(yè)面上看到結(jié)果
更新 state
除了訪問(wèn)狀態(tài)的值, 我們也可以修改 store 來(lái)改變 votes 的初始值, 我們先創(chuàng)建兩個(gè)額外的屬性 addVotes
和 subtractVotes
, 前者的作用是每次增加 1, 后者減 1
const useStore = create(set => ({
votes: 0,
addVotes: () => set(state => ({ votes: state.votes + 1 })),
subtractVotes: () => set(state => ({ votes: state.votes - 1 })),
}));
下面我們?cè)谖覀兊某绦蛑惺褂盟鼈?/p>
const addVotes = useStore(state => state.addVotes);
const subtractVotes = useStore(state => state.subtractVotes);
<div className="App">
<h1>{getVotes} people have cast their votes</h1>
<button onClick={addVotes}>Cast a vote</button>
<button onClick={subtractVotes}>Delete a vote</button>
</div>
addVotes
會(huì)更新 votes 的值 加1, subtractVotes
會(huì)更新 votes 的值減1, 因此任何時(shí)間點(diǎn)擊button, 都會(huì)更新 state
訪問(wèn)存儲(chǔ)狀態(tài)
當(dāng)我們定義上面的狀態(tài)時(shí), 我們使用 set()
方法, 假設(shè)我們?cè)谝粋€(gè)程序里, 我們需要存儲(chǔ) 其他地方
的值添加到我們的狀態(tài), 為此, 我們將使用 Zustand
提供的方法 get()
代替, 此方法允許多個(gè)狀態(tài)使用相同的值
// 第二個(gè)參數(shù) get
const useStore = create((set,get) => ({
votes: 0,
action: () => {
// 使用 get()
const userVotes = get().votes
// ...
}
}));
處理異步數(shù)據(jù)
Zustand
讓存儲(chǔ)異步數(shù)據(jù)變得容易, 這里, 我們只需要發(fā)出 fetch
請(qǐng)求和 set()
方法來(lái)設(shè)置我們的狀態(tài)值
const useStore = create((set) => ({
Votes: {},
fetch: async (voting) => {
const response = await fetch(voting)
set({ Votes: await response.json() })
},
}))
當(dāng) async
函數(shù)返回值, Votes 被分配返回的值, 我們使用 GitHub API
來(lái)演示, 如下所示
const voting = "https://api.github.com/search/users?q=john&per_page=5";
const useStore = create((set) => ({
voting: voting,
Votes: {},
fetch: async () => {
const response = await fetch(voting);
const json = await response.json();
set({ Votes: json.items })
},
}))
在上面的代碼中, 這個(gè) URL 對(duì) github api 進(jìn)行調(diào)用, 返回對(duì)應(yīng)的值, store
會(huì)等待獲取請(qǐng)求, 然后更新值, 我們可以將 URL 作為參數(shù)傳遞給狀態(tài)的 fetch 屬性, 如下所示
import create from "zustand";
const useStore = create((set, get) => ({
votes: 0,
addVotes: () =>
set((state) => ({
votes: state.votes + 1
})),
subtractVotes: () =>
set((state) => ({
votes: state.votes - 1
})),
fetch: async (voting: any) => {
const response = await fetch(voting);
const json = await response.json();
set({
votes: json.items.length
});
}
}));
export { useStore };
import { useState } from "react";
import { useStore } from "./store";
const voting = "https://api.github.com/search/users?q=john&per_page=5";
export default function App() {
const getVotes = useStore((state) => state.votes);
const addVotes = useStore((state) => state.addVotes);
const subtractVotes = useStore((state) => state.subtractVotes);
const fetch = useStore((state) => state.fetch);
return (
<div className="App">
<h1>{getVotes} People</h1>
<button onClick={addVotes}>Cast a vote</button>
<button onClick={subtractVotes}>Delete a vote</button>
<button
onClick={() => {
fetch(voting);
}}
>
Fetch votes
</button>
</div>
);
}
上面的代碼中, API 返回的 items 的長(zhǎng)度顯示在 h1
元素中, 并且這個(gè)按鈕有一個(gè) onClick
事件, 該事件在狀態(tài)的 fetch
屬性中運(yùn)行該函數(shù), voting
當(dāng)做參數(shù)傳遞過(guò)去, 使用 Zustand
, 一旦異步請(qǐng)求完成, 狀態(tài)就會(huì)更新
在狀態(tài)中訪問(wèn)和存儲(chǔ)數(shù)組
假設(shè)我們需要在 Zustand
中存儲(chǔ)一個(gè) state 中的數(shù)組, 我們可以像下面這樣定義
const useStore = create(set => ({
fruits: ['apple', 'banana', 'orange'],
addFruits: (fruit) => {
set(state => ({
fruits: [...state.fruits, fruit]
}));
}
}));
以上, 我們創(chuàng)建了一個(gè) store
包含了 fruits state
, 其中包含了一系列水果, 第二個(gè)參數(shù)是 addFruits
, 接受一個(gè)參數(shù) fruit
并運(yùn)行一個(gè)函數(shù)來(lái)得到 fruits state
和 新增的 fruits
, 第二個(gè)變量用于更新我們存儲(chǔ)狀態(tài)的值
在我們應(yīng)用中訪問(wèn)狀態(tài), 我們需要循環(huán)數(shù)組來(lái)返回我們所有的水果, 我們還可以通過(guò) input
字段來(lái)更新
const fruits = useStore((state) => state.fruits);
const addFruits = useStore((state) => state.addFruits);
const inputRef = useRef();
const addFruit = () => {
addFruits(inputRef.current.value);
inputRef.current.value = "";
};
return (
<div className="App">
<h1>I have {fruits.length} fruits in my basket</h1>
<p>Add a new fruit</p>
<input ref={inputRef} />
<button onClick={addFruit}>Add a fruit</button>
{fruits.map((fruit) => (
<p key={fruit}>{fruit}</p>
))}
</div>
);
持續(xù)狀態(tài)
狀態(tài)管理庫(kù)的一個(gè)共同特點(diǎn)是持久化狀態(tài), 例如: 在有 form
的網(wǎng)站中, 你希望保存用戶(hù)信息, 如果用戶(hù)不小心刷新了頁(yè)面, 你會(huì)丟失所有數(shù)據(jù)記錄. 在我們的應(yīng)用中, 刷新時(shí), 添加到狀態(tài)的數(shù)據(jù)會(huì)丟失
Zustand
提供了持久化狀態(tài)以防止數(shù)據(jù)丟失的功能, 這個(gè)功能, 我們將使用 Zustand
提供的名為 persist
的中間件, 該中間件通過(guò) localStorage
來(lái)持久化來(lái)自應(yīng)用程序的數(shù)據(jù), 這樣, 當(dāng)我們刷新頁(yè)面或者完全關(guān)閉頁(yè)面時(shí), 狀態(tài)不會(huì)重置
import {persist} from "zustand/middleware"
// and modify our existing state
let store = (set) => ({
fruits: ["apple", "banana", "orange"],
addFruits: (fruit) => {
set((state) => ({
fruits: [...state.fruits, fruit],
}));
},
});
// persist the created state
store = persist(store, {name: "basket"})
// create the store
const useStore = create(store);
在上面的代碼中, 我們持久化了 store
的值, localStorage
的 key 設(shè)為 basket
, 有了這個(gè), 我們?cè)谒⑿马?yè)面時(shí)不會(huì)丟失新增的數(shù)據(jù), 永久保存(即: 在執(zhí)行清除本地存儲(chǔ)的操作之前, 狀態(tài)保持不變)
另外: Zustand
提供了一個(gè)中間件來(lái)使用 Redux
開(kāi)發(fā)工具擴(kuò)展從瀏覽器查看狀態(tài)值, 這個(gè), 我們 import devtools from 'zustand/middleware'
, 像使用 持久化的方法使用它
store = devtools(store)
結(jié)論
本教程告訴讀者如何在 React
中使用 Zustand
管理狀態(tài), Zustand
提供了一種簡(jiǎn)單的方式來(lái)訪問(wèn)和更新 state