This commit is contained in:
2022-07-18 02:50:52 +00:00
parent befd344ab0
commit 06181b34d6
8569 changed files with 818704 additions and 352705 deletions
+30 -3
View File
@@ -11,6 +11,10 @@ const eventTypes: Array<Function> = [String, Array]
const noop = () => {}
let warnedCustomSlot
let warnedTagProp
let warnedEventProp
export default {
name: 'RouterLink',
props: {
@@ -22,7 +26,9 @@ export default {
type: String,
default: 'a'
},
custom: Boolean,
exact: Boolean,
exactPath: Boolean,
append: Boolean,
replace: Boolean,
activeClass: String,
@@ -66,8 +72,8 @@ export default {
? createRoute(null, normalizeLocation(route.redirectedFrom), null, router)
: route
classes[exactActiveClass] = isSameRoute(current, compareTarget)
classes[activeClass] = this.exact
classes[exactActiveClass] = isSameRoute(current, compareTarget, this.exactPath)
classes[activeClass] = this.exact || this.exactPath
? classes[exactActiveClass]
: isIncludedRoute(current, compareTarget)
@@ -106,13 +112,17 @@ export default {
})
if (scopedSlot) {
if (process.env.NODE_ENV !== 'production' && !this.custom) {
!warnedCustomSlot && warn(false, 'In Vue Router 4, the v-slot API will by default wrap its content with an <a> element. Use the custom prop to remove this warning:\n<router-link v-slot="{ navigate, href }" custom></router-link>\n')
warnedCustomSlot = true
}
if (scopedSlot.length === 1) {
return scopedSlot[0]
} else if (scopedSlot.length > 1 || !scopedSlot.length) {
if (process.env.NODE_ENV !== 'production') {
warn(
false,
`RouterLink with to="${
`<router-link> with to="${
this.to
}" is trying to use a scoped slot but it didn't provide exactly one child. Wrapping the content with a span element.`
)
@@ -121,6 +131,23 @@ export default {
}
}
if (process.env.NODE_ENV !== 'production') {
if ('tag' in this.$options.propsData && !warnedTagProp) {
warn(
false,
`<router-link>'s tag prop is deprecated and has been removed in Vue Router 4. Use the v-slot API to remove this warning: https://next.router.vuejs.org/guide/migration/#removal-of-event-and-tag-props-in-router-link.`
)
warnedTagProp = true
}
if ('event' in this.$options.propsData && !warnedEventProp) {
warn(
false,
`<router-link>'s event prop is deprecated and has been removed in Vue Router 4. Use the v-slot API to remove this warning: https://next.router.vuejs.org/guide/migration/#removal-of-event-and-tag-props-in-router-link.`
)
warnedEventProp = true
}
}
if (this.tag === 'a') {
data.on = on
data.attrs = { href, 'aria-current': ariaCurrentValue }
+7 -1
View File
@@ -1,5 +1,6 @@
import { warn } from '../util/warn'
import { extend } from '../util/misc'
import { handleRouteEntered } from '../util/route'
export default {
name: 'RouterView',
@@ -94,10 +95,15 @@ export default {
) {
matched.instances[name] = vnode.componentInstance
}
// if the route transition has already been confirmed then we weren't
// able to call the cbs during confirmation as the component was not
// registered yet, so we call it here.
handleRouteEntered(route)
}
const configProps = matched.props && matched.props[name]
// save route and configProps in cachce
// save route and configProps in cache
if (configProps) {
extend(cache[name], {
route,
+28 -2
View File
@@ -7,10 +7,13 @@ import { createRoute } from './util/route'
import { fillParams } from './util/params'
import { createRouteMap } from './create-route-map'
import { normalizeLocation } from './util/location'
import { decode } from './util/query'
export type Matcher = {
match: (raw: RawLocation, current?: Route, redirectedFrom?: Location) => Route;
addRoutes: (routes: Array<RouteConfig>) => void;
addRoute: (parentNameOrRoute: string | RouteConfig, route?: RouteConfig) => void;
getRoutes: () => Array<RouteRecord>;
};
export function createMatcher (
@@ -23,6 +26,28 @@ export function createMatcher (
createRouteMap(routes, pathList, pathMap, nameMap)
}
function addRoute (parentOrRoute, route) {
const parent = (typeof parentOrRoute !== 'object') ? nameMap[parentOrRoute] : undefined
// $flow-disable-line
createRouteMap([route || parentOrRoute], pathList, pathMap, nameMap, parent)
// add aliases of parent
if (parent && parent.alias.length) {
createRouteMap(
// $flow-disable-line route is defined if parent is
parent.alias.map(alias => ({ path: alias, children: [route] })),
pathList,
pathMap,
nameMap,
parent
)
}
}
function getRoutes () {
return pathList.map(path => pathMap[path])
}
function match (
raw: RawLocation,
currentRoute?: Route,
@@ -166,6 +191,8 @@ export function createMatcher (
return {
match,
addRoute,
getRoutes,
addRoutes
}
}
@@ -185,10 +212,9 @@ function matchRoute (
for (let i = 1, len = m.length; i < len; ++i) {
const key = regex.keys[i - 1]
const val = typeof m[i] === 'string' ? decodeURIComponent(m[i]) : m[i]
if (key) {
// Fix #1994: using * with props: true generates a param named 0
params[key.name || 'pathMatch'] = val
params[key.name || 'pathMatch'] = typeof m[i] === 'string' ? decode(m[i]) : m[i]
}
}
+18 -3
View File
@@ -8,7 +8,8 @@ export function createRouteMap (
routes: Array<RouteConfig>,
oldPathList?: Array<string>,
oldPathMap?: Dictionary<RouteRecord>,
oldNameMap?: Dictionary<RouteRecord>
oldNameMap?: Dictionary<RouteRecord>,
parentRoute?: RouteRecord
): {
pathList: Array<string>,
pathMap: Dictionary<RouteRecord>,
@@ -22,7 +23,7 @@ export function createRouteMap (
const nameMap: Dictionary<RouteRecord> = oldNameMap || Object.create(null)
routes.forEach(route => {
addRouteRecord(pathList, pathMap, nameMap, route)
addRouteRecord(pathList, pathMap, nameMap, route, parentRoute)
})
// ensure wildcard routes are always at the end
@@ -70,6 +71,14 @@ function addRouteRecord (
path || name
)} cannot be a ` + `string id. Use an actual component instead.`
)
warn(
// eslint-disable-next-line no-control-regex
!/[^\u0000-\u007F]+/.test(path),
`Route with path "${path}" contains unencoded characters, make sure ` +
`your path is correctly encoded before passing it to the router. Use ` +
`encodeURI to encode static segments of your path.`
)
}
const pathToRegexpOptions: PathToRegexpOptions =
@@ -84,7 +93,13 @@ function addRouteRecord (
path: normalizedPath,
regex: compileRouteRegex(normalizedPath, pathToRegexpOptions),
components: route.components || { default: route.component },
alias: route.alias
? typeof route.alias === 'string'
? [route.alias]
: route.alias
: [],
instances: {},
enteredCbs: {},
name,
parent,
matchAs,
@@ -114,7 +129,7 @@ function addRouteRecord (
`Named Route '${route.name}' has a default child route. ` +
`When navigating to this named route (:to="{name: '${
route.name
}'"), ` +
}'}"), ` +
`the default child route will not be rendered. Remove the name from ` +
`this route and use the name of the default child route for named ` +
`links instead.`
+6 -3
View File
@@ -2,8 +2,7 @@
import type Router from '../index'
import { History } from './base'
import { isRouterError } from '../util/warn'
import { NavigationFailureType } from './errors'
import { NavigationFailureType, isNavigationFailure } from '../util/errors'
export class AbstractHistory extends History {
index: number
@@ -47,11 +46,15 @@ export class AbstractHistory extends History {
this.confirmTransition(
route,
() => {
const prev = this.current
this.index = targetIndex
this.updateRoute(route)
this.router.afterHooks.forEach(hook => {
hook && hook(route, prev)
})
},
err => {
if (isRouterError(err, NavigationFailureType.duplicated)) {
if (isNavigationFailure(err, NavigationFailureType.duplicated)) {
this.index = targetIndex
}
}
+53 -56
View File
@@ -4,8 +4,8 @@ import { _Vue } from '../install'
import type Router from '../index'
import { inBrowser } from '../util/dom'
import { runQueue } from '../util/async'
import { warn, isError, isRouterError } from '../util/warn'
import { START, isSameRoute } from '../util/route'
import { warn } from '../util/warn'
import { START, isSameRoute, handleRouteEntered } from '../util/route'
import {
flatten,
flatMapComponents,
@@ -16,8 +16,11 @@ import {
createNavigationCancelledError,
createNavigationRedirectedError,
createNavigationAbortedError,
isError,
isNavigationFailure,
NavigationFailureType
} from './errors'
} from '../util/errors'
import { handleScroll } from '../util/scroll'
export class History {
router: Router
@@ -35,7 +38,11 @@ export class History {
// implemented by sub-classes
+go: (n: number) => void
+push: (loc: RawLocation, onComplete?: Function, onAbort?: Function) => void
+replace: (loc: RawLocation, onComplete?: Function, onAbort?: Function) => void
+replace: (
loc: RawLocation,
onComplete?: Function,
onAbort?: Function
) => void
+ensureURL: (push?: boolean) => void
+getCurrentLocation: () => string
+setupListeners: Function
@@ -77,11 +84,21 @@ export class History {
onComplete?: Function,
onAbort?: Function
) {
const route = this.router.match(location, this.current)
let route
// catch redirect option https://github.com/vuejs/vue-router/issues/3201
try {
route = this.router.match(location, this.current)
} catch (e) {
this.errorCbs.forEach(cb => {
cb(e)
})
// Exception should still be thrown
throw e
}
const prev = this.current
this.confirmTransition(
route,
() => {
const prev = this.current
this.updateRoute(route)
onComplete && onComplete(route)
this.ensureURL()
@@ -102,17 +119,15 @@ export class History {
onAbort(err)
}
if (err && !this.ready) {
this.ready = true
// Initial redirection should still trigger the onReady onSuccess
// Initial redirection should not mark the history as ready yet
// because it's triggered by the redirection instead
// https://github.com/vuejs/vue-router/issues/3225
if (!isRouterError(err, NavigationFailureType.redirected)) {
// https://github.com/vuejs/vue-router/issues/3331
if (!isNavigationFailure(err, NavigationFailureType.redirected) || prev !== START) {
this.ready = true
this.readyErrorCbs.forEach(cb => {
cb(err)
})
} else {
this.readyCbs.forEach(cb => {
cb(route)
})
}
}
}
@@ -121,17 +136,20 @@ export class History {
confirmTransition (route: Route, onComplete: Function, onAbort?: Function) {
const current = this.current
this.pending = route
const abort = err => {
// changed after adding errors with
// https://github.com/vuejs/vue-router/pull/3047 before that change,
// redirect and aborted navigation would produce an err == null
if (!isRouterError(err) && isError(err)) {
if (!isNavigationFailure(err) && isError(err)) {
if (this.errorCbs.length) {
this.errorCbs.forEach(cb => {
cb(err)
})
} else {
warn(false, 'uncaught error during route navigation:')
if (process.env.NODE_ENV !== 'production') {
warn(false, 'uncaught error during route navigation:')
}
console.error(err)
}
}
@@ -146,6 +164,9 @@ export class History {
route.matched[lastRouteIndex] === current.matched[lastCurrentIndex]
) {
this.ensureURL()
if (route.hash) {
handleScroll(this.router, current, route, false)
}
return abort(createNavigationDuplicatedError(current, route))
}
@@ -167,7 +188,6 @@ export class History {
resolveAsyncComponents(activated)
)
this.pending = route
const iterator = (hook: NavigationGuard, next) => {
if (this.pending !== route) {
return abort(createNavigationCancelledError(current, route))
@@ -204,11 +224,9 @@ export class History {
}
runQueue(queue, iterator, () => {
const postEnterCbs = []
const isValid = () => this.current === route
// wait until async components are resolved before
// extracting in-component enter guards
const enterGuards = extractEnterGuards(activated, postEnterCbs, isValid)
const enterGuards = extractEnterGuards(activated)
const queue = enterGuards.concat(this.router.resolveHooks)
runQueue(queue, iterator, () => {
if (this.pending !== route) {
@@ -218,9 +236,7 @@ export class History {
onComplete(route)
if (this.router.app) {
this.router.app.$nextTick(() => {
postEnterCbs.forEach(cb => {
cb()
})
handleRouteEntered(route)
})
}
})
@@ -236,11 +252,18 @@ export class History {
// Default implementation is empty
}
teardownListeners () {
teardown () {
// clean up event listeners
// https://github.com/vuejs/vue-router/issues/2341
this.listeners.forEach(cleanupListener => {
cleanupListener()
})
this.listeners = []
// reset current history route
// https://github.com/vuejs/vue-router/issues/3294
this.current = START
this.pending = null
}
}
@@ -331,15 +354,13 @@ function bindGuard (guard: NavigationGuard, instance: ?_Vue): ?NavigationGuard {
}
function extractEnterGuards (
activated: Array<RouteRecord>,
cbs: Array<Function>,
isValid: () => boolean
activated: Array<RouteRecord>
): Array<?Function> {
return extractGuards(
activated,
'beforeRouteEnter',
(guard, _, match, key) => {
return bindEnterGuard(guard, match, key, cbs, isValid)
return bindEnterGuard(guard, match, key)
}
)
}
@@ -347,41 +368,17 @@ function extractEnterGuards (
function bindEnterGuard (
guard: NavigationGuard,
match: RouteRecord,
key: string,
cbs: Array<Function>,
isValid: () => boolean
key: string
): NavigationGuard {
return function routeEnterGuard (to, from, next) {
return guard(to, from, cb => {
if (typeof cb === 'function') {
cbs.push(() => {
// #750
// if a router-view is wrapped with an out-in transition,
// the instance may not have been registered at this time.
// we will need to poll for registration until current route
// is no longer valid.
poll(cb, match.instances, key, isValid)
})
if (!match.enteredCbs[key]) {
match.enteredCbs[key] = []
}
match.enteredCbs[key].push(cb)
}
next(cb)
})
}
}
function poll (
cb: any, // somehow flow cannot infer this is a function
instances: Object,
key: string,
isValid: () => boolean
) {
if (
instances[key] &&
!instances[key]._isBeingDestroyed // do not reuse being destroyed instance
) {
cb(instances[key])
} else if (isValid()) {
setTimeout(() => {
poll(cb, instances, key, isValid)
}, 16)
}
}
-12
View File
@@ -124,18 +124,6 @@ export function getHash (): string {
if (index < 0) return ''
href = href.slice(index + 1)
// decode the hash but not the search or hash
// as search(query) is already decoded
// https://github.com/vuejs/vue-router/issues/2708
const searchIndex = href.indexOf('?')
if (searchIndex < 0) {
const hashIndex = href.indexOf('#')
if (hashIndex > -1) {
href = decodeURI(href.slice(0, hashIndex)) + href.slice(hashIndex)
} else href = decodeURI(href)
} else {
href = decodeURI(href.slice(0, searchIndex)) + href.slice(searchIndex)
}
return href
}
+8 -2
View File
@@ -86,8 +86,14 @@ export class HTML5History extends History {
}
export function getLocation (base: string): string {
let path = decodeURI(window.location.pathname)
if (base && path.toLowerCase().indexOf(base.toLowerCase()) === 0) {
let path = window.location.pathname
const pathLowerCase = path.toLowerCase()
const baseLowerCase = base.toLowerCase()
// base="/a" shouldn't turn path="/app" into "/a/pp"
// https://github.com/vuejs/vue-router/issues/3555
// so we ensure the trailing slash in the base
if (base && ((pathLowerCase === baseLowerCase) ||
(pathLowerCase.indexOf(cleanPath(baseLowerCase + '/')) === 0))) {
path = path.slice(base.length)
}
return (path || '/') + window.location.search + window.location.hash
+78 -46
View File
@@ -2,12 +2,13 @@
import { install } from './install'
import { START } from './util/route'
import { assert } from './util/warn'
import { assert, warn } from './util/warn'
import { inBrowser } from './util/dom'
import { cleanPath } from './util/path'
import { createMatcher } from './create-matcher'
import { normalizeLocation } from './util/location'
import { supportsPushState } from './util/push-state'
import { handleScroll } from './util/scroll'
import { HashHistory } from './history/hash'
import { HTML5History } from './history/html5'
@@ -15,24 +16,32 @@ import { AbstractHistory } from './history/abstract'
import type { Matcher } from './create-matcher'
export default class VueRouter {
static install: () => void;
static version: string;
import { isNavigationFailure, NavigationFailureType } from './util/errors'
app: any;
apps: Array<any>;
ready: boolean;
readyCbs: Array<Function>;
options: RouterOptions;
mode: string;
history: HashHistory | HTML5History | AbstractHistory;
matcher: Matcher;
fallback: boolean;
beforeHooks: Array<?NavigationGuard>;
resolveHooks: Array<?NavigationGuard>;
afterHooks: Array<?AfterNavigationHook>;
export default class VueRouter {
static install: () => void
static version: string
static isNavigationFailure: Function
static NavigationFailureType: any
static START_LOCATION: Route
app: any
apps: Array<any>
ready: boolean
readyCbs: Array<Function>
options: RouterOptions
mode: string
history: HashHistory | HTML5History | AbstractHistory
matcher: Matcher
fallback: boolean
beforeHooks: Array<?NavigationGuard>
resolveHooks: Array<?NavigationGuard>
afterHooks: Array<?AfterNavigationHook>
constructor (options: RouterOptions = {}) {
if (process.env.NODE_ENV !== 'production') {
warn(this instanceof VueRouter, `Router must be called with the new operator.`)
}
this.app = null
this.apps = []
this.options = options
@@ -42,7 +51,8 @@ export default class VueRouter {
this.matcher = createMatcher(options.routes || [], this)
let mode = options.mode || 'hash'
this.fallback = mode === 'history' && !supportsPushState && options.fallback !== false
this.fallback =
mode === 'history' && !supportsPushState && options.fallback !== false
if (this.fallback) {
mode = 'hash'
}
@@ -68,11 +78,7 @@ export default class VueRouter {
}
}
match (
raw: RawLocation,
current?: Route,
redirectedFrom?: Location
): Route {
match (raw: RawLocation, current?: Route, redirectedFrom?: Location): Route {
return this.matcher.match(raw, current, redirectedFrom)
}
@@ -81,11 +87,12 @@ export default class VueRouter {
}
init (app: any /* Vue component instance */) {
process.env.NODE_ENV !== 'production' && assert(
install.installed,
`not installed. Make sure to call \`Vue.use(VueRouter)\` ` +
`before creating root instance.`
)
process.env.NODE_ENV !== 'production' &&
assert(
install.installed,
`not installed. Make sure to call \`Vue.use(VueRouter)\` ` +
`before creating root instance.`
)
this.apps.push(app)
@@ -99,11 +106,7 @@ export default class VueRouter {
// we do not release the router so it can be reused
if (this.app === app) this.app = this.apps[0] || null
if (!this.app) {
// clean up event listeners
// https://github.com/vuejs/vue-router/issues/2341
this.history.teardownListeners()
}
if (!this.app) this.history.teardown()
})
// main app previously initialized
@@ -117,14 +120,28 @@ export default class VueRouter {
const history = this.history
if (history instanceof HTML5History || history instanceof HashHistory) {
const setupListeners = () => {
history.setupListeners()
const handleInitialScroll = routeOrError => {
const from = history.current
const expectScroll = this.options.scrollBehavior
const supportsScroll = supportsPushState && expectScroll
if (supportsScroll && 'fullPath' in routeOrError) {
handleScroll(this, routeOrError, from, false)
}
}
history.transitionTo(history.getCurrentLocation(), setupListeners, setupListeners)
const setupListeners = routeOrError => {
history.setupListeners()
handleInitialScroll(routeOrError)
}
history.transitionTo(
history.getCurrentLocation(),
setupListeners,
setupListeners
)
}
history.listen(route => {
this.apps.forEach((app) => {
this.apps.forEach(app => {
app._route = route
})
})
@@ -193,11 +210,14 @@ export default class VueRouter {
if (!route) {
return []
}
return [].concat.apply([], route.matched.map(m => {
return Object.keys(m.components).map(key => {
return m.components[key]
return [].concat.apply(
[],
route.matched.map(m => {
return Object.keys(m.components).map(key => {
return m.components[key]
})
})
}))
)
}
resolve (
@@ -213,12 +233,7 @@ export default class VueRouter {
resolved: Route
} {
current = current || this.history.current
const location = normalizeLocation(
to,
current,
append,
this
)
const location = normalizeLocation(to, current, append, this)
const route = this.match(location, current)
const fullPath = route.redirectedFrom || route.fullPath
const base = this.history.base
@@ -233,7 +248,21 @@ export default class VueRouter {
}
}
getRoutes () {
return this.matcher.getRoutes()
}
addRoute (parentOrRoute: string | RouteConfig, route?: RouteConfig) {
this.matcher.addRoute(parentOrRoute, route)
if (this.history.current !== START) {
this.history.transitionTo(this.history.getCurrentLocation())
}
}
addRoutes (routes: Array<RouteConfig>) {
if (process.env.NODE_ENV !== 'production') {
warn(false, 'router.addRoutes() is deprecated and has been removed in Vue Router 4. Use router.addRoute() instead.')
}
this.matcher.addRoutes(routes)
if (this.history.current !== START) {
this.history.transitionTo(this.history.getCurrentLocation())
@@ -256,6 +285,9 @@ function createHref (base: string, fullPath: string, mode) {
VueRouter.install = install
VueRouter.version = '__VERSION__'
VueRouter.isNavigationFailure = isNavigationFailure
VueRouter.NavigationFailureType = NavigationFailureType
VueRouter.START_LOCATION = START
if (inBrowser && window.Vue) {
window.Vue.use(VueRouter)
+86
View File
@@ -0,0 +1,86 @@
// When changing thing, also edit router.d.ts
export const NavigationFailureType = {
redirected: 2,
aborted: 4,
cancelled: 8,
duplicated: 16
}
export function createNavigationRedirectedError (from, to) {
return createRouterError(
from,
to,
NavigationFailureType.redirected,
`Redirected when going from "${from.fullPath}" to "${stringifyRoute(
to
)}" via a navigation guard.`
)
}
export function createNavigationDuplicatedError (from, to) {
const error = createRouterError(
from,
to,
NavigationFailureType.duplicated,
`Avoided redundant navigation to current location: "${from.fullPath}".`
)
// backwards compatible with the first introduction of Errors
error.name = 'NavigationDuplicated'
return error
}
export function createNavigationCancelledError (from, to) {
return createRouterError(
from,
to,
NavigationFailureType.cancelled,
`Navigation cancelled from "${from.fullPath}" to "${
to.fullPath
}" with a new navigation.`
)
}
export function createNavigationAbortedError (from, to) {
return createRouterError(
from,
to,
NavigationFailureType.aborted,
`Navigation aborted from "${from.fullPath}" to "${
to.fullPath
}" via a navigation guard.`
)
}
function createRouterError (from, to, type, message) {
const error = new Error(message)
error._isRouter = true
error.from = from
error.to = to
error.type = type
return error
}
const propertiesToLog = ['params', 'query', 'hash']
function stringifyRoute (to) {
if (typeof to === 'string') return to
if ('path' in to) return to.path
const location = {}
propertiesToLog.forEach(key => {
if (key in to) location[key] = to[key]
})
return JSON.stringify(location, null, 2)
}
export function isError (err) {
return Object.prototype.toString.call(err).indexOf('Error') > -1
}
export function isNavigationFailure (err, errorType) {
return (
isError(err) &&
err._isRouter &&
(errorType == null || err.type === errorType)
)
}
+1 -1
View File
@@ -70,5 +70,5 @@ export function parsePath (path: string): {
}
export function cleanPath (path: string): string {
return path.replace(/\/\//g, '/')
return path.replace(/\/(?:\s*\/)+/g, '/')
}
+50 -32
View File
@@ -9,11 +9,21 @@ const commaRE = /%2C/g
// fixed encodeURIComponent which is more conformant to RFC3986:
// - escapes [!'()*]
// - preserve commas
const encode = str => encodeURIComponent(str)
.replace(encodeReserveRE, encodeReserveReplacer)
.replace(commaRE, ',')
const encode = str =>
encodeURIComponent(str)
.replace(encodeReserveRE, encodeReserveReplacer)
.replace(commaRE, ',')
const decode = decodeURIComponent
export function decode (str: string) {
try {
return decodeURIComponent(str)
} catch (err) {
if (process.env.NODE_ENV !== 'production') {
warn(false, `Error decoding "${str}". Leaving it intact.`)
}
}
return str
}
export function resolveQuery (
query: ?string,
@@ -29,11 +39,16 @@ export function resolveQuery (
parsedQuery = {}
}
for (const key in extraQuery) {
parsedQuery[key] = extraQuery[key]
const value = extraQuery[key]
parsedQuery[key] = Array.isArray(value)
? value.map(castQueryParamValue)
: castQueryParamValue(value)
}
return parsedQuery
}
const castQueryParamValue = value => (value == null || typeof value === 'object' ? value : String(value))
function parseQuery (query: string): Dictionary<string> {
const res = {}
@@ -46,9 +61,7 @@ function parseQuery (query: string): Dictionary<string> {
query.split('&').forEach(param => {
const parts = param.replace(/\+/g, ' ').split('=')
const key = decode(parts.shift())
const val = parts.length > 0
? decode(parts.join('='))
: null
const val = parts.length > 0 ? decode(parts.join('=')) : null
if (res[key] === undefined) {
res[key] = val
@@ -63,33 +76,38 @@ function parseQuery (query: string): Dictionary<string> {
}
export function stringifyQuery (obj: Dictionary<string>): string {
const res = obj ? Object.keys(obj).map(key => {
const val = obj[key]
const res = obj
? Object.keys(obj)
.map(key => {
const val = obj[key]
if (val === undefined) {
return ''
}
if (val === null) {
return encode(key)
}
if (Array.isArray(val)) {
const result = []
val.forEach(val2 => {
if (val2 === undefined) {
return
if (val === undefined) {
return ''
}
if (val2 === null) {
result.push(encode(key))
} else {
result.push(encode(key) + '=' + encode(val2))
if (val === null) {
return encode(key)
}
if (Array.isArray(val)) {
const result = []
val.forEach(val2 => {
if (val2 === undefined) {
return
}
if (val2 === null) {
result.push(encode(key))
} else {
result.push(encode(key) + '=' + encode(val2))
}
})
return result.join('&')
}
return encode(key) + '=' + encode(val)
})
return result.join('&')
}
return encode(key) + '=' + encode(val)
}).filter(x => x.length > 0).join('&') : null
.filter(x => x.length > 0)
.join('&')
: null
return res ? `?${res}` : ''
}
+2 -1
View File
@@ -1,7 +1,8 @@
/* @flow */
import { _Vue } from '../install'
import { warn, isError } from './warn'
import { warn } from './warn'
import { isError } from '../util/errors'
export function resolveAsyncComponents (matched: Array<RouteRecord>): Function {
return (to, from, next) => {
+29 -10
View File
@@ -70,23 +70,23 @@ function getFullPath (
return (path || '/') + stringify(query) + hash
}
export function isSameRoute (a: Route, b: ?Route): boolean {
export function isSameRoute (a: Route, b: ?Route, onlyPath: ?boolean): boolean {
if (b === START) {
return a === b
} else if (!b) {
return false
} else if (a.path && b.path) {
return (
a.path.replace(trailingSlashRE, '') === b.path.replace(trailingSlashRE, '') &&
return a.path.replace(trailingSlashRE, '') === b.path.replace(trailingSlashRE, '') && (onlyPath ||
a.hash === b.hash &&
isObjectEqual(a.query, b.query)
)
isObjectEqual(a.query, b.query))
} else if (a.name && b.name) {
return (
a.name === b.name &&
a.hash === b.hash &&
(onlyPath || (
a.hash === b.hash &&
isObjectEqual(a.query, b.query) &&
isObjectEqual(a.params, b.params)
isObjectEqual(a.params, b.params))
)
)
} else {
return false
@@ -96,14 +96,18 @@ export function isSameRoute (a: Route, b: ?Route): boolean {
function isObjectEqual (a = {}, b = {}): boolean {
// handle null value #1566
if (!a || !b) return a === b
const aKeys = Object.keys(a)
const bKeys = Object.keys(b)
const aKeys = Object.keys(a).sort()
const bKeys = Object.keys(b).sort()
if (aKeys.length !== bKeys.length) {
return false
}
return aKeys.every(key => {
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') {
return isObjectEqual(aVal, bVal)
@@ -130,3 +134,18 @@ function queryIncludes (current: Dictionary<string>, target: Dictionary<string>)
}
return true
}
export function handleRouteEntered (route: Route) {
for (let i = 0; i < route.matched.length; i++) {
const record = route.matched[i]
for (const name in record.instances) {
const instance = record.instances[name]
const cbs = record.enteredCbs[name]
if (!instance || !cbs) continue
delete record.enteredCbs[name]
for (let i = 0; i < cbs.length; i++) {
if (!instance._isBeingDestroyed) cbs[i](instance)
}
}
}
}
+11 -1
View File
@@ -160,6 +160,16 @@ function scrollToPosition (shouldScroll, position) {
}
if (position) {
window.scrollTo(position.x, position.y)
// $flow-disable-line
if ('scrollBehavior' in document.documentElement.style) {
window.scrollTo({
left: position.x,
top: position.y,
// $flow-disable-line
behavior: shouldScroll.behavior
})
} else {
window.scrollTo(position.x, position.y)
}
}
}
+1 -8
View File
@@ -7,15 +7,8 @@ export function assert (condition: any, message: string) {
}
export function warn (condition: any, message: string) {
if (process.env.NODE_ENV !== 'production' && !condition) {
if (!condition) {
typeof console !== 'undefined' && console.warn(`[vue-router] ${message}`)
}
}
export function isError (err: any): boolean {
return Object.prototype.toString.call(err).indexOf('Error') > -1
}
export function isRouterError (err: any, errorType: ?string): boolean {
return isError(err) && err._isRouter && (errorType == null || err.type === errorType)
}