Научитесь использовать API, Vue.js, SCSS и Promises и писать семантический HTML5.

Шаги, которые мы предпримем вместе

  1. Разбейте виджет, чтобы понять, какие HTML-элементы нам следует использовать.
  2. Изучите, протестируйте и зарегистрируйтесь в службе API, которая предоставит нам необходимые данные.
  3. Добавьте фиктивные данные из WordsAPI в наш HTML-шаблон, чтобы проверить его структуру.
  4. Используйте Vue.js, чтобы быстро создать прототип нашего виджета с использованием данных из API.
  5. По частям заменяйте жестко запрограммированный текст данными из нашей модели Vue.
  6. Протестируйте наше приложение и обновите части, чтобы учесть данные, полученные от API, для любого слова.
  7. Стиль нашего приложения с использованием SCSS имитирует виджет Google.

Разбейте виджет, чтобы понять, какие HTML-элементы нам следует использовать

Вот разбивка

  • У виджета есть заголовок «Словарь».
  • Затем краткая форма: кнопки input и submit.
  • Результатом поиска является список определений с одним dt и dd для каждого набора существительных и глаголов.
  • Внутри каждого dd находится ol с элементами списка для каждого существительного или глагола.
  • Каждый li будет содержать определение и может включать термин, используемый в предложении, и один или несколько синонимов. Определение - p. Предложение представляет собой q, заключенное в p,, а список синонимов - это еще один dl.

Вот HTML-код с заполнителями, отмеченными {{ }}

<div id="app">
  <h1>Dictionary</h1>
  <input type="text" />
  <button>Search</button>
  <dl>
    <dt>
      <p>{{syllables}}</p>
      <p>{{pronunciation}}</p>
    </dt>
    <dd>
      <p>noun</p>
      <ol>
        <li>
          <p>{{definition}}</p>
          <p>
            <q>{{sentence}}</q>
          </p>
          <dl>
            <dt>synonyms</dt>
            <dd>{{synonym}}</dd>
          </dl>
        </li>
      </ol>
    </dd>
    <dd>
      <p>verb</p>
      <ol>
        <li>
          <p>{{definition}}</p>
          <p>
            <q>{{sentence}}</q>
          </p>
          <dl>
            <dt>synonyms</dt>
            <dd>{{synonym}}</dd>
          </dl>
        </li>
      </ol>
    </dd>
  </dl>
</div>

Исследование, тестирование и подписка на службу API, которая предоставит нам необходимые данные

В левом нижнем углу виджета Словаря Google есть надпись From Oxford. Это привело меня к Google oxford dictionary api и, таким образом, к официальному Oxford Dictionaries API.

К сожалению, я обнаружил два узких места:

  1. У API есть связанная стоимость всякий раз, когда он вызывается. Я бы предпочел не платить только за несколько вызовов API.
  2. Не похоже, что API возвращает синонимы, которые являются ключевой частью виджета, который мы пытаемся воссоздать.

Скорее всего, я недостаточно прочитал об этом веб-сайте, а то, что я считаю узкими местами, на самом деле не так.

Но когда в другом поиске Google по запросу dictionary api было обнаружено простое английское название ресурса WordsAPI без каких-либо узких мест, описанных выше, мое решение было легко принять.

Для использования WordsAPI и почти всех API-интерфейсов требуется ключ.

Ключ используется, помимо прочего, для отслеживания количества запросов, сделанных за день.

Чтобы получить свой ключ WordsAPI, вы должны:

  1. Зарегистрируйтесь в RapidAPI, зонтичном сервисе, через который вы можете подписаться на WordsAPI.
  2. Введите платежную информацию на случай, если вы превысите лимит уровня бесплатного пользования, и они должны будут взимать с вас плату.
  3. Подпишитесь на WordsAPI, чтобы любые запросы, которые вы делаете к API, могли быть проверены, и вы получали реальные данные вместо надоедливого сообщения об ошибке.

Добавьте фиктивные данные из WordsAPI в наш HTML-шаблон, чтобы проверить его структуру

Интересующие части ответа, возвращенного на этот запрос API, представлены ниже.

