Skip to content

Latest commit

 

History

History
208 lines (141 loc) · 9.82 KB

web_09.md

File metadata and controls

208 lines (141 loc) · 9.82 KB

К содержанию

Vue.js практика: computed, watch

#17 Криптономикон: рефакторинг

Содержание урока:

Материалы к изучению:

Расшифровка скринкаста

Список проблем существующего кода

  • одинаковый код в наблюдателях
  • при удалении остается подписка (таймер)
  • количество запросов (в АПИ есть возможность получить данные по списку тикеров)
  • запросы к АПИ внутри компонента
  • обработка ошибок АПИ
  • наличие состояния зависимых данных
  • график переполняется
  • при удалении тикера не меняется LocalStorage
  • LocalStorage и анонимные вкладки (LocalStorage может быть недоступен)
  • магические строки и числа (url, 5000 мсек, ключ localstorage, размер страницы)

Параллельно

  • график сломан, если везде одинаковые значения
  • при удалении тикера остается выбор

Наличие состояния зависимых данных

Метод filteredTickers зависит от страниц и массива тикеров, в таком случае нужно использовать вычисляемые свойства

В Composition API у вычисляемых свойств другой синтаксис:

const calculatedProperty = computed(() => {
    // тут какие-то вычисления на основе состояний
    return result
})

Вычисляемые свойства кешируются, т.е. будут вычисляться только при изменнии используемых реактивных переменных (состояний). В нашем случае это "номер страницы", "строка фильтра" и "список тикеров"

При использовании в шаблоне надо помнить, что computed это свойство, а не функция. Он не может принимать аргументы (не нужно указывать круглые скобки)

Если установлен линтер, то он может показать ошибку, что присваиванию hasNextPage не место в computed

Т.к. это тоже вычисляемое свойство, то его тоже можно завернуть в computed

Попутно решили, что start и end тоже можно вынести в computed для единообразия

В итоге получились такие вычисляемые свойства:

const startIndex = computed(() => {
  return (page.value - 1) * 6 
})

const endIndex = computed(() => {
  return page.value * 6
})

// это объясняется чуть позже
const filteredTickers = computed(() => {
  return tickers.value.filter(
    t => t.name.includes(filter.value.toUpperCase()))
})

// не забудьте поменять в шаблоне filteredTickers на paginatedTickers
const paginatedTickers = computed(() => {
  return filteredTickers.value.slice(startIndex.value, endIndex.value)
})

// не забудьте удалить переменную hasNextPage
const hasNextPage = computed(() => {
  // я уже упоминал, что computed возвращает реактивный объект, т.е. к его значению надо обращаться через свойство .value  
  return filteredTickers.value.length > endIndex.value
})

Как понять что нужно использовать computed? Очень просто. Если нет прямого изменения состояния (присваивания), а только вычисление по другим реактивным переменным (в лекции сказали ещё "если результат используется в шаблоне", но это не так, в этом же примере startIndex и endIndex сделаны вычисляемыми, но они используются только в коде).

Смотрим, что ещё можно поменять на computed?

  • normalizeGraph - переделайте в computed самостоятельно, заодно переименуйте в normalizedGraph (прилагательное вместо глагола, т.е. у нас не действие, а свойство)

  • разбить filteredTickers на два вычисляемых свойства (выше уже есть код)

Починка графика

Если минимальное значение равно максимальному, то возвращать 50%, чтобы было ровное плато посередине.

const normalizedGraph = computed(() => {
  const maxValue = Math.max(...graph.value)
  const minValue = Math.min(...graph.value)

  if (maxValue == minValue) {
    return graph.value.map(() => 50)
  }

  return graph.value.map(
    price => 5 + ((price - minValue) * 95) / (maxValue - minValue)
  )
})

Применение "наблюдателей"

В коде часто встречаются условия "если изменилось что-то, то сделай то-то". Это значит, что можно использовать watch

Рассматривается пример с удалением тикеров на второй странице. В результате мы остаемся на пустой странице.

Вариантов решения два:

  • в методе удаления тикеров проверить длину массива paginatedTickers и если он пустой, то уменьшить номер страницы

  • использовать наблюдатели

    Обратите внимание, я использую синтаксис с параметрами. Первым параметром в функции приходит новое значение переменной.

    watch(paginatedTickers, (value) => {
        if (value.length === 0 && page.value > 1) page.value--
    })

при удалении тикера остается выбор

В метод handleDelete добавляем логику проверки и очистки выделенного тикера

if (sel.value == tickerToRemove) {
    sel.value = null
}

Заодно поменяйте sel на человеко-понятное наименование (названия переменных должны быть самоочевидны)

Вынос сброса графика в методе select в watch

Напоминаю текущий код:

function select (ticker) {
  sel.value = ticker
  graph.value = []
}

Он соответствует правилу "если изменилось что-то, то сделай то-то", соответственно можно его перенести очистку графика в наблюдатель (сделайте самостоятельно)

Вынос логики сохранения тикеров в LocalStorage в наблюдатель (заодно исправится бага, когда список не сохранялся при удалении тикера)

Наблюдатель реализуйте сами. И не забудьте изменить добавление тикеров в массив (иначе на сработает watch на массив)

tickers.value = [...tickers.value, newTicker]

Что делает этот код?

Выражение ...array делает декомпозицию массива, т.е. извлекает все его элементы и вставляет в новый массив. В итоге в массив тикеров записывается новый массив, содержащий все старые элементы и новый.

Исправление дублирования логики в наблюдателях page и filter

  1. Создаем вычисляемое свойство pageStateOptions, которое будет хранить и фильтр и страницу:

    const pageStateOptions = computed(() => {
        return {
            filter: filter.value,
            page: page.value
        }
    })
  2. И вешаем на него наблюдателя

    watch(pageStateOptions, (value) => {
        window.history.pushState(
            null,
            document.title,
            `${window.location.pathname}?filter=${value.filter}&page=${value.page}`
        )
    })

Задание

Назад | Дальше