ТОС:

  • Что означает асинхронный вызов?
  • Ожидание загрузки асинхронных компонентов
  • Использование с vue-router
  • Как отобразить в запросе API загрузку
  • Использовать с vuex
  • Захватить все асинхронные вызовы в дереве компонентов
  • Менеджер ресурсов
  • Форк менеджера ресурсов
  • Предотвратить опцию и предотвратить дублирование представлений
  • Форма загрузки
  • Обработка ошибок
  • О LRU Cache

Github: https://github.com/shuidi-fed/vue-async-manager

Документы: https://shuidi-fed.github.io/vue-async-manager/

Что означает асинхронный вызов?

Упомянутый здесь асинхронный вызов в основном относится к двум вещам:

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

Ожидание загрузки асинхронных компонентов

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

new Vue({
  // ...
  components: {
    'my-component': () => ({
        // async component
        component: import('./my-async-component'),
        // loading component
        loading: LoadingComponent,
        // delay
        delay: 200
    })
  }
})

delay используется для указания времени задержки для отображения компонента loading. Время задержки в приведенном выше коде 200ms. Если загрузка асинхронного компонента завершена в пределах 200ms, компонент loading не может быть отображен.

Но у него есть две проблемы:

  • 1. Компонент loading тесно связан с асинхронным компонентом, а loadingcomponent не может быть повышен до более высокого уровня и использоваться для загрузки нескольких асинхронных компонентов.
  • 2 、 Если у самого асинхронного компонента все еще есть асинхронные вызовы, такие как API запроса, скрытие компонента loading не будет ждать завершения запроса API.

vue-async-manager предоставляет компонент <Suspense> для решения двух вышеупомянутых проблем.

1. Создайте асинхронный компонент с помощью функции lazy.

Раньше мы создавали асинхронный компонент :

const asyncComponent = () => import('./my-async.component.vue')

Теперь мы используем функцию lazy, предоставленную vue-async-manager, для создания асинхронного компонента:

import { lazy } from 'vue-async-manager'
 
const asyncComponent = lazy(() => import('./my-async.component.vue'))

Как показано в приведенном выше коде, просто передайте исходную асинхронную фабричную функцию в качестве параметра функции lazy.

2. Оберните асинхронные компоненты в компонент <Suspense>.

<template>
  <div id="app">
    <!-- Use the Suspense component to wrap a component tree that may have async components -->
    <Suspense>
      <!-- Loading slot -->
      <div slot="fallback">loading</div>
      <!-- Async component -->
      <asyncComponent1/>
      <asyncComponent2/>
    </Suspense>
  </div>
</template>
<script>
// Create async components
const asyncComponent1 = lazy(() => import('./my-async.component1.vue'))
const asyncComponent2 = lazy(() => import('./my-async.component2.vue'))
 
export default {
  name: 'App',
  components: {
    // Registration component
    asyncComponent1,
    asyncComponent2
  }
}
</script>

Компонент loading исчезнет только после загрузки <asyncComponent1/> и <asyncComponent2/>.

Живая демонстрация: Ожидание загрузки всех асинхронных компонентов

Использование с vue-router

При разработке приложения Vue наиболее распространенным способом использования асинхронных компонентов является разделение кода с помощью vue-router, например:

const router = new VueRouter({
  routes: [
    {
      path: '/',
      component: () => import('./my-async-component.vue')
    }
  ]
})

Чтобы компонент <Suspense> ожидал загрузки этого асинхронного компонента, мы можем обернуть эту фабричную функцию асинхронного компонента функцией lazy:

const router = new VueRouter({
  routes: [
    {
      path: '/',
      component: lazy(() => import('./my-async-component.vue'))
    }
  ]
})

Наконец, нам нужно только обернуть компонент <router-view> компонентом <Suspense>:

<Suspense :delay="200">
  <div slot="fallback">loading</div>
  <router-view/>
</Suspense>

Живая демонстрация: С vue-router

Как отобразить загрузку в запросе API

Раньше мы обычно вручную определяли, нужно ли показывать loading, например, когда «запрос на запуск» отображался loading. Скрыть loading, когда «конец запроса». А если есть несколько запросов на параллелизм, вы должны дождаться завершения всех запросов, прежде чем скрывать loading. Короче говоря, вам необходимо самостоятельно поддерживать состояние loading, независимо от того, хранится ли оно в компоненте или в store.

Теперь давайте посмотрим, как vue-async-manager решает loading проблему отображения в процессе запроса API, предполагая следующий код:

<Suspense>
  <div slot="fallback">loading...</div>
  <MyComponent/>
</Suspense>

Компонент <MyComponent> отображается внутри компонента <Suspense>, который является обычным компонентом. Внутри компонента <Suspense> отправляется запрос API, как показано в следующем коде:

<!-- MyComponent.vue -->
<template>
  <!-- Display data -->
  <div>{{ res }}</div>
</template>
 
<script>
import { getAsyncData } from 'api'
 
export default {
  data: {
    res: {}
  },
  async created() {
    // Fetch data
    this.res = await getAsyncData(id)
  }
}
</script>

