React / JSX動的コンポーネント名


168

タイプに基づいてコンポーネントを動的にレンダリングしようとしています。

例えば:

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

ここで提案されたソリューションを試しましたReact / JSX動的コンポーネント名

コンパイル時にエラーが発生しました(gulpにbrowserifyを使用)。私は配列構文を使用していた場所でXMLを期待していました。

これを解決するには、すべてのコンポーネントのメソッドを作成します。

newExampleComponent() {
    return <ExampleComponent />;
}

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

しかし、それは私が作成するすべてのコンポーネントの新しいメソッドを意味します。この問題に対するより洗練された解決策があるはずです。

私は提案に非常にオープンです。

回答:


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 2016年

4
また、動的変数名を大文字にしてください。
saada 2016年

28
変数には、コンポーネントの名前だけではなく、コンポーネント自体を文字列として保持する必要があることに注意してください。
totymedli 2017年

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 2017年

4
それが公式ドキュメントであるという事実に加えて、それは簡単に最良の答えであり、最も構造化されたソリューションです。多分それがそれがドキュメントにある理由です:)
domdambrogia

1
すばらしい答えをありがとう。次のリーダーの場合、マップオブジェクト(ここでのマップオブジェクトはconstコンポーネントであり、値はPhotoStoryおよびVideoStoryです)内の値には引用符を付けないでください。そうしないと、コンポーネントがレンダリングされず、エラーが発生します-私の見逃して時間を無駄にしたケース
Erez Lieberman

11

動的に使用されるはずのすべてのコンポーネントにコンポーネント名をマップするコンテナが必要です。コンポーネントクラスはコンテナに登録する必要があります。モジュール環境では、コンポーネントクラスにアクセスできる場所が1つもないためです。機能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/>;

これは、モジュールのコードスタイルごとに1つのクラスでうまく機能します。

デコレータ

デコレータは、構文砂糖のクラスコンポーネントで使用できますが、これにはクラス名を明示的に指定してマップに登録する必要があります。

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配列が定義されていないため、受け入れられた回答は私には機能しません。
Vadim、

3
任意の名前のオブジェクトをグローバルスコープ(euw)にアタッチするのではなく、登録して必要なときにコンポーネント参照を取得できるコンポーネントレジストリを維持することを検討する必要があります。
16年

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

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の検索文字列から解析された文字列を使用してこれを実現する方法の実用的な例を示しています。

これらのURLパスを使用して、2つの固有のビューを持つページ「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から、全く異なるのstateprops

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

~~~

const Compo = flag ? ComponentOne : ComponentTwo;

~~~

<Compo someProp={someValue} />

フラグ付きCompoの1で塗りつぶし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} />

    }
  }

ちょうどこれに再び遭遇しました。これがその方法です。とにかくコンポーネントを常に知っているので、それらをロードする必要があります。したがって、これは優れたソリューションです。ありがとう。
ジェイク

-1

編集:他の回答の方が優れています。コメントを参照してください。

私はこの方法で同じ問題を解決しました:

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

3
React.createElement一度に1つしかレンダリングされない場合でも、ルックアップオブジェクトのすべてのコンポーネントに対してが呼び出されるため、おそらくこれを実行する必要はありません。さらに悪いことに、render親コンポーネントのメソッドにルックアップオブジェクトを配置することで、親がレンダリングされるたびに再度ルックアップオブジェクトが実行されます。上位の回答は、同じことを達成するはるかに良い方法です。
インクリング

2
@インクリング、同意する。これは、Reactを使い始めたばかりの頃でした。私はこれを書いた後、私がよく知ったときにそれについてすべてを忘れました。ご指摘いただきありがとうございます。
Hammad Akhwand 2017
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.