zustand 的使用 | 豆包MarsCode AI刷题

179 阅读5分钟

上周学习了 zustand 的使用,记录一下 zustand 的存储库

zustand 是一个状态管理的库,它是一个基于 react hooks 的状态管理库,它的特点是简单、轻量、易用,它的 api 设计的很简单,只有三个函数,create、useStore、devtools

安装 zustand

npm i zustand

因为 zustand 是基于 react 的 hooks 的 api,使用还是比较简单易懂的。

我们的存储是一个钩子,可以在里面放任何的东西,原始值,对象,函数,通过create((set)=>({state:0}))的方式来创建一个 store,然后就可以在任何的地方使用这一个钩子,当状态发生改变的时候,就会自动地更新组件。

状态管理的本质

单例模式,我们在在一个地方创建了一个记录,我们在后续的操作中,都是在这个记录上进行操作,这样就可以保证数据的一致性。 观察者模式,当数据发生改变的时候,会通知所有的观察者,这样就可以保证数据的一致性。

那么 zustand 的伪代码就可以写成这样

function CreateStore(initState) {
  let state = initState;
  const listeners = []; // 存储订阅的监听器

  // 更新状态并通知所有订阅者
  function setState(newState) {
    state = { ...state, ...newState }; // 合并新状态
    listeners.forEach((listener) => listener(state)); // 通知所有订阅者
  }

  // 获取当前的状态
  const getState = () => state;

  // 订阅状态变化
  const subscribe = (listener) => {
    listeners.push(listener); // 将监听器添加到订阅列表
    // 返回一个取消订阅的函数
    return () => {
      // 更新 listeners 数组,移除对应的 listener
      listeners = listeners.filter((l) => l !== listener);
    };
  };

  // 返回 store 对外的接口
  return { setState, getState, subscribe };
}
const store = CreateStore({ count: 0 });

// 订阅状态变化
const unsubscribe = store.subscribe((state) => {
  console.log("State updated:", state);
});

// 更新状态
store.setState({ count: 1 }); // 会触发订阅者的回调,打印 "State updated: { count: 1 }"

// 获取当前状态
console.log(store.getState()); // 输出: { count: 1 }

// 取消订阅
unsubscribe();

// 再次更新状态,不会触发取消的订阅者
store.setState({ count: 2 }); // 不会打印任何信息

创建一个 store,初始状态为 { count: 0 },然后通过订阅状态的变化,当状态发生变化时,会通知所有的订阅者,这样就实现了状态管理的功能。

zustand避免了使用 ReactContext 或者传递 props 的操作,在 React 中状态时需要多层组件传递然后通过上下文来实行共享的,而在 zustand 中,任何组件都可以使用 store 中的状态或者更新方法。

zustand 的比较方式,zustand 使用了浅比较来优化性能,当 store 中的某个状态发生变化的时候,订阅了改状态的组件才会被重新渲染。

zustand 的使用

import { create } from "zustand";
import { devtools, persist } from "zustand/middleware";

interface BearState {
  bears: number;
  increase: () => void;
}
const useBearState = create<BearState>()(
  devtools(
    persist((set, get) => {
      return {
        bears: 0,
        increase: () => set((state) => ({ bears: state.bears + 1 })),
      };
    }),
    {
      name: "bear-storage",
    }
  )
);

注意这里的create<BearState>()括号是很重要的,create<BearState>()表示我们在创建 store 的时候指定了 store 的状态,更重要的为了让中间件可以更好的运行

快速上手

type BearType = {
  bears: number;
  incrementBears: () => void;
  resetBears: () => void;
  decrementBears: (step?: number) => void;
  //异步修改
  asyncIncrementBears: () => void;
};
//store/index.ts
import { create } from "zustand";
//首先导入了zustand的create
const useStore = create<BearType>()((set, get) => {
  return {
    //bears相关的数据
    bears: 0,
    incrementBears: () => {
      set((prevState) => ({ bears: prevState.bears + 1 }));
    },
    resetBears: () => {
      set({ bears: 0 });
    },
    decrementBears: (step = 1) => {
      set((prevState) => ({ bears: prevState.bears - step }));
    },
    asyncIncrementBears: () => {
      setTimeout(() => {
        get().incrementBears();
      }, 1000);
    },
  };
});

