From e333060d66d7c082bae9d73f4c31e658c1c8e4e5 Mon Sep 17 00:00:00 2001 From: lvisei Date: Mon, 23 Dec 2024 14:11:36 +0800 Subject: [PATCH] feat: add event for data communicate (#62) * feat: add context for data communication * chore: changeset * wip: event * feat: event for data communicate * chore: add changeset * chore: update changesets --- .changeset/slow-drinks-thank.md | 5 ++ package.json | 1 + src/GPTVis/Lite.tsx | 52 ++++++++++++++++---- src/GPTVis/demos/context-provider.tsx | 68 +++++++++++++++++++++++++++ src/GPTVis/demos/event.tsx | 53 +++++++++++++++++++++ src/GPTVis/hooks/useContext.ts | 17 +++++++ src/GPTVis/hooks/useEvent.ts | 7 +++ src/GPTVis/index.md | 8 ++++ src/index.ts | 2 +- 9 files changed, 202 insertions(+), 11 deletions(-) create mode 100644 .changeset/slow-drinks-thank.md create mode 100644 src/GPTVis/demos/context-provider.tsx create mode 100644 src/GPTVis/demos/event.tsx create mode 100644 src/GPTVis/hooks/useContext.ts create mode 100644 src/GPTVis/hooks/useEvent.ts diff --git a/.changeset/slow-drinks-thank.md b/.changeset/slow-drinks-thank.md new file mode 100644 index 0000000..cc1e979 --- /dev/null +++ b/.changeset/slow-drinks-thank.md @@ -0,0 +1,5 @@ +--- +'@antv/gpt-vis': minor +--- + +feat: support event for data communicate diff --git a/package.json b/package.json index be7ced7..85e3f7a 100644 --- a/package.json +++ b/package.json @@ -56,6 +56,7 @@ "@ant-design/graphs": "^2.0.2", "@ant-design/icons": "^5.5.2", "@ant-design/plots": "^2.3.2", + "@antv/event-emitter": "^0.1.3", "@antv/l7": "^2.22.3", "@antv/larkmap": "^1.5.1", "@babel/runtime": "^7.26.0", diff --git a/src/GPTVis/Lite.tsx b/src/GPTVis/Lite.tsx index 6541626..cffab16 100644 --- a/src/GPTVis/Lite.tsx +++ b/src/GPTVis/Lite.tsx @@ -1,16 +1,26 @@ -import React, { memo } from 'react'; +import EventEmitter from '@antv/event-emitter'; +import React, { memo, useEffect, useMemo } from 'react'; import type { Options } from 'react-markdown'; import Markdown from 'react-markdown'; import rehypeRaw from 'rehype-raw'; import remarkGfm from 'remark-gfm'; +import { GPTVisContext } from './hooks/useContext'; +import { useEventPublish } from './hooks/useEvent'; export interface GPTVisLiteProps extends Options { - /** 自定义 markdown components样式 */ + /** + * 自定义 markdown components + */ components?: | Options['components'] | { [key: string]: (props: any) => React.ReactNode; }; + /** + * 🧪 订阅组件事件,实验性属性 + * 用于子组件与容器组件通信 + */ + eventSubs?: Record void>; } const GPTVisLite: React.FC = ({ @@ -18,18 +28,40 @@ const GPTVisLite: React.FC = ({ components, rehypePlugins, remarkPlugins, + eventSubs, ...rest }) => { + const eventBus = useMemo(() => new EventEmitter(), []); + const contextValue = useMemo(() => ({ eventBus }), [eventBus]); + + useEffect(() => { + if (eventSubs) { + const events = Object.keys(eventSubs); + for (const eventName of events) { + eventBus.on(eventName, eventSubs[eventName]); + } + return () => { + for (const eventName of events) { + eventBus.off(eventName, eventSubs[eventName]); + } + }; + } + }, [eventBus, eventSubs]); + return ( - - {children} - + + + {children} + + ); }; +export { useEventPublish }; + export default memo(GPTVisLite); diff --git a/src/GPTVis/demos/context-provider.tsx b/src/GPTVis/demos/context-provider.tsx new file mode 100644 index 0000000..d4f0255 --- /dev/null +++ b/src/GPTVis/demos/context-provider.tsx @@ -0,0 +1,68 @@ +import type { CodeBlockComponent } from '@antv/gpt-vis'; +import { GPTVisLite, withChartCode } from '@antv/gpt-vis'; +import React, { useCallback, useMemo, useState } from 'react'; + +export const MyContext = React.createContext(null as any); + +export function useMyContext() { + const context = React.useContext(MyContext); + if (context === undefined || Object.keys(context).length === 0) { + throw new Error(`useMyContext must be used within a MyContext.Provider`); + } + + return context; +} + +/** + * 自定义代码块渲染器 + */ +const MyUIRenderer: CodeBlockComponent = ({ children }) => { + const context = useMyContext(); + console.log('context: ', context); + return ( +
+

{children}

+ +
+ ); +}; +const customRenderers = { 'my-ui': MyUIRenderer }; +const components = { + code: withChartCode({ + languageRenderers: customRenderers, // register custom block renderer + components: {}, + }), +}; + +const content = ` +\`\`\`my-ui +my ui data ... +\`\`\` +`; + +export default () => { + const [count, setCount] = useState(0); + const handleClick = useCallback(() => { + console.log('handleClick'); + setCount((pre) => pre + 1); + // do something + }, []); + const context = useMemo(() => ({ count: count, onClick: handleClick }), [count]); + + return ( + <> +

count: {count}

+ +
+ {/* other component ... */} +
+ {/* other component ... */} + {content} +
+
+
+ + ); +}; diff --git a/src/GPTVis/demos/event.tsx b/src/GPTVis/demos/event.tsx new file mode 100644 index 0000000..421b93b --- /dev/null +++ b/src/GPTVis/demos/event.tsx @@ -0,0 +1,53 @@ +import type { CodeBlockComponent } from '@antv/gpt-vis'; +import { GPTVisLite, useEventPublish, withChartCode } from '@antv/gpt-vis'; +import React, { useState } from 'react'; + +/** + * 自定义代码块渲染器 + */ +const MyUIRenderer: CodeBlockComponent = ({ children }) => { + const eventPublish = useEventPublish(); + return ( +
+

{children}

+ +
+ ); +}; +const customRenderers = { 'my-ui': MyUIRenderer }; +const components = { + code: withChartCode({ + languageRenderers: customRenderers, + components: {}, + }), +}; + +const content = ` +\`\`\`my-ui +my ui data ... +\`\`\` +`; +export default () => { + const [count, setCount] = useState(0); + const onClick = (data: any) => { + console.log('data: ', data); + setCount((pre) => pre + 1); + // do something + }; + + return ( + <> +

count: {count}

+ + {content} + + + ); +}; diff --git a/src/GPTVis/hooks/useContext.ts b/src/GPTVis/hooks/useContext.ts new file mode 100644 index 0000000..1f15281 --- /dev/null +++ b/src/GPTVis/hooks/useContext.ts @@ -0,0 +1,17 @@ +import type EventEmitter from '@antv/event-emitter'; +import React from 'react'; + +type GPTVisContextValue = { + eventBus: EventEmitter; +}; + +export const GPTVisContext = React.createContext(null as any); + +export function useGPTVisContext() { + const context = React.useContext(GPTVisContext); + if (context === undefined || Object.keys(context).length === 0) { + throw new Error(`useGPTVisContext must be used within a GPTVisContext.Provider`); + } + + return context as T; +} diff --git a/src/GPTVis/hooks/useEvent.ts b/src/GPTVis/hooks/useEvent.ts new file mode 100644 index 0000000..0bd956c --- /dev/null +++ b/src/GPTVis/hooks/useEvent.ts @@ -0,0 +1,7 @@ +import { useGPTVisContext } from './useContext'; + +export const useEventPublish = () => { + const { eventBus } = useGPTVisContext(); + + return eventBus.emit.bind(eventBus); +}; diff --git a/src/GPTVis/index.md b/src/GPTVis/index.md index 40e5c81..1c50c45 100644 --- a/src/GPTVis/index.md +++ b/src/GPTVis/index.md @@ -4,6 +4,7 @@ group: order: 10 title: 其他 toc: content +demo: { cols: 2 } --- # GPTVis 协议渲染器 @@ -26,6 +27,13 @@ GPTVis 协议的 Markdown 渲染器,基于 Markdown 语法扩展 `vis-chart` +## 容器组件通信 + +通过发布订阅组件事件与 Context 传递数据,来用于子组件与容器组件通信。 + +订阅组件事件 +Context 传递数据 + ## API 继承 [react-markdown](https://github.com/remarkjs/react-markdown#options) 组件全部属性。 diff --git a/src/index.ts b/src/index.ts index 5fbf487..36366a4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -9,6 +9,6 @@ export { withChartCode, withDefaultChartCode } from './ChartCodeRender'; export type { CodeBlockComponent, WithChartCodeOptions } from './ChartCodeRender/type'; export { default as ConfigProvider, type ConfigProviderProps } from './ConfigProvider'; export { default as GPTVis, type GPTVisProps } from './GPTVis'; -export { default as GPTVisLite, type GPTVisLiteProps } from './GPTVis/Lite'; +export { default as GPTVisLite, useEventPublish, type GPTVisLiteProps } from './GPTVis/Lite'; export { default as version } from './version';