Edit on GitHub

Estado(state) e ciclo de vida

Considerando o exemplo de relógio tique-taque de uma das seções anteriores.

Até agora aprendemos apenas uma maneira de atualizar a interface do usuário.

Nós chamamos ReactDOM.render() para alterar a saída renderizada:

function tick() {
  const element = (
    <div>
      <h1>Hello, world!</h1>
      <h2>It is {new Date().toLocaleTimeString()}.</h2>
    </div>
  );
  ReactDOM.render(
    element,
    document.getElementById('root')
  );
}

setInterval(tick, 1000);

Teste este código no CodePen.

Nesta seção, aprenderemos como tornar o componente Clock verdadeiramente reusável e encapsulado. Ele irá setar seu próprio horário e se atualiza-rá a cada segundo.

Nós podemos começar por encapsular como o relógio se parece:

function Clock(props) {
  return (
    <div>
      <h1>Hello, world!</h1>
      <h2>It is {props.date.toLocaleTimeString()}.</h2>
    </div>
  );
}

function tick() {
  ReactDOM.render(
    <Clock date={new Date()} />,
    document.getElementById('root')
  );
}

setInterval(tick, 1000);

Teste este código no CodePen.

Entretanto, esquecemos um requisito crucial: o fato de que o Clock setar o horário e atualizar a interface do usuário a cada segundo deve ser um detalhe de implenentação do Clock.

Idealmente queremos escrever apenas uma vez e ter o Clock atualizando-se:

ReactDOM.render(
  <Clock />,
  document.getElementById('root')
);

Para implementar isto, nós precisamos adicionar "state(estado)" ao componente Clock.

State(estado) é similar ao props(propriedades), mas ele é privado e totalmente controlado pelo componente.

Nós mencionamos antes que os componentes definidos como classes tem alguns recursos adicionais. Estado local é exatamente isso: um recurso disponível apenas para classes.

Convertendo uma função para uma classe #

Você pode converter um componente funcional como o Clock para uma classe em cinco passos:

  1. Crie uma classe ES6 com o mesmo nome que extenda de React.Component.

  2. Adicione um método vazio e único chamado render().

  3. Mova o corpo da função para dentro do método render().

  4. Altere props para this.props no corpo do método render().

  5. Delete o restante da declaração vazia da função.

class Clock extends React.Component {
  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.props.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}

Teste este código no CodePen.

Clock agora é definida como uma classe aon invés de uma função.

Adicionando estado local para a classe #

Nós iremos mover date de props para state em três passos:

1) Substitua this.props.date com this.state.date no método render():

class Clock extends React.Component {
  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}

2) Adicione um construtor de classe que atribua o estado inicial de this.state:

class Clock extends React.Component {
  constructor(props) {
    super(props);
    this.state = {date: new Date()};
  }

  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}

Note como passamos props para o construtor:

  constructor(props) {
    super(props);
    this.state = {date: new Date()};
  }

Componentes de classes devem sempre chamar o construtor com props;

3) Remova o prop date do elemento <Clock /> element:

ReactDOM.render(
  <Clock />,
  document.getElementById('root')
);

Iremos depois adicionar o código do horário de volta para o componente em si.

O resultado se parece com isto:

class Clock extends React.Component {
  constructor(props) {
    super(props);
    this.state = {date: new Date()};
  }

  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}

ReactDOM.render(
  <Clock />,
  document.getElementById('root')
);

Teste este código no CodePen.

Próximo, nós fazer o Clock setar seu próprio horário e atualizar-se a cada segundo.

Adicionando métodos do ciclo de vida a classe #

Em aplicações com muitos componentes, é muito importante liberar recursos obtidos pelos componentes quando eles forem destruídos.

Nós queremos setar o horário sempre que o Clock é renderizado no DOM pela primeira vez. Isto é chamado de "mounting"(montagem) em React.

Nós também queremos limpar o horário sempre que o DOM produzido pelo Clock for removido. Isto é chamado de "unmounting"(desmontagem) en React.

Nós podemos declarar métodos especiais na classe do componente para executar alguns códigos quando um componente mount e quando unmount.

class Clock extends React.Component {
  constructor(props) {
    super(props);
    this.state = {date: new Date()};
  }

  componentDidMount() {

  }

  componentWillUnmount() {

  }

  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}

Estes métodos são chamados ganchos do ciclo de vida("lifecycle hooks").

O gancho componentDidMount executa depois que a saída do componente é renderizada para o DOM. Este é um bom lugar para setar o horário:

  componentDidMount() {
    this.timerID = setInterval(
      () => this.tick(),
      1000
    );
  }

Note how we save the timer ID right on this.

While this.props is set up by React itself and this.state has a special meaning, you are free to add additional fields to the class manually if you need to store something that is not used for the visual output.

If you don't use something in render(), it shouldn't be in the state.

We will tear down the timer in the componentWillUnmount() lifecycle hook:

  componentWillUnmount() {
    clearInterval(this.timerID);
  }

Finally, we will implement the tick() method that runs every second.

It will use this.setState() to schedule updates to the component local state:

class Clock extends React.Component {
  constructor(props) {
    super(props);
    this.state = {date: new Date()};
  }

  componentDidMount() {
    this.timerID = setInterval(
      () => this.tick(),
      1000
    );
  }

  componentWillUnmount() {
    clearInterval(this.timerID);
  }

