状态管理一直是现代程序应用中的重要组成部分, 在早期, 通过将数据传递给各种应用程序来管理状态, 但随着应用程序复杂性的增加, 这种方法开始变得低效, 为了解决这些, 不同的状态管理库被开发出来, 他们唯一的目标是创建一个中央的存储来管理这些状态, 在各个组件分发数据, 这样做的结果是有了清晰的代码和更好的数据处理和组件之间的共享.
Zustand作为一个状态管理库
Zustand 是由 Jotai 和 React springs 的开发人员构建的快速且可扩展的状态管理解决方案, Zustand 以简单被大家所知, 它使用 hooks 来管理状态无需样板代码
有很多的流行 React 状态管理工具, 但一下是您更喜欢使用 Zustand 的一些原因,也是Zustand的特点
- 更少的样板代码
- Zustand 只在 state 的值改变时渲染组件, 通常可以处理状态的改变而无需渲染代码
- 状态管理通过简单定义的操作进行集中和更新, 在这方面和 Redux 类似, 但是又和 Redux 不太类似,Redux 开发必须创建 reducer、action、dispatch来处理状态, Zustand 让它变得更加容易
- 使用 hooks 来管理 states, Hooks 在 react 中很流行, 因此是一个很受欢迎的状态管理库
- Zustand 使用简单使用和简单实现的代码
- 通过消除使用 Context Provides 从而使代码更短、更易读
启动
第一步就是创建一个新的React应用并且安装 Zustand 依赖, 运行下的的命令
npm install zustand # or yarn add zustand
现在, 在我们的项目文件中安装了我们的状态管理供我们使用, 现在我们需要定义一个 store 希望包含应用程序使用的所有状态和他们的函数, 我们在 App.js 文件中定义
import create from 'zustand'
const useStore = create(set => ({
votes: 0
}))
以上, 我们创建了一个 store 来跟踪关于 votes 的状态, 初始值是 0, 在这里, 我们的 store 名字是 useStore. 定义 store, 我们使用了 function create 从 Zustand 引入, 它接受一个回调函数来创建 store
访问Store
在我们的应用中使用这个 state, 我们可以将创建状态的值绑定到 DOM 元素
const getVotes = useStore(state => state.votes);
return (
<div className="App">
<h1>{getVotes} people have cast their votes</h1>
</div>
);
更新Store
除了访问状态的值, 我们也可以修改 store 来改变 votes 的初始值, 我们先创建两个额外的属性 addVotes 和 subtractVotes, 前者的作用是每次增加 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>
访问存储状态
当我们定义上面的状态时, 我们使用 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>
);
}
在状态中访问和存储数据
假设我们需要在 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>
);
持续状态
状态管理库的一个共同特点是持久化状态, 例如: 在有 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, 有了这个, 我们在刷新页面时不会丢失新增的数据, 永久保存(即: 在执行清除本地存储的操作之前, 状态保持不变)