задний план
Это произошло около полугода назад, кто-тоviteрепозиторий упоминает оvue-router
изissue:
вопрошающий имеет в видуvue-router
В его предстоящем проекте есть нерешенная ошибка, иvue-router
Сопровождающий не смог решить эту проблему, я надеюсь, что Youda поможет ее решить.
Однако очень неуместно, чтобы спрашивающий заходил в репозиторий vite, чтобы опубликовать эту проблему.
Ю Да был явно очень недоволен таким поведением, и ответ тоже был властным:
-
Не поднимайте неуместные проблемы в неуместных репозиториях.
-
Все очень заняты, если у вас нет времени, у вас просто нет времени, не торопитесь.
-
Повторные нарушения будут заблокированы.
Причина, по которой спрашивающий пришел сюда, чтобы опубликовать вопрос, заключается в том, что You Da часто активен в сообществе vite. На мой взгляд, Ю Да должен был давно знать об этом вопросе, т.к.vue-router
сопровождающие также являются одними из основных членов Vue, и они, должно быть, говорили об этом в частном порядке.
Итак, почему задержка не решает эту проблему, я думаю, есть две причины:
-
Саму эту проблему решить непросто, и она может потребовать внесения большого количества изменений в код.
-
Команда Vue усердно работает над тем, что они считают важным и срочным, и это не является приоритетом.
Так с чем именно столкнулся спрашивающий? Почему я обращаю внимание на этот вопрос? Так как недавно у меня была аналогичная проблема.
аналогичный вопрос
в моемКурс "Разработка корпоративного музыкального приложения Vue3"В области вопросов и ответов студент ответил вопросом:RouterView
СотрудничатьKeepAilve
После использования компонента страница сведений о певце вторичной маршрутизацииcreated
Функция ловушки выполняется дважды.
После того, как я протестировал его, я обнаружил, что эта проблема существует. Сначала я подозревал, что это Vue3 илиvue-router
Ошибка версии, поэтому я поставил Vue3 иvue-router
Оба обновились до последней версии и обнаружили, что проблема все еще существует.
Итак, не возникнет ли проблем с написанием моего бизнес-кода? Интуиция подсказывает нет, чтобы найти первопричину проблемы и снизить сложность отладки, я написал минимизацию для воспроизведения проблемы.demo.
На демонстрационной странице есть вторичные маршруты, которые определяются следующим образом:
import { createRouter, createWebHashHistory } from 'vue-router'
const Home = import('../views/Home.vue')
const HomeSub = import('../views/HomeSub.vue')
const Sub = import('../views/Sub.vue')
const About = import('../views/About.vue')
const routes = [
{
path: '/',
redirect: '/home'
},
{
path: '/home',
name: 'Home',
component: Home,
children: [
{
path: 'sub',
component: HomeSub
}
]
},
{
path: '/about',
name: 'About',
component: About,
children: [
{
path: 'sub',
component: Sub
}
]
}
]
const router = createRouter({
history: createWebHashHistory(),
routes
})
export default router
Обратите внимание,Обе основные страницы маршрутизации должны иметь вложенные подмаршруты..
Далее давайте посмотрим на определения нескольких компонентов Vue страницы, среди которыхApp.vue
Для компонента входа на страницу:
<template>
<div id="nav">
<router-link to="/">Home</router-link> |
<router-link to="/about">About</router-link>
</div>
<router-view v-slot="{ Component }">
<keep-alive>
<component :is="Component"/>
</keep-alive>
</router-view>
</template>
Home.vue
является компонентом маршрутизации первого уровня:
<template>
<div class="home">
<img alt="Vue logo" src="../assets/logo.png">
<button @click="showSub">click me</button>
<router-view></router-view>
</div>
</template>
<script>
export default {
name: 'Home',
created() {
console.log('home page created')
},
methods: {
showSub() {
this.$router.push('/home/sub')
}
}
}
</script>
HomeSub
даHome
Компоненты вторичной маршрутизации в компонентах:
<template>
<div>This is home sub</div>
</template>
<script>
export default {
name: 'HomeSub',
created() {
console.log('home sub created')
}
}
</script>
About.vue
является компонентом маршрутизации первого уровня:
<template>
<div class="about">
<h1>This is an about page</h1>
<button @click="showSub">click me</button>
<router-view></router-view>
</div>
</template>
<script>
export default {
name: 'About',
created() {
console.log('about page created')
},
methods: {
showSub() {
this.$router.push('/about/sub')
}
}
}
</script>
Sub.vue
даAbout
Компоненты вторичной маршрутизации в компонентах:
<template>
<div>This is sub</div>
</template>
<script>
export default {
name: 'Sub',
created() {
console.log('sub created')
}
}
</script>
Шаги для воспроизведения очень просты, сначала введитеHome
Страница:
затем нажмитеAbout
запись тегаAbout
Страница:
Затем нажмите кнопку для визуализацииSub
Дочерний компонент маршрутизации:
Отрисовка страницы нормальная, но мы обнаружили, чтоSub
компонентcreated
Функция ловушки выполняется дважды и выводится дваждыsub created
. Это эквивалентно рендерингу дваждыSub
С компонентами явно беда.
анализ ошибок
Я включил метод отладки, вSub
компонентcreated
Крючок в функции крючкаdebugger
точки останова, а затем пройтись по стеку вызовов функции.
Очевидно,debugger
вcreated
Внутри функции хука, а выполнение функции хука находится на этапе монтирования компонента, так какая операция запускает монтирование компонента?
Продолжая просматривать стек вызовов, мы обнаружили, что последней причиной была модификация маршрутизации.currentRoute
, сработалsetter
, затем срабатываетRouterView
Перенаправления компонента, который в конечном итоге триггерыSub
Рендеринг компонентов.
так почемуcurrentRoute
модификация триггераRouterView
Как насчет повторного рендеринга компонентов? Это изRouterView
Принцип реализации:
const RouterViewImpl = defineComponent({
name: 'RouterView',
inheritAttrs: false,
props: {
name: {
type: String,
default: 'default',
},
route: Object,
},
setup(props, { attrs, slots }) {
(process.env.NODE_ENV !== 'production') && warnDeprecatedUsage()
const injectedRoute = inject(routerViewLocationKey)
const routeToDisplay = computed(() => props.route || injectedRoute.value)
const depth = inject(viewDepthKey, 0)
const matchedRouteRef = computed(() => routeToDisplay.value.matched[depth])
provide(viewDepthKey, depth + 1)
provide(matchedRouteKey, matchedRouteRef)
provide(routerViewLocationKey, routeToDisplay)
const viewRef = ref()
watch(() => [viewRef.value, matchedRouteRef.value, props.name], ([instance, to, name], [oldInstance, from, oldName]) => {
if (to) {
to.instances[name] = instance
if (from && from !== to && instance && instance === oldInstance) {
if (!to.leaveGuards.size) {
to.leaveGuards = from.leaveGuards
}
if (!to.updateGuards.size) {
to.updateGuards = from.updateGuards
}
}
}
if (instance &&
to &&
(!from || !isSameRouteRecord(to, from) || !oldInstance)) {
(to.enterCallbacks[name] || []).forEach(callback => callback(instance))
}
}, { flush: 'post' })
return () => {
const route = routeToDisplay.value
const matchedRoute = matchedRouteRef.value
const ViewComponent = matchedRoute && matchedRoute.components[props.name]
const currentName = props.name
if (!ViewComponent) {
return normalizeSlot(slots.default, { Component: ViewComponent, route })
}
const routePropsOption = matchedRoute.props[props.name]
const routeProps = routePropsOption
? routePropsOption === true
? route.params
: typeof routePropsOption === 'function'
? routePropsOption(route)
: routePropsOption
: null
const onVnodeUnmounted = vnode => {
if (vnode.component.isUnmounted) {
matchedRoute.instances[currentName] = null
}
}
const component = h(ViewComponent, assign({}, routeProps, attrs, {
onVnodeUnmounted,
ref: viewRef,
}))
return (
normalizeSlot(slots.default, { Component: component, route }) ||
component)
}
},
})
RouterView
Компонент реализован на базе Composition API, мы сосредоточимся на его рендеринговой части, т.к.setup
Возвращаемое значение функции является функцией, тогда эта функция является ее функцией рендеринга.
RouterView
Основная идея - идти по путиroute
и текущийRouterView
Глубина вложенности для соответствия соответствующим компонентам маршрутизации в конфигурации маршрутизации и визуализации.
Доступ к вычисляемым свойствам осуществляется на протяжении всего процесса рендеринга.routeToDisplay
, который определяется следующим образом:
const injectedRoute = inject(routerViewLocationKey)
const routeToDisplay = computed(() => props.route || injectedRoute.value)
routeToDisplay
внутренний доступinjectedRoute
,а такжеinjectedRoute
вводитсяkey
дляrouterViewLocationKey
Данные.
в исполненииcreateRouter
Когда маршрут создан, он будет создан внутриcurrentRoute
Реактивные переменные для поддержания текущего пути.
const currentRoute = shallowRef(START_LOCATION_NORMALIZED)
затем выполнениеcreateApp(App).use(router)
При установке маршрута он выполнитrouter
предоставленный объектinstall
метод, который будетcurrentRoute
пройти черезrouterViewLocationKey
доступны для приложения.
app.provide(routerViewLocationKey, currentRoute)
так рендерингRouterView
При доступе к компонентуrouteToDisplay
, доступ к которому будет осуществляться изнутриinjectedRoute
, а затем доступ кcurrentRoute
, и из-заcurrentRoute
является реактивным объектом, который, в свою очередь, запускает процесс сбора зависимостей.
Итак, когда мы выполняемrouter
объектpush
Когда метод изменяет путь маршрутизации, он будет выполняться внутренне.finalizeNavigation
метод, а затем модифицированныйcurrentRoute
, это вызоветвсе RouterView
Рендеринг компонента.
По умолчанию с этой логикой проблем нет, так зачем добавлятьKeepAlive
Есть проблема?
Прежде чем ответить на этот вопрос, давайте подумаем над другим вопросом: в примере при нормальных обстоятельствах маршрут изHome
сократить доAbout
После этого мы его модифицируем.currentRoute
, вызоветHome
внутри компонентаRouterView
перерисовать?
Ответ отрицательный, потому что при маршрутизации изHome
сократить доAbout
, это вызоветHome
Разгрузка компонента, что, в свою очередь, приводит к его внутреннемуRouterView
Удаление компонента.
RouterView
В процессе удаления компонента все зависимости в рамках компонента будут очищены, в том числе, конечноcurrentRoute
собранных компонентовrender effect
. Итак, когда мы изменяемcurrentRoute
, это не сработаетHome
внутри компонентаRouterView
Компонент перерисовывается.
Однако однаждыHome
компонент, соответствующийRouterView
одеялоKeepAlive
После того, как компонент завернут, когда маршрут отHome
сократить доAbout
, он не будет выполнятьсяHome
Процесс удаления компонента не приведет к удалению внутреннегоRouterView
Компонент, конечно же, не очищает зависимости под своей областью действия.
затем, когда мы изменимcurrentRoute
, не только рендеритAbout
внутри компонентаRouterView
компонент, который также вызываетHome
внутри компонентаRouterView
Перерендерить.
из-заHome
внутри компонентаRouterView
а такжеAbout
внутри компонентаRouterView
являются вторичными компонентами маршрутизации, согласноRouterView
Логика рендеринга, на данный моментHome
внутри компонентаRouterView
также будет отображаться какSub
компоненты, поэтомуSub
Причина, по которой компонент отображается дважды.
Сообщить о проблеме для Vue3
Хотя эта ошибка была обнаружена, я какое-то время не мог придумать хорошего решения, поэтому попытался предложить решение для Vue3.issue.
Кстати, вот несколько заметок для поднятия вопросов:
-
Обычно у некоторых хороших проектов с открытым исходным кодом есть шаблон задачи, и вы можете создать задачу в соответствии с его рекомендациями.
-
Чтобы позволить специалистам по сопровождению проектов с открытым исходным кодом быстрее и точнее находить проблемы, обычно вам нужно свести к минимуму повторение проблемы и предоставить демонстрацию, которая воспроизводит проблему, а не предоставлять проблемный проект.
-
Перед тем, как задавать вопросы, рекомендуется добавить немного собственного анализа и размышлений о проблеме.Хотя это и не обязательно, этот процесс познакомит вас с этим проектом с открытым исходным кодом, а также поможет сопровождающим легче находить проблемы.
-
Если это действительно ошибка и у вас есть возможность ее исправить, вы можете отправить запрос на вытягивание после поднятия проблемы и непосредственно участвовать в совместной разработке проекта с открытым исходным кодом.Этот процесс будет очень полезен для вашего собственного технического роста. .
Но, к моему смущению, проблема была закрыта менее чем через пять минут после того, как я ее подал, потому что она была связана сvue-router-next
один из проектовissueповторил.
Я провел общее сканирование проблемы и обнаружил, что она была поднята еще 1 декабря 2020 года, и что многие люди сталкивались с подобными проблемами. По этому вопросу можно найти много связанных вопросов, в том числе вопрос, упомянутый в начале статьи, поэтому я могу обратить на него внимание.
vue-router
Мейнтейнер . также пытался ее решить, но столкнулся с некоторыми проблемами, подробности см. в его ответе в выпуске.
К сожалению, до сих пор проблема также не решена, мейнтейнер опубликовал ее.help wanted
, надеясь получить помощь от сообщества.
У Vue2 тоже есть эта проблема?
Поскольку наша компания все еще использует Vue2, меня больше всего беспокоит, есть ли у Vue2 эта проблема.
Итак, я написал такое же демо с Vue2, и я рад, что у Vue2 нет этой ошибки, так в чем же причина?
Поскольку Vue используетvue-router
3.x версия , это соответствуетRouterView
Реализация компонента следующая:
var View = {
name: 'RouterView',
functional: true,
props: {
name: {
type: String,
default: 'default'
}
},
render: function render(_, ref) {
var props = ref.props
var children = ref.children
var parent = ref.parent
var data = ref.data
data.routerView = true
var h = parent.$createElement
var name = props.name
var route = parent.$route
var cache = parent._routerViewCache || (parent._routerViewCache = {})
var depth = 0
var inactive = false
while (parent && parent._routerRoot !== parent) {
var vnodeData = parent.$vnode ? parent.$vnode.data : {}
if (vnodeData.routerView) {
depth++
}
if (vnodeData.keepAlive && parent._directInactive && parent._inactive) {
inactive = true
}
parent = parent.$parent
}
data.routerViewDepth = depth
if (inactive) {
var cachedData = cache[name]
var cachedComponent = cachedData && cachedData.component
if (cachedComponent) {
if (cachedData.configProps) {
fillPropsinData(cachedComponent, data, cachedData.route, cachedData.configProps)
}
return h(cachedComponent, data, children)
} else {
return h()
}
}
var matched = route.matched[depth]
var component = matched && matched.components[name]
if (!matched || !component) {
cache[name] = null
return h()
}
cache[name] = { component: component }
data.registerRouteInstance = function(vm, val) {
var current = matched.instances[name]
if (
(val && current !== vm) ||
(!val && current === vm)
) {
matched.instances[name] = val
}
}
(data.hook || (data.hook = {})).prepatch = function(_, vnode) {
matched.instances[name] = vnode.componentInstance
}
data.hook.init = function(vnode) {
if (vnode.data.keepAlive &&
vnode.componentInstance &&
vnode.componentInstance !== matched.instances[name]
) {
matched.instances[name] = vnode.componentInstance
}
handleRouteEntered(route)
}
var configProps = matched.props && matched.props[name]
if (configProps) {
extend(cache[name], {
route: route,
configProps: configProps
})
fillPropsinData(component, data, route, configProps)
}
return h(component, data, children)
}
}
RouterView
Логика отрисовки компонента и новая версияvue-router-next
Достижение согласованности: согласно путиroute
и текущийRouterView
Глубина вложенности для соответствия соответствующим компонентам маршрутизации в конфигурации маршрутизации и визуализации.
Разница в том, что версия 3.xvue-router
обработанныйKeepAlive
Ситуация: если текущийRouterView
Экземпляр родительского компонента, в котором находится компонент, находится вKeepAlive
в построенном дереве и естьinactive
состояние, то он будет отображаться только как последний визуализированный вид.
Поэтому здесь есть два ключевых момента: один — уметь судить о текущей среде, а другой — кэшироватьRouterView
Последний визуализированный вид.
Очевидно, вvue-router-next
, соответствующей логики нет, в основном из-за отсутствия хранилища в экземпляре компонентаKeepAlive
компонент, связанныйinactive
условие,RouterView
Компоненты также не имеют возможности узнать свое текущее окружение.
По-моему, еслиvue-router-next
Для решения этой проблемы также могут потребоваться некоторые изменения в Vue3, предоставляющие больше информации и данных, позволяющиеRouterView
Когда компонент визуализируется, он знает свое текущее окружение.
Один из способов решения этой проблемы заключается в том, что в этом вложенном маршруте он не используется.KeepAlive
пакетRouterView
.
Суммировать
Когда мы выбираем технологию, нам действительно нужно учитывать этот риск.Если в проекте с открытым исходным кодом есть ошибка или он не может удовлетворить ваши потребности и не может быстро реагировать, есть ли у вас способ помочь проекту с открытым исходным кодом. строить вместе, или с помощью магии изменить способ решения возникшей проблемы.
Версия Vue2 CSP, используемая нашей компанией, основана на версии Vue.js 2.6.11. Сообщество не предоставляет поддержку, поэтому вам нужно сделать это самостоятельно.
У сопровождающих проектов с открытым исходным кодом, естественно, есть свои планы и соображения.Вы не можете просить сопровождающих решить проблему для вас немедленно, потому что ваш проект срочный. Конечно, если вы действительно хотите обратиться за экстренной помощью, подход с более высоким EQ — это пожертвовать мейнтейнерам с открытым исходным кодом.Платя, вы можете повысить приоритетность исправляемых ошибок.
Конечно, самый надежный способ — сделать себя надежным.Не паникуйте, когда вы сталкиваетесь с ошибкой, сначала определите основную причину ошибки, а затем найдите подходящее решение.
Если вы сейчас разрабатываете проект с Vue3, то обязательно обратите внимание на этот баг.Если сможете, то можете его хорошо изучить.Еще лучше, если вы подадите пулреквест для Vue3. По сравнению с исправлением орфографических ошибок и смешиванием участников, я думаю, что только те, кто может решить такие проблемы, могут считаться участниками в истинном смысле.