{
  "word": "program",
  "results": [
    {
      "definition": "an integrated course of academic studies",
      "partOfSpeech": "noun",
      "synonyms": [
        "course of study",
        "curriculum",
        "programme",
        "syllabus"
      ],
      "examples": [
        "he was admitted to a new program at the university"
      ]
    },
    // more result objects
  ],
  "syllables": {
    "count": 2,
    "list": [
      "pro",
      "gram"
    ]
  },
  "pronunciation": {
    "all": "'proʊgræm"
  }
}

На этом этапе мы должны ответить на вопрос: «Настроили ли мы наш HTML-шаблон для эффективного отображения каждого блока данных, показанных выше?»

И снова наш шаблон

<div id="app">
  <h1>Dictionary</h1>
  <input type="text" />
  <button>Search</button>
  <dl>
    <dt>
      <p>{{syllables}}</p>
      <p>{{pronunciation}}</p>
    </dt>
    <dd> <!-- will repeat for each result that is a noun -->
      <p>noun</p>
      <ol>
        <li>
          <p>{{definition}}</p>
          <p>
            <q>{{sentence}}</q> <!-- repeat for each example -->
          </p>
          <dl>
            <dt>synonyms</dt>
            <dd>{{synonym}}</dd> <!-- repeat for each synonym -->
          </dl>
        </li>
      </ol>
    </dd>
    <dd> <!-- will repeat for each result that is a verb -->
      <p>verb</p>
      <ol>
        <li>
          <p>{{definition}}</p>
          <p>
            <q>{{sentence}}</q>
          </p>
          <dl>
            <dt>synonyms</dt>
            <dd>{{synonym}}</dd> <!-- repeat for each synonym -->
          </dl>
        </li>
      </ol>
    </dd>
  </dl>
</div>
  • {{ syllables }} можно получить из массива syllables.list.
  • {{ pronunciation }} передан нам из одноименной собственности.
  • {{ sentence }} можно получить из массива examples каждого результата.
  • Каждый {{ synonym }} может быть получен из массива synonyms.
  • {{ definition }} передан нам из одноименной собственности.
  • И мы можем правильно группировать существительные и глаголы на основе свойства partOfSpeech для каждого результата.

Наш HTML-шаблон готов. Теперь давайте создадим экземпляр Vue и поработаем над отображением данных из объекта ответа в этом шаблоне.

Используйте Vue.js для быстрого создания прототипа нашего виджета с использованием данных из API

Давайте помнить на этом этапе, что:

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

Учитывая это понимание, я считаю, что Vue.js, прогрессивный JavaScript-фреймворк дает мне возможность работать быстрее всего при создании прототипов таких виджетов.

Как добавить Vue.js в свой проект

Используете редактор кода с загруженным HTML-файлом? Добавьте тег script непосредственно перед закрывающим тегом </body> с атрибутом src, установленным для одного из множества CDN, доступных для Vue.js, например:

...
    <script src="https://unpkg.com/[email protected]/dist/vue.js" />
  </body>
</html>

Пользуетесь сервисом вроде Codepen? Нажмите на ручке шестеренку рядом с JS и найдите vue. Он должен автоматически заполнить список, первым из которых является Vue.js. Выберите это, чтобы добавить к ручке. Сохрани и закрой.

Как начать прототипирование с Vue.js

Vue - это функция-конструктор: это функция, которая создает объект. Это означает, что для использования Vue мы должны включить ключевое слово new, набор () круглых скобок и сохранить то, что возвращается в переменной, как это.

var app = new Vue();

Как и большинство функций, Vue ожидает один или несколько аргументов, когда вы его вызываете. Для наших целей мы передадим единственный аргумент - объект.

var app = new Vue( {} );

Этот объект будет иметь несколько свойств, каждое из которых соответствует ожиданиям Vue. Начнем со свойства, которое подключает Vue к нашему виджету. Напомним, что в нашем HTML-шаблоне я заключил все в <div> с id из app.

<div id="app">
   ...
</div>

Я сделал это для Vue. Vue подключается к одному элементу HTML, и проще всего использовать id при нацеливании на элемент.

