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
相关推荐
小_太_阳23 分钟前
Scala_【2】变量和数据类型
开发语言·后端·scala·intellij-idea
直裾26 分钟前
scala借阅图书保存记录(三)
开发语言·后端·scala
星就前端叭1 小时前
【开源】一款基于Vue3 + WebRTC + Node + SRS + FFmpeg搭建的直播间项目
前端·后端·开源·webrtc
小林coding2 小时前
阿里云 Java 后端一面,什么难度?
java·后端·mysql·spring·阿里云
AI理性派思考者2 小时前
【保姆教程】手把手教你在Linux系统搭建早期alpha项目cysic的验证者&证明者
后端·github·gpu
从善若水3 小时前
【2024】Merry Christmas!一起用Rust绘制一颗圣诞树吧
开发语言·后端·rust
机器之心3 小时前
终于等来能塞进手机的文生图模型!十分之一体量,SnapGen实现百分百的效果
人工智能·后端
机器之心3 小时前
首次!大模型自动搜索人工生命,做出AI科学家的Sakana AI又放大招
人工智能·后端
运维&陈同学3 小时前
【模块一】kubernetes容器编排进阶实战之基于velero及minio实现etcd数据备份与恢复
数据库·后端·云原生·容器·kubernetes·etcd·minio·velero
waterme1onY4 小时前
Spring AOP 中记录日志
java·开发语言·笔记·后端