import { createElement, forwardRef, useImperativeHandle, useState, useMemo, useRef, createRef, RefObject, } from 'react' import { extend } from './index' import { createRoot } from 'react-dom/client' export type AgentComponentRef = { $$setProps(props: ComponentProps, nocache?: boolean): void } & ComponentImperativeHandle export interface ImperativeOptions { component: any baseProps?: ComponentProps props?: ComponentProps afterRender?: () => void } export interface ImperativeCallbackOptions< ComponentProps, ComponentImperativeHandle > { ref: RefObject> unmount: () => any container: any } export interface ImperativeCallback { ( options: ImperativeCallbackOptions< ComponentProps, ComponentImperativeHandle > ): ImperativeOptions } function createAgentComponent( component: any, baseProps: ComponentProps ) { return forwardRef((props: ComponentProps, ref) => { const [customProps, setCustomdProps] = useState(props) const oldProps = useRef({}) const componentRef = useRef(null) const mergedProps = useMemo(() => { const props = extend({}, customProps, baseProps) return Object.assign(oldProps.current, props) }, [customProps]) useImperativeHandle( ref, () => ({ $$setProps(props, nocache) { if (nocache) { oldProps.current = {} setCustomdProps(props) } else { ;(Object.keys(props as any) as (keyof ComponentProps)[]).some( (k) => { if (props[k] !== oldProps.current[k]) { setCustomdProps(props) return true } } ) } }, ...componentRef.current, } as AgentComponentRef) ) return createElement(component, { ...mergedProps, ref: componentRef, }) }) } // 命令式组件 // # 功能 // - 不缓存 // - 基础 props export function imperative( callback: ImperativeCallback ): RefObject> { const container = document.createElement('div') document.body.appendChild(container) const unmount = () => { setTimeout(() => { root.unmount() document.body.removeChild(container) }) } const ref = createRef>() const { component, baseProps, props, afterRender } = callback({ ref, unmount, container, }) const element = createElement( createAgentComponent(component, { ...baseProps, } as ComponentProps), { ...props, ref, } as any ) const root = createRoot(container) root.render(element) setTimeout(() => { afterRender?.() }) return ref }