Skip to content

Commit dc9d52f

Browse files
committed
refactor: split scrollBehavior logic and pushState logic into separate modules
1 parent a7a5d9c commit dc9d52f

File tree

8 files changed

+136
-104
lines changed

8 files changed

+136
-104
lines changed

src/history/abstract.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
/* @flow */
22

3-
import type VueRouter from '../index'
3+
import type Router from '../index'
44
import { History } from './base'
55

66
export class AbstractHistory extends History {
77
index: number;
88
stack: Array<Route>;
99

10-
constructor (router: VueRouter, base: ?string) {
10+
constructor (router: Router, base: ?string) {
1111
super(router, base)
1212
this.stack = []
1313
this.index = -1

src/history/base.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
/* @flow */
22

3-
import type VueRouter from '../index'
3+
import type Router from '../index'
44
import { warn } from '../util/warn'
55
import { inBrowser } from '../util/dom'
66
import { runQueue } from '../util/async'
77
import { START, isSameRoute } from '../util/route'
88
import { _Vue } from '../install'
99

1010
export class History {
11-
router: VueRouter;
11+
router: Router;
1212
base: string;
1313
current: Route;
1414
pending: ?Route;
@@ -21,7 +21,7 @@ export class History {
2121
ensureURL: (push?: boolean) => void;
2222
getCurrentLocation: () => string;
2323

24-
constructor (router: VueRouter, base: ?string) {
24+
constructor (router: Router, base: ?string) {
2525
this.router = router
2626
this.base = normalizeBase(base)
2727
// start with a route object that stands for "nowhere"

src/history/hash.js

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,30 @@
11
/* @flow */
22

3-
import type VueRouter from '../index'
3+
import type Router from '../index'
44
import { History } from './base'
5-
import { getLocation } from './html5'
65
import { cleanPath } from '../util/path'
6+
import { getLocation } from './html5'
77

88
export class HashHistory extends History {
9-
constructor (router: VueRouter, base: ?string, fallback: boolean) {
9+
constructor (router: Router, base: ?string, fallback: boolean) {
1010
super(router, base)
1111
// check history fallback deeplinking
12-
if (fallback && this.checkFallback()) {
12+
if (fallback && checkFallback(this.base)) {
1313
return
1414
}
1515
ensureSlash()
1616
}
1717

18-
checkFallback () {
19-
const location = getLocation(this.base)
20-
if (!/^\/#/.test(location)) {
21-
window.location.replace(
22-
cleanPath(this.base + '/#' + location)
23-
)
24-
return true
25-
}
26-
}
27-
28-
onHashChange () {
29-
if (!ensureSlash()) {
30-
return
31-
}
32-
this.transitionTo(getHash(), route => {
33-
replaceHash(route.fullPath)
18+
// this is delayed until the app mounts
19+
// to avoid the hashchange listener being fired too early
20+
setupListeners () {
21+
window.addEventListener('hashchange', () => {
22+
if (!ensureSlash()) {
23+
return
24+
}
25+
this.transitionTo(getHash(), route => {
26+
replaceHash(route.fullPath)
27+
})
3428
})
3529
}
3630

@@ -64,6 +58,16 @@ export class HashHistory extends History {
6458
}
6559
}
6660

61+
function checkFallback (base) {
62+
const location = getLocation(base)
63+
if (!/^\/#/.test(location)) {
64+
window.location.replace(
65+
cleanPath(base + '/#' + location)
66+
)
67+
return true
68+
}
69+
}
70+
6771
function ensureSlash (): boolean {
6872
const path = getHash()
6973
if (path.charAt(0) === '/') {

src/history/html5.js

Lines changed: 11 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -2,58 +2,45 @@
22

33
import type Router from '../index'
44
import { History } from './base'
5-
import { inBrowser } from '../util/dom'
65
import { cleanPath } from '../util/path'
7-
import { handleScroll, saveScrollPosition } from '../util/scroll'
8-
9-
// use User Timing api (if present) for more accurate key precision
10-
const Time = inBrowser && window.performance && window.performance.now
11-
? window.performance
12-
: Date
13-
14-
const genKey = () => String(Time.now())
15-
let _key: string = genKey()
6+
import { setupScroll, handleScroll } from '../util/scroll'
7+
import { pushState, replaceState } from '../util/push-state'
168

179
export class HTML5History extends History {
1810
constructor (router: Router, base: ?string) {
1911
super(router, base)
2012

2113
const expectScroll = router.options.scrollBehavior
14+
15+
if (expectScroll) {
16+
setupScroll()
17+
}
18+
2219
window.addEventListener('popstate', e => {
23-
_key = e.state && e.state.key
24-
const current = this.current
25-
this.transitionTo(getLocation(this.base), next => {
20+
this.transitionTo(getLocation(this.base), route => {
2621
if (expectScroll) {
27-
handleScroll(router, _key, next, current, true)
22+
handleScroll(router, route, this.current, true)
2823
}
2924
})
3025
})
31-
32-
if (expectScroll) {
33-
window.addEventListener('scroll', () => {
34-
saveScrollPosition(_key)
35-
})
36-
}
3726
}
3827

3928
go (n: number) {
4029
window.history.go(n)
4130
}
4231

4332
push (location: RawLocation, onComplete?: Function, onAbort?: Function) {
44-
const current = this.current
4533
this.transitionTo(location, route => {
4634
pushState(cleanPath(this.base + route.fullPath))
47-
handleScroll(this.router, _key, route, current, false)
35+
handleScroll(this.router, route, this.current, false)
4836
onComplete && onComplete(route)
4937
}, onAbort)
5038
}
5139

5240
replace (location: RawLocation, onComplete?: Function, onAbort?: Function) {
53-
const current = this.current
5441
this.transitionTo(location, route => {
5542
replaceState(cleanPath(this.base + route.fullPath))
56-
handleScroll(this.router, _key, route, current, false)
43+
handleScroll(this.router, route, this.current, false)
5744
onComplete && onComplete(route)
5845
}, onAbort)
5946
}
@@ -77,24 +64,3 @@ export function getLocation (base: string): string {
7764
}
7865
return (path || '/') + window.location.search + window.location.hash
7966
}
80-
81-
function pushState (url: string, replace?: boolean) {
82-
// try...catch the pushState call to get around Safari
83-
// DOM Exception 18 where it limits to 100 pushState calls
84-
const history = window.history
85-
try {
86-
if (replace) {
87-
history.replaceState({ key: _key }, '', url)
88-
} else {
89-
_key = genKey()
90-
history.pushState({ key: _key }, '', url)
91-
}
92-
saveScrollPosition(_key)
93-
} catch (e) {
94-
window.location[replace ? 'replace' : 'assign'](url)
95-
}
96-
}
97-
98-
function replaceState (url: string) {
99-
pushState(url, true)
100-
}

src/index.js

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
11
/* @flow */
22

33
import { install } from './install'
4+
import { START } from './util/route'
5+
import { assert } from './util/warn'
6+
import { inBrowser } from './util/dom'
7+
import { cleanPath } from './util/path'
48
import { createMatcher } from './create-matcher'
9+
import { normalizeLocation } from './util/location'
10+
import { supportsPushState } from './util/push-state'
11+
512
import { HashHistory } from './history/hash'
613
import { HTML5History } from './history/html5'
714
import { AbstractHistory } from './history/abstract'
8-
import { inBrowser, supportsHistory } from './util/dom'
9-
import { assert } from './util/warn'
10-
import { cleanPath } from './util/path'
11-
import { normalizeLocation } from './util/location'
12-
import { START } from './util/route'
1315

1416
export default class VueRouter {
1517
static install: () => void;
@@ -34,7 +36,7 @@ export default class VueRouter {
3436
this.match = createMatcher(options.routes || [])
3537

3638
let mode = options.mode || 'hash'
37-
this.fallback = mode === 'history' && !supportsHistory
39+
this.fallback = mode === 'history' && !supportsPushState
3840
if (this.fallback) {
3941
mode = 'hash'
4042
}
@@ -86,9 +88,7 @@ export default class VueRouter {
8688
history.transitionTo(history.getCurrentLocation())
8789
} else if (history instanceof HashHistory) {
8890
const setupHashListener = () => {
89-
window.addEventListener('hashchange', () => {
90-
history.onHashChange()
91-
})
91+
history.setupListeners()
9292
}
9393
history.transitionTo(
9494
history.getCurrentLocation(),

src/util/dom.js

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,3 @@
11
/* @flow */
22

33
export const inBrowser = typeof window !== 'undefined'
4-
5-
export const supportsHistory = inBrowser && (function () {
6-
const ua = window.navigator.userAgent
7-
8-
if (
9-
(ua.indexOf('Android 2.') !== -1 || ua.indexOf('Android 4.0') !== -1) &&
10-
ua.indexOf('Mobile Safari') !== -1 &&
11-
ua.indexOf('Chrome') === -1 &&
12-
ua.indexOf('Windows Phone') === -1
13-
) {
14-
return false
15-
}
16-
17-
return window.history && 'pushState' in window.history
18-
})()

src/util/push-state.js

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/* @flow */
2+
3+
import { inBrowser } from './dom'
4+
import { saveScrollPosition } from './scroll'
5+
6+
export const supportsPushState = inBrowser && (function () {
7+
const ua = window.navigator.userAgent
8+
9+
if (
10+
(ua.indexOf('Android 2.') !== -1 || ua.indexOf('Android 4.0') !== -1) &&
11+
ua.indexOf('Mobile Safari') !== -1 &&
12+
ua.indexOf('Chrome') === -1 &&
13+
ua.indexOf('Windows Phone') === -1
14+
) {
15+
return false
16+
}
17+
18+
return window.history && 'pushState' in window.history
19+
})()
20+
21+
// use User Timing api (if present) for more accurate key precision
22+
const Time = inBrowser && window.performance && window.performance.now
23+
? window.performance
24+
: Date
25+
26+
let _key: string = genKey()
27+
28+
function genKey (): string {
29+
return Time.now().toFixed(3)
30+
}
31+
32+
export function getStateKey () {
33+
return _key
34+
}
35+
36+
export function setStateKey (key: string) {
37+
_key = key
38+
}
39+
40+
export function genNewKey () {
41+
_key = genKey()
42+
}
43+
44+
export function pushState (url?: string, replace?: boolean) {
45+
// try...catch the pushState call to get around Safari
46+
// DOM Exception 18 where it limits to 100 pushState calls
47+
const history = window.history
48+
try {
49+
if (replace) {
50+
history.replaceState({ key: _key }, '', url)
51+
} else {
52+
_key = genKey()
53+
history.pushState({ key: _key }, '', url)
54+
}
55+
saveScrollPosition()
56+
} catch (e) {
57+
window.location[replace ? 'replace' : 'assign'](url)
58+
}
59+
}
60+
61+
export function replaceState (url?: string) {
62+
pushState(url, true)
63+
}

src/util/scroll.js

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,22 @@
22

33
import type Router from '../index'
44
import { assert } from './warn'
5+
import { getStateKey, setStateKey } from './push-state'
56

67
const positionStore = Object.create(null)
78

9+
export function setupScroll () {
10+
window.addEventListener('popstate', e => {
11+
if (e.state && e.state.key) {
12+
setStateKey(e.state.key)
13+
}
14+
})
15+
16+
window.addEventListener('scroll', saveScrollPosition)
17+
}
18+
819
export function handleScroll (
920
router: Router,
10-
key: string,
1121
to: Route,
1222
from: Route,
1323
isPop: boolean
@@ -27,7 +37,7 @@ export function handleScroll (
2737

2838
// wait until re-render finishes before scrolling
2939
router.app.$nextTick(() => {
30-
let position = getScrollPosition(key)
40+
let position = getScrollPosition()
3141
const shouldScroll = behavior(to, from, isPop ? position : null)
3242
if (!shouldScroll) {
3343
return
@@ -50,17 +60,21 @@ export function handleScroll (
5060
})
5161
}
5262

53-
export function saveScrollPosition (key: string) {
54-
if (!key) return
55-
positionStore[key] = {
56-
x: window.pageXOffset,
57-
y: window.pageYOffset
63+
export function saveScrollPosition () {
64+
const key = getStateKey()
65+
if (key) {
66+
positionStore[key] = {
67+
x: window.pageXOffset,
68+
y: window.pageYOffset
69+
}
5870
}
5971
}
6072

61-
function getScrollPosition (key: string): ?Object {
62-
if (!key) return
63-
return positionStore[key]
73+
function getScrollPosition (): ?Object {
74+
const key = getStateKey()
75+
if (key) {
76+
return positionStore[key]
77+
}
6478
}
6579

6680
function getElementPosition (el: Element): Object {

0 commit comments

Comments
 (0)