From f7e002cc7794ca0c187f73dcc5c33bd462211c70 Mon Sep 17 00:00:00 2001 From: arvinxx Date: Tue, 22 Aug 2023 14:11:48 +0800 Subject: [PATCH 1/3] :recycle: fix: refactor with zustand v4.4 equalFn --- .../demos/realtimeCollaboration/store.ts | 6 ++-- src/Awareness/store.ts | 7 +++-- src/ComponentAsset/store/createAssetStore.ts | 6 ++-- .../store/createTestAssetStore.ts | 28 +++++++++++-------- src/DataFill/store/index.ts | 10 +++---- src/IconPicker/store/createStore.ts | 8 ++++-- src/IconPicker/store/store.ts | 12 ++------ src/ProEditor/store/createStore.ts | 8 ++++-- src/SortableList/store/index.ts | 8 ++++-- src/SortableTree/store/index.ts | 8 ++++-- 10 files changed, 60 insertions(+), 41 deletions(-) diff --git a/docs/pro-editor/demos/realtimeCollaboration/store.ts b/docs/pro-editor/demos/realtimeCollaboration/store.ts index caefc94c..91879d69 100644 --- a/docs/pro-editor/demos/realtimeCollaboration/store.ts +++ b/docs/pro-editor/demos/realtimeCollaboration/store.ts @@ -1,8 +1,9 @@ import { yjsMiddleware } from '@ant-design/pro-editor'; +import isEqual from 'fast-deep-equal'; import { Doc } from 'yjs'; import type { StoreApi } from 'zustand'; -import { create } from 'zustand'; import { createContext } from 'zustand-utils'; +import { createWithEqualityFn } from 'zustand/traditional'; interface Store { count: number; @@ -14,7 +15,7 @@ interface Store { export const doc = new Doc(); export const createStore = () => - create( + createWithEqualityFn( yjsMiddleware(doc, 'shared', (set) => ({ count: 0, text: '', @@ -26,6 +27,7 @@ export const createStore = () => })), updateText: (text) => set((state) => ({ ...state, text })), })), + isEqual, ); export const { useStore, Provider } = createContext>(); diff --git a/src/Awareness/store.ts b/src/Awareness/store.ts index 6b513b2c..b73173ed 100644 --- a/src/Awareness/store.ts +++ b/src/Awareness/store.ts @@ -1,12 +1,13 @@ // FIXME:这里理论上不应该使用 faker 的,后续需要重构优化掉 import { faker } from '@faker-js/faker'; +import isEqual from 'fast-deep-equal'; import { useEffect } from 'react'; import type { Position } from 'react-rnd'; import type { Awareness } from 'y-protocols/awareness'; import type { WebrtcProvider } from 'y-webrtc'; import type { StoreApi } from 'zustand'; -import { create } from 'zustand'; import { createContext } from 'zustand-utils'; +import { createWithEqualityFn } from 'zustand/traditional'; import { useAwarenessEvent } from './event'; @@ -33,7 +34,7 @@ interface ProviderStore { } export const createStore = (provider: WebrtcProvider) => { - const useStore = create((set) => { + const useStore = createWithEqualityFn((set) => { return { provider, awareness: provider.awareness, @@ -44,7 +45,7 @@ export const createStore = (provider: WebrtcProvider) => { set({ followUser }); }, }; - }); + }, isEqual); const { awareness } = useStore.getState(); // eslint-disable-next-line react-hooks/rules-of-hooks diff --git a/src/ComponentAsset/store/createAssetStore.ts b/src/ComponentAsset/store/createAssetStore.ts index 0fcff54d..517792ea 100644 --- a/src/ComponentAsset/store/createAssetStore.ts +++ b/src/ComponentAsset/store/createAssetStore.ts @@ -1,7 +1,9 @@ +import isEqual from 'fast-deep-equal'; import { ReactNode } from 'react'; -import { StateCreator, StoreApi, create } from 'zustand'; +import { StateCreator, StoreApi } from 'zustand'; import { UseContextStore, createContext, optionalDevtools } from 'zustand-utils'; import { DevtoolsOptions } from 'zustand/middleware'; +import { createWithEqualityFn } from 'zustand/traditional'; import type { ProEditorInstance } from '@/ProEditor'; @@ -34,7 +36,7 @@ export const createAssetStore = ( const devtools = optionalDevtools(!(options?.devtools === false)); - return create()(devtools(createStore, devtoolsOptions)); + return createWithEqualityFn()(devtools(createStore, devtoolsOptions), isEqual); }; return { Provider, createStore: store, useStoreApi }; diff --git a/src/ComponentAsset/store/createTestAssetStore.ts b/src/ComponentAsset/store/createTestAssetStore.ts index c65bf536..ee180245 100644 --- a/src/ComponentAsset/store/createTestAssetStore.ts +++ b/src/ComponentAsset/store/createTestAssetStore.ts @@ -1,4 +1,5 @@ -import { create } from 'zustand'; +import isEqual from 'fast-deep-equal'; +import { createWithEqualityFn } from 'zustand/traditional'; import { CreateAssetStore } from './createAssetStore'; @@ -7,21 +8,24 @@ import { CreateAssetStore } from './createAssetStore'; * @param createStore */ export const createTestAssetStore = (createStore: CreateAssetStore) => { - const useEditorStore = create<{ + const useEditorStore = createWithEqualityFn<{ config: T; setConfig: (config: T) => void; getConfig: () => T; - }>((set, get) => ({ - config: null, - setConfig: (config) => { - set({ config: { ...get().config, ...config } }); - }, - getConfig: () => { - return get().config; - }, - })); + }>( + (set, get) => ({ + config: null, + setConfig: (config) => { + set({ config: { ...get().config, ...config } }); + }, + getConfig: () => { + return get().config; + }, + }), + isEqual, + ); - const useAssetStore = create(createStore); + const useAssetStore = createWithEqualityFn(createStore, isEqual); const { getConfig, setConfig } = useEditorStore.getState(); const init = () => { diff --git a/src/DataFill/store/index.ts b/src/DataFill/store/index.ts index 42df9d1e..221afa41 100644 --- a/src/DataFill/store/index.ts +++ b/src/DataFill/store/index.ts @@ -1,8 +1,9 @@ +import isEqual from 'fast-deep-equal'; import { fromEvent, Subject } from 'rxjs'; import { switchMap, tap } from 'rxjs/operators'; import type { StoreApi } from 'zustand'; -import { create } from 'zustand'; import { createContext } from 'zustand-utils'; +import { createWithEqualityFn } from 'zustand/traditional'; import DataFiller from '../DataFiller'; import type { ShowDemoDataPayload } from '../types'; @@ -21,7 +22,7 @@ export interface DataFillAction { export type DataFillStore = DataFillState & DataFillAction; const createStore = () => - create((set, get) => { + createWithEqualityFn((set, get) => { // 处理鼠标交互处理 const mouseMoveIn$ = new Subject(); @@ -65,9 +66,8 @@ const createStore = () => }, handleShowDemoData: (payload) => mouseMoveIn$.next(payload), }; - }); + }, isEqual); -const { Provider, useStore, useStoreApi } = - createContext>(); +const { Provider, useStore, useStoreApi } = createContext>(); export { createStore, Provider, useStore, useStoreApi }; diff --git a/src/IconPicker/store/createStore.ts b/src/IconPicker/store/createStore.ts index 0ebedca1..9de2573e 100644 --- a/src/IconPicker/store/createStore.ts +++ b/src/IconPicker/store/createStore.ts @@ -1,11 +1,15 @@ +import isEqual from 'fast-deep-equal'; import type { StoreApi } from 'zustand'; -import { create } from 'zustand'; import { createContext, optionalDevtools } from 'zustand-utils'; +import { createWithEqualityFn } from 'zustand/traditional'; import type { Store } from './store'; import vanillaStore from './store'; export const createStore = (showDevtools?: boolean) => - create()(optionalDevtools(showDevtools)(vanillaStore, { name: 'IconPicker' })); + createWithEqualityFn()( + optionalDevtools(showDevtools)(vanillaStore, { name: 'IconPicker' }), + isEqual, + ); export const { Provider, useStore, useStoreApi } = createContext>(); diff --git a/src/IconPicker/store/store.ts b/src/IconPicker/store/store.ts index 51dbfc32..f6c103d7 100644 --- a/src/IconPicker/store/store.ts +++ b/src/IconPicker/store/store.ts @@ -1,11 +1,8 @@ +import isEqual from 'fast-deep-equal'; import produce from 'immer'; -import isEqual from 'lodash.isequal'; import type { StateCreator } from 'zustand/vanilla'; import type { ExternalScripts, IconUnit } from '../types'; -import { - extractListFormIconfontJS, - findNeighborIndex, -} from '../utils/iconfont'; +import { extractListFormIconfontJS, findNeighborIndex } from '../utils/iconfont'; import type { State } from './initialState'; import { initialState } from './initialState'; @@ -23,10 +20,7 @@ export interface Action { export type Store = State & Action; -const vanillaStore: StateCreator = ( - set, - get, -) => ({ +const vanillaStore: StateCreator = (set, get) => ({ ...initialState, resetIcon: () => { diff --git a/src/ProEditor/store/createStore.ts b/src/ProEditor/store/createStore.ts index 9a58df23..67dff9b1 100644 --- a/src/ProEditor/store/createStore.ts +++ b/src/ProEditor/store/createStore.ts @@ -1,7 +1,8 @@ +import isEqual from 'fast-deep-equal'; import type { StateCreator } from 'zustand'; -import { create } from 'zustand'; import { optionalDevtools } from 'zustand-utils'; import { DevtoolsOptions } from 'zustand/middleware'; +import { createWithEqualityFn } from 'zustand/traditional'; import { AwarenessSlice, AwarenessSliceState, awarenessSlice } from './slices/awareness'; import { CanvasSlice, PublicCanvasState, canvasSlice } from './slices/canvas'; @@ -42,5 +43,8 @@ export const createStore = (options: boolean | DevtoolsOptions = false) => { const devtoolOptions = options === false ? undefined : options === true ? { name: 'ProEditorStore' } : options; - return create()(devtools(vanillaStore, devtoolOptions)); + return createWithEqualityFn()( + devtools(vanillaStore, devtoolOptions), + isEqual, + ); }; diff --git a/src/SortableList/store/index.ts b/src/SortableList/store/index.ts index 62f627aa..feef941c 100644 --- a/src/SortableList/store/index.ts +++ b/src/SortableList/store/index.ts @@ -1,12 +1,16 @@ +import isEqual from 'fast-deep-equal'; import type { StoreApi } from 'zustand'; -import { create } from 'zustand'; import { createContext, optionalDevtools } from 'zustand-utils'; +import { createWithEqualityFn } from 'zustand/traditional'; import type { Store } from './store'; import vanillaStore from './store'; const createStore = (showDevTools: boolean) => - create(optionalDevtools(showDevTools)(vanillaStore, { name: 'SortableList' })); + createWithEqualityFn( + optionalDevtools(showDevTools)(vanillaStore, { name: 'SortableList' }), + isEqual, + ); const { useStore, useStoreApi, Provider } = createContext>(); diff --git a/src/SortableTree/store/index.ts b/src/SortableTree/store/index.ts index 330f8d7c..3cde4555 100644 --- a/src/SortableTree/store/index.ts +++ b/src/SortableTree/store/index.ts @@ -1,12 +1,16 @@ +import isEqual from 'fast-deep-equal'; import type { StoreApi } from 'zustand'; -import { create } from 'zustand'; import { createContext, optionalDevtools } from 'zustand-utils'; +import { createWithEqualityFn } from 'zustand/traditional'; import type { InternalSortableTreeStore } from './store'; import vanillaStore from './store'; const createStore = (showDevTools: boolean) => - create(optionalDevtools(showDevTools)(vanillaStore, { name: 'SortableTree' })); + createWithEqualityFn( + optionalDevtools(showDevTools)(vanillaStore, { name: 'SortableTree' }), + isEqual, + ); const { useStore, useStoreApi, Provider } = createContext>(); From bd6e70a28b7907579ac442023a3f7c7e42044f2d Mon Sep 17 00:00:00 2001 From: arvinxx Date: Tue, 22 Aug 2023 14:12:27 +0800 Subject: [PATCH 2/3] :arrow_up: fix: upgrade zustand version --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 45b85bad..425df3fb 100644 --- a/package.json +++ b/package.json @@ -108,9 +108,9 @@ "umi-request": "^1.4.0", "use-merge-value": "^1.2.0", "yjs": "^13.6.7", - "zustand": "^4.4.0", + "zustand": "^4.4.1", "zustand-middleware-yjs": "^1.3.1", - "zustand-utils": "^1.3.0" + "zustand-utils": "^1.3.1" }, "devDependencies": { "@emotion/jest": "^11.11.0", From b22c3a953adfda4eaa45d165c7912b1129852e21 Mon Sep 17 00:00:00 2001 From: arvinxx Date: Tue, 22 Aug 2023 14:12:33 +0800 Subject: [PATCH 3/3] :white_check_mark: test: update useStore mock --- __mocks__/{zustand.ts => zustand/traditional.ts} | 12 +++++------- src/ProEditor/store/createStore.test.ts | 2 +- 2 files changed, 6 insertions(+), 8 deletions(-) rename __mocks__/{zustand.ts => zustand/traditional.ts} (62%) diff --git a/__mocks__/zustand.ts b/__mocks__/zustand/traditional.ts similarity index 62% rename from __mocks__/zustand.ts rename to __mocks__/zustand/traditional.ts index 32cf6429..0e3c9a16 100644 --- a/__mocks__/zustand.ts +++ b/__mocks__/zustand/traditional.ts @@ -1,13 +1,13 @@ import { act } from 'react-dom/test-utils'; import { beforeEach } from 'vitest'; -import { create as actualCreate } from 'zustand'; +import { createWithEqualityFn as actualCreate } from 'zustand/traditional'; // a variable to hold reset functions for all stores declared in the app -const storeResetFns = new Set(); +const storeResetFns = new Set<() => void>(); // when creating a store, we get its initial state, create a reset function and add it in the set const createImpl = (createState) => { - const store = actualCreate(createState); + const store = actualCreate(createState, Object.is); const initialState = store.getState(); storeResetFns.add(() => store.setState(initialState, true)); return store; @@ -22,8 +22,6 @@ beforeEach(() => { ); }); -export const create = (f) => (f === undefined ? createImpl : createImpl(f)); +export const createWithEqualityFn = (f) => (f === undefined ? createImpl : createImpl(f)); -export default create; - -export { useStore } from 'zustand'; +export { useStoreWithEqualityFn as useStore } from 'zustand/traditional'; diff --git a/src/ProEditor/store/createStore.test.ts b/src/ProEditor/store/createStore.test.ts index 7698a946..08e72317 100644 --- a/src/ProEditor/store/createStore.test.ts +++ b/src/ProEditor/store/createStore.test.ts @@ -4,7 +4,7 @@ import { useEffect, useState } from 'react'; import type { CanvasInteractRule, InteractStatus } from '../../InteractContainer'; import { createStore } from './createStore'; -vi.mock('zustand'); +vi.mock('zustand/traditional'); const useStore = createStore();