简体   繁体   English

React / JSX 动态组件名称

[英]React / JSX Dynamic Component Name

I am trying to dynamically render components based on their type.我正在尝试根据组件的类型动态渲染组件。

For example:例如:

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

I tried the solution proposed here React/JSX dynamic component names我尝试了这里提出的解决方案React/JSX dynamic component names

That gave me an error when compiling (using browserify for gulp).这在编译时给了我一个错误(使用 browserify for gulp)。 It expected XML where I was using an array syntax.在我使用数组语法的地方,它需要 XML。

I could solve this by creating a method for every component:我可以通过为每个组件创建一个方法来解决这个问题:

newExampleComponent() {
    return <ExampleComponent />;
}

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

But that would mean a new method for every component I create.但这意味着我创建的每个组件都有一个新方法。 There must be a more elegant solution to this problem.这个问题一定有更优雅的解决方案。

I am very open to suggestions.我很乐意接受建议。

EDIT: As pointed out by gmfvpereira these days there is an official documentation entry for this: https://reactjs.org/docs/jsx-in-depth.html#choosing-the-type-at-runtime编辑:正如gmfvpereira这些天所指出的,有一个官方文档条目: https ://reactjs.org/docs/jsx-in-depth.html#choosing-the-type-at-runtime

<MyComponent /> compiles to React.createElement(MyComponent, {}) , which expects a string (HTML tag) or a function (ReactClass) as first parameter. <MyComponent />编译为React.createElement(MyComponent, {}) ,它需要一个字符串(HTML 标记)或一个函数(ReactClass)作为第一个参数。

You could just store your component class in a variable with a name that starts with an uppercase letter.您可以将组件类存储在名称以大写字母开头的变量中。 See HTML tags vs React Components .请参阅HTML 标签与 React 组件

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

compiles to编译为

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

There is an official documentation about how to handle such situations is available here: https://facebook.github.io/react/docs/jsx-in-depth.html#choosing-the-type-at-runtime此处提供了有关如何处理此类情况的官方文档: https : //facebook.github.io/react/docs/jsx-in-depth.html#choosing-the-type-at-runtime

Basically it says:基本上它说:

Wrong:错误的:

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

Correct:正确的:

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

There should be a container that maps component names to all components that are supposed to be used dynamically.应该有一个容器将组件名称映射到所有应该动态使用的组件。 Component classes should be registered in a container because in modular environment there's otherwise no single place where they could be accessed.组件类应该在容器中注册,因为在模块化环境中,否则没有可以访问它们的单一位置。 Component classes cannot be identified by their names without specifying them explicitly because function name is minified in production.组件类不能在没有明确指定的情况下通过它们的名称来识别,因为函数name在生产中被缩小了。

Component map组件图

It can be plain object:它可以是普通对象:

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

Or Map instance:Map实例:

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

Plain object is more suitable because it benefits from property shorthand.普通对象更合适,因为它受益于属性简写。

Barrel module枪管模块

A barrel module with named exports can act as such map:具有命名导出的桶模块可以充当这样的映射:

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

This works well with one class per module code style.这适用于每个模块代码样式的一个类。

Decorator装饰器

Decorators can be used with class components for syntactic sugar, this still requires to specify class names explicitly and register them in a 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'
  ...
}

A decorator can be used as higher-order component with functional components:装饰器可以用作具有功能组件的高阶组件:

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

export default dynamic(Bar);

The use of non-standard displayName instead of random property also benefits debugging.使用非标准displayName而不是随机属性也有利于调试。

I figured out a new solution.我想出了一个新的解决方案。 Do note that I am using ES6 modules so I am requiring the class.请注意,我使用的是 ES6 模块,因此我需要该类。 You could also define a new React class instead.你也可以定义一个新的 React 类。

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

var type = "example";

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

If your components are global you can simply do:如果您的组件是全局的,您可以简单地执行以下操作:

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

For a wrapper component, a simple solution would be to just use React.createElement directly (using ES6).对于包装器组件,一个简单的解决方案是直接使用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 })
    )
  }
}

Across all options with component maps I haven't found the simplest way to define the map using ES6 short syntax:在组件映射的所有选项中,我还没有找到使用 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/>
}

With the introduction of React.lazy , we can now use a true dynamic approach to import the component and render it.随着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>
  );
};

This approach makes some assumptions about the file hierarchy of course and can make the code easy to break.这种方法当然对文件层次结构做了一些假设,并且可以使代码容易被破坏。

Assume we have a flag , no different from the state or props :假设我们有一个flag ,与stateprops没有区别:

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

~~~

const Compo = flag ? ComponentOne : ComponentTwo;

~~~

<Compo someProp={someValue} />

With flag Compo fill with one of ComponentOne or ComponentTwo and then the Compo can act like a React Component.使用标志Compo填充ComponentOneComponentTwo ,然后Compo可以像 React 组件一样工作。

Having a map doesn't look good at all with a large amount of components.拥有大量组件的地图看起来一点也不好看。 I'm actually surprised that no one has suggested something like this:我真的很惊讶没有人提出这样的建议:

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

This one has really helped me when I needed to render a pretty large amount of components loaded in a form of json array.当我需要渲染以 json 数组形式加载的大量组件时,这对我真的很有帮助。

Suspose we wish to access various views with dynamic component loading.The following code gives a working example of how to accomplish this by using a string parsed from the search string of a url.假设我们希望通过动态组件加载访问各种视图。以下代码提供了一个工作示例,说明如何使用从 url 的搜索字符串解析的字符串来完成此操作。

Lets assume we want to access a page 'snozberrys' with two unique views using these url paths:假设我们要使用这些 url 路径访问具有两个独特视图的页面“snozberrys”:

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

and

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

we define our view's controller like this:我们像这样定义视图的控制器:

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

Assuming you are able to export * from components like so...假设您能够像这样从组件导出 * ......

// src/components/index.js

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

You can then re-import * into a new comps object, which can then be used to access your modules.然后您可以将 * 重新导入到一个新的 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} />
}

Just pass in a string value that identifies which component you want to paint, wherever you need to paint it.只需传入一个字符串值,该值标识您要绘制的组件,无论您需要在何处绘制它。

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

You can create a reusable component with a fallback component.您可以使用后备组件创建可重用组件。

export const StringComponent = (Base, { name, Fallback = undefined, ...rest }) => {
    const Component = Base[name];

    // return fallback if the component doesn't exist
    if (!Component) return <Fallback/>

    return <Component {...rest}/>;
};

And call it like this:并这样称呼它:

import * as Pages from "../pages"

const routes = [
  {path: "/", element: "Home" },
  {path: "/about", element: "About" },
  {path: "*", element: "NotFound" },
]

export function App(){
  
  const Fallback = Pages.NotFound

  // render each route using a string as name
  return (
    <div>
      { 
        routes.map(page => 
          StringComponent(Pages, { name: page.element, Fallback }) 
        )
      }
    </div>
  )
}

OBS: Imported Pages needs to be something like this: OBS:导入的页面需要是这样的:

import Home from "./home"
import About from "./about"
import NotFound from "./not-found"

export { Home, About, NotFound }

I used a bit different Approach, as we always know our actual components so i thought to apply switch case.我使用了一些不同的方法,因为我们总是知道我们的实际组件,所以我想应用 switch case。 Also total no of component were around 7-8 in my 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} />

    }
  }

Edit: Other answers are better, see comments.编辑:其他答案更好,请参阅评论。

I solved the same problem this way:我用这种方式解决了同样的问题:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM