从 0 到 1 的反应路由器

标签:JavaScript,反应

介绍

本文将讨论react生态中常见的路由库,React-router的版本迭代和源码架构,并尝试讨论路由思维的变化和未来。

什么是路由?

路由是向用户显示不同页面的能力。这意味着用户可以通过输入 URL 或单击页面元素在 Web 应用程序的不同部分之间切换。

版本

为了探索react-router的设计思路,从v3开始有几个版本:

  • react-router 3 “静态路由”
  • react-router 4 “动态路由”
  • react-router 5“意外发布”
  • @reach/router “简化和轻量级”
  • react-router 6 “完整解决方案”

让我们一一参与讨论。

react-router3:静态路由

静态路由的设计如下图所示:

 做出反应。使成为((  
 <路由器>  
 < 路由路径 = "/" 组件 = {Wrap} >  
 < 路由路径 = "a" 组件 = {App} />  
 < 路由路径 = "b" 组件 = {Button} />  
 </ Route >  
 </ Router >  
 ), 文档。身体)  
 复制代码

特征:

  • 路由集中在外层
  • 页面路由配置通过 路线 组件的嵌套
  • 布局和页面组件是完全纯的,它们是路由的一部分

v3静态路由的设计相对前端工程师更容易接受,因为很多前端工程师都接触过类似的路由配置设计,比如express、rails等框架。

尽管细节有所不同,但想法大致相同——将路径静态映射到渲染模块。

react-router4:动态路由

虽然 v3 以朴实无华的方式完成了基本的路由工作,但 react-router 的几个核心成员认为现有的实现受到 ReactAPI 的严重限制,实现不够优雅。

于是,经过激烈的思考和讨论,他们大胆地对v4进行了更彻底的改动。

React-router4 不再提倡静态路由的集中式架构,而是布局和 UI 之间存在路由:

 常量应用 = () => (  
 <浏览器路由器>  
 <div>  
 < 路由路径 = "/a" 组件 = {A}/ >  
 </ div >  
 </ BrowserRouter >  
 );  
  
 常量 A = ( { 匹配 }) => (  
 <div>  
 <跨度>一个</ span >  
 < 路线  
 路径 = {match.url + '/b'} 组件 = {B} />  
 </ div >  
 );  
  
 const B = () => < div > B</ div > ;  
 复制代码

我们看一下上面代码的逻辑

  1. 在App组件的开头,只有一个路由 /一个
  2. 用户跳转访问 /一个 渲染时 一个 组件,字母A出现在浏览器上,然后是子路由 /b 被定义
  3. 用户跳转访问 /a/b 渲染时 组件,字母 B 出现在浏览器上

我们可以在 v4 中看到:

  • 路由不再在一个地方
  • 布局和页面的级联不再由级联决定 <Route> 组件控制, <Route> 与组件的替换关系
  • 布局和页面组件也不再是路由的一部分

这称为“动态路由”。

动态路由

传统的静态路径是在程序渲染之前定义的。

动态是指在渲染应用的时候动态生成路由功能,这就需要把路由看成是一个普通的React 道具 正常使用它,并用它来控制组件的呈现。这样就没有静态配置的路由规则,而是在渲染过程中由程序动态控制。

动态路由会带来很大的好处。比如代码拆分,也就是 react 常说的 代码拆分 ,由于不需要在渲染前决定结果,动态路由可以满足代码块的按需加载,对于大型在线应用非常有帮助。

然而,毕竟路由对于应用程序的架构来说是非常重要的。这么大的变化太激进了,会改变一些开发者习惯的模式。由于本次更新过于激进,因此受到了开发者的负面影响。反馈:

这给我们带来了动态路由的缺点:

  • 不够直观,不能从顶层知道程序中的所有路由,一层一层应用,最后看不出来是什么,可读性很差
  • 测试很困难。组件中混有路由逻辑,针对组件的单元测试(功能层面)根本不需要知道路由的存在,但是现在必须要考虑

