React / JSX动态组件名称

我试图根据组件的类型动态呈现组件。

例如:

var type = "Example";
var ComponentName = type + "Component";
return <ComponentName />;
// Returns <examplecomponent />  instead of <ExampleComponent />

我尝试了这里提出的解决方案React/JSX动态组件名称

这在编译时给了我一个错误(使用browserify for gulp)。当我使用数组语法时,它期望XML。

我可以通过为每个组件创建一个方法来解决这个问题:

newExampleComponent() {
return <ExampleComponent />;
}


newComponent(type) {
return this["new" + type + "Component"]();
}

但这意味着我创建的每个组件都有一个新方法。这个问题一定有更优雅的解决办法。

我很愿意接受建议。

239074 次浏览

我想出了一个新的解决办法。请注意,我使用ES6模块,所以我需要类。你也可以定义一个新的React类。

var components = {
example: React.createFactory( require('./ExampleComponent') )
};


var type = "example";


newComponent() {
return components[type]({ attribute: "value" });
}

<MyComponent />编译为React.createElement(MyComponent, {}),它需要一个字符串(HTML标记)或一个函数(ReactClass)作为第一个参数。

您可以将组件类存储在一个名称以大写字母开头的变量中。看到HTML标签vs React组件

var MyComponent = Components[type + "Component"];
return <MyComponent />;

编译,

var MyComponent = Components[type + "Component"];
return React.createElement(MyComponent, {});
如果你的组件是全局的,你可以简单地做:
var nameOfComponent = "SomeComponent";
React.createElement(window[nameOfComponent], {});

我使用了一点不同的方法,因为我们总是知道我们的实际组件,所以我认为应用开关情况。 此外,在我的情况下,组件总数在7-8左右

getSubComponent(name) {
let customProps = {
"prop1" :"",
"prop2":"",
"prop3":"",
"prop4":""
}


switch (name) {
case "Component1": return <Component1 {...this.props} {...customProps} />
case "Component2": return <Component2 {...this.props} {...customProps} />
case "component3": return <component3 {...this.props} {...customProps} />


}
}

编辑:其他答案更好,见评论。

我用这种方法解决了同样的问题:

...
render : function () {
var componentToRender = 'component1Name';
var componentLookup = {
component1Name : (<Component1 />),
component2Name : (<Component2 />),
...
};
return (<div>
{componentLookup[componentToRender]}
</div>);
}
...

对于包装器组件,一个简单的解决方案是直接使用React.createElement(使用ES6)。

import RaisedButton from 'mui/RaisedButton'
import FlatButton from 'mui/FlatButton'
import IconButton from 'mui/IconButton'


class Button extends React.Component {
render() {
const { type, ...props } = this.props


let button = null
switch (type) {
case 'flat': button = FlatButton
break
case 'icon': button = IconButton
break
default: button = RaisedButton
break
}


return (
React.createElement(button, { ...props, disableTouchRipple: true, disableFocusRipple: true })
)
}
}

这里有一个关于如何处理这种情况的官方文档:https://facebook.github.io/react/docs/jsx-in-depth.html#choosing-the-type-at-runtime

基本上它说:

错误的:

import React from 'react';
import { PhotoStory, VideoStory } from './stories';


const components = {
photo: PhotoStory,
video: VideoStory
};


function Story(props) {
// Wrong! JSX type can't be an expression.
return <components[props.storyType] story={props.story} />;
}

正确的:

import React from 'react';
import { PhotoStory, VideoStory } from './stories';


const components = {
photo: PhotoStory,
video: VideoStory
};


function Story(props) {
// Correct! JSX type can be a capitalized variable.
const SpecificStory = components[props.storyType];
return <SpecificStory story={props.story} />;
}

假设我们希望通过动态组件加载访问各种视图。下面的代码给出了一个工作示例,说明如何通过使用从url的搜索字符串解析出来的字符串来实现这一点。

让我们假设我们想要访问一个页面'snozberrys'有两个独特的视图使用这些url路径:

'http://localhost:3000/snozberrys?aComponent'

而且

'http://localhost:3000/snozberrys?bComponent'

我们像这样定义视图控制器:

import React, { Component } from 'react';
import ReactDOM from 'react-dom'
import {
BrowserRouter as Router,
Route
} from 'react-router-dom'
import AComponent from './AComponent.js';
import CoBComponent sole from './BComponent.js';