export default useStore;
import { FC } from "react";
import useStore from "./store";
export const Bear: FC = () => {
  const bears = useStore((state) => state.bears);
  return (
    <div>
      <h1>小熊的数量是{bears}</h1>
    </div>
  );
};
export const Child: FC = () => {
  const incrementBears = useStore((state) => state.incrementBears);
  const resetBears = useStore((state) => state.resetBears);
  const decrementBears = useStore((state) => state.decrementBears);
  const asyncIncrementBears = useStore((state) => state.asyncIncrementBears);
  return (
    <div>
      <button onClick={incrementBears}>增加</button>
      <button onClick={resetBears}>重置</button>
      <button onClick={decrementBears}>减少</button>
      <button onClick={asyncIncrementBears}>异步增加</button>
    </div>
  );
};

使用自动生成选择器

import { StoreApi, UseBoundStore } from "zustand";

type WithSelectors<S> = S extends { getState: () => infer T }
  ? S & { use: { [K in keyof T]: () => T[K] } }
  : never;

const createSelectors = <S extends UseBoundStore<StoreApi<object>>>(
  _store: S
) => {
  let store = _store as WithSelectors<typeof _store>;
  store.use = {};
  for (let k of Object.keys(store.getState())) {
    (store.use as any)[k] = () => store((s) => s[k as keyof typeof s]);
  }

  return store;
};

然后在 store/index.ts 中使用

import { createSelectors } from "./createSelectors";

import { create } from "zustand";
//首先导入了zustand的create
const useStore = create<BearType>()((set, get) => {
  return {
    //bears相关的数据
    bears: 0,
    incrementBears: () => {
      set((prevState) => ({ bears: prevState.bears + 1 }));
    },
    resetBears: () => {
      set({ bears: 0 });
    },
    decrementBears: (step = 1) => {
      set((prevState) => ({ bears: prevState.bears - step }));
    },
    asyncIncrementBears: () => {
      setTimeout(() => {
        get().incrementBears();
      }, 1000);
    },
  };
});

export default useStore;
export const useStoreWithSelectors = createSelectors(useStore);

然后就可以愉快的使用

import { useStoreWithSelectors } from "./store";
//
const bears11 = useStoreWithSelectors.use.bears(); //可以使用这样的方式来导入获取数据

优化

可以将操作和状态拆分开来,这样可以更好的管理状态,这有利于代码的分割

//1 按需导入create函数
import { create } from "zustand";
//在zustand中中间件本身就是一个函数
import { devtools, persist } from "zustand/middleware";
import { immer } from "zustand/middleware/immer";
//2 创建store的hook
//使用devtools一定要晚于immer
import createSelector from "@/tools/createSelector";

const useBearStore = create<BearStoreType>()(
  immer(
    devtools(
      persist(
        () => {
          return {
            //bears相关的数据
            bears: 0,
          };
        },
        { name: "bear-store" }
      ),
      { name: "bear-store" }
    )
  )
);
export const incrementBears = () => {
  //使用immer的语法
  useBearStore.setState((prevState) => {
    prevState.bears += 1;
  }); //函数体的大括号不可以省略
};
export const resetBears = () => {
  useBearStore.setState({ bears: 0 });
}; //这是对象合并的方式
export const decrementBears = (step = 1) => {
  useBearStore.setState((prevState) => {
    prevState.bears -= step;
  });
};
export const asyncIncrementBear = () => {
  setTimeout(() => {
    incrementBears();
  }, 1000);
};

export default useBearStore;
export const useBearSelector = createSelector(useBearStore);

这里使用了 Zustand 的多个中间件(devtools、persist、immer)来创建一个 store, ,通过封装的操作函数(incrementBears、decrementBears、resetBears 等)来操作 store 的状态。

重置状态的方法,可以实现使用一个 init 状态

//然后再使用useBearStore的时候,可以使用这个initState
import { StateCreator } from "zustand";
import useStore from "@/store";
import resetters from "../tools/resetters";
const initBearState = {
  bears: 0,
};
//这里就是我的init状态
const createBearSlice: StateCreator<BearSliceType> = (set) => {
  resetters.push(() => {
    set(initBearState);
  });
  return {
    ...initBearState,
  };
};
export const incrementBears = () =>
  useStore.setState((prevState) => {
    prevState.bears++;
  });

export const resetBears = () => {
  useStore.setState(initBearState);
};
export const decrementBears = (step = 1) => {
  useStore.setState((prevState) => ({ bears: prevState.bears - step }));
};
export const asyncIncrementBears = () => {
  setTimeout(() => {
    incrementBears();
  }, 1000);
};
export default createBearSlice;

使用这种方式,可以使结构更加清晰,在创建的时候就已经使用了 init 状态