소개
Vue 라우터는 Vue.js의 공식 라우터입니다. Vue.js 코어와의 긴밀한 통합으로 Vue.js로 단일 페이지 애플리케이션(SPA)을 더 쉽게 구축할 수 있습니다.
사용
창조하다
1. Vue 라우터 종속성을 설치한 후 App.vue
가져 오면 router-view
렌더링을 위한 컨테이너입니다.
<div id="app"><router-view></router-view>
</div>
2. 경로 만들기router/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
라우팅 사용
import router from "./router";
const app = createApp(App)
app.use(router)
app.mount('#app')
그런 다음 this.$router
모든 구성 요소 this.$route
에서 액세스하고 다음과 같이 현재 경로에 액세스할 수 있습니다.
// 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')}},},
}
중첩 경로
일부 애플리케이션 UI는 중첩 구성 요소의 여러 레이어로 구성됩니다. 이 경우 URL의 조각은 일반적으로 특정 중첩 구성 요소 구조에 해당합니다. 예를 들면 다음과 같습니다.
/user/johnny/profile /user/johnny/posts
+------------------++-----------------+
| User || User|
| +--------------+ || +-------------+ |
| | Profile| |+------------>| | Posts | |
| || || | | |
| +--------------+ || +-------------+ |
+------------------++-----------------+
상위 앱 노드의 최상위 레이어 아래에 포함된 구성 요소는 위의 템플릿 과 같이 router-view
자체적으로 중첩됩니다 .router-view
user
const User = {template: `<div class="user"><h2>User {
{ $route.params.id }}</h2><router-view></router-view></div>`,
}
구성 요소를 이 중첩으로 렌더링하려면 router-view
경로에서 다음을 구성해야 합니다 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,},],},
]
소스 코드 관점에서 페이지가 로드되고 페이지에 표시되는 방식을 살펴보겠습니다.
원칙
위의 기본 사용 방법에서 알 수 있듯이 주로 세 단계로 구성됩니다.
1. 앱에서 이 경로 생성 createRouter
및 사용 2. 템플릿에서 태그 사용 3. 페이지 탐색 및 이동use
router-view
push
라우터가 선언한 배열 구조에서 알 수 있듯이 선언된 경로 는 선언된 컴포넌트 path
를 가리키는 라우팅 테이블로 등록되고 메소드가 호출되면 라우팅 테이블에서 해당 컴포넌트를 찾아 로드하게 됩니다. 소스코드가 이 과정을 어떻게 구현하는지 살펴보자.Vue 라우터 소스코드 분석 버전은 4.1.5이다.component
push
생성 설치
createRouter
먼저 메소드 구현 을 살펴보십시오 .
/**
* 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
이 메서드는 현재 라우팅 인스턴스를 반환하고 구성 요소 this.$router
에서 구조를 인쇄하는 것과 동일한 몇 가지 일반적인 라우팅 메서드를 내부적으로 초기화합니다 install
. 설치 중에 호출됩니다 . 아래 방법 을 app.use(router)
살펴보십시오 .use
runtime-core.cjs.prod.js
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;},
router-view
지금까지 글로벌 라우터 생성 및 설치가 완료되었으며 코드 에서 사용할 수 있으며 this.$router
예제의 일부 방법은 로드된 페이지를 표시하는 방법은 component
무엇입니까? 렌더링 구성 요소 router-view
의 내부 구현 을 살펴봐야 합니다.
표현
install
메서드는 RouterView
구성 요소를 등록하고 다음에서 구현됩니다 RouterView.ts
.
/**
* Component to display the current route the user is at.
*/
export const RouterView = RouterViewImpl as unknown as {// ...
}
RouterViewImpl
성취하다:
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)}},
})
중첩 라우팅 구현의 핵심은 깊이 depth
제어를 사용하는 것입니다.초기 router-view
깊이는 0이고 내부 중첩 깊이는 1씩 증가합니다.예를 들어 다음 중첩 관계의 경우:
const routes = [{path: '/',component: Home,children: [{path: 'product',component: ProductManage},]},{ path: '/login', name: 'login', component: Login }]
다음 과 같이 순서대로 resolve
파싱됩니다 .routeToDisplay.value
도약
점프 프로세스를 분석하기 전에 먼저 경로 등록의 구문 분석 논리를 살펴보십시오. createRouter
메소드는 메소드에서 호출됩니다 createRouterMatcher
. 이 메소드는 경로 일치자를 생성하고 경로 등록 및 점프의 특정 구현을 내부적으로 캡슐화하고 외부에서 router
올바른 matcher
패키지 를 생성합니다. 계층은 API를 제공하고 구현 세부 정보를 보호합니다. 구현을 살펴보십시오.
/**
* 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" />
}
정리 하면 메소드 createRouterMatcher
는 각각 의 메소드에 대한 메소드를 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
마지막 점프를 수행하려면 다음 구현을 참조하십시오.
/** * - 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
실행 후 중앙값 변경 을 트리거 router-view
하고 새 값을 얻기 위해 다시 계산 하고 페이지 새로고침을 완료합니다. 위의 2개의 포인트가 더 있는데 , 방금 언급 한 배열 을 채우고 메소드가 내비게이션에서 가드를 실행합니다. 이 두 단계는 읽지 않겠습니다. 관심 있는 학생들은 스스로 확인할 수 있습니다. 지금까지 주요 과정을 분석했습니다.routeToDisplay
matchedRouteRef
ViewComponent
router
resolve
matcher
resolve
matched
navigate
인용하다
tchedRouteRef 获得新的
ViewComponent ,完成页面刷新。 上面还有两点,
라우터 的
확인 会调用到
매처 的
해결 ,填充刚刚说过的
일치 数组,
탐색 방법은 내비게이션에서 가드를 실행합니다. 이 두 단계는 읽지 않겠습니다. 관심 있는 학생이 직접 확인할 수 있습니다. 지금까지 주요 프로세스를 분석했습니다.
마침내
최근에 VUE의 다양한 지식 포인트를 요약하고 "Vue 개발이 알아야 할 36가지 기술"로 정리한 VUE 문서를 발견했습니다. 내용이 비교적 상세하고, 각 지식 포인트에 대한 설명도 자리하고 있습니다.
도움이 필요한 친구는 아래 카드를 클릭하여 무료로 받고 공유할 수 있습니다.