React / JSX动态组件名称


167

我正在尝试根据组件的类型动态呈现组件。

例如:

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

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

这给我一个编译时的错误(使用browserify进行gulp)。我使用数组语法的地方应该是XML。

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

newExampleComponent() {
    return <ExampleComponent />;
}

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

但这将意味着为我创建的每个组件提供一种新方法。必须对此问题有一个更优雅的解决方案。

我很乐意提出建议。

Answers:


157

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

您可以只将组件类存储在名称以大写字母开头的变量中。参见HTML标签与React组件

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

编译为

var MyComponent = Components[type + "Component"];
return React.createElement(MyComponent, {});

5
将来的读者也可能会发现{...this.props}有用的方法,可以将道具透明地传递给父对象的子类型组件。喜欢return <MyComponent {...this.props} />
Dr.Strangelove

4
另外,请确保将大写的动态变量名大写。
萨达

28
请记住,变量应该包含组件本身,不仅仅是字符串形式的组件名称。
totymedli

3
如果您还想知道为什么 var需要大写的话facebook.github.io/react/docs/…–
Nobita

3
组件未定义
ness-EE

144

有关如何处理此类情况的官方文档,请参见: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} />;
}

25
非常重要的
事情

4
除了它是官方文档外,它还很容易成为最佳答案和最结构化的解决方案。也许这就是它在文档中的原因
:)

1
感谢您的出色回答。对于以下读者,请注意,地图对象中的值(此处的地图对象是const组件,而值是PhotoStory和VideoStory)必须不带引号-否则,该组件将无法呈现,并且会出现错误-在我的案例我错过了,只是浪费了时间...
Erez Lieberman

11

应该有一个容器,用于将组件名称映射到所有应该动态使用的组件。组件类应该在容器中注册,因为在模块化环境中,否则没有单个位置可以访问它们。如果不明确指定组件类,则无法通过它们的名称来标识它们,因为函数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);

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

机筒模块

筒模块与命名出口可以充当这样的地图:

// 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);

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


谢谢!该componentMap干净漂亮:)
Leon Gaban

10

我想出了一个新的解决方案。请注意,我正在使用ES6模块,因此我需要该类。您也可以改为定义一个新的React类。

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

var type = "example";

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

1
@klinore您是否尝试访问该default属性?即:require('./ ExampleComponent')。default?
Khanh Hua

7

如果您的组件是全局组件,则可以执行以下操作:

var nameOfComponent = "SomeComponent";
React.createElement(window[nameOfComponent], {});


1
这可以很好地工作,尤其是在使用Rails的情况下。接受的答案对我不起作用,因为Components未定义数组。
瓦迪姆

3
与其将任意命名的对象附加到全局范围(euw),还不如考虑维护一个组件注册表,可以注册该组件注册表,然后在需要时从中检索组件引用。
slashwhat16年

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 })
    )
  }
}

我的项目中实际上有一个具有相同目的的组件)
Dziamid '16

2

在组件映射的所有选项中,我还没有找到使用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/>
}

1

拥有大量组件,拥有地图看起来一点也不好。实际上,我很惊讶没有人提出这样的建议:

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 />
semaj1919

0

假设我们希望通过动态组件加载来访问各种视图。以下代码提供了一个有效的示例,说明了如何使用从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'));

0

假设我们有一个flag,与state或没有区别props

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

~~~

const Compo = flag ? ComponentOne : ComponentTwo;

~~~

<Compo someProp={someValue} />

使用标志Compo填充时,用ComponentOne或之一填充,ComponentTwo然后Compo可以充当React组件。


-1

我使用了一些不同的方法,因为我们一直都知道我们的实际组件,因此我认为应该使用开关盒。在我的情况下,组件总数也不超过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} />

    }
  }

只是再次遇到这个。这是这样做的方法。无论如何,您总是知道您的组件,并且需要加载它们。因此,这是一个很好的解决方案。谢谢。
杰克

-1

编辑:其他答案更好,请参阅注释。

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

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

3
您可能不希望这样做,因为React.createElement即使一次只渲染其中一个,也会为查找对象中的每个组件调用该组件。更糟糕的是,通过将查找对象放在render父组件的方法中,它将在每次渲染父组件时再次执行此操作。最佳答案是实现同一目标的更好方法。
Inkling'4

2
@墨水,我同意。那是我刚开始使用React的时候。我写了这篇文章,但是当我知道得更多时,就忘记了这一切。感谢您指出了这一点。
哈马德·阿赫瓦德
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.