由于 React-router 团队保证 v3 会持续维护,所以当时很多开发者并没有选择升级。

反应路由器5:跟随

只计划发布 React Router 4.4,但不小心被误用 ^ 字符,依赖被错误地写为 “反应路由器”:“^4.3.1” ,导致错误。所以最终团队决定撤销 4.4 版本,直接发布 React Router v5。

react-router5 延续了动态路由模型,但提供了更直观的写法:

 导出默认函数 App() {  
 返回 (  
 <路由器>  
 < 开关 >  
 < 路由路径 = "/about" >  
 < 关于 />  
 </ Route >  
 < 路由路径 = "/topics" >  
 < 话题 />  
 </ Route >  
 < 路由路径 = "/" >  
 < 首页 />  
 </ Route >  
 </ Switch >  
 </ Router >  
 );  
 }  
 复制代码

以上写法, /关于 节目 <About> 成分, /话题 节目 <Topic> 组件,根路由展示 <Home> 成分。

同时,v5 还允许你将路由配置写成 config json 数据,并在组件外部导入。

<Route> 会作为匹配路由的父组件,还有一系列辅助组件,比如 <Switch> 子元素可以限制为单个路由匹配。当然,这也带来了一定的

@reach/router:简洁

到达路由器 是 向前 一组由 ReactRouter 成员 Ryan Florence 开发的基于反应的路由控制。

那么已经有一个比较成熟的ReactRouter了,为什么还要“重新”做一个路由器呢?

  • 可访问性“易用性”
  • 相对链接的跳转方法
  • 嵌套路由配置
  • 适当的路径优先级(顺序无关紧要)等。

优点:小而简单

  • 4kb,压缩比 反应路由器 更小约 40kb,同时配置更少
  • 比 react-router 需要 3 个包( 历史 , 反应路由器dom , 反应路由器-redux ), 到达路由器 只需要一个
  • 不需要在 店铺 配置 路由器 相关信息
  • 不需要展示的用途 历史
  • API基本一样,学习成本很低
  • 源码很简单,一共3个文件,900行

react-router6:终极解决方案

2021年11月,react-router 6.0.0正式版发布:

  • 全部用 ts 重写
  • 不要以'/'开头,都是“相对路径”
  • 路由根据最佳匹配选择,可以嵌套或分散

v6的设计可以说很大程度上是基于@reach/router,API与@reach/router v1.3非常相似。所以官方也声称v6可以看成是@reach/router的v2。

总的来说,v6更像是对以前版本的完善和整​​合,有相对路径和嵌套和分散的选择,让大家可以根据个人喜好来构建路径。

源代码

在讨论了设计理念和版本变化之后,我们正式进入了从0到1的源码学习。

本文对源码的讨论基于v6(中间有各种简化)。

让我们从一个简单的 V6 示例开始:

 从“react-dom”导入{渲染};  
 进口 {  
 浏览器路由器,  
 路线,  
 路线,  
 关联,  
 } 来自“react-router-dom”;  
 从“./App”导入应用程序;  
 从“./routes/expenses”导入费用;  
 从“./routes/invoices”导入发票;  
  
 常量根元素 = 文档。 getElementById("根");  
 使成为(  
 <浏览器路由器>  
 < 路线 >  
 <Route path = "/" element = { < App /> }> <Route path = "expenses" element = { < Expenses /> } /> <Route path = "invoices" element = { < Invoices /> } /></ Route >  
 </ Routes >  
 < 链接到 = "/invoices" > 检查发票</ Link >  
 </ BrowserRouter >,  
 根元素  
 );  
 复制代码

React-router的结构主要分为四个模块:

  • 历史:
  • 历史
  • “状态机”
  • 负责路由状态的管理和记录
  • 路由器:
  • <Router>
  • “路线管理器”
  • 负责从上到下路由数据
  • 路线:
  • <Route>
  • “路由端口”
  • 路由对应组件配置
  • 关联:
  • <Link /> , <Navigate />
  • “导航”
  • 负责导航的跳转链接

