监听router_深入揭秘前端路由本质,手写 mini-router

d97e61fe53243e7ea22aea2abc541d62.png

前言

前端路由一直是一个很经典的话题,不管是日常的使用还是面试中都会经常遇到。本文通过实现一个简单版的 react-router 来一起揭开路由的神秘面纱。

通过本文,你可以学习到:

  • 前端路由本质上是什么。
  • 前端路由里的一些坑和注意点。
  • hash 路由和 history 路由的区别。
  • Router 组件和 Route 组件分别是做什么的。
92f86b5c2c11198f67acdc0c4c6bdb52.gif

路由的本质

简单来说,浏览器端路由其实并不是真实的网页跳转(和服务器没有任何交互),而是纯粹在浏览器端发生的一系列行为,本质上来说前端路由就是:

对 url 进行改变和监听,来让某个 dom 节点显示对应的视图。

仅此而已。新手不要被路由这个概念给吓到。

路由的区别

一般来说,浏览器端的路由分为两种:

  1. hash 路由,特征是 url 后面会有 # 号,如 baidu.com/#foo/bar/baz。
  2. history 路由,url 和普通路径没有差异。如 baidu.com/foo/bar/baz。

我们已经讲过了路由的本质,那么实际上只需要搞清楚两种路由分别是如何 改变,并且组件是如何监听并完成视图的展示,一切就真相大白了。

不卖关子,先分别谈谈两种路由用什么样的 api 实现前端路由:

hash

通过 location.hash = 'foo' 这样的语法来改变,路径就会由 baidu.com 变更为 baidu.com/#foo。

通过 window.addEventListener('hashchange') 这个事件,就可以监听到 hash 值的变化。

history

其实是用了 history.pushState 这个 API 语法改变,它的语法乍一看比较怪异,先看下 mdn 文档里对它的定义:

history.pushState(state, title[, url])

其中 state 代表状态对象,这让我们可以给每个路由记录创建自己的状态,并且它还会序列化后保存在用户的磁盘上,以便用户重新启动浏览器后可以将其还原。

title 当前没啥用。

url 在路由中最重要的 url 参数反而是个可选参数,放在了最后一位。

通过 history.pushState({}, '', foo),可以让 baidu.com 变化为baidu.com/foo。

为什么路径更新后,浏览器页面不会重新加载?

这里我们需要思考一个问题,平常通过 location.href = 'baidu.com/foo' 这种方式来跳转,是会让浏览器重新加载页面并且请求服务器的,但是 history.pushState 的神奇之处就在于它可以让 url 改变,但是不重新加载页面,完全由用户决定如何处理这次 url 改变。

因此,这种方式的前端路由必须在支持 histroy API 的浏览器上才可以使用。

为什么刷新后会 404?

本质上是因为刷新以后是带着 baidu.com/foo 这个页面去请求服务端资源的,但是服务端并没有对这个路径进行任何的映射处理,当然会返回 404,处理方式是让服务端对于"不认识"的页面,返回 index.html,这样这个包含了前端路由相关js代码的首页,就会加载你的前端路由配置表,并且此时虽然服务端给你的文件是首页文件,但是你的 url 上是 baidu.com/foo,前端路由就会加载 /foo 这个路径相对应的视图,完美的解决了 404 问题。

history 路由的监听也有点坑,浏览器提供了window.addEventListener('popstate') 事件,但是它只能监听到浏览器回退和前进所产生的路由变化,对于主动的 pushState 却监听不到。解决方案当然有,下文实现 react-router 的时候再细讲~

实现 react-mini-router

本文实现的 react-router 基于 history 版本,用最小化的代码还原路由的主要功能,所以不会有正则匹配或者嵌套子路由等高阶特性,回归本心,从零到一实现最简化的版本。

实现 history

对于 history 难用的官方 API,我们专门抽出一个小文件对它进行一层封装,对外提供:

  1. history.push。
  2. history.listen。

这两个 API,减轻用户的心智负担。

