ТОС:
- Что означает асинхронный вызов?
- Ожидание загрузки асинхронных компонентов
- Использование с 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
тесно связан с асинхронным компонентом, аloading
component не может быть повышен до более высокого уровня и использоваться для загрузки нескольких асинхронных компонентов. - 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.$result
property, важно отметить, что само свойство реактивный, поэтому вам не нужно объявлять его в опции 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-интерфейсов, действительно идемпотентных в заданный период времени, поэтому нет никаких условий для возможности кэширования ресурсов.