Skip to content

Commit cf25179

Browse files
committed
fix(experimental): align hash parameter parsing with queries
1 parent 5b6cfa6 commit cf25179

18 files changed

Lines changed: 1100 additions & 423 deletions

‎packages/docs/experimental/param-parsers.md‎

Lines changed: 39 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ route.params.id // number
6565

6666
You define param parsers as modules exporting a `parser` in the configured param parser directory. The file name is the parser name you use in routes. For example, `src/params/uuid.ts` exports a `parser` that validates UUIDs and can be used as `[id=uuid]` in route files.
6767

68-
A parser is just an object with a _getter_ and a _setter_ but to make things simpler to use, Vue Router provides two helpers: [`defineParamParser()`](#defineParamParser) and [`defineParamParserRaw()`](#defineParamParserRaw).
68+
A parser is just an object with a _getter_ and a _setter_. Vue Router provides three helpers: [`defineParamParser()`](#defineParamParser), [`defineParamParserRaw()`](#defineParamParserRaw), and [`defineHashParamParser()`](#defineHashParamParser).
6969

7070
Reach for `defineParamParser` first, it's the most common use case for simple one-to-one transforms. Use `defineParamParserRaw` when you need to collapse multiple input shapes into one output type or you want to reject _nullish_ or array values outright.
7171

@@ -155,6 +155,26 @@ Here is a table of the different meaningful combinations of return values from `
155155

156156
:::
157157

158+
### `defineHashParamParser`
159+
160+
`defineHashParamParser` defines a transform for the hash. Both `get` and `set` are required: `get` receives a `string`, and `set` returns a `string`. The helper returns your parser unchanged, without array or nullish wrapping. You can specify the parsed type with `defineHashParamParser<TParam>` and a wider setter input with `defineHashParamParser<TParam, TParamRaw>`.
161+
162+
The getter receives the hash contents without the leading `#`: `#setup` calls `get('setup')`, and a bare `#` calls `get('')`. An absent hash (`''`) skips the getter and produces `undefined`, or the configured default.
163+
164+
For example, use the hash contents as a heading name:
165+
166+
```ts
167+
// src/params/heading.ts
168+
import { defineHashParamParser } from 'vue-router/experimental'
169+
170+
export const parser = defineHashParamParser<string>({
171+
get: hash => hash,
172+
set: heading => heading,
173+
})
174+
```
175+
176+
Use `params: { hash: { heading: 'heading' } }` in `definePage()`. A hash of `#setup` gives `route.params.heading === 'setup'`. The setter returns contents without `#`. The matcher adds `#` to a present result, including `''`, which produces a bare `#`. Pass `undefined` to clear an optional hash param. You can also pass `null` when the parser or default type includes it.
177+
158178
## Errors
159179

160180
Throw any error from `get` to mark the value as not matching. The router skips the route (treat it like a 404 candidate). `miss(reason?)` is just sugar for throwing a typed error.
@@ -232,7 +252,7 @@ definePage({
232252
</script>
233253
```
234254

235-
Hash params are optional by default. A parser error or a nullish result becomes `null`. Set `required: true` to reject the match instead, or provide a default:
255+
Hash params are optional by default. An absent hash skips the getter and produces `undefined`. Parsed values such as `null`, `false`, `0`, and `''` are preserved. For optional hash params, parser errors produce `undefined` or the configured default. Set `required: true` to reject missing hashes, parser errors, or `undefined` results. A default replaces only `undefined` results and parser errors:
236256

237257
```vue
238258
<script setup lang="ts">
@@ -249,30 +269,33 @@ definePage({
249269
</script>
250270
```
251271

252-
- `parser`: parser name. Omit to keep the hash string.
253-
- `required`: reject the match when parsing fails or returns a nullish result. Defaults to `false`.
254-
- `default`: a parsed value or factory used when the hash is absent or parsing fails. A declared default takes precedence over the getter for an absent hash.
272+
- `parser`: parser name. Omit to use `string`, which returns the hash contents without `#`. An absent hash produces `undefined` unless you provide a default.
273+
- `required`: reject the match when the hash is absent, parsing fails, or the parsed value is `undefined`. Defaults to `false`.
274+
- `default`: a parsed value or factory used when the hash is absent, parsing fails, or the parsed value is `undefined`. An absent hash skips the getter.
255275

256276
`required: true` and `default` cannot be used together.
257277

258-
Without a custom getter or a default, an absent hash becomes `null`.
278+
Without a parser, a default must be a `string`, `null`, or a factory that returns `string | null`. Other default types require an explicit parser: `{ default: 0 }` is invalid; `{ parser: 'int', default: 0 }` is valid.
279+
280+
With `hash: { section: {} }`, an absent hash becomes `undefined`, `#` becomes `''`, and `#setup` becomes `'setup'`. The parsed type is `string | undefined`.
259281

260-
The parser receives the entire hash string: an empty string when absent, or a string that starts with `#`. To extract several values, return an object from this one parser. Only the deepest matched route's hash parser runs.
282+
A default can itself be `null`, including when the parser is omitted. For example, `{ default: null }` and `{ parser: 'heading', default: null }` have the parsed type `string | null`, using the `heading` parser above. An absent hash uses the `null` default, while a bare `#` preserves `''`. The parsed type includes the default type, including `null` for `default: null`. Without a default, an optional hash param also includes `undefined` for an absent hash.
283+
284+
The API limits defaults without an explicit parser to `string | null`. However, code generation currently uses `unknown` if it cannot infer the type of such a default expression. Use an explicit parser to keep the generated type bounded by the parser's declared type.
285+
286+
The parser receives the hash contents without `#`. An absent hash skips the getter. To extract several values, return an object from this one parser. Only the deepest matched route's hash parser runs.
261287

262288
```ts
263289
// src/params/section.ts
264-
import { defineParamParserRaw } from 'vue-router/experimental'
290+
import { defineHashParamParser } from 'vue-router/experimental'
265291

266-
export const parser = defineParamParserRaw<
267-
{ heading: string; tab: string },
268-
{ heading: string; tab: string },
269-
string
270-
>({
292+
export const parser = defineHashParamParser<{ heading: string; tab: string }>({
271293
get: hash => {
272-
const [heading = '', tab = ''] = hash.slice(1).split('/')
294+
if (!hash) throw new Error('Missing section')
295+
const [heading = '', tab = ''] = hash.split('/')
273296
return { heading, tab }
274297
},
275-
set: ({ heading, tab }) => (heading ? `#${heading}/${tab}` : ''),
298+
set: ({ heading, tab }) => `${heading}/${tab}`,
276299
})
277300
```
278301

@@ -285,4 +308,4 @@ router.push({
285308
})
286309
```
287310

288-
You can omit an optional hash param or one with a default during navigation. Pass `null` to clear an optional hash param. Required params without a default must be provided. Nullish values produce an empty hash without calling the setter. The setter returns the entire hash, including `#`, or an empty string. The router then runs the getter to validate and normalize the result.
311+
You can omit an optional hash param or one with a default during navigation. Pass `undefined` to clear an optional hash param. You can also pass `null` when the parser or default type includes it. Required params without a default must be provided. Nullish values produce an empty hash without calling the setter. The setter returns contents without `#`. The matcher adds `#` to each present result, including `''`, which produces a bare `#`. The router then runs the getter on these contents to validate and normalize the result. Clearing the URL hash skips the getter and produces `undefined` or the configured default in `route.params`.
Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,37 @@
1-
<script lang="ts" setup></script>
1+
<script lang="ts" setup>
2+
import { useRoute } from 'vue-router'
3+
4+
definePage({
5+
params: {
6+
hash: {
7+
myHash: {
8+
parser: 'int',
9+
default: 46,
10+
},
11+
},
12+
},
13+
})
14+
15+
const route = useRoute()
16+
console.log('Route params:', route.params)
17+
route.params.myHash
18+
</script>
219

320
<template>
421
<h1>Home</h1>
522

623
<p>This is the homepage.</p>
24+
25+
<h2>Hash with the integer parser</h2>
26+
<p>An absent or invalid hash uses the default: 46.</p>
27+
<ul>
28+
<li><RouterLink to="/">No hash → 46</RouterLink></li>
29+
<li><RouterLink to="/#">Bare # → 46</RouterLink></li>
30+
<li><RouterLink to="/#0">#0 → 0</RouterLink></li>
31+
<li>
32+
<RouterLink to="/test-params/hash">Custom hash parser</RouterLink>
33+
</li>
34+
</ul>
35+
36+
<pre>{{ $route.params }}</pre>
737
</template>
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<script setup lang="ts">
2+
definePage({
3+
params: {
4+
hash: {
5+
section: { parser: 'test-hash-split', default: () => [] },
6+
},
7+
},
8+
})
9+
</script>
10+
11+
<template>
12+
<h1>Custom hash parser</h1>
13+
<p>
14+
The parser receives the hash contents without #. An absent hash skips the
15+
getter and uses the empty array default. A bare # calls the getter with an
16+
empty string and produces [""]. Other contents split into an array at each
17+
/.
18+
</p>
19+
<ul>
20+
<li>
21+
<RouterLink to="/test-params/hash">No hash → []</RouterLink>
22+
</li>
23+
<li><RouterLink to="/test-params/hash#">Bare # → [""]</RouterLink></li>
24+
<li>
25+
<RouterLink to="/test-params/hash#setup">#setup → ["setup"]</RouterLink>
26+
</li>
27+
<li>
28+
<RouterLink to="/test-params/hash#setup/vue">
29+
#setup/vue → ["setup", "vue"]
30+
</RouterLink>
31+
</li>
32+
</ul>
33+
<pre>{{ $route.params }}</pre>
34+
<RouterLink to="/">Hash without a custom parser</RouterLink>
35+
</template>
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { defineHashParamParser } from 'vue-router/experimental'
2+
3+
export const parser = defineHashParamParser<string[]>({
4+
get: value => value.split('/'),
5+
set: value => value.join('/'),
6+
})

0 commit comments

Comments
 (0)