Skip to content

Commit 50332e5

Browse files
committed
feat: useLink()
1 parent 56f1364 commit 50332e5

File tree

7 files changed

+277
-122
lines changed

7 files changed

+277
-122
lines changed

examples/composables/app.js

+47-7
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
import Vue, { defineComponent, watch, ref } from 'vue'
22
import VueRouter from 'vue-router'
3-
import { useRoute, useRouter } from 'vue-router/composables'
4-
import { onBeforeRouteLeave, onBeforeRouteUpdate } from '../../src/composables'
3+
import {
4+
useRoute,
5+
useRouter,
6+
onBeforeRouteLeave,
7+
onBeforeRouteUpdate,
8+
useLink
9+
} from 'vue-router/composables'
510

611
Vue.use(VueRouter)
712

@@ -16,6 +21,7 @@ const Foo = defineComponent({
1621
console.log('Foo leaving')
1722
next()
1823
})
24+
1925
return { route }
2026
},
2127
template: `
@@ -46,9 +52,12 @@ const Home = defineComponent({
4652

4753
const watchCount = ref(0)
4854

49-
watch(() => route.query.n, () => {
50-
watchCount.value++
51-
})
55+
watch(
56+
() => route.query.n,
57+
() => {
58+
watchCount.value++
59+
}
60+
)
5261

5362
function navigate () {
5463
router.push({ query: { n: 1 + (Number(route.query.n) || 0) }})
@@ -82,11 +91,31 @@ const About = defineComponent({
8291
`
8392
})
8493

94+
const Nested = defineComponent({
95+
template: `<RouterView />`
96+
})
97+
98+
const NestedEmpty = defineComponent({
99+
template: `<div>NestedEmpty</div>`
100+
})
101+
102+
const NestedA = defineComponent({
103+
template: `<div>NestedA</div>`
104+
})
105+
85106
const router = new VueRouter({
86107
mode: 'history',
87108
base: __dirname,
88109
routes: [
89110
{ path: '/', component: Home },
111+
{
112+
path: '/nested',
113+
component: Nested,
114+
children: [
115+
{ path: '', component: NestedEmpty },
116+
{ path: 'a', component: NestedA }
117+
]
118+
},
90119
{ path: '/about', component: About }
91120
]
92121
})
@@ -98,9 +127,20 @@ new Vue({
98127
<h1>Basic</h1>
99128
<ul>
100129
<li><router-link to="/">/</router-link></li>
101-
<li><router-link to="/about">/foo</router-link></li>
130+
<li><router-link to="/about">/about</router-link></li>
131+
<li><router-link to="/nested">/nested</router-link></li>
132+
<li><router-link to="/nested/a">/nested/a</router-link></li>
102133
</ul>
103134
<router-view class="view"></router-view>
135+
136+
<pre id="nested-active" @click="navigate">{{ href }}: {{ isActive }}, {{ isExactActive }}</pre>
104137
</div>
105-
`
138+
`,
139+
setup () {
140+
const { href, isActive, isExactActive, navigate, route } = useLink({
141+
to: '/nested'
142+
})
143+
144+
return { href, isActive, navigate, route, isExactActive }
145+
}
106146
}).$mount('#app')

src/components/link.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ export default {
189189
}
190190
}
191191

