前端面试100道手写题(5)—— Router路由

简介: 前端路由,大家都使用过,那么有没有想过它是怎么实现的吗?如:Vue-Router 或者 React-Router。或许有个大概印象,但是真正要自己去实现还是没有什么思路,那么这篇文章将完整的实现思路去实现一次。

前言

前端路由,大家都使用过,那么有没有想过它是怎么实现的吗?如:Vue-Router 或者 React-Router。或许有个大概印象,但是真正要自己去实现还是没有什么思路,那么这篇文章将完整的实现思路去实现一次。

手写难度:⭐️⭐️⭐️

涉及知识点:

  • history api 和监听事件
  • onhashchange 监听事件
  • Web Component 自定义组件

路由管理

路由管理,是指的 web 应用在浏览器下根据不同的url地址展示不同的内容或者页面。

不管是 Vue-Router 或者 React-Router,基本上都是基于浏览器两种路由控制有一定了解,如下:

  • hash,代表网页中的一个位置,通常用来做锚点使用,后面被用于单页web 应用的路由控制
  • history,代表网页的历史记录,同时提供接口操作浏览器的曾经在标签页或者框架里访问的会话历史记录

下面我们对两个进行简单了解。

Hash

Hash,通常是指的浏览器 URL 地址中带#的值,如:URL = https://baidu.com/#/page1,那么 URL.hash='#/page1'

Hash 常用的 的几个方法:

hashchange

当 URL 的片段标识符更改时,将触发hashchange事件

window.addEventListener('hashchange', function() {
   
  console.log('The hash has changed!')
}, false);

调整 hash

除了监听改变之外,我们还需要对Hash 自由调整,如:添加或者修改,代码如下:

location.hash = '#/page2'

History

History 接口允许操作浏览器的曾经在标签页或者框架里访问的会话历史记录。

History 提供的 API 接口:

  • pushState(state, unused, url) 按指定的名称和 URL(如果提供该参数)将数据 push 进会话历史栈 如:history.pushState({page: 1}, "title 1", "?page=1")
  • replaceState(state, unused, url) 按指定的数据、名称和 URL(如果提供该参数),更新 history 栈上最新的条目 如:history.replaceState({page: 3}, "title 3", "?page=3");
  • back() 转到浏览器会话历史的上一页 等价于 history.go(-1)
  • forward() 转到浏览器会话历史的下一页 等价于 history.go(1)

还有一个比较重要的事件就是 onpopstate,用来监听浏览器的历史记录发生变化的。

Router 实现

在了解完路由管理机制,接下来我们对 Vue-RouterReact-Router 的功能实现做一个总结,一个基础的 Router 应该具备以下功能:

  • 路由中心,负责注册、匹配、存储等功能
  • router-viewrouter-link组件实现
  • 跳转api

Router基本功能流程要点如下:

{% diagramsnet "/assets/drawio/router-flow.drawio" %}

接下来我们就按照每个功能模块进行简单实现。

路由中心

路由中心功能分为两块,一是注册管理,二是监听匹配。

注册管理

注册管理,顾名思义就是将所有路由对应页面组件配置统一管理,当路由改变的时候,可以直接从配置找到对应页面组件。

/**
 * 注册路由
 * @param {*} routes 
 * @param {*} mode 
 * @returns 
 */
function createRouter(routes, mode='history'){
   
    // 保存路由
    const matcherMap = new Map()
    for (let route of routes) {
   
        matcherMap.set(route.name, route)
    }

    // 添加路由
    function addRoutes(routes){
   
        for (let route of routes) {
   
            matcherMap.set(route.name, route)
        }
    }

    // 删除路由
    function removeRoutes(routes){
   
        for (let route of routes) {
   
            matcherMap.delete(route.name)
        }
    }

    // 获取路由
    function getRoutes(){
   
        return matcherMap
    }

    // 获取路由
    function getRoute(name){
   
        return matcherMap.get(name)
    }

    const router = {
   
        addRoutes,
        removeRoutes,
        getRoutes,
        getRoute
    }

    return router
}

路由匹配

这里我们就简单实现一下,将 path作为 map 的 key 去存储,忽略一下比较复杂的情况,如: query 中 params和 /path/:id等情况

因此我们只需要通过获取 matcherMap 对象中对应的组件即可。

组件

组件渲染,其实在 Vue 或者 React 中都有对应渲染组件的方法,这里为了更简单实现例子,我们使用了Web Component规范去实现自定义组件<router-view>展示和渲染组件。

分为两个功能点:

  1. 自定义组件<router-view>
  2. 匹配到路由组件后渲染对应组件

<router-view>组件实现

这里使用 WebCompoent 去实现,代码如下:

// 自定义路由组件
customElements.define('router-view', class extends HTMLElement {
   
    constructor() {
   
        super();
        const template = document.createElement('template');
        template.id = 'router-view';
        template.innerHTML = '<div><slot name="content"></slot></div>';
        const templateContent = template.content;

        const shadowRoot = this.attachShadow({
    mode: "open" });
        shadowRoot.appendChild(templateContent.cloneNode(true));
    }
});

