【问题标题】:React / JSX Dynamic Component NameReact / JSX 动态组件名称
【发布时间】: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"]();
}

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

我非常愿意接受建议。

【问题讨论】:

    标签: javascript reactjs react-jsx


    【解决方案1】:

    &lt;MyComponent /&gt; 编译为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 &lt;MyComponent {...this.props} /&gt;
    • 还要确保您的动态变量名大写。
    • 请记住,您的变量应该包含组件本身,而而不是只是组件的名称作为字符串.
    • 如果您还想知道为什么 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 &lt;MyComponent /&gt;
          【解决方案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>
            );
          };
          

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

          【讨论】:

          • 您的退货声明不应该返回&lt;DynamicComponent /&gt;吗?看起来您在第 4 行编辑了组件名称,但在第 8 行没有编辑
          【解决方案10】:

          假设我们有一个flag,与stateprops 没有什么不同:

          import ComponentOne from './ComponentOne';
          import ComponentTwo from './ComponentTwo';
          
          ~~~
          
          const Compo = flag ? ComponentOne : ComponentTwo;
          
          ~~~
          
          <Compo someProp={someValue} />
          

          使用标志 Compo 填充 ComponentOneComponentTwo 之一,然后 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 方法中,每次渲染父组件时都会再次执行此操作。最佳答案是实现相同目标的更好方法。
                猜你喜欢
                • 2015-05-04
                • 2020-09-11
                • 2015-07-26
                相关资源
                最近更新 更多