Мне нравится видеть, как мой код превращается на экране во что-то красивое и интерактивное. Но когда вы разрабатываете что-то в одиночку или в небольшой группе, вы должны носить много шляп как инженер, дизайнер, автор и менеджер проекта.

Когда я чувствую себя подавленным перед тем, как погрузиться в большой проект, возникает искушение расслабиться и сначала заняться интересной частью, а затем начать набрасывать макеты и выбирать цветовые схемы. Но когда я оставляю сложную часть напоследок, не представляя, как я это сделаю, я впадаю в полную панику: «КАК Я ЭТО СДЕЛАЮ??».

Вот почему мое личное правило — всегда сначала делать уродливую часть.

Вот недавний проект, который я сделал с использованием популярного JavaScript-фреймворка React:



React — популярный фреймворк для создания одностраничных приложений. Приложение разделено на отдельные компоненты. Каждый компонент хранит данные в своем собственном внутреннем состоянии. Когда состояние обновляется, этот компонент повторно отображается в DOM без обновления всей страницы. Это обеспечивает быстрый, привлекательный и удобный пользовательский интерфейс.

Этот проект представляет собой приложение-викторину, подобное тому, что вы видите на популярных сайтах, на которые все любят тратить время. Пользователь отвечает на несколько вопросов, и приложение сообщает им, что их ответы говорят об их личности, предпочтениях или чем-то еще. Я знал, что хочу, чтобы он выглядел счастливым и веселым, с плавными переходами, которые придают ему современный вид, и, прежде всего, без лишних кнопок. Но прежде всего я хотел, чтобы это работало! Остальное не имеет значения, если я не могу принимать ответы в качестве входных данных и выдавать результат в качестве вывода. Итак, сначала я сделал уродливую часть.

Мне нужен был алгоритм, чтобы сделать две вещи. Во-первых, он должен был отслеживать ответы пользователей. Во-вторых, он должен был рассчитать результаты на основе этих ответов.

Этот стиль викторины широко распространен в Интернете, но логика для него возникла в журналах. Для каждого вопроса читатель выбирал вариант (A, B, C или D) для каждого вопроса. В конце они складывали числа A, B, C и D, и их результатом было то, что соответствовало букве с наибольшим количеством очков. Я следовал этой технике при построении логики своего приложения. Во-первых, я создал следующий объект в качестве викторины-заполнителя, с фактическими вопросами, которые нужно будет заполнить позже (еще один пример того, как сначала сделать уродливую часть):

let quiz = {
   title: "which whatever are you?",
   questions: [
      {question: "What is the first thing?",
      answers: ["a-ish", "b-ish", "c-ish", "d-ish"]
      },
      {question: "What is the second thing?",
      answers: ["eey", "bee", "sea", "dee"]
      },
      {question: "What is the third thing?",
      answers: ["the a one", "the b one", "the c one", "the d one"]
      }
   ],
   results : ["A", "B", "C", "D"]
}

Индекс вариантов напрямую соответствует индексу результатов. Таким образом, если пользователь выбирает в основном варианты с индексом 1, его результатом будет тот, который находится в массиве результатов с индексом 1.

Затем мне нужно отобразить его в DOM. Вышеупомянутый тест состоит из трех вопросов, на каждый из которых есть четыре возможных ответа. Но мне нужно только правильно написать код для одного вопроса и одного ответа. Это потому, что React (и его гибридный язык html-JavaScript JSX) делает это простым. Используя метод Array.map(), я могу отображать один и тот же код в DOM один раз для каждого вопроса в массиве «questions». Я снова использую этот метод для отображения каждого варианта в массиве «ответы». Итак, вся викторина отображается в DOM, используя всего несколько строк кода ниже:

{quiz.questions.map( x => {
   return ( 
      <div key={x.question}>
         <div > {x.question} </div>
         <div> {x.answers.map( y => <div key={y} onClick={ () =>
            keepScore(
               x.answers.indexOf(y), quiz.questions.indexOf(x)
               )}> {y}</div>)}  
         </div>
      </div>
 )})}

Здесь нет стиля. Опять же, сначала я делаю уродливую часть.

В приведенном выше коде есть прослушиватель событий, который использует синтаксис React onClick. Это вызывает следующую функцию:

function keepScore(index, questionNum){
   const scoreCopy = {...scoreSheet}
   // increase the score based on the answer selected
   if (Object.keys(scoreCopy).includes(index.toString()){
      scoreCopy[index]++}
   else {
      scoreCopy[index] = 1
   }
   // put this new score in the component's state
   setScoreSheet(scoreCopy)
  // if it's the last question call the function to add up the score
  if (questionNum == (quiz.questions.length-1).toString()){
      addScore(scoreCopy)}
}

Эта функция берет индекс вопроса из массива «вопросы» и индекс варианта ответа из массива «ответы». Затем он обновляет оценку, добавляя «балл» к объекту, хранящемуся в состоянии этого компонента React, где ключ — это индекс выбранного параметра, а значение — количество баллов, которые он имеет.

const [scoreSheet, setScoreSheet] = useState({})

Наконец, если вопрос, на который в данный момент отвечает последний вопрос в викторине, будет вызвана другая функция, которая подсчитывает баллы. Написание таким образом позволяет каждой викторине иметь разное количество вопросов.

Окончательный результат рассчитывается следующим образом:

function addScore(finalScore){
   const highScore = Math.max(...Object.values(finalScore))
   let winners = Object.keys(finalScore).filter( 
      x => finalScore[x]    == highScore)
   console.log(quiz.results[winners[0]] + " score: " + highScore)
}

Эта функция использует встроенный метод JavaScript Math.max(), чтобы определить, какой из индексов ответов имеет наивысший балл. Затем он записывает результат с тем же индексом в консоль (или, в конечном проекте, в DOM).

Вот и все. Тяжелая часть позади. Приведенный выше код ничего не добавляет в DOM, кроме текста вопросов и их вариантов. В конце ничего не видно, кроме `console.log()`. Но это самое сердце приложения. Когда сначала делаешь уродливую часть, все после этого просто течет. В итоге будет выглядеть так: