Пишем современный маршрутизатор на JavaScript

—

от автора

Доброго времени суток, друзья!

Простые одностраничные приложения, основанные на 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/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *