Nuxt Fetch. How?

Начиная с версии 2.12 в Nuxt появился новый подход к получению данных приложением.

Хук Fetch и жизненный цикл Nuxt

Сверху вниз

    NuxtServerInit
        |
   Route Middleware
        |
     validate     
        |
    asyncData()
        |
   beforeCreate()
        |
     created() 
        |
[$fetchState.pending = true]
  >>> fetch() <<<
        |
     mounted()
[$fetchState.pending = false]
      

Хук fetch() вызывается  после  того, как экземпляр компонента создан на стороне сервера. Это делает доступным контекст this внутри fetch().

export default {
	fetch() {
        console.log(this)
    }
}

Page Components

И так что же это означает для компонентов страниц (pages). При помощи контекста this, fetch может изменить данные компонента напрямую. Это значит, что можно определить локальные данные компонента, без необходимости вызывать события/фиксировать изменения через  Vuex Store для Page компонента.

В результате Vuex становиться не обязательным. Конечно по прежнему можно использовать  this.$store, как обычно для доступа к Vuex Store, если необходимо.

Доступность хука fetch()

Используя fetch() можно асинхронно загружать данные в любых компонентах.  Это означает, что не только компоненты в директории /pages/*, но и любые другие .vue компоненты в  /layouts и /components могут использовать этот подход.

Layouts (макеты)

Теперь, используя новый подход с fetch(), возможно делать запросы непосредственно из Layout компонентов. Что  было невозможно до версии Nuxt 2.12.

Возможное применение:

  • Загрузка конфигурации с сервера, для динамической генерации футер (footer) или навигации (navbar)
  • Загрузка данных пользователя, например профиль, корзина в navbar
  • Загрузка соответствующих сайту данных в layouts/error.vue

Блочные (Child/Nested) компоненты

Используя fetch() в дочерних, можно исключить загрузку некоторых данных из уровня компонента страницы Page, делегировать часть задач загрузки в дочерние компоненты. Все еще можно передать параметры в дочерние компоненты, но данные каждый компонент сможет загрузить самостоятельно.

Порядок выполнения при множественном использовании fetch()

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

fetch() выполняется на стороне сервера один раз (при первом запросе Nuxt приложения) и затем выполняется на стороне клиента при запросе соответствующего маршрута. Но поскольку можно определить fetch() для каждого компонента, все они будут исполняться последовательно в соответствие с иерархией.

Отключение fetch() на стороне сервера

Можно отключить  выполнение fecth() на стороне сервера, выполнение будет проходить только на стороне клиента:

export default {
    fetchOnServer: false
}

Свойство $fetchState.pending на стороне сервера будет установлено в true

Обработка ошибок

Обработка ошибок теперь происходит на уровне компонента. Поскольку данные загружаются асинхронно, fetch() предлагает  объект $fetchState для контроля за состоянием запроса:

$fetchState = {
    pending: true | false,
    error: null | [],
    timestamp: Integer
}
  1. pending - поможет в отображении статуса загрузки
  2. Error - сообщения об ошибках
  3. Timestamp - отображает временную метку последнего запроса, которую можно использовать при кешировании совместно с keep-alive
<template>
    <div>
      <p v-if="$fetchState.pending">Loading data...</p>
	  <p v-else-if="$fetchState.error">Error while lodaing!</p>
      <ul v-else>
          <li v-for="..." />
      </ul>    
    </div>
</template>    

При возникновении ошибки  на уровне компонента, можно установить HTTP статус на стороне сервера используя проверку process.server внутри fetch() и передать через throw new Error()

async fetch() {
    const post = await fetch('//url/post').then((res) => res.json())
    if (post.id === this.$route.params.id) {
        this.post = post
    } else {
        // on server
        if (process.server) {
            this.$nuxt.context.res.statusCode = 404
        }
        
        // throw
        throw new Error('Post not found')
    }
}

Установка правильного статуса ответа кране важна для SEO.

fetch() как метод

fetch() можно использовать как обычный метод. Например для программного обновления данных

<button @click="$fetch">refresh</button>

или в методе компонента

export default {
    methods: {
        refresh() {
            this.$fetch()
        }
    }
}

Эффективность страниц

Используем :keep-alive-props, хук activated и хук fetch() вместе, для повышения эффективности страниц.

Nuxt может кешировать в памяти страницы вместе с извлеченными данными. И указать количество секунд, через которое следует запросить данные снова.

Используем keep-alive на <nuxt /> и <nuxt-child />

<template>
	<div>
    	<nuxt keep-alive />
    </div>
</template>

для добавления параметров используем :keep-alive-props

<nuxt keep-alive :keep-alive-props="{ max: 10 }" />

Это один из способов повышения производительности страниц. Это общий универсальный способ. Другой подход использовать свойство $fetchData.timestamp для более точной настройки. Используется Vue хук activated

export default {
    activated() {
        // more than 1 minute ago
        if (this.$fetchState.timestamp <= Date.now() - 60000) {
            this.$fetch()
        }
    }
}

asyncData vs Fetch

asyncData

  1. asyncData работает только для page-компонентов
  2. this контекст не доступен
  3. Добавляет результат возвращая значение
export default {
	async asyncData(context) {
        const data await context.$axios.$get('//fetch/url')
        return  {
            todos: data.item
        }
    }
}

новый fetch()

  1. Доступен во всех Vue компонентах
  2. this контекст доступен
  3. Просто изменяет локальные данные
export default {
    data() {
        return {
            todos: []
        }
    },
    async fetch() {
        const { data } = await axios.get('//fetch/url')
        this.todos = data
    }
}

И так

Новый fetch() предлагает множество улучшений, большую гибкость при получении данных и организации маршрутов, а также возможность загружать данные на уровне отдельных строительных блоков страницы (pages, layouts, componnets).  Новый подход предполагает множество запросов к API для одной страницы, что требует внимательней относиться к проектированию страниц и приложения в целом.