让我们分开讨论源代码的每个部分。

历史

每个 <Router> 将创建一个 历史 记录当前和历史路由位置的对象。

反应路由器使用 历史 该库充当路由历史状态的管理模块:

历史 该库允许您在 JavaScript 运行的任何地方轻松管理对话历史记录, 历史 对象抽象了环境之间的差异,并为您提供了最易于使用的 API 来管理历史堆栈、导航和维护会话之间的持久状态。 —— React 培训文档

这部分源码值得关注:

  1. 工厂函数 创建浏览器历史 等待
  2. 它们之间的代码差异很小,不同 路由器 只要 解析路径 输入参数不同。还有其他区别,例如 哈希历史 增加 哈希变化 事件监控等
  3. 限于篇幅,这里只讨论 创建浏览器历史
  4. 历史.push , 用于基本交换路由
  5. / 代替 / 向前 / 背部 类似,但是 是的 历史 堆栈更改的基础知识
  6. 历史.听
  7. 添加路由侦听器以在路由切换时接收最新更新 行动 地点 ,从而做出不同的判断, 浏览器路由器 通过 history.listen(setState) 监控路由的变化,从而管理所有的路由
  8. 历史.block
  9. 添加一个拦截器,它会阻止 和其他行为以及浏览器的前进和后退,防止离开当前页面。并且只要判断为 阻滞剂 ,则浏览器刷新、关闭等默认行为会同时被屏蔽。并且只要有 阻滞剂 ,将防止上述情况 听众 监控

创建浏览器历史

我们先看一下工厂函数:

工厂函数的目的是创建一个 历史 对象,之后 不听 所有都挂载在这个 API 的返回对象上。

  • 历史.听 :这在Router组件中用于监控路由变化

  • 历史.unlisten : 这个也用在Router组件中,是的 方法的返回值,用于在清理时取消监听

    导出函数 createBrowserHistory(
    选项:BrowserHistoryOptions = {}
    ): 浏览器历史 {
    // - - - - - - - - - - - - - - -第一部分 - - - - - - - - - ---------------
    const [索引,位置] = getIndexAndLocation();

    函数 getIndexAndLocation(): [数字,位置] {
    const { 路径名,搜索,哈希 } = 窗口 .location;
    常量状态 = 窗口 .history .state || {};
    返回 [ state.idx,只读({ 路径名, 搜索, 哈希, 状态: state.usr || null, key: state.key || 'default' }) ];
    }

    如果(索引==空){
    索引 = 0;
    window .history .replaceState({ ...window.history.state, idx: index }, '');
    }

    函数句柄流行(){
    const [nextIndex, nextLocation] = getIndexAndLocation();
    常量增量 = 索引 - 下一个索引;
    去(三角洲)
    }

    窗口 .addEventListener('popstate', handlePop);

    // ----------------------------------------第二部分------ --------------

    const listeners = createEvents ();
    常量拦截器 = createEvents ();

    函数创建事件(): 事件{
    让处理程序:F[] = [];

    返回 {
    获取长度(){
    返回处理程序 .length;
    },
    推(fn:F){
    处理程序 .push(fn);
    返回函数(){
    handlers = handlers .filter(handler => handler !== fn);
    };
    },
    呼叫(参数){
    处理程序 .forEach(fn => fn && fn(arg));
    }
    };
    }

    听众 .call({ action, location });
    拦截器 .call({ action, location, retry });

    // ----------------------------------------第三部分------ --------------

    常量历史:BrowserHistory = {
    获得行动(){
    返回动作;
    },
    获取位置(){
    返回位置;
    },
    创建Href,
    推,//焦点
    代替,
    去(增量:数字){
    窗口 .history .go(delta);
    },
    背部() {
    去(- 1);
    },
    向前() {
    去(1);
    },
    listen(listener) { // 焦点
    返回监听器 .push(listener);
    },
    block(blocker) { // 焦点
    const unblock = blockers .push(blocker);
    if (blockers.length === 1) {
    window .addEventListener('beforeunload', promptBeforeUnload);
    }
    返回函数(){
    解锁();
    如果(!blockers.length){
    window .removeEventListener('beforeunload', promptBeforeUnload);
    }
    };
    }
    };
    返回历史
    }
    复制代码

