/** * This file is part of the NocoBase (R) project. * Copyright (c) 2020-2024 NocoBase Co., Ltd. * Authors: NocoBase Team. * * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. * For more information, please refer to: https://www.nocobase.com/agreement. */ import { observer as originalObserver, IObserverOptions, ReactFC } from '@formily/reactive-react'; import React, { useMemo, useEffect, useRef } from 'react'; import { useFlowContext } from '../FlowContextProvider'; import { reaction } from '@formily/reactive'; import { FlowEngineContext } from '..'; type ObserverComponentProps = Options extends { forwardRef: true; } ? P & { ref?: 'ref' extends keyof P ? P['ref'] : React.RefAttributes; } : React.PropsWithoutRef

; export const observer = ( Component: ReactFC

, options?: Options, ): React.MemoExoticComponent>> => { const ComponentWithDefaultScheduler = React.memo((props: any) => { const ctx = useFlowContext(); const ctxRef = useRef(ctx); ctxRef.current = ctx; // 保存延迟更新的监听器,避免重复创建监听。 const pendingDisposerRef = useRef<(() => void) | null>(null); // 保存延迟创建监听器的定时器,避免组件卸载后仍继续调度。 const pendingTimerRef = useRef | null>(null); /** * 清理挂起的可见性监听器。 * * @example * ```typescript * clearPendingDisposer(); * ``` */ const clearPendingDisposer = () => { if (pendingDisposerRef.current) { pendingDisposerRef.current(); pendingDisposerRef.current = null; } }; /** * 清理挂起的定时器。 * * @example * ```typescript * clearPendingTimer(); * ``` */ const clearPendingTimer = () => { if (pendingTimerRef.current) { clearTimeout(pendingTimerRef.current); pendingTimerRef.current = null; } }; /** * 判断当前页面与标签页是否允许立即更新。 * * @returns 当前上下文是否处于可更新状态。 * @example * ```typescript * if (isContextActive()) { * updater(); * } * ``` */ const isContextActive = () => { const pageActive = getPageActive(ctxRef.current); const tabActive = ctxRef.current?.tabActive?.value; return pageActive !== false && tabActive !== false; }; // 组件卸载时统一清理所有挂起任务,避免异步回调在卸载后继续运行。 useEffect(() => { return () => { if (pendingTimerRef.current) { clearTimeout(pendingTimerRef.current); pendingTimerRef.current = null; } if (pendingDisposerRef.current) { pendingDisposerRef.current(); pendingDisposerRef.current = null; } }; }, []); const ObservedComponent = useMemo( () => originalObserver(Component, { scheduler(updater) { if (!isContextActive()) { if (pendingTimerRef.current || pendingDisposerRef.current) { return; } // 通过异步任务打断同步调度,避免连续触发时形成递归更新。 pendingTimerRef.current = setTimeout(() => { pendingTimerRef.current = null; if (pendingDisposerRef.current) { return; } if (isContextActive()) { updater(); return; } // 只监听组合后的“是否可更新”状态,条件恢复后执行一次并立即销毁。 pendingDisposerRef.current = reaction( () => isContextActive(), (active) => { if (!active) { return; } clearPendingDisposer(); updater(); }, { name: 'FlowObserverPendingUpdate', }, ); }); return; } clearPendingTimer(); clearPendingDisposer(); updater(); }, ...options, }), [], ); return ; }); ComponentWithDefaultScheduler.displayName = `ComponentWithDefaultScheduler`; return ComponentWithDefaultScheduler as React.MemoExoticComponent>>; }; export function getPageActive(context: FlowEngineContext): boolean | undefined { return typeof context?.pageActive?.value === 'boolean' ? context?.pageActive?.value : context?.view?.inputArgs.pageActive; }