roger-melo-treinamentos / curso-de-js-roger-melo

Repositório de informações do CJRM
494 stars 172 forks source link

Aplicação: [Quiz Interativo] #2832

Closed MivlaM closed 2 years ago

MivlaM commented 2 years ago

As cores da sua versão da aplicação são diferentes das cores da aplicação mostrada na aula?

Sim

A aplicação contém funcionalidades que não foram mostradas nas aulas?

Não

A aplicação contém features da linguagem que não foram mostradas nas aulas?

Não

Link do repositório da aplicação

https://github.com/MivlaM/Aplicacao-Quiz-Interativo-JS

Maiores dificuldades durante a implementação

Listo abaixo algumas:

-> Saber o que fazer após cada parte do código estar completa. Por exemplo, eu sabia que tinha que adicionar um eventListener no form e sabia o que tinha que pegar o valor do input do usuário, mas o processo de como fazer isso na prática me custaram mais minutos que eu pensei que custariam.

-> Organizar os elementos na tela, acho que isso é mais questão do HTML/CSS. Acredito que a imagem que inseri não esteja responsiva como desejado.

-> Após a refatoração do código, saber onde deixar cada pedaço do código. Acredito ter conseguido refatorar algumas partes, mas me surgiram dúvidas em relação a onde (em qual canto do código) devo colocá-las.

Menores dificuldades durante a implementação

-> Não tive problemas com a lógica de Javascript, tampouco com a funcionalidade dos comandos apresentados até o momento.

-> Compreender o que tinha que fazer e elaborar um ''primeiro plano'' para resolver os desafios.

-> Os desafios propostos para a minha versão do quiz não me foram difíceis, apesar de me sentir um pouco inseguro e ligeiramente ansioso para saber se consegui fazer mesmo o requisitado.

Roger-Melo commented 2 years ago

Olá @MivlaM.

Passando para dizer que visualizei a issue e vou pedir um pouquinho de paciência, pois nesse momento a demanda está muito alta. Em até 10 dias úteis a análise será postada aqui =)

Roger-Melo commented 2 years ago

Olá @MivlaM!

Parabéns pelo esforço em construir a primeira aplicação do treinamento =)

-> Saber o que fazer após cada parte do código estar completa. Por exemplo, eu sabia que tinha que adicionar um eventListener no form e sabia o que tinha que pegar o valor do input do usuário, mas o processo de como fazer isso na prática me custaram mais minutos que eu pensei que custariam.

Excelente.

Se vc elaborou um "primeiro plano", como descreveu em "Menores dificuldades", de como resolver um problema antes mesmo de colocar a mão na massa, isso é um indício que pegou bem a lógica até aqui =)


-> Organizar os elementos na tela, acho que isso é mais questão do HTML/CSS. Acredito que a imagem que inseri não esteja responsiva como desejado.

Como o foco do treinamento é JS, não vou me aprofundar neste tópico. Porém, vou deixar abaixo um material muito bom sobre imagens responsivas. Aliás, esse website tem posts incríveis sobre HTML e CSS =)


-> Após a refatoração do código, saber onde deixar cada pedaço do código. Acredito ter conseguido refatorar algumas partes, mas me surgiram dúvidas em relação a onde (em qual canto do código) devo colocá-las.

Vou falar sobre possíveis abordagens de refatoração mais abaixo (além das que vc verá nas aulas seguintes).


-> Os desafios propostos para a minha versão do quiz não me foram difíceis, apesar de me sentir um pouco inseguro e ligeiramente ansioso para saber se consegui fazer mesmo o requisitado.

Conseguir === fazer funcionar.

Fazer funcionar é o mais importante num primeiro momento, com vc fez =)


Boas decisões

Você entendeu que não era necessário manipular DOM dentro do forEach (um erro comum que alguns cometem). A ideia do forEach ali naquele momento era apenas calcular os pontos, exatamente como vc fez =)

O parâmetro do callback do forEach foi nomeado de forma coerente, como singular de itens do array no qual o forEach foi encadeado. Isso dá mais legibilidade ao ler a expressão userAnswer === correctAnswers[index].

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.

