diff --git a/flow/declarations.js b/flow/declarations.js index 824c3eca0..859237ade 100644 --- a/flow/declarations.js +++ b/flow/declarations.js @@ -1,3 +1,5 @@ +/* @flow */ + declare var document: Document; declare class RouteRegExp extends RegExp { @@ -18,6 +20,8 @@ declare module 'path-to-regexp' { } declare type Dictionary = { [key: string]: T } +declare type QueryValue = string | null | Array +declare type QueryDictionary = { [key: string]: QueryValue } declare type NavigationGuard = ( to: Route, @@ -84,7 +88,7 @@ declare type Location = { name?: string; path?: string; hash?: string; - query?: Dictionary; + query?: QueryDictionary; params?: Dictionary; append?: boolean; replace?: boolean; @@ -96,7 +100,7 @@ declare type Route = { path: string; name: ?string; hash: string; - query: Dictionary; + query: QueryDictionary; params: Dictionary; fullPath: string; matched: Array; diff --git a/src/util/location.js b/src/util/location.js index fb98d4537..760835c2d 100644 --- a/src/util/location.js +++ b/src/util/location.js @@ -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 ) diff --git a/src/util/query.js b/src/util/query.js index a2f2a56cc..0ece6d470 100644 --- a/src/util/query.js +++ b/src/util/query.js @@ -27,9 +27,9 @@ export function decode (str: string) { export function resolveQuery ( query: ?string, - extraQuery: Dictionary = {}, + extraQuery: QueryDictionary = {}, _parseQuery: ?Function -): Dictionary { +): QueryDictionary { const parse = _parseQuery || parseQuery let parsedQuery try { @@ -49,7 +49,7 @@ export function resolveQuery ( const castQueryParamValue = value => (value == null || typeof value === 'object' ? value : String(value)) -function parseQuery (query: string): Dictionary { +function parseQuery (query: string): QueryDictionary { const res = {} query = query.trim().replace(/^(\?|#|&)/, '') @@ -75,7 +75,7 @@ function parseQuery (query: string): Dictionary { return res } -export function stringifyQuery (obj: Dictionary): string { +export function stringifyQuery (obj: QueryDictionary): string { const res = obj ? Object.keys(obj) .map(key => { diff --git a/src/util/route.js b/src/util/route.js index 9d8e31cd1..aeaa3557c 100644 --- a/src/util/route.js +++ b/src/util/route.js @@ -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 { @@ -126,11 +124,9 @@ export function isIncludedRoute (current: Route, target: Route): boolean { ) } -function queryIncludes (current: Dictionary, target: Dictionary): 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 } diff --git a/types/router.d.ts b/types/router.d.ts index a334bc95f..d458a6e2e 100644 --- a/types/router.d.ts +++ b/types/router.d.ts @@ -411,7 +411,7 @@ export interface Route { path: string name?: string | null hash: string - query: Dictionary + query: Dictionary params: Dictionary fullPath: string matched: RouteRecord[] diff --git a/types/test/index.ts b/types/test/index.ts index a0445dd19..c202e9fa9 100644 --- a/types/test/index.ts +++ b/types/test/index.ts @@ -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( diff --git a/types/test/query-types.test.ts b/types/test/query-types.test.ts new file mode 100644 index 000000000..01d7b39a5 --- /dev/null +++ b/types/test/query-types.test.ts @@ -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: [] +}