import React, { startTransition, useEffect, useRef, useState } from 'react' import { createEgreactRoot } from '../renderer/create' import { ArrayContainer } from '../Host/custom/ArrayContainer' import { DevThrow, is } from '../utils' interface ItemRendererClassProps { children: (data: any, instance: eui.ItemRenderer) => React.ReactElement useRenderer?: boolean // 是否使用渲染器渲染,否则在原渲染树中渲染 concurrent?: boolean // 是否使用 concurrent 模式更新 sync?: boolean // 是否使用同步模式更新 updateChildren?: boolean | ReadonlyArray // 渲染器渲染时是否更新 children } interface ItemRenderer extends eui.IItemRenderer { element: React.ReactElement } export function ItemRendererClass({ children, concurrent = true, useRenderer = true, sync = false, updateChildren = false, }: ItemRendererClassProps) { const [, setFlag] = useState(0) const _update = () => setFlag((flag) => flag + 1) const update = concurrent ? () => startTransition(_update) : _update const elementRef = useRef(children) elementRef.current = children const set = useRef(new Set()).current const firstItemRendered = useRef(false) const [ItemRendererClass] = useState( () => class ItemRenderer extends eui.ItemRenderer { root = (useRenderer ? createEgreactRoot(new ArrayContainer()) : null)! element: React.ReactElement = () constructor() { super() set.add(this) this.addEventListener( egret.Event.REMOVED_FROM_STAGE, () => { if (useRenderer) this.root.unmount() set.delete(this) }, this, ) } dataChanged() { const newNode = elementRef.current(this.data, this) if (newNode.type !== 'eui-itemRenderer') DevThrow(`The container of item must be \`eui-itemRenderer\` in \`ItemRendererClass\``, { from: 'ItemRendererClass', link: 'https://xingxinglieo.github.io/egreact/guide/basic#%E5%BE%AA%E7%8E%AF', }) this.element = React.cloneElement(newNode, { object: this, ...(newNode.key === null ? { key: this.$hashCode } : {}), }) /* 第一个渲染项的第一次渲染必须开启 sync 否则会影响 eui.DataGroup 对于高度的测量 */ const isSync = !firstItemRendered.current || sync if (useRenderer) this.root.render(this.element, { sync: isSync, concurrent }) else update() } }, ) const isUpdateChildren = !!updateChildren const updateChildrenDeps = is.arr(updateChildren) ? updateChildren : [children] useEffect(() => { if (useRenderer && isUpdateChildren) { for (let renderer of set) { // @ts-ignore renderer.dataChanged() } } }, [isUpdateChildren, ...updateChildrenDeps]) useEffect(() => () => set.clear(), []) return ( <> {!useRenderer && {[...set].map((_) => _.element)}} ) }