No geral, você escolheu nomes descritivos para suas variáveis e funções =)


Usabilidade

Prefira deixar o input dentro da label, como foi mostrado nas aulas. Fazendo isso, o usuário não precisa posicionar o mouse e clicar exatamente no input type radio. Basta clicar no texto da alternativa.

Uma outra coisa que vc pode fazer para melhorar a usabilidade é deixar o usuário mais consciente do progresso dele no quiz em relação à pontuação total. Vc pode fazer isso com uma mensagem indicando a pontuação com porcentagem ("você acertou 50%") ou inserindo uma barra e pontuação total do quiz após a quantidade de acertos, exemplo: "Vossa nota final é de 50/100".

Um outro ponto que pode ser melhorado é o local da exibição dos pontos.

Talvez, depois que você hospedar o quiz no Netlify (como orientado na bateria de exercícios 20), alguém execute a aplicação no celular.

Nesse caso, é bom ficar atento ao o local onde a pontuação é exibida na tela.

No print abaixo, após enviar o form, a pontuação é exibida na parte inferior da tela:

image

A mensagem está, parcialmente, visível, mas vc pode testar exibi-la em um local em que o usuário está olhando no momento do envio e que não há necessidade dele rolar a tela. Poderia ser, por exemplo, logo acima do botão "Enviar".


CSS inline

O bootstrap é meio chatinho para adicionar cores diferentes da paleta que eles disponibilizam por padrão (precisa mexer com Sass, etc).

Porém, o princípio aqui é: evite declarar CSS inline em seu HTML.

Vc pode ver alguns motivos aqui: What's so bad about in-line CSS?.

Sempre que possível, use classes ao invés de CSS inline.


Formatação de atributos HTML

Cuidado com a formatação dos atributos de seus elementos CSS.

Não declare-os dessa forma style = "background-color: #530707;". É uma boa prática padronizar seus atributos declarando eles sem espaços em branco antes e/ou depois do sinal de atribuição (=).

Ainda sobre padronização, no código abaixo, um valor de atributo foi declarado com aspas simples e o outro com aspas duplas.

<body class='body-content' style = "background-color: #530707;">

Escolha uma das formas e seja consistente com essa escolha no decorrer do código. Por convenção, quando se trata de marcação HTML, recomendo que sempre escolha aspas duplas.


Linhas em branco

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 as linhas em branco em excesso 44 a 47, 28, 6 e 7.

Por convenção, prefira que a primeira e última linha de um bloco sejam linhas preenchidas com código.

// antes
form.addEventListener('submit', event=>{

    const correctAnswersCondition = (userAnswer, index) =>{
        // ...
    }

  // ...
})
// depois
form.addEventListener('submit', event=>{
    const correctAnswersCondition = (userAnswer, index) =>{
        // ...
    }

  // ...
})

let score = 0 pode ter uma linha em branco antes e depois de sua declaração.


event.preventDefault()

Prefira invocar event.preventDefault() na primeira linha da função.

Isso é uma convenção e faz mais sentido que um callback de submit informe na primeira linha que a página não será recarregada.

// antes
form.addEventListener('submit', event=>{
    const correctAnswersCondition = (userAnswer, index) =>{
        if (userAnswer === correctAnswers[index]) {
            score += 25
        }
    }

    event.preventDefault()
    // ...
})
// depois
form.addEventListener('submit', event=>{
    event.preventDefault()

    // ...
}

4 espaços vs 2

Recomendo que ao invés de 4 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 4.

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 4.

Para configurar espaços no VSCode, dá uma olhada aqui:

Indentation - VSCode

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 =)


Nomes de variáveis e funções

Um princípio que gosto de seguir ao nomear é:

Considerando isso...

correctAnswersCondition é uma função, então o nome dela deve descrever a ação que ela faz (verbo). Em vias de regra, o código dela calcula a pontuação do usuário, então ela pode se chamar, por exemplo, calculateScore.

