likes
comments
collection
share

zustand状态管理源码解析(一)

作者站长头像
站长
· 阅读数 21
关于react状态管理工具库有很多,比较出名的有redux、mobx,这两款工具库使用相对来说比较广泛,今天我们所要了解的是一个比较小而精美的状态管理工具库zustand,截止目前为止有17+的start,最主要的是这个库提供的store写法简单,不在像以往我们通过state、action、dispatch等等一系列繁琐的操作进行状态管理,同时也可以脱离组件使用,让我们一起读一读源码了解一下他的状态管理机制;

  本次我们先从3.x版本解读,4.x版本作者还在rc阶段,等最终发布后我们在继续追加4.x版本的改动解析;

1、create创建store,与使用

  这个工具库相对来说很简单,我们直接使用

import create from 'zustand'

interface BearStore {
  bears: number,
  increasePopulation: () => void,
  removeAllBears: () => void
}

const useBearStore = create<BearStore>(set => ({
  bears: 0,
  increasePopulation: () => set(state => {
    console.log('用于debug')
    return { bears: state.bears + 1 }
  }),
  removeAllBears: () => set({ bears: 0 })
}))

项目中的使用办法

import useBearStore from './BearStore'

const Home: React.FC = () => {
  const bearStore = useBearStore()
  return <div>
    内容信息
    <div>{ bearStore.bears }</div>
    <button onClick={bearStore.increasePopulation}>增加</button>
    <button onClick={bearStore.removeAllBears}>重置</button>
  </div>
}

  在使用我们对于create函数的使用做一个详细的说明,我们解读一下源码对于create函数的描述

// 源码对于create函数的说明,
const create = (<T extends State>(
  createState: StateCreator<T, [], []> | undefined
) => {
  return createState ? createImpl(createState) : createImpl
}) as Create
// createState参数定义说明
export type StateCreator<
  T extends State,
  Mis extends [StoreMutatorIdentifier, unknown][] = [],
  Mos extends [StoreMutatorIdentifier, unknown][] = [],
  U = T
  > = ((
    setState: Get<Mutate<StoreApi<T>, Mis>, 'setState', undefined>,
    getState: Get<Mutate<StoreApi<T>, Mis>, 'getState', undefined>,
    store: Mutate<StoreApi<T>, Mis>,
    $$storeMutations: Mis
  ) => U) & { $$storeMutators?: Mos }

  关于createState类型定义的描述有很多,本期简单的讲用法;  create我们可以传递一个函数类型的参数,里面包含了(set, get, other) => useStore,返回一个useStore的hooks,在react组件中通过useStore(selector)可以选择性的获取对应store切片,下面我们就看看这些是怎么上面所讲的内容是怎么实现;

2、了解源码create过程

  经过一系列的源码分析与学习,我这边大概总结了一张create执行过程的图解,通过这个图,我们一步步分析他的实现过程,参考图如下:zustand状态管理源码解析(一)简单的来说分为以下几个步骤:

  • 1、通过create方式传入一个构建store的方法(set, get) => ({ xxxx })
  • 2、在create函数内部,通过createStore创建一个状态管理store(通俗点来说就是闭包);
  • 3、createStore创建store以外,还返回下面内容

    • 返回setState方法,setState方法更新值时候,去遍历listener订阅事件列表;
    • 返回getState方法,去获取store对应值;
    • 返回subscribe注入订阅事件的方法;
    • 返回destroy清空订阅事件列表方法;
  • 4、在create方法体内,继续创建一个useStore hooks,返回给react组件去获取store,关于useStore主要处理了下面几件事情:

    • 获取store存储的内容(具体我们可以通过useStore(selector, enqulity)第一个参数获取store切片)
    • 通过useEffect or useLayoutEffect初始化组件store值,并且将state变化事件注入listeners;

下图是我总结出其zustand状态管理的整个流程,大家可以参考一下:zustand状态管理源码解析(一)

3、手写一个toy级别的zustand

  上面我们分析了zustand执行的过程及状态管理的流程,下面我们就尝试着手写toyzustand,这块我们分成两块,一个是创建store部分,一个是创建useStore hooks的部分,具体如下:

