VueRouter底层实现-Pjax技术

介绍

Pjax ,即 pushState + ajax ,用于无刷新页面资源加载,pjax 的工作原理是通过 ajax 从服务器端获取 HTML,在页面中用获取到的 HTML 替换指定容器元素中的内容。然后使用pushState技术更新浏览器地址栏中的当前地址。pjax 可以避免渲染页面重复内容,做到页面局部更新,从而达到性能提升。

实现

首先封装一个 ajax

1
2
3
4
5
6
7
8
9
10
function request(url) {
fetch(url).then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
})
.then(data => console.log(data))
.catch(error => console.error('There was a problem with your fetch operation:', error));
}

页面链接事件绑定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function convertLinks(opt: Options) {
const links = document.querySelectorAll('a')

for (const link of links) {
// 只监听内部链接, 且不监听同一页面
if (
link.host === window.location.host &&
link.pathname !== window.location.pathname &&
link.target !== '_blank'
) {
bindLink(link, opt)
}
}
}

function bindLink(el, opt) {
el.onclick = (e) => {
e.preventDefault()
// 阻止默认事件
handlePopState(el.href, opt, el.hash)
}
}

lastTrigger互斥变量防止链接多次跳转

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
let lastTrigger = null

export function handlePopState(
url,
opt,
hash,
push = true,
) {
if (lastTrigger === url) return
opt.beforeSend && opt.beforeSend()
lastTrigger = url
requestPage(url, opt.container)
.then((res) => {
if (!res.targetContent) {
throw new Error('targetContent is empty')
}
push && history.pushState({ url: window.location.href }, '', url)

scollToHash(hash)

if (opt.container && lastTrigger === url) {
const container = document.querySelector(opt.container)
container && (container.innerHTML = res.targetContent)
document.title = res.title
lastTrigger = null
// 再次绑定
convertLinks(opt)
opt.complete && opt.complete(true)
}
})
.catch((err) => {
console.error('PJAX:', err)
if (lastTrigger === url) {
lastTrigger = null
opt.complete && opt.complete(false)
}
})
}

导出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
const pjax = {
version: '__VERSION__',
initialed: false,
connect(opt?: Options) {
const option: Options = {
container: opt?.container,
beforeSend() {
history.replaceState(
{ url: window.location.href },
'',
window.location.href,
)
opt?.beforeSend && opt.beforeSend()
},
complete(success) {
opt?.complete && opt.complete(success)
},
}
convertLinks(option)

if (!this.initialed) {
window.addEventListener('popstate', (e) => {
handlePopState(e.state.url, option, '', false)
})
}

this.initialed = true
},
}