Como encapsular condicionalmente um componente React?

90

Tenho um componente que às vezes precisa ser renderizado como um <anchor>e outras vezes como um <div>. O propque li para determinar isso, é this.props.url.

Se existir, preciso renderizar o componente envolvido em um <a href={this.props.url}>. Caso contrário, ele apenas será renderizado como um <div/>.

Possível?

Isso é o que estou fazendo agora, mas sinto que poderia ser simplificado:

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

ATUALIZAR:

Aqui está o travamento final. Obrigado pela dica, @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
fonte
Você também pode mover const baseClasses =para esse if (this.props.link)galho. Como você está usando o ES6, também pode simplificar um pouco const {link, className} = this.props;usando linke classNamecomo variáveis ​​locais.
Sulthan
Cara, eu amo isso. Aprender mais e mais sobre o ES6 e isso sempre melhora a legibilidade. Obrigado pela dica extra!
Brandon Durham
1
O que é um "bloqueio final"?
Chris Harrison

Respostas:

94

Basta usar uma variável.

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;

ou você pode usar uma função auxiliar para renderizar o conteúdo. JSX é um código como qualquer outro. Se você quiser reduzir as duplicações, use funções e variáveis.

Sulthan
fonte
23

Crie um HOC (componente de ordem superior) para envolver seu 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>
);
Faça assíncrono
fonte
4
HOC deve morrer lentamente: P
Jamie Hutber
O termo HOCé horrível. É apenas uma função colocada no meio. Eu realmente desloco esse nome repentinamente na moda "HPC". o que é tão importante em uma função simples que é colocada entre ... conceito antigo por décadas.
vsync
13

Aqui está um exemplo de um componente útil que vi ser usado (não tenho certeza para quem creditar) que faz o trabalho:

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>
Antony
fonte
2
O crédito provavelmente deve ir aqui: gist.github.com/kitze/23d82bb9eb0baabfd03a6a720b1d637f
Roy Prins
Eu vi isso de Kitze. Mas eu não tinha certeza se ele tirou a ideia de outra pessoa
antony
Nem eu. Este foi o primeiro resultado que apareceu e eu presumi que fosse a fonte - ou pelo menos mais perto dela;).
Roy Prins de
Você deve usar o wrapdeclarativamente e não como uma função para manter as coisas mais "React" -spirit
vsync
Como você o tornaria mais declarativo @vsync? Achei que os adereços de renderização estavam dentro do espírito de React?
antony
10

Há outra maneira de usar uma variável de referência

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
fonte
Você pode condensar a primeira metade paralet Wrapper = someCondition ? ParentComponent : React.Fragment
mpoisot de
Isso é incrível, mas às vezes você deseja manter o código declarativo , o que significa que ele retorna apenas JSX
vsync
Recebo um erro React.Fragment can only have 'key' and 'children' porque passo alguns adereços para "<Wrapper>" como "className" e então
vsync
@vsync, você também precisa adicionar uma condição para os adereços, algo como propId = {someCondition? parentProps: undefined} ..
Avinash
1
Eu sei :) Eu estava escrevendo isso para fins de documentação para outras pessoas que vêm aqui com esse problema, então o Google armazenará esta página em cache em seus resultados de pesquisa para essas palavras
vsync
1

Você também pode usar uma função util como esta:

const wrapIf = (conditions, content, wrapper) => conditions
        ? React.cloneElement(wrapper, {}, content)
        : content;
Tomek
fonte
1
const ConditionalWrapper = ({ condition, wrapper, children }) => 
  condition ? wrapper(children) : children;

O componente que você quer envolver como

<ConditionalWrapper
   condition={link}
   wrapper={children => <a href={link}>{children}</a>}>
   <h2>{brand}</h2>
</ConditionalWrapper>

Talvez este artigo possa ajudá-lo mais https://blog.hackages.io/conditionally-wrap-an-element-in-react-a8b9a47fab2

Adam Anjum
fonte
0

Você deve usar um JSX if-else conforme descrito aqui . Algo assim deve 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>
        )
    }
});
user2027202827
fonte
-2

Um componente funcional que renderiza 2 componentes, um é empacotado e o outro não.

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>

Isso também pode ser usado assim:

<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
fonte