var app = new Vue({
  el: "#app"
});

Мы добавили одну пару key: value к объекту, который передаем в функцию конструктора Vue. key - это el, сокращение от element, а значение - "#app", строка, содержащая селектор CSS, нацеленный на наш div.

И вот так наш div теперь является экземпляром Vue! К сожалению, пока ничего не происходит. Давай исправим это.

Как добавить состояние по умолчанию в наш экземпляр Vue

Давайте добавим к объекту еще одну key: value пару. Эта пара играет важную роль в экземпляре Vue: она хранит состояние нашего приложения. key называется data, а его значением является объект, как показано ниже.

var app = new Vue({
  el: "#app",
  data: {}
});

Внутри этого объекта мы можем добавлять key: value пары с любыми именами, которые захотим, поскольку они уникальны для нашего приложения и не диктуются или не ожидаются Vue.

Нам нужно сохранить только два фрагмента данных: ответ на наш запрос API и слово, которое мы хотим найти.

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

var app = new Vue({
  el: "#app",
  data: {
    response: null,
    word: ""
  }
});

Я выбрал интуитивно понятные метки для каждого ключа объекта данных, response для ответа API и word для слова, которое мы хотим найти.

Как настроить запрос WordsAPI как часть нашего экземпляра Vue

Этот шаг состоит из трех частей:

  • Нам нужно отправить запрос GET в WordsAPI, который включает слово, которое мы хотим найти.
  • Когда мы получаем ответ от API, мы хотим преобразовать его в JSON, чтобы мы могли выполнять с ним операции.
  • После преобразования в JSON мы хотим сохранить объект ответа в свойстве response объекта data, который мы инициализировали с null в качестве значения.

Все это мы сделаем внутри function. Эта функция будет сохранена в нашем экземпляре Vue, потому что Vue будет вызывать ее в соответствующее время.

Приступим к написанию функции:

function handleAPIRequest() {
  ...
}

Это длинное имя функции, но совершенно ясно, что она делает: она обрабатывает наш запрос API.

Удобный встроенный способ отправки GET-запросов - Fetch API.

В статье Использование Fetch ​​в сети разработчиков Mozilla предлагается полезный базовый пример:

fetch('http://example.com/movies.json')
  .then(function(response) {
    return response.json();
  })
  .then(function(myJson) {
    console.log(JSON.stringify(myJson));
  });

Что тут происходит? Ну много.

  • fetch - это function, который вызывается с одним аргументом: строка, содержащая URL-адрес, в данном случае указывающий на файл JSON.
  • Следующая строка фактически является продолжением первой: вызов функции fetch возвращает объект. Мы немедленно вызываем метод then (он же функция), передавая ему единственный аргумент: function.
  • Эта функция - та, которая передана в качестве аргумента для вызова функции then - при вызове ожидает единственный параметр - в данном случае помеченный как response. Функция выполняет единственную команду: вернуть результат вызова json на себя. Эта команда эффективно преобразует строку текста, отправленную запросом API, в объект JavaScript, с которым мы предпочитаем работать.
  • Следующая строка - это еще одно продолжение вызова функции fetch в первой строке. Он работает аналогично предыдущему вызову функции then. Переданная ему функция будет ожидать один параметр. Он получит объект JSON, возвращенный из предыдущего вызова функции.
  • Мы заменим команду console.log() на команду, которая сохраняет объект JSON в свойстве response в объектеdata нашего экземпляра Vue.

Выражаясь более простыми, но продвинутыми терминами, Fetch возвращает обещание. Каждый из вызовов функции then регистрирует последующие действия, которые должны быть выполнены, если и когда обещание успешно разрешится.

Как мы можем изменить образец кода в соответствии с нашими потребностями?

Четырьмя важными способами:

  1. Нам нужно отправить запрос на другой URL.
  2. Нам нужно добавить часть данных к этому URL-адресу.
  3. Нам нужно настроить этот запрос перед отправкой, чтобы WordsAPI мог правильно проверить наш запрос.
  4. Нам нужно сохранить возможный JSON обратно в наш экземпляр Vue.
