Artigo original: These are the concepts you should know in React.js (after you learn the basics)

Escrito por: Chris Chuck

Nota da tradução: o autor lançou recentemente uma versão atualizada deste texto, já contemplando as atualizações mais recentes do React. Se você tiver o conhecimento do inglês e quiser ler o material mais recente a respeito deste assunto, a versão de 2023 está aqui (texto em inglês).

Você acompanhou seu primeiro tutorial do React.js e gostou do que viu. Certo, mas e agora? Neste artigo, discutirei cinco conceitos que trarão suas habilidades e conhecimentos sobre o React para o próximo nível.

Se você é totalmente novo no React, reserve algum tempo para concluir este tutorial e volte aqui depois!

1. O ciclo de vida dos componentes

De longe, o conceito mais importante nesta lista é entender o ciclo de vida do componente. O ciclo de vida do componente é exatamente o que parece: ele detalha a vida útil de um componente. Como nós, os componentes nascem, fazem algumas coisas durante seu tempo aqui na terra e, então, deixam de existir. ☹️

Diferente de nós, no entanto, os estágios de vida de um componente são um pouco diferentes. Veja como eles são:

1_U13Mlxz_ktcajaeJCyYkwg
Imagem retirada deste site (em inglês)!

Vamos entender essa imagem, ponto a ponto. Cada retângulo horizontal colorido representa um método de ciclo de vida (exceto para "React updates DOM and refs" – em português, "o React atualiza o DOM e as referências). As colunas representam diferentes estágios na vida útil dos componentes.

Um componente só pode estar em um estágio de cada vez. Começa com a montagem e vai para a atualização. Ele permanece atualizando continuamente até que seja removido do DOM virtual. Em seguida, ele entra na fase de desmontagem e é removido do DOM.

Os métodos de ciclo de vida nos permitem executar código em pontos específicos da vida útil do componente ou em resposta a alterações na vida útil do componente.

Vamos passar por cada estágio do componente e pelos métodos associados.

Montagem

Como os componentes baseados em classe são classes – motivo para o nome – o primeiro método que é executado é o método do construtor, ou constructor. Normalmente, o constructor é onde você inicializaria o estado do componente.

Em seguida, o componente executa getDerivedStateFromProps. Vou pular este método, pois ele tem uso limitado.

Agora, chegamos ao método render, que retorna seu JSX. É aqui que o React faz a "montagem" no DOM.

Por fim, o método componentDidMount é executado. Aqui, você faria todas as chamadas assíncronas para bancos de dados ou manipularia diretamente o DOM, se necessário. Assim, nasce o nosso componente.

Atualização

Esta fase ocorre toda vez que o state ou as props mudam. Como na montagem, getDerivedStateFromProps é chamado (mas sem o constructor desta vez!).

Em seguida, shouldComponentUpdate é executado. Aqui, você pode comparar props/state antigos com o novo conjunto de props/state. Você pode determinar se o componente deve ser renderizado novamente ou não, retornando true ou false. Isso pode tornar sua aplicação da web mais eficiente, reduzindo as novas renderizações adicionais. Se shouldComponentUpdate retornar false, esse ciclo de atualização será encerrado.

Caso contrário, o React renderiza novamente e getSnapshotBeforeUpdate é executado logo após. Este método também tem uso limitado. O React, em seguida, executa componentDidUpdate. Assim como componentDidMount, você pode usá-lo para fazer chamadas assíncronas ou manipular o DOM.

Desmontagem

Nosso componente viveu o bastante, mas todas as coisas boas devem terminar. A fase de desmontagem é o último estágio do ciclo de vida do componente. Quando você remove um componente do DOM, o React executa componentWillUnmount antes de ele ser removido. Você deve usar esse método para limpar todas as conexões abertas, como WebSockets ou intervalos.

Outros métodos do ciclo de vida

Antes de passarmos para o próximo tópico, vamos falar brevemente sobre forceUpdate e getDerivedStateFromError.

forceUpdate é um método que causa diretamente uma nova renderização. Embora possa haver alguns casos de uso para isso, normalmente deve ser evitado.

getDerivedStateFromError, por outro lado, é um método de ciclo de vida que não faz parte diretamente do ciclo de vida do componente. No caso de um erro em um componente, getDerivedStateFromError é executado e você pode atualizar o state para refletir que ocorreu um erro. Use este método o quanto quiser.

Este trecho de código do CodePen mostra as etapas da fase de montagem:

1_f6eAmkAEw-wFCNkkICOVXA
Métodos do ciclo de vida de montagem em ordem

Compreender o ciclo de vida e os métodos do componente do React permitirá que você mantenha um fluxo de dados e eventos de manipulação adequados em sua aplicação.

2. Componentes de ordem superior

Você já pode ter usado componentes de ordem superior (em inglês, higher-order components, ou HOCs). A função connect do Redux, por exemplo, é uma função que retorna um HOC. O que, no entanto, é um HOC exatamente?

Conforme a documentação do React:

Um componente de ordem superior é uma função que recebe um componente e retorna um novo componente.

Voltando à função connect do Redux, vejamos o trecho de código a seguir:

const hoc = connect(state => state)
const WrappedComponent = hoc(SomeComponent)

Quando chamamos connect, recebemos um HOC de volta, o qual podemos usar para encapsular um componente. A partir daqui, apenas passamos nosso componente para o HOC e começamos a usar o componente que nosso HOC retorna.

O que os HOCs nos permitem fazer é abstrair a lógica compartilhada entre os componentes em um único componente abrangente.

Um bom caso de uso para um HOC é a autorização. Você pode escrever seu código de autenticação em cada componente que precisar dele. Isso incharia rápida e desnecessariamente seu código.

Vejamos como você pode fazer a autenticação para componentes sem HOCs:

class ComponenteNormal extends React.Component {
  render() {
    if (this.props.estaLogado) {
      return <p>Olá</p>
    }
    return <p>Você não está logado ☹️</p>
  }
}
// Código repetido!
class OutroComponenteNormal extends React.Component {
  render() {
    if (this.props.estaLogado) {
      return <p>Olá</p>
    }
    return <p>Você não está logado ☹️</p>
  }
}
// Perceba como precisamos de lógicas diferentes para componentes funcionais
const FunctionalComponent = ({ estaLogado }) => ( estaLogado ? <p>Olá</p> : <p>Você não está logado ☹️</p> )

Usando HOCs, você pode fazer algo assim:

function AuthWrapper(WrappedComponent) {
  return class extends React.Component {
    render() {
      if (this.props.estaLogado) {
        return <WrappedComponent {...this.props} />
      }
      return <p>Você não está logado ☹️</p>
    }
  }
}

class ComponenteNormal extends React.Component {
  render() {
    return <p>Olá</p>
  }
}
class OutroComponenteNormal extends React.Component {
  render() {
    return <p>Oi!</p>
  }
}
const ComponenteFuncional = () => (<p>E aí?</p>)

const WrappedOne = AuthWrapper(ComponenteNormal)
const WrappedTwo = AuthWrapper(OutroComponenteNormal)
const WrappedThree = AuthWrapper(ComponenteFuncional)

Aqui temos um trecho de código do CodePen para o código acima.

Olhando para o código acima, você pode ver que somos capazes de manter nossos componentes regulares muito simples e "burros", enquanto ainda fornecemos autenticação para eles. O componente AuthWrapper eleva toda a lógica de autenticação em um componente unificador. Tudo o que ele faz é pegar a prop chamada estaLogado e retornar o WrappedComponent ou uma tag de parágrafo com base em confirmar se essa prop é true ou false.

Como você pode ver, os HOCs são extremamente úteis, pois nos permitem reutilizar o código e remover o excesso. Teremos mais prática com eles em breve!

3. React State e setState()

A maioria de vocês, provavelmente, já usou states no React. Até o utilizamos em nosso exemplo de HOCs. É importante, porém, entender que, quando houver uma alteração de state, o React disparará uma nova renderização nesse componente (a menos que você especifique em shouldComponentUpdate para fazer outra coisa).

Agora, vamos falar sobre como mudamos o state. A única maneira de alterar o state é por meio do método setState. Esse método pega um objeto e o mescla no state atual. Além disso, há algumas coisas que você também deve saber sobre isso.

Primeiro, setState é assíncrono. Isso significa que o state não será atualizado logo depois de você chamar setState. Isso pode levar a um comportamento estranho que esperamos poder evitar!

1_qle8858T8Amobp6-WCrLZA
Comportamento assíncrono de setState

Olhando para a imagem acima, você pode ver que chamamos setState e, em seguida, o state em um console.log. Nossa nova variável contadora deveria ser 1, mas, na verdade, é 0. Então, e se quisermos acessar o novo state depois que setState atualizar de fato o state?

Isso nos leva ao próximo conhecimento que devemos saber sobre setState e que pode receber uma função de callback. Vamos corrigir o nosso código!

1_typSaWY-BfT4fMUaAP_jJg
Funciona!

Ótimo, funciona! Agora, está tudo certo, não é? Bem, não exatamente. Na verdade, não estamos usando setState corretamente neste caso. Em vez de passar um objeto para setState, vamos dar a ele uma função. Esse padrão é normalmente usado quando você está usando o state atual para definir o novo state, como em nosso exemplo acima. Se você não estiver fazendo isso, sinta-se à vontade para continuar passando um objeto para setState. Vamos atualizar nosso código novamente!

1_jWrcTSN4rr3f1rEYNiFcxQ
Agora, sim!

Aqui está o CodePen para o código acima que usa o setState.

Qual é o sentido de se passar uma função em vez de um objeto? Como setState é assíncrono, confiar nele para criar nosso valor trará algumas armadilhas. Por exemplo, quando setState é executado, outro setState pode ter alterado o state. Passar para setState uma função nos traz dois benefícios. O primeiro está no fato de que nos permite obter uma cópia estática do nosso state que nunca mudará por conta própria. O segundo é colocar em fila as chamadas de setState para que elas sejam executadas em ordem.

Basta dar uma olhada no exemplo a seguir, onde tentamos incrementar o contador duas vezes, usando duas chamadas de setState consecutivas:

1_iuNhuy16nNN8BeSWvdRqkg
Comportamento assíncrono típico de antes

Acima, vemos aquilo que percebemos antes. Abaixo, vemos como consertar isso.

1_UaRuXtcBpVGrHHNknBAKTw
A solução para consertar nosso comportamento esperado

CodePen do código acima.

Na primeira imagem, as duas funções de setState usam diretamente this.state.counter e, como aprendemos anteriormente, this.state.counter ainda será zero depois que a primeira função de setState for chamada. Assim, obtemos 1 em vez de 2, pois as duas funções setState estão definindo o contador como 1.

Na segunda imagem, passamos a setState uma função que garantirá que as duas funções setState sejam executadas em ordem. Além disso, ela guarda um instantâneo do state, em vez de usar o state atual e não atualizado. Agora, temos o nosso resultado esperado de 2.

Isso é tudo o que você precisa saber sobre o state em React!

4. React Context

Isso nos leva agora ao React Context, que é apenas o state global para os componentes.

A API do React Context permite que você crie objetos de contexto global, que podem ser passados a qualquer componente que você criar. Isso permite que você compartilhe dados sem ter que passar props por todo o caminho através da árvore do DOM.

Então, como usamos o Context?

Primeiro, crie um objeto para o Context:

const ContextObject = React.createContext({ foo: "bar" })

A documentação do React descreve a configuração do Context em um componente, assim:

MyClass.contextType = MyContext;

No entanto, no CodePen (React 16.4.2), isso não funcionou. Em vez disso, usaremos um HOC para consumir o Context de modo semelhante àquele recomendado por Dan Abramov.

function contextWrapper(WrappedComponent, Context) {
  return class extends React.Component {
    render() {
      return (
        <Context.Consumer>
          { context => <WrappedComponent context={context} { ...this.props } /> }
        </Context.Consumer>
      )
    }
  }
}

O que estamos fazendo é envolver nosso componente com o componente Context.Consumer e passá-lo no Contexto como uma prop.

Agora, podemos escrever algo assim:

class Child extends React.Component {
  render() {
    console.log(this.props.context)
    return <div>Child</div>
  }
}
const ChildWithContext = contextWrapper(Child, AppContext)

Teremos, então, acesso ao foo do nosso objeto do Context nas props.

Como mudamos o Context, você pode estar se perguntando. Infelizmente, é um pouco mais complicado, mas podemos usar um HOC novamente, assim:

function contextProviderWrapper(WrappedComponent, Context, initialContext) {
  return class extends React.Component {
    constructor(props) {
      super(props)
      this.state = { ...initialContext }
    }
    
    // define any state changers
    changeContext = () => {
      this.setState({ foo: 'baz' })
    }

    render() {
      return (
        <Context.Provider value={{
          ...this.state,
          changeContext: this.changeContext
        }} >
          <WrappedComponent />
        </Context.Provider>
      )
    }
  }
}

Vamos examinar o código. Primeiro, pegamos o state do Context inicial, o objeto que passamos para React.createContext() e o definimos como o state do nosso componente wrapper. Em seguida, definimos os métodos que vamos usar para mudar nosso state. Por fim, envolvemos nosso componente com o componente Context.Provider. Passamos nosso state e a função para a prop de valor. Agora, todos os elementos filhos os obterão do Context quando encapsulados com o componente Context.Consumer.

Unindo todos os conceitos (HOCs omitidas para reduzir o tamanho):

const initialContext = { foo: 'bar' }
const AppContext = React.createContext(initialContext);

class Child extends React.Component {
  render() {
    return (
      <div>
        <button onClick={this.props.context.changeContext}>Click</button>
        {this.props.context.foo}
      </div>
     )
  }
}

const ChildWithContext = contextConsumerWrapper(Child, AppContext)
const ChildWithProvide = contextProviderWrapper(ChildWithContext, AppContext, initialContext)

class App extends React.Component {
  render() {
    return (
      <ChildWithProvide />
    );
  }
}

Agora, nosso componente filho tem acesso ao Context global. Ele tem a capacidade de alterar o atributo foo no state para bar.

Aqui está um link para o CodePen completo para o código do Context.

5. Mantenha-se atualizado com o React!

Este último conceito é provavelmente o mais fácil de entender. Basta simplesmente acompanhar os últimos lançamentos do React. O React fez algumas mudanças sérias ultimamente e só vai continuar a crescer e se desenvolver.

Por exemplo, no React 16.3, alguns métodos do ciclo de vida foram abandonados. No React 16.6, temos componentes assíncronos. No 16.7, temos os hooks, que buscam substituir completamente os componentes de classe.

Conclusão

Obrigado pela leitura! Espero que tenham gostado do texto e aprendido muito sobre o React. Embora eu espere que você tenha aprendido muito apenas lendo, encorajo você a experimentar com todos esses recursos/peculiaridades por si mesmo. Ler é uma coisa, mas a única maneira de dominar o React é fazendo você mesmo!

Por fim, siga programando. Aprender uma nova tecnologia pode parecer assustador, mas quando você se der conta, já será um especialista em React.

Boa programação para você.