渲染对应组件

当监听到

// 路由回调
function callback() {
   
    const route = match(window.location)
    if (currentRoute && currentRoute.path === route.path) {
   
        return
    }
    if (!route) {
   
        // 路由不存在,跳转到首页
        push('/')
        return
    }
    if (route) {
   
        currentRoute = route
        const component = route.component
        // 渲染组件
        document.querySelector('router-view').innerHTML = `<${component} slot="content"></${component}>`
    }
}

完整代码我放到 github 上,大家感兴趣可以去看看Github Router完整实现

Demo体验可以看这里

额外知识点

WebComponent

Web Component 是一套不同的技术,允许你创建可重用的定制元素(它们的功能封装在你的代码之外)并且在你的 web 应用中使用它们。 —— Web Component

简单的理解,就是浏览器可以允许你自定义HTML 标签,且包含自定义的 CSS 样式和 JS 脚本逻辑。里面有三个点学习:

  • Custom element(自定义元素),通过 JS 可以自定义 HTML 标签
  • Shadow DOM(影子 DOM),可以将HTML DOM 树以附加 Shadow DOM到自定义 HTMl 标签中,从而不影响原本 HTML DOM 树结构
  • HTML template(HTML 模板),支持 和 元素,使您可以编写不在呈现页面中显示的标记模板。然后它们可以作为自定义元素结构的基础被多次重用

实践例子

  1. 自定义HTML 标签代码如下:
class CustomHTMl extends HTMLElement{
   
    constructor(){
   
        // 必须首先调用 super 方法
        super();

        // 创建一个 shadow root
        const shadow = this.attachShadow({
   mode: 'open'});

        // 创建一个 spans
        const wrapper = document.createElement('h1');
        wrapper.innerHTML = '测试自定义元素';

        shadow.appendChild(wrapper);
    }

    // 首次被插入到文档 DOM 节点上时被调用
    connectedCallback() {
   
        console.log('首次被插入到文档 DOM 节点上时被调用');
    }
    // 当 custom element 从文档 DOM 中删除时,被调用
    disconnectedCallback() {
   
        console.log('当 custom element 从文档 DOM 中删除时,被调用');
    }
    // 当 custom element 被移动到新的文档时,被调用
    adoptedCallback() {
   
        console.log('当 custom element 被移动到新的文档时,被调用');
    }
    // 增加、删除或者修改某个属性时被调用
    attributeChangedCallback(name, oldValue, newValue) {
   
        console.log('增加、删除或者修改某个属性时被调用');
    }

}
// 注册组件标签,这里比较重要
customElements.define('custom-html', PopUpInfo);

实际应用如下:

<body>
    <!-- 这里就会展示h1 -->
    <custom-html></custom-html>
</body>
  1. 使用 template模板 + slot插槽
    ```html













这是标题
这是内容



```

参考资料

目录
相关文章
|
24天前
|
JavaScript 前端开发 开发者
Vue.js 框架大揭秘:响应式系统、组件化与路由管理,震撼你的前端世界!
【8月更文挑战第27天】Vue.js是一款备受欢迎的前端JavaScript框架,以简洁、灵活和高效著称。本文将从三个方面深入探讨Vue.js:响应式系统、组件化及路由管理。响应式系统为Vue.js的核心特性,能自动追踪数据变动并更新视图。例如,通过简单示例代码展示其响应式特性:`{{ message }}`,当`message`值改变,页面随之自动更新。此外,Vue.js支持组件化设计,允许将复杂界面拆分为独立且可复用的组件,提高代码可维护性和扩展性。如创建一个包含标题与内容的简单组件,并在其他页面中重复利用。
47 3
|
1月前
|
缓存 前端开发 中间件
[go 面试] 前端请求到后端API的中间件流程解析
[go 面试] 前端请求到后端API的中间件流程解析
|
1月前
|
存储 XML 移动开发
前端大厂面试真题
前端大厂面试真题
|
1月前
|
存储 前端开发 JavaScript
44 个 React 前端面试问题
【8月更文挑战第18天】
27 2
|
1月前
|
存储 JavaScript 前端开发
2022年前端js面试题
2022年前端js面试题
21 0
|
1月前
|
存储 前端开发 JavaScript
44 个 React 前端面试问题
44 个 React 前端面试问题
|
1月前
|
存储 JavaScript 前端开发
|
1月前
|
Web App开发 存储 缓存
|
1月前
|
前端开发 容器
前端面试热门问题--浮动和清除浮动(CSS)
前端面试热门问题--浮动和清除浮动(CSS)
|
2月前
|
开发框架 JSON 前端开发
Vue&Element 前端应用开发之菜单和路由的关系
Vue&Element 前端应用开发之菜单和路由的关系