我们利用观察者模式封装了一个简单的 listen API,让用户可以监听到history.push 所产生的路径改变。

// 存储 history.listen 的回调函数let listeners: Listener[] = [];function listen(fn: Listener) {  listeners.push(fn);  return function() {    listeners = listeners.filter(listener => listener !== fn);  };}

这样外部就可以通过:

history.listen(location => {  console.log('changed', location);});

这样的方式感知到路由的变化了,并且在 location 中,我们还提供了 state、pathname、search 等关键的信息。

实现改变路径的核心方法 push 也很简单:

function push(to: string, state?: State) {  // 解析用户传入的 url  // 分解成 pathname、search 等信息  location = getNextLocation(to, state);  // 调用原生 history 的方法改变路由  window.history.pushState(state, '', to);  // 执行用户传入的监听函数  listeners.forEach(fn => fn(location));}

在 history.push('foo') 的时候,本质上就是调用了 window.history.pushState 去改变路径,并且通知 listen 所挂载的回调函数去执行。

当然,别忘了用户点击浏览器后退前进按钮的行为,也需要用 popstate 这个事件来监听,并且执行同样的处理:

// 用于处理浏览器前进后退操作window.addEventListener('popstate', () => {  location = getLocation();  listeners.forEach(fn => fn(location));});

接下来我们需要实现 Router 和 Route 组件,你就会看到它们是如何和这个简单的 history 库结合使用了。

实现 Router

Router 的核心原理就是通过 Provider 把 location 和 history 等路由关键信息传递给子组件,并且在路由发生变化的时候要让子组件可以感知到:

import React, { useState, useEffect, ReactNode } from 'react';import { history, Location } from './history';interface RouterContextProps {  history: typeof history;  location: Location;}export const RouterContext = React.createContext(  null,);export const Router: React.FC = ({ children }) => {  const [location, setLocation] = useState(history.location);  // 初始化的时候 订阅 history 的变化  // 一旦路由发生改变 就会通知使用了 useContext(RouterContext) 的子组件去重新渲染  useEffect(() => {    const unlisten = history.listen(location => {      setLocation(location);    });    return unlisten;  }, []);  return (          {children}      );};

注意看注释的部分,我们在组件初始化的时候利用 history.listen 监听了路由的变化,一旦路由发生改变,就会调用 setLocation 去更新 location 并且通过Provider 传递给子组件。

并且这一步也会触发 Provider 的 value 值的变化,通知所有用 useContext 订阅了history 和 location 的子组件去重新 render。

实现 Route

Route 组件接受 path 和 children 两个 prop,本质上就决定了在某个路径下需要渲染什么组件,我们又可以通过 Router 的 Provider 传递下来的 location 信息拿到当前路径,所以这个组件需要做的就是判断当前的路径是否匹配,渲染对应组件。

import { ReactNode } from 'react';import { useLocation } from './hooks';interface RouteProps {  path: string;  children: ReactNode;}export const Route = ({ path, children }: RouteProps) => {  const { pathname } = useLocation();  const matched = path === pathname;  if (matched) {    return children;  }  return null;};

这里的实现比较简单,路径直接用了全等,实际上真正的实现考虑的情况比较复杂,使用了 path-to-regexp 这个库去处理动态路由等情况,但是核心原理其实就是这么简单。

实现 useLocation、useHistory

这里就很简单了,利用 useContext 简单封装一层,拿到 Router 传递下来的history 和 location 即可。

import { useContext } from 'react';import { RouterContext } from './Router';export const useHistory = () => {  return useContext(RouterContext)!.history;};export const useLocation = () => {  return useContext(RouterContext)!.location;};

实现验证 demo

至此为止,以下的路由 demo 就可以跑通了:

import React, { useEffect } from 'react';import { Router, Route, useHistory } from 'react-mini-router';const Foo = () => 'foo';const Bar = () => 'bar';const Links = () => {  const history = useHistory();  const go = (path: string) => {    const state = { name: path };    history.push(path, state);  };  return (    
       go('foo')}>foo       go('bar')}>bar    
  );};export default () => {  return (    

结语

通过本文的学习,相信小伙伴们已经搞清楚了前端路由的原理,其实它只是对浏览器提供 API 的一个封装,以及在框架层去联动做对应的渲染,换个框架 vue-router 也是类似的原理。

喜欢的老铁,加个关注!今后会分享更多的前端干货,欢迎点赞转发关注[比心]

本文源码地址:https://github.com/sl1673495/react-mini-router

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/500303.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

AI应用开发基础傻瓜书系列附录-基本数学导数公式

基本函数导数公式 Copyright © Microsoft Corporation. All rights reserved. 适用于License版权许可 更多微软人工智能学习资源,请见微软人工智能教育与学习共建社区 Content01.0-神经网络的基本工作原理01.1-基本数学导数公式01.2-Python-Numpy库的点滴02.…

5gh掌上云计算认证不通过_阿里云ACP认证考试攻略、考试心得、费用及常见问题...

阿里云ACP级认证是阿里云的专业工程师认证,云吞铺子分享ACP认证考试攻略、考试心得、考试费用及常见问题:ACP认证分类ACP认证根据所属领域不同分为五类,即即云计算、大数据、大数据分析师、云安全、企业互联网架构,考试方向不同考…

AI应用开发基础傻瓜书系列2-神经网络中反向传播与梯度下降的基本概念

AI应用开发基础傻瓜书系列2-神经网络中反向传播与梯度下降的基本概念 Copyright © Microsoft Corporation. All rights reserved. 适用于License版权许可 更多微软人工智能学习资源,请见微软人工智能教育与学习共建社区 Content01.0-神经网络的基本工作原理0…

verilog异步复位jk触发器_Verilog专题(九)DFF、Dlatch、JK flipflop

DFF、Dlatch、JK flip-flop对于verilog的学习,这里推荐一个比较好的实践网站HDLBits:https://hdlbits.01xz.net/wiki/Main_Page本系列记录一些我觉得有价值的题目,希望通过这些题目可以对verilog更加熟练。D flip-flops D触发器根据复位的…

AI应用开发基础傻瓜书系列3-激活函数和损失函数

Copyright © Microsoft Corporation. All rights reserved. 适用于License版权许可 更多微软人工智能学习资源,请见微软人工智能教育与学习共建社区 Content01.0-神经网络的基本工作原理01.1-基本数学导数公式01.2-Python-Numpy库的点滴02.0-反向传播与梯度下…

中两个数做减法_人生下半场,学会做减法

作者:洞见余生人生如逆旅,你我皆行人。梭罗在瓦尔登湖中写道:“一个人,只要满足了基本生活所需,不再汲汲于声名,不再汲汲于富贵,便可以更从容,更充实地享受人生。”曾经觉得&#xf…

AI应用开发基础傻瓜书系列3-激活函数

Copyright © Microsoft Corporation. All rights reserved. 适用于License版权许可 更多微软人工智能学习资源,请见微软人工智能教育与学习共建社区 Content01.0-神经网络的基本工作原理01.1-基本数学导数公式01.2-Python-Numpy库的点滴02.0-反向传播与梯度下…

cifs挂载 mount ubuntu_centos或者Ubuntu挂载windows10文件夹

一、centos挂载windows文件夹格式:mount -t cifs //IP/share-folder /mnt-point -o usernameyour-username,passwdyour-password或者mount //192.168.1.100/www /usr/local/nginx/html/ -o username"你的window管理员账号",password"你的window管理…

AI应用开发基础傻瓜书系列3-损失函数

Copyright © Microsoft Corporation. All rights reserved. 适用于License版权许可 更多微软人工智能学习资源,请见微软人工智能教育与学习共建社区 Content01.0-神经网络的基本工作原理01.1-基本数学导数公式01.2-Python-Numpy库的点滴02.0-反向传播与梯度下…

五大质量工具详解及运用案例_掌握质量管理五大工具,实现九段质量管理成长...

对于工厂企业来说,产品的质量是企业经营的命脉,那么质量管理工作要怎么去做好就是一个关键。今天给大家分享关于质量管理五大核心工具的内容,那何为五大工具,他们都有什么特点,又有何要求?我们将其中的主要…

双层板在哪层覆铜_PCB覆铜箔层压板分类和工艺解析

PCB覆铜箔层压板随着电子信息产业的快速发展,电子产品和电路组装技术也迈上了一个新的台阶。它推动了pcb制造技术向微孔径、细线、高密度布线、多层化方向发展。对覆铜板的耐热性、低膨胀系数、高尺寸稳定性和低介电损耗提出了新的要求。①PCB覆铜箔层压板分类PCB覆…

现代软件工程 作业 团队冲刺阶段的要求

1. 对团队冲刺的要求 团队在日期区间任选 10 天进行冲刺 (sprint),每天冲刺要在当天固定时间点发布一篇随笔。具体的博文规范如下: 每篇博客的要求: ① SCRUM: 每个成员描述:我昨天的成就(完成了哪个任务,花了多少时间…

两个numpy取相同值_闲谈Numpy的切片规则

我想说在学numpy库的时候切片真的让我有点痛苦的,逗号分号括号数字交织在一起刚开始看的我简直脑袋要爆炸,不过后来静下心来仔细看了看,发现其实也米有这么复杂,毕竟基于python的numpy库也是遵循着特定的语法的。今天就来聊聊这个…

微软开源自动机器学习工具 – NNI安装与使用

微软开源自动机器学习工具 – NNI安装与使用NNI的众多特点开启你的第一次NNI之旅 安装 三步准备实验(1) 准备搜索空间(2) 准备实验代码(3)定义实验配置 一行命令开始训练 webUI查看结果扩展阅读 基础定义 扩…

神经网络基本原理简明教程-0-Python-Numpy库的点滴

Python中的Numpy的基本知识 Copyright © Microsoft Corporation. All rights reserved. 适用于License版权许可 更多微软人工智能学习资源,请见微软人工智能教育与学习共建社区 以下列出一些关于Numpy矩阵运算的基本知识和坑点。 首先需要在命令行中安装Num…

神经网络基本原理简明教程-0-基本函数导数公式

基本函数导数公式 Copyright © Microsoft Corporation. All rights reserved. 适用于License版权许可 更多微软人工智能学习资源,请见微软人工智能教育与学习共建社区 如何浏览本系列教程 由于里面包含了大量必要的数学公式,都是用LaTex格式编写…

微软发布人工智能教育与学习共建社区

步入2019,人工智能(Artificial Intelligence)的浪潮依然汹涌,各国对于AI人才的需求进一步加大:2月,美国总统特朗普签署行政命令,正式启动美国人工智能计划;加拿大正通过“全球技能战…

流量复制_快速体验之《gor+diffy实现线上流量复制到测试环境》

对于没有副作用的接口(重复发送不会产生两份数据、不会产生多余的监控统计等等),就可以用这种方式方便的做回归测试。 部署三个不接外部流量的服务,两份老版本、一份新版本,把生产环境的流量复制到 Diffy 上。 如果生产环境支持通过请求头之类…

顶级程序员的心得 –– Coders at Work

顶级程序员的心得 –– Coders at Work说明:这篇文章是我 2010 年的原创,但是发现 csdn 的版本把格式全部搞坏了,原文在这里 我2009年读了 “Coders at Work”, 这是作者对15 位顶级程序员的采访, 总共600页。 从采访的模式看&…

springboot 技术图谱_java后台(Springboot)开发知识图谱高频技术汇总-学习路线...

【原创】java后台(Springboot)开发知识图谱&&高频技术汇总1.引言:学习一个新的技术时,其实不在于跟着某个教程敲出了几行、几百行代码,这样你最多只能知其然而不知其所以然,进步缓慢且深度有限,最重要的是一开…