编写现代的JavaScript路由器

朋友们,美好的一天!

基于React,Vue或纯JavaScript的简单单页应用程序无处不在。一个好的“一页”假定有适当的路由机制。

诸如“ navigo”或“ react-router”之类的库非常有用。但是它们如何工作?我们需要导入整个库吗?还是某个部分足够了,例如10%?实际上,您可以自己轻松地编写一个快速且有用的路由器,这将花费一些时间,并且该程序将包含少于100行的代码。

要求


我们的路由器应该是:

  • 用ES6 +编写
  • 与历史和哈希兼容
  • 可重用库

通常,一个Web应用程序使用一个路由器实例,但是在许多情况下,我们需要多个实例,因此我们将无法使用Singleton作为模板。要工作,我们的路由器需要以下属性:

  • 路由器:已注册路由器的列表
  • 模式:哈希或历史记录
  • 根元素:应用程序的根元素,如果我们处于历史使用模式
  • 构造函数:用于创建新路由器实例的主要功能

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

导航


好的,我们有一个用于添加和删除URL的API。我们也有机会获得当前地址。下一步是浏览路由器。我们使用“ 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

观察变化


现在,我们需要使用链接或使用我们创建的“导航”方法来跟踪地址更改的逻辑。我们还需要确保首次访问时呈现正确的页面。我们可以使用应用程序的状态来注册更改,但是,出于研究目的,我们将使用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

上的代码和用法示例 感谢您的关注。


All Articles