Это код, который мы часто видим, обычно мы отправляем асинхронные запросы в ловушках created или mounted. Однако для компонента <Suspense> он не знает, что ему нужно дождаться завершения асинхронных запросов, прежде чем скрывать loading. Чтобы решить эту проблему, мы можем создать диспетчер ресурсов, используя функцию createResource, предоставленную vue-async-manager:

<template>
  <!-- Display data -->
  <div>{{ $rm.$result }}</div>
</template>
 
<script>
import { getAsyncData } from 'api'
import { createResource } from 'vue-async-manager'
 
export default {
  created() {
    // Create a resource manager(rm)
    this.$rm = createResource((params) => getAsyncData(params))
    // Read data
    this.$rm.read(params)
  }
}
</script>

Передав фабричную функцию функции createResource, мы создаем диспетчер ресурсов: $rm, а затем вызываем функцию $rm.read() диспетчера ресурсов для чтения данных. Обратите внимание, что приведенный выше код написан синхронно, и компонент <Suspense> знает, что компонент выполняет асинхронный вызов, поэтому компонент <Suspense> будет ждать завершения асинхронного вызова, прежде чем скрыть loading.

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

Живая демонстрация: Приостановленный компонент ожидает, пока менеджер ресурсов получит данные

Использовать с vuex

С vuex это очень просто, просто используйте mapActions для сопоставления actions с методами:

export default {
  name: "AsyncComponent",
  methods: {
    ...mapActions(['increase'])
  },
  created() {
    this.$rm = createResource(() => this.increase())
    this.$rm.read()
  }
};

Живая демонстрация: Использование с vuex

Захватить все асинхронные вызовы в дереве компонентов

Компонент <Suspense> захватывает не только загрузку асинхронных компонентов. Если у самого асинхронного компонента есть другие асинхронные вызовы, такие как чтение данных через диспетчер ресурсов, компонент <Suspense> также может захватывать эти асинхронные вызовы и ждать завершения всех асинхронных вызовов, прежде чем скрывать loading.

Давайте посмотрим на пример :

<Suspense>
  <div slot="fallback">loading</div>
  <!-- MyLazyComponent is a component created by the lazy function -->
  <MyLazyComopnent/>
</Suspense>

Компонент <MyLazyComopnent/> - это компонент, созданный функцией lazy, поэтому компонент <Suspense> может ждать загрузки асинхронного компонента, тогда как сам асинхронный компонент считывает данные через диспетчер ресурсов:

// Async component
export default {
  created() {
    // Read data through the resource manager
    this.$rm = createResource((params) => getAsyncData(params))
    this.$rm.read(params)
  }
}

На этом этапе компонент <Suspense> будет ждать завершения обоих асинхронных вызовов, прежде чем скрыть loading, а именно:

  • 1 、 Асинхронная загрузка компонентов
  • 2. Асинхронные запросы, отправленные диспетчером ресурсов в рамках асинхронного компонента.

Живая демонстрация: Приостановленный компонент ожидает, пока диспетчер ресурсов получит данные

Менеджер ресурсов

Мы подчеркнули слово: диспетчер ресурсов , возвращаемое значение createResourceфункции - диспетчер ресурсов, который является объектом, мы обычно используем $rm для имени.

Полная форма диспетчера ресурсов выглядит следующим образом:

this.$rm = createResource(() => getAsyncData())
this.$rm = {
    read(){},   // A function that calls this function to actually send an async request to fetch data
    $result,    // The initial value is null. After the async data is successfully acquired, the obtained data is stored.
    $error,     // The initial value is null, which holds the err data when the async request fails.
    $loading,   // A boolean value with an initial value of false, indicating whether the request is in progress
    fork()      // Create a new resource manager based on an existing resource manager
}

Функция $rm.read() используется для отправки асинхронных запросов на выборку данных, которые можно вызывать несколько раз, например, нажав кнопку для повторного вызова. $rm.$result мы также видели это, которое используется для хранения данных, полученных асинхронно. $rm.$loading - логическое значение, указывающее, выполняется ли запрос. Обычно мы можем настроить отображение loading следующим образом:

<template>
  <!-- Control loading display -->
  <MyButton :loading="$rm.$loading" @click="submit" >提交</MyButton>
</template>
 
<script>
import { getAsyncData } from 'api'
import { createResource } from 'vue-async-manager'
 
export default {
  created() {
    // Create a resource manager
    this.$rm = createResource((params) => getAsyncData(params))
  },
  methods: {
    submit() {
      this.$rm.read(params)
    }
  }
}
</script>

Что еще более важно: createResource() можно использовать отдельно от <Suspense>.

Если диспетчер ресурсов обнаружил ошибку при выборке данных, данные об ошибке сохраняются в свойстве $rm.$error. Функция $rm.fork() используется для создания идентичного диспетчера ресурсов на основе существующего диспетчера ресурсов.

Форк менеджера ресурсов

Когда API используется для получения данных, и нам нужно получить данные дважды, нам нужно только вызвать $rm.read()twice:

