From c3ce042c835b0c6c8f319dc9c286bc43a903c61a Mon Sep 17 00:00:00 2001 From: liujuping Date: Tue, 7 Feb 2023 17:18:47 +0800 Subject: [PATCH] feat: the renderer-core leaf component removes the react.createElement call --- .../src/builtin-simulator/renderer.ts | 3 + .../designer/src/document/document-model.ts | 1 + packages/designer/src/document/node/node.ts | 9 +- packages/renderer-core/src/hoc/leaf.tsx | 166 +++++++++--------- packages/renderer-core/src/renderer/base.tsx | 14 +- .../renderer-core/src/renderer/renderer.tsx | 2 + packages/renderer-core/src/types/index.ts | 54 ++++-- .../renderer-core/tests/hoc/leaf.test.tsx | 2 + .../tests/renderer/base.test.tsx | 3 +- packages/renderer-core/tests/utils/node.ts | 4 + 10 files changed, 160 insertions(+), 98 deletions(-) diff --git a/packages/designer/src/builtin-simulator/renderer.ts b/packages/designer/src/builtin-simulator/renderer.ts index be32b6be6..d9171c355 100644 --- a/packages/designer/src/builtin-simulator/renderer.ts +++ b/packages/designer/src/builtin-simulator/renderer.ts @@ -3,6 +3,9 @@ import { IPublicTypeNodeSchema, IPublicTypeComponentInstance, IPublicTypeNodeIns export interface BuiltinSimulatorRenderer { readonly isSimulatorRenderer: true; + autoRepaintNode?: boolean; + components: Record; + rerender: () => void; createComponent(schema: IPublicTypeNodeSchema): Component | null; getComponent(componentName: string): Component; getClosestNodeInstance( diff --git a/packages/designer/src/document/document-model.ts b/packages/designer/src/document/document-model.ts index 5eb60c396..9600e8008 100644 --- a/packages/designer/src/document/document-model.ts +++ b/packages/designer/src/document/document-model.ts @@ -64,6 +64,7 @@ export interface IDocumentModel extends Omit< IPublicModelDocumentModel, 'select dragObject: IPublicTypeDragNodeObject | IPublicTypeNodeSchema | INode | IPublicTypeDragNodeDataObject, ): boolean; + getNodeCount(): number; } export class DocumentModel implements IDocumentModel { diff --git a/packages/designer/src/document/node/node.ts b/packages/designer/src/document/node/node.ts index 7baa5ad0a..7739bcf1c 100644 --- a/packages/designer/src/document/node/node.ts +++ b/packages/designer/src/document/node/node.ts @@ -15,6 +15,7 @@ import { IPublicModelNode, IPublicModelExclusiveGroup, IPublicEnumTransformStage, + IPublicTypeDisposable, } from '@alilc/lowcode-types'; import { compatStage, isDOMText, isJSExpression, isNode } from '@alilc/lowcode-utils'; import { SettingTopEntry } from '@alilc/lowcode-designer'; @@ -112,6 +113,10 @@ export interface INode extends IPublicModelNode { replaceChild(node: INode, data: any): INode; getSuitablePlace(node: INode, ref: any): any; + + onChildrenChange(fn: (param?: { type: string; node: INode }) => void): IPublicTypeDisposable; + + onPropChange(func: (info: IPublicTypePropChangeOptions) => void): IPublicTypeDisposable; } /** @@ -1080,7 +1085,7 @@ export class Node return this.props; } - onChildrenChange(fn: (param?: { type: string; node: Node }) => void): (() => void) | undefined { + onChildrenChange(fn: (param?: { type: string; node: Node }) => void): IPublicTypeDisposable { const wrappedFunc = wrapWithEventSwitch(fn); return this.children?.onChange(wrappedFunc); } @@ -1273,7 +1278,7 @@ export class Node this.emitter?.emit('propChange', val); } - onPropChange(func: (info: IPublicTypePropChangeOptions) => void): Function { + onPropChange(func: (info: IPublicTypePropChangeOptions) => void): IPublicTypeDisposable { const wrappedFunc = wrapWithEventSwitch(func); this.emitter.on('propChange', wrappedFunc); return () => { diff --git a/packages/renderer-core/src/hoc/leaf.tsx b/packages/renderer-core/src/hoc/leaf.tsx index 8af892f05..5df988048 100644 --- a/packages/renderer-core/src/hoc/leaf.tsx +++ b/packages/renderer-core/src/hoc/leaf.tsx @@ -1,4 +1,4 @@ -import { BuiltinSimulatorHost, Node, IPublicTypePropChangeOptions } from '@alilc/lowcode-designer'; +import { INode, IPublicTypePropChangeOptions } from '@alilc/lowcode-designer'; import { GlobalEvent, IPublicEnumTransformStage, IPublicTypeNodeSchema, IPublicTypeEngineOptions } from '@alilc/lowcode-types'; import { isReactComponent, cloneEnumerableProperty } from '@alilc/lowcode-utils'; import { debounce } from '../utils/common'; @@ -16,15 +16,17 @@ export interface IComponentHocProps { __tag: any; componentId: any; _leaf: any; - forwardedRef: any; + forwardedRef?: any; } export interface IComponentHocState { childrenInState: boolean; nodeChildren: any; nodeCacheProps: any; + /** 控制是否显示隐藏 */ visible: boolean; + /** 控制是否渲染 */ condition: boolean; nodeProps: any; @@ -40,15 +42,17 @@ export interface IComponentHoc { export type IComponentConstruct = (Comp: types.IBaseRenderComponent, info: IComponentHocInfo) => types.IGeneralConstructor; interface IProps { - _leaf: Node | undefined; + _leaf: INode | undefined; visible: boolean; - componentId?: number; + componentId: number; - children?: Node[]; + children?: INode[]; - __tag?: number; + __tag: number; + + forwardedRef?: any; } enum RerenderType { @@ -61,8 +65,7 @@ enum RerenderType { // 缓存 Leaf 层组件,防止重新渲染问题 class LeafCache { - constructor(public documentId: string, public device: string) { - } + /** 组件缓存 */ component = new Map(); @@ -77,6 +80,9 @@ class LeafCache { event = new Map(); ref = new Map(); + + constructor(public documentId: string, public device: string) { + } } let cache: LeafCache; @@ -155,11 +161,11 @@ export function leafWrapper(Comp: types.IBaseRenderComponent, { const curDocumentId = baseRenderer.props?.documentId ?? ''; const curDevice = baseRenderer.props?.device ?? ''; const getNode = baseRenderer.props?.getNode; - const container: BuiltinSimulatorHost = baseRenderer.props?.__container; + const container = baseRenderer.props?.__container; const setSchemaChangedSymbol = baseRenderer.props?.setSchemaChangedSymbol; const editor = host?.designer?.editor; const runtime = adapter.getRuntime(); - const { forwardRef } = runtime; + const { forwardRef, createElement } = runtime; const Component = runtime.Component as types.IGeneralConstructor< IComponentHocProps, IComponentHocState >; @@ -192,14 +198,64 @@ export function leafWrapper(Comp: types.IBaseRenderComponent, { recordInfo: { startTime?: number | null; type?: string; - node?: Node; + node?: INode; } = {}; + + private curEventLeaf: INode | undefined; + static displayName = schema.componentName; disposeFunctions: Array<((() => void) | Function)> = []; __component_tag = 'leafWrapper'; + renderUnitInfo: { + minimalUnitId?: string; + minimalUnitName?: string; + singleRender?: boolean; + }; + + // 最小渲染单元做防抖处理 + makeUnitRenderDebounced = debounce(() => { + this.beforeRender(RerenderType.MinimalRenderUnit); + const schema = this.leaf?.export?.(IPublicEnumTransformStage.Render); + if (!schema) { + return; + } + const nextProps = getProps(schema, scope, Comp, componentInfo); + const children = getChildren(schema, scope, Comp); + const nextState = { + nodeProps: nextProps, + nodeChildren: children, + childrenInState: true, + }; + if ('children' in nextProps) { + nextState.nodeChildren = nextProps.children; + } + + __debug(`${this.leaf?.componentName}(${this.props.componentId}) MinimalRenderUnit Render!`); + this.setState(nextState); + }, 20); + + constructor(props: IProps, context: any) { + super(props, context); + // 监听以下事件,当变化时更新自己 + __debug(`${schema.componentName}[${this.props.componentId}] leaf render in SimulatorRendererView`); + clearRerenderEvent(componentCacheId); + this.curEventLeaf = this.leaf; + + cache.ref.set(componentCacheId, { + makeUnitRender: this.makeUnitRender, + }); + + let cacheState = cache.state.get(componentCacheId); + if (!cacheState || cacheState.__tag !== props.__tag) { + cacheState = this.getDefaultState(props); + } + + this.state = cacheState; + } + recordTime = () => { if (!this.recordInfo.startTime) { return; @@ -216,6 +272,14 @@ export function leafWrapper(Comp: types.IBaseRenderComponent, { this.recordInfo.startTime = null; }; + makeUnitRender = () => { + this.makeUnitRenderDebounced(); + }; + + get autoRepaintNode() { + return container?.autoRepaintNode; + } + componentDidUpdate() { this.recordTime(); } @@ -243,27 +307,6 @@ export function leafWrapper(Comp: types.IBaseRenderComponent, { }; } - constructor(props: IProps, context: any) { - super(props, context); - // 监听以下事件,当变化时更新自己 - __debug(`${schema.componentName}[${this.props.componentId}] leaf render in SimulatorRendererView`); - clearRerenderEvent(componentCacheId); - this.curEventLeaf = this.leaf; - - cache.ref.set(componentCacheId, { - makeUnitRender: this.makeUnitRender, - }); - - let cacheState = cache.state.get(componentCacheId); - if (!cacheState || cacheState.__tag !== props.__tag) { - cacheState = this.getDefaultState(props); - } - - this.state = cacheState; - } - - private curEventLeaf: Node | undefined; - setState(state: any) { cache.state.set(componentCacheId, { ...this.state, @@ -274,23 +317,13 @@ export function leafWrapper(Comp: types.IBaseRenderComponent, { } /** 由于内部属性变化,在触发渲染前,会执行该函数 */ - beforeRender(type: string, node?: Node): void { + beforeRender(type: string, node?: INode): void { this.recordInfo.startTime = Date.now(); this.recordInfo.type = type; this.recordInfo.node = node; setSchemaChangedSymbol?.(true); } - renderUnitInfo: { - minimalUnitId?: string; - minimalUnitName?: string; - singleRender?: boolean; - }; - - get autoRepaintNode() { - return container.autoRepaintNode; - } - judgeMiniUnitRender() { if (!this.renderUnitInfo) { this.getRenderUnitInfo(); @@ -308,7 +341,7 @@ export function leafWrapper(Comp: types.IBaseRenderComponent, { if (!ref) { __debug('Cant find minimalRenderUnit ref! This make rerender!'); - container.rerender(); + container?.rerender(); return; } __debug(`${this.leaf?.componentName}(${this.props.componentId}) need render, make its minimalRenderUnit ${renderUnitInfo.minimalUnitName}(${renderUnitInfo.minimalUnitId})`); @@ -321,7 +354,7 @@ export function leafWrapper(Comp: types.IBaseRenderComponent, { return; } - if (leaf.isRoot()) { + if (leaf.isRootNode) { this.renderUnitInfo = { singleRender: true, ...(this.renderUnitInfo || {}), @@ -347,32 +380,6 @@ export function leafWrapper(Comp: types.IBaseRenderComponent, { } } - // 最小渲染单元做防抖处理 - makeUnitRenderDebounced = debounce(() => { - this.beforeRender(RerenderType.MinimalRenderUnit); - const schema = this.leaf?.export?.(IPublicEnumTransformStage.Render); - if (!schema) { - return; - } - const nextProps = getProps(schema, scope, Comp, componentInfo); - const children = getChildren(schema, scope, Comp); - const nextState = { - nodeProps: nextProps, - nodeChildren: children, - childrenInState: true, - }; - if ('children' in nextProps) { - nextState.nodeChildren = nextProps.children; - } - - __debug(`${this.leaf?.componentName}(${this.props.componentId}) MinimalRenderUnit Render!`); - this.setState(nextState); - }, 20); - - makeUnitRender = () => { - this.makeUnitRenderDebounced(); - }; - componentWillReceiveProps(nextProps: any) { let { componentId } = nextProps; if (nextProps.__tag === this.props.__tag) { @@ -420,7 +427,7 @@ export function leafWrapper(Comp: types.IBaseRenderComponent, { // 目前多层循坏无法判断需要从哪一层开始渲染,故先粗暴解决 if (key === '___loop___') { __debug('key is ___loop___, render a page!'); - container.rerender(); + container?.rerender(); // 由于 scope 变化,需要清空缓存,使用新的 scope cache.component.delete(componentCacheId); return; @@ -536,7 +543,7 @@ export function leafWrapper(Comp: types.IBaseRenderComponent, { return []; } - get leaf(): Node | undefined { + get leaf(): INode | undefined { if (this.props._leaf?.isMock) { // 低代码组件作为一个整体更新,其内部的组件不需要监听相关事件 return undefined; @@ -570,13 +577,12 @@ export function leafWrapper(Comp: types.IBaseRenderComponent, { } } - let LeafWrapper = forwardRef((props: any, ref: any) => ( - // @ts-ignore - - )); + let LeafWrapper = forwardRef((props: any, ref: any) => { + return createElement(LeafHoc, { + ...props, + forwardedRef: ref, + }); + }); LeafWrapper = cloneEnumerableProperty(LeafWrapper, Comp); diff --git a/packages/renderer-core/src/renderer/base.tsx b/packages/renderer-core/src/renderer/base.tsx index 601faf6a9..b4439f4fe 100644 --- a/packages/renderer-core/src/renderer/base.tsx +++ b/packages/renderer-core/src/renderer/base.tsx @@ -3,7 +3,7 @@ /* eslint-disable react/prop-types */ import classnames from 'classnames'; import { create as createDataSourceEngine } from '@alilc/lowcode-datasource-engine/interpret'; -import { IPublicTypeNodeSchema, IPublicTypeNodeData, JSONValue, IPublicTypeCompositeValue } from '@alilc/lowcode-types'; +import { IPublicTypeNodeSchema, IPublicTypeNodeData, IPublicTypeJSONValue, IPublicTypeCompositeValue } from '@alilc/lowcode-types'; import { isI18nData, isJSExpression, isJSFunction } from '@alilc/lowcode-utils'; import adapter from '../adapter'; import divFactory from '../components/Div'; @@ -121,6 +121,8 @@ export default function baseRendererFactory(): IBaseRenderComponent { let scopeIdx = 0; return class BaseRenderer extends Component> { + [key: string]: any; + static displayName = 'BaseRenderer'; static defaultProps = { @@ -139,6 +141,7 @@ export default function baseRendererFactory(): IBaseRenderComponent { __compScopes: Record = {}; __instanceMap: Record = {}; __dataHelper: any; + /** * keep track of customMethods added to this context * @@ -155,8 +158,6 @@ export default function baseRendererFactory(): IBaseRenderComponent { */ __styleElement: any; - [key: string]: any; - constructor(props: IBaseRendererProps, context: IBaseRendererContext) { super(props, context); this.context = context; @@ -242,6 +243,7 @@ export default function baseRendererFactory(): IBaseRenderComponent { super.forceUpdate(); } } + /** * execute method in schema.lifeCycles * @PRIVATE @@ -490,6 +492,10 @@ export default function baseRendererFactory(): IBaseRenderComponent { } const _children = getSchemaChildren(schema); + if (!schema.componentName) { + logger.error('The componentName in the schema is invalid, please check the schema: ', schema); + return; + } // 解析占位组件 if (schema.componentName === 'Fragment' && _children) { const tarChildren = isJSExpression(_children) ? this.__parseExpression(_children, scope) : _children; @@ -758,7 +764,7 @@ export default function baseRendererFactory(): IBaseRenderComponent { const itemArg = (schema.loopArgs && schema.loopArgs[0]) || DEFAULT_LOOP_ARG_ITEM; const indexArg = (schema.loopArgs && schema.loopArgs[1]) || DEFAULT_LOOP_ARG_INDEX; const { loop } = schema; - return loop.map((item: JSONValue | IPublicTypeCompositeValue, i: number) => { + return loop.map((item: IPublicTypeJSONValue | IPublicTypeCompositeValue, i: number) => { const loopSelf: any = { [itemArg]: item, [indexArg]: i, diff --git a/packages/renderer-core/src/renderer/renderer.tsx b/packages/renderer-core/src/renderer/renderer.tsx index a1ed88701..c0f1c2afc 100644 --- a/packages/renderer-core/src/renderer/renderer.tsx +++ b/packages/renderer-core/src/renderer/renderer.tsx @@ -6,6 +6,7 @@ import baseRendererFactory from './base'; import divFactory from '../components/Div'; import { IRenderComponent, IRendererProps, IRendererState } from '../types'; import { IPublicTypeNodeSchema, IPublicTypeRootSchema } from '@alilc/lowcode-types'; +import logger from '../utils/logger'; export default function rendererFactory(): IRenderComponent { const { PureComponent, Component, createElement, findDOMNode } = adapter.getRuntime(); @@ -168,6 +169,7 @@ export default function rendererFactory(): IRenderComponent { } // 兼容乐高区块模板 if (schema.componentName !== 'Div' && !isFileSchema(schema)) { + logger.error('The root component name needs to be one of Page、Block、Component, please check the schema: ', schema); return '模型结构异常'; } debug('entry.render'); diff --git a/packages/renderer-core/src/types/index.ts b/packages/renderer-core/src/types/index.ts index 9c2aaa143..8d619737c 100644 --- a/packages/renderer-core/src/types/index.ts +++ b/packages/renderer-core/src/types/index.ts @@ -1,5 +1,5 @@ import type { ComponentLifecycle, CSSProperties } from 'react'; -import { BuiltinSimulatorHost } from '@alilc/lowcode-designer'; +import { BuiltinSimulatorHost, BuiltinSimulatorRenderer } from '@alilc/lowcode-designer'; import { RequestHandler, IPublicTypeNodeSchema, IPublicTypeRootSchema, IPublicTypeJSONObject } from '@alilc/lowcode-types'; export type ISchema = IPublicTypeNodeSchema | IPublicTypeRootSchema; @@ -8,16 +8,16 @@ export type ISchema = IPublicTypeNodeSchema | IPublicTypeRootSchema; ** Duck typed component type supporting both react and rax */ interface IGeneralComponent

extends ComponentLifecycle { + readonly props: Readonly

& Readonly<{ children?: any | undefined }>; + state: Readonly; + refs: Record; + context: any; setState( state: ((prevState: Readonly, props: Readonly

) => (Pick | S | null)) | (Pick | S | null), callback?: () => void ): void; forceUpdate(callback?: () => void): void; render(): any; - readonly props: Readonly

& Readonly<{ children?: any | undefined }>; - state: Readonly; - refs: Record; - context: any; } export type IGeneralConstructor< @@ -60,20 +60,28 @@ export interface ILocationLike { } export type IRendererAppHelper = Partial<{ + /** 全局公共函数 */ utils: Record; + /** 全局常量 */ constants: Record; + /** react-router 的 location 实例 */ location: ILocationLike; + /** react-router 的 history 实例 */ history: IHistoryLike; + /** @deprecated 已无业务使用 */ match: any; + /** @experimental 内部使用 */ logParams: Record; + /** @experimental 内部使用 */ addons: Record; + /** @experimental 内部使用 */ requestHandlersMap: Record; + /** CSS 类名 */ className?: string; + /** style */ style?: CSSProperties; + /** id */ id?: string | number; + /** 语言 */ locale?: string; + /** * 多语言语料 * 配置规范参见《低代码搭建组件描述协议》https://lowcode-engine.cn/lowcode 中 2.6 国际化多语言支持 * */ messages?: Record; + /** 主要用于设置渲染模块的全局上下文,里面定义的内容可以在低代码中通过 this 来访问,比如 this.utils */ appHelper?: IRendererAppHelper; + /** * 配置规范参见《低代码搭建组件描述协议》https://lowcode-engine.cn/lowcode * 主要在搭建场景中使用,用于提升用户搭建体验。 @@ -112,33 +129,46 @@ export interface IRendererProps { * > 在生产环境下不需要设置 */ componentsMap?: { [key: string]: any }; + /** 设计模式,可选值:live、design */ designMode?: string; + /** 渲染模块是否挂起,当设置为 true 时,渲染模块最外层容器的 shouldComponentUpdate 将始终返回false,在下钻编辑或者多引擎渲染的场景会用到该参数。 */ suspended?: boolean; + /** 组件获取 ref 时触发的钩子 */ onCompGetRef?: (schema: IPublicTypeNodeSchema, ref: any) => void; + /** 组件 ctx 更新回调 */ onCompGetCtx?: (schema: IPublicTypeNodeSchema, ref: any) => void; + /** 传入的 schema 是否有变更 */ getSchemaChangedSymbol?: () => boolean; + /** 设置 schema 是否有变更 */ setSchemaChangedSymbol?: (symbol: boolean) => void; + /** 自定义创建 element 的钩子 */ customCreateElement?: (Component: any, props: any, children: any) => any; + /** 渲染类型,标识当前模块是以什么类型进行渲染的 */ rendererName?: 'LowCodeRenderer' | 'PageRenderer' | string; + /** 当找不到组件时,显示的组件 */ notFoundComponent?: IGeneralComponent; + /** 当组件渲染异常时,显示的组件 */ faultComponent?: IGeneralComponent; + /** 设备信息 */ device?: string; + /** * @default true * JSExpression 是否只支持使用 this 来访问上下文变量 */ thisRequiredInJSE?: boolean; + /** * @default false * 当开启组件未找到严格模式时,渲染模块不会默认给一个容器组件 @@ -162,7 +192,7 @@ export interface IBaseRendererProps { __ctx: Record; __schema: IPublicTypeRootSchema; __host?: BuiltinSimulatorHost; - __container?: any; + __container?: BuiltinSimulatorRenderer; config?: Record; designMode?: 'design'; className?: string; @@ -173,6 +203,7 @@ export interface IBaseRendererProps { thisRequiredInJSE?: boolean; documentId?: string; getNode?: any; + /** * 设备类型,默认值:'default' */ @@ -213,13 +244,13 @@ export interface DataSource { } export interface IRuntime { + [key: string]: any; Component: IGeneralConstructor; PureComponent: IGeneralConstructor; createElement: (...args: any) => any; createContext: (...args: any) => any; forwardRef: (...args: any) => any; findDOMNode: (...args: any) => any; - [key: string]: any; } export interface IRendererModules { @@ -285,21 +316,22 @@ export interface IBaseRenderComponent { } export interface IRenderComponent { + displayName: string; + defaultProps: IRendererProps; + findDOMNode: (...args: any) => any; + new(props: IRendererProps, context: any): IGeneralComponent & { [x: string]: any; + __getRef: (ref: any) => void; componentDidMount(): Promise; componentDidUpdate(): Promise; componentWillUnmount(): Promise; componentDidCatch(e: any): Promise; shouldComponentUpdate(nextProps: IRendererProps): boolean; - __getRef: (ref: any) => void; isValidComponent(SetComponent: any): any; patchDidCatch(SetComponent: any): void; createElement(SetComponent: any, props: any, children?: any): any; getNotFoundComponent(): any; getFaultComponent(): any; }; - displayName: string; - defaultProps: IRendererProps; - findDOMNode: (...args: any) => any; } diff --git a/packages/renderer-core/tests/hoc/leaf.test.tsx b/packages/renderer-core/tests/hoc/leaf.test.tsx index 86675c771..94d50f87a 100644 --- a/packages/renderer-core/tests/hoc/leaf.test.tsx +++ b/packages/renderer-core/tests/hoc/leaf.test.tsx @@ -324,6 +324,7 @@ describe('mini unit render', () => { it('change component leaf isRoot is true', () => { const TextNode = new Node(textSchema, { isRoot: true, + isRootNode: true, }); nodeMap.set(textSchema.id, TextNode); @@ -356,6 +357,7 @@ describe('mini unit render', () => { id: 'rootId', }, { isRoot: true, + isRootNode: true }), }) }); diff --git a/packages/renderer-core/tests/renderer/base.test.tsx b/packages/renderer-core/tests/renderer/base.test.tsx index fd9b45345..3d19e324b 100644 --- a/packages/renderer-core/tests/renderer/base.test.tsx +++ b/packages/renderer-core/tests/renderer/base.test.tsx @@ -1,5 +1,5 @@ -import React, { Component, createElement, PureComponent, createContext } from 'react'; +import React, { Component, createElement, forwardRef, PureComponent, createContext } from 'react'; const mockGetRenderers = jest.fn(); const mockGetRuntime = jest.fn(); const mockParseExpression = jest.fn(); @@ -59,6 +59,7 @@ describe('Base Render methods', () => { createElement, PureComponent, createContext, + forwardRef, }); RendererClass = baseRendererFactory(); }) diff --git a/packages/renderer-core/tests/utils/node.ts b/packages/renderer-core/tests/utils/node.ts index 9f8eeb420..01c6ab507 100644 --- a/packages/renderer-core/tests/utils/node.ts +++ b/packages/renderer-core/tests/utils/node.ts @@ -40,6 +40,10 @@ export default class Node { isRoot = () => this._isRoot; + get isRootNode () { + return this._isRoot; + }; + // componentMeta() { // return this.componentMeta; // }