// antes
  const correctAnswersCondition = (userAnswer, index) => {
    // ...
  }

  // ...

  userAnswers.forEach(correctAnswersCondition)
// depois
  const calculateScore = (userAnswer, index) => {
    // ...
  }

  // ...

  userAnswers.forEach(calculateScore)

p pode ter um nome mais descritivo sobre o valor que ela armazena.

Como essa const armazena um parágrafo que, em resumo, é uma mensagem sobre a pontuação, ela pode ser renomeada como scoreMessage. Observe como o código tende a ficar mais legível com essa mudança:

// antes
  const p = document.createElement('p')

  if (higherThanAverageScore) {
    p.style.color = 'White'
    p.textContent = `Minhas congratulações! Vossa nota final é de ${score} :>`
    event.target.insertAdjacentElement('afterend', p)
  } else {
    p.style.color = 'White'
    p.textContent = `Valeu a tentativa! Vossa nota final é de ${score}. Se for de vossa vontade, pode tentar novamente.`
    event.target.insertAdjacentElement('afterend', p)
  }
// depois
  const scoreMessage = document.createElement('p')

  if (higherThanAverageScore) {
    scoreMessage.style.color = 'White'
    scoreMessage.textContent = `Minhas congratulações! Vossa nota final é de ${score} :>`
    event.target.insertAdjacentElement('afterend', scoreMessage)
  } else {
    scoreMessage.style.color = 'White'
    scoreMessage.textContent = `Valeu a tentativa! Vossa nota final é de ${score}. Se for de vossa vontade, pode tentar novamente.`
    event.target.insertAdjacentElement('afterend', scoreMessage)
  }

Else

Não é necessário usar else no código abaixo.

A desvantagens do else é que ele adiciona um bloco desnecessário no código.

// antes
  if (higherThanAverageScore) {
    scoreMessage.style.color = 'White'
    scoreMessage.textContent = `Minhas congratulações! Vossa nota final é de ${score} :>`
    event.target.insertAdjacentElement('afterend', scoreMessage)
  } else {
    scoreMessage.style.color = 'White'
    scoreMessage.textContent = `Valeu a tentativa! Vossa nota final é de ${score}. Se for de vossa vontade, pode tentar novamente.`
    event.target.insertAdjacentElement('afterend', scoreMessage)
  }
// depois
  if (higherThanAverageScore) {
    scoreMessage.style.color = 'White'
    scoreMessage.textContent = `Minhas congratulações! Vossa nota final é de ${score} :>`
    event.target.insertAdjacentElement('afterend', scoreMessage)
    return
  }

  scoreMessage.style.color = 'White'
  scoreMessage.textContent = `Valeu a tentativa! Vossa nota final é de ${score}. Se for de vossa vontade, pode tentar novamente.`
  event.target.insertAdjacentElement('afterend', scoreMessage)

Além disso, geralmente, a abertura desse bloco fica na mesma linha que o fechamento do bloco do if e da palavra-chave else, e isso tende a deixar a leitura do código mais "carregada".

// antes
  if (higherThanAverageScore) {
    // ...
  } else {
    // ...
  }
// depois
  if (higherThanAverageScore) {
    // ...
  }

  // ...

Percebe a diferença no fluxo da leitura?


Eliminando repetições

Vc pode eliminar a repetição da manipulação de DOM ao inserir a mensagem.

Observe que a única informação que varia nas 3 linhas é a mensagem em si:

padrao-e-variacao

Quando existe um padrão com pequenas variações, você pode encapsular esse código em uma função e injetar o que varia por parâmetro =)

Como o código dessa função irá exibir a mensagem da pontuação, ela pode se chamar showScoreMessage.

Olha como fica:

const showScoreMessage = ({ scoreMessage, content, siblingElement }) => {
  scoreMessage.style.color = 'White'
  scoreMessage.textContent = content
  siblingElement.insertAdjacentElement('afterend', scoreMessage)
}

