React:是否可以渲染使用“ new”创建的组件对象?

时间:2019-06-23 14:41:26

标签: reactjs

我正在构建一个应用程序,用户可以在其中动态更改页面的布局。有两个接口。它们在一起提供了可用于构建布局的组件的集合:

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一起使用,则提供的解决方案将被完全破坏。这是因为渲染的组件和原始组件是不同的对象。如果在原始对象上设置可观察对象,那么它将对渲染的对象没有任何影响。有一个非常混乱的解决方法,但可能不值得。

1 个答案:

答案 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} />;