我正在构建一个应用程序,用户可以在其中动态更改页面的布局。有两个接口。它们在一起提供了可用于构建布局的组件的集合:
export interface IComponentProvider {
key : string;
displaylabel : string;
description ?: string;
render(): React.ReactNode;
}
export interface IComponentCollection {
forEach(callbackfn: (value: IComponentProvider, key: string) => void, thisArg?: any): void;
get(key: string): IComponentProvider | undefined;
has(key: string): boolean;
readonly size: number;
}
注意:
IComponentProvider
可以由React.Component
实现。它实际上是具有键和displaylabel的特殊组件。该键用于标识集合中的组件。 displaylabel用于为其指定一个友好名称。 (因此,用户将看到“用户详细信息”,而不是“ userDetailPanelId132”。)Map<string, React.Component>
实现IComponentCollection
。实际上,在大多数情况下,我将使用Map来实现它。应使用两个类来渲染布局。有一个DynamicLayoutConfiguration
类,用于定义布局的结构。它存储组件类型和组件引用的树。例如:
{
type: "tabs"
items: [
{type:"tab", title:"First tab",
items:[
{
type:"component",
key:"masterComponent1"}
]},
{type:"tab", title:"Second tab",
items:[
{
type:"component",
key:"detailPanel1"}
]},
]
}
布局配置不是组件,并且不包含实际的组件。只有它们是这些组件的键。可以保存/加载配置。这为布局提供了持久性。
最后一部分是DynamicLayout
组件,它获取集合和配置,并且应该能够呈现布局:
interface IDynamicLayoutProps {
configuration: DynamicLayoutConfiguration;
collection: IComponentCollection;
}
export class DynamicLayout extends React.PureComponent<IDynamicLayoutProps> {
public render() {
/* Here we should render, using this.props.configuration and this.props.components */
return null; // ???
}
}
上面的示例配置应呈现如下内容:
<Tabs>
<TabList>
<Tab>First tab</Tab>
<Tab>Second tab</Tab>
</TabList>
<TabPanel>{this.props.components.get('masterComponent1').render()}</TabPanel>
<TabPanel>{this.props.components.get('detailPanel1').render()}</TabPanel>
</Tabs>
为什么我需要这样做:因为必须以各种方式将相同的数据呈现给用户,并且我希望用户能够定义自己的布局。有一个单独的布局编辑器,用户可以在其中创建具有容器,行,列,选项卡,手风琴等的UI组件树结构,并且可以将组件插入到此布局中(来自组件集合)。
问题来了。显然,需要在用户创建布局配置之前创建组件集合。例如,如果我有一个名为DetailPanel
的组件:
export class DetailPanel extends React.Component<...>{...}
然后从理论上讲,我可以这样做:
const components = new Map<string,IComponentProvider>();
const detailPanel = new DetailPanel(detailPanelProps);
components.set(detailPanel.key, detailPanel);
然后为用户打开布局编辑器,以便在其中可以使用DetailPanel(如果需要)。但是请注意,DetailPanel
是使用new关键字创建的。
如何渲染以这种方式创建的组件?显然,如果我只是从render方法返回该对象,那么我将收到“对象作为反应子无效”错误。我试图返回render()的结果:
<TabPanel>
{this.props.components.get(componentKey).render()}
</TabPanel>
尽管这确实渲染了组件,但它既不调用componentDidMount
也不componentWillUnmount
,但通常它是有缺陷的,无法使用。
我认为我不应该使用new DetailPanel()
手动创建这些组件。
但是我不知道还有什么呢?如何在不创建组件的情况下创建常规的“组件集合”?如果必须手动创建它们,那么如何正确渲染它们?
更新:正如@Berouminum指出的那样,没有好的解决方案。一个人只能用JSX或React.createElement渲染元素。只有在需要时,这些对象中的任何一个都不会立即真正创建对象。这意味着要么我用React.createElement创建它们,然后就无法访问key和displaylabel属性,要么无法使用new
关键字创建它们(正常的对象构造),但是我将无法正确渲染它们。
到目前为止,我发现的最佳解决方法是将IComponentProvider更改为具有“ createElement”方法而不是render方法:
export interface IComponentProvider {
key : string;
displaylabel : string;
description ?: string;
createElement(): React.ReactElement;
}
任何希望实现此接口的组件,都可以轻松实现createElement方法:
class ExampleComponent extends React.Component<IProps,IState> impements IComponentProvider {
public get key() { return this.calculateKey(); }
public get displaylabel() { return this.calculateDisplaylabel(); }
public get description() { return this.calculateDescription(); }
public createElement() {
return React.createElement(ExampleComponent,this.props, null);
}
}
使用此功能,可以使用键/ displaylabel属性,在对象上调用方法并正确呈现它:
const exampleComponent = new ExampleComponent(props);
/* It is possible to use its properties here. */
components.set(exampleComponent.key, exampleComponent);
/* It is also possible to render it PROPERLY, all lifecycle methods called correctly. */
class SomeContainer extends React.Component<...> {
...
public render() {
return <div>
....
{exampleComponent.createElement()}
...
</div>;
}
当然,该解决方案也是错误的,因为对象将被多次构造(一次使用new关键字手动构造,并且可能在需要该元素时由React多次构造)。因此,渲染的对象实际上将是一个不同的实例。但是到目前为止,这是我发现的最佳解决方法。它可重复使用且足够通用。
更新2 :如果我尝试将其与MobX一起使用,则提供的解决方案将被完全破坏。这是因为渲染的组件和原始组件是不同的对象。如果在原始对象上设置可观察对象,那么它将对渲染的对象没有任何影响。有一个非常混乱的解决方法,但可能不值得。
答案 0 :(得分:1)
在React中,有两种实例化组件的方法。
通过JSX
CURRENT_HEROKU_USER=chap@heroku.com rails c by user chap@heroku.com
通过<timer-interceptor>
<flow name="dzone-mule-appsFlow">
<!-- inbound and your components -->
<timer-interceptor doc:name="Timer Interceptor"/>
<!-- your components -->
</flow>
此后,可以通过从render函数返回它来渲染所创建的组件(就像其他任何元素一样)。您不应该尝试手动调用渲染。
我为您建立了一个小例子。您可以通过这种方式呈现无功能组件和类组件。 ComponentDidMount也被称为
const Comp = <Component prop1={prop1} prop2={prop2} />;