React / JSX Dynamic Component Name


168

ฉันกำลังพยายามแสดงองค์ประกอบแบบไดนามิกตามประเภทของพวกเขา

ตัวอย่างเช่น:

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

ฉันลองวิธีแก้ปัญหาที่เสนอที่นี่ชื่อส่วนประกอบแบบไดนามิก React / JSX

นั่นทำให้ฉันมีข้อผิดพลาดเมื่อรวบรวม (ใช้ browserify สำหรับอึก) มันคาดว่า XML ที่ฉันใช้ไวยากรณ์อาร์เรย์

ฉันสามารถแก้ปัญหานี้โดยการสร้างวิธีการสำหรับทุกองค์ประกอบ:

newExampleComponent() {
    return <ExampleComponent />;
}

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

แต่นั่นจะหมายถึงวิธีการใหม่สำหรับทุกองค์ประกอบที่ฉันสร้าง จะต้องมีวิธีการแก้ปัญหาที่สง่างามกว่านี้

ฉันเปิดกว้างสำหรับคำแนะนำ

คำตอบ:


158

<MyComponent />คอมไพล์React.createElement(MyComponent, {})ซึ่งคาดว่าสตริง (แท็ก HTML) หรือฟังก์ชั่น (ReactClass) เป็นพารามิเตอร์แรก

คุณสามารถเก็บคลาสองค์ประกอบของคุณในตัวแปรด้วยชื่อที่ขึ้นต้นด้วยตัวอักษรตัวพิมพ์ใหญ่ ดูแท็ก VS 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
ตรวจสอบให้แน่ใจว่าคุณใช้ประโยชน์จากชื่อตัวแปรแบบไดนามิกของคุณ
saada

28
โปรดจำไว้ว่าคุณตัวแปรควรถือเป็นส่วนประกอบของตัวเองและไม่ได้เป็นเพียงแค่ชื่อขององค์ประกอบเป็นสตริง
totymedli

3
หากคุณสงสัยเช่นกันว่าทำไม var ต้องเป็นตัวพิมพ์ใหญ่facebook.github.io/react/docs/…
โนบิตะ

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
สิ่งที่สำคัญมาก: ตัวแปรตัวพิมพ์ใหญ่
mpyw

4
นอกเหนือจากข้อเท็จจริงที่ว่ามันเป็นเอกสารอย่างเป็นทางการแล้วมันเป็นคำตอบที่ดีที่สุดและทางออกที่มีโครงสร้างมากที่สุด บางทีนั่นอาจเป็นเหตุผลที่อยู่ในเอกสาร
:)

1
ขอบคุณสำหรับคำตอบที่ดี สำหรับผู้อ่านต่อไปนี้โปรดทราบว่าค่าภายในวัตถุแผนที่ (วัตถุแผนที่ที่นี่คือองค์ประกอบ const และค่าเป็น PhotoStory และ VideoStory) ต้องไม่มีเครื่องหมายคำพูด - มิฉะนั้นองค์ประกอบจะไม่แสดงผลและคุณจะได้รับข้อผิดพลาด - ใน กรณีที่ฉันคิดถึงมันและก็เสียเวลา ...
Erez ลีเบอร์แมน

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

วัตถุธรรมดามีความเหมาะสมมากกว่าเพราะได้รับประโยชน์จากการจดชวเลข

โมดูล Barrel

โมดูลบาร์เรลกับการส่งออกชื่อสามารถทำหน้าที่เป็นแผนที่ดังกล่าว

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

ใช้งานได้ดีกับหนึ่งคลาสต่อสไตล์โค้ดของโมดูล

มัณฑนากร

มัณฑนากรสามารถใช้กับส่วนประกอบของคลาสสำหรับน้ำตาล syntactic นี้ยังคงต้องระบุชื่อคลาสอย่างชัดเจนและลงทะเบียนในแผนที่:

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แทนที่จะเป็นคุณสมบัติแบบสุ่มก็มีประโยชน์ในการดีบักเช่นกัน


ขอบคุณ! ส่วนประกอบนั้นแผนที่สะอาดและดี :)
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ไม่ได้กำหนดอาร์เรย์
Vadim

3
แทนที่จะแนบวัตถุที่ตั้งชื่อตามอำเภอใจไปยังขอบเขตทั่วโลก (euw) คุณควรพิจารณาการบำรุงรักษาส่วนประกอบรีจิสตรีที่คุณสามารถลงทะเบียนแล้วดึงการอ้างอิงคอมโพเนนต์จากเมื่อจำเป็น
slashwhething

6

สำหรับส่วนประกอบของ wrapper คำตอบง่ายๆคือใช้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

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' ด้วยมุมมองที่ไม่ซ้ำกันสองรายการโดยใช้เส้นทาง URL เหล่านี้:

'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สามารถทำหน้าที่เหมือนองค์ประกอบการตอบสนอง


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

    }
  }

เพิ่งเจอสิ่งนี้อีกครั้ง นี่คือวิธีที่จะทำ คุณมักจะรู้องค์ประกอบของคุณอยู่แล้วและจำเป็นต้องโหลดมัน ดังนั้นนี่คือทางออกที่ดี ขอบคุณ
Jake

-1

แก้ไข: คำตอบอื่น ๆ ดีกว่าดูความคิดเห็น

ฉันแก้ไขปัญหาเดียวกันด้วยวิธีนี้:

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

3
คุณอาจไม่ต้องการทำเช่นนี้เพราะReact.createElementจะถูกเรียกใช้สำหรับทุกองค์ประกอบในวัตถุการค้นหาของคุณแม้ว่าจะมีการแสดงผลเพียงหนึ่งรายการเท่านั้นในแต่ละครั้ง ยิ่งไปกว่านั้นโดยการวางวัตถุค้นหาในrenderวิธีการขององค์ประกอบหลักมันจะทำมันอีกครั้งทุกครั้งที่มีการแสดงผลแม่ คำตอบยอดนิยมเป็นวิธีที่ดีกว่ามากในการบรรลุสิ่งเดียวกัน
Inkling

2
@Inkling ฉันเห็นด้วย นี่คือตอนที่ฉันเพิ่งเริ่มต้นด้วยปฏิกิริยา ฉันเขียนสิ่งนี้จากนั้นลืมทุกอย่างเมื่อฉันรู้ดีขึ้น ขอบคุณสำหรับการชี้ให้เห็นว่า
Hammad Akhwand
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.