我们可以将源代码分为三部分:

  • 第 1 部分“初始化和绑定”
  • 经过 获取索引和位置 获取初始当前路径 指数 地点 ,初始索引为空,对应的history.state.idx为0。
  • 同时, 贸易流行 存在 窗户 监视器 网址 改变在 句柄状态 内触发。
  • 第二部分“发布与订阅”
  • 我们看到这部分是一个标准的发布-订阅模式:
  • 创建事件 是创造 听众 阻滞剂 一个工厂函数,它返回一个对象,通过 添加每个 听众 ,经过 称呼 通知每个 听众 , 在代码中调用 处理程序
  • 听众 经过 称呼 传入 行动 地点 , 使得每个 听众 可以在路由发生变化时接收,从而做出相应的判断
  • 阻滞剂 ,相比 听众 再来一张 重试 ,从而判断是否阻塞路由,如果没有,则需要调用该函数 重试
  • 第 3 部分“构建历史”
  • 我们可以看到我们得到了什么 历史 目的
  • 行动 代表最后修改的电流 地点 行动 , 流行音乐 / / 代替 等待
  • 行动 地点 两个属性都传递了修饰符 得到 得到,那么每次我们想得到,就可以通过 历史行动 或者 历史位置 .避免了只能获取第一次创建的值,每次调用函数都可以获取。
  • 创建Href 通过函数 地点 返回新的 链接 , 返回一个字符串 , 否则返回 路径名 + 搜索 + 哈希
  • 背部 向前 都通过 完成
  • 这里我们重点关注: , , 堵塞

历史.push

代替 非常相似,区别在于 代替 将当前放入历史堆栈 地点 更换一个新的,被取代的将不复存在,所以我们重点关注

 函数推送(到:到,状态?:状态){  
 常量 nextAction = Action.Push;  
 const nextLocation = getNextLocation(to, state);  
  
 函数getNextLocation(到:到,状态:状态=空):位置{  
 返回只读<Location>({  
 ...地点,  
 ...( typeof to === 'string' ? parsePath(to) : to),  
 状态,  
 键:创建键()  
 });  
 }  
  
 函数重试(){  
 推(到,状态);  
 }  
  
 if (allowTx(nextAction, nextLocation, retry)) { // blockers的限制  
  
 const [historyState, url] = getHistoryStateAndUrl(nextLocation, index + 1);  
  
 函数 getHistoryStateAndUrl(  
 下一个位置:位置,  
 索引号  
 ): [历史状态,字符串] {  
 返回 [  
 {  
 usr: nextLocation.state,  
 关键:下一个位置。钥匙,  
 idx:索引  
 },  
 createHref(下一个位置)  
 ];  
 }  
    
 window.history.pushState(historyState, '', url);  
  
 尝试 {  
 globalHistory.pushState(historyState, '', url);  
 } 捕捉(错误){  
 window.location.assign(url);  
 } // 用 try- catch的原因是因为ios限制了 100次pushState的调用, catch后只能选择刷新页面  
  
 applyTx(nextAction); // 调用listeners  
 }  
 }  
 复制代码
  • 允许发送 在下面 阻滞剂 将被提及,用于阻塞路由
  • 应用Tx 在下面 听众 会提到它是用来调用监听器的
  • 获取下一个位置
  • 路由未切换时,根据 历史.push 状态 (新的路径和状态)获得新的 地点
  • 如果它是一个字符串,它将通过 解析路径 解析对应的 路径名 , 搜索 , 哈希 (这三个都是可选的,不一定会出现在返回的对象中)
  • 获取HistoryStateAndUrl
  • 根据新 地点 获得新的 状态 网址
  • 因为 ,这里 指数 自然加一
  • 重拨 创建Href ,根据 地点 产生 网址
  • 最后一次通话 历史.pushState 页面跳转成功,此时切换路由。