fetch(`https://wordsapiv1.p.mashape.com/words/${this.word}`, {
   headers: {
      "X-Mashape-Key": "someCrazyLongAPIKeyThatContainsNumbersAndLetters"
   }
})
   .then(function(response) { return response.json() })
   .then(function(myJson) { this.response = myJson })

Какой URL использовать?

Мы получаем это из WordsAPI. В их документации указано, что при поиске слова мы используем этот URL: https://wordsapiv1.p.mashape.com/words/

Как мы добавляем наши данные к этому URL-адресу?

Этот URL-адрес ожидает один последний бит после части words/: слово, которое мы хотим найти. В нашем экземпляре Vue это значение будет сохранено в свойстве с меткой word внутри объекта data. Vue дает нам удобный способ ссылаться на любое значение, хранящееся в объекте data: мы пишем this.word вместо this.data.word или получаем доступ к слову другими способами.

Мы можем использовать template literal, чтобы четко указать полный URL-адрес, включая наши данные. Литералы шаблонов окружены обратными галочками: нижний символ на клавише слева от 1 на большинстве клавиатур.

Литералы шаблона позволяют нам вставлять оцененные данные в строку, используя следующий синтаксис: ${ expression }.

Следовательно, строка, которую мы передаем fetch, будет выглядеть так:

fetch(`https://wordsapiv1.p.mashape.com/words/${this.word}`)

Как мы отправляем требуемый ключ API с нашим запросом, чтобы WordsAPI не возвращал ошибку о недопустимом доступе?

Вызов функции fetch принимает второй параметр: объект, используемый для настройки запроса API.

fetch(`https://wordsapiv1.p.mashape.com/words/${this.word}`, {})

Этот объект допускает множество возможных key: value пар. Нам нужно установить только один, headers.

fetch(`https://wordsapiv1.p.mashape.com/words/${this.word}`, {
  headers: {}
})

WordsAPI требует, чтобы мы добавили одну пару key: value к объекту headers. Ключ - X-Mashape-Key, а значение - длинная строка символов, которая присваивается вашей учетной записи при подписке на WordsAPI.

fetch(`https://wordsapiv1.p.mashape.com/words/${this.word}`, {
   headers: {
      "X-Mashape-Key": "yourAPIKeyThatContainsNumbersAndLetters"
   }
})

Теперь размещение этого вызова API в контексте нашей функции выглядит так:

function handleAPIRequest() {
   fetch(`https://wordsapiv1.p.mashape.com/words/${this.word}`, {
      headers: {
         "X-Mashape-Key": "yourAPIKeyThatContainsNumbersAndLetters"
      }
   })
}

Напомним, что пример кода MDN содержал два связанных вызова then:

fetch('http://example.com/movies.json')
  .then(function(response) {
    return response.json();
  })
  .then(function(myJson) {
    console.log(JSON.stringify(myJson));
  });

Мы можем скопировать первый then вызов точно так, как написано.

function handleAPIRequest() {
   fetch(`https://wordsapiv1.p.mashape.com/words/${this.word}`, {
      headers: {
         "X-Mashape-Key": "yourAPIKeyThatContainsNumbersAndLetters"
      }
    })
    .then(function(response) {
       return response.json();
    })
}

Нам нужно изменить второй вызов then, чтобы сохранить JSON обратно в свойство response нашего экземпляра Vue.

function handleAPIRequest() {
   fetch(`https://wordsapiv1.p.mashape.com/words/${this.word}`, {
      headers: {
         "X-Mashape-Key": "yourAPIKeyThatContainsNumbersAndLetters"
      }
    })
    .then(function(response) {
       return response.json();
    })
    .then(function(body) {
       this.response = body;
    })
}

Как и в случае с word, мы можем удобно ссылаться на любые свойства, хранящиеся внутри объекта data Vue, через this., а затем имя свойства, вместо того, чтобы вставлять this.data. или каким-либо другим способом.

Если вы предпочитаете использовать новые => функции стрелок JavaScript, ваша handleAPIRequest функция будет выглядеть так:

