Perguntas clássicas da entrevista | Conceito e implementação de roteamento de front-end

"Me inscrevi para participar do 1º desafio do Projeto Pedra Dourada - dividir a premiação de 100.000, este é meu primeiro artigo, clique para ver os detalhes do evento "

O recrutamento de outono está se aproximando. Ao se candidatar ao cargo de front-end, acredito que muitos alunos tenham sido questionados pelo entrevistador sobre o roteamento de front-end e seu método de implementação. Após o estudo, o autor falará brevemente sobre seus pontos de vista e compreensão dos problemas acima.

prefácio

Quando se trata de roteamento, temos que mencionar a aplicação SPA single-page, ou seja, todo o projeto possui apenas um arquivo html, e toda conversão de dados de página e salto de conteúdo são implementados por roteamento. Ou seja, corresponda a diferentes caminhos de URL, analise-os e, em seguida, renderize dinamicamente o conteúdo html regional.

Roteamento de front-end

Descreva o mapeamento entre url e interface do usuário (página), vamos chamá-lo de 前端路由, pertencer 单向映射, mudança de url faz com que a interface do usuário mude (renderize conteúdo html diferente).

Como implementar o roteamento front-end

  1. Como posso detectar que o URL mudou?
  2. Como posso alterar o URL sem fazer com que a página seja atualizada?

Modo de implementação de roteamento front-end

Para a maioria dos projetos atuais são projetos SPA, quando o projeto é um pouco mais complicado, o roteamento de front-end é necessário, e nas duas estruturas de front-end mais populares do vue, vue-router é o roteamento padrão do vue , e existem dois modos nele: hashe history.

Vamos analisar em detalhes como os dois modos implementam o roteamento de front-end.

modo hash

Antes de tudo, precisamos saber que o valor do hash do navegador (hash) significa que url 后面的#号以及后面的字符a alteração do valor do hash não fará com que o navegador envie uma solicitação ao servidor, e a alteração do valor do hash acionará o evento onhashchange. Embora o hash apareça no URL, ele não será incluído na solicitação HTTP e não terá efeito no back-end, portanto, alterar o hash não recarregará a página.

O núcleo de implementação do hash:

1. A alteração do valor de hash do navegador não fará com que o navegador envie uma solicitação ao servidor e não atualizará a página da interface do usuário.

2. Use o próprio hashchangemétodo do navegador para monitorar as alterações do hash do navegador.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <ul>
        <li><a href="#/home">首页</a></li>
        <li><a href="#/about">关于</a></li>
    </ul>

    <!-- 渲染对应的ui -->
    <div id="routerView"></div>


    <script>
        let routerView = document.getElementById('routerView');   // 获取插入html的dom结构

        window.addEventListener('load',onHashchange)

        window.addEventListener('hashchange', onHashchange)// 浏览器自带的监听哈希值改变的方法:hashchange
         
        // 控制渲染对应的 UI 
        function onHashchange() {
            // console.log(location.hash);
            switch (location.hash) { //  location.hash为哈希值
                case '#/home': 
                  routerView.innerHTML = 'Home'
                  return
                case '#/about':
                  routerView.innerHTML = 'About'
                  return
                default:
                  return
            }
        }

        routerView.innerHTML = 'Home'
    </script>
</body>
</html>
复制代码

load 事件在整个页面及所有依赖资源如样式表和图片都已完成加载时触发。先记录一次浏览器的url,当点击两个a标签中任意一个都可以触发hashchange事件,而我们写的onHashchange函数,就是模拟页面匹配 url 去渲染相应的 HTML 内容,例子中我们使用的是向一个div容器中插入html内容 。

  • hash模式所有的操作都是在前端完成的,不需要向后端(服务器)发出请求。
  • 通过监听URL中hash部分的变化,从而做出对应的渲染逻辑。
  • 缺点是url中带有 '#' 影响美观。

简单了解了hash模式的原理及实现方式,我们来聊聊history模式的原理及实现方式。

history模式

