diff --git a/src/ComponentAsset/ComponentAsset.test.tsx b/src/ComponentAsset/ComponentAsset.test.tsx index e3971466..6e5163d8 100644 --- a/src/ComponentAsset/ComponentAsset.test.tsx +++ b/src/ComponentAsset/ComponentAsset.test.tsx @@ -3,12 +3,7 @@ import { act, fireEvent, render, screen } from '@testing-library/react'; import { ComponentAsset } from '../ComponentAsset'; import type { ButtonConfig } from './demoAssets'; -import { - contentModel, - demoAssets, - dynamicContentModel, - Provider, -} from './demoAssets'; +import { contentModel, demoAssets, dynamicContentModel } from './demoAssets'; describe('ComponentAsset', () => { describe('状态值', () => { @@ -111,14 +106,14 @@ describe('ComponentAsset', () => { test('同一实例重复渲染多次', async () => { const asset = new ComponentAsset(demoAssets); const comp = render( - asset.componentStore}> + asset.componentStore}> - , + , ); render( - asset.componentStore}> + asset.componentStore}> - , + , ); const components = await comp.queryAllByTestId('component'); @@ -132,18 +127,18 @@ describe('ComponentAsset', () => { render( <> - asset1.componentStore}> + asset1.componentStore}> - - asset2.componentStore}> + + asset2.componentStore}> - + , ); @@ -163,15 +158,16 @@ describe('ComponentAsset', () => { test('渲染时同一实例的组件时共享相同状态', async () => { const asset = new ComponentAsset(demoAssets); + const comp = render( - asset.componentStore}> + asset.componentStore}> - , + , ); const panel = render( - asset.componentStore}> + asset.componentStore}> - , + , ); const component = await comp.findByTestId('component'); diff --git a/src/ComponentAsset/ComponentAsset.tsx b/src/ComponentAsset/ComponentAsset.tsx index c021d2f4..cddca451 100644 --- a/src/ComponentAsset/ComponentAsset.tsx +++ b/src/ComponentAsset/ComponentAsset.tsx @@ -1,12 +1,14 @@ /*eslint no-invalid-this: "error"*/ import { getDefaultValueFromSchema } from '@c2d2c/utils'; -import { FC, ReactNode } from 'react'; +import { FC, PropsWithChildren, ReactNode } from 'react'; import type { UseBoundStore } from 'zustand/react'; import type { EditorMode } from '../ProEditor'; import type { AssetModels, CanvasRule, CodeEmitter, ComponentAssetParams } from './types'; import { DataProvider, EmitterEnv } from './types'; +import { createAssetStore } from './store'; + export class ComponentAsset { /** * 组件 ID @@ -36,7 +38,9 @@ export class ComponentAsset { /** * 组件数据提供者 */ - DataProvider: DataProvider; + DataProvider?: FC; + + AssetProvider: DataProvider; ErrorBoundary: FC<{ children: ReactNode }> = ({ children }) => <>{children}; @@ -62,12 +66,15 @@ export class ComponentAsset { this.defaultConfig = params.defaultConfig; } - // 初始化 store - this.componentStore = params.createStore({ - showDevtools: params.showDevtools, - defaultConfig: params.defaultConfig, + const { createStore, Provider } = createAssetStore(params.createStore, { + initialState: params.defaultConfig, + ...params.storeOptions, }); + // 初始化 store + this.componentStore = createStore(); + this.AssetProvider = Provider; + // 交互规则 this.rules = params.ui.rules; diff --git a/src/ComponentAsset/demoAssets/index.ts b/src/ComponentAsset/demoAssets/index.ts index 79f87ee2..d76bbd48 100644 --- a/src/ComponentAsset/demoAssets/index.ts +++ b/src/ComponentAsset/demoAssets/index.ts @@ -3,9 +3,9 @@ import type { ComponentAssetParams } from '../types'; import { TestComponent, TestPanel } from './Component'; import type { ButtonConfig } from './store'; -import { createStore, Provider } from './store'; +import { createStore } from './store'; -import { contentModel } from './assets'; +import { contentModel } from './models'; export const demoAssets: ComponentAssetParams = { id: 'Button', @@ -15,7 +15,6 @@ export const demoAssets: ComponentAssetParams = { Component: TestComponent, ConfigPanel: TestPanel, rules: [], - DataProvider: Provider, }, models: [contentModel], @@ -23,5 +22,5 @@ export const demoAssets: ComponentAssetParams = { codeEmitter: () => '', }; -export * from './assets'; +export * from './models'; export * from './store'; diff --git a/src/ComponentAsset/demoAssets/assets.ts b/src/ComponentAsset/demoAssets/models.ts similarity index 100% rename from src/ComponentAsset/demoAssets/assets.ts rename to src/ComponentAsset/demoAssets/models.ts diff --git a/src/ComponentAsset/demoAssets/store.ts b/src/ComponentAsset/demoAssets/store.ts index 15edb10e..098ad514 100644 --- a/src/ComponentAsset/demoAssets/store.ts +++ b/src/ComponentAsset/demoAssets/store.ts @@ -1,6 +1,4 @@ -import type { StoreApi } from 'zustand'; -import { create } from 'zustand'; -import { createContext } from 'zustand-utils'; +import { CreateAssetStore, createUseAssetStore } from '@ant-design/pro-editor'; export interface ButtonConfig { content: { @@ -12,19 +10,18 @@ export interface ButtonStore extends ButtonConfig { setText: (text: string) => void; } -const createStore = () => - create((set, get) => ({ - content: { - text: '123', - icon: '', - }, +const createStore: CreateAssetStore = (set, get) => ({ + content: { + text: '123', + icon: '', + }, - setText: (text) => { - const { content } = get(); - set({ content: { ...content, text } }); - }, - })); + setText: (text) => { + const { content } = get(); + set({ content: { ...content, text } }); + }, +}); -const { Provider, useStore } = createContext>(); +const { useStore } = createUseAssetStore(); -export { Provider, createStore, useStore }; +export { createStore, useStore }; diff --git a/src/ComponentAsset/index.ts b/src/ComponentAsset/index.ts index a5079c20..01f93faa 100644 --- a/src/ComponentAsset/index.ts +++ b/src/ComponentAsset/index.ts @@ -1,2 +1,3 @@ export * from './ComponentAsset'; +export { createUseAssetStore, type CreateAssetStore } from './store'; export * from './types'; diff --git a/src/ComponentAsset/store/index.ts b/src/ComponentAsset/store/index.ts new file mode 100644 index 00000000..cee90fd6 --- /dev/null +++ b/src/ComponentAsset/store/index.ts @@ -0,0 +1,49 @@ +import { StateCreator, StoreApi, create } from 'zustand'; +import { UseContextStore, createContext, optionalDevtools } from 'zustand-utils'; +import { DevtoolsOptions } from 'zustand/middleware'; + +import { PublicProEditorStore } from '@/ProEditor/store'; + +export interface AssetStoreOptions { + initialState: T; + devtools?: false | DevtoolsOptions; +} + +export type CreateAssetStore = StateCreator< + T & PublicProEditorStore, + [['zustand/devtools', never]], + [], + T +>; + +const { Provider, useStore, useStoreApi } = createContext>(); + +export const createAssetStore = ( + createStore: StateCreator, + options?: AssetStoreOptions, +) => { + const store = () => { + const devtoolsOptions = options?.devtools; + const devtools = optionalDevtools(!!devtoolsOptions); + + const initialState = options?.initialState || {}; + + return create()( + devtools( + (...params) => ({ ...createStore(...params), ...initialState }), + !devtoolsOptions ? {} : devtoolsOptions, + ), + ); + }; + + return { Provider, createStore: store }; +}; + +type WithoutCallSignature = { + [K in keyof T]: T[K]; +}; + +export const createUseAssetStore = (): { + useStore: UseContextStore>; + useStoreApi: () => WithoutCallSignature>; +} => ({ useStore, useStoreApi }); diff --git a/src/ComponentAsset/types/asset.ts b/src/ComponentAsset/types/asset.ts index 56444aee..e28c4487 100644 --- a/src/ComponentAsset/types/asset.ts +++ b/src/ComponentAsset/types/asset.ts @@ -1,10 +1,11 @@ -import type { UseBoundStore } from 'zustand/react'; - import { FC, ReactNode } from 'react'; -import type { CanvasInteractRule } from '../../InteractContainer'; + +import type { CreateAssetStore } from '@/ComponentAsset'; +import { AssetStoreOptions } from '@/ComponentAsset/store'; +import type { CanvasInteractRule } from '@/InteractContainer'; import type { CodeEmitter } from './code'; import type { AssetModels } from './model'; -import { DataProvider } from './render'; +import type { DataProvider } from './render'; export type CanvasRule = CanvasInteractRule; @@ -60,12 +61,10 @@ export interface ComponentAssetParams { * 初始化的默认值 */ defaultConfig?: Partial; - /** - * 是否显示 devtools - */ - showDevtools?: boolean; - createStore: (params?: any) => UseBoundStore; + createStore: CreateAssetStore; + + storeOptions?: AssetStoreOptions; /** * 组件的业务数据模型 * @param config diff --git a/src/ProEditor/container/App.tsx b/src/ProEditor/container/App.tsx index a5a9dc18..f37a8256 100644 --- a/src/ProEditor/container/App.tsx +++ b/src/ProEditor/container/App.tsx @@ -50,6 +50,7 @@ export const ProEditor: FC = memo((props) => { // 第一次渲染的时候 componentAsset 是不存在的,为避免面板模块报错,返回空状态组件 if (!componentAsset) return ; + const AssetProvider = componentAsset.AssetProvider; const DataProvider = componentAsset.DataProvider; const children = ( @@ -68,11 +69,9 @@ export const ProEditor: FC = memo((props) => { return ( - {!DataProvider ? ( - children - ) : ( - componentAsset.componentStore}>{children} - )} + componentAsset.componentStore}> + {!DataProvider ? children : {children}} + ); });