React.js: agrupando um componente em outro

187

Muitas linguagens de modelos possuem instruções "slots" ou "yield", que permitem fazer algum tipo de inversão de controle para agrupar um modelo dentro de outro.

Angular possui a opção "transcluir" .

Rails possui declaração de rendimento . Se o React.js tivesse uma declaração de rendimento, seria assim:

var Wrapper = React.createClass({
  render: function() {
    return (
      <div className="wrapper">
        before
          <yield/>
        after
      </div>
    );
  }
});

var Main = React.createClass({
  render: function() {
    return (
      <Wrapper><h1>content</h1></Wrapper>
    );
  }
});

Saída desejada:

<div class="wrapper">
  before
    <h1>content</h1>
  after
</div>

Infelizmente, React.js não tem um <yield/>. Como defino o componente Wrapper para obter a mesma saída?

NVI
fonte

Respostas:

159

Usando children

const Wrapper = ({children}) => (
  <div>
    <div>header</div>
    <div>{children}</div>
    <div>footer</div>
  </div>
);

const App = ({name}) => <div>Hello {name}</div>;

const WrappedApp = ({name}) => (
  <Wrapper>
    <App name={name}/>
  </Wrapper>
);

render(<WrappedApp name="toto"/>,node);

Isso também é conhecido como transclusion em Angular.

