Skip to content

Commit 0aa608f

Browse files
authored
Scoped slot API for router-link (#34)
1 parent fe46aa6 commit 0aa608f

File tree

1 file changed

+118
-0
lines changed

1 file changed

+118
-0
lines changed
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
- Start Date: 2019-04-29
2+
- Target Major Version: Vue (2.x / 3.x) Vue Router (3.x / 4.x)
3+
- Reference Issues: https://github.com/vuejs/vue-router/issues/2611,
4+
- Implementation PR: Implemented in both [v3.x](https://github.com/vuejs/vue-router/commit/e289ddee99fcc3129e65485e32f394c1308bb98b) and v4.x
5+
6+
# Summary
7+
8+
- Remove `tag` prop
9+
- Remove `event` prop
10+
- Stop automatically assigning click events to inner anchors
11+
- Add a scoped-slot API
12+
13+
# Basic example
14+
15+
```vue
16+
<router-link to="/">
17+
<Icon>home</Icon> Home
18+
</router-link>
19+
```
20+
21+
# Motivation
22+
23+
Current implementation of Router Link has many limitations:
24+
25+
- Active state customization is not complete ([#2611](https://github.com/vuejs/vue-router/issues/2611)
26+
- Cannot be integrated with custom components ([#2021](https://github.com/vuejs/vue-router/issues/2021))
27+
- click event cannot be prevented (through `@click.prevent` nor through a `disabled` attribute [#2098](https://github.com/vuejs/vue-router/pull/2098))
28+
29+
The idea of this RFC is to solve those issues by providing a scoped slot that allow app developers to easily extend Links based on their applications and to allow library authors to provide an even easier integration with Vue Router.
30+
31+
# Detailed design
32+
33+
## Slot with content
34+
35+
A simple use case would be slot with content (no nested anchors or buttons)
36+
37+
```vue
38+
<router-link to="/">
39+
<Icon>home</Icon> Home
40+
</router-link>
41+
```
42+
43+
This implementation would:
44+
45+
- generate an anchor (`a`) element and apply the corresponding properties:
46+
- `href` with the destination
47+
- `class` with `router-link-active` and/or `router-link-exact-active` (can be changed through prop or global option)
48+
- click listener to trigger navigation through `router.push` or `router.replace` with a `event.preventDefault` (except when the link is clicked using a modifier like <kbd>⌘</kbd> or <kbd>Ctrl</kbd>)
49+
- Put anything passed as the children of the anchor
50+
- Pass down any attributes that aren't props to the `a` element
51+
52+
**Breaking changes**:
53+
54+
- no longer accept a `tag` prop -> use the scoped slot instead (see the point below)
55+
- no longer accepts `event` -> use the scoped slot instead
56+
- no longer works as a wrapper automatically looking for the first `a` inside -> use the scoped slot instead
57+
58+
## Custom `tag` prop
59+
60+
I am not sure about keeping the `tag` prop if it can be replaced which a scoped slot because it wouldn't handle custom components and except for very simple cases, we will likely use custom UI components instead of the basics ones:
61+
62+
```vue
63+
<router-link to="/" tag="button">
64+
<Icon>home</Icon><span class="xs-hidden">Home</span>
65+
</router-link>
66+
```
67+
68+
is equivalent to
69+
70+
```vue
71+
<router-link to="/" v-slot="{ navigate, isActive, isExactActive }">
72+
<button role="link" @click="navigate" :class="{ active: isActive, 'exact-active': isExactActive }">
73+
<Icon>home</Icon><span class="xs-hidden">Home</span>
74+
</button>
75+
</router-link>
76+
```
77+
78+
(see below for explanation about the attributes passed to the scoped-slot)
79+
80+
## Scoped slot
81+
82+
A scoped slot would get access to every bit of information needed to provide a custom integration and allows applying the active classes, click listener, links, etc at any level. This would allow a better integration with frameworks like Bootstrap (https://getbootstrap.com/docs/4.3/components/navbar/). The idea would be to create a Vue component to avoid the boilerplate like bootstrap-vue does (https://bootstrap-vue.js.org/docs/components/navbar/#navbar)
83+
84+
```vue
85+
<router-link to="/" v-slot="{ href, navigate, isActive }">
86+
<li :class="{ 'active': isActive }">
87+
<a :href="href" @click="navigate">
88+
<Icon>home</Icon><span class="xs-hidden">Home</span>
89+
</a>
90+
</li>
91+
</router-link>
92+
```
93+
94+
### Accessible variables
95+
96+
The slot should provide values that are computed inside `router-link`:
97+
98+
- `href`: resolved relative url to be added to an anchor tag (contains the base if provided while route.fullPath doesn't)
99+
- `route`: resolved normalized route location from the `to` (same shape as `$route`)
100+
- `navigate`: function to trigger navigation (usually attached to a click). Also calls `preventDefault` if the click is directly pressed.
101+
- `isActive`: true whenever `router-link-active` is applied. Can be modified by `exact` prop
102+
- `isExactActive`: true whenever `router-link-exact-active` is aplied. Can be modified by `exact` prop.
103+
104+
# Drawbacks
105+
106+
- Whereas it's possible to keep existing behaviour working and only expose a new behaviour with scoped slots, it will still prevent us from fixing existing issues with current implementation. That's why there are some breaking changes, to make things more consistent.
107+
- No access to the default `router-link` classes like `router-link-active` and `router-link-exact-active`.
108+
109+
# Alternatives
110+
111+
- Keeping `event` prop for convienience
112+
113+
# Adoption strategy
114+
115+
- Document new slot behaviour based on examples
116+
- Deprecate `tag` and `event` with a message and link to documentation the remove in v4
117+
118+
# Unresolved questions

0 commit comments

Comments
 (0)