history模式的实现主要是因为HTML5中提供的history全局对象中的一些方法,比如:

  • window.history.go(pageNum) 可以跳转到浏览器会话历史中的指定的某一个记录页
  • window.history.forward() 指向浏览器会话历史中的下一页,跟浏览器的前进按钮相同
  • window.history.back() 返回浏览器会话历史中的上一页,跟浏览器的回退按钮功能相同
  • window.history.pushState(stateData, title, url) 可以将给定的数据压入到浏览器会话历史栈中
  • window.history.replaceState(stateData, title, url) 将当前的会话页面的url替换成指定的数据

而history模式的核心就是利用了 window.history.pushState(stateData, title, url)window.history.replaceState(stateData, title, url),因为这两种方法用于修改 url 时,不会刷新页面。

pushState是压入浏览器的会话历史栈中,会使得history.length加1,而replaceState是替换当前的这条会话历史,因此不会增加history.length。

第一个问题:如何改变 url 却不引起页面的刷新?history模式使用以上两种方法就可以实现,但是第二个问题,如何监听 url 发生改变?

其实官方提供了一个事件 Window: popstate event,当用户导航会话历史记录时活动历史记录条目发生更改时,会触发[Window](Window: popstate event - Web APIs | MDN (mozilla.org)) 界面的 popstate 事件。

但是官方也说明了

imagem.png

其中关键就是仅调用 history.pushState 或者 history.replaceState 方法时,不会触发popstate事件,但是单击浏览器前进或后退按钮会触发popstate事件(或者在js中调用history.forward()或者history.back()

意思就是不能用 popstate 来监听 history.forward()或者history.back()

在笔者查阅文章资料的时候发现了,我们可以重写 history.forward()history.back()两个方法,使其暴露在window全局,这样我们就可以监听重写的两个方法了。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <ul>
    <li><a href="/home">首页</a></li>
    <li><a href="/about">关于</a></li>
  </ul>

  <!-- 渲染对应的UI -->
  <div id="routerView"></div>

  <script>
    let routerView = document.getElementById('routerView')

    window.addEventListener('DOMContentLoaded', onLoad)


    // window.addEventListener('popstate', onPopState)  // 浏览器的前进后退能匹配
    

    function onLoad() {
      onPopState()

      let links = document.querySelectorAll('li a[href]')
      // 拦截a标签的默认跳转行为
      links.forEach(a => {
        a.addEventListener('click', (e) => {
          e.preventDefault() // 阻止a标签的href行为
          
          history.pushState(null, '', a.getAttribute('href'))  // 跳转
          onPopState()
        })
      })
    }


    function onPopState() {
      // console.log(location.pathname);
      switch (location.pathname) {
        case '/home':
          routerView.innerHTML = '<h2>home page</h2>'
          return
        case '/about':
          routerView.innerHTML = '<h2>about page</h2>'
          return
        default:
          return
      }
    }
  </script>
</body>
</html>
复制代码

笔者的例子中是采用了preventDefault()阻止a标签的默认href跳转行为,再利用document.querySelectorAll获取到a标签上的href属性,在此基础上再监听a标签的点击事件,在点击时间内,利用history.pushState 或者 history.replaceState 方法来实现模拟路由跳转。想监听history.pushState 或者 history.replaceState 可参考 (# 面试官为啥总是喜欢问前端路由实现方式?)

history 致命的缺点就是当改变页面地址后,强制刷新浏览器时,(如果后端没有做准备的话)会报错,因为刷新是拿当前地址去请求服务器的,如果服务器中没有相应的响应,会出现 404 页面。

结语

Portanto, a escolha do nosso roteamento front-end depende de nossos cenários de uso, requisitos, etc. Ambos podem ser implementados, cada um com suas próprias vantagens e desvantagens. Acima estão algumas das minhas opiniões pessoais sobre roteamento front-end. Qualquer dúvida pode ser discutido na área de comentários, obrigado Obrigado por assistir!

Curtir.jpg

Acho que você gosta

Origin juejin.im/post/7143059639196712997
Recomendado
Clasificación