<script>
import { getAsyncData } from 'api'
import { createResource } from 'vue-async-manager'
 
export default {
  created() {
    // Create a resource manager
    this.$rm = createResource((type) => getAsyncData(type))
     
    // Continuous fetch data twice
    this.$rm.read('top')
    this.$rm.read('bottom')
  }
}
</script>

Но это создаст проблему, поскольку один менеджер ресурсов связан только с одним $rm.$result, поэтому в приведенном выше коде $rm.$result сохранит только данные $rm.read('bottom'). Конечно, иногда это ожидается, но если вам нужно сохранить дважды вызванные данные, вам нужно fork, чтобы создать новый диспетчер ресурсов:

<script>
import { getAsyncData } from 'api'
import { createResource } from 'vue-async-manager'
 
export default {
  created() {
    // Create a resource manager
    this.$rm = createResource((type) => getAsyncData(type))
    // Fork a new resource manager based on the existing resource manager
    this.$rm2 = this.$rm.fork()
    
    // The data read twice will be stored separately
    this.$rm.read('top')
    this.$rm2.read('bottom')
  }
}
</script>

Таким образом, поскольку $rm и $rm2 - два отдельных диспетчера ресурсов, они не влияют друг на друга.

prevent вариант и повторяющиеся заявки

Предположим, мы отправляем форму. Если пользователь щелкнет кнопку дважды, это вызовет дублирование отправки, как в следующем примере:

<template>
  <button @click="submit">Submit</button>
</template>
<script>
import { getAsyncData } from 'api'
import { createResource } from 'vue-async-manager'
 
export default {
  created() {
    // Create a resource manager
    this.$rm = createResource((type) => getAsyncData(type))
  },
  methods: {
    submit() {
      this.$rm.read(data)
    }
  }
}
</script>

Фактически, мы можем предоставить параметр prevent при создании диспетчера ресурсов, поэтому созданный диспетчер ресурсов автоматически предотвратит дублирование отправлений для нас:

<template>
  <button @click="submit">Submit</button>
</template>
<script>
import { getAsyncData } from 'api'
import { createResource } from 'vue-async-manager'
 
export default {
  created() {
    // Create a resource manager with the prevent option
    this.$rm = createResource((type) => getAsyncData(type), { prevent: true })
  },
  methods: {
    submit() {
      this.$rm.read(data)
    }
  }
}
</script>

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

стиль загрузки

Стиль loading можно разделить на два типа: первый предназначен для отображения только loading и не для отображения другого содержимого; другой - отображать loading при обычном рендеринге другого содержимого, например длинной полосы загрузки в верхней части страницы. Эта полоса загрузки не влияет на нормальный рендеринг другого содержимого.

Итак, vue-async-manager предоставляет два режима рендеринга:

import VueAsyncManager from 'vue-async-manager'
Vue.use(VueAsyncManager, {
  // Specify the rendering mode, the optional value is 'visible' | 'hidden', the default value is: 'visible'
  mode: 'visible'
})

По умолчанию используется режим рендеринга 'visible'. Это означает, что loading может сосуществовать с другим контентом. Если вам не нужен этот режим рендеринга, вы можете указать mode на 'hidden'.

До сих пор мы видели только использование <Suspense> компонентов для управления отображением loading, а содержимое loading определяется слотом fallback компонента <Suspense>. Но иногда мы хотим быть более гибкими, мы часто сталкиваемся с таким сценарием: когда вы нажимаете кнопку и отображаете крошечный значок loading на кнопке, наш код может выглядеть так:

<MyButton :loading="isLoading" >Submit</MyButton>

Стиль loading обеспечивается компонентом <MyButton> , другими словами, мы отказались от слота fallback <Suspense> как loading, чтобы показать. Следовательно, нам нужно знать, загружается ли он в данный момент. Мы уже представили решение этой проблемы, мы можем использовать свойство $rm.$loading диспетчера ресурсов:

<MyButton :loading="$rm.$loading" >Submit</MyButton>

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

Когда компонент lazy не загружается, отображается слот error компонента <Suspense>. Вы также можете настроить обработку ошибок, прослушивая событие rejected для <Suspense>.

Живая демонстрация: При загрузке не удалось отобразить слот ошибки

Помимо отображения слота error при возникновении ошибки, вы также можете настроить обработку, прослушивая событие rejected компонента <Suspense>:

<template>
  <Suspense :delay="200" @rejected="handleError">
    <p class="fallback" slot="fallback">loading</p>
    <AsyncComponent/>
  </Suspense>
</template>
<script>
export default {
  // ......
  methods: {
    handleError() {
      // Custom behavior
    }
  }
};
</script>

Живая демонстрация: Ошибка обработки события

О LRU Cache

React Cache использует алгоритм LRU для кэширования ресурсов, который требует, чтобы API был идемпотентным. Однако в моей рабочей среде очень мало API-интерфейсов, действительно идемпотентных в заданный период времени, поэтому нет никаких условий для возможности кэширования ресурсов.