Uso de Vue-router y análisis de principios

Introducción

Vue Router es el enrutador oficial de Vue.js. La integración profunda con el núcleo de Vue.js facilita la creación de aplicaciones de una sola página (SPA) con Vue.js.

utilizar

crear

1. Después de instalar la dependencia de Vue Router, App.vueimpórtelo router-view, es el contenedor para renderizar

<div id="app"><router-view></router-view>
</div> 

2. Crea una rutarouter/index.js

 const routes = [	{ path: '/', component: Home},{ path: '/login', name: 'login', component: Login},
]
constrouter = createRouter({history: createWebHistory(),routes: routes,
})
export default router 

3. main.jsUtilice el enrutamiento en

import router from "./router";
const app = createApp(App)

app.use(router)

app.mount('#app') 

Luego puede this.$routeracceder a él en cualquier componente this.$routey acceder a la ruta actual como:

// Home.vue
export default {computed: {username() {// 我们很快就会看到 `params` 是什么return this.$route.params.username},},methods: {goToDashboard() {if (isAuthenticated) {this.$router.push('/dashboard')} else {this.$router.push('/login')}},},
} 

rutas anidadas

Algunas interfaces de usuario de aplicaciones constan de varias capas de componentes anidados. En este caso, los fragmentos de la URL suelen corresponder a estructuras específicas de componentes anidados, por ejemplo:

/user/johnny/profile /user/johnny/posts
+------------------++-----------------+
| User || User|
| +--------------+ || +-------------+ |
| | Profile| |+------------>| | Posts | |
| || || | | |
| +--------------+ || +-------------+ |
+------------------++-----------------+ 

Debajo de la capa superior del nodo superior de la aplicación router-view, los componentes contenidos están anidados por sí mismos router-view, como la userplantilla anterior:

const User = {template: `<div class="user"><h2>User {
   
   { $route.params.id }}</h2><router-view></router-view></div>`,
} 

Para renderizar componentes en este nido router-view, necesitamos configurar en la ruta children:

const routes = [{path: '/user/:id',component: User,children: [{// 当 /user/:id/profile 匹配成功// UserProfile 将被渲染到 User 的 <router-view> 内部path: 'profile',component: UserProfile,},{// 当 /user/:id/posts 匹配成功// UserPosts 将被渲染到 User 的 <router-view> 内部path: 'posts',component: UserPosts,},],},
] 

Veamos cómo se carga y se muestra la página en la página desde el punto de vista del código fuente.

principio

Como se puede ver en el método de uso básico anterior, incluye principalmente tres pasos:

1. Cree createRoutery useuse esta ruta en la aplicación 2. Use etiquetas
en la plantilla 3. Navegue y salte a la páginarouter-view
push

Como se puede ver en la estructura de matriz declarada por los enrutadores, la ruta declarada pathse registrará como una tabla de enrutamiento que apunta al componentcomponente declarado, y cuando pushse llame al método, el componente correspondiente se encontrará en la tabla de enrutamiento y se cargará. Echemos un vistazo a cómo el código fuente implementa este proceso.La versión de análisis del código fuente de Vue Router es 4.1.5

crear instalar

Primero observe la createRouterimplementación del método:

/**
 * Creates a Router instance that can be used by a Vue app.
 *
 * @param options - {@link RouterOptions}
 */
export function createRouter(options: RouterOptions): Router {const matcher = createRouterMatcher(options.routes, options)// ...function addRoute( parentOrRoute: RouteRecordName | RouteRecordRaw,route?: RouteRecordRaw ) {// ...}function getRoutes() {return matcher.getRoutes().map(routeMatcher => routeMatcher.record)}function hasRoute(name: RouteRecordName): boolean {return !!matcher.getRecordMatcher(name)}function push(to: RouteLocationRaw) {return pushWithRedirect(to)}function replace(to: RouteLocationRaw) {return push(assign(locationAsObject(to), { replace: true }))}// ...const router: Router = {currentRoute,listening: true,addRoute,removeRoute,hasRoute,getRoutes,resolve,options,push,replace,go,back: () => go(-1),forward: () => go(1),beforeEach: beforeGuards.add,beforeResolve: beforeResolveGuards.add,afterEach: afterGuards.add,onError: errorHandlers.add,isReady,// 在app全局安装routerinstall(app: App) {const router = this// 全局注册组件RouterLink、RouterViewapp.component('RouterLink', RouterLink)app.component('RouterView', RouterView)	// 全局声明router实例,this.$router访问app.config.globalProperties.$router = router// 全局注册this.$route 访问当前路由currentRouteObject.defineProperty(app.config.globalProperties, '$route', {enumerable: true,get: () => unref(currentRoute),})// this initial navigation is only necessary on client, on server it doesn't// make sense because it will create an extra unnecessary navigation and could// lead to problemsif (isBrowser &&// used for the initial navigation client side to avoid pushing// multiple times when the router is used in multiple apps!started &&currentRoute.value === START_LOCATION_NORMALIZED) {// see above// 浏览器情况下,push一个初始页面,不指定url默认首页‘/’started = truepush(routerHistory.location).catch(err => {if (__DEV__) warn('Unexpected error when starting the router:', err)})}	// ...app.provide(routerKey, router)app.provide(routeLocationKey, reactive(reactiveRoute))// 全局注入当前路由currentRouteapp.provide(routerViewLocationKey, currentRoute)	// ...},}return router
} 

createRouterEl método devuelve la instancia de enrutamiento actual e inicializa internamente algunos métodos de enrutamiento comunes, que this.$routerson lo mismo que imprimir la estructura en el componente. install¿Dónde se llama el método? Llamado durante la instalación app.use(router), mire el usemétodo runtime-core.cjs.prod.js, a continuación:

use(plugin, ...options) {if (installedPlugins.has(plugin)) ;else if (plugin && shared.isFunction(plugin.install)) {installedPlugins.add(plugin);// 如果是插件,调用插件的install方法,并把当前app传入plugin.install(app, ...options);}else if (shared.isFunction(plugin)) {installedPlugins.add(plugin);plugin(app, ...options);}else ;return app;}, 

Hasta ahora, la creación e instalación del enrutador global se ha completado, y se puede usar en el código router-viewy en this.$routeralgunos métodos del ejemplo, entonces, ¿cómo mostrar la carga en la página component? router-viewNecesidad de mirar la implementación interna del componente de renderizado

representación

installEl método registra el RouterViewcomponente y se implementa en RouterView.ts:

/**
 * Component to display the current route the user is at.
 */
export const RouterView = RouterViewImpl as unknown as {// ...
} 

RouterViewImplrealizar:

 export const RouterViewImpl = /*#__PURE__*/ defineComponent({name: 'RouterView',	// ...setup(props, { attrs, slots }) {__DEV__ && warnDeprecatedUsage()	// 拿到之前注册的currentRouteconst injectedRoute = inject(routerViewLocationKey)!// 当前要显示的route,监听route值变化时会刷新const routeToDisplay = computed<RouteLocationNormalizedLoaded>(() => props.route || injectedRoute.value)// 获取当前router-view深度层级,在嵌套路由时使用const injectedDepth = inject(viewDepthKey, 0)// 在当前router-view深度下去匹配要显示的路由matched// matched 是个数组,在resolve方法被赋值,如果有匹配到则在当前router-view渲染const depth = computed<number>(() => {let initialDepth = unref(injectedDepth)const { matched } = routeToDisplay.valuelet matchedRoute: RouteLocationMatched | undefinedwhile ((matchedRoute = matched[initialDepth]) &&!matchedRoute.components) {initialDepth++}return initialDepth})const matchedRouteRef = computed<RouteLocationMatched | undefined>(() => routeToDisplay.value.matched[depth.value])provide(viewDepthKey,computed(() => depth.value + 1))provide(matchedRouteKey, matchedRouteRef)provide(routerViewLocationKey, routeToDisplay)const viewRef = ref<ComponentPublicInstance>()// watch at the same time the component instance, the route record we are// rendering, and the name// 监听匹配路由变化时,刷新 watch(() => [viewRef.value, matchedRouteRef.value, props.name] as const,([instance, to, name], [oldInstance, from, oldName]) => {// ...},{ flush: 'post' })return () => {const route = routeToDisplay.value// we need the value at the time we render because when we unmount, we// navigated to a different location so the value is differentconst currentName = props.nameconst matchedRoute = matchedRouteRef.valueconst ViewComponent =matchedRoute && matchedRoute.components![currentName]if (!ViewComponent) {return normalizeSlot(slots.default, { Component: ViewComponent, route })}	// ...	// 关键:h函数,渲染路由中获得的组件const component = h(ViewComponent,assign({}, routeProps, attrs, {onVnodeUnmounted,ref: viewRef,}))return (// pass the vnode to the slot as a prop.// h and <component :is="..."> both accept vnodesnormalizeSlot(slots.default, { Component: component, route }) ||component)}},
}) 

El núcleo de la implementación del enrutamiento anidado es utilizar el depthcontrol de profundidad. La profundidad inicial router-viewes 0 y la profundidad de anidamiento interna se incrementa en 1. Por ejemplo, para las siguientes relaciones de anidamiento:

const routes = [{path: '/',component: Home,children: [{path: 'product',component: ProductManage},]},{ path: '/login', name: 'login', component: Login }] 

Se resolveanalizan de la routeToDisplay.valuesiguiente manera en orden:

saltar

Antes de analizar el proceso de salto, primero mire la lógica de análisis del registro de ruta. El createRoutermétodo se llama en el createRouterMatchermétodo. Este método crea un comparador de ruta, encapsula internamente la implementación específica del registro de ruta y el salto, y crea el paquete routercorrecto externamente. matcherUna capa proporciona la API y protege los detalles de implementación. Mira la implementación:

 /**
 * Creates a Router Matcher.
 *
 * @internal
 * @param routes - array of initial routes
 * @param globalOptions - global route options
 */
export function createRouterMatcher( routes: Readonly<RouteRecordRaw[]>,globalOptions: PathParserOptions ): RouterMatcher {// normalized ordered array of matchers// 匹配器的两个容器,匹配器Array和命名路由Mapconst matchers: RouteRecordMatcher[] = []const matcherMap = new Map<RouteRecordName, RouteRecordMatcher>()function getRecordMatcher(name: RouteRecordName) {return matcherMap.get(name)}function addRoute( record: RouteRecordRaw,parent?: RouteRecordMatcher,originalRecord?: RouteRecordMatcher ) {// ...// 如果记录中声明'alias'别名,把别名当作path,插入一条新的记录if ('alias' in record) {const aliases =typeof record.alias === 'string' ? [record.alias] : record.alias<img src="https://github.com/vuejs/router/issues/1124(matcher.record.path !== matchers[i].record.path ||!isRecordChildOf(matcher, matchers[i])))i++	// 将matcher添加到数组末尾matchers.splice(i, 0, matcher)// only add the original record to the name map// 命名路由添加到路由Mapif (matcher.record.name && !isAliasRecord(matcher))matcherMap.set(matcher.record.name, matcher)}function resolve( location: Readonly<MatcherLocationRaw>,currentLocation: Readonly<MatcherLocation> ): MatcherLocation {let matcher: RouteRecordMatcher | undefinedlet params: PathParams = {}let path: MatcherLocation['path']let name: MatcherLocation['name']if ('name' in location && location.name) {// 命名路由解析出pathmatcher = matcherMap.get(location.name)// ...// throws if cannot be stringifiedpath = matcher.stringify(params)} else if ('path' in location) {// no need to resolve the path with the matcher as it was provided// this also allows the user to control the encodingpath = location.path//...matcher = matchers.find(m => m.re.test(path))// matcher should have a value after the loopif (matcher) {// we know the matcher works because we tested the regexpparams = matcher.parse(path)!name = matcher.record.name}// push相对路径} else {// match by name or path of current routematcher = currentLocation.name? matcherMap.get(currentLocation.name): matchers.find(m => m.re.test(currentLocation.path))if (!matcher)throw createRouterError<MatcherError>(ErrorTypes.MATCHER_NOT_FOUND, {location,currentLocation,})name = matcher.record.name// since we are navigating to the same location, we don't need to pick the// params like when `name` is providedparams = assign({}, currentLocation.params, location.params)path = matcher.stringify(params)}const matched: MatcherLocation['matched'] = []let parentMatcher: RouteRecordMatcher | undefined = matcherwhile (parentMatcher) {// reversed order so parents are at the beginning	// 和当前path匹配的记录,插入到数组头部,让父级先匹配matched.unshift(parentMatcher.record)parentMatcher = parentMatcher.parent}return {name,path,params,matched,meta: mergeMetaFields(matched),}}// 添加初始路由routes.forEach(route => addRoute(route))return { addRoute, resolve, removeRoute, getRoutes, getRecordMatcher " style="margin: auto" />
} 

En resumen, el método ejecuta el método createRouterMatcherpara cada método, lo llama e inserta el generado en el contenedor. Cuando se llama más tarde, a través del método, el registro se compara con la matriz donde se guarda el registro , y el el seguimiento se basará en la obtención de la matriz El elemento que se debe representar. Flujo de ejecución del método:routresaddRouteinsertMatchermatchersresolveMatcher.recordMatcherLocationmatchedrouter-viewdepthpush

function push(to: RouteLocationRaw) {return pushWithRedirect(to)}

// ...function pushWithRedirect( to: RouteLocationRaw | RouteLocation,redirectedFrom?: RouteLocation ): Promise<NavigationFailure | void | undefined> {// 解析出目标locationconst targetLocation: RouteLocation = (pendingLocation = resolve(to))const from = currentRoute.valueconst data: HistoryState | undefined = (to as RouteLocationOptions).stateconst force: boolean | undefined = (to as RouteLocationOptions).force// to could be a string where `replace` is a functionconst replace = (to as RouteLocationOptions).replace === trueconst shouldRedirect = handleRedirectRecord(targetLocation)// 重定向逻辑if (shouldRedirect)return pushWithRedirect(assign(locationAsObject(shouldRedirect), {state:typeof shouldRedirect === 'object'? assign({}, data, shouldRedirect.state): data,force,replace,}),// keep original redirectedFrom if it existsredirectedFrom || targetLocation)// if it was a redirect we already called `pushWithRedirect` aboveconst toLocation = targetLocation as RouteLocationNormalized	// ...return (failure ? Promise.resolve(failure) : navigate(toLocation, from)).catch((error: NavigationFailure | NavigationRedirectError) =>// ...).then((failure: NavigationFailure | NavigationRedirectError | void) => {if (failure) {// ...} else {// if we fail we don't finalize the navigationfailure = finalizeNavigation(toLocation as RouteLocationNormalizedLoaded,from,true,replace,data)}triggerAfterEach(toLocation as RouteLocationNormalizedLoaded,from,failure)return failure})} 

finalizeNavigationLlame para hacer el salto final sin fallar , vea la implementación:

/** * - Cleans up any navigation guards * - Changes the url if necessary * - Calls the scrollBehavior */function finalizeNavigation( toLocation: RouteLocationNormalizedLoaded,from: RouteLocationNormalizedLoaded,isPush: boolean,replace?: boolean,data?: HistoryState ): NavigationFailure | void {// a more recent navigation took placeconst error = checkCanceledNavigation(toLocation, from)if (error) return error// only consider as push if it's not the first navigationconst isFirstNavigation = from === START_LOCATION_NORMALIZEDconst state = !isBrowser ? {} : history.state// change URL only if the user did a push/replace and if it's not the initial navigation because// it's just reflecting the url// 如果是push保存历史到routerHistoryif (isPush) {// on the initial navigation, we want to reuse the scroll position from// history state if it existsif (replace || isFirstNavigation)routerHistory.replace(toLocation.fullPath,assign({scroll: isFirstNavigation && state && state.scroll,},data))else routerHistory.push(toLocation.fullPath, data)}// accept current navigation// 给当前路由赋值,会触发监听的router-view刷新currentRoute.value = toLocationhandleScroll(toLocation, from, isPush, isFirstNavigation)markAsReady()} 

currentRoute.value = toLocationDespués de la ejecución, activará router-viewel routeToDisplaycambio de la mediana, volverá a calcular para matchedRouteRefobtener un nuevo valor ViewComponenty completará la actualización de la página. Hay dos puntos más arriba, routerque resolvese llamarán , completando la matriz que se acaba de mencionar matcher, y el método ejecutará la guardia en la navegación. No leeré estos dos pasos. Los estudiantes interesados ​​pueden verificarlos por sí mismos. Hasta ahora, el principal proceso ha sido analizado.resolvematchednavigate

Referirse a

El enrutador 获得新的tchedRouteRef ViewComponent resolve matcher resolve matched navigation`method ejecutará la protección en la navegación. No leeré estos dos pasos. Los estudiantes interesados ​​pueden verificarlo por sí mismos. Hasta ahora, se ha analizado el proceso principal.,完成页面刷新。 上面还有两点,会调用到,填充刚刚说过的数组,

Por fin

Recientemente encontré un documento de VUE, que resume los diversos puntos de conocimiento de VUE y los organiza en "36 habilidades que el desarrollo de Vue debe conocer". El contenido es relativamente detallado y la explicación de cada punto de conocimiento también está en su lugar.



Amigos necesitados, puede hacer clic en la tarjeta a continuación para recibir y compartir gratis

Supongo que te gusta

Origin blog.csdn.net/web22050702/article/details/128723312
Recomendado
Clasificación