历史监听器

 常量历史:HashHistory = {  
 // ...  
 听(听众){  
 返回监听器 .push(listener);  
 },  
 // ...  
 }  
  
 函数 applyTx(nextAction: Action) {  
 const [索引,位置] = getIndexAndLocation();  
 听众 .call({ action: nextAction, location });  
 }  
  
 function push(to: To, state?: State) { // 替换  
 // ...  
 if (allowTx(nextAction, nextLocation, retry)) {  
 // ...  
 应用Tx(下一个动作);  
 }  
 }  
  
 函数句柄流行(){  
 if (blockedPopTx) {  
 // ...  
 } 别的 {  
 // ...  
 如果(blockers.length){  
 // ...  
 } 别的 {  
 应用Tx(下一个动作);  
 }  
 }  
 }  
  
 function allowTx(action: Action, location: Location, retry: () => void): boolean {  
 返回 (  
 !blockers.length || (blockers.call({ action, location, retry }), false)  
 );  
 }  
 复制代码

历史.听 它是一个标准的发布-订阅模型,可以去 历史 添加在 听众 ,它返回一个取消监听器的可调用方法

  • 听众 存在 , 代替 贸易流行 三个函数中路由切换成功后调用
  • 每当路由切换成功时,都会调用 applyTx(下一个动作) 通知每个 听众
  • 允许发送 的作用是判断是否允许路由切换,有 阻滞剂 是不允许的,也就是说, 听众 能否监听到路由变化取决于当前页面是否为 阻滞剂 被封锁

历史.block

 常量历史:BrowserHistory = {  
 // ...  
 块(拦截器){  
 const unblock = blockers .push(blocker);  
  
 if (blockers.length === 1) {  
 window .addEventListener('beforeunload', promptBeforeUnload);  
 }  
  
 返回函数(){  
 解锁();  
 如果(!blockers.length){  
 window .removeEventListener('beforeunload', promptBeforeUnload);  
 }  
 };  
 }  
 };  
 复制代码

阻滞剂 听众 类似的,区别在于:

  • 添加第一个 阻滞剂 将在何时添加 卸载前 事件
  • 要是 堵塞 ,然后我们刷新并关闭页面,通过修改地址栏进入 网址 背部 进入 会触发
  • 删除时发现 阻滞剂 清空,然后删除 卸载前 事件

路由器

应用顶层使用,供后代使用 路线 供应 语境 数据传输。

路由器 有很多,区别在于路由在url上的存在方式:

  • 浏览器路由器 “完整路由”,路由路径与url完全对应,需要服务器支持
  • 哈希路由器 “哈希路由”,路径在url中 # 后部
  • 静态路由器 “静态路由”,无状态:不改变路径地址,不记录历史栈

并且 内存路由器 (存储在内存中), 本机路由器 (存在 反应原生 用于)等,他们使用 历史 状态机也不同。

浏览器路由器

限于篇幅,这里我们主要讨论最常见的 浏览器路由器

  • 利用 浏览器历史
  • 需要服务器支持
  • 原因:如果只给用户提供cdn静态html文件,强制刷新或通过“复杂路径”访问时,找不到路径下的匹配资源
  • 为了 浏览器路由器 应用,服务端渲染完成后,后续的路由由 浏览器路由器 独立完成分析
  • 将相关路径转发到静态文件。静态文件执行后会读取当前浏览器路径并正确渲染对应的组件

