页面目录

1
2
3
4
5
6
7
8
9
10
11
single
├── frontend
| ├── static
| | ├──js
| | | └─ index.js
| | └─ views
| | ├─ View.js
| | └─ Setting.js
| └─ index.html
├── package.json
└── server.js

1.使用express构建应用

1
2
npm init -y
npm i express
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// server.js
const express = require('express')
const path = require('path')
const app = express()

function resolvePath(_path) {
return path.resolve(__dirname, 'frontend', _path)
}
app.use('/static', express.static(resolvePath('static')))
app.get('/*', (req, res) => {
res.sendFile(resolvePath('index.html'))
})

app.listen(process.env.PORT || 5001 , () => console.log('serve running...'))

express开启

2.创建首页

此时点击链接会跳转相应页面,并刷新当前页

1
2
3
4
5
6
7
8
9
10
11
<!-- frontend/index.html -->
<html>
<body>
<nav>
<a href="/" data-link>/</a>
<a href="/post" data-link>post</a>
<a href="/setting" data-link>setting</a>
</nav>
<script type="module" src="/static/js/index.js"></script>
</body>
</html>

3.创建路由

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// fronted/static/js/index.js
const router = async () => {
const routes = [
{path: '/', view: () => console.log('/')},
{path: '/setting', view: () => console.log('/setting')}
]
// 匹配当前路由。无匹配项则匹配根路由
const potentialMatches = routes.map(route => {
return {
route,
isMatch: route.path === location.pathname
}
})
let match = potentialMatches.find(potentialMatche => potentialMatche.isMatch)
if(!match) {
match = {
route: routes[0],
isMatch: true
}
}
console.log(match.route.view())
}

4.监听路由切换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// fronted/static/js/index.js
// 监听历史记录条目的更改
window.addEventListener('popstate', () => {
console.log("popstate");
router()
})
// 当初始的HTML文档被完全加载和解析完毕后,监听页面的click事件,当触发clik的元素包含[data-link]属性,阻止冒泡,并对路由进行处理
window.addEventListener("DOMContentLoaded", () => {
router()
document.body.addEventListener('click', e => {
if (e.target.matches('[data-link]')) {
e.preventDefault()
// ...
}
})
})

5.切换路由,但DOM不刷新

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// fronted/static/js/index.js
const navigateTo = url => {
// 切换路由,但不刷新页面
history.pushState(null, null, url)
router()
}
window.addEventListener("DOMContentLoaded", () => {
router()
document.body.addEventListener('click', e => {
if (e.target.matches('[data-link]')) {
e.preventDefault()
navigateTo(e.target.href)
}
})
})

6.创建单页面

6-1.创建根页面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// fronted/static/views/View.js
export default class {
constructor() {
this.title = null
}
setTitle(title) {
this.title = title
document.title = title
}

async getHtml() {
return ``
}
}

6-2.创建/setting页面

1
2
3
4
5
6
7
8
9
10
11
12
13
// fronted/static/views/Setting.js
import View from './View.js'

export default class extends View {
constructor() {
super()
this.setTitle('Setting')
}

async getHtml() {
return `<h1>Setting</h1>`
}
}

6-3.在页面中创建展示页面的元素

1
2
3
4
5
6
7
8
<!-- frontend/index.html -->
<html>
<body>
<nav>...</nav>
<div id="app"></div>
<script type="module" src="/static/js/index.js"></script>
</body>
</html>

7.在框架中应用页面

1
2
3
4
5
6
7
8
9
10
11
12
13
// fronted/static/js/index.js
import SettingView from '../views/Setting.js'
import View from '../views/View.js'
const router = async () => {
const routes = [
{path: '/', view: View},
{path: '/setting', view: SettingView}
]
// ...
const view = new match.route.view()
// 将返回的当页面嵌入当前页面
document.querySelector('#app').innerHTML = await view.getHtml()
}

8.获取地址栏中的参数

主要使用正则获取

8-1.路由匹配

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
const pathToRegex = path => new RegExp('^' + path.replace(/\//g, '\\/').replace(/:\w+/g, '(.+)') + '$')

import SettingView from '../views/Setting.js'
import View from '../views/View.js'

const router = async () => {
const routes = [
{ path: '/', view: View },
{ path: '/setting/:id', view: SettingView }
]

const potentialMatches = routes.map(route => {
return {
route,
result: location.pathname.match(pathToRegex(route.path))
}
})
let match = potentialMatches.find(potentialMatche => potentialMatche.result !== null)
if(!match) {
match = {
route: routes[0],
result: [location.pathname]
}
}
// ...
}

8-2.路由传值

1
2
3
4
5
6
7
8
9
10
11
12
13
const getParams = match => {
const values = match.result.slice(1)
const keys = Array.from(match.route.path.matchAll(/:(\w+)/g)).map(result => result[1]);
return Object.fromEntries( keys.map( (key,i) => {
return [key, values[i]]
}))

}
const router = async () => {
// ...
const view = new match.route.view(getParams(match))
// ...
}

8-3.页面接受参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// fronted/static/views/View.js
export default class {
constructor(params) {
this.params = params
this.title = null
}
setTitle(title) {
this.title = title
document.title = title
}

async getHtml() {
return ``
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
// fronted/static/views/Setting.js
import View from './View.js'

export default class extends View {
constructor() {
super(props)
this.setTitle('Setting')
}

async getHtml() {
return `<h1>Setting${this.params.id}</h1>`
}
}

源码:

https://github.com/WiSiW/frontend/tree/master/single

最后更新: 2021年12月06日 17:26