function createStore(createState) {
    let state;
    let listeners = new Set();
    // 获取store内容
    const getState = () => state;
    // 更新store内容
    const setState = (partial, replace) => {

        const nextState = typeof partial === 'function' ? partial(state) : partial;

        if (nextState !== state) {
            const prevState = state;
            state = replace ? nextState : Object.assign({}, state, nextState);
            listeners.forEach(listener => listener(state, prevState));
        }
    }
    // 添加订阅信息
    const subscribe = (listener) => {
        listeners.add(listener);
        // 清除订阅信息
        return () => { listeners.delete(listener); };
    }
    // 清除所有的listener
    const destroy = () => listeners.clear();
    
    const api = {getState, setState, destroy, subscribe};
    // 创建初始的state
    state = createState(setState, getState, api);

    return api;
}

export default createStore

生成hooks方法

import { useLayoutEffect } from "react";
import { useReducer, useRef } from "react";
import createStore from "./createStore";

function create(createState) {
    // 根据createStore 结合createState 创建一个store
    const api = createStore(createState);

    /**
     * @description 创建 hooks
     * @param {Function} selector  可选的,返回store的内容,默认api.getState
     * @param {Function} enqulityFn  可选,默认用Object.is 判断
     * @returns 
     */
    const useStore = (selector = api.getState, enqulityFn = Object.is) => {
        // 生辰一个forceUpdate函数
        const [, forceUpdate] = useReducer(c => c + 1, 0);

        const state = api.getState();
        const stateRef = useRef(state);
        // 存储方法
        const selectorRef = useRef(selector);
        const enqulityFnRef = useRef(enqulityFn);

        // 当前current状态存储
        let currentStateRef = useRef();
        if (currentStateRef.current === undefined) {
            currentStateRef.current = selector(state);
        }

        /**
         * 当前用户所需要的状态切片(这块需要注意,zustand用户可以根据selector获取部分store内容值)
         * 所以我们判断是否需要更新,对比的是切片内容,而非整个store
         */
        let newStateSlice;
        // 更新标志
        let hasNewStateSlice = false;

        if (stateRef.current !== state || selector !== selectorRef.current || enqulityFn !== enqulityFnRef.current) {
            newStateSlice = selector(state);
            hasNewStateSlice = !enqulityFn(newStateSlice, currentStateRef.current);
        }

        // 初始化数据
        useLayoutEffect(() => {
            if (hasNewStateSlice) {
                currentStateRef.current = newStateSlice;
            }
            stateRef.current = state;
            selectorRef.current = selector;
            enqulityFnRef.current = enqulityFn;
        })

        // 添加state变化订阅事件
        useLayoutEffect(() => {
            const listener = () => {
                // 获取当前最新的state状态值
                const nextState = api.getState();
                // 拿到当前用户所需的store切片
                const nextStateSlice = selectorRef.current(nextState);
                // 比较当前用户current切片 与 最新store切片是否是一样的,如果不一样,就更新到最新的切片
                if (!enqulityFnRef.current(nextStateSlice, currentStateRef.current)) {
                    stateRef.current = nextState;
                    currentStateRef.current = nextStateSlice;
                    forceUpdate();
                }
            }
            const unSubscribe = api.subscribe(listener);
            // 当组件销毁,我们需要取消订阅
            return unSubscribe
        }, []);

        // 返回用户所需切片
        const sliceToReturn = hasNewStateSlice ? newStateSlice: currentStateRef.current;

        return sliceToReturn;

    }
    // 将修改store的方法{getState, setState, destroy, subscribe}暴露出去,这样用户可以脱离react组件去使用状态管理
    // example: useStore.getState() ....
    Object.assign(useStore, api);

    return useStore;
}

export default create

项目中使用方法:

// 创建store
import create from '../create'

export const useCounterStore = create(set => ({
  count: 0,
  increament: () => set(state => ({ count: state.count + 1 })),
}))

// 项目中使用方式
import React from 'react';
import { useCounterStore } from './store';

const Other = () => {
    const counter = useCounterStore();
    return (
        <div>
            <h1>Other</h1>
            <div>
                <div>{ counter.count }</div>
            </div>
        </div>
    );
}

export default Other;

本期先到这儿,如果觉得还不错,就点赞加关注支持一下~