Доброго времени суток, друзья!
Простые одностраничные приложения, основанные на 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.
Благодарю за внимание.
ссылка на оригинал статьи https://habr.com/ru/post/496590/
Добавить комментарий