¿Cómo envuelvo condicionalmente un componente de React?

85

Tengo un componente que a veces necesitará renderizarse como an <anchor>y otras veces como <div>. El propque leí para determinar esto, es this.props.url.

Si existe, necesito renderizar el componente envuelto en un <a href={this.props.url}>. De lo contrario, simplemente se renderiza como <div/>.

¿Posible?

Esto es lo que estoy haciendo ahora mismo, pero creo que podría simplificarse:

if (this.props.link) {
    return (
        <a href={this.props.link}>
            <i>
                {this.props.count}
            </i>
        </a>
    );
}

return (
    <i className={styles.Icon}>
        {this.props.count}
    </i>
);

ACTUALIZAR:

Aquí está el bloqueo final. ¡Gracias por el consejo, @Sulthan !

import React, { Component, PropTypes } from 'react';
import classNames from 'classnames';

export default class CommentCount extends Component {

    static propTypes = {
        count: PropTypes.number.isRequired,
        link: PropTypes.string,
        className: PropTypes.string
    }

    render() {
        const styles = require('./CommentCount.css');
        const {link, className, count} = this.props;

        const iconClasses = classNames({
            [styles.Icon]: true,
            [className]: !link && className
        });

        const Icon = (
            <i className={iconClasses}>
                {count}
            </i>
        );

        if (link) {
            const baseClasses = classNames({
                [styles.Base]: true,
                [className]: className
            });

            return (
                <a href={link} className={baseClasses}>
                    {Icon}
                </a>
            );
        }

        return Icon;
    }
}
Brandon Durham
fuente
También puede pasar const baseClasses =a esa if (this.props.link)rama. Como está usando ES6, también puede simplificar un poco const {link, className} = this.props;y luego usar linky classNamecomo variables locales.
Sulthan
Hombre, me encanta. Aprender más y más sobre ES6 y siempre mejora la legibilidad. ¡Gracias por el consejo extra!
Brandon Durham
1
¿Qué es un "cierre final"?
Chris Harrison

Respuestas:

92

Solo usa una variable.

var component = (
    <i className={styles.Icon}>
       {this.props.count}
    </i>
);

if (this.props.link) {
    return (
        <a href={this.props.link} className={baseClasses}>
            {component}
        </a>
    );
}

return component;

o puede utilizar una función auxiliar para representar el contenido. JSX es un código como cualquier otro. Si desea reducir las duplicaciones, use funciones y variables.

Sulthan
fuente
21

Cree un HOC (componente de orden superior) para envolver su elemento:

const WithLink = ({ link, className, children }) => (link ?
  <a href={link} className={className}>
    {children}
  </a>
  : children
);

return (
  <WithLink link={this.props.link} className={baseClasses}>
    <i className={styles.Icon}>
      {this.props.count}
    </i>
  </WithLink>
);
Hacer async
fuente
4
HOC debería morir lentamente: P
Jamie Hutber
El término HOCes espantoso. Es simplemente una función que se coloca en el medio. Realmente desplazo este nombre repentinamente de moda "HPC". lo que es tan importante en una función simple que se coloca en el medio ... concepto antiguo durante décadas.
vsync
12

Aquí hay un ejemplo de un componente útil que he visto utilizado (no estoy seguro de a quién acreditarlo) que hace el trabajo:

const ConditionalWrap = ({ condition, wrap, children }) => (
  condition ? wrap(children) : children
);

Caso de uso:

<ConditionalWrap condition={someCondition}
  wrap={children => (<a>{children}</a>)} // Can be anything
>
  This text is passed as the children arg to the wrap prop
</ConditionalWrap>
Antonio
fuente
2
El crédito probablemente debería ir aquí: gist.github.com/kitze/23d82bb9eb0baabfd03a6a720b1d637f
Roy Prins
Lo vi de Kitze. Pero no estaba segura de que tuvo la idea de otra persona
Antony
Yo tampoco. Este fue el primer resultado que apareció y asumí que era la fuente, o al menos más cercana a ella;).
Roy Prins
Debería usar wrapdeclarativamente y no como una función para mantener las cosas más "React" -spirit
vsync
¿Cómo lo haría más declarativo @vsync? Pensé que los accesorios de renderizado estaban dentro del espíritu de React.
antony
10

Hay otra forma de usar una variable de referencia

let Wrapper = React.Fragment //fallback in case you dont want to wrap your components

if(someCondition) {
    Wrapper = ParentComponent
}

return (
    <Wrapper parentProps={parentProps}>
        <Child></Child>
    </Wrapper>

)
Avinash
fuente
Puede condensar la primera mitad enlet Wrapper = someCondition ? ParentComponent : React.Fragment
mpoisot
Esto es increíble, pero a veces desea mantener el código declarativo , lo que significa que solo devuelve JSX
vsync
Recibo un error React.Fragment can only have 'key' and 'children' porque paso algunos accesorios a "<Wrapper>" como "className" y así
vsync
@vsync necesita agregar una condición para los accesorios, así como algo como propId = {someCondition? parentProps: undefined} ..
Avinash
1
Lo sé :) Estaba escribiendo esto por el bien de la documentación para otros que vienen aquí con este problema, por lo que Google almacenará en caché esta página en sus resultados de búsqueda para esas palabras clave
vsync
1

También puede usar una función útil como esta:

const wrapIf = (conditions, content, wrapper) => conditions
        ? React.cloneElement(wrapper, {}, content)
        : content;
Tomek
fuente
0

Debe usar un JSX if-else como se describe aquí . Algo como esto debería funcionar.

App = React.creatClass({
    render() {
        var myComponent;
        if(typeof(this.props.url) != 'undefined') {
            myComponent = <myLink url=this.props.url>;
        }
        else {
            myComponent = <myDiv>;
        }
        return (
            <div>
                {myComponent}
            </div>
        )
    }
});
usuario2027202827
fuente
-2

Un componente funcional que presenta 2 componentes, uno está empaquetado y el otro no.

Método 1:

// The interesting part:
const WrapIf = ({ condition, With, children, ...rest }) => 
  condition 
    ? <With {...rest}>{children}</With> 
    : children

 
    
const Wrapper = ({children, ...rest}) => <h1 {...rest}>{children}</h1>


// demo app: with & without a wrapper
const App = () => [
  <WrapIf condition={true} With={Wrapper} style={{color:"red"}}>
    foo
  </WrapIf>
  ,
  <WrapIf condition={false} With={Wrapper}>
    bar
  </WrapIf>
]

ReactDOM.render(<App/>, document.body)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>

Esto también se puede usar así:

<WrapIf condition={true} With={"h1"}>

Método 2:

// The interesting part:
const Wrapper = ({ condition, children, ...props }) => condition 
  ? <h1 {...props}>{children}</h1>
  : <React.Fragment>{children}</React.Fragment>;   
    // stackoverflow prevents using <></>
  

// demo app: with & without a wrapper
const App = () => [
  <Wrapper condition={true} style={{color:"red"}}>
    foo
  </Wrapper>
  ,
  <Wrapper condition={false}>
    bar
  </Wrapper>
]

ReactDOM.render(<App/>, document.body)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>

vsync
fuente