Skip to content

Fix/route query type 3566 #3931

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions flow/declarations.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
/* @flow */

declare var document: Document;

declare class RouteRegExp extends RegExp {
Expand All @@ -18,6 +20,8 @@ declare module 'path-to-regexp' {
}

declare type Dictionary<T> = { [key: string]: T }
declare type QueryValue = string | null | Array<string | null>
declare type QueryDictionary = { [key: string]: QueryValue }

declare type NavigationGuard = (
to: Route,
Expand Down Expand Up @@ -84,7 +88,7 @@ declare type Location = {
name?: string;
path?: string;
hash?: string;
query?: Dictionary<string>;
query?: QueryDictionary;
params?: Dictionary<string>;
append?: boolean;
replace?: boolean;
Expand All @@ -96,7 +100,7 @@ declare type Route = {
path: string;
name: ?string;
hash: string;
query: Dictionary<string>;
query: QueryDictionary;
params: Dictionary<string>;
fullPath: string;
matched: Array<RouteRecord>;
Expand Down
4 changes: 2 additions & 2 deletions src/util/location.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,9 @@ export function normalizeLocation (
? resolvePath(parsedPath.path, basePath, append || next.append)
: basePath

const query = resolveQuery(
const query: QueryDictionary = resolveQuery(
parsedPath.query,
next.query,
next.query || {},
router && router.options.parseQuery
)

Expand Down
8 changes: 4 additions & 4 deletions src/util/query.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ export function decode (str: string) {

export function resolveQuery (
query: ?string,
extraQuery: Dictionary<string> = {},
extraQuery: QueryDictionary = {},
_parseQuery: ?Function
): Dictionary<string> {
): QueryDictionary {
const parse = _parseQuery || parseQuery
let parsedQuery
try {
Expand All @@ -49,7 +49,7 @@ export function resolveQuery (

const castQueryParamValue = value => (value == null || typeof value === 'object' ? value : String(value))

function parseQuery (query: string): Dictionary<string> {
function parseQuery (query: string): QueryDictionary {
const res = {}

query = query.trim().replace(/^(\?|#|&)/, '')
Expand All @@ -75,7 +75,7 @@ function parseQuery (query: string): Dictionary<string> {
return res
}

export function stringifyQuery (obj: Dictionary<string>): string {
export function stringifyQuery (obj: QueryDictionary): string {
const res = obj
? Object.keys(obj)
.map(key => {
Expand Down
38 changes: 17 additions & 21 deletions src/util/route.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,26 +94,24 @@ export function isSameRoute (a: Route, b: ?Route, onlyPath: ?boolean): boolean {
}

function isObjectEqual (a = {}, b = {}): boolean {
// handle null value #1566
if (!a || !b) return a === b
const aKeys = Object.keys(a).sort()
const bKeys = Object.keys(b).sort()
if (aKeys.length !== bKeys.length) {
return false
if (Array.isArray(a) && Array.isArray(b)) {
return a.length === b.length && a.every((v, i) => String(v) === String(b[i]))
}
return aKeys.every((key, i) => {
const aVal = a[key]
const bKey = bKeys[i]
if (bKey !== key) return false
const bVal = b[key]
// query values can be null and undefined
if (aVal == null || bVal == null) return aVal === bVal
// check nested equality
if (typeof aVal === 'object' && typeof bVal === 'object') {
if (a instanceof Object && b instanceof Object) {
const aKeys = Object.keys(a).sort()
const bKeys = Object.keys(b).sort()
if (aKeys.length !== bKeys.length) return false
return aKeys.every((key, i) => {
const aVal = a[key]
const bKey = bKeys[i]
if (bKey !== key) return false
const bVal = b[key]
if (aVal == null || bVal == null) return aVal === bVal
return isObjectEqual(aVal, bVal)
}
return String(aVal) === String(bVal)
})
})
}
return String(a) === String(b)
}

export function isIncludedRoute (current: Route, target: Route): boolean {
Expand All @@ -126,11 +124,9 @@ export function isIncludedRoute (current: Route, target: Route): boolean {
)
}

function queryIncludes (current: Dictionary<string>, target: Dictionary<string>): boolean {
function queryIncludes (current: QueryDictionary, target: QueryDictionary): boolean {
for (const key in target) {
if (!(key in current)) {
return false
}
if (!(key in current)) return false
}
return true
}
Expand Down
2 changes: 1 addition & 1 deletion types/router.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -411,7 +411,7 @@ export interface Route {
path: string
name?: string | null
hash: string
query: Dictionary<string | (string | null)[]>
query: Dictionary<string | null | (string | null)[]>
params: Dictionary<string>
fullPath: string
matched: RouteRecord[]
Expand Down
19 changes: 14 additions & 5 deletions types/test/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,14 +193,23 @@ router.push({
foo: 'foo'
},
query: {
bar: 'bar',
empty: null,
removed: undefined,
withEmpty: ['1', null],
foo: ['foo1', 'foo2']
bar: 'bar',
empty: null,
removed: undefined,
withEmpty: ['1', null],
foo: ['foo1', 'foo2'],
queryWithoutValue: null,
mixedArray: [null, 'value']
},
hash: 'hash'
})

const routeQuery: Route['query'] = {
stringValue: 'test',
nullValue: null,
arrayValue: ['test', null],
emptyValue: null
}
router.replace({ name: 'home' })

router.push(
Expand Down
53 changes: 53 additions & 0 deletions types/test/query-types.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import VueRouter from '../index'
import { Route } from '../index'

const router = new VueRouter()
const route: Route = router.currentRoute

const stringQuery: string | null | (string | null)[] = route.query['stringParam']
if (typeof stringQuery === 'string') {
console.log(stringQuery.toLowerCase())
}

const nullQuery: string | null | (string | null)[] = route.query['paramWithoutValue']
if (nullQuery === null) {
console.log('Parameter exists but has no value')
}

const arrayQuery: string | null | (string | null)[] = route.query['arrayParam']
if (Array.isArray(arrayQuery)) {
arrayQuery.forEach(value => {
if (value === null) {
console.log('Array contains null value')
} else {
console.log(value.toLowerCase())
}
})
}

router.push({
path: '/test',
query: {
string: 'value', // string value
empty: null, // null value
array: ['value1', 'value2'], // array of strings
mixedArray: ['value', null], // array with null
noValue: null // parameter without value
}
})

const routeWithQueries: Route = {
path: '/test',
name: null,
hash: '',
query: {
param1: 'string', // string value
param2: null, // null value
param3: ['val1', 'val2'], // array of strings
param4: ['val', null], // array with null
param5: null // parameter without value
},
params: {},
fullPath: '/test',
matched: []
}