import difference from 'lodash/difference'; import omit from 'lodash/omit'; import React from 'react'; import LazyComponent from './components/LazyComponent'; import { filterSchema, loadRenderer, RendererConfig, RendererEnv, RendererProps, resolveRenderer } from './factory'; import {renderChild, renderChildren} from './Root'; import {Schema, SchemaNode} from './types'; import {anyChanged, chainEvents} from './utils/helper'; interface SchemaRendererProps extends Partial { schema: Schema; $path: string; env: RendererEnv; } const defaultOmitList = [ 'type', 'name', '$ref', 'className', 'data', 'children', 'ref', 'visible', 'visibleOn', 'hidden', 'hiddenOn', 'disabled', 'disabledOn', 'component', 'detectField', 'required', 'requiredOn', 'syncSuperStore' ]; export class SchemaRenderer extends React.Component { static displayName: string = 'Renderer'; renderer: RendererConfig | null; ref: any; schema: any; path: string; constructor(props: SchemaRendererProps) { super(props); this.refFn = this.refFn.bind(this); this.renderChild = this.renderChild.bind(this); this.reRender = this.reRender.bind(this); } componentWillMount() { this.resolveRenderer(this.props); } componentWillReceiveProps(nextProps: SchemaRendererProps) { const props = this.props; if ( props.schema && nextProps.schema && (props.schema.type !== nextProps.schema.type || props.schema.$$id !== nextProps.schema.$$id) ) { this.resolveRenderer(nextProps); } } // 限制:只有 schema 除外的 props 变化,或者 schema 里面的某个成员值发生变化才更新。 shouldComponentUpdate(nextProps: SchemaRendererProps) { const props = this.props; const list: Array = difference(Object.keys(nextProps), [ 'schema', 'scope' ]); if ( difference(Object.keys(props), ['schema', 'scope']).length !== list.length || anyChanged(list, this.props, nextProps) ) { return true; } else { const list: Array = Object.keys(nextProps.schema); if ( Object.keys(props.schema).length !== list.length || anyChanged(list, props.schema, nextProps.schema) ) { return true; } } return false; } resolveRenderer(props: SchemaRendererProps, skipResolve = false): any { let schema = props.schema; let path = props.$path; if (schema && schema.$ref) { schema = { ...props.resolveDefinitions(schema.$ref), ...schema }; path = path.replace(/(?!.*\/).*/, schema.type); } if (!skipResolve) { const rendererResolver = props.env.rendererResolver || resolveRenderer; this.renderer = rendererResolver(path, schema, props); } return {path, schema}; } getWrappedInstance() { return this.ref; } refFn(ref: any) { this.ref = ref; } renderChild( region: string, node?: SchemaNode, subProps: { data?: object; [propName: string]: any; } = {} ) { let {schema, $path, env, ...rest} = this.props; if (schema && schema.$ref) { const result = this.resolveRenderer(this.props, true); schema = result.schema; $path = result.path; } const omitList = defaultOmitList.concat(); if (this.renderer) { const Component = this.renderer.component; Component.propsList && omitList.push.apply(omitList, Component.propsList as Array); } return renderChild(`${$path}${region ? `/${region}` : ''}`, node || '', { ...omit(rest, omitList), ...subProps, data: subProps.data || rest.data, env: env }); } reRender() { this.resolveRenderer(this.props); this.forceUpdate(); } render(): JSX.Element | null { let {$path, schema, ...rest} = this.props; if (schema === null) { return null; } if (schema.$ref) { const result = this.resolveRenderer(this.props, true); schema = result.schema; $path = result.path; } const theme = this.props.env.theme; if (Array.isArray(schema)) { return renderChildren($path, schema as any, rest) as JSX.Element; } else if (schema.children) { return React.isValidElement(schema.children) ? schema.children : (schema.children as Function)({ ...rest, $path: $path, $schema: schema, render: this.renderChild, forwardedRef: this.refFn }); } else if (typeof schema.component === 'function') { const isSFC = !(schema.component.prototype instanceof React.Component); return React.createElement(schema.component as any, { ...rest, ...schema, $path: $path, $schema: schema, ref: isSFC ? undefined : this.refFn, forwardedRef: isSFC ? this.refFn : undefined, render: this.renderChild }); } else if (Object.keys(schema).length === 0) { return null; } else if (!this.renderer) { return ( { const result = await rest.env.loadRenderer( schema, $path, this.reRender ); if (result && typeof result === 'function') { return result; } else if (result && React.isValidElement(result)) { return () => result; } this.reRender(); return () => loadRenderer(schema, $path); }} $path={$path} $schema={schema} retry={this.reRender} /> ); } const renderer = this.renderer as RendererConfig; schema = filterSchema(schema, renderer, rest); const {data: defaultData, ...restSchema} = schema; const Component = renderer.component; return ( ); } }