React / JSX 动态组件名称
Posted
技术标签:
【中文标题】React / JSX 动态组件名称【英文标题】:React / JSX Dynamic Component Name 【发布时间】:2015-07-04 17:35:40 【问题描述】:我正在尝试根据组件的类型动态呈现组件。
例如:
var type = "Example";
var ComponentName = type + "Component";
return <ComponentName />;
// Returns <examplecomponent /> instead of <ExampleComponent />
我尝试了这里提出的解决方案React/JSX dynamic component names
编译时出现错误(使用 browserify 进行 gulp)。它在我使用数组语法的地方需要 XML。
我可以通过为每个组件创建一个方法来解决这个问题:
newExampleComponent()
return <ExampleComponent />;
newComponent(type)
return this["new" + type + "Component"]();
但这意味着我创建的每个组件都需要一种新方法。这个问题一定有更优雅的解决方案。
我非常愿意接受建议。
【问题讨论】:
【参考方案1】:<MyComponent />
编译为React.createElement(MyComponent, )
,它需要一个字符串(html 标记)或一个函数(ReactClass)作为第一个参数。
您可以将组件类存储在一个名称以大写字母开头的变量中。见HTML tags vs React Components。
var MyComponent = Components[type + "Component"];
return <MyComponent />;
编译成
var MyComponent = Components[type + "Component"];
return React.createElement(MyComponent, );
【讨论】:
未来的读者也可能会发现...this.props
对于透明地将 props 从父组件传递给子类型组件很有用。喜欢return <MyComponent ...this.props />
还要确保您的动态变量名大写。
请记住,您的变量应该包含组件本身,而而不是只是组件的名称作为字符串.
如果您还想知道为什么 var 需要大写 facebook.github.io/react/docs/…
组件未定义【参考方案2】:
这里有一个关于如何处理这种情况的官方文档: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 />;
【讨论】:
非常重要的事情:一个大写变量 感谢您的出色回答。下面的读者请注意地图对象内的值(这里的地图对象是const组件,值是PhotoStory和VideoStory)必须不带引号——否则组件不会渲染,你会得到错误 - 就我而言,我错过了它,只是浪费了时间......【参考方案3】:应该有一个容器将组件名称映射到应该动态使用的所有组件。组件类应该在容器中注册,因为在模块化环境中,没有一个地方可以访问它们。如果不明确指定组件类,则无法通过其名称来识别它们,因为函数 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);
普通对象更适合,因为它受益于属性简写。
桶模块
具有命名导出的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/>;
这适用于每个模块代码样式一个类。
装饰器
装饰器可以与类组件一起使用作为语法糖,这仍然需要明确指定类名并在映射中注册它们:
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
代替随机属性也有利于调试。
【讨论】:
【参考方案4】:我想出了一个新的解决方案。请注意,我正在使用 ES6 模块,因此我需要该类。你也可以定义一个新的 React 类。
var components =
example: React.createFactory( require('./ExampleComponent') )
;
var type = "example";
newComponent()
return components[type]( attribute: "value" );
【讨论】:
@klinore 您是否尝试访问default
属性?即:require('./ExampleComponent').default?【参考方案5】:
如果您的组件是全局的,您可以简单地执行以下操作:
var nameOfComponent = "SomeComponent";
React.createElement(window[nameOfComponent], );
【讨论】:
这很好用,尤其是在使用 Rails 时。接受的答案对我不起作用,因为未定义Components
数组。
与其将任意命名的对象附加到全局范围 (euw),不如考虑维护一个组件注册表,您可以注册并在需要时从中检索组件引用。【参考方案6】:
对于包装器组件,一个简单的解决方案是直接使用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 )
)
【讨论】:
【参考方案7】:在所有带有组件映射的选项中,我还没有找到使用 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/>
【讨论】:
【参考方案8】:拥有大量组件的地图看起来一点也不好看。我真的很惊讶没有人提出这样的建议:
var componentName = "StringThatContainsComponentName";
const importedComponentModule = require("path/to/component/" + componentName).default;
return React.createElement(importedComponentModule);
当我需要渲染大量以 json 数组形式加载的组件时,这个确实帮助了我。
【讨论】:
这与对我有用的方法很接近,并引导我朝着正确的方向前进。尝试直接调用React.createElement(MyComponent)
会报错。具体来说,我不希望父级必须知道要导入的所有组件(在映射中)——因为这似乎是一个额外的步骤。相反,我使用了const MyComponent = require("path/to/component/" + "ComponentNameString").default; return <MyComponent />
。【参考方案9】:
随着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>
);
;
这种方法当然对文件层次结构做了一些假设,并且可以使代码容易破解。
【讨论】:
您的退货声明不应该返回<DynamicComponent />
吗?看起来您在第 4 行编辑了组件名称,但在第 8 行没有编辑【参考方案10】:
假设我们有一个flag
,与state
或props
没有什么不同:
import ComponentOne from './ComponentOne';
import ComponentTwo from './ComponentTwo';
~~~
const Compo = flag ? ComponentOne : ComponentTwo;
~~~
<Compo someProp=someValue />
使用标志 Compo
填充 ComponentOne
或 ComponentTwo
之一,然后 Compo
可以像 React 组件一样工作。
【讨论】:
【参考方案11】:假设我们希望通过动态组件加载访问各种视图。以下代码给出了一个工作示例,说明如何使用从 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'));
【讨论】:
【参考方案12】:假设您能够从这样的组件中导出 *...
// 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 />
【讨论】:
【参考方案13】:我使用了一些不同的方法,因为我们总是知道我们的实际组件,所以我想应用 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 />
【讨论】:
刚刚又遇到了这个。这是做到这一点的方法。无论如何,您总是知道您的组件,并且需要加载它们。所以这是一个很好的解决方案。谢谢。【参考方案14】:编辑:其他答案更好,请参阅 cmets。
我用这种方式解决了同样的问题:
...
render : function ()
var componentToRender = 'component1Name';
var componentLookup =
component1Name : (<Component1 />),
component2Name : (<Component2 />),
...
;
return (<div>
componentLookup[componentToRender]
</div>);
...
【讨论】:
您可能不想这样做,因为React.createElement
将为您的查找对象中的每个组件调用,即使一次只呈现其中一个。更糟糕的是,通过将查找对象放在父组件的render
方法中,每次渲染父组件时都会再次执行此操作。最佳答案是实现相同目标的更好方法。以上是关于React / JSX 动态组件名称的主要内容,如果未能解决你的问题,请参考以下文章