Published on
791

实现一个简单的zustand状态管理器

Authors
  • avatar
    Name
    小辉辉
    Twitter

背景

从官方zustand文档我们可以了解到这个状态管理器有以下几个优点

  • 使用简单无歧义
  • 主要使用自定义hooks模式来管理状态
  • 不需要用context provider包裹应用
  • 支持通过订阅形式来实现临时更新,但不用重新渲染整个组件

上面几个优点里,我对第二点和第三点尤其觉得方便,特别是用过redux的人来说。

抽空就瞄了下zustand内部的实现,初步看完以后发现实现起来果然很简单的,前提是要对react本身提供的useSyncExternalStorehook有一定的了解,详细可以查看官方使用文档。

这里简单说明下用法,该hook接收3个参数,subscribe, getSnapShot, getServerSnapshot, 其中我们用到前面两个参数。第一个是更新的订阅方法,用于store变化时重新渲染当前组件;第二个参数提供一个方法返回当前的store值。

实现

好了,接下来直接上完整的代码,一共50行不到

import React, { useSyncExternalStore } from 'react';

const create = (fn: any) => {
  let listeners: any[] = [];
  let state: any;
  const set = (update: any) => {
    state = {
      ...state,
      ...update(state),
    };
    listeners.forEach((listener) => {
      listener();
    });
  };

  state = fn(set);
  return (selector: any) => {
    const value = useSyncExternalStore(
      (listener) => {
        listeners.push(listener);

        return () => {
          listeners = listeners.filter((l) => l !== listener);
        };
      },
      () => state,
    );
    return selector(value);
  };
};

const useMaoStore = create((set: any) => ({
  mao: 0,
  change: () =>
    set(() => ({
      mao: Math.random(),
    })),
}));

const P = () => {
  const mao = useMaoStore((state: any) => state.mao);
  const changeMao = useMaoStore((state: any) => state.change);

  return <div onClick={changeMao}>{mao}</div>;
};

export default P;

代码主要实现create方法,用于生成useMaoStore这个自定义hook,在组件里使用该hook获取store钟对应的属性或者更新方法, 也就是我们所谓的selector。

接下来我们看看create方法是怎么实现的,内部主要分三大部分:

一部分是维护一个subscribe的存储数组我们命名为listener,还有一个state变量也就是存储我们所谓的store。

第二部分就是store对外暴露的更新方法,该方法有一个入参update,由外部传入通过和调用该方法来返回新的state和当前的state进行一个合并操作来替换旧store,同时为了实现组件的自动渲染,我们还需要手动依次调用listener数组中订阅的更新方法了。

第三部分就是需要返回一个自定义hook,该hook接收一个seletor参数,函数类型,除此之外调用react自身提供的useSyncExternalStore hook来实现绑定关系,在第一个参数中我们需要将每个更新值push到listener数组中,同时还需返回一个函数在组件销毁时清空lisnter数组中订阅的当前listener,第二个参数只需返回当前的state变量值即可,它的返回值作为selector方法的入参。

可以发现整个实现主要还依赖了js的闭包功能,由此才能实现state和listener变量可以在不同的store之间实现隔离。