function handleAPIRequest() {
   fetch(`https://wordsapiv1.p.mashape.com/words/${this.word}`, {
      headers: {
         "X-Mashape-Key": "yourAPIKeyThatContainsNumbersAndLetters"
      }
    })
    .then(response => response.json())
    .then(body => this.response = body)
}

Где мы поместим эту функцию во Vue?

В настоящее время у вас есть две key: value пары в объекте конфигурации Vue: el и data. Давайте добавим еще один, который распознает Vue: methods. Значение - это объект, в котором хранится одна или несколько функций.

var app = new Vue({
  el: "#app",
  data: {
    response: null,
    word: ""
  },
  methods: {}
});

Наша функция handleAPIRequest может быть одним из значений свойства этого объекта, например:

var app = new Vue({
  el: "#app",
  data: {
    response: null,
    word: ""
  },
  methods: {
    handleAPIRequest: function () {
      fetch(`https://wordsapiv1.p.mashape.com/words/${this.word}`, {
         headers: {
            "X-Mashape-Key": "yourAPIKey"
         }
      })
       .then(response => response.json())
       .then(body => this.response = body)
    }
  }
});

Как мы запускаем запрос API с любым словом, которое нам нужно?

Вернемся к части нашего HTML-шаблона:

<div id="app>
  <h1>Dictionary</h1>
  <input type="text" />
  <button>Search</button>
   ...
</div>

Нам нужно сделать две вещи:

  1. Свяжите input со свойством word в нашем объекте data.
  2. Добавьте к кнопке прослушиватель событий, чтобы при нажатии вызывалась функция handleAPIRequest.

Свяжите input со свойством word в нашем объекте data

Когда вы вводите текст в поле input, этот текст должен быть сохранен в объекте data вашего Vue как значение, повторно присвоенное свойству word. Кроме того, все, что хранится в word, всегда должно отображаться как значение поля input.

Это называется двусторонней привязкой данных, и Vue предлагает очень удобный синтаксис для ее создания: директива v-model.

Директивы Vue удобно записывать как поддельные атрибуты HTML с префиксом v-.

Директива model Vue, как и многие другие, написана так:

<input v-model="word" type="text" />

Внутри кавычек находится любое допустимое выражение JavaScript (также известное как все, что вы обычно пишете в правой части оператора присваивания, =)

Нам нужно только поместить четырехсимвольную строку word между кавычками, как показано выше. Теперь начало нашего HTML-шаблона выглядит так:

<div id="app>
  <h1>Dictionary</h1>
  <input v-model="word" type="text" />
  <button>Search</button>
   ...
</div>

Добавьте к кнопке прослушиватель событий, чтобы при нажатии вызывалась функция handleAPIRequest

Во Vue есть директива, предназначенная для прослушивания событий DOM: on.

Его синтаксис сначала немного странный.

<button v-on:click="handleAPIRequest">Search</button>
  • Он имеет префикс v-.
  • Затем имя директивы on.
  • Затем двоеточие, обозначающее, что то, что следует далее, является аргументом (как при вызове функции).
  • Затем название аргумента. В этом случае это должно быть действительное событие DOM, например click.
  • Затем обычный знак равенства, котировка открытия и котировка закрытия.
  • Между кавычками идет выражение JavaScript. В этом случае мы вводим имя функции, которая должна быть вызвана. Присмотритесь: мы не вызываем функцию (скобок нет).

Теперь наш шаблон выглядит так:

<div id="app>
  <h1>Dictionary</h1>
  <input v-model="word" type="text" />
  <button v-on:click="handleAPIRequest">Search</button>
   ...
</div>

Как отобразить различные части объекта ответа в нашем шаблоне

Возвращаясь к нашему шаблону, есть несколько элементов, которые нам нужно отобразить, и каждый из них будет поступать из JSON, возвращенного из нашего запроса API:

  • {{ syllables }}
  • {{ pronunciation }}
  • {{ definition }} {{ sentences }} и {{ synonyms }} для каждого существительного или глагола

Vue предлагает два способа хранения данных, один зависит от другого:

  • В объекте data хранятся все необработанные данные
  • Объект computed может хранить данные, которые каким-то образом получены из значений, хранящихся в data

