React / JSX 动态组件名称
React / JSX Dynamic Component Name
我正在尝试根据组件的类型动态呈现组件。
例如:
var type = "Example";
var ComponentName = type + "Component";
return <ComponentName />;
// Returns <examplecomponent /> instead of <ExampleComponent />
我尝试了此处提出的解决方案
编译时出现错误(对 gulp 使用 browserify)。它期望 XML 我在其中使用数组语法。
我可以通过为每个组件创建一个方法来解决这个问题:
newExampleComponent() {
return <ExampleComponent />;
}
newComponent(type) {
return this["new" + type + "Component"]();
}
但这意味着我创建的每个组件都有一个新方法。这个问题一定有更优雅的解决方案。
我很乐意接受建议。
我想出了一个新的解决方案。请注意,我使用的是 ES6 模块,所以我需要 class。您也可以定义一个新的 React class。
var components = {
example: React.createFactory( require('./ExampleComponent') )
};
var type = "example";
newComponent() {
return components[type]({ attribute: "value" });
}
<MyComponent />
编译为 React.createElement(MyComponent, {})
,它需要一个字符串(HTML 标签)或一个函数(ReactClass)作为第一个参数。
您可以将组件 class 存储在名称以大写字母开头的变量中。参见 HTML tags vs React Components。
var MyComponent = Components[type + "Component"];
return <MyComponent />;
编译为
var MyComponent = Components[type + "Component"];
return React.createElement(MyComponent, {});
如果您的组件是全局的,您可以简单地执行以下操作:
var nameOfComponent = "SomeComponent";
React.createElement(window[nameOfComponent], {});
我使用了一些不同的方法,因为我们总是知道我们的实际组件,所以我想应用 switch case。
在我的案例中,组件的总数也约为 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} />;
}
假设我们希望使用动态组件 loading.The 访问各种视图,以下代码给出了一个工作示例,说明如何使用从 url 的搜索字符串解析的字符串来实现此目的。
假设我们要使用这些 url 路径访问具有两个独特视图的页面 'snozberrys':
'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'));
应该有一个容器将组件名称映射到应该动态使用的所有组件。组件 classes 应该在容器中注册,因为在模块化环境中,没有其他地方可以访问它们。组件 classes 在没有明确指定的情况下无法通过它们的名称来识别,因为函数 name
在生产中被缩小了。
分量图
可以是普通对象:
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);
普通对象更合适,因为它受益于 属性 shorthand.
桶形模块
具有命名导出的 barrel module 可以用作这样的映射:
// 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/>;
这适用于每个模块代码样式一个 class。
装饰器
装饰器可以与 class 组件一起使用作为语法糖,这仍然需要明确指定 class 名称并将它们注册到映射中:
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);
使用 non-standard displayName
而不是随机 属性 也有利于调试。
假设我们有一个 flag
,与 state
或 props
没有区别:
import ComponentOne from './ComponentOne';
import ComponentTwo from './ComponentTwo';
~~~
const Compo = flag ? ComponentOne : ComponentTwo;
~~~
<Compo someProp={someValue} />
使用标志 Compo
填充 ComponentOne
或 ComponentTwo
之一,然后 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} />
我正在尝试根据组件的类型动态呈现组件。
例如:
var type = "Example";
var ComponentName = type + "Component";
return <ComponentName />;
// Returns <examplecomponent /> instead of <ExampleComponent />
我尝试了此处提出的解决方案
编译时出现错误(对 gulp 使用 browserify)。它期望 XML 我在其中使用数组语法。
我可以通过为每个组件创建一个方法来解决这个问题:
newExampleComponent() {
return <ExampleComponent />;
}
newComponent(type) {
return this["new" + type + "Component"]();
}
但这意味着我创建的每个组件都有一个新方法。这个问题一定有更优雅的解决方案。
我很乐意接受建议。
我想出了一个新的解决方案。请注意,我使用的是 ES6 模块,所以我需要 class。您也可以定义一个新的 React class。
var components = {
example: React.createFactory( require('./ExampleComponent') )
};
var type = "example";
newComponent() {
return components[type]({ attribute: "value" });
}
<MyComponent />
编译为 React.createElement(MyComponent, {})
,它需要一个字符串(HTML 标签)或一个函数(ReactClass)作为第一个参数。
您可以将组件 class 存储在名称以大写字母开头的变量中。参见 HTML tags vs React Components。
var MyComponent = Components[type + "Component"];
return <MyComponent />;
编译为
var MyComponent = Components[type + "Component"];
return React.createElement(MyComponent, {});
如果您的组件是全局的,您可以简单地执行以下操作:
var nameOfComponent = "SomeComponent";
React.createElement(window[nameOfComponent], {});
我使用了一些不同的方法,因为我们总是知道我们的实际组件,所以我想应用 switch case。 在我的案例中,组件的总数也约为 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} />;
}
假设我们希望使用动态组件 loading.The 访问各种视图,以下代码给出了一个工作示例,说明如何使用从 url 的搜索字符串解析的字符串来实现此目的。
假设我们要使用这些 url 路径访问具有两个独特视图的页面 'snozberrys':
'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'));
应该有一个容器将组件名称映射到应该动态使用的所有组件。组件 classes 应该在容器中注册,因为在模块化环境中,没有其他地方可以访问它们。组件 classes 在没有明确指定的情况下无法通过它们的名称来识别,因为函数 name
在生产中被缩小了。
分量图
可以是普通对象:
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);
普通对象更合适,因为它受益于 属性 shorthand.
桶形模块
具有命名导出的 barrel module 可以用作这样的映射:
// 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/>;
这适用于每个模块代码样式一个 class。
装饰器
装饰器可以与 class 组件一起使用作为语法糖,这仍然需要明确指定 class 名称并将它们注册到映射中:
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);
使用 non-standard displayName
而不是随机 属性 也有利于调试。
假设我们有一个 flag
,与 state
或 props
没有区别:
import ComponentOne from './ComponentOne';
import ComponentTwo from './ComponentTwo';
~~~
const Compo = flag ? ComponentOne : ComponentTwo;
~~~
<Compo someProp={someValue} />
使用标志 Compo
填充 ComponentOne
或 ComponentTwo
之一,然后 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} />