作为应用程序最外层的容器组件, 浏览器路由器 源代码如下:

 导出函数 BrowserRouter({  
 基本名称,  
 孩子们,  
 窗户  
 }) {  
 常量历史 = useRef<BrowserHistory> ();  
 if (historyRef.current == null) {  
 historyRef.current = createBrowserHistory({ window }) ;  
 }  
  
 const history = historyRef.current ;  
 const [状态,setState] = useState({  
 动作:history.action,  
 位置:history.location  
 }) ;  
  
 使用布局效果(()=> {  
 history.listen(setState)  
 }, [历史]) ;  
  
 返回 (  
 <Router  
 基本名称={基本名称}  
 孩子={孩子}  
 动作={state.action}  
 位置={state.location}  
 导航器={历史}  
 />  
 ) ;  
 }  
 复制代码

可以看出是一个build 历史 <Router> 组件包

  • 路由器 初始化会产生 历史 实例, 历史 一般变化是 行动 地点 , 并放 设置状态 放入对应的 听众 ,那么路由交换机将 设置状态 .
  • 路由器 它接收到的属性的变化是路由相关的变化( 行动 , 地点 ),这部分路由存储在 语境 .作为消费者,子组件可以修改页面、跳转、获取这些值。

让我们来看看 路由器

 导出函数 Router( { action = Action.Pop, basename: basenameProp = "/" , children = null , location: locationProp, navigator, static : staticProp = false }: RouterProps):反应。反应元素 |无效的 {  
  
 // ...  
  
 返回 (  
 < NavigationContext.Provider 值 = {navigationContext} >  
 < LocationContext.Provider  
 孩子 = {孩子}  
 价值 = {{ 行动 , 位置 }} />  
 </ NavigationContext.Provider >  
 );  
 }  
  
 const { basename, navigator } = 反应。使用上下文(导航上下文);  
 常量 { 位置 } = 反应。使用上下文(位置上下文);  
  
 导出函数 useLocation(): Location {  
 返回反应。使用上下文(位置上下文)。地点;  
 }  
 复制代码

路由器 终于回了两个 上下文提供者 , 中间为 地点 加工

路由“路由端口”

我们直接看 路线 路线 的源代码:

 导出函数 Routes( { children, location }: RoutesProps): React。反应元素 |无效的 {  
 返回 useRoutes(createRoutesFromChildren(children), location);  
 }  
  
 导出函数 Route(_props: PathRouteProps | LayoutRouteProps | IndexRouteProps): React.反应元素 |无效的 {  
  
 不变的(  
 错误的,  
 `一个<Route>只能被用作<Routes>元素,` +  
 `从不直接渲染。请把你的<Route>在一个<Routes>.`  
 );  
 }  
 复制代码

可以找到

  • 路线 实际上 使用路线 包的
  • 路线 实际上没有 使成为 , 正如 路线 存在的子组件

我们只需要专注于研究 createRoutesFromChildren 使用路线

 导出函数 createRoutesFromChildren(  
 孩子:React.ReactNode  
 ): 路由对象 [] {  
 常量路线:RouteObject [] = [] ;  
 React.Children.forEach(children, element => {  
 if (!React.isValidElement(element)) return ;  
  
 if ( element.type === React.Fragment) {  
 路线.push.apply(  
 路线,  
 createRoutesFromChildren(element.props.children)  
 ) ;  
 返回 ;  
 }  
  
 常量路线:RouteObject = {  
 caseSensitive:element.props.caseSensitive,  
 元素:element.props.element,  
 索引:element.props.index,  
 路径:element.props.path  
 } ;  
  
 如果(元素.props.children){  
 route.children = createRoutesFromChildren(element.props.children) ;  
 }  
  
 routes.push(route) ;  
 }) ;  
 回程路线;  
 }  
 复制代码