192-
function guardEvent (e) {
192+
export function guardEvent (e) {
193193
// don't redirect with control keys
194194
if (e.metaKey || e.altKey || e.ctrlKey || e.shiftKey) return
195195
// don't redirect when preventDefault called

src/composables/globals.js

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import {
2+
getCurrentInstance,
3+
shallowReactive,
4+
effectScope
5+
} from 'vue'
6+
import { throwNoCurrentInstance } from './utils'
7+
8+
export function useRouter () {
9+
if (process.env.NODE_ENV !== 'production') {
10+
throwNoCurrentInstance('useRouter')
11+
}
12+
13+
return getCurrentInstance().proxy.$root.$router
14+
}
15+
16+
export function useRoute () {
17+
if (process.env.NODE_ENV !== 'production') {
18+
throwNoCurrentInstance('useRoute')
19+
}
20+
21+
const root = getCurrentInstance().proxy.$root
22+
if (!root._$route) {
23+
const route = effectScope(true).run(() =>
24+
shallowReactive(Object.assign({}, root.$router.currentRoute))
25+
)
26+
root._$route = route
27+
28+
root.$router.afterEach(to => {
29+
Object.assign(route, to)
30+
})
31+
}
32+
33+
return root._$route
34+
}

src/composables/guards.js

+68
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import { getCurrentInstance, onUnmounted } from 'vue'
2+
import { throwNoCurrentInstance } from './utils'
3+
import { useRouter } from './globals'
4+
5+
export function onBeforeRouteUpdate (guard) {
6+
if (process.env.NODE_ENV !== 'production') {
7+
throwNoCurrentInstance('onBeforeRouteUpdate')
8+
}
9+
10+
return useFilteredGuard(guard, isUpdateNavigation)
11+
}
12+
function isUpdateNavigation (to, from, depth) {
13+
const toMatched = to.matched
14+
const fromMatched = from.matched
15+
return (
16+
toMatched.length >= depth &&
17+
toMatched
18+
.slice(0, depth + 1)
19+
.every((record, i) => record === fromMatched[i])
20+
)
21+
}
22+
23+
function isLeaveNavigation (to, from, depth) {
24+
const toMatched = to.matched
25+
const fromMatched = from.matched
26+
return toMatched.length < depth || toMatched[depth] !== fromMatched[depth]
27+
}
28+
29+
export function onBeforeRouteLeave (guard) {
30+
if (process.env.NODE_ENV !== 'production') {
31+
throwNoCurrentInstance('onBeforeRouteLeave')
32+
}
33+
34+
return useFilteredGuard(guard, isLeaveNavigation)
35+
}
36+
37+
const noop = () => {}
38+
function useFilteredGuard (guard, fn) {
39+
const instance = getCurrentInstance()
40+
const router = useRouter()
41+
42+
let target = instance.proxy
43+
// find the nearest RouterView to know the depth
44+
while (
45+
target &&
46+
target.$vnode &&
47+
target.$vnode.data &&
48+
target.$vnode.data.routerViewDepth == null
49+
) {
50+
target = target.$parent
51+
}
52+
53+
const depth =
54+
target && target.$vnode && target.$vnode.data
55+
? target.$vnode.data.routerViewDepth
56+
: null
57+
58+
if (depth != null) {
59+
const removeGuard = router.beforeEach((to, from, next) => {
60+
return fn(to, from, depth) ? guard(to, from, next) : next()
61+
})
62+
63+
onUnmounted(removeGuard)
64+
return removeGuard
65+
}
66+
67+
return noop
68+
}

src/composables/index.js

+3-114
Original file line numberDiff line numberDiff line change
@@ -1,114 +1,3 @@
1-
import {
2-
getCurrentInstance,
3-
shallowReactive,
4-
effectScope,
5-
onUnmounted
6-
} from 'vue'
7-
8-
export function useRouter () {
9-
const i = getCurrentInstance()
10-
if (process.env.NODE_ENV !== 'production' && !i) {
11-
throwNoCurrentInstance('useRouter')
12-
}
13-
14-
return i.proxy.$root.$router
15-
}
16-
17-
export function useRoute () {
18-
const i = getCurrentInstance()
19-
if (process.env.NODE_ENV !== 'production' && !i) {
20-
throwNoCurrentInstance('useRoute')
21-
}
22-
23-
const root = i.proxy.$root
24-
if (!root._$route) {
25-
const route = effectScope(true).run(() =>
26-
shallowReactive(Object.assign({}, root.$router.currentRoute))
27-
)
28-
root._$route = route
29-
30-
root.$router.afterEach(to => {
31-
Object.assign(route, to)
32-
})
33-
}
34-
35-
return root._$route
36-
}
37-
38-
// TODO:
39-
// export function useLink () {}
40-
41-
export function onBeforeRouteUpdate (guard) {
42-
const i = getCurrentInstance()
43-
if (process.env.NODE_ENV !== 'production' && !i) {
44-
throwNoCurrentInstance('onBeforeRouteUpdate')
45-
}
46-
47-
return useFilteredGuard(guard, isUpdateNavigation)
48-
}
49-
50-
function useFilteredGuard (guard, fn) {
51-
const i = getCurrentInstance()
52-
const router = useRouter()
53-
54-
let target = i.proxy
55-
// find the nearest RouterView to know the depth
56-
while (
57-
target &&
58-
target.$vnode &&
59-
target.$vnode.data &&
60-
target.$vnode.data.routerViewDepth == null
61-
) {
62-
target = target.$parent
63-
}
64-
65-
const depth =
66-
target && target.$vnode && target.$vnode.data
67-
? target.$vnode.data.routerViewDepth
68-
: null
69-
70-
if (depth != null) {
71-
const removeGuard = router.beforeEach((to, from, next) => {
72-
return fn(to, from, depth) ? guard(to, from, next) : next()
73-
})
74-
75-
onUnmounted(removeGuard)
76-
return removeGuard
77-
}
78-
79-
return noop
80-
}
81-
82-
function isUpdateNavigation (to, from, depth) {
83-
const toMatched = to.matched
84-
const fromMatched = from.matched
85-
return (
86-
toMatched.length >= depth &&
87-
toMatched
88-
.slice(0, depth + 1)
89-
.every((record, i) => record === fromMatched[i])
90-
)
91-
}
92-
93-
function isLeaveNavigation (to, from, depth) {
94-
const toMatched = to.matched
95-
const fromMatched = from.matched
96-
return toMatched.length < depth || toMatched[depth] !== fromMatched[depth]
97-
}
98-
99-
const noop = () => {}
100-
101-
export function onBeforeRouteLeave (guard) {
102-
const i = getCurrentInstance()
103-
if (process.env.NODE_ENV !== 'production' && !i) {
104-
throwNoCurrentInstance('onBeforeRouteLeave')
105-
}
106-
107-
return useFilteredGuard(guard, isLeaveNavigation)
108-
}
109-
110-
function throwNoCurrentInstance (method) {
111-
throw new Error(
112-
`[vue-router]: Missing current instance. ${method}() must be called inside <script setup> or setup().`
113-
)
114-
}
1+
export * from './guards'
2+
export * from './globals'
3+
export * from './useLink'

0 commit comments

Comments
 (0)