2024-07-03 11:16:40 +08:00

151 lines
4.3 KiB
TypeScript

import { invariant, isLowCodeComponentPackage, type Spec } from '@alilc/lowcode-shared';
import { forwardRef, useRef, useEffect } from 'react';
import { isValidElementType } from 'react-is';
import { useRendererContext } from '../api/context';
import { reactiveStateFactory } from './reactiveState';
import { type ReactComponent, type ReactWidget, createElementByWidget } from './elements';
import { appendExternalStyle } from '../utils/element';
import type {
RenderContext,
IComponentTreeModel,
CreateComponentTreeModelOptions,
} from '@alilc/lowcode-renderer-core';
import type { ReactInstance, CSSProperties, ForwardedRef, ReactNode } from 'react';
export interface ComponentOptions {
displayName?: string;
modelOptions?: Pick<CreateComponentTreeModelOptions, 'id' | 'metadata'>;
beforeElementCreate?(widget: ReactWidget): ReactWidget;
elementCreated?(widget: ReactWidget, element: ReactNode): ReactNode;
componentRefAttached?(widget: ReactWidget, instance: ReactInstance | null): void;
}
export interface LowCodeComponentProps {
id?: string;
/** CSS 类名 */
className?: string;
/** style */
style?: CSSProperties;
[key: string]: any;
}
const lowCodeComponentsCache = new Map<string, ReactComponent>();
export function getComponentByName(
name: string,
{ packageManager }: RenderContext,
componentOptions: ComponentOptions = {},
): ReactComponent {
const result = lowCodeComponentsCache.get(name) || packageManager.getComponent(name);
if (isLowCodeComponentPackage(result)) {
const { schema, ...metadata } = result;
const lowCodeComponent = createComponent(schema, {
...componentOptions,
displayName: name,
modelOptions: {
id: metadata.id,
metadata,
},
});
lowCodeComponentsCache.set(name, lowCodeComponent);
return lowCodeComponent;
}
invariant(isValidElementType(result), `${name} must be a React Component`);
return result;
}
export function createComponent(
schema: string | Spec.ComponentTreeRoot,
componentOptions: ComponentOptions = {},
) {
const { displayName = '__LowCodeComponent__', modelOptions } = componentOptions;
const LowCodeComponent = forwardRef(function (
props: LowCodeComponentProps,
ref: ForwardedRef<any>,
) {
const context = useRendererContext();
const { options: globalOptions, componentTreeModel } = context;
const modelRef = useRef<IComponentTreeModel<ReactComponent, ReactInstance>>();
if (!modelRef.current) {
const finalOptions: CreateComponentTreeModelOptions = {
...modelOptions,
codeScopeValue: {
props,
},
stateCreator: reactiveStateFactory,
dataSourceCreator: globalOptions.dataSourceCreator,
};
if (typeof schema === 'string') {
modelRef.current = componentTreeModel.createById(schema, finalOptions);
} else {
modelRef.current = componentTreeModel.create(schema, finalOptions);
}
console.log(
'%c [ model ]-103',
'font-size:13px; background:pink; color:#bf2c9f;',
modelRef.current,
);
}
const model = modelRef.current!;
const isConstructed = useRef(false);
const isMounted = useRef(false);
if (!isConstructed.current) {
model.triggerLifeCycle('constructor');
isConstructed.current = true;
const cssText = model.getCssText();
if (cssText) {
appendExternalStyle(cssText, { id: model.id });
}
}
useEffect(() => {
// trigger lifeCycles
// componentDidMount?.();
model.triggerLifeCycle('componentDidMount');
// 当 state 改变之后调用
// const unwatch = watch(scopeValue.state, (_, oldVal) => {
// if (isMounted.current) {
// model.triggerLifeCycle('componentDidUpdate', props, oldVal);
// }
// });
isMounted.current = true;
return () => {
// componentWillUnmount?.();
model.triggerLifeCycle('componentWillUnmount');
// unwatch();
isMounted.current = false;
};
}, []);
return (
<div id={props.id} className={props.className} style={props.style} ref={ref}>
{model.widgets.map((w) => createElementByWidget(w, w.model.codeRuntime, componentOptions))}
</div>
);
});
LowCodeComponent.displayName = displayName;
return LowCodeComponent;
}