const views = {
aComponent: <AComponent />,
console: <BComponent />
}


const View = (props) => {
let name = props.location.search.substr(1);
let view = views[name];
if(view == null) throw "View '" + name + "' is undefined";
return view;
}


class ViewManager extends Component {
render() {
return (
<Router>
<div>
<Route path='/' component={View}/>
</div>
</Router>
);
}
}


export default ViewManager


ReactDOM.render(<ViewManager />, document.getElementById('root'));

应该有一个容器将组件名称映射到所有应该动态使用的组件。组件类应该注册在容器中,因为在模块化环境中,没有单独的地方可以访问它们。如果不显式地指定组件类,就不能通过它们的名称来标识它们,因为name函数在生产中被简化了。

组件图

它可以是plain object:

class Foo extends React.Component { ... }
...
const componentsMap = { Foo, Bar };
...
const componentName = 'Fo' + 'o';
const DynamicComponent = componentsMap[componentName];
<DynamicComponent/>;

Map实例:

const componentsMap = new Map([[Foo, Foo], [Bar, Bar]]);
...
const DynamicComponent = componentsMap.get(componentName);

普通对象更适合,因为它受益于属性简写。

桶模块

带有命名导出的桶模块可以充当这样的映射:

// Foo.js
export class Foo extends React.Component { ... }


// dynamic-components.js
export * from './Foo';
export * from './Bar';


// some module that uses dynamic component
import * as componentsMap from './dynamic-components';


const componentName = 'Fo' + 'o';
const DynamicComponent = componentsMap[componentName];
<DynamicComponent/>;

如果每个模块代码风格有一个类,效果会很好。

装饰

装饰器可以用于类组件的语法糖,这仍然需要显式地指定类名并将它们注册到map中:

const componentsMap = {};


function dynamic(Component) {
if (!Component.displayName)
throw new Error('no name');


componentsMap[Component.displayName] = Component;


return Component;
}


...


@dynamic
class Foo extends React.Component {
static displayName = 'Foo'
...
}

装饰器可以作为功能组件的高阶组件使用:

const Bar = props => ...;
Bar.displayName = 'Bar';


export default dynamic(Bar);

使用非标准displayName而不是random属性也有利于调试。

假设我们有一个flag,与stateprops没有区别:

import ComponentOne from './ComponentOne';
import ComponentTwo from './ComponentTwo';


~~~


const Compo = flag ? ComponentOne : ComponentTwo;


~~~


<Compo someProp={someValue} />

通过标记Compo,用ComponentOneComponentTwo中的一个填充,然后Compo可以像React组件一样工作。

在所有的组件映射选项中,我还没有找到使用ES6短语法定义映射的最简单的方法:

import React from 'react'
import { PhotoStory, VideoStory } from './stories'


const components = {
PhotoStory,
VideoStory,
}


function Story(props) {
//given that props.story contains 'PhotoStory' or 'VideoStory'
const SpecificStory = components[props.story]
return <SpecificStory/>
}

拥有一个带有大量组件的地图并不好看。事实上,我很惊讶居然没有人提出这样的建议:

var componentName = "StringThatContainsComponentName";
const importedComponentModule = require("path/to/component/" + componentName).default;
return React.createElement(importedComponentModule);

当我需要以json数组的形式加载相当多的组件时,这一个真的帮助了我。

随着React.lazy的引入,我们现在可以使用真正的动态方法来导入组件并呈现它。

import React, { lazy, Suspense } from 'react';


const App = ({ componentName, ...props }) => {
const DynamicComponent = lazy(() => import(`./${componentName}`));
    

return (
<Suspense fallback={<div>Loading...</div>}>
<DynamicComponent {...props} />
</Suspense>
);
};

当然,这种方法对文件层次结构做了一些假设,并且可以使代码很容易被破坏。

假设你能够像这样从组件中导出*…

// src/components/index.js


export * from './Home'
export * from './Settings'
export * from './SiteList'


然后你可以重新导入*到一个新的comps对象中,然后它可以用来访问你的模块。

// src/components/DynamicLoader.js


import React from 'react'
import * as comps from 'components'


export default function ({component, defaultProps}) {
const DynamicComponent = comps[component]


return <DynamicComponent {...defaultProps} />
}

只需传入一个字符串值,该值标识您想要绘制的组件,以及需要绘制组件的位置。

<DynamicLoader component='Home' defaultProps={someProps} />