页面目录
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
| 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...'))
|
2.创建首页
此时点击链接会跳转相应页面,并刷新当前页
1 2 3 4 5 6 7 8 9 10 11
| <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
| 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
|
window.addEventListener('popstate', () => { console.log("popstate"); router() })
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
| 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
| 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
| 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
| <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
| 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
| 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
| 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