Например, data может хранить массив [1, 2, 3, 4, 5]. Если бы где-то в моем шаблоне я хотел отобразить нечетные числа, которые являются подмножеством этого массива [1, 3, 5], я бы использовал свойство computed, которое является отфильтрованной копией этого массива.

В нашем случае каждая из вышеперечисленных меток, которые мы хотим отобразить в нашем шаблоне, может быть получена из объекта JSON, который мы будем хранить в response внутри объекта data. Поэтому мы создадим computed свойств для каждого из них. Каждое свойство computed по существу будет хранить значения, взятые из response.

Давайте начнем со слогов, чтобы разобраться в вещах

Помните нашу цель:

  • Переводить данные из одной структуры в другую - от того, как они структурированы ответом API, до того, как мы хотим представить их в нашем шаблоне.

Для слогов нам дан массив:

"syllables": {
    "count": 2,
    "list": [
      "pro",
      "gram"
    ]
},

… И мы хотим, чтобы в нашем шаблоне отображалось pro•gram.

  • Мы должны перейти от массива list к строке.
  • Элементы массива будут объединены (или объединены) с помощью маркера • в качестве клея.

list - это свойство родительского объекта syllables, которое является свойством родительского объекта, которое было возвращено нашим запросом API и сохранено в свойстве data объекта нашего экземпляра Vue response.

Все это, чтобы сказать ... вот как мы будем обращаться к list внутри нашего экземпляра Vue:

this.response.syllables.list

Все массивы в JavaScript имеют доступ к функции join. При вызове он объединяет каждый элемент в массиве, используя символ, переданный в качестве аргумента функции. Это как раз та функция, которая нам нужна для достижения нашей цели:

this.response.syllables.list.join("•")
// "pro•gram"

Мы знаем, как выполнить наш перевод. Теперь давайте разместим эту команду внутри Vue как свойство computed.

Вспомните форму нашего экземпляра Vue:

var app = new Vue({
  el: "#app",
  data: {...},
  methods: {
    handleAPIRequest: function() {...}
  }
});

Свойство computed Vue работает очень похоже на methods: оно хранит объект, все свойства которого указывают на функции.

Давайте добавим computed и его первую key: value пару для хранения syllables:

var app = new Vue({
  el: "#app",
  data: {...},
  methods: {
    handleAPIRequest: function() {...}
  },
  computed: {
    syllables: function() {
      return this.response.syllables.list.join("•");
    }
  }
});

Есть одно предостережение, которое мы должны учитывать

Прежде чем мы найдем слово, this.response будет его значением по умолчанию, null.

Следовательно, JavaScript выдаст ошибку при попытке доступа к this.response.syllables, потому что null.syllables не существует.

Нам нужно управлять потоком JavaScript:

  • Если this.response равно null, верните, скажем, пустой массив (на самом деле подойдет любой пустой объект).
  • В противном случае (например, когда this.response является возвращенным объектом JSON), верните this.response.syllables.list.join("•").

Чтобы сделать это кратко, мы будем использовать JavaScript ternary operator, который принимает эту псевдоформу:

(is this true) ? [yes: do this] : [no: do this instead]

Другими словами, короче:

condition ? true : false

Наша обновленная функция syllables выглядит так:

var app = new Vue({
  el: "#app",
  data: {...},
  methods: {
    handleAPIRequest: function() {...}
  },
  computed: {
    syllables: function() {
      return this.response === null ? [] : this.response.syllables.list.join("•");
    }
  }
});

Если this.response равно null, вернуть пустой массив. В противном случае верните вызов нашей join функции.

Ниже приведены все четыре свойства computed и соответствующие им значения функций, как своего рода перемотка вперед:

