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);
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);
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.
Você pode converter um componente funcional como o Clock
para uma classe em cinco passos:
Crie uma classe ES6 com o mesmo nome que extenda de React.Component
.
Adicione um método vazio e único chamado render()
.
Mova o corpo da função para dentro do método render()
.
Altere props
para this.props
no corpo do método render()
.
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>
);
}
}
Clock
agora é definida como uma classe aon invés de uma função.
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')
);
Próximo, nós fazer o Clock
setar seu próprio horário e atualizar-se a cada segundo.
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')
);
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.
Existem três coisas que você precisa saber sobre setState()
.
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.
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
};
});
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.
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>;
}
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')
);
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.