form.addEventListener('submit', event => {
  // ...

  if (higherThanAverageScore) {
    return showScoreMessage({ 
      scoreMessage, 
      content: `Minhas congratulações! Vossa nota final é de ${score} :>`, 
      siblingElement: event.target 
    })
  }

  showScoreMessage({ 
    scoreMessage, 
    content: `Valeu a tentativa! Vossa nota final é de ${score}. Se for de vossa vontade, pode tentar novamente.`,
    siblingElement: event.target 
  })
})

Observe que a repetição foi abstraída, mas o código ficou um pouquinho extenso, não?

Nesse caso, é possível eliminar a propriedade siblingElement. Basta usar a própria const form:

const showScoreMessage = ({ scoreMessage, content }) => {
  scoreMessage.style.color = 'White'
  scoreMessage.textContent = content
  form.insertAdjacentElement('afterend', scoreMessage)
}

form.addEventListener('submit', event => {
  // ...

  if (higherThanAverageScore) {
    return showScoreMessage({ 
      scoreMessage, 
      content: `Minhas congratulações! Vossa nota final é de ${score} :>`
    })
  }

  showScoreMessage({ 
    scoreMessage, 
    content: `Valeu a tentativa! Vossa nota final é de ${score}. Se for de vossa vontade, pode tentar novamente.`
  })
})

Se vc quiser, tb pode eliminar o parâmetro scoreMessage, declarando a criação do parágrafo dentro da função showScoreMessage:

const showScoreMessage = message => {
  const scoreMessage = document.createElement('p')

  scoreMessage.style.color = 'White'
  scoreMessage.textContent = message
  form.insertAdjacentElement('afterend', scoreMessage)
}

form.addEventListener('submit', event => {
  // ...

  if (higherThanAverageScore) {
    return showScoreMessage(`Minhas congratulações! Vossa nota final é de ${score} :>`)
  }

  showScoreMessage(`Valeu a tentativa! Vossa nota final é de ${score}. Se for de vossa vontade, pode tentar novamente.`)
})

Agora... não sei se foi a intenção, mas a cada envio do quiz (sem recarregar a pág.), um novo p está sendo criado (e isso já acontecia antes da declaração do parágrafo ser movida para dentro de showScoreMessage).

image

Vc se lembra pq isso acontece?

Olha só...

Um princípio que gosto de seguir é: deixe as declarações de suas variáveis perto de onde elas são usadas.

Porém, nem sempre esse princípio deve ser aplicado. Por questões de escopo.

Um exemplo clássico é quando vc tem duas funções que precisam acessar a mesma variável.

No código abaixo, func1 e func2 acessam myConst normalmente:

const myConst = 'my value'

const func1 = () => {
  console.log(myConst)
}

const func2 = () => {
  console.log(myConst)
}

func1() // 'my value'
func2() // 'my value'

Porém, se myConst é declarada dentro de func1, func2 não consegue acessar myConst por que myConst agora é acessível apenas dentro do escopo de func1:

const func1 = () => {
  const myConst = 'my value'
  console.log(myConst)
}

const func2 = () => {
  console.log(myConst)
}

func1() // 'my value'
func2() // app.js:7 Uncaught ReferenceError: myConst is not defined

Esse é um caso onde myConst precisa estar em escopo global, para que ela seja acessada dentro das duas funções.

Mais à frente, vc começará a ver por que variáveis globais não são uma boa ideia. Mas por enquanto, isso não é importante.

Um outro caso que vc precisa considerar é o escopo de bloco...

Lembra que não há problema em duas consts de mesmo nome serem criadas em escopos diferentes?

No código abaixo,

const myFunc = () => {
  const myConst = 'my value'
  console.log(myConst)
}

myFunc()
myFunc()

a cada vez que myFunc é invocada, o bloco que contém a declaração da const e o console.log é executado, certo?

Blocos criam escopos.

Isso significa que na primeira vez que myFunc foi invocada, um escopo com a const myConst foi criado. E na segunda vez em que ela foi invocada, um outro escopo com uma outra const myConst foi criado.

Ou seja, se myFunc é invocada 2x, duas consts myConst foram criadas em memória.

