问题
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
That gave me an error when compiling (using browserify for gulp). It expected XML where I was using an array syntax.
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.
回答1:
<MyComponent />
compiles to React.createElement(MyComponent, {})
, which expects a string (HTML tag) or a function (ReactClass) as first parameter.
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.
var MyComponent = Components[type + "Component"];
return <MyComponent />;
compiles to
var MyComponent = Components[type + "Component"];
return React.createElement(MyComponent, {});
回答2:
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
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} />;
}
回答3:
I figured out a new solution. Do note that I am using ES6 modules so I am requiring the class. You could also define a new React class instead.
var components = {
example: React.createFactory( require('./ExampleComponent') )
};
var type = "example";
newComponent() {
return components[type]({ attribute: "value" });
}
回答4:
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.
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:
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.
回答5:
If your components are global you can simply do:
var nameOfComponent = "SomeComponent";
React.createElement(window[nameOfComponent], {});
回答6:
For a wrapper component, a simple solution would be to just use React.createElement
directly (using 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:
I used a bit different Approach, as we always know our actual components so i thought to apply switch case. Also total no of component were around 7-8 in my case.
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} />
}
}
回答8:
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.
Lets assume we want to access a page 'snozberrys' with two unique views using these url paths:
'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'));
回答9:
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>);
}
...
来源:https://stackoverflow.com/questions/29875869/react-jsx-dynamic-component-name