Zustand: 一个轻量、现代的状态管理库

翻译自 Zustand: simple, modern state management for React , 本人英语水平一般, 借助了谷歌翻译和自己的理解, 大概翻译出了全文, 如有错误, 请谅解, 下面所有代码可以查看 codesandbox

状态管理一直是现代程序应用中的重要组成部分, 在早期, 通过将数据传递给各种应用程序来管理状态, 但随着应用程序复杂性的增加, 这种方法开始变得低效, 为了解决这些, 不同的状态管理库被开发出来, 他们唯一的目标是创建一个中央的存储来管理这些状态, 在各个组件分发数据, 这样做的结果是有了清晰的代码和更好的数据处理和组件之间的共享. 在这个教程中, 读者会学习如何使用 Zustand 来管理 states , 以在他们的应用程序中实现简洁的代码和更好的数据流

Zustand 作为一个状态管理库

Zustand 是由 JotaiReact springs 的开发人员构建的快速且可扩展的状态管理解决方案, Zustand 以简单被大家所知, 它使用 hooks 来管理状态无需样板代码

"Zustand" 只是德语的"state"

有很多的流行 React 状态管理工具, 但以下是您更喜欢使用 Zustand 的一些原因

  • 更少的样板代码
  • Zustand 只在 state 的值改变时渲染组件, 通常可以处理状态的改变而无需渲染代码
  • 状态管理通过简单定义的操作进行集中和更新, 在这方面和 Redux 类似, 但是又和 Redux 不太类似, Redux 开发必须创建 reducer、action、dispatch来处理状态, Zustand 让它变得更加容易
  • 使用 hooks 来管理 states, Hooks 在 react 中很流行, 因此是一个很受欢迎的状态管理库
  • Zustand 使用简单使用和简单实现的代码
  • 通过消除使用 Context Provides 从而使代码更短、更易读

启动一个 app

第一步就是创建一个新的React应用并且安装 Zustand 依赖, 运行下的的命令

npx create-react-app zustand
cd zustand
npm install zustand

现在, 在我们的项目文件中安装了我们的状态管理供我们使用, 现在我们需要定义一个 store 希望包含应用程序使用的所有状态和他们的函数, 我们在 App.js 文件中定义


import create from 'zustand'

// define the store
const useStore = create(set => ({
  votes: 0
}))

以上, 我们创建了一个 store 来跟踪关于 votes 的状态, 初始值是 0, 在这里, 我们的 store 名字是 useStore. 定义 store, 我们使用了 function createZustand 引入, 它接受一个回调函数来创建 store

访问 Store

在我们的应用中使用这个 state, 我们可以将创建状态的值绑定到 DOM 元素

const getVotes = useStore(state => state.votes);

return (
    <div className="App">
      <h1>{getVotes} people have cast their votes</h1>
    </div>
  );

在上面的代码中, 我们有一个变量 getVotes , 这个变量包含了 state 的属性 votes 的值, 有了这个, 我们可以访问这个值放入到 h1 DOM 元素中展示这个值, 现在, 如果我们运行我们程序 npm start, 我们可以在页面上看到结果

结果

更新 state

除了访问状态的值, 我们也可以修改 store 来改变 votes 的初始值, 我们先创建两个额外的属性 addVotessubtractVotes, 前者的作用是每次增加 1, 后者减 1

const useStore = create(set => ({
  votes: 0,
  addVotes: () => set(state => ({ votes: state.votes + 1 })),
  subtractVotes: () => set(state => ({ votes: state.votes - 1 })),
}));

下面我们在我们的程序中使用它们

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 会更新 votes 的值 加1, subtractVotes 会更新 votes 的值减1, 因此任何时间点击button, 都会更新 state

访问存储状态

当我们定义上面的状态时, 我们使用 set() 方法, 假设我们在一个程序里, 我们需要存储 其他地方 的值添加到我们的状态, 为此, 我们将使用 Zustand 提供的方法 get() 代替, 此方法允许多个状态使用相同的值

// 第二个参数 get
const useStore = create((set,get) => ({
  votes: 0,
  action: () => {
    // 使用 get()
    const userVotes = get().votes
    // ...
  }
}));

处理异步数据

Zustand 让存储异步数据变得容易, 这里, 我们只需要发出 fetch 请求和 set() 方法来设置我们的状态值

const useStore = create((set) => ({
  Votes: {},
  fetch: async (voting) => {
    const response = await fetch(voting)
    set({ Votes: await response.json() })
  },
}))

async 函数返回值, Votes 被分配返回的值, 我们使用 GitHub API 来演示, 如下所示

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 })
  },
}))

在上面的代码中, 这个 URL 对 github api 进行调用, 返回对应的值, store 会等待获取请求, 然后更新值, 我们可以将 URL 作为参数传递给状态的 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 的长度显示在 h1 元素中, 并且这个按钮有一个 onClick 事件, 该事件在状态的 fetch 属性中运行该函数, voting 当做参数传递过去, 使用 Zustand, 一旦异步请求完成, 状态就会更新

在状态中访问和存储数组

假设我们需要在 Zustand 中存储一个 state 中的数组, 我们可以像下面这样定义

const useStore = create(set => ({
  fruits: ['apple', 'banana', 'orange'],
  addFruits: (fruit) => {
    set(state => ({
      fruits: [...state.fruits, fruit]
    }));
  }
}));

以上, 我们创建了一个 store 包含了 fruits state, 其中包含了一系列水果, 第二个参数是 addFruits , 接受一个参数 fruit 并运行一个函数来得到 fruits state 和 新增的 fruits, 第二个变量用于更新我们存储状态的值

在我们应用中访问状态, 我们需要循环数组来返回我们所有的水果, 我们还可以通过 input 字段来更新

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

持续状态

状态管理库的一个共同特点是持久化状态, 例如: 在有 form 的网站中, 你希望保存用户信息, 如果用户不小心刷新了页面, 你会丢失所有数据记录. 在我们的应用中, 刷新时, 添加到状态的数据会丢失

Zustand 提供了持久化状态以防止数据丢失的功能, 这个功能, 我们将使用 Zustand 提供的名为 persist 的中间件, 该中间件通过 localStorage 来持久化来自应用程序的数据, 这样, 当我们刷新页面或者完全关闭页面时, 状态不会重置

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 设为 basket, 有了这个, 我们在刷新页面时不会丢失新增的数据, 永久保存(即: 在执行清除本地存储的操作之前, 状态保持不变)

持久化存储

另外: Zustand 提供了一个中间件来使用 Redux 开发工具扩展从浏览器查看状态值, 这个, 我们 import devtools from 'zustand/middleware', 像使用 持久化的方法使用它

store = devtools(store)

结论

本教程告诉读者如何在 React 中使用 Zustand 管理状态, Zustand 提供了一种简单的方式来访问和更新 state

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,569评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,499评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,271评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,087评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,474评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,670评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,911评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,636评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,397评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,607评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,093评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,418评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,074评论 3 237
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,092评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,865评论 0 196
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,726评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,627评论 2 270

推荐阅读更多精彩内容