全面的 Zustand 示例

774 阅读4分钟

1. 基本用法

Zustand 的核心是通过 create 函数创建一个状态管理器(store),并使用 useStore Hook 在组件中访问和更新状态。

创建状态管理器

import create from 'zustand';

const useStore = create((set) => ({
  count: 0, // 定义状态
  increment: () => set((state) => ({ count: state.count + 1 })), // 更新状态的方法
  decrement: () => set((state) => ({ count: state.count - 1 })),
}));

在组件中使用状态

import React from 'react';
import { useStore } from './store';

function Counter() {
  const { count, increment, decrement } = useStore(); // 订阅状态和方法
  return (
    <div>
      <h1>Count: {count}</h1>
      <button onClick={increment}>Increment</button>
      <button onClick={decrement}>Decrement</button>
    </div>
  );
}

export default Counter;

2. 高级特性

2.1 中间件

Zustand 支持中间件,用于扩展功能,例如持久化状态或集成开发工具。

  • 持久化状态

    import create from 'zustand';
    import { persist } from 'zustand/middleware';
    
    const useStore = create(
      persist(
        (set) => ({
          count: 0,
          increment: () => set((state) => ({ count: state.count + 1 })),
        }),
        { name: 'my-store' } // 持久化存储的名称
      )
    );
    
  • 集成 Redux DevTools

    import create from 'zustand';
    import { devtools } from 'zustand/middleware';
    
    const useStore = create(
      devtools(
        (set) => ({
          count: 0,
          increment: () => set((state) => ({ count: state.count + 1 })),
        }),
        { name: 'my-store' } // 可选配置
      )
    );
    

2.2 异步操作

Zustand 支持在状态管理器中处理异步逻辑。

const useAsyncStore = create((set) => ({
  data: null,
  loading: false,
  fetchData: async () => {
    set({ loading: true }); // 设置加载状态
    try {
      const response = await fetch('https://api.example.com/data');
      const result = await response.json();
      set({ data: result, loading: false }); // 更新数据
    } catch (error) {
      set({ loading: false });
      console.error(error);
    }
  },
}));

2.3 选择性订阅

Zustand 支持选择性订阅,只监听需要的状态片段,减少不必要的组件重渲染。

const useStore = create((set) => ({
  user: { name: '', age: 0 },
  updateUser: (user) => set({ user }),
}));

function UserProfile() {
  const name = useStore((state) => state.user.name); // 只监听 name 属性
  const updateName = useStore((state) => state.updateUser);

  return (
    <div>
      <p>Name: {name}</p>
      <input
        type="text"
        value={name}
        onChange={(e) => updateName({ name: e.target.value })}
      />
    </div>
  );
}

3. 实际项目中的应用

示例:待办事项应用

以下是一个完整的待办事项应用示例,展示如何在项目中使用 Zustand 管理状态。

创建待办事项 Store
import create from 'zustand';

const useTodoStore = create((set) => ({
  todos: [],
  addTodo: (todo) => set((state) => ({ todos: [...state.todos, todo] })),
  removeTodo: (index) => set((state) => {
    const todos = [...state.todos];
    todos.splice(index, 1);
    return { todos };
  }),
}));
使用待办事项 Store
import React, { useState } from 'react';
import useTodoStore from './todoStore';

const TodoApp = () => {
  const { todos, addTodo, removeTodo } = useTodoStore();
  const [inputValue, setInputValue] = useState('');

  const handleAddTodo = () => {
    if (inputValue.trim()) {
      addTodo(inputValue);
      setInputValue('');
    }
  };

  return (
    <div>
      <h1>Todo List</h1>
      <input
        type="text"
        value={inputValue}
        onChange={(e) => setInputValue(e.target.value)}
      />
      <button onClick={handleAddTodo}>Add Todo</button>
      <ul>
        {todos.map((todo, index) => (
          <li key={index}>
            {todo}
            <button onClick={() => removeTodo(index)}>Remove</button>
          </li>
        ))}
      </ul>
    </div>
  );
};

export default TodoApp;

4. 其他特性

4.1 TypeScript 支持

Zustand 提供了完整的 TypeScript 支持,确保类型安全。

import create, { StateCreator } from 'zustand';

interface StoreState {
  count: number;
  increment: () => void;
}

const createStore: StateCreator<StoreState> = (set) => ({
  count: 0,
  increment: () => set((state) => ({ count: state.count + 1 })),
});

const useStore = create<StoreState>(createStore);

Zustand 不仅适用于 React,还可以在其他框架(如 Svelte 或 Vue)中使用。

在使用 Zustand 时,直接修改 Store 的值并不是推荐的方式,因为 Zustand 的设计是基于不可变数据原则的。这意味着你需要通过定义好的 更新函数 来修改状态,而不是直接操作状态对象。

如果你需要将一个外部的数组(比如 useList)存放到 Zustand 的 Store 中,可以通过以下方式实现:

示例:将外部数组存入 Zustand Store

1. 定义 Store

假设你有一个 useList,你希望将其存储到 Zustand 的状态中。你需要在 Store 中定义一个状态字段(比如 list),并提供一个方法来更新这个字段。

import create from 'zustand';

const useStore = create((set) => ({
  list: [], // 定义初始状态
  setList: (newList) => set({ list: newList }), // 提供更新方法
}));

2. 在组件中使用并更新状态

假设你在某个组件中获取了 useList,并希望将其存入 Zustand 的 Store 中。

import React, { useEffect } from 'react';
import { useStore } from './store'; // 引入上面定义的 Store

const MyComponent = () => {
  const { list, setList } = useStore(); // 从 Store 中获取状态和更新方法
  const useList = ['item1', 'item2', 'item3']; // 假设这是你从外部获取的数组

  // 在组件加载时将 useList 存入 Zustand 的 list 状态中
  useEffect(() => {
    setList(useList);
  }, [setList]);

  return (
    <div>
      <h1>Items from Zustand Store:</h1>
      <ul>
        {list.map((item, index) => (
          <li key={index}>{item}</li>
        ))}
      </ul>
    </div>
  );
};

export default MyComponent;

关键点说明

  1. 不可变数据原则:Zustand 遵循不可变数据原则,因此你不能直接修改状态对象,而是通过调用更新函数来替换状态。
  2. 更新方法:在 Store 中定义一个更新方法(如 setList),并通过这个方法将外部数组存入状态中。
  3. 副作用处理:在组件中使用 useEffect 来处理状态更新,确保在组件加载或数据变化时触发更新。

如果需要动态更新数组

如果你需要动态更新数组(比如添加、删除或修改数组中的元素),可以在 Store 中定义更细粒度的更新方法。例如:

const useStore = create((set) => ({
  list: [], // 初始状态
  setList: (newList) => set({ list: newList }), // 设置整个数组
  addItem: (item) => set((state) => ({ list: [...state.list, item] })), // 添加元素
  removeItem: (index) => set((state) => {
    const newList = [...state.list];
    newList.splice(index, 1);
    return { list: newList };
  }),
}));

这样,你可以根据需要调用 addItemremoveItem 方法来动态更新数组。

总结

在 Zustand 中,永远不要直接修改状态对象,而是通过定义好的更新方法来操作状态。这种方式不仅符合不可变数据原则,还能确保状态更新的可预测性和组件的正确渲染。