  tick() {
    this.setState({
      date: new Date()
    });
  }

  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}

ReactDOM.render(
  <Clock />,
  document.getElementById('root')
);

Teste este código no CodePen.

Agora o horário muda a cada segundo.

Vamos recaptular rapidamente o que está acontecendo e a ordem que os métodos são chamados:

1) Quando <Clock /> é passado para ReactDOM.render(), React chama o construtor do componente Clock. Uma vez que Clock precisa exibir o horário atual, ele inicializa o this.state com um objeto incluindo o horário atual. Mais tarde iremos atualizar este state.

2) React então chama o método render() do componente Clock. É assim que o React aprende o que deve ser exibido na tela. React então atualiza o DOM com a saída da renderização do Clock.

3) Quando a saída de Clock é inserida no DOM, React chama o gancho do ciclo de vida componenteDidMount(). Dentro dele, o componente Clock pede ao browser que configure um temporizador para chamar tick() a cada segundo.

4) A cada segundo o browser chama o método tick(). Dentro dele, o componente Clock agenda uma atualização da interface de usuário chamando setState() com um objeto contendo horário atual. Graças a chamada ao setState(), React sabe que o estado mudou, e chama o método render() novamente para aprender o que deve ser exibido na tela. Desta vez, this.state.date no método render() será diferente, então a saída do render incluirá o horário atualizado. Na sequência React atualiza o DOM.

5) Se o componente Clock for removido do DOM, React chama o gancho do ciclo de vida componentWillUnmount então o temporizador é parado.

Usando State corretamente #

Existem três coisas que você precisa saber sobre setState().

Não modifique o State diretamente #

Por exemplo, isto não irá re-renderizar um componente:

// Errado
this.state.comment = 'Hello';

Ao invés disso, use setState():

// Correto
this.setState({comment: 'Hello'});

O único lugar que você pode atribuir this.state é no construtor.

Atualizações de State podem ser assíncronas #

Por performance, React pode chamar múltiplos setState() em uma única atualização.

Por causao do this.props e this.state poder ser atualizados asíncronamente, você não deve confiar em seus valores para calcular o próximo estado.

Por exemplo, este código pode falhar falhar ao atualizar o contador:

// Wrong
this.setState({
  counter: this.state.counter + this.props.increment,
});

Para resolver isso, use uma segunda forma de setState() que aceita uma função ao invés de um objeto. Esta função receberá o estado anterior como primeiro argumento, e o props no tempo em que a atualização é aplicada como segundo argumento:

// Correto
this.setState((prevState, props) => ({
  counter: prevState.counter + props.increment
}));

Nós usamos uma função de seta (arrow function) acima, mas também funciona com funções regulares:

// Correto
this.setState(function(prevState, props) {
  return {
    counter: prevState.counter + props.increment
  };
});

Atualizações de State são mesclados #

Quando você chama setState(), React mescla o objeto fornecido dentro do estado atual.

Por exemplo, seu state pode conter várias variáveis independentes:

  constructor(props) {
    super(props);
    this.state = {
      posts: [],
      comments: []
    };
  }

Então, você pode atualizá-lo independentemente com chamadas separadas de setState():

  componentDidMount() {
    fetchPosts().then(response => {
      this.setState({
        posts: response.posts
      });
    });

    fetchComments().then(response => {
      this.setState({
        comments: response.comments
      });
    });
  }

A mescla é superficial, então this.setState({comments}) deixa this.state.posts intacto, porém substitui this.state.comments completamente.

Os dados fluem para baixo #

Nem os componentes pai nem filhos podem saber se determinado componente é com estado(statefull) or sem estado(stateless), e eles não devem se importar se são definidos como função ou uma classe.

É por isto que state é frequentemente chamdo de local ou encapsulado. Não é acessível a nenhum outro componente que não seja aquele que o possui e o configura.

Um componente deve escolher passar seu estado para baixo como props para seus componentes filhos:

<h2>It is {this.state.date.toLocaleTimeString()}.</h2>

Isto também funciona para componenet definidos pelo usuário:

<FormattedDate date={this.state.date} />

O componente FormattedDate deve receber o date em seus props e não deve saber se vem do state de Clock, de props, ou se foi digitado a mão:

function FormattedDate(props) {
  return <h2>It is {props.date.toLocaleTimeString()}.</h2>;
}

Teste este código no CodePen.

Isto é comumente chamado de fluxo de dados de "baixo para cima" ou "unidirecional". Qualquer estado é sempre propriedade de algum componente específico, e qualquer informação ou interface de usuário entrege por um estado só pode afetar componentes "abaixo" deles na árvore.

Se você imaginar uma árvore de componente como uma cachoeira de props, cada state do componente é como uma fonte de água adicional, que se junta a ele em algum ponto arbitrário mas também flui para baixo.

Para mostrar que todos os componentes são verdadeiramente isolados, nós criamos um componente App que renderiza três <Clock />s:

function App() {
  return (
    <div>
      <Clock />
      <Clock />
      <Clock />
    </div>
  );
}

ReactDOM.render(
  <App />,
  document.getElementById('root')
);

Teste este código no CodePen.

Cada Clock configura seu próprio horário e os atualiza independentemente.

Em aplicações React, se um componente é com estado ou sem estado é considerado um detalhe de implementação de um componente que pode mudar ao longo do tempo. Você pode usar componentes sem estado dentro de componentes com estado e vice e versa.