E isso é importante pelo seguinte...

Escreva o código abaixo e execute ele:

<div></div>
<button>Adicionar parágrafo</button>
const div = document.querySelector('div')
const button = document.querySelector('button')

const paragraph = document.createElement('p')

button.addEventListener('click', () => {
  div.appendChild(paragraph)
})

Ao executar esse código e clicar no botão pela primeira vez, vc verá que um <p> foi adicionado dentro da <div>.

Porém, do segundo clique em diante, não são adicionados novos <p>.

Por que isso acontece?

Isso acontece por que a cada execução do callback de clique, o paragraph ao invés de ser criado novamente está apenas sendo reaproveitado.

Ou seja, como paragraph foi declarado fora do escopo do callback, a cada execução do bloco do callback, o mesmo parágrafo está sendo inserido dentro da div.

Lembre-se: quando um callback de listener de evento é executado, ele não faz o código que está fora dele ser declarado novamente. Apenas o bloco do callback é executado novamente. Isso significa que o mesmo paragraph está sendo usado dentro do callback.

Agora, se paragraph for declarado dentro do callback,

button.addEventListener('click', () => {
  const paragraph = document.createElement('p')
  div.appendChild(paragraph)
})

toda vez que esse bloco do callback for executado, um novo paragraph será criado em memória e inserido como filho da div. Ou seja, o segundo clique no botão irá inserir um segundo <p> como filho da div, um terceiro clique irá inserir um terceiro <p> e assim por diante. Graças ao escopo de bloco =)

Sacou a ideia?

Considerando isso...

scoreMessage pode ser declarada no topo do código para que outros parágrafos não sejam criados novamente à cada execução do callback de submit do form.

// antes
const showScoreMessage = message => {
  const scoreMessage = document.createElement('p')

  scoreMessage.style.color = 'White'
  scoreMessage.textContent = message
  form.insertAdjacentElement('afterend', scoreMessage)
}
// depois
const form = document.querySelector('.quiz-form')
const button = document.querySelector('button')
const scoreMessage = document.createElement('p')

// ...

const showScoreMessage = message => {
  scoreMessage.style.color = 'White'
  scoreMessage.textContent = message
  form.insertAdjacentElement('afterend', scoreMessage)
}

No print abaixo, o botão foi clicado várias vezes e apenas 1 único parágrafo é exibido.

image


Code styling

Observando a versão anterior do código (antes das refatorações acima), ao observar as linhas abaixo, percebe que algo pode estar "desorganizado"?

  if (higherThanAverageScore) {
    // ...
  } else{
    // ...
  }

Uma abertura de bloco contém um espaço em branco antes dela e a outra, não.

Novamente... escolha uma abordagem e seja consistente com ela no decorrer do código.

Por convenção, recomendo que escolha deixar um espaço em branco antes da abertura do código.

No exemplo abaixo, também há uma inconsistência:

form.addEventListener('submit', event=>{
  const correctAnswersCondition = (userAnswer, index) =>{
    // ...
  }
  // ...
}

Recomendo que use espaços em branco antes e depois de =>. Deve ficar assim:

form.addEventListener('submit', event => {
  const correctAnswersCondition = (userAnswer, index) => {
    // ...
  }
  // ...
}

Futuramente, vc 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.


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?

MivlaM commented 2 years ago

Oi!

Sim, eu entendi. Boa parte do que fiz foram de hábitos que eu adquiri quando estava começando a aprender a programar, por exemplo o caso do css inline, o uso de else e também a mania de deixar muitos espaços. Acredito que minha maior dificuldade seja mesmo onde colocar os pedaços do código a medida que vou escrevendo e refatorando para deixá-los de fácil compreensão a todo mundo. De qualquer forma, vou aplicar as observações que você mostrou no meu código atual e passar a me acostumar a empregá-las nos meus futuros programas.

Por fim, eu lhe agradeço pela correção.

Roger-Melo commented 2 years ago

Show!

No que precisar, é só abrir uma nova issue =)