实现一个简单的路由
手写 Hash 路由和 History 路由
问题
实现一个简单的前端路由,支持 Hash 模式和 History 模式。
解答
Hash 路由
class HashRouter {
constructor() {
this.routes = {}
this.currentPath = ''
// 监听 hash 变化
window.addEventListener('hashchange', this.onHashChange.bind(this))
// 页面加载时也要处理
window.addEventListener('load', this.onHashChange.bind(this))
}
// 注册路由
route(path, callback) {
this.routes[path] = callback
}
// hash 变化时触发
onHashChange() {
// 获取 # 后面的路径
this.currentPath = window.location.hash.slice(1) || '/'
const callback = this.routes[this.currentPath]
if (callback) {
callback()
}
}
// 跳转
push(path) {
window.location.hash = path
}
}
// 使用示例
const router = new HashRouter()
router.route('/', () => {
console.log('首页')
})
router.route('/about', () => {
console.log('关于页')
})
router.route('/user', () => {
console.log('用户页')
})
// 跳转到 /about
router.push('/about')
History 路由
class HistoryRouter {
constructor() {
this.routes = {}
this.currentPath = ''
// 监听浏览器前进后退
window.addEventListener('popstate', this.onPopState.bind(this))
}
// 注册路由
route(path, callback) {
this.routes[path] = callback
}
// popstate 触发时执行
onPopState() {
this.currentPath = window.location.pathname
const callback = this.routes[this.currentPath]
if (callback) {
callback()
}
}
// 跳转(会添加历史记录)
push(path) {
window.history.pushState({}, '', path)
this.currentPath = path
const callback = this.routes[path]
if (callback) {
callback()
}
}
// 替换(不添加历史记录)
replace(path) {
window.history.replaceState({}, '', path)
this.currentPath = path
const callback = this.routes[path]
if (callback) {
callback()
}
}
// 前进/后退
go(n) {
window.history.go(n)
}
back() {
this.go(-1)
}
forward() {
this.go(1)
}
}
// 使用示例
const router = new HistoryRouter()
router.route('/', () => {
console.log('首页')
})
router.route('/about', () => {
console.log('关于页')
})
router.push('/about')
支持参数的路由
class Router {
constructor(mode = 'hash') {
this.mode = mode
this.routes = []
this.currentPath = ''
this.init()
}
init() {
if (this.mode === 'hash') {
window.addEventListener('hashchange', this.handleChange.bind(this))
window.addEventListener('load', this.handleChange.bind(this))
} else {
window.addEventListener('popstate', this.handleChange.bind(this))
}
}
// 获取当前路径
getPath() {
if (this.mode === 'hash') {
return window.location.hash.slice(1) || '/'
}
return window.location.pathname
}
// 注册路由,支持动态参数如 /user/:id
route(path, callback) {
// 将 /user/:id 转换为正则
const paramNames = []
const regexPath = path.replace(/:([^/]+)/g, (_, name) => {
paramNames.push(name)
return '([^/]+)'
})
this.routes.push({
path,
regex: new RegExp(`^${regexPath}$`),
paramNames,
callback
})
}
// 路径变化处理
handleChange() {
this.currentPath = this.getPath()
for (const route of this.routes) {
const match = this.currentPath.match(route.regex)
if (match) {
// 提取参数
const params = {}
route.paramNames.forEach((name, index) => {
params[name] = match[index + 1]
})
route.callback(params)
return
}
}
}
push(path) {
if (this.mode === 'hash') {
window.location.hash = path
} else {
window.history.pushState({}, '', path)
this.handleChange()
}
}
}
// 使用示例
const router = new Router('hash')
router.route('/user/:id', (params) => {
console.log('用户ID:', params.id)
})
router.route('/post/:id/comment/:commentId', (params) => {
console.log('文章ID:', params.id, '评论ID:', params.commentId)
})
router.push('/user/123') // 输出: 用户ID: 123
关键点
- Hash 路由通过
hashchange事件监听 URL 中#后面的变化 - History 路由通过
popstate事件监听浏览器前进后退,pushState/replaceState不会触发该事件 - History 模式需要服务端配合,所有路由都返回同一个 HTML
- 动态路由参数可以用正则匹配,将
/user/:id转换为/user/([^/]+) - Hash 模式兼容性好,History 模式 URL 更美观
目录