Closed MateusCavalheiro-prog closed 1 year ago
Olá @MateusCavalheiro-prog
Passando para dizer que a issue foi visualizada e em até 10 dias úteis a análise será feita =)
Olá @MateusCavalheiro-prog
Parabéns pelo esforço em construir a primeira aplicação do treinamento =)
Como foi minha primeira aplicação minha maior dificuldade foi quebrar os problemas em partes e organizar o código.
Mesmo com a dificuldade relatada, você conseguiu!
Essa habilidade de organização é uma habilidade que é elevada com a prática.
Conforme você praticar, você vai percebendo aos poucos o que pode ser melhorado.
Tive dificuldade em nomear as funções e variáveis para que elas fizessem sentido no código.
No geral você escolheu bons nomes para as funções e variáveis, mas de qualquer forma, fica aqui uma sugestão:
Um princípio que gosto de seguir ao nomear é:
getName
, setName
, incrementCounter
;user
, score
, color
. is
, has
ou should
. isOdd
, hasSevenItems
, shouldUpdate
.Recomendo que você siga esse princípio ao nomear, é uma excelente maneira de deixar cada parte do código bem específica em relação aos nomes.
tive um pouco de dificuldade em fazer aparecer o popup com o resultado do quiz devido as classes do bootstrap, mas acredito que eu tenha conseguido resolver.
Sem problemas, não há certo ou errado na forma de exibir os pontos, o mais importante num primeiro momento você já fez, que é implementar a lógica de exibição. De qualquer forma, uma maneira de implementar os pontos será mostrada nas aulas seguintes.
Quanto à estilização, o uso do Bootstrap (ou qualquer lib CSS) é opcional.
Veja o que fica mais confortável pra você.
Você deixou a primeira alternativa de cada pergunta marcada por padrão. Acredito que isso incentiva o usuário a marcar a resposta que ele realmente acredita que seja a verdadeira.
Outra boa decisão que você tomou foi a de deixar o input dentro da label. Isso faz com que o usuário não precise posicionar o mouse e clicar exatamente no input type radio. Basta clicar no texto da alternativa =)
Uma ideia interessante que você teve em relação à usabilidade foi a de posicionar a mensagem dos pontos de forma que ela estivesse bem próxima do centro da tela após clicar no botão. Isso é bom pois a pontuação aparece justamente onde o usuário estará olhando após enviar as respostas.
Você entendeu que não era necessário manipular DOM dentro do forEach (um erro comum que algumas pessoas cometem). A ideia do forEach ali naquele momento era apenas calcular os pontos, exatamente como vc fez =)
Vou deixar abaixo algumas observações sobre sua aplicação
No index.html há alguns trechos de código comentados, não é uma regra, mas evite comentários.
Comentários podem mentir se o código for modificado e o comentário correspondente não for atualizado, o comentário perde o sentido. Isso sem mencionar o retrabalho de atualizar comentários.
Recomendo que ao invés de 3 espaços para indentação, use 2. E isso vale para qualquer linguagem (HTML, CSS, JS, etc).
Se um dia vc quiser enviar alguma sugestão ou modificação de artigo pro MDN, por exemplo, seu código terá que seguir o code-guideline deles:
E não só o MDN, mas muitas outras fontes recomendam que o código tenha 2 espaços ao invés de 3.
Seu olho irá cansar menos, pq suas linhas de código serão menos extensas, o que impacta na legibilidade e carga cognitiva na leitura.
À partir daqui, os exemplos do seu código que postarei abaixo já estarão formatados com 2 espaços, ao invés de 3.
Para configurar espaços no VSCode, dá uma olhada aqui:
Vc verá que nesse parágrafo, há um link para o get started de Settings do VSCode. Recomendo dar uma lida, mas não invista muito tempo nisso =)
Um princípio: linhas em branco indicam, visualmente, a separação entre conceitos. Procure evitar o excesso ou a falta delas no código.
Sabendo disso, você pode remover do início do código, a linha 5 que está em branco.
// antes
const correctAnswers = ['B', 'B', 'B', 'B']
const form = document.querySelector('.quiz-form')
const quizResultPopup = document.querySelector('.wrapper-card')
const getUserAnswers = form => {
// ...
}
// depois
const correctAnswers = ['B', 'B', 'B', 'B']
const form = document.querySelector('.quiz-form')
const quizResultPopup = document.querySelector('.wrapper-card')
const getUserAnswers = form => {
// ...
}
Ao longo do código, há algumas inconsistências em relação ao espaço antes e depois das chaves de abertura da arrow function:
// Espaço em excesso após a seta da arrow function
const scoreMessage = score => {
// ...
}
// Espaço em excesso após o sinal de atribuição da função
const showResultQuiz = event => {
// ...
}
// Função declarada sem nenhuma ocorrência de espaços em excesso
const getUserAnswers = form => {
// ...
}
Uma sugestão é que você escolha uma das formas e seja consistente com essa escolha no decorrer do código. Por convenção, recomendo que evite deixar esses espaços em branco e prefira declarar uma função como a última mostrada no bloco acima.
Futuramente, você pode usar uma ferramenta automatizada de formatação de código, mas nesse momento da sua jornada, eu recomendo que vc formate manualmente.
Isso vai "forçar" vc a adotar um estilo de escrita de código e ser consistente com esse estilo ao implementar suas aplicações.
Feito esses ajustes, nessa observação e nas seguintes, vou te mostrar duas abordagens de refactoring usando métodos que você vai encontrar em breve nas aulas seguintes, ambos serão explicados com detalhes futuramente. O que quero que você entenda, nesse primeiro momento, é o conceito por trás deles. Sabendo disso, observe o código abaixo:
Podemos fazer uma abordagem interessante aqui:
const getUserAnswers = form => {
return [
form.inputQuestion1.value,
form.inputQuestion2.value,
form.inputQuestion3.value,
form.inputQuestion4.value
]
}
Você percebe todas essas repetições de form.inputQuestion.value
? Pois então, vamos fazer uma refatoração nessa função para que ela possa obter as respostas dos inputs do usuário sem precisar de todas essas repetições. Para isso, vamos manter apenas o nome getUserAnswers
, depois disso, nela podemos criar uma const userAnswer
que vai receber uma expressão que pega o valor de cada inputQuestion
do form. userAnswers
vai receber um array vazio que será preenchido após percorrermos cada item dentro do array correctAnswers
por meio do método forEach
. Ficaria assim:
const getUserAnswers = () => {
const userAnswers = []
correctAnswers.forEach((_, index) => {
const userAnswer = form[`inputQuestion${index + 1}`].value
userAnswers.push(userAnswer)
})
return userAnswers
}
Tudo certo até aqui? Se sim, vamos prosseguir! A ideia do código acima é a seguinte, baseado em um array, você quer obter um novo array que contém a mesma quantidade de itens do array original. Por quê? correctAnswers
tem quatro itens e userAnswers
quando está sendo retornado também vai ter quatro itens. Lembre-se da ideia de que quando baseado em um array, você precisa obter um novo array com a mesma quantidade de itens do array original, você pode usar um método chamado map
. Sabendo disso, ao invés de userAnswers
ser declarada como um array vazio, o userAnswers
pode receber o retorno dessa expressão. Feito isso, ao invés de executar o método push
, é necessário apenas retornar userAnswers
. Veja abaixo:
const getUserAnswers = () => {
const userAnswers = correctAnswers.map((_, index) => {
const userAnswer = form[`inputQuestion${index + 1}`].value
return userAnswer
})
return userAnswers
}
Feito isso, você pode refatorar a função ainda mais ao usar os retornos implícitos das arrow functions, removendo os blocos e retornos:
const getUserAnswers = () => correctAnswers.map((_, index) => form[`inputQuestion${index + 1}`].value)
Fazendo isso, o map
vai ser executado para cada item de correctAnswers
, o callback está retornando o valor da resposta do usuário, esse valor que o callback está retornando vai ser inserido num novo array que o map
está gerando por baixo dos panos. A cada execução desse callback, o valor que a função está retornando vai ser inserido nesse novo array que o map
está gerando por baixo dos panos. Quando esse callback for executado para todos os itens, o map
vai retornar esse novo array que ele gerou e toda a expressão vai resultar no array que o map
retornou.
Por fim, a getUserAnswers
já vai estar funcionando normalmente e você pode manter a const userAnswers
armazenando a invocação dela, porém não será mais necessário passar o form
como argumento dessa função em showResultQuiz
.
const showResultQuiz = event => {
event.preventDefault()
const userAnswers = getUserAnswers()
// ...
}
Há um padrão que você estabeleceu nesse trecho do código que é o da porcentagem, é uma boa prática inserir uma porcentagem na mensagem dos pontos. Isso é bom pq traz mais clareza do progresso do usuário no quiz em relação a pontuação máxima. Você pode fazer o mesmo para caso o usuário não acerte nenhuma alternativa, a vantagem disso, além dos benefícios citados acima, é manter o padrão estabelecido com as outras pontuações:
// antes
const scoreMessage = score => {
return {
0: 'Mais sorte da proxima vez! você não acertou nenhuma =(',
25: 'Parabéns você acertou 25%!',
50: 'Parabéns você acertou 50%!',
75: 'Parabéns você acertou 75%!',
100: 'Exelente você acertou 100%!',
}[score]
}
// depois
const scoreMessage = score => {
return {
0: 'Mais sorte da proxima vez! você não acertou nenhuma, 0% =(',
25: 'Parabéns você acertou 25%!',
50: 'Parabéns você acertou 50%!',
75: 'Parabéns você acertou 75%!',
100: 'Exelente você acertou 100%!',
}[score]
}
Se fizer sentido para você, no intuito de melhorar a legibilidade do código, você também pode renomear a função showResultQuiz
para algo como showScore
. Dessa forma, você reduz a quantidade de caracteres, além de que ao bater o olho no nome da função, a ação que ela executa fica mais explícita. Dessa forma, a leitura e escrita do código se torna mais fácil. Ficaria assim:
// antes
const showResultQuiz = event => {
// ...
}
// depois
const showScore = event => {
// ...
}
Para a segunda abordagem, quero que observe o código abaixo:
userAnswers.forEach((useranswer, index) => {
const correctAnswer = useranswer === correctAnswers[index]
if (correctAnswer) {
score += 25
}
})
Toda a ideia do código acima é a seguinte: baseado em um array, você quer gerar um número, isto é, um número que seja a pontuação. E quando baseado num array você quer gerar um número, você pode fazer isso de forma funcional, sem efeito colateral e sem reatribuições e ainda pode diminuir algumas linhas do código. A forma que você pode fazer isso é, por exemplo, por meio do método reduce()
. Mas como isso pode ser feito? Vamos lá, acompanhe comigo codando passo a passo. Você pode criar uma função própria para obter a pontuação, essa função se chamará getScore
, vamos declarar um parâmetro que também se chamará userAnswers
e esse parâmetro também tera um default parameter de array vazio. Por esse momento, vamos manter uma lógica similar a que você usou dentro dessa nova função:
const getScore = (userAnswers = []) => {
userAnswers.forEach((userAnswer, index) => {
const correctAnswer = userAnswer === correctAnswers[index]
if (correctAnswer){
score += 25
}
})
}
Depois, a let score
que antigamente recebia o valor 0, agora vai ser uma const score
e irá receber o retorno da expressão que antes era do forEach
, mas agora contém o método reduce
. O reduce vai receber um parâmetro chamado accumulator
e como segundo argumento, ele irá receber um valor inicial 0. Por quê 0? Porque 0 é o ponto de partida do valor que você deseja obter na pontuação e porque é uma boa prática usar o segundo argumento do reduce
para indicar visualmente o tipo de valor que o reduce deve retornar. Feito isso, se a resposta estiver correta, o valor de accumulator
somado com 25 será retornado, se a condição do if não for verdadeira, apenas o valor de accumulator
vai ser retornado, ficaria assim:
const getScore = (userAnswers = []) => {
const score = userAnswers.reduce((accumulator,userAnswer,index) => {
const correctAnswer = userAnswer === correctAnswers[index]
if (correctAnswer){
return accumulator + 25
}
return accumulator
}, 0)
return score
}
Na primeira execução dessa função, accumulator
vai receber o segundo argumento do reduce
, userAnswer
vai receber o primeiro item do array e o index
é o mesmo index do item do array. Se a resposta estiver correta, a função vai retornar accumulator + 25, que na primeira execução será (0 + 25). Por outro lado, se a resposta não estiver correta, accumulator
será retornado (na primeira execução, o accumulator
estará armazenando 0). Quando a função for executada pela segunda vez, o parâmetro accumulator
estará armazenando o valor que foi retornado na execução passada da função, então se na primeira execução for retornado accumulator + 25, na segunda execução o parâmetro accumulator
estará armazenando 25 ao invés de 0 como na primeira execução. Assim, a operação será feita novamente. E isso vai se repetir para cada item do array userAnswers
. A const score
está recebendo o resultado dessa expressão e por fim, esse valor é retornado.
Há mais algumas refatorações que você pode fazer para o código se tornar ainda mais conciso, você pode eliminar os returns para aproveitar o return implícito da arrow function e ao invés de usar o bloco de if, você pode usar um ternário, você também ira aprender mais sobre o ternário posteriormente, mas para você ter uma ideia, se o que está a esquerda do sinal de ? for um valor truthy
, accumulator + 25 é retornado, caso contrário, apenas accumulator
será retornado. O score pode ser retornado diretamente:
// Usando o operador ternário no lugar do bloco do if
const getScore = (userAnswers = []) => {
return userAnswers.reduce((accumulator, userAnswer, index) => {
return userAnswer === correctAnswers[index] ? accumulator + 25 : accumulator
}, 0)
}
// Usando o return implícito das arrow functions
const getScore = (userAnswers = []) => userAnswers
.reduce((accumulator, userAnswer, index) => userAnswer === correctAnswers[index] ? accumulator + 25 : accumulator, 0)
Por fim, você pode invocar a função getScore()
dentro da função showScore
recebendo o array armazenado por userAnswers
como argumento. A função atualizada ficaria assim:
const showScore = event => {
event.preventDefault()
const userAnswers = getUserAnswers()
const score = getScore(userAnswers)
const scoreFeedback = scoreMessage(score)
// ...
}
Na função showPopup
, não é necessário que você remova a classe para exibir o popup, você pode adicionar a classe d-flex
sem prejudicar nenhuma funcionalidade e ainda assim, o código estará funcionando normalmente. Além disso, a função terá um parâmetro a menos e se tornará mais curta. Ficaria assim:
// antes
const showPopup = (popup, removeClass, addClass, scoreFeedback) => {
popup.classList.remove(removeClass)
popup.classList.add(addClass)
popup.innerHTML = `
<div class="card" style="width: 28rem;">
<button class="close-card">X</button>
<div class="card-body d-flex flex-column justify-content-center">
<h5 class="card-title text-center">${scoreFeedback}</h5>
<a href="#" class="btn btn-primary">Reiniciar</a>
</div>
</div>
`
}
// depois
const showPopup = (popup, addClass, scoreFeedback) => {
popup.classList.add(addClass)
popup.innerHTML = `
<div class="card" style="width: 28rem;">
<button class="close-card">X</button>
<div class="card-body d-flex flex-column justify-content-center">
<h5 class="card-title text-center">${scoreFeedback}</h5>
<a href="#" class="btn btn-primary">Reiniciar</a>
</div>
</div>
`
}
Uma outra sugestão seria, se você achar que faz sentido, fazer um destructuring ao invés de usar a sintaxe de colchetes na const elementPopup
. Você pode tornar o código mais legível ao usar uma abordagem envolvendo o destructuring. Na const elementPopup
, a sintaxe de colchetes está sendo usada para pegar o primeiro elemento do array ao clicar em qualquer lugar na tela. Isso também pode ser feito por meio do destructuring. Ao envolver por colchetes a const elementPopup
, você está colocando-a na primeira posição no destructuring de array, ou seja, o primeiro item do array vai para a essa const, pois ela se torna o primeiro item do destructuring. Ficaria assim:
// antes
const removePopup = event => {
const elementPopup = event.target.classList[0]
// ...
}
// depois
const removePopup = event => {
const [elementPopup] = event.target.classList
// ...
}
Há também um outro detalhe, para a funcionalidade de fechar a janela do popup, você não precisa especificar cada uma das classes que o elementPopup
pode armazenar. Ao invés disso, você pode deixar esse código muito mais curto ao fazer com que closePopup
receba apenas elementPopup
, sabendo que o mesmo irá receber uma das três classes dependendo de onde foi o click, a lógica do if para fechar o popup continuará funcionando normalmente. Ficaria assim:
// antes
const closePopup = elementPopup === 'btn' || elementPopup === 'close-card' || elementPopup === 'wrapper-card'
// depois
const closePopup = elementPopup
Por fim, se fizer sentido para você, a sugestão aqui é colocar a linha do form.reset fora do bloco do if, a ideia aqui é manter no bloco do if apenas o código referente ao fechamento do popup, a vantagem aqui é para que assim esse trecho se responsabilize por apenas esse funcionamento. O código completamente refatorado dessa função seria assim:
// antes
const removePopup = event => {
const [elementPopup] = event.target.classList
const closePopup = elementPopup
if (closePopup) {
quizResultPopup.classList.remove('d-flex')
quizResultPopup.classList.add('d-none')
form.reset()
}
}
// depois
const removePopup = event => {
const [elementPopup] = event.target.classList
const closePopup = elementPopup
if (closePopup) {
quizResultPopup.classList.remove('d-flex')
quizResultPopup.classList.add('d-none')
}
form.reset()
}
Nas aulas seguintes, você verá outras formas de refatorar o código dessa aplicação.
Mais uma vez, parabéns pelo esforço =)
As observações fizeram sentido?
Sim as observações fizeram sentido! Vou prestar mais atenção sobre padronizar a escrita do código era algo que eu nem tinha pensado antes.
Fiquei com uma dúvida sobre o map, vou tentar explicar com as minhas palavras. Eu entendi que o map retorna um novo array com a mesma quantidade de itens do array correctAnawers(o array das respostas certas), eu entendi que você "omitiu" o primeiro parâmetro do map e usou apenas o index para que de forma dinâmica o inputQuestion fosse alterando para "inputQuestion1, 2, 3 etc...",.
Eu joguei um typeof no form e me retornou um objeto, ai eu entendi que como ele era um objeto a notação de colchete fazia sentido, pois você usou template strings para injetar o index que é uma variável local.
Mas eu não consegui entender porque o form é um objeto? As tags HTML também são objetos também?
const getUserAnswers = () => {
const userAnswers = correctAnswers.map((_, index) => {
const userAnswer = form[`inputQuestion${index + 1}`].value
return userAnswer
})
return userAnswers
}
@MateusCavalheiro-prog isso mesmo, tags HTML representam elementos do DOM, e elementos do DOM são objetos.
Isso foi mostrado nas aulas da etapa 05, recomendo que revise para reforçar e, no que tiver dúvidas, é só dar o toque =)
@MateusCavalheiro-prog só pra complementar o que escrevi acima, tags HTML são usadas pra definir elementos que formam a estrutura e o conteúdo de uma página web.
As tags em si não são consideradas objetos, mas sim instruções que indicam ao navegador como exibir o conteúdo.
Porém, os elementos criados a partir dessas tags são considerados objetos, pq possuem propriedades e métodos que podem ser manipulados através do JavaScript.
Ficou mais claro?
Ahh sim, agora minha mente abriu hahahah ! vou revisar a etapa 05 para fixar essas informações! Obrigado pela atenção.
As cores da sua versão da aplicação são diferentes das cores da aplicação mostrada na aula?
Sim
Sua aplicação contém funcionalidades que não foram mostradas nas aulas da aplicação?
Não
Sua aplicação contém funcionalidades da linguagem que não foram mostradas no CJRM até aqui?
Não
Link do repositório da aplicação (ou pasta pública no Google Drive)
https://github.com/MateusCavalheiro-prog/rock-quiz-cjrm.git
Maiores dificuldades durante a implementação
Menores dificuldades durante a implementação
-obter as referências das tags e valores das respostas do usuário. -usar o listeners de eventos. -iterar pelos array.
obs: meu email de cadastro no curso é: mateuscavalheiro02@gmail.com.