Capítulo 6
React: jogo do Ateliê
3 min de leitura 452 palavras 1 gravura
Neste capítulo você vai montar um jogo da velha temático para praticar estado imutável. O projeto-fio é o Ateliê Boa Viagem, ateliê digital fictício em Boa Viagem, Recife.
Seu ponto de partida
Use o exemplo 06-react-jogo do pacote anexo e copie inicio/ para uma pasta de prática fora do original. A raiz do exemplo é a referência completa desta etapa.
Use servidor só em 127.0.0.1 quando o README do exemplo pedir HTTP local. Abrir ou testar não publica nada.
Como praticar
Leia → copie ou digite → mude uma coisa → preveja → salve → recarregue → confira. Restaure cada experiência antes da próxima. Faça as atividades antes de abrir as respostas comentadas.

Estado do tabuleiro em array
Nove casas em um array. Cada jogada cria um novo array em vez de alterar o anterior. Isso facilita voltar jogadas depois.
Faça na sua cópia
Confira o trecho no contexto do exemplo antes de mudar.
const next = squares.slice();
next[i] = xIsNext ? 'X' : 'O';
setSquares(next);
Mude uma coisa e preveja
Impida clicar numa casa já preenchida.
Anote o que espera observar antes de salvar e recarregar.
Recarregue e confira
A casa ocupada ignora o clique.
Se não acontecer
Return cedo se squares[i] já tiver valor.
Calcular vencedor
Uma função pura recebe o array e devolve o vencedor ou null. Funções puras são fáceis de testar mentalmente.
Faça na sua cópia
Confira o trecho no contexto do exemplo antes de mudar.
function calculateWinner(sq) {
const lines = [[0,1,2],[3,4,5],[6,7,8],[0,3,6],[1,4,7],[2,5,8],[0,4,8],[2,4,6]];
for (const [a,b,c] of lines) {
if (sq[a] && sq[a] === sq[b] && sq[a] === sq[c]) return sq[a];
}
return null;
}
Mude uma coisa e preveja
Mostre o vencedor no status.
Anote o que espera observar antes de salvar e recarregar.
Recarregue e confira
Três iguais anunciam o fim.
Se não acontecer
Pare de aceitar jogadas quando houver vencedor.
Reiniciar sem recarregar a página
Um botão redefine o estado inicial. Não use location.reload como única solução.
Faça na sua cópia
Confira o trecho no contexto do exemplo antes de mudar.
<button onClick={() => setSquares(Array(9).fill(null))}>Nova partida</button>
Mude uma coisa e preveja
Reinicie também o indicador de vez.
Anote o que espera observar antes de salvar e recarregar.
Recarregue e confira
Tabuleiro limpo e X começa de novo.
Se não acontecer
Redefina xIsNext para true.
Atividades
Atividade 1
Por que copiar o array com slice antes de alterar?
Atividade 2
O que é uma função pura neste jogo?
Atividade 3
Como reiniciar sem location.reload?
Respostas comentadas
Resposta da atividade 1
Preservar imutabilidade e permitir histórico/comparação.
Resposta da atividade 2
calculateWinner: só depende da entrada e não altera fora.
Resposta da atividade 3
Resetar os estados com os valores iniciais.
O arquivo completo
Referência do exemplo 06-react-jogo, reproduzida aqui para que você possa conferir sem sair da página.
styles.css
:root{--ink:#1a1a1a;--bg:#f7f4ef;--brand:#1f3a5f;--accent:#c45c26;--line:#d9d2c5;--focus:#0b57d0}
*{box-sizing:border-box}html{font-family:"Segoe UI",Tahoma,sans-serif;color:var(--ink);background:var(--bg)}
body{margin:0;line-height:1.5}a:focus-visible,button:focus-visible,input:focus-visible{outline:3px solid var(--focus);outline-offset:2px}
.skip{position:absolute;left:-999px}.skip:focus{left:1rem;top:1rem;z-index:10;background:#fff;padding:.5rem}
header,footer{background:var(--brand);color:#fff;padding:1rem 1.25rem}header a,footer a{color:#fff}
main{max-width:52rem;margin:0 auto;padding:1.25rem}h1{color:var(--brand);margin-top:0}
.card{background:#fff;border:1px solid var(--line);padding:1rem;margin:.75rem 0}
button{background:var(--accent);color:#fff;border:0;padding:.55rem 1rem;cursor:pointer}
.board{display:grid;grid-template-columns:repeat(3,4.5rem);gap:.4rem}
.square{width:4.5rem;height:4.5rem;font-size:1.5rem;background:#fff;border:2px solid var(--brand)}
label{display:block;margin:.5rem 0 .2rem;font-weight:600}
input,textarea{width:100%;max-width:28rem;padding:.45rem;border:1px solid var(--line)}
ul.nav{list-style:none;padding:0;display:flex;gap:1rem;flex-wrap:wrap;margin:0}
.status{font-weight:600;margin:.75rem 0}