Я использую Suspense и извлекаю асинхронные данные на страницах. При инициализации виден индикатор резервной загрузки, но при переключении страниц я сохраняю текущее содержимое страницы, пока новая страница не будет готова + полоса nprogress, поэтому сайт ведет себя больше как SSR. Есть еще Переход между, но это не важно для этого вопроса.
И это прекрасно работает. Проблема в том, что на моей странице есть дочерние страницы (вложенные маршруты), также с Suspense. Когда я нахожусь на вложенной странице, такой как /demo/demo1, и перехожу на главную, содержимое шаблона /demo остается таким, каким должно быть, но содержимое подстраницы demo1 исчезает. Мне нужно сохранить вложенную приостановку, пока родительская приостановка не будет готова. Есть идеи?
Я максимально упростил код. Я использую один и тот же код <RouterView> как в корневом приложении, так и на странице.
<RouterView v-slot = "{ Component }">
<template v-if = "Component">
<Suspense>
<component :is = "Component" />
<template #fallback>
Loading...
</template>
</Suspense>
</template>
</RouterView>
Вот живое воспроизведение: https://stackblitz.com/edit/vitejs-vite-gg6kqo?file=src/pages/demo/demo1.vue (нет nprogress, просто подождите 1 с между страницами).
Шаги к размножению:
Vue v3.3.2, теперь это работает, как я и ожидал.
@Марк странно. Я протестировал альфа-версию, прежде чем спросить здесь, потому что в 3.3.0 реализована опция «приостановка». Но это ничего не изменило в моем вопросе. Затем снова протестирую его с 3.3.2, с этой опцией и без нее. Спасибо.
Я заметил журнал изменений в Nuxt3, в котором явно упоминается о повышении его версии зависимости Vue до 3.3.2, потому что было выпущено исправление ошибки вложенных страниц, но я не видел документированных изменений в самом журнале изменений Vue 3 для этой проблемы.
@Marc проверил это снова, и после перехода на 3.3.4 в моей репродукции или реальном приложении ничего не изменилось: / Подстраница по-прежнему исчезает после переключения корневой страницы.
Я обновил свой ответ дальше по теме тем, что я делаю, возможно, там есть что-то, что могло бы ответить на это, это вызывающе работает для меня, которое я запустил на прошлой неделе, как только заметил дискретное исправление. Я предполагаю, что они вполне в этом заинтересованы, потому что официально производство не готово.



![Безумие обратных вызовов в javascript [JS]](https://i.imgur.com/WsjO6zJb.png)


Я бы не сказал, что это лучший ответ, но мне кажется, что для того, чтобы <Suspense> работал, его дочерний элемент должен быть асинхронным компонентом. В этом случае <RouterView> отображается как неасинхронный компонент (даже несмотря на то, что его дочерние элементы являются асинхронными компонентами), поэтому корень <Suspense> не будет ждать <Suspense> внутри <RouterView>.
В качестве обходного пути, я думаю, мы можем кэшировать компонент, чтобы он не выглядел так, как будто он исчез при смене маршрутизатора (третий случай).
Мой метод кэширования заключается в добавлении этого <script setup> в файл demo.vue:
<script setup>
import { ref } from 'vue';
const CachedComponent = ref(null);
function cacheComponent(Component) {
CachedComponent.value = Component || CachedComponent.value;
}
</script>
А затем измените <RouterView> внутри того же файла на это:
...
<RouterView v-slot = "{ Component }">
{{ cacheComponent(Component) }}
<template v-if = "Component || CachedComponent">
<Suspense>
<component :is = "Component || CachedComponent" />
<template #fallback>
<div style = "font-size: 1.5rem">Loading Demo Nested...</div>
</template>
</Suspense>
</template>
</RouterView>
...
Строка {{ cacheComponent(Component) }} будет кэшировать компонент при его рендеринге (она используется таким образом, потому что <RouterView> не имеет обратного вызова, когда его слоты смонтированы). Строка CachedComponent.value = Component || CachedComponent.value; обновит значение CachedComponent, если есть новый компонент, который не является ложным. И если это ложно (это означает, что он еще не смонтирован или больше не смонтирован), он отобразит предыдущую последнюю кэшированную версию компонента. Я знаю, что это хакерство, но, на мой взгляд, это очень простой обходной путь.
Это ответвление стека, если вам интересно.
Я уже об этом думал, но мне не понравилась идея кэширования всего экземпляра компонента и выполнения этого метода внутри шаблона, потому что это анти-шаблон. Но если других вариантов не будет, попробую этот. Просто нужно немного рабочего времени, чтобы проверить это. Спасибо!
Я только что протестировал эту версию, и вложенные страницы маршрутизатора с async setup работают.
Из того, что я вижу, страницы вложенных страниц маршрутизатора больше не отображают пустые страницы с предупреждением консоли о необходимости завернуть в Suspense.
Вместо этого верхний уровень Suspense теперь обрабатывает дочерние страницы, не предполагая, что вокруг вложенных Suspense должен быть RouterView.
// App.vue
<RouterView v-slot = "{ Component, route }">
<template v-if = "Component">
<KeepAlive>
<Suspense>
<component :is = "Component"/>
<template #fallback>
LOADING...
</template>
</Suspense>
</KeepAlive>
</template>
</RouterView>
// pages/nested-path/index.vue
<div>
<RouterView v-slot = "{ Component, route }">
<component :is = "Component" :key = "route.path"/>
</RouterView>
</div>
Это просто не работает в настоящее время для сложных проектов, когда у вас есть вложенные маршруты, оно отлично работает с маршрутизацией на одном уровне, как вы заметили.
Я некоторое время боролся с этим, перепробовал много комбинаций и обнаружил, что вложенная приостановка не работает, это связано с нарушением функциональности вокруг key, который пассивно используется для компонента маршрутизатора, я попытался установить это вручную, что дало различные результаты и другие неустранимые / раздражающие проблемы, включая любое использование с keep-alive.
Невозможность использовать async setup была действительно раздражающей и досадной, особенно для приостановки во время согласования других промисов перед попыткой вызова API, мне особенно нравится, насколько чистым был бы код.
Мне пришлось согласиться на асинхронный watch, где я задал loading = ref(true) состояние, переданное пользовательской оболочке компонента состояния загрузки, которая будет блокировать ввод-вывод контента и отображать ход загрузки в теле контента.
// myPage.vue
<script setup lang = "ts">
const props = defineProps<{
id: string
}>()
const loading = ref(true)
watch(props, async () => {
loading.value = true
await myApi.endpoint.get(props)
loading.value = false
}, {immediate: true})
</script>
<template>
<content-loader :loading = "loading">
<div>
My page content
</div>
</content-loader>
</template>
// ContentLoader.vue
<script setup lang = "ts">
withDefaults(defineProps<{
loading: boolean
}>(), {
loading: true
})
</script>
<template>
<div class = "d-flex fill-height justify-center align-center">
<template v-if = "$props.loading">
<span>Content loading...
</template>
<template v-else>
<slot/>
</template>
</div>
</template>
Если хотите, вы также можете добавить туда переходы.
Я также думал о повторном выводе повторения оболочки загрузки и ссылки на загрузку, переместив ее в родительский компонент, содержащий дочерний элемент router-view, и используя emit на дочерней странице для переключения загрузки. Я не пробовал это, нужно убедиться, что начальное состояние истинно при переходе на другие страницы.
Может еще не работает. github.com/vuejs/core/issues/5637