1. 简介
- 什么是
Zustand
Zustand
是一个小巧但功能强大的状态管理库,它使用了React Hooks
的强大功能,使得状态管理变得更加简单和直观。
Zustand
的优势
Zustand
的优势在于它的轻量级和易用性。它的API设计非常简单,易于使用和理解。它还提供了一些非常有用的功能,如派生状态和中间件。
Zustand
的应用场景
Zustand
适用于任何需要状态管理的React
应用程序。它特别适用于中小型应用程序,因为它非常轻量级,不需要太多的配置和设置。
- 应用场景:
- 简单的表单处理
- 状态共享
- 复杂的状态逻辑
- 使用
Zustand
的示例: -
js
import create from 'zustand'
const useStore = create((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
decrement: () => set((state) => ({ count: state.count - 1 })),
}))
function Counter() {
const { count, increment, decrement } = useStore()
return (
<div>
<h1>Count: {count}</h1>
<button onClick={increment}>Increment</button>
<button onClick={decrement}>Decrement</button>
</div>
)
}
2. 安装和使用
-
安装
Zustand
npm install zustand
-
创建一个
Zustand store
javascript
import create from 'zustand'
const useStore = create((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
decrement: () => set((state) => ({ count: state.count - 1 })),
}))
- 使用
Zustand store
js
function Counter() {
const { count, increment, decrement } = useStore()
return (
<div>
<h1>Count: {count}</h1>
<button onClick={increment}>Increment</button>
<button onClick={decrement}>Decrement</button>
</div>
)
}
3. 使用中间件
Zustand
提供了中间件的功能,可以用于在状态更新前后执行一些操作。中间件是一个函数,它接收一个对象,其中包含了当前状态和一个更新状态的函数。中间件可以在状态更新前后执行一些操作,例如打印日志、发送网络请求等。
logger
中间件
js
import create from 'zustand'
const logger = (config) => (set, get, api) =>
config(
(args) => {
console.log('before update', get())
set(args)
console.log('after update', get())
},
get,
api
)
const useStore = create(
logger((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
decrement: () => set((state) => ({ count: state.count - 1 })),
}))
)
function Counter() {
const { count, increment, decrement } = useStore()
return (
<div>
<h1>Count: {count}</h1>
<button onClick={increment}>Increment</button>
<button onClick={decrement}>Decrement</button>
</div>
)
}
immer
中间件
js
import create from 'zustand'
import produce from 'immer'
const immer = (config) => (set, get, api) =>
config(
(args) => {
set(produce(args))
},
get,
api
)
const useStore = create(
immer((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
decrement: () => set((state) => ({ count: state.count - 1 })),
}))
)
function Counter() {
const { count, increment, decrement } = useStore()
return (
<div>
<h1>Count: {count}</h1>
<button onClick={increment}>Increment</button>
<button onClick={decrement}>Decrement</button>
</div>
)
}
persist
js
import create from 'zustand'
import produce from 'immer'
const immer = (config) => (set, get, api) =>
config(
(args) => {
set(produce(args))
},
get,
api
)
const store = (set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
decrement: () => set((state) => ({ count: state.count - 1 })),
})
const persistStore = persist(immer(store), { name: 'demo' });
const useStore = create(persistStore);
function Counter() {
const { count, increment, decrement } = useStore()
return (
<div>
<h1>Count: {count}</h1>
<button onClick={increment}>Increment</button>
<button onClick={decrement}>Decrement</button>
</div>
)
}
4. 状态管理
- 什么是状态管理
-
- 状态管理是指在应用程序中管理和维护状态的过程。状态是指应用程序中的数据,例如用户输入、网络请求、组件状态等。状态管理的目的是使应用程序的状态变得可预测和可控,从而提高应用程序的可维护性和可扩展性。
- 状态管理的优势
-
- 状态管理的优势在于它可以使应用程序的状态变得可预测和可控。它可以帮助我们避免一些常见的问题,例如状态不一致、状态更新不及时等。它还可以提高应用程序的可维护性和可扩展性,因为它可以使我们更容易地理解和修改应用程序的状态。
- 状态管理的视线
-
- 状态管理可以通过多种方式实现,例如使用
React
的Context API、Redux、MobX、Zustand
等。
- 状态管理可以通过多种方式实现,例如使用
js
import React, { createContext, useContext, useState } from 'react';
// 创建一个 StoreContext
const StoreContext = createContext();
// 创建一个自定义 hook,用于获取 StoreContext 的值
const useStore = () => useContext(StoreContext);
// 创建一个 StoreProvider 组件,用于包裹子组件,并提供 state 和一些操作 state 的方法
const StoreProvider = ({ children }) => {
const [state, setState] = useState({ count: 0 });
const subscribers = [];
// 定义一个 increment 方法,用于增加 count 的值,并通知所有的 subscribers
const increment = () => {
setState((prevState) => {
const newState = { count: prevState.count + 1 };
subscribers.forEach((subscriber) => subscriber(newState));
return newState;
});
};
// 定义一个 decrement 方法,用于减少 count 的值,并通知所有的 subscribers
const decrement = () => {
setState((prevState) => {
const newState = { count: prevState.count - 1 };
subscribers.forEach((subscriber) => subscriber(newState));
return newState;
});
};
// 定义一个 subscribe 方法,用于添加一个 subscriber,并返回一个取消订阅的方法
const subscribe = (subscriber) => {
subscribers.push(subscriber);
return () => {
const index = subscribers.indexOf(subscriber);
subscribers.splice(index, 1);
};
};
// 返回一个 StoreContext.Provider,将 state、increment、decrement、subscribe 作为 value 传递给子组件
return (
<StoreContext.Provider value={{ state, increment, decrement, subscribe }}>
{children}
</StoreContext.Provider>
);
};
// 创建一个 Counter 组件,用于展示 count 的值,并提供增加和减少 count 的按钮
function Counter() {
const { state, increment, decrement, subscribe } = useStore();
const { count } = state;
// 定义一个 handleStateChange 方法,用于处理 state 的变化
const handleStateChange = (newState) => {
console.log('New state:', newState);
};
// 添加一个 subscriber,用于监听 state 的变化
subscribe(handleStateChange);
// 返回一个包含 count、增加按钮
5. 实战
- 使用
Zustand
实现一个计数器
在这个案例中,我们将使用Zustand
来实现一个简单的计数器。首先,我们需要使用Zustand
来创建一个状态管理器。我们可以使用create
函数来创建一个状态管理器,如下所示:
js
import create from 'zustand'
const useCounter = create((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
decrement: () => set((state) => ({ count: state.count - 1 })),
}))
// 在这个状态管理器中,我们定义了一个count状态,它的初始值为0。我们还定义了两个函数increment和decrement,它们分别用于增加和减少count状态的值。
js
import React from 'react'
import { useCounter } from './useCounter'
export const Counter = () => {
const { count, increment, decrement } = useCounter()
return (
<div>
<h1>{count}</h1>
<button onClick={increment}>+</button>
<button onClick={decrement}>-</button>
</div>
)
}
// 在这个组件中,我们使用useCounter hook来访问count、increment和decrement状态。我们将count状态的值显示在页面上,并在两个按钮上分别绑定increment和decrement函数。
- 使用
Zustand
实现一个Todo
应用
js
import create from 'zustand'
const useStore = create((set) => ({
todos: [],
addTodo: (todo) => set((state) => ({ todos: [...state.todos, todo] })),
deleteTodo: (id) =>
set((state) => ({ todos: state.todos.filter((todo) => todo.id !== id) })),
}))
// 在这里,我们定义了一个空的todos数组和两个方法addTodo和deleteTodo。addTodo方法将一个新的todo添加到todos数组中,而deleteTodo方法从todos数组中删除一个指定id的todo。
js
import React, { useState } from 'react'
import { useStore } from './store'
function App() {
const [text, setText] = useState('')
const todos = useStore((state) => state.todos)
const addTodo = useStore((state) => state.addTodo)
const deleteTodo = useStore((state) => state.deleteTodo)
const handleSubmit = (e) => {
e.preventDefault()
addTodo({
id: Date.now(),
text,
})
setText('')
}
return (
<div>
<h1>Todo List</h1>
<form onSubmit={handleSubmit}>
<input value={text} onChange={(e) => setText(e.target.value)} />
<button>添加</button>
</form>
<ul>
{todos.map((todo) => (
<li key={todo.id}>
{todo.text}{' '}
<button onClick={() => deleteTodo(todo.id)}>删除</button>
</li>
))}
</ul>
</div>
)
}
export default App
// 在这个组件中,我们定义了一个text状态,它代表输入框的值。我们还使用了useStore hook来获取todos、addTodo和deleteTodo方法。当用户提交表单时,我们会调用addTodo方法来添加一个新的todo,并将text状态重置为空字符串。每个todo都有一个删除按钮,当用户点击它时,我们会调用deleteTodo方法来删除相应的todo。
- 使用
Zustand
实现一个购物车应用
在这个案例中,我们将使用Zustand
来实现一个简单的购物车应用。首先,我们需要使用Zustand
来创建一个状态管理器。我们可以使用create
函数来创建一个状态管理器,如下所示:
js
import create from 'zustand';
const useStore = create((set) => ({
products: [],
cart: [],
addProduct: (product) =>
set((state) => ({ products: [...state.products, product] })),
addToCart: (productId) =>
set((state) => ({ cart: [...state.cart, productId] })),
}));
export default useStore;
// 在这个store中,我们定义了三个状态变量:products表示所有商品的列表,cart表示购物车中添加的商品的id列表,addProduct表示添加商品到商品列表中的方法,addToCart表示将商品添加到购物车中的方法
js
import React from 'react';
import useStore from './useStore';
export default function App() {
const { products, cart, addProduct, addToCart } = useStore();
const handleAddProduct = () => {
const newProduct = { id: products.length + 1, name: `Product ${products.length + 1}`, price: Math.random() * 100 };
addProduct(newProduct);
};
const handleAddToCart = (productId) => {
addToCart(productId);
};
return (
<div className="App">
<h1>购物车应用</h1>
<h2>商品列表</h2>
<ul>
{products.map((product) => (
<li key={product.id}>
{product.name}: {product.price}元
<button onClick={() => handleAddToCart(product.id)}>加入购物车</button>
</li>
))}
</ul>
<button onClick={handleAddProduct}>添加商品</button>
<h2>购物车</h2>
<ul>
{cart.map((productId) => {
const product = products.find((p) => p.id === productId);
return (
<li key={productId}>
{product.name}: {product.price}元
</li>
);
})}
</ul>
</div>
);
}
// 如上代码所示,我们使用了useStore来从store中取出products和cart状态变量,以及addProduct和addToCart方法。在页面中展示了商品列表和购物车列表,并可以通过点击按钮添加商品到商品列表中或者购物车中。
6. 总结
6.1 Zustand
的优势和不足
Zustand
的优势:
- 状态管理简单易懂,代码易于维护
- 状态更新可预测,避免了
redux
中的action type
命名冲突问题 - 可以使用
hooks API
,方便使用
6.2 Zustand
的应用场景和限制
Zustand适用于以下场景:
- 简单的状态管理
- 需要使用
hooks API
的场景 - 需要避免
redux
中action type
命名冲突问题的场景
Zustand
的限制和不足:
- 无法处理复杂的异步操作
- 无法处理多个组件之间的状态共享
Zustand本身并没有直接提供多个组件之间状态共享的功能。虽然可以通过在Provider中注册global state来实现状态共享,但这种方式有时可能会导致过度使用和混乱的全局状态。而且在这种情况下,需要手动处理状态订阅和取消等问题。相比之下,像Redux这样的库专门为跨组件状态管理而设计,具有更好的可扩展性和更强大的工具生态系统。
6.3Zustand
的未来发展方向
- 支持更多的异步操作
- 支持多个组件之间的状态共享
- 提供更多的
hooks API
未来,Zustand可能会添加更多的功能和特性,比如更好的支持异步操作、更灵活的状态订阅方式以及更好的DevTools等等。此外,它还可能会与新的React特性和API(例如React Server Components)进行整合。
7.zustand的源码分析
arduino
// create函数:用于创建store
// createStore函数:用于创建store
// combine函数:用于合并多个store
// devtools函数:用于开启调试工具
// immer函数:用于实现不可变性
create
js
// create函数用于创建store
const create = (fn) => {
// 使用useState函数创建状态
let state = useState(fn())[0]
// 使用useReducer函数创建reducer
const reducer = (state, action) => action(state)
// 使用useReducer函数创建状态操作方法
const setState = useReducer(reducer, state)[1]
// 使用useContext函数获取context
const context = useContext(Context)
// 使用useMemo函数缓存计算结果
const get = (fn) => useMemo(() => fn(state), [state])
// 使用useEffect函数处理副作用
useEffect(() => {
// 订阅状态变化
const unsubscribe = context.subscribe(setState)
// 返回取消订阅函数
return () => unsubscribe()
}, [])
// 使用useCallback函数缓存函数
const api = useCallback((fn) => setState(fn), [])
// 使用useRef函数创建ref
const ref = useRef(api)
// 使用useLayoutEffect函数处理副作用(同步)
useLayoutEffect(() => {
// 将ref.current设置为api
ref.current = api
})
// 返回一个对象,该对象包含状态和操作状态的方法,以及一个subscribe方法,用于订阅状态的变化
return {
...get((s) => s),
...api,
subscribe: (listener) => context.subscribe((state) => listener(get(state))),
}
}
// create函数用于创建store,它接受一个函数作为参数,该函数返回一个对象,该对象包含状态和操作状态的方法。create函数内部使用了useState和useReducer函数来创建状态,使用immer函数来实现不可变性,使用useContext函数来获取context,使用useMemo函数来缓存计算结果,使用useEffect函数来处理副作用,使用useCallback函数来缓存函数,使用useRef函数来创建ref,使用useLayoutEffect函数来处理副作用(同步)。最后,create函数返回一个对象,该对象包含了状态和操作状态的方法,以及一个subscribe方法,用于订阅状态的变化。
- createStore
javascript
// 使用create函数创建store
const createStore = (reducer, initialState) => create(() => initialState || reducer(undefined, { type: '@@zustand/init' }))
// 返回createStore函数
return createStore
中间件
js
// 在代码中,有以下函数:
// applyMiddleware函数:用于应用中间件
// compose函数:用于组合多个函数
// applyMiddleware函数用于应用中间件
const applyMiddleware = (...middlewares) => (createStore) => (reducer, initialState) => {
// 创建store
const store = createStore(reducer, initialState)
// 获取getState方法
const { getState } = store
// 定义next函数
let next = (action) => {
throw new Error('Dispatching while constructing your middleware is not allowed. Other middleware would not be applied to this dispatch.')
}
// 定义chain数组
const chain = middlewares.map((middleware) => middleware({
getState: () => getState(),
dispatch: (action) => next(action),
}))
// 使用compose函数组合chain数组中的函数
next = compose(...chain)(store.dispatch)
// 返回一个新的store,该store包含中间件
return {
...store,
dispatch: next,
}
}
// compose函数用于组合多个函数
const compose = (...funcs) => {
if (funcs.length === 0) {
return (arg) => arg
}
if (funcs.length === 1) {
return funcs[0]
}
return funcs.reduce((a, b) => (...args) => a(b(...args)))
}
// 使用applyMiddleware函数应用中间件
const createStoreWithMiddleware = applyMiddleware(middleware1, middleware2, ...)(create)
useSyncExternalStore
js
// 可以基于外部store创建一个state:
const state = useSyncExternalStore(store.subscribe, ()=>store.getSnapshot().data);
// 传入的第一个参数是是一个订阅函数,react会传入一个listener,当数据发生改变时必须调用这个listener。
// 第二个参数是获取store中state的方法,这里可以直接传store.getSnapshot(),也可以通过()=>store.getSnapshot().data进行select。
// 设计一个store
const store = {
currentState:{data:0},
listeners:[],
reducer(action){
switch(action.type) {
case 'ADD':
return {data:store.currentState.data+1}
default:
return store.state
}
},
subscribe(l){
store.listeners.push(l)
},
getSnapshot() {
return store.currentState
},
dispatch(action) {
store.currentState = store.reducer(action)
store.listeners.forEach(l=>l())
return action;
}
}
// 使用useSyncExternalStorece创建state
import {useSyncExternalStore} from 'react';
function Demo() {
const state = useSyncExternalStore(store.subscribe, ()=>store.getSnapshot().data);
return <div className='p-100'>
<div>count:{state}</div>
<div>
<button onClick={()=>store.dispatch({type:'ADD'})}>add+</button>
</div>
</div>
}
export default Demo