var app = new Vue({
  el: "#app",
  data: {...},
  methods: {
    handleAPIRequest: function() {...}
  },
  computed: {
    syllables: function() {
      return this.response === null ? [] : this.response.syllables.list.join("•");
    },
    pronunciation: function() {
      return this.response === null ? [] : `/${this.response.pronunciation.all}/`
    },
    nouns: function() {
      return this.response === null ? [] : this.response.results.filter(result => result.partOfSpeech === "noun"
    },
    verbs: function() {
      return this.response === null ? [] : this.response.results.filter(result => result.partOfSpeech === "verb"
    }
  }
});
  • Произношение записывается в виде строки, прикрепленной к pronunciation.all. Мы добавляем / символов в начало и конец в целях презентации.
  • nouns и verbs получаются путем фильтрации массива results внутри response, чтобы соответственно включать только результаты, свойство partOfSpeech которых является строкой noun или verb. Оба вернут массивы

Отображение всех этих данных в нашем шаблоне… наконец-то!

Чтобы отобразить эти вычисленные свойства в нашем шаблоне, нам сначала нужно просмотреть еще две директивы Vue: v-if и v-for.

  • v-if оценит выражение. Если true, элемент DOM и все его дочерние элементы будут добавлены к компоненту. Если false, элемент DOM и все его дочерние элементы будут удалены из компонента.
  • v-for будет перебирать объект или массив, создавая элементы DOM для каждой пары или элемента "ключ-значение".

Начнем с самого родительского <dl>. Он должен появляться только тогда, когда пользователь ввел слово, нажал кнопку, и API вернет ответ с данными определения. Другими словами, если пользователь не нашел слово или слово недействительно для API, мы не должны видеть <dl>, отображаемого на странице или в DOM.

Таким образом, открывающий тег <dl> теперь выглядит так:

<dl v-if="response">

Если response равно null, это выражение будет оцениваться как false. Если response является объектом JSON, он будет оцениваться как true.

Затем два <dd>, которые потенциально будут содержать список определений для слова, разделенных на nouns и verbs.

Согласно нашим свойствам computed, оба значения будут массивами независимо от того, что такое response. Единственная разница в том, пуст каждый или нет.

Следовательно, наши v-if директивы могут проверять length массива, чтобы определить, отображает ли <dd> (длина ›0) или нет (длина == 0).

<dd v-if="nouns.length">
  ...
</dd>
<dd v-if="verbs.length">
  ...
</dd>

В данном результате есть три элемента, которые могут содержать 0, 1 или более элементов:

  • Количество определений.
  • Количество примеров предложений.
  • Количество синонимов.

Для каждого из этих потенциальных наборов элементов мы будем использовать директиву Vue v-for, чтобы исключить элемент DOM для каждого элемента.

Вот синтаксис директивы Vue v-for в контексте элемента списка:

<li v-for="item in collection">...</li>

Внутри <li> мы можем использовать item для ссылки на текущий элемент в итерации.

Вспоминая часть нашего шаблона из ранее, теперь мы можем добавить соответствующие директивы v-for в разметку:

<dd v-if="nouns.length">
      <p>noun</p>
      <ol>
        <li v-for="noun in nouns">
          <p>{{noun.definition}}</p>
          <p v-for="sentence in noun.examples">
            <q>{{noun.sentence}}</q>
          </p>
          <dl>
            <dt>synonyms</dt>
            <dd v-for="synonym in synonyms">{{synonym}}</dd>
          </dl>
        </li>
      </ol>
    </dd>
  • <li> для каждого результата.
  • <p> и <q> для каждого примера предложения.
  • <dd> для каждого синонима.

Протестируйте наше приложение и обновите его части, чтобы учесть данные, полученные от API для Any Word

Увы! Теперь у вас должны быть разметка, скрипт и ключи API, необходимые для тестирования вашего виджета.

HTML

<div id="app">
  <h1>Dictionary</h1>
  <input type="text" v-model="word" />
  <button @click="lookup">Look-up</button>
  <dl v-if="response">
    <dt>
      <p class="syllables">{{syllables}}</p>
      <p class="pronunciation">{{pronunciation}}</p>
    </dt>
    <dd v-if="nouns.length">
      <p class="part-of-speech">noun</p>
      <ol>
        <li class="definition-group" v-for="noun in nouns">
          <p>{{noun.definition}}</p>
          <p v-for="example in noun.examples">
            <q class="example">{{example}}</q>
          </p>
          <dl>
            <dt class="synonym-heading">synonyms</dt>
            <dd class="synonym" v-for="synonym in noun.synonyms">
              {{synonym}}
            </dd>
          </dl>
        </li>
      </ol>
    </dd>
    <dd v-if="verbs.length">
      <p class="part-of-speech">verb</p>
      <ol>
        <li class="definition-group" v-for="verb in verbs">
          <p>{{verb.definition}}</p>
          <p v-for="example in verb.examples">
            <q class="example">{{example}}</q>
          </p>
          <dl>
            <dt class="synonym-heading">synonyms</dt>
            <dd class="synonym" v-for="synonym in verb.synonyms">
              {{synonym}}
            </dd>
          </dl>
        </li>
      </ol>
    </dd>
  </dl>
</div>

JavaScript

var vm = new Vue({
  el: "#app",
  data: {
    response: null,
    word: "program"
  },
  computed: {
    syllables() {
      return this.response !== null ? this.response.syllables.list.join("•") : [];
    },
    pronunciation() {
      return this.response !== null ? `/${this.response.pronunciation.all}/` : [];
    },
    verbs() {
      return this.response !== null ? this.response.results.filter(result => result.partOfSpeech === "verb") : [];
    },
    nouns() {
      return this.response !== null ? this.response.results.filter(result => result.partOfSpeech === "noun") : [];
    },
  },
  methods: {
    lookup() {
      fetch(`https://wordsapiv1.p.mashape.com/words/${this.word}`, {
        headers: {
          "X-Mashape-Key": "yourAPIKeyHere"
        }
      })
        .then(response => response.json())
        .then(body => this.response = body)
    },
  }
})

В нашем коде отсутствуют две функции, которые я оставлю вам в качестве домашнего задания.

  1. Если слово не имеет синонимов, наш виджет по-прежнему отображает слово synonyms, хотя его не должно быть.
  2. В версии Google многие синонимы служат гиперссылками для поиска определения этого слова. Однако не все. Как мы можем добиться такого же эффекта и поведения?

А если вы настроены амбициозно, изучите компонентную архитектуру Vue и произведите рефакторинг этого однокомпонентного приложения в одно, состоящее из нескольких компонентов.

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

Во фрагменте кода в конце этого раздела вы несколько раз увидите символ &. Этот специальный символ позволяет автору эффективно вкладывать стили элементов, которые являются дочерними по отношению к другому, в блок объявления стиля этого элемента, при этом ссылаясь на этот родительский элемент в любом месте дочернего селектора.

Например:

.synonym-heading {
    font-style: italic;
    
    &::after {
      content: ":";
    }
}

в обычном CSS будет выглядеть так:

.synonym-heading {
    font-style: italic;
}
.synonym-heading::after {
    content: ":";
}

Таким образом, & в этом контексте компилируется в .synonym-heading при интерпретации этого блока селектора.

SCSS

#app {
  min-width: 50vw;
  max-width: 600px;
  margin: 2em auto;
  font-family: sans-serif;
  border: 1px solid lightgray;
  border-radius: 10px;
  padding: 1em;
  
  h1 {
    font-weight: normal;
    font-size: 2em;
    line-height: 1;
    margin-top: 1em;
  }
  
  input, button {
    font-size: 1.5em;
    color: gray;
  }
  
  .syllables {
    font-size: 2em;
    margin: 0;
    line-height: 1;
  }
  
  .pronunciation {
    color: gray;
  }
  
  .synonym-heading {
    font-style: italic;
    
    &::after {
      content: ":"
    }
  }
  
  .synonym {
    &:not(:last-child)::after {
      content: ", ";
    }
  }
  
  .synonym-heading,
  .synonym {
    color: gray;
    margin: 0;
    display: inline;
  }
  
  .part-of-speech {
    font-style: italic;
  }
  
  .definition-group {
    margin-bottom: 1em;
  }
  
  .definition-item {
    margin: 0;
  }
  
  .example {
    color: gray;
  }
}

Вы достигли конца - либо с помощью твердости, либо путем прокрутки страницы.

В любом случае, спасибо за чтение. Я надеюсь, что вы последовали за мной, потому что это сделает вас гораздо более сильным разработчиком интерфейса.