我们看到, createRoutesFromChildren 效果如下:

  • 递归收集子元素 路线 属性 on,最终返回一个嵌套数组

  • 支持 反应片段

  • 创造 路线 路由配置

    导出函数 useRoutes(
    路线:RouteObject [],
    ): React.ReactElement |无效的 {
    // --------------------------------第一段------------------------------------

    const { 匹配:parentMatches } = React.useContext(RouteContext) ;
    常量 routeMatch = parentMatches[parentMatches.length - 1] ;
    常量父参数 = 路由匹配? routeMatch.params : {} ;
    常量 parentPathnameBase = 路由匹配? routeMatch.pathnameBase : "/" ;

    // --------------------------------第二段------------------------------------

    让位置 = 使用位置();
    常量路径名 = 位置.路径名 || "/" ;
    const 剩余路径名 =
    parentPathnameBase === "/"
    ?路径名
    : 路径名.slice(parentPathnameBase.length) || "/" ;

    常量匹配= matchRoutes(路由,{路径名:剩余路径名});

    // --------------------------------第三段-------------------------------------

    返回 _renderMatches(
    火柴 &&
    比赛地图(比赛=>
    Object.assign({}, 匹配, {
    参数:Object.assign({}, parentParams, match.params),
    路径名:joinPaths([parentPathnameBase, match.pathname]),
    pathnameBase: joinPaths([parentPathnameBase, match.pathnameBase])
    })
    ),
    父匹配
    ) ;
    }
    复制代码

使用路线 参数 路线 嵌套数组是 createRoutesFromChildren 返回的路由配置是通过将路由配置与相应的路由元素进行匹配来呈现的:

  • 第一段:得到 父匹配 最后一项“routeMatch”
  • 路线 中间,最后一次 使用路线 匹配后得到 火柴 将是下一层 父匹配 , 如果匹配,则获取匹配 参数 , 路径名 和其他信息
  • 第二段:通过当前Routes的相对路径 剩余路径名 路线 匹配到对应的 火柴
  • 这里最复杂的部分,也是react-router最本质的部分,就是匹配路由,这部分的逻辑在 匹配路由 优越的:
  • 导出函数matchRoutes(路线:RouteObject [],locationArg:部分<Location>|字符串,basename = "/" ): RouteMatch [] | null { const location = typeof locationArg === "string" ?parsePath(locationArg) : locationArg ; const pathname = stripBasename(location.pathname || "/" , basename) ; if ( 路径名 == null) { return null ; } const 分支 = flattenRoutes(routes) ; rankRouteBranches(分支) ;让匹配 = null ; for (let i = 0 ; 匹配 == null && i <brans.length; ++i) { 匹配 = matchRouteBranch(branches[i], pathname) ; } 返回匹配项; } 复制代码
  • 匹配路由 作用是将当前的相对路径和路由配置匹配到对应的 火柴
  • 路线 可能是多维路由配置,那么在扁平化过程中,每条路由的属性会被收集为 路由元 ,收集过程是深度优先遍历, 路线元 长度等于路由嵌套本身的级别
  • 对扁平化后的路由进行排序,根据权重对每个分支进行排序,比较权重是否相等 路线元 每个自重
  • 直到 火柴 有值(意思是匹配的,自然不用再找了)或者遍历完就跳出循环
  • 匹配路由分支 通过每个部分 路线元 ,看对应的路由能不能从头到尾匹配,只要有不匹配的,返回null
  • 路线元 最后一项是这条路由的路由信息​​,前面几项是 父元
  • 第三段:通过 _renderMatches 渲染上面得到的匹配元素
  • 最后得到“Route Matching Element”匹配,然后就需要根据匹配进行渲染了。
  • 函数_renderMatches(匹配:RouteMatch[] | null,parentMatches:RouteMatch[] = []):反应。反应元素 | null { if (matches == null ) return null ;返回匹配项。 reduceRight ( ( _, match, index ) => { return ( < RouteContext.Provider children = {match.route.element} value = {{ outlet , matches: parentMatches.concat (matches.slice ( 0 , index + 1 )) }} /> ); }, null as React . ReactElement | null ); } 复制代码
  • _renderMatches 将根据 match 和 parent 匹配元素 父匹配
  • 从右到左,从子元素到父元素,渲染 RouteContext.Provider

