'use client'; import React, { useState, useCallback, useEffect } from 'react'; import type { RefComponentMountAdapter, RefComponentMountParams, RefComponentMountResult, } from '../types/refComponentMount'; import type { ModalRef } from '../types/modal'; type MountQueueItem = { id: number, RefComponent: RefComponentMountParams['RefComponent'], props: RefComponentMountParams['props'], options: RefComponentMountParams['options'], resolve: (value: RefComponentMountResult) => void, reject: (reason?: any) => void, }; type InTreeMountStore = { push: ((item: MountQueueItem) => void) | null, remove: ((id: number) => void) | null, settledIds: Set, }; export type InTreeMountAdapter = { /** 供 mergeNativeAdapter / mergeRefComponentMount / options.mountAdapter 使用的适配器 */ adapter: RefComponentMountAdapter, /** * 挂载 Host,须渲染在组件树内。 * 与 adapter 通过工厂闭包关联,而非 React Context(命令式 API 无法读取 Context)。 */ Host: React.FC, }; /** * 创建「组件树内挂载」适配器与 Host 组件对。 * 命令式 API 无法 useContext,须在应用层用 useEffect 调用 mergeNativeAdapter({ refComponentMount: adapter }) 或 mergeRefComponentMount(adapter) 注册, * 并将 Host 渲染到树中;二者由本工厂闭包绑定,不依赖 Provider/Context 透传。 * @returns adapter + Host */ function createInTreeMountAdapter(): InTreeMountAdapter { let mountQueueSeed = 0; const store: InTreeMountStore = { push: null, remove: null, settledIds: new Set(), }; /** * 将挂载请求推入 Host 队列。 * @param params - 挂载参数 * @returns ref 与 destroy */ const mount = < M extends ModalRef, P extends Record = Record, >( params: RefComponentMountParams, ): Promise> => new Promise((resolve, reject) => { if (!store.push) { reject(new Error( '[use-modal-ref] In-tree mount Host is not mounted. ' + 'Render the Host from createInTreeMountAdapter() in your component tree ' + 'and call mergeNativeAdapter({ refComponentMount: adapter }) before createRefComponent / showRefModal.', )); return; } const id = ++mountQueueSeed; store.push({ id, RefComponent: params.RefComponent, props: params.props, options: params.options, resolve: resolve as (value: RefComponentMountResult) => void, reject, }); }); const adapter: RefComponentMountAdapter = { id: 'in-tree', mount, }; /** * 在树内渲染队列中的 RefComponent。 * @returns Host 节点 */ function Host() { const [items, setItems] = useState([]); /** * 从队列移除挂载项。 * @param id - 队列项 id */ const removeItem = useCallback((id: number) => { store.settledIds.delete(id); setItems((prev) => prev.filter((item) => item.id !== id)); }, []); useEffect(() => { store.push = (item) => { setItems((prev) => [...prev, item]); }; store.remove = removeItem; return () => { store.push = null; store.remove = null; }; }, [removeItem]); return ( <> {items.map((item) => { const { RefComponent } = item; return ( { if (!instance || store.settledIds.has(item.id)) { return; } store.settledIds.add(item.id); const destroy = () => { store.remove?.(item.id); }; item.resolve([instance, destroy]); item.options.onRef?.(instance, destroy); }} /> ); })} ); } Host.displayName = 'RefComponentInTreeMountHost'; return { adapter, Host }; } export default createInTreeMountAdapter;