Zustand

1. 简介

  • 什么是Zustand

Zustand是一个小巧但功能强大的状态管理库,它使用了React Hooks的强大功能,使得状态管理变得更加简单和直观。

  • Zustand的优势

Zustand的优势在于它的轻量级和易用性。它的API设计非常简单,易于使用和理解。它还提供了一些非常有用的功能,如派生状态和中间件。

  • Zustand的应用场景

Zustand适用于任何需要状态管理的React应用程序。它特别适用于中小型应用程序,因为它非常轻量级,不需要太多的配置和设置。

  • 应用场景:
  1. 简单的表单处理
  2. 状态共享
  3. 复杂的状态逻辑
  • 使用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. 状态管理

  • 什么是状态管理
    • 状态管理是指在应用程序中管理和维护状态的过程。状态是指应用程序中的数据,例如用户输入、网络请求、组件状态等。状态管理的目的是使应用程序的状态变得可预测和可控,从而提高应用程序的可维护性和可扩展性。
  • 状态管理的优势
    • 状态管理的优势在于它可以使应用程序的状态变得可预测和可控。它可以帮助我们避免一些常见的问题,例如状态不一致、状态更新不及时等。它还可以提高应用程序的可维护性和可扩展性,因为它可以使我们更容易地理解和修改应用程序的状态。
  • 状态管理的视线
    • 状态管理可以通过多种方式实现,例如使用ReactContext 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的场景
  • 需要避免reduxaction 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
相关推荐
Marktowin2 小时前
Mybatis-Plus更新操作时的一个坑
java·后端
赵文宇3 小时前
CNCF Dragonfly 毕业啦!基于P2P的镜像和文件分发系统快速入门,在线体验
后端
程序员爱钓鱼3 小时前
Node.js 编程实战:即时聊天应用 —— WebSocket 实现实时通信
前端·后端·node.js
Libby博仙4 小时前
Spring Boot 条件化注解深度解析
java·spring boot·后端
源代码•宸4 小时前
Golang原理剖析(Map 源码梳理)
经验分享·后端·算法·leetcode·golang·map
小周在成长4 小时前
动态SQL与MyBatis动态SQL最佳实践
后端
瓦尔登湖懒羊羊4 小时前
TCP的自我介绍
后端
小周在成长4 小时前
MyBatis 动态SQL学习
后端
子非鱼9214 小时前
SpringBoot快速上手
java·spring boot·后端