From 7b181521b98f47f26a19d161df03c80a44effcf8 Mon Sep 17 00:00:00 2001 From: "liujuping.liujupin" Date: Wed, 3 Nov 2021 11:38:37 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E5=BE=AA=E7=8E=AF?= =?UTF-8?q?=EF=BC=8C=E6=8F=92=E6=A7=BD=E7=AD=89=E4=BD=9C=E7=94=A8=E5=9F=9F?= =?UTF-8?q?=E8=8E=B7=E5=8F=96=E5=A4=B1=E8=B4=A5=E9=97=AE=E9=A2=98=20&=20?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=9C=80=E5=B0=8F=E6=B8=B2=E6=9F=93=E5=8D=95?= =?UTF-8?q?=E5=85=83=E5=8A=9F=E8=83=BD=EF=BC=8C=E8=A7=A3=E5=86=B3=E5=A2=9E?= =?UTF-8?q?=E9=87=8F=E6=9B=B4=E6=96=B0=E6=9C=BA=E5=88=B6=E7=9A=84=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/designer/src/component-meta.ts | 7 ++ packages/renderer-core/src/hoc/leaf.tsx | 115 ++++++++++++++---- packages/renderer-core/src/renderer/base.tsx | 100 +++++++-------- packages/types/src/metadata.ts | 2 + .../src/bundle/upgrade-metadata.ts | 6 + 5 files changed, 154 insertions(+), 76 deletions(-) diff --git a/packages/designer/src/component-meta.ts b/packages/designer/src/component-meta.ts index 9baa5726d..fec7eedf6 100644 --- a/packages/designer/src/component-meta.ts +++ b/packages/designer/src/component-meta.ts @@ -85,6 +85,10 @@ export class ComponentMeta { return this._isContainer! || this.isRootComponent(); } + get isMinimalRenderUnit(): boolean { + return this._isMinimalRenderUnit || false; + } + private _isModal?: boolean; get isModal(): boolean { @@ -128,6 +132,8 @@ export class ComponentMeta { private _title?: TitleContent; + private _isMinimalRenderUnit?: boolean; + get title(): string | I18nData | ReactElement { // TODO: 标记下。这块需要康师傅加一下API,页面正常渲染。 // string | i18nData | ReactElement @@ -220,6 +226,7 @@ export class ComponentMeta { this._isModal = !!component.isModal; this._descriptor = component.descriptor; this._rootSelector = component.rootSelector; + this._isMinimalRenderUnit = component.isMinimalRenderUnit; if (component.nestingRule) { const { parentWhitelist, childWhitelist } = component.nestingRule; this.parentWhitelist = buildFilter(parentWhitelist); diff --git a/packages/renderer-core/src/hoc/leaf.tsx b/packages/renderer-core/src/hoc/leaf.tsx index 41b3b22ff..aaa15de4d 100644 --- a/packages/renderer-core/src/hoc/leaf.tsx +++ b/packages/renderer-core/src/hoc/leaf.tsx @@ -16,6 +16,7 @@ export interface IComponentHocInfo { schema: any; baseRenderer: types.IBaseRendererInstance; componentInfo: any; + scope: any; } type DesignMode = Pick['designMode']; @@ -45,6 +46,7 @@ enum RerenderType { ChildChanged = 'ChildChanged', PropsChanged = 'PropsChanged', VisibleChanged = 'VisibleChanged', + MinimalRenderUnit = 'MinimalRenderUnit', } // 缓存 Leaf 层组件,防止重新渲染问题 @@ -63,6 +65,8 @@ class LeafCache { * 订阅事件缓存,导致 rerender 的订阅事件 */ event = new Map(); + + ref = new Map(); } let cache: LeafCache; @@ -119,6 +123,7 @@ export function leafWrapper(Comp: types.IBaseRenderer, { schema, baseRenderer, componentInfo, + scope, }: IComponentHocInfo) { const { __debug, @@ -152,8 +157,8 @@ export function leafWrapper(Comp: types.IBaseRenderer, { getNode, }); - if (curDocumentId && cache.component.has(schema.componentName)) { - return cache.component.get(schema.componentName); + if (curDocumentId && cache.component.has(schema.id)) { + return cache.component.get(schema.id); } class LeafHoc extends Component { @@ -259,9 +264,71 @@ export function leafWrapper(Comp: types.IBaseRenderer, { setSchemaChangedSymbol?.(true); } - // get isInWhitelist() { - // return whitelist.includes(schema.componentName); - // } + renderUnitInfo: { + minimalUnitId?: string, + minimalUnitName?: string; + singleRender?: boolean, + }; + + shouldRenderSingleNode(): boolean { + if (!this.renderUnitInfo) { + this.getRenderUnitInfo(); + } + + const renderUnitInfo = this.renderUnitInfo; + + if (renderUnitInfo.singleRender) { + return true; + } + + const ref = cache.ref.get(renderUnitInfo.minimalUnitId); + + if (!ref) { + __debug('Cant find minimalRenderUnit ref! This make rerender!'); + container.rerender(); + return false; + } + __debug(`${this.leaf?.componentName}(${this.props.componentId}) need render, make its minimalRenderUnit ${renderUnitInfo.minimalUnitName}(${renderUnitInfo.minimalUnitId})`); + ref.makeUnitRender(); + + return false; + } + + getRenderUnitInfo(leaf = this.leaf) { + if (leaf?.isRoot()) { + this.renderUnitInfo = { + singleRender: true, + ...(this.renderUnitInfo || {}), + }; + } + if (leaf?.componentMeta.isMinimalRenderUnit) { + this.renderUnitInfo = { + minimalUnitId: leaf.id, + minimalUnitName: leaf.componentName, + singleRender: false, + }; + } + if (leaf?.parent) { + this.getRenderUnitInfo(leaf.parent); + } + } + + makeUnitRender() { + this.beforeRender(RerenderType.MinimalRenderUnit); + const nextProps = getProps(this.leaf?.export?.(TransformStage.Render) as types.ISchema, Comp, componentInfo); + const children = getChildren(this.leaf?.export?.(TransformStage.Render) as types.ISchema, Comp); + const nextState = { + 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); + } componentWillReceiveProps(nextProps: any) { let { _leaf, componentId } = nextProps; @@ -294,11 +361,6 @@ export function leafWrapper(Comp: types.IBaseRenderer, { } = propChangeInfo; const node = leaf; - // if (this.isInWhitelist) { - // container.rerender(); - // return; - // } - // 如果循坏条件变化,从根节点重新渲染 // 目前多层循坏无法判断需要从哪一层开始渲染,故先粗暴解决 if (key === '___loop___') { @@ -307,9 +369,12 @@ export function leafWrapper(Comp: types.IBaseRenderer, { return; } - this.beforeRender(RerenderType.PropsChanged); __debug(`${leaf?.componentName}[${this.props.componentId}] component trigger onPropsChange event`); - const nextProps = getProps(node?.export?.(TransformStage.Render) as types.ISchema, Comp, componentInfo); + if (!this.shouldRenderSingleNode()) { + return; + } + this.beforeRender(RerenderType.PropsChanged); + const nextProps = getProps(node?.export?.(TransformStage.Render) as types.ISchema, scope, Comp, componentInfo); this.setState(nextProps.children ? { nodeChildren: nextProps.children, nodeProps: nextProps, @@ -330,12 +395,11 @@ export function leafWrapper(Comp: types.IBaseRenderer, { return; } - // if (this.isInWhitelist) { - // container.rerender(); - // return; - // } - __debug(`${leaf?.componentName}[${this.props.componentId}] component trigger onVisibleChange event`); + if (!this.shouldRenderSingleNode()) { + return; + } + this.beforeRender(RerenderType.VisibleChanged); this.setState({ visible: flag, @@ -354,16 +418,15 @@ export function leafWrapper(Comp: types.IBaseRenderer, { type, node, } = param || {}; - // if (this.isInWhitelist) { - // container.rerender(); - // return; - // } - this.beforeRender(`${RerenderType.ChildChanged}-${type}`, node); __debug(`${schema.componentName}[${this.props.componentId}] component trigger onChildrenChange event`); + if (!this.shouldRenderSingleNode()) { + return; + } + this.beforeRender(`${RerenderType.ChildChanged}-${type}`, node); // TODO: 缓存同级其他元素的 children。 // 缓存二级 children Next 查询筛选组件有问题 // 缓存一级 children Next Tab 组件有问题 - const nextChild = getChildren(leaf?.export?.(TransformStage.Render) as types.ISchema, Comp); // this.childrenMap + const nextChild = getChildren(leaf?.export?.(TransformStage.Render) as types.ISchema, scope, Comp); // this.childrenMap this.setState({ nodeChildren: nextChild, childrenInState: true, @@ -431,7 +494,7 @@ export function leafWrapper(Comp: types.IBaseRenderer, { const LeafWrapper = forwardRef((props: any, ref: any) => ( // @ts-ignore - + cache.ref.set(props.componentId, ref)} /> )); if (typeof Comp === 'object') { @@ -446,9 +509,7 @@ export function leafWrapper(Comp: types.IBaseRenderer, { LeafWrapper.displayName = (Comp as any).displayName; - if (curDocumentId) { - cache.component.set(schema.componentName, LeafWrapper); - } + cache.component.set(schema.id, LeafWrapper); return LeafWrapper; } \ No newline at end of file diff --git a/packages/renderer-core/src/renderer/base.tsx b/packages/renderer-core/src/renderer/base.tsx index 8fcbf8e86..f75b51385 100644 --- a/packages/renderer-core/src/renderer/base.tsx +++ b/packages/renderer-core/src/renderer/base.tsx @@ -347,34 +347,31 @@ export default function baseRenererFactory() { __createDom = () => { const { __schema, __ctx, __components = {} } = this.props; - const self: any = {}; - self.__proto__ = __ctx || this; + const scope: any = {}; + scope.__proto__ = __ctx || this; if (!this._self) { - this._self = self; + this._self = scope; } const _children = this.getSchemaChildren(__schema); let Comp = __components[__schema.componentName]; - return this.__createVirtualDom(_children, self, ({ + if (!Comp) { + console.error(`${__schema.componentName} is invalid!`); + } + + return this.__createVirtualDom(_children, scope, ({ schema: __schema, - Comp: this.__getHocComp(Comp, __schema), + Comp: this.__getHocComp(Comp, __schema, scope), } as IInfo)); }; - private get self() { - const { __ctx } = this.props; - const self: any = {}; - self.__proto__ = __ctx || this; - - return self; - } // 将模型结构转换成react Element // schema 模型结构 // self 为每个渲染组件构造的上下文,self是自上而下继承的 // parentInfo 父组件的信息,包含schema和Comp // idx 若为循环渲染的循环Index - __createVirtualDom = (schema: ISchema, self: any, parentInfo: IInfo, idx: string | number = ''): any => { + __createVirtualDom = (schema: ISchema, scope: any, parentInfo: IInfo, idx: string | number = ''): any => { const { engine } = this.context || {}; try { if (!schema) return null; @@ -388,28 +385,28 @@ export default function baseRenererFactory() { const { __appHelper: appHelper, __components: components = {} } = this.props || {}; if (isJSExpression(schema)) { - return parseExpression(schema, self); + return parseExpression(schema, scope); } if (isI18n(schema)) { - return parseI18n(schema, self); + return parseI18n(schema, scope); } if (isJSSlot(schema)) { - return this.__createVirtualDom(schema.value, self, parentInfo); + return this.__createVirtualDom(schema.value, scope, parentInfo); } if (typeof schema === 'string') return schema; if (typeof schema === 'number' || typeof schema === 'boolean') { return String(schema); } if (Array.isArray(schema)) { - if (schema.length === 1) return this.__createVirtualDom(schema[0], self, parentInfo); - return schema.map((item, idy) => this.__createVirtualDom(item, self, parentInfo, item?.__ctx?.lceKey ? '' : String(idy))); + if (schema.length === 1) return this.__createVirtualDom(schema[0], scope, parentInfo); + return schema.map((item, idy) => this.__createVirtualDom(item, scope, parentInfo, item?.__ctx?.lceKey ? '' : String(idy))); } // FIXME const _children = this.getSchemaChildren(schema); // 解析占位组件 if (schema.componentName === 'Flagment' && _children) { - const tarChildren = isJSExpression(_children) ? parseExpression(_children, self) : _children; - return this.__createVirtualDom(tarChildren, self, parentInfo); + const tarChildren = isJSExpression(_children) ? parseExpression(_children, scope) : _children; + return this.__createVirtualDom(tarChildren, scope, parentInfo); } if (schema.$$typeof) { @@ -429,26 +426,26 @@ export default function baseRenererFactory() { } if (schema.loop != null) { - const loop = parseData(schema.loop, self); + const loop = parseData(schema.loop, scope); if ((Array.isArray(loop) && loop.length > 0) || isJSExpression(loop)) { return this.__createLoopVirtualDom( { ...schema, loop, }, - self, + scope, parentInfo, idx, ); } } - const condition = schema.condition == null ? true : parseData(schema.condition, self); + const condition = schema.condition == null ? true : parseData(schema.condition, scope); if (!condition) return null; let scopeKey = ''; // 判断组件是否需要生成scope,且只生成一次,挂在this.__compScopes上 if (Comp.generateScope) { - const key = parseExpression(schema.props.key, self); + const key = parseExpression(schema.props.key, scope); if (key) { // 如果组件自己设置key则使用组件自己的key scopeKey = key; @@ -469,8 +466,8 @@ export default function baseRenererFactory() { // 如果组件有设置scope,需要为组件生成一个新的scope上下文 if (scopeKey && this.__compScopes[scopeKey]) { const compSelf = { ...this.__compScopes[scopeKey] }; - compSelf.__proto__ = self; - self = compSelf; + compSelf.__proto__ = scope; + scope = compSelf; } // 容器类组件的上下文通过props传递,避免context传递带来的嵌套问题 @@ -488,7 +485,7 @@ export default function baseRenererFactory() { otherProps.__tag = Math.random(); } const componentInfo: any = {}; - const props: any = this.__getComponentProps(schema, Comp, { + const props: any = this.__getComponentProps(schema, scope, Comp, { ...componentInfo, props: transformArrayToMap(componentInfo.props, 'name'), }) || {}; @@ -498,6 +495,7 @@ export default function baseRenererFactory() { schema, componentInfo, baseRenderer: this, + scope, }); }); @@ -522,7 +520,7 @@ export default function baseRenererFactory() { } if (schema?.__ctx?.lceKey) { if (!isFileSchema(schema)) { - engine?.props?.onCompGetCtx(schema, self); + engine?.props?.onCompGetCtx(schema, scope); } props.key = props.key || `${schema.__ctx.lceKey}_${schema.__ctx.idx || 0}_${idx !== undefined ? idx : ''}`; } else if (typeof idx === 'number' && !props.key) { @@ -535,7 +533,7 @@ export default function baseRenererFactory() { props.key = props.__id; } - let child: any = parentInfo.componentChildren || this.__getSchemaChildrenVirtualDom(schema, Comp); + let child: any = parentInfo.componentChildren || this.__getSchemaChildrenVirtualDom(schema, scope, Comp); const renderComp = (props: any) => engine.createElement(Comp, props, child); // 设计模式下的特殊处理 if (engine && [DESIGN_MODE.EXTEND, DESIGN_MODE.BORDER].includes(engine.props.designMode)) { @@ -568,7 +566,7 @@ export default function baseRenererFactory() { return engine.createElement(engine.getFaultComponent(), { error: e, schema, - self, + self: scope, parentInfo, idx, }); @@ -594,7 +592,7 @@ export default function baseRenererFactory() { .map((d: IComponentHoc) => d.hoc); } - __getSchemaChildrenVirtualDom = (schema: ISchema, Comp: any, childrenMap?: Map) => { + __getSchemaChildrenVirtualDom = (schema: ISchema, scope: any, Comp: any, childrenMap?: Map) => { let _children = this.getSchemaChildren(schema); let children: any = []; @@ -605,8 +603,8 @@ export default function baseRenererFactory() { _children.forEach((_child: any) => { const _childVirtualDom = this.__createVirtualDom( - isJSExpression(_child) ? parseExpression(_child, this.self) : _child, - this.self, + isJSExpression(_child) ? parseExpression(_child, scope) : _child, + scope, { schema, Comp, @@ -625,11 +623,11 @@ export default function baseRenererFactory() { return null; }; - __getComponentProps = (schema: ISchema, Comp: any, componentInfo?: any) => { + __getComponentProps = (schema: ISchema, scope: any, Comp: any, componentInfo?: any) => { if (!schema) { return {}; } - return this.__parseProps(schema?.props, this.self, '', { + return this.__parseProps(schema?.props, scope, '', { schema, Comp, componentInfo: { @@ -639,7 +637,7 @@ export default function baseRenererFactory() { }) || {}; }; - __createLoopVirtualDom = (schema: ISchema, self: any, parentInfo: IInfo, idx: number | string) => { + __createLoopVirtualDom = (schema: ISchema, scope: any, parentInfo: IInfo, idx: number | string) => { if (isFileSchema(schema)) { console.warn('file type not support Loop'); return null; @@ -652,7 +650,7 @@ export default function baseRenererFactory() { [itemArg]: item, [indexArg]: i, }; - loopSelf.__proto__ = self; + loopSelf.__proto__ = scope; return this.__createVirtualDom( { ...schema, @@ -670,7 +668,7 @@ export default function baseRenererFactory() { return engine?.props?.designMode === 'design'; } - __parseProps = (props: any, self: any, path: string, info: IInfo): any => { + __parseProps = (props: any, scope: any, path: string, info: IInfo): any => { const { schema, Comp, componentInfo = {} } = info; const propInfo = getValue(componentInfo.props, path); // FIXME! 将这行逻辑外置,解耦,线上环境不要验证参数,调试环境可以有,通过传参自定义 @@ -683,7 +681,7 @@ export default function baseRenererFactory() { const parseReactNode = (data: any, params: any) => { if (isEmpty(params)) { - return checkProps(this.__createVirtualDom(data, self, ({ schema, Comp } as IInfo))); + return checkProps(this.__createVirtualDom(data, scope, ({ schema, Comp } as IInfo))); } return checkProps(function () { const args: any = {}; @@ -696,8 +694,8 @@ export default function baseRenererFactory() { } }); } - args.__proto__ = self; - return self.__createVirtualDom(data, args, { schema, Comp }); + args.__proto__ = scope; + return scope.__createVirtualDom(data, args, { schema, Comp }); }); }; @@ -713,7 +711,7 @@ export default function baseRenererFactory() { return checkProps(props); } if (isJSExpression(props)) { - props = parseExpression(props, self); + props = parseExpression(props, scope); // 只有当变量解析出来为模型结构的时候才会继续解析 if (!isSchema(props) && !isJSSlot(props)) return checkProps(props); } @@ -726,7 +724,7 @@ export default function baseRenererFactory() { if (i18nProp) { props = i18nProp; } else { - return parseI18n(props, self); + return parseI18n(props, scope); } } @@ -768,10 +766,10 @@ export default function baseRenererFactory() { ); } if (Array.isArray(props)) { - return checkProps(props.map((item, idx) => this.__parseProps(item, self, path ? `${path}.${idx}` : `${idx}`, info))); + return checkProps(props.map((item, idx) => this.__parseProps(item, scope, path ? `${path}.${idx}` : `${idx}`, info))); } if (typeof props === 'function') { - return checkProps(props.bind(self)); + return checkProps(props.bind(scope)); } if (props && typeof props === 'object') { if (props.$$typeof) return checkProps(props); @@ -781,7 +779,7 @@ export default function baseRenererFactory() { res[key] = val; return; } - res[key] = this.__parseProps(val, self, path ? `${path}.${key}` : key, info); + res[key] = this.__parseProps(val, scope, path ? `${path}.${key}` : key, info); }); return checkProps(res); } @@ -829,12 +827,13 @@ export default function baseRenererFactory() { return createElement(AppContext.Consumer, {}, children); }; - __getHocComp(Comp: any, schema: any) { + __getHocComp(Comp: any, schema: any, scope: any) { this.componentHoc.forEach((ComponentConstruct: IComponentConstruct) => { Comp = ComponentConstruct(Comp || Div, { schema, componentInfo: {}, baseRenderer: this, + scope, }); }); @@ -843,8 +842,11 @@ export default function baseRenererFactory() { __renderComp(Comp: any, ctxProps: object) { const { __schema } = this.props; - Comp = this.__getHocComp(Comp, __schema); - const data = this.__parseProps(__schema?.props, this.self, '', { + const { __ctx } = this.props; + const scope: any = {}; + scope.__proto__ = __ctx || this; + Comp = this.__getHocComp(Comp, __schema, scope); + const data = this.__parseProps(__schema?.props, scope, '', { schema: __schema, Comp, componentInfo: {}, diff --git a/packages/types/src/metadata.ts b/packages/types/src/metadata.ts index 77476d758..c266755a1 100644 --- a/packages/types/src/metadata.ts +++ b/packages/types/src/metadata.ts @@ -29,6 +29,8 @@ export interface ComponentConfigure { descriptor?: string; nestingRule?: NestingRule; + isMinimalRenderUnit?: boolean; + rootSelector?: string; // copy, move, remove | * disableBehaviors?: string[] | string; diff --git a/packages/vision-polyfill/src/bundle/upgrade-metadata.ts b/packages/vision-polyfill/src/bundle/upgrade-metadata.ts index 5817a7b5d..89487ac73 100644 --- a/packages/vision-polyfill/src/bundle/upgrade-metadata.ts +++ b/packages/vision-polyfill/src/bundle/upgrade-metadata.ts @@ -132,6 +132,7 @@ export interface OldPrototypeConfig { componentName: string; // => docUrl?: string; // => defaultProps?: any; // => ? + isMinimalRenderUnit?: boolean; // => false /** * extra actions on the outline of current selected node * by default we have: remove / clone @@ -702,6 +703,9 @@ export function upgradeMetadata(oldConfig: OldPrototypeConfig) { devMode, schema, isTopFixed, + + // render + isMinimalRenderUnit, } = oldConfig; let { canResizing, // resizing @@ -714,6 +718,7 @@ export function upgradeMetadata(oldConfig: OldPrototypeConfig) { docUrl, devMode: devMode || 'procode', schema: schema?.componentsTree[0], + // isMinimalRenderUnit, }; if (category) { @@ -732,6 +737,7 @@ export function upgradeMetadata(oldConfig: OldPrototypeConfig) { isModal, isFloating, descriptor, + isMinimalRenderUnit, }; if (canOperating === false) {