Доброго времени суток, друзья!
Простые одностраничные приложения, основанные на React, Vue или чистом JavaScript, окружают нас повсюду. Хороший «одностраничник» предполагает соответствующий механизм маршрутизации.
Такие библиотеки, как «navigo» или «react-router», приносят большую пользу. Но как они работают? Необходимо ли нам импортировать всю библиотеку? Или достаточно какой-то части, скажем, 10%? В действительности, быстрый и полезный маршрутизатор можно легко написать самому, это займет немного времени, а программа будет состоять менее чем из 100 строчек кода.
Требования
Наш маршрутизатор должен быть:
- написан на ES6+
- совместим с историей и хешем
- переиспользуемой библиотекой
Обычно в веб приложении используется один экземпляр маршрутизатора, но во многих случаях нам требуется несколько экземпляров, поэтому мы не сможем использовать синглтон (Singleton) в качестве шаблона. Для работы нашему маршрутизатору необходимы следующие свойства:
- маршрутизаторы (routes): список зарегистрированных маршрутизаторов
- режим (mode): хеш или история
- корневой элемент (root): корневой элемент приложения, если мы находимся в режиме использования истории
- конструктор (constructor): основная функция для создания нового экземпляра маршрутизатора
class Router {
routes = []
mode = null
root = '/'
constructor(options) {
this.mode = window.history.pushState ? 'history' : 'hash'
if (options.mode) this.mode = options.mode
if (options.root) this.root = options.root
}
}
export default Router
Добавление и удаление маршрутизаторов
Добавление и удаление маршрутизаторов осуществляется через добавление и удаление элементов массива:
class Router {
routes = []
mode = null
root = '/'
constructor(options) {
this.mode = window.history.pushState ? 'history' : 'hash'
if (options.mode) this.mode = options.mode
if (options.root) this.root = options.root
}
add = (path, cb) => {
this.routes.push({
path,
cb
})
return this
}
remove = path => {
for (let i = 0; i < this.routes.length; i += 1) {
if (this.routes[i].path === path) {
this.routes.slice(i, 1)
return this
}
}
return this
}
flush = () => {
this.routes = []
return this
}
}
export default Router
Получение текущего пути
Мы должны знать, где находимся в приложении в определенный момент времени.
Для этого нам потребуется обработка обоих режимов (истории и хеша). В первом случае, нам нужно удалить путь к корневому элементу из window.location, во втором — "#". Нам также необходима функция (clearSlash) для удаления всех маршрутизаторов (строки от начала до конца):
[...]
clearSlashes = path =>
path
.toString()
.replace(//$/, '')
.replace(/^//, '')
getFragment = () => {
let fragment = ''
if (this.mode === 'history') {
fragment = this.clearSlashes(decodeURI(window.location.pathname + window.location.search))
fragment = fragment.replace(/?(.*)$/, '')
fragment = this.root !== '/' ? fragment.replace(this.root, '') : fragment
} else {
const match = window.location.href.match(/#(.*)$/)
fragment = match ? match[1] : ''
}
return this.clearSlashes(fragment)
}
}
export default Router
Навигация
Ок, у нас имеется API для добавления и удаления URL. Также у нас имеется возможность получать текущий адрес. Следующий шаг — навигация по маршрутизатору. Работаем со свойством «mode»:
[...]
getFragment = () => {
let fragment = ''
if (this.mode === 'history') {
fragment = this.clearSlashes(decodeURI(window.location.pathname + window.location.search))
fragment = fragment.replace(/?(.*)$/, '')
fragment = this.root !== '/' ? fragment.replace(this.root, '') : fragment
} else {
const match = window.location.href.match(/#(.*)$/)
fragment = match ? match[1] : ''
}
return this.clearSlashes(fragment)
}
navigate = (path = '') => {
if (this.mode === 'history') {
window.history.pushState(null, null, this.root + this.clearSlashes(path))
} else {
window.location.href = `${window.location.href.replace(/#(.*)$/, '')}#${path}`
}
return this
}
}
export default Router
Наблюдаем за изменениями
Теперь нам нужна логика для отслеживания изменений адреса как с помощью ссылки, так и с помощью созданного нами метода «navigate». Также нам необходимо обеспечить рендеринг правильной страницы при первом посещении. Мы могли бы использовать состояние приложения для регистрации изменений, однако в целях изучения сделаем это с помощью setInterval:
class Router {
routes = [];
mode = null;
root = "/";
constructor(options) {
this.mode = window.history.pushState ? "history" : "hash";
if (options.mode) this.mode = options.mode;
if (options.root) this.root = options.root;
this.listen();
}
[...]
listen = () => {
clearInterval(this.interval)
this.interval = setInterval(this.interval, 50)
}
interval = () => {
if (this.current === this.getFragment()) return
this.current = this.getFragment()
this.routes.some(route => {
const match = this.current.match(route.path)
if (match) {
match.shift()
route.cb.apply({}, match)
return match
}
return false
})
}
}
export default Router
Заключение
Наша библиотека готова к использованию. Она состоит всего лишь из 84 строчек кода!
Код и пример использования на Github.
Благодарю за внимание.
Специально для сайта ITWORLD.UZ. Новость взята с сайта Хабр