From 373e424e6ad5d56ce478f5952b9b698997c889f9 Mon Sep 17 00:00:00 2001 From: moonrailgun Date: Sat, 17 Dec 2022 00:25:36 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0KeepAliveOverlay?= =?UTF-8?q?=E7=BB=84=E4=BB=B6=E7=94=A8=E4=BA=8E=E7=BC=93=E5=AD=98iframe?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/group/GroupWebPanelRender.tsx | 4 +- .../KeepAliveOverlay/KeepAliveOverlayHost.tsx | 33 +++++++ .../src/components/KeepAliveOverlay/README.md | 3 + .../src/components/KeepAliveOverlay/index.ts | 2 + .../src/components/KeepAliveOverlay/store.ts | 79 +++++++++++++++++ .../KeepAliveOverlay/withKeepAliveOverlay.tsx | 86 +++++++++++++++++++ client/web/src/components/Webview.tsx | 13 +-- client/web/src/routes/Main/Provider.tsx | 5 +- 8 files changed, 216 insertions(+), 9 deletions(-) create mode 100644 client/web/src/components/KeepAliveOverlay/KeepAliveOverlayHost.tsx create mode 100644 client/web/src/components/KeepAliveOverlay/README.md create mode 100644 client/web/src/components/KeepAliveOverlay/index.ts create mode 100644 client/web/src/components/KeepAliveOverlay/store.ts create mode 100644 client/web/src/components/KeepAliveOverlay/withKeepAliveOverlay.tsx diff --git a/client/web/plugins/com.msgbyte.webview/src/group/GroupWebPanelRender.tsx b/client/web/plugins/com.msgbyte.webview/src/group/GroupWebPanelRender.tsx index 5336cc3e..f7e5abae 100644 --- a/client/web/plugins/com.msgbyte.webview/src/group/GroupWebPanelRender.tsx +++ b/client/web/plugins/com.msgbyte.webview/src/group/GroupWebPanelRender.tsx @@ -11,9 +11,7 @@ const GroupWebPanelRender: React.FC<{ panelInfo: any }> = (props) => { const url = panelInfo?.meta?.url; - return ( - - ); + return ; }; GroupWebPanelRender.displayName = 'GroupWebPanelRender'; diff --git a/client/web/src/components/KeepAliveOverlay/KeepAliveOverlayHost.tsx b/client/web/src/components/KeepAliveOverlay/KeepAliveOverlayHost.tsx new file mode 100644 index 00000000..7573bcbc --- /dev/null +++ b/client/web/src/components/KeepAliveOverlay/KeepAliveOverlayHost.tsx @@ -0,0 +1,33 @@ +import React, { FC } from 'react'; +import { useKeepAliveStore } from './store'; + +export const KeepAliveOverlayHost: FC = (props) => { + const cachedComponents = useKeepAliveStore((state) => state.cachedComponents); + + return ( + <> + {props.children} + +
+ {Object.entries(cachedComponents).map(([cacheId, cacheItem]) => { + const { visible, element, rect } = cacheItem; + + return ( +
+ {element} +
+ ); + })} +
+ + ); +}; +KeepAliveOverlayHost.displayName = 'KeepAliveOverlayHost'; diff --git a/client/web/src/components/KeepAliveOverlay/README.md b/client/web/src/components/KeepAliveOverlay/README.md new file mode 100644 index 00000000..a9ec9504 --- /dev/null +++ b/client/web/src/components/KeepAliveOverlay/README.md @@ -0,0 +1,3 @@ +组件状态缓存, 使用覆盖层的方式来保持存活(类似小程序叠加原生程序的感觉) + +支持`iframe`的渲染缓存. diff --git a/client/web/src/components/KeepAliveOverlay/index.ts b/client/web/src/components/KeepAliveOverlay/index.ts new file mode 100644 index 00000000..d753ddba --- /dev/null +++ b/client/web/src/components/KeepAliveOverlay/index.ts @@ -0,0 +1,2 @@ +export { KeepAliveOverlayHost } from './KeepAliveOverlayHost'; +export { withKeepAliveOverlay } from './withKeepAliveOverlay'; diff --git a/client/web/src/components/KeepAliveOverlay/store.ts b/client/web/src/components/KeepAliveOverlay/store.ts new file mode 100644 index 00000000..9b389b6e --- /dev/null +++ b/client/web/src/components/KeepAliveOverlay/store.ts @@ -0,0 +1,79 @@ +import type React from 'react'; +import create from 'zustand'; +import { immer } from 'zustand/middleware/immer'; + +interface ElementDisplayRect { + left: number; + top: number; + width: number; + height: number; +} + +interface KeepAliveState { + cachedComponents: Record< + string, + { + visible: boolean; + element: React.ReactElement; + rect: ElementDisplayRect; + } + >; + mount: (cacheId: string, element: React.ReactElement) => void; + show: (cacheId: string) => void; + hide: (cacheId: string) => void; + updateRect: (cacheId: string, rect: ElementDisplayRect) => void; +} + +export const useKeepAliveStore = create()( + immer((set) => ({ + cachedComponents: {}, + mount: (cacheId, element) => { + set((state) => { + const cachedComponents = state.cachedComponents; + if (cachedComponents[cacheId]) { + // 已经挂载过 + state.cachedComponents[cacheId].visible = true; + return; + } + + cachedComponents[cacheId] = { + visible: true, + element, + rect: { + left: 0, + top: 0, + width: 0, + height: 0, + }, + }; + }); + }, + show: (cacheId) => { + set((state) => { + if (!state.cachedComponents[cacheId]) { + return; + } + + state.cachedComponents[cacheId].visible = true; + }); + }, + hide: (cacheId) => { + set((state) => { + if (!state.cachedComponents[cacheId]) { + return; + } + + state.cachedComponents[cacheId].visible = false; + }); + }, + updateRect: (cacheId: string, rect: ElementDisplayRect) => { + set((state) => { + if (!state.cachedComponents[cacheId]) { + return; + } + + state.cachedComponents[cacheId].rect = rect; + }); + }, + })) +); diff --git a/client/web/src/components/KeepAliveOverlay/withKeepAliveOverlay.tsx b/client/web/src/components/KeepAliveOverlay/withKeepAliveOverlay.tsx new file mode 100644 index 00000000..27c341e5 --- /dev/null +++ b/client/web/src/components/KeepAliveOverlay/withKeepAliveOverlay.tsx @@ -0,0 +1,86 @@ +import React, { useEffect, useMemo, useRef } from 'react'; +import { useKeepAliveStore } from './store'; +import _omit from 'lodash/omit'; + +/** + * 样式相关配置 + */ +interface StyleProps { + className?: string; + style?: React.CSSProperties; +} + +/** + * 注意: 样式相关的字段(className, style)会被单独抽出来, 会应用于占位组件,不会透传到渲染组件中 + * 如果实际渲染组件需要样式自定义的话需要另外换个名字 + */ +export function withKeepAliveOverlay< + P extends StyleProps = StyleProps, + OP extends Omit = Omit +>( + OriginComponent: React.ComponentType, + config: { cacheId: string | ((props: OP) => string) } +) { + // eslint-disable-next-line react/display-name + return (props: P) => { + const containerRef = useRef(null); + const originProps = _omit(props, ['className', 'style']) as OP; + const { mount, hide, updateRect } = useKeepAliveStore(); + const cacheId = useMemo(() => { + if (typeof config.cacheId === 'function') { + return config.cacheId(originProps); + } + + return config.cacheId; + }, []); + + useEffect(() => { + mount(cacheId, ); + + return () => { + hide(cacheId); + }; + }, []); + + useEffect(() => { + if (!containerRef.current) { + return; + } + + const resizeObserver = new ResizeObserver((entries) => { + entries.forEach((entry) => { + const { target } = entry; + if (!target.parentElement) { + return; + } + + const rect = target.getBoundingClientRect(); + + updateRect(cacheId, { + left: rect.left, + top: rect.top, + width: rect.width, + height: rect.height, + }); + }); + }); + + resizeObserver.observe(containerRef.current); + + return () => { + if (containerRef.current) { + resizeObserver.unobserve(containerRef.current); + } + }; + }, []); + + return ( +
+ ); + }; +} diff --git a/client/web/src/components/Webview.tsx b/client/web/src/components/Webview.tsx index 002e8a02..bebc761b 100644 --- a/client/web/src/components/Webview.tsx +++ b/client/web/src/components/Webview.tsx @@ -1,17 +1,20 @@ import React from 'react'; +import { withKeepAliveOverlay } from './KeepAliveOverlay'; interface WebviewProps { - url: string; className?: string; style?: React.CSSProperties; + url: string; } /** * 网页渲染容器 */ -export const Webview: React.FC = React.memo((props) => { - return ( -