|
|
@@ -2,19 +2,256 @@
|
|
|
|
|
|
# Vue.js:
|
|
|
|
|
|
-## #18 Криптономикон: рефакторинг
|
|
|
+## #18 Криптономикон: рефакторинг (live)
|
|
|
|
|
|
<!-- 2ч -->
|
|
|
|
|
|
* [YouTube](https://www.youtube.com/live/AzsO67rloQw?feature=share&t=248)
|
|
|
* [RuTube](https://rutube.ru/video/979b2cce76478860252ca7f6ee63b4b1/?r=wd&t=250)
|
|
|
|
|
|
+>Качество на RuTube очень паршивое, если не сможете посмотреть оригинал на YouTube, то можете скачать видео с помощью какого-нибудь сервиса.
|
|
|
+
|
|
|
**Содержание урока:**
|
|
|
|
|
|
- Презентация задачи для шаблона письма
|
|
|
- разделение бизнес логики и реализации (вынос запросов АПИ в отдельный файл, вебсокеты, подписка на обновления)
|
|
|
|
|
|
+## Расшифровка скринкаста
|
|
|
+
|
|
|
+>Спойлер с новой задачей (шаблон письма) не делаем, это просто презентация на будущее
|
|
|
+
|
|
|
+>В этом видео много раз переделывается одно и то же. Не спешите копипастить, постарайтесь понять что происходит.
|
|
|
+
|
|
|
+### Вынесение действий, не относящихся к бизнес-логике, в отдельный js-файл (api.js)
|
|
|
+
|
|
|
+1. Создаём в каталоге `src` файл `api.js`
|
|
|
+
|
|
|
+1. Выносим в него функцию получения валюты
|
|
|
+
|
|
|
+ Ключевое слово **export** перед переменной или функцией говорит, что эта переменная (или функция) будет доступна для импорта в других модулях
|
|
|
+
|
|
|
+ >Синтаксис с промисами я уже описывал в прошлых лекциях. Вы должны понимать, что этот метод вернёт **Promise**, т.е. вызывающая функция должна либо использовать асинхронный вызов **await**, либо метод _.then_
|
|
|
+
|
|
|
+ ```js
|
|
|
+ const API_KEY = 'ce3fd966e7a1d10d65f907b20bf000552158fd3ed1bd614110baa0ac6cb57a7e'
|
|
|
+
|
|
|
+ export const loadTiker = tickerName => fetch(
|
|
|
+ `https://min-api.cryptocompare.com/data/price?fsym=${tickerName}&tsyms=USD&api_key=${API_KEY}`
|
|
|
+ ).then(r => r.json())
|
|
|
+ ```
|
|
|
+
|
|
|
+ Не очень похоже на функцию? На самом деле это один из вариантов объявления лямбда функции.
|
|
|
+
|
|
|
+ ```
|
|
|
+ переменная = (параметры функции) => { тело функции }
|
|
|
+ ```
|
|
|
+
|
|
|
+ Просто тут сделано два упрощения:
|
|
|
+
|
|
|
+ * если у функции только один параметр, то можно не писать круглые скобки
|
|
|
+ * если тело функции состоит из одного выражения, то можно не писать и фигурные скобки: результат этого выражения и будет результатом функции
|
|
|
+
|
|
|
+ Т.е. более привычный способ записи был бы такой:
|
|
|
+
|
|
|
+ ```js
|
|
|
+ export const loadTiker = (tickerName) => {
|
|
|
+ return fetch(
|
|
|
+ `https://min-api.cryptocompare.com/data/price?fsym=${tickerName}&tsyms=USD&api_key=${API_KEY}`
|
|
|
+ ).then(r => r.json())
|
|
|
+ }
|
|
|
+ ```
|
|
|
+
|
|
|
+ а если совсем боитесь (не понимаете) лямбда функций, то такой:
|
|
|
+
|
|
|
+ ```js
|
|
|
+ export function loadTiker (tickerName) {
|
|
|
+ return fetch(
|
|
|
+ `https://min-api.cryptocompare.com/data/price?fsym=${tickerName}&tsyms=USD&api_key=${API_KEY}`
|
|
|
+ ).then(r => r.json())
|
|
|
+ }
|
|
|
+ ```
|
|
|
+
|
|
|
+1. В основном файле приложения (`App.vue`) прописываем импорт новой функции и меняем код в том месте, где был запрос валюты
|
|
|
+
|
|
|
+ * импорт
|
|
|
+
|
|
|
+ ```js
|
|
|
+ import { loadTiker } from './api'
|
|
|
+ ```
|
|
|
+
|
|
|
+ Фигурные скобки означают декомпозицию, т.е. имя переменной _loadTiker_ доступно в модуле напрямую.
|
|
|
+
|
|
|
+ >Все экспорты модуля снаружи выглядят как объект и, если не использовать декомпозицию, то для обращения к функции пришлось бы использовать свойство объекта. Например
|
|
|
+ >
|
|
|
+ >```js
|
|
|
+ >import someModule from './api'
|
|
|
+ >...
|
|
|
+ >someModule.loadTiker
|
|
|
+ >```
|
|
|
+
|
|
|
+ * замена кода
|
|
|
+
|
|
|
+ ```js
|
|
|
+ setInterval(async () => {
|
|
|
+ const data = await loadTiker(tickerName)
|
|
|
+ // тут удаляем старый код запроса данных
|
|
|
+ ```
|
|
|
+
|
|
|
+### Переписываем кучу одинарных запросов валюты на один пакетный
|
|
|
+
|
|
|
+>Весь текст не расшифровываю - смотрите видео
|
|
|
+
|
|
|
+Смысл в том, что в текущей реализации мы при добавлении новой валюты запускаем таймер с получением данных. При этом у нас нет способов отключить таймер. Получается, что если 10 раз добавить/удалить одну и ту же валюту, то у нас получится 10 одинаковых таймеров при пустом списке валют (это конечно маловероятный случай, но наглядно показывающий ущербность текущей реализации)
|
|
|
+
|
|
|
+К счастью АПИ позволяет получить обратные курсы валют. Не `<валюта>` к доллару, как сейчас, а доллар к списку валют. Т.е. мы одним выстрелом убиваем двух зайцев: минимизируем количество запросов (в этом варианте одним запросом можно запросить до 500 валют) и избавляемся от необходимости удалять таймеры.
|
|
|
+
|
|
|
+1. Переписываем функцию _loadTiker_
|
|
|
+
|
|
|
+ ```js
|
|
|
+ export const loadTiker = tickers => fetch(
|
|
|
+ `https://min-api.cryptocompare.com/data/price?fsym=USD&tsyms=${tickers.join(',')}&api_key=${API_KEY}`
|
|
|
+ ).then(r => r.json())
|
|
|
+ ```
|
|
|
+
|
|
|
+ Обратите внимание:
|
|
|
+
|
|
|
+ * в параметрах метода вместо одной валюты передается список валют
|
|
|
+ * в параметрах запроса `fsym` поменяли на `USD`, а `tsyms` на список валют (функция **join** преобразует массив в строку, разделенную запятыми)
|
|
|
+
|
|
|
+
|
|
|
+1. В основном компоненте (`App.vue`) реализуем функцию _updateTickers_
|
|
|
+
|
|
|
+ Эта функция вызывает функцию получения данных о курсах валют и для каждой валюты в списке валют обновляет её стоимость, учитывая возможность отсутствия валюты в результате
|
|
|
+
|
|
|
+ ```js
|
|
|
+ async function updateTickers () {
|
|
|
+ // если список валют пуст, то ничего не делаем
|
|
|
+ if (!tickers.value.length) return
|
|
|
+
|
|
|
+ // получаем данные о валютах
|
|
|
+ // в параметрах передаём список валют
|
|
|
+ const data = await loadTiker(
|
|
|
+ tickers.value.map(
|
|
|
+ t => t.name
|
|
|
+ )
|
|
|
+ )
|
|
|
+
|
|
|
+ tickers.value.forEach(t => {
|
|
|
+ // в верхний регистр переводить не надо,
|
|
|
+ // мы уже делали самостоятельную работу с валидацией
|
|
|
+ // и в списке у нас валюты должны быть в верхнем регистре
|
|
|
+ if (!data[t.name]) {
|
|
|
+ t.price = '-'
|
|
|
+ return
|
|
|
+ }
|
|
|
+ t.price = 1 / data[t.name]
|
|
|
+ })
|
|
|
+ }
|
|
|
+ ```
|
|
|
+
|
|
|
+1. Удаляем из метода _add_ получение данных (таймер)
|
|
|
+
|
|
|
+1. В методе жизненного цикла **onBeforeMount** переписываем создание таймера для периодического получения данных (хотя можно и непосредственно в `script setup`)
|
|
|
+
|
|
|
+ ```js
|
|
|
+ onBeforeMount(() => {
|
|
|
+ const tickersData = localStorage.getItem('cryptonomicon-list') ?? '[]'
|
|
|
+ if (tickersData) {
|
|
|
+ tickers.value = JSON.parse(tickersData)
|
|
|
+ }
|
|
|
+ setInterval(() => {
|
|
|
+ updateTickers()
|
|
|
+ }, 5000)
|
|
|
+ })
|
|
|
+ ```
|
|
|
+
|
|
|
+ Обратите внимание, в видео описан вариант без обёртки в лямбда-функцию, но у меня так не заработало. Используем "классический" вариант.
|
|
|
+
|
|
|
+1. В массиве валют стоимость теперь хранится в исходном виде (число с кучей цифр после запятой)
|
|
|
+
|
|
|
+ Реализуем метод форматирования цены:
|
|
|
+
|
|
|
+ ```js
|
|
|
+ function formatPrice (price) {
|
|
|
+ if (typeof price == 'number') {
|
|
|
+ return price > 1 ? price.toFixed(2) : price.toPrecision(2)
|
|
|
+ }
|
|
|
+ return price
|
|
|
+ }
|
|
|
+ ```
|
|
|
+ >У меня иногда появлялось исключение типа "метод toFixed не определён", возможно при значении `'-'`, поэтому я добавил проверку на тип данных
|
|
|
+
|
|
|
+ И вставляем его в шаблон, в то место, где выводится стоимость валюты (самостоятельно)
|
|
|
+
|
|
|
+### Улучшайзинг полученного кода
|
|
|
+
|
|
|
+В рамках отделения бизнес-логики от реализации перенесём обратное преобразование цены в код метода _loadTickers_ (добавили множественное число)
|
|
|
+
|
|
|
+Добавляем действие в цепочку:
|
|
|
+
|
|
|
+```js
|
|
|
+export const loadTikers = tickers => fetch(
|
|
|
+ `https://min-api.cryptocompare.com/data/price?fsym=USD&tsyms=${tickers.join(',')}&api_key=${API_KEY}`
|
|
|
+).then(
|
|
|
+ r => r.json()
|
|
|
+).then(rawData => {
|
|
|
+ return Object.fromEntries(
|
|
|
+ Object.entries(rawData)
|
|
|
+ .map(([key,value]) => [key, 1 / value])
|
|
|
+ )
|
|
|
+})
|
|
|
+```
|
|
|
+
|
|
|
+Более подробно о преобразованиях:
|
|
|
+
|
|
|
+* _rawData_ на входе выглядит примерно так: `{a: 1, b: 2}`
|
|
|
+* Метод **Object.entries()** превращает их в массив массивов: `[['a', 1], ['b', 2]]`
|
|
|
+* который мы перебираем методом **map** и получаем прямые цены `[['a', 1], ['b', 0.5]]`
|
|
|
+* **Object.fromEntries()** упаковывает массивы обратно в объект `{a: 1, b: 0.5}`
|
|
|
+
|
|
|
+Теперь в методе _updateTickers_ мы можем выкинуть лишнюю логику и оставить только присваивание:
|
|
|
+
|
|
|
+```js
|
|
|
+const data = await loadTikers(
|
|
|
+ tickers.value.map(
|
|
|
+ t => t.name
|
|
|
+ )
|
|
|
+)
|
|
|
+
|
|
|
+tickers.value.forEach(t => {
|
|
|
+ t.price = data[t.name] ?? '-'
|
|
|
+})
|
|
|
+```
|
|
|
+
|
|
|
+### Ещё один улучшайзинг
|
|
|
+
|
|
|
+Кто-то нашёл, что апи поддерживает и прямой запрос список-список
|
|
|
+
|
|
|
+Мы меняем **endpoint** на _pricemulti_, в _fsyms_ передаём список валют, а в tsyms USD (тоже список, но из одной валюты)
|
|
|
+
|
|
|
+```js
|
|
|
+export const loadTikers = tickers => fetch(
|
|
|
+ `https://min-api.cryptocompare.com/data/pricemulti?fsyms=${tickers.join(',')}&tsyms=USD&api_key=${API_KEY}`
|
|
|
+).then(r => r.json()).then(rawData => {
|
|
|
+ return Object.fromEntries(
|
|
|
+ Object.entries(rawData)
|
|
|
+ .map(([key,value]) => [key, value.USD])
|
|
|
+ )
|
|
|
+})
|
|
|
+```
|
|
|
+
|
|
|
+Т.к. формат ответа отлчается, то немного правим действие в методе **map**. *Value* у нас теперь не обратная цена, а объект с названиями валют и их ценами.
|
|
|
+
|
|
|
+Обратите внимание, мы правили только слой апи, а слой бизнес логики не меняли, но всё продолжает работать. В этом и есть цель разделения бизнес-логики и апи.
|
|
|
+
|
|
|
+На мой взгляд, для **junior**-ов этого уже достаточно.
|
|
|
+
|
|
|
+Исходный курс рассчитан на **middl**-ов, если кому-то интересно, то самостоятельно реализуйте задачу с подпиской на стоимость (за дополнительную оценку)
|
|
|
+
|
|
|
---
|
|
|
|
|
|
+1. Посмотреть видео
|
|
|
+1. Реализовать описанные в скринкасте изменения
|
|
|
+1. В новой реализации потерялось добавление данных в график выбранной валюты. Исправьте это самостоятельно.
|
|
|
|
|
|
[Назад](./web_09.md) | [Дальше](./web_11.md)
|