refactor: abstract excuteLifeCycleMethod and some minor refactors

This commit is contained in:
JackLian 2022-07-13 11:52:38 +08:00 committed by 刘菊萍(絮黎)
parent 6399cce05a
commit 706dad4aa5
4 changed files with 262 additions and 57 deletions

View File

@ -10,7 +10,7 @@ const jestConfig = {
// // '^.+\\.(js|jsx)$': 'babel-jest', // // '^.+\\.(js|jsx)$': 'babel-jest',
// }, // },
// testMatch: ['(/tests?/.*(test))\\.[jt]s$'], // testMatch: ['(/tests?/.*(test))\\.[jt]s$'],
// testMatch: ['**/*/common.test.ts'], // testMatch: ['**/*/base.test.tsx'],
transformIgnorePatterns: [ transformIgnorePatterns: [
`/node_modules/(?!${esModules})/`, `/node_modules/(?!${esModules})/`,
], ],

View File

@ -35,6 +35,38 @@ import { IComponentConstruct, IComponentHoc, leafWrapper } from '../hoc/leaf';
import logger from '../utils/logger'; import logger from '../utils/logger';
import isUseLoop from '../utils/is-use-loop'; 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 { export default function baseRendererFactory(): IBaseRenderComponent {
const { BaseRenderer: customBaseRenderer } = adapter.getRenderers(); const { BaseRenderer: customBaseRenderer } = adapter.getRenderers();
@ -72,32 +104,33 @@ export default function baseRendererFactory(): IBaseRenderComponent {
static contextType = AppContext; static contextType = AppContext;
__namespace = 'base';
appHelper?: IRendererAppHelper; appHelper?: IRendererAppHelper;
i18n: any;
getLocale: any;
setLocale: any;
dataSourceMap: Record<string, any> = {};
__namespace = 'base';
__compScopes: Record<string, any> = {}; __compScopes: Record<string, any> = {};
__instanceMap: Record<string, any> = {}; __instanceMap: Record<string, any> = {};
__dataHelper: any; __dataHelper: any;
__customMethodsList: any[] = []; __customMethodsList: any[] = [];
dataSourceMap: Record<string, any> = {}; __parseExpression: any;
__ref: any; __ref: any;
i18n: any;
getLocale: any;
setLocale: any;
/** /**
* reference of style element contains schema.css * reference of style element contains schema.css
* *
* @type {any} * @type {any}
*/ */
styleElement: any; __styleElement: any;
parseExpression: any;
[key: string]: any; [key: string]: any;
constructor(props: IBaseRendererProps, context: IBaseRendererContext) { constructor(props: IBaseRendererProps, context: IBaseRendererContext) {
super(props, context); super(props, context);
this.context = context; this.context = context;
this.parseExpression = props?.thisRequiredInJSE ? parseThisRequiredExpression : parseExpression; this.__parseExpression = props?.thisRequiredInJSE ? parseThisRequiredExpression : parseExpression;
this.__beforeInit(props); this.__beforeInit(props);
this.__init(props); this.__init(props);
this.__afterInit(props); this.__afterInit(props);
@ -119,21 +152,7 @@ export default function baseRendererFactory(): IBaseRenderComponent {
__afterInit(_props: IBaseRendererProps) { } __afterInit(_props: IBaseRendererProps) { }
static getDerivedStateFromProps(props: IBaseRendererProps, state: any) { static getDerivedStateFromProps(props: IBaseRendererProps, state: any) {
logger.log('getDerivedStateFromProps'); return excuteLifeCycleMethod(this, props?.__schema, 'getDerivedStateFromProps', [props, state], props.thisRequiredInJSE);
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;
} }
async getSnapshotBeforeUpdate(...args: any[]) { async getSnapshotBeforeUpdate(...args: any[]) {
@ -198,23 +217,7 @@ export default function baseRendererFactory(): IBaseRenderComponent {
* @PRIVATE * @PRIVATE
*/ */
__excuteLifeCycleMethod = (method: string, args?: any) => { __excuteLifeCycleMethod = (method: string, args?: any) => {
const lifeCycleMethods = getValue(this.props.__schema, 'lifeCycles', {}); excuteLifeCycleMethod(this, this.props.__schema, method, args, this.props.thisRequiredInJSE);
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);
}
}
}; };
/** /**
@ -242,7 +245,7 @@ export default function baseRendererFactory(): IBaseRenderComponent {
forEach(__schema.methods, (val: any, key: string) => { forEach(__schema.methods, (val: any, key: string) => {
let value = val; let value = val;
if (isJSExpression(value) || isJSFunction(value)) { if (isJSExpression(value) || isJSFunction(value)) {
value = this.parseExpression(value, this); value = this.__parseExpression(value, this);
} }
if (typeof value !== 'function') { if (typeof value !== 'function') {
console.error(`自定义函数${key}类型不符`, value); console.error(`自定义函数${key}类型不符`, value);
@ -351,16 +354,16 @@ export default function baseRendererFactory(): IBaseRenderComponent {
__writeCss = (props: IBaseRendererProps) => { __writeCss = (props: IBaseRendererProps) => {
const css = getValue(props.__schema, 'css', ''); const css = getValue(props.__schema, 'css', '');
this.__debug('create this.styleElement with css', css); this.__debug('create this.styleElement with css', css);
let style = this.styleElement; let style = this.__styleElement;
if (!this.styleElement) { if (!this.__styleElement) {
style = document.createElement('style'); style = document.createElement('style');
style.type = 'text/css'; style.type = 'text/css';
style.setAttribute('from', 'style-sheet'); style.setAttribute('from', 'style-sheet');
const head = document.head || document.getElementsByTagName('head')[0]; const head = document.head || document.getElementsByTagName('head')[0];
head.appendChild(style); head.appendChild(style);
this.styleElement = style; this.__styleElement = style;
this.__debug('this.styleElement is created', this.styleElement); this.__debug('this.styleElement is created', this.__styleElement);
} }
if (style.innerHTML === css) { if (style.innerHTML === css) {
@ -459,7 +462,7 @@ export default function baseRendererFactory(): IBaseRenderComponent {
const { __appHelper: appHelper, __components: components = {} } = this.props || {}; const { __appHelper: appHelper, __components: components = {} } = this.props || {};
if (isJSExpression(schema)) { if (isJSExpression(schema)) {
return this.parseExpression(schema, scope); return this.__parseExpression(schema, scope);
} }
if (isI18nData(schema)) { if (isI18nData(schema)) {
return parseI18n(schema, scope); return parseI18n(schema, scope);
@ -486,7 +489,7 @@ export default function baseRendererFactory(): IBaseRenderComponent {
const _children = this.__getSchemaChildren(schema); const _children = this.__getSchemaChildren(schema);
// 解析占位组件 // 解析占位组件
if (schema.componentName === 'Fragment' && _children) { 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); return this.__createVirtualDom(tarChildren, scope, parentInfo);
} }
@ -553,7 +556,7 @@ export default function baseRendererFactory(): IBaseRenderComponent {
let scopeKey = ''; let scopeKey = '';
// 判断组件是否需要生成scope且只生成一次挂在this.__compScopes上 // 判断组件是否需要生成scope且只生成一次挂在this.__compScopes上
if (Comp.generateScope) { if (Comp.generateScope) {
const key = this.parseExpression(schema.props?.key, scope); const key = this.__parseExpression(schema.props?.key, scope);
if (key) { if (key) {
// 如果组件自己设置key则使用组件自己的key // 如果组件自己设置key则使用组件自己的key
scopeKey = key; scopeKey = key;
@ -704,7 +707,7 @@ export default function baseRendererFactory(): IBaseRenderComponent {
children.forEach((child: any) => { children.forEach((child: any) => {
const childVirtualDom = this.__createVirtualDom( const childVirtualDom = this.__createVirtualDom(
isJSExpression(child) ? this.parseExpression(child, scope) : child, isJSExpression(child) ? this.__parseExpression(child, scope) : child,
scope, scope,
{ {
schema, schema,
@ -806,7 +809,7 @@ export default function baseRendererFactory(): IBaseRenderComponent {
}; };
if (isJSExpression(props)) { if (isJSExpression(props)) {
props = this.parseExpression(props, scope); props = this.__parseExpression(props, scope);
// 只有当变量解析出来为模型结构的时候才会继续解析 // 只有当变量解析出来为模型结构的时候才会继续解析
if (!isSchema(props) && !isJSSlot(props)) { if (!isSchema(props) && !isJSSlot(props)) {
return checkProps(props); return checkProps(props);

View File

@ -40,8 +40,6 @@ export default function pageRendererFactory(): IBaseRenderComponent {
this.__bindCustomMethods(this.props); this.__bindCustomMethods(this.props);
this.__initDataSource(this.props); this.__initDataSource(this.props);
// this.__excuteLifeCycleMethod('constructor', arguments);
this.__generateCtx({ this.__generateCtx({
page: this, page: this,
}); });

View File

@ -1,13 +1,31 @@
import React, { Component, createElement, PureComponent, createContext } from 'react';
const mockGetRenderers = jest.fn(); const mockGetRenderers = jest.fn();
const mockGetRuntime = jest.fn();
const mockParseExpression = jest.fn();
jest.mock('../../src/adapter', () => { jest.mock('../../src/adapter', () => {
return { 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', () => { it('customBaseRenderer logic works', () => {
mockGetRenderers.mockReturnValue({BaseRenderer: {}}); mockGetRenderers.mockReturnValue({BaseRenderer: {}});
const baseRenderer = baseRendererFactory(); const baseRenderer = baseRendererFactory();
@ -15,4 +33,190 @@ describe('Base Render', () => {
expect(baseRenderer).toStrictEqual({}); expect(baseRenderer).toStrictEqual({});
mockGetRenderers.mockClear(); 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
<RendererClass
__schema={mockSchema}
components={components as any}
thisRequiredInJSE={false}
a='1'
/>);
// console.log(component.root.props.a);
// component.update(<RendererClasssnippets
// schema={mockSchema}
// components={components as any}
// thisRequiredInJSE={false}
// a='2'
// />);
// 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', () => {
// });
}); });