childrené um suporte especial no React e conterá o que está dentro das tags do seu componente (aqui <App name={name}/>está dentroWrapper , então é ochildren

Observe que você não precisa necessariamente usar children, que é exclusivo para um componente, e também pode usar adereços normais, se desejar, ou misturar adereços e filhos:

const AppLayout = ({header,footer,children}) => (
  <div className="app">
    <div className="header">{header}</div>
    <div className="body">{children}</div>
    <div className="footer">{footer}</div>
  </div>
);

const appElement = (
  <AppLayout 
    header={<div>header</div>}
    footer={<div>footer</div>}
  >
    <div>body</div>
  </AppLayout>
);

render(appElement,node);

Isso é simples e adequado para muitos casos de uso, e eu recomendo isso para a maioria dos aplicativos de consumo.


render adereços

É possível passar funções de renderização para um componente, esse padrão é geralmente chamado render prope ochildren prop é frequentemente usado para fornecer esse retorno de chamada.

Esse padrão não é realmente destinado ao layout. O componente wrapper geralmente é usado para manter e gerenciar algum estado e injetá-lo em suas funções de renderização.

Exemplo de contador:

const Counter = () => (
  <State initial={0}>
    {(val, set) => (
      <div onClick={() => set(val + 1)}>  
        clicked {val} times
      </div>
    )}
  </State>
); 

Você pode ficar ainda mais sofisticado e até fornecer um objeto

<Promise promise={somePromise}>
  {{
    loading: () => <div>...</div>,
    success: (data) => <div>{data.something}</div>,
    error: (e) => <div>{e.message}</div>,
  }}
</Promise>

Observe que você não precisa necessariamente usar children, é uma questão de gosto / API.

<Promise 
  promise={somePromise}
  renderLoading={() => <div>...</div>}
  renderSuccess={(data) => <div>{data.something}</div>}
  renderError={(e) => <div>{e.message}</div>}
/>

Atualmente, muitas bibliotecas estão usando objetos de renderização (contexto de reação, movimento de reação, Apollo ...) porque as pessoas tendem a achar essa API mais fácil do que as HOCs. O react-powerplug é uma coleção de componentes simples de render-prop. reagir-adotar ajuda na composição.


Componentes de ordem superior (HOC).

const wrapHOC = (WrappedComponent) => {
  class Wrapper extends React.PureComponent {
    render() {
      return (
        <div>
          <div>header</div>
          <div><WrappedComponent {...this.props}/></div>
          <div>footer</div>
        </div>
      );
    }  
  }
  return Wrapper;
}

const App = ({name}) => <div>Hello {name}</div>;

const WrappedApp = wrapHOC(App);

render(<WrappedApp name="toto"/>,node);

Um componente de ordem superior / HOC geralmente é uma função que pega um componente e retorna um novo componente.

O uso de um componente de ordem superior pode ter mais desempenho do que o uso childrenou render props, porque o wrapper pode ter a capacidade de causar um curto-circuito na renderização um passo à frente shouldComponentUpdate.

Aqui estamos usando PureComponent. Ao renderizar novamente o aplicativo, se o WrappedAppnome prop não for alterado com o tempo, o wrapper poderá dizer "Não preciso renderizar porque os props (na verdade, o nome) são os mesmos de antes". Com a childrensolução baseada acima, mesmo que o wrapper seja PureComponent, não é o caso, porque o elemento filho é recriado toda vez que o pai é renderizado, o que significa que o wrapper provavelmente sempre será renderizado novamente, mesmo que o componente embrulhado seja puro. Existe um plugin babel que pode ajudar a mitigar isso e garantir um childrenelemento constante ao longo do tempo.


Conclusão

Componentes de ordem superior podem oferecer melhor desempenho. Não é tão complicado, mas certamente parece hostil no começo.

Não migre toda a sua base de código para o HOC depois de ler isso. Lembre-se de que nos caminhos críticos do seu aplicativo você pode usar HOCs em vez de wrappers de tempo de execução por motivos de desempenho, principalmente se o mesmo wrapper for usado várias vezes, vale a pena considerar torná-lo um HOC.

O Redux usou inicialmente um wrapper de tempo de execução <Connect>e depois mudou para um HOC connect(options)(Comp)por motivos de desempenho (por padrão, o wrapper é puro e usado shouldComponentUpdate). Esta é a ilustração perfeita do que eu queria destacar nesta resposta.

Observe que, se um componente possui uma API de renderização, geralmente é fácil criar um HOC sobre ela; portanto, se você é um autor da lib, deve primeiro criar uma API de renderização e, eventualmente, oferecer uma versão HOC. É isso que a Apollo faz com o <Query>componente render-prop e o graphqlHOC usando-o.

Pessoalmente, uso os dois, mas em caso de dúvida prefiro HOCs porque:

  • É mais idiomático compô-las ( compose(hoc1,hoc2)(Comp)) em comparação com renderizações
  • Pode me dar melhores desempenhos
  • Eu estou familiarizado com este estilo de programação

Não hesito em usar / criar versões HOC das minhas ferramentas favoritas:

  • React's Context.Consumercomp
  • Não declarado Subscribe
  • usando graphqlHOC da Apollo em vez de Queryrender prop

Na minha opinião, às vezes os objetos de renderização tornam o código mais legível, às vezes menos ... Eu tento usar a solução mais pragmática de acordo com as restrições que tenho. Às vezes, a legibilidade é mais importante que as performances, às vezes não. Escolha sabiamente e não siga a tendência de 2018 de converter tudo em adereços de renderização.

Sebastien Lorber
fonte
1
Essa abordagem também facilita a transmissão dos componentes props do children (neste caso, Hello). A partir do React 0.14. *, A única maneira de transmitir adereços aos componentes filhos seria usar o React.createClone, que pode ser caro.
Mukesh Soni
2
Pergunta: A resposta menciona "melhor desempenho" - o que eu não entendo: melhor em comparação com qual outra solução?
Philipp
1
Os HOCs podem ter melhor desempenho em comparação com os wrappers de tempo de execução, pois podem causar um curto-circuito na renderização anterior.
Sebastien Lorber
1
Obrigado! É como se você tomou as palavras do meu mês, mas você expressá-las com maior talento 👍
MacKentoch
1
Esta é uma resposta muito melhor:] Obrigado!
cullanrocks
31

Além da resposta de Sophie, também achei útil enviar tipos de componentes filhos, fazendo algo assim:

var ListView = React.createClass({
    render: function() {
        var items = this.props.data.map(function(item) {
            return this.props.delegate({data:item});
        }.bind(this));
        return <ul>{items}</ul>;
    }
});

var ItemDelegate = React.createClass({
    render: function() {
        return <li>{this.props.data}</li>
    }
});

var Wrapper = React.createClass({    
    render: function() {
        return <ListView delegate={ItemDelegate} data={someListOfData} />
    }
});
krs
fonte
2
Não vi nenhuma documentação delegate, como você a encontrou?
NVI
4
você pode adicionar os adereços que desejar a um componente e nomeá-los como quiser, estou usando o this.props.delegate na linha 4, mas poderia muito bem nomear outra coisa.
KRS