From 706dad4aa5b47eaad8a2dadadb5a5c1a2da18e3e Mon Sep 17 00:00:00 2001 From: JackLian Date: Wed, 13 Jul 2022 11:52:38 +0800 Subject: [PATCH] refactor: abstract excuteLifeCycleMethod and some minor refactors --- packages/renderer-core/jest.config.js | 2 +- packages/renderer-core/src/renderer/base.tsx | 105 ++++----- packages/renderer-core/src/renderer/page.tsx | 2 - .../tests/renderer/base.test.tsx | 210 +++++++++++++++++- 4 files changed, 262 insertions(+), 57 deletions(-) diff --git a/packages/renderer-core/jest.config.js b/packages/renderer-core/jest.config.js index 4e27e6c90..ceb8e8b56 100644 --- a/packages/renderer-core/jest.config.js +++ b/packages/renderer-core/jest.config.js @@ -10,7 +10,7 @@ const jestConfig = { // // '^.+\\.(js|jsx)$': 'babel-jest', // }, // testMatch: ['(/tests?/.*(test))\\.[jt]s$'], - // testMatch: ['**/*/common.test.ts'], + // testMatch: ['**/*/base.test.tsx'], transformIgnorePatterns: [ `/node_modules/(?!${esModules})/`, ], diff --git a/packages/renderer-core/src/renderer/base.tsx b/packages/renderer-core/src/renderer/base.tsx index 0c2e09152..29ed7a7ba 100644 --- a/packages/renderer-core/src/renderer/base.tsx +++ b/packages/renderer-core/src/renderer/base.tsx @@ -35,6 +35,38 @@ import { IComponentConstruct, IComponentHoc, leafWrapper } from '../hoc/leaf'; import logger from '../utils/logger'; import isUseLoop from '../utils/is-use-loop'; +/** + * execute method in schema.lifeCycles with context + * @PRIVATE + */ +export function excuteLifeCycleMethod(context: any, schema: NodeSchema, method: string, args: any, thisRequiredInJSE: boolean | undefined): any { + if (!context || !isSchema(schema) || !method) { + return; + } + const lifeCycleMethods = getValue(schema, 'lifeCycles', {}); + let fn = lifeCycleMethods[method]; + + if (!fn) { + return; + } + + // TODO: cache + if (isJSExpression(fn) || isJSFunction(fn)) { + fn = thisRequiredInJSE ? parseThisRequiredExpression(fn, context) : parseExpression(fn, context); + } + + if (typeof fn !== 'function') { + console.error(`生命周期${method}类型不符`, fn); + return; + } + + try { + return fn.apply(context, args); + } catch (e) { + console.error(`[${schema.componentName}]生命周期${method}出错`, e); + } +} + export default function baseRendererFactory(): IBaseRenderComponent { const { BaseRenderer: customBaseRenderer } = adapter.getRenderers(); @@ -72,32 +104,33 @@ export default function baseRendererFactory(): IBaseRenderComponent { static contextType = AppContext; - __namespace = 'base'; - appHelper?: IRendererAppHelper; + i18n: any; + getLocale: any; + setLocale: any; + dataSourceMap: Record = {}; + + __namespace = 'base'; __compScopes: Record = {}; __instanceMap: Record = {}; __dataHelper: any; __customMethodsList: any[] = []; - dataSourceMap: Record = {}; + __parseExpression: any; __ref: any; - i18n: any; - getLocale: any; - setLocale: any; /** * reference of style element contains schema.css * * @type {any} */ - styleElement: any; - parseExpression: any; + __styleElement: any; + [key: string]: any; constructor(props: IBaseRendererProps, context: IBaseRendererContext) { super(props, context); this.context = context; - this.parseExpression = props?.thisRequiredInJSE ? parseThisRequiredExpression : parseExpression; + this.__parseExpression = props?.thisRequiredInJSE ? parseThisRequiredExpression : parseExpression; this.__beforeInit(props); this.__init(props); this.__afterInit(props); @@ -119,21 +152,7 @@ export default function baseRendererFactory(): IBaseRenderComponent { __afterInit(_props: IBaseRendererProps) { } static getDerivedStateFromProps(props: IBaseRendererProps, state: any) { - logger.log('getDerivedStateFromProps'); - const func = props?.__schema?.lifeCycles?.getDerivedStateFromProps; - - if (func) { - if (isJSExpression(func) || isJSFunction(func)) { - const fn = props.thisRequiredInJSE ? parseThisRequiredExpression(func, this) : parseExpression(func, this); - return fn?.(props, state); - } - - if (typeof func === 'function') { - // eslint-disable-next-line @typescript-eslint/ban-types - return (func as Function)(props, state); - } - } - return null; + return excuteLifeCycleMethod(this, props?.__schema, 'getDerivedStateFromProps', [props, state], props.thisRequiredInJSE); } async getSnapshotBeforeUpdate(...args: any[]) { @@ -198,23 +217,7 @@ export default function baseRendererFactory(): IBaseRenderComponent { * @PRIVATE */ __excuteLifeCycleMethod = (method: string, args?: any) => { - const lifeCycleMethods = getValue(this.props.__schema, 'lifeCycles', {}); - let fn = lifeCycleMethods[method]; - if (fn) { - // TODO: cache - if (isJSExpression(fn) || isJSFunction(fn)) { - fn = this.parseExpression(fn, this); - } - if (typeof fn !== 'function') { - console.error(`生命周期${method}类型不符`, fn); - return; - } - try { - return fn.apply(this, args); - } catch (e) { - console.error(`[${this.props.__schema.componentName}]生命周期${method}出错`, e); - } - } + excuteLifeCycleMethod(this, this.props.__schema, method, args, this.props.thisRequiredInJSE); }; /** @@ -242,7 +245,7 @@ export default function baseRendererFactory(): IBaseRenderComponent { forEach(__schema.methods, (val: any, key: string) => { let value = val; if (isJSExpression(value) || isJSFunction(value)) { - value = this.parseExpression(value, this); + value = this.__parseExpression(value, this); } if (typeof value !== 'function') { console.error(`自定义函数${key}类型不符`, value); @@ -351,16 +354,16 @@ export default function baseRendererFactory(): IBaseRenderComponent { __writeCss = (props: IBaseRendererProps) => { const css = getValue(props.__schema, 'css', ''); this.__debug('create this.styleElement with css', css); - let style = this.styleElement; - if (!this.styleElement) { + let style = this.__styleElement; + if (!this.__styleElement) { style = document.createElement('style'); style.type = 'text/css'; style.setAttribute('from', 'style-sheet'); const head = document.head || document.getElementsByTagName('head')[0]; head.appendChild(style); - this.styleElement = style; - this.__debug('this.styleElement is created', this.styleElement); + this.__styleElement = style; + this.__debug('this.styleElement is created', this.__styleElement); } if (style.innerHTML === css) { @@ -459,7 +462,7 @@ export default function baseRendererFactory(): IBaseRenderComponent { const { __appHelper: appHelper, __components: components = {} } = this.props || {}; if (isJSExpression(schema)) { - return this.parseExpression(schema, scope); + return this.__parseExpression(schema, scope); } if (isI18nData(schema)) { return parseI18n(schema, scope); @@ -486,7 +489,7 @@ export default function baseRendererFactory(): IBaseRenderComponent { const _children = this.__getSchemaChildren(schema); // 解析占位组件 if (schema.componentName === 'Fragment' && _children) { - const tarChildren = isJSExpression(_children) ? this.parseExpression(_children, scope) : _children; + const tarChildren = isJSExpression(_children) ? this.__parseExpression(_children, scope) : _children; return this.__createVirtualDom(tarChildren, scope, parentInfo); } @@ -553,7 +556,7 @@ export default function baseRendererFactory(): IBaseRenderComponent { let scopeKey = ''; // 判断组件是否需要生成scope,且只生成一次,挂在this.__compScopes上 if (Comp.generateScope) { - const key = this.parseExpression(schema.props?.key, scope); + const key = this.__parseExpression(schema.props?.key, scope); if (key) { // 如果组件自己设置key则使用组件自己的key scopeKey = key; @@ -704,7 +707,7 @@ export default function baseRendererFactory(): IBaseRenderComponent { children.forEach((child: any) => { const childVirtualDom = this.__createVirtualDom( - isJSExpression(child) ? this.parseExpression(child, scope) : child, + isJSExpression(child) ? this.__parseExpression(child, scope) : child, scope, { schema, @@ -806,7 +809,7 @@ export default function baseRendererFactory(): IBaseRenderComponent { }; if (isJSExpression(props)) { - props = this.parseExpression(props, scope); + props = this.__parseExpression(props, scope); // 只有当变量解析出来为模型结构的时候才会继续解析 if (!isSchema(props) && !isJSSlot(props)) { return checkProps(props); diff --git a/packages/renderer-core/src/renderer/page.tsx b/packages/renderer-core/src/renderer/page.tsx index 41c1a713f..17e3ef4c8 100644 --- a/packages/renderer-core/src/renderer/page.tsx +++ b/packages/renderer-core/src/renderer/page.tsx @@ -40,8 +40,6 @@ export default function pageRendererFactory(): IBaseRenderComponent { this.__bindCustomMethods(this.props); this.__initDataSource(this.props); - // this.__excuteLifeCycleMethod('constructor', arguments); - this.__generateCtx({ page: this, }); diff --git a/packages/renderer-core/tests/renderer/base.test.tsx b/packages/renderer-core/tests/renderer/base.test.tsx index 5d8d24a80..f69688908 100644 --- a/packages/renderer-core/tests/renderer/base.test.tsx +++ b/packages/renderer-core/tests/renderer/base.test.tsx @@ -1,13 +1,31 @@ + +import React, { Component, createElement, PureComponent, createContext } from 'react'; const mockGetRenderers = jest.fn(); +const mockGetRuntime = jest.fn(); +const mockParseExpression = jest.fn(); jest.mock('../../src/adapter', () => { return { - getRenderers: () => { return mockGetRenderers();} + getRenderers: () => { return mockGetRenderers();}, + getRuntime: () => { return mockGetRuntime();}, + }; +}); +jest.mock('../../src/utils', () => { + const originalUtils = jest.requireActual('../../src/utils'); + return { + ...originalUtils, + parseExpression: (...args) => { mockParseExpression(args);}, }; }); -import baseRendererFactory from '../../src/renderer/base'; -describe('Base Render', () => { +import baseRendererFactory from '../../src/renderer/base'; +import { IBaseRendererProps } from '../../src/types'; +import TestRenderer from 'react-test-renderer'; +import components from '../utils/components'; +import schema from '../fixtures/schema/basic'; + + +describe('Base Render factory', () => { it('customBaseRenderer logic works', () => { mockGetRenderers.mockReturnValue({BaseRenderer: {}}); const baseRenderer = baseRendererFactory(); @@ -15,4 +33,190 @@ describe('Base Render', () => { expect(baseRenderer).toStrictEqual({}); mockGetRenderers.mockClear(); }); +}); + +describe('Base Render methods', () => { + let RendererClass; + const mockRendererFactory = () => { + return class extends Component { + constructor(props: IBaseRendererProps, context: any) { + super(props, context); + } + } + } + beforeEach(() => { + const mockRnederers = { + PageRenderer: mockRendererFactory(), + ComponentRenderer: mockRendererFactory(), + BlockRenderer: mockRendererFactory(), + AddonRenderer: mockRendererFactory(), + TempRenderer: mockRendererFactory(), + DivRenderer: mockRendererFactory(), + }; + mockGetRenderers.mockReturnValue(mockRnederers); + mockGetRuntime.mockReturnValue({ + Component, + createElement, + PureComponent, + createContext, + }); + RendererClass = baseRendererFactory(); + }) + + afterEach(() => { + mockGetRenderers.mockClear(); + }) + + it('should excute lifecycle.getDerivedStateFromProps when defined', () => { + const mockGetDerivedStateFromProps = { + type: 'JSFunction', + value: 'function() {\n console.log(\'did mount\');\n }', + }; + const mockSchema = schema; + (mockSchema.lifeCycles as any).getDerivedStateFromProps = mockGetDerivedStateFromProps; + + // const originalUtils = jest.requireActual('../../src/utils'); + // mockParseExpression.mockImplementation(originalUtils.parseExpression); + const component = TestRenderer.create( + // @ts-ignore + ); + // console.log(component.root.props.a); + // component.update(); + // console.log(component.root.props.a); + // expect(mockParseExpression).toHaveBeenCalledWith(mockGetDerivedStateFromProps, expect.anything()) + // test lifecycle.getDerivedStateFromProps is null + + // test lifecycle.getDerivedStateFromProps is JSExpression + + // test lifecycle.getDerivedStateFromProps is JSFunction + + // test lifecycle.getDerivedStateFromProps is function + + }); + + + // it('should excute lifecycle.getSnapshotBeforeUpdate when defined', () => { + // }); + + // it('should excute lifecycle.componentDidMount when defined', () => { + // }); + + // it('should excute lifecycle.componentDidUpdate when defined', () => { + // }); + + // it('should excute lifecycle.componentWillUnmount when defined', () => { + // }); + + // it('should excute lifecycle.componentDidCatch when defined', () => { + // }); + + // it('__excuteLifeCycleMethod should work', () => { + // }); + + // it('reloadDataSource should work', () => { + // }); + + // it('shouldComponentUpdate should work', () => { + // }); + + + // it('_getComponentView should work', () => { + // }); + + // it('__bindCustomMethods should work', () => { + // }); + + // it('__generateCtx should work', () => { + // }); + + // it('__parseData should work', () => { + // }); + + // it('__initDataSource should work', () => { + // }); + + // it('__initI18nAPIs should work', () => { + // }); + + // it('__writeCss should work', () => { + // }); + + // it('__render should work', () => { + // }); + + // it('__getSchemaChildren should work', () => { + // }); + + // it('__createDom should work', () => { + // }); + + // it('__createVirtualDom should work', () => { + // }); + + // it('__componentHoc should work', () => { + // }); + + // it('__getSchemaChildrenVirtualDom should work', () => { + // }); + + // it('__getComponentProps should work', () => { + // }); + + // it('__createLoopVirtualDom should work', () => { + // }); + + // it('__designModeIsDesign should work', () => { + // }); + + // it('__parseProps should work', () => { + // }); + + // it('$ should work', () => { + // }); + + // it('__renderContextProvider should work', () => { + // }); + + // it('__renderContextConsumer should work', () => { + // }); + + // it('__getHocComp should work', () => { + // }); + + // it('__renderComp should work', () => { + // }); + + // it('__renderContent should work', () => { + // }); + + // it('__checkSchema should work', () => { + // }); + + // it('requestHandlersMap should work', () => { + // }); + + // it('utils should work', () => { + // }); + + // it('constants should work', () => { + // }); + + // it('history should work', () => { + // }); + + // it('location should work', () => { + // }); + + // it('match should work', () => { + // }); }); \ No newline at end of file