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.vue
impó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.js
Utilice el enrutamiento en
import router from "./router";
const app = createApp(App)
app.use(router)
app.mount('#app')
Luego puede this.$router
acceder a él en cualquier componente this.$route
y 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 user
plantilla 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 createRouter
y use
use 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 path
se registrará como una tabla de enrutamiento que apunta al component
componente declarado, y cuando push
se 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 createRouter
implementació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 &¤tRoute.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
}
createRouter
El método devuelve la instancia de enrutamiento actual e inicializa internamente algunos métodos de enrutamiento comunes, que this.$router
son 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 use
mé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-view
y en this.$router
algunos métodos del ejemplo, entonces, ¿cómo mostrar la carga en la página component
? router-view
Necesidad de mirar la implementación interna del componente de renderizado
representación
install
El método registra el RouterView
componente y se implementa en RouterView.ts
:
/**
* Component to display the current route the user is at.
*/
export const RouterView = RouterViewImpl as unknown as {// ...
}
RouterViewImpl
realizar:
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 depth
control de profundidad. La profundidad inicial router-view
es 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 resolve
analizan de la routeToDisplay.value
siguiente manera en orden:
saltar
Antes de analizar el proceso de salto, primero mire la lógica de análisis del registro de ruta. El createRouter
método se llama en el createRouterMatcher
mé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 router
correcto externamente. matcher
Una 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 createRouterMatcher
para 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:routres
addRoute
insertMatcher
matchers
resolve
Matcher.record
MatcherLocation
matched
router-view
depth
push
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})}
finalizeNavigation
Llame 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 = toLocation
Después de la ejecución, activará router-view
el routeToDisplay
cambio de la mediana, volverá a calcular para matchedRouteRef
obtener un nuevo valor ViewComponent
y completará la actualización de la página. Hay dos puntos más arriba, router
que resolve
se 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.resolve
matched
navigate
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