Zustand: 一個(gè)輕量、現(xiàn)代的狀態(tài)管理庫(kù)

翻譯自 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 是由 JotaiReact 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 createZustand 引入, 它接受一個(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é)果

結(jié)果

更新 state

除了訪問(wèn)狀態(tài)的值, 我們也可以修改 store 來(lái)改變 votes 的初始值, 我們先創(chuàng)建兩個(gè)額外的屬性 addVotessubtractVotes, 前者的作用是每次增加 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>

update state

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>
);

array

持續(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)保持不變)

持久化存儲(chǔ)

另外: 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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末豪墅,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子黔寇,更是在濱河造成了極大的恐慌偶器,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,123評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件缝裤,死亡現(xiàn)場(chǎng)離奇詭異屏轰,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)憋飞,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門(mén)霎苗,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人榛做,你說(shuō)我怎么就攤上這事唁盏∧诶辏” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,723評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵厘擂,是天一觀的道長(zhǎng)昆淡。 經(jīng)常有香客問(wèn)我,道長(zhǎng)刽严,這世上最難降的妖魔是什么昂灵? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,357評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮舞萄,結(jié)果婚禮上倔既,老公的妹妹穿的比我還像新娘。我一直安慰自己鹏氧,他們只是感情好渤涌,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,412評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著把还,像睡著了一般实蓬。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上吊履,一...
    開(kāi)封第一講書(shū)人閱讀 49,760評(píng)論 1 289
  • 那天安皱,我揣著相機(jī)與錄音,去河邊找鬼艇炎。 笑死酌伊,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的缀踪。 我是一名探鬼主播居砖,決...
    沈念sama閱讀 38,904評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼驴娃!你這毒婦竟也來(lái)了奏候?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,672評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤唇敞,失蹤者是張志新(化名)和其女友劉穎蔗草,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體疆柔,經(jīng)...
    沈念sama閱讀 44,118評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡咒精,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,456評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了旷档。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片模叙。...
    茶點(diǎn)故事閱讀 38,599評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖彬犯,靈堂內(nèi)的尸體忽然破棺而出向楼,到底是詐尸還是另有隱情查吊,我是刑警寧澤,帶...
    沈念sama閱讀 34,264評(píng)論 4 328
  • 正文 年R本政府宣布湖蜕,位于F島的核電站逻卖,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏昭抒。R本人自食惡果不足惜评也,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,857評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望灭返。 院中可真熱鬧盗迟,春花似錦、人聲如沸熙含。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,731評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)怎静。三九已至邮弹,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間蚓聘,已是汗流浹背腌乡。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,956評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留夜牡,地道東北人与纽。 一個(gè)月前我還...
    沈念sama閱讀 46,286評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像塘装,于是被迫代替她去往敵國(guó)和親急迂。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,465評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容