Link、Switch等“导航”

  • 关联 组件的作用是实现跳转
  • 直接使用通用 一个 标签将刷新页面,因此您需要使用 历史
  • 历史.pushState 只会改变 历史 状态,不会刷新页面
  • 历史.pushState , 不触发 流行状态 事件,所以 历史 用户使用时不会自动调用里面的回调 历史.push 当我们需要手动调用回调函数时

让我们看一下源代码:

 导出默认函数 Link({  
 至,  
 ...休息  
 }) {  
 返回 (  
 <RouterContext.Consumer>  
 { 上下文 => {  
 常量 { 历史 } = 上下文;  
 常量道具 = {  
 ...休息,  
 href: 到,  
 onClick: 事件 => {  
 event.preventDefault() ;  
 history.push(to) ;  
 }  
 } ;  
  
 返回<a {...props} />;</a>  
 }}  
 </RouterContext.Consumer>  
 ) ;  
 }  
 复制代码

我们看到, <Link> 只渲染一个没有默认行为的 一个 标签,其跳跃行为由下式给出 语境 传入 历史.push 完成。

未来:混音

Remix 是由 react-router 原团队打造的 ts 全栈明星开发框架,已获得 300 万美元融资。笔者认为remix是一个值得关注的全新全栈解决方案,其路由功能非常灵活高效。

“我们经常将 Remix 描述为“React Router 的编译器”,因为 Remix 的一切都利用了嵌套路由。”

官网对remix的介绍如下:

  • 编译器
  • 带有 HTTP 处理程序的服务器
  • 服务器端框架
  • 浏览器端框架

Remix可以杀掉骨架屏等加载状态,所有资源都可以预加载,管理后台对于数据加载、嵌套数据或组件路由、并发加载优化、异常处理都可以精准定位水平:

Remix 告别瀑布式获取数据的方式,在服务端并行获取数据,生成完整的 HTML 文档,类似 React 的并发特性:

相比之下,Next.js 更像是一个静态站点生成器。相比之下,Gatsby 的门槛很高,需要一定的 GraphQL 基础。

同时,客户端和服务端可以有一致的开发体验。客户端代码和服务端代码写在一个文件中,数据交互无缝。同时,基于TypeScript,类型定义可以跨客户端和服务器共享,路由也可以同步,实现组件化、路由主导的全栈模型。

结尾

我们看到,随着Web技术思维的转变,最早的渐进式应用正在朝着更强的融合方向发展,大前端和泛前端的思维本质越来越强。

服务器端技术已通过云技术转移到 SaaS。容器化是一种更灵活、成本更低的路径,旨在为应用端提供更便捷的开发。

在未来的 Web3 浪潮中,由于公链的存在,“胖协议+瘦应用”将是大势所趋,越来越敏捷、低成本的开发将更加重要。

路由作为前后端交互最紧密的桥梁,将是一个关键的变革领域。或许有一天我们可以看到Web技术通过路由实现真正的前后端统一,走向人人可开发的大栈。未来。

- 结尾 -

关于齐舞团

齐舞团是360集团最大的前端团队,代表集团参与W3C和ECMA成员(TC39)的工作。齐舞团非常重视人才培养。有工程师、讲师、翻译、业务接口人员、团队负责人等多种发展方向供员工选择,并辅以相应的技术、专业、通识、领导力等培训课程。齐舞团以开放、有才华的心态欢迎各类优秀人才关注并加入齐舞团。

​​​

版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议。转载请附上原文出处链接和本声明。

这篇文章的链接: https://homecpp.art/0029/11441/2124

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明

本文链接:https://www.qanswer.top/40090/51083021

posted @ 2022-09-30 21:52  哈哈哈来了啊啊啊  阅读(108)  评论(0编辑  收藏  举报