You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
constSettings=({ match })=>{return(// ...)}// 直接传入一个组件, 组件参数包括 match 等路由参数<Routepath="/settings"exactcomponent={Settings}/>// 传入一个回调函数, 可自定义渲染内容, 回调函数参数包括 match 等路由参数<Routepath="/settings"exactrender={(props)=>{return<Settingsauthed={isAuthed}{...props}/>;}}/>
最初的实现如下:
classRouteextendsReact.Component{constructor(props){super(props)}render(){const{
path,
exact,
component,
render,}=this.propsconstmatch=matchPath(window.location.pathname,{ path, exact })if(match){if(component){returnReact.createElement(component,{ match });}if(render){returnrender({ match });}}returnnull;}}Route.propTypes={path: PropTypes.string,exact: PropTypes.bool,component: PropTypes.func,render: PropTypes.func,}
几个点需要注意:
path 属性不是必须的, 根据官网定义: Routes without a path always match. 即当不指定 path 时, 默认直接匹配当前浏览器 url, 那么给定的组件一定会被渲染
constmatchPath=(pathname,options)=>{const{ exact =false, path }=options;if(!path){// if a Route isn’t given a path, it will automatically be renderedreturn{path: null,url: pathname,isExact: true};}constmatch=newRegExp(`^${path}`).exec(pathname);if(!match){returnnull;}consturl=match[0];constisExact=pathname===url;if(exact&&!isExact){// There was a match, but it wasn't// an exact match as specified by// the exact prop.returnnull;}return{
path,
url,
isExact
};};
之前提到的改变浏览器 url 可以有 4 种方法. 前两种已经描述用于 Route 组件, Link 组件则适用于后两种方法. 本质上 Link 组件可以看成是 <a> 标签的扩展, 只不过关于路由的跳转不再使用默认浏览器行为, 而是使用 history.push(replace)State 方法
Link 组件使用大致如下:
<Linkto="/some-path"replace={false}/>
两个属性:
to 属性表示将要跳转的路径
replace 属性表明当前的跳转是要当做 history 的添加, 也就是可以前进后退. 还是表示当前路由记录被取代. 默认为 false
完整实现如下:
classLinkextendsReact.Component{constructor(props){super(props);}handleClick=e=>{e.preventDefault();const{ replace, to }=this.props;if(replace){historyReplace(to);}else{historyPush(to);}};render(){const{ to, children }=this.props;return(<ahref={to}onClick={this.handleClick}>{children}</a>);}}Link.propTypes={to: PropTypes.string.isRequired,replace: PropTypes.bool};
React Router v6 其实已经计划在开发中了, 这篇文章只是通过实现一个简单的 v4 大致了解一下路由的基本概念.
需求与实现效果
需要实现三个基本组件:
Route
Link
Redirect
最后的效果如下:
需要实现的有三个基本页面:
/
, 单纯渲染主页面/about
, 该页面 1.5s 后重定向到主页面/topics
, 该页面包含三个子路由, 对应三个子页面, 分别为:/topics/react
,topics/vue
,topics/angular
最后,
App
组件的列表导航栏能直接定位到各自路由渲染对应内容, 当然在浏览器内直接输入路径也是可以的. 通过点击浏览器的回退/前进按钮进行路由导航也是可行的源码在此: https://stackblitz.com/edit/react-router-implement
v4 的基本理念
与 v3 不同, v4 不在对路由进行集中式管理(虽然理论上还是可以做到). 整个路由系统更强调一切都是组件. 比较核心的两个组件
Route
和Link
, 定义可以理解为如下:Route
: 根据给定的path
属性和浏览器当前的路径(url)是否匹配决定渲染内容, 即根据路由渲染 UILink
: 通过该组件改变当前浏览器路径(url)关于 React Router v4 和 v5 的设计哲学和理念, 可参考这两篇文章:
实现
测试页面
页面组件与路由配置如下:
Route
根据之前的定义,
Route
组件是根据path
和url
的匹配情况来决定是否渲染对应内容, 即如果匹配我们渲染 UI, 不匹配, 我们返回null
. 同时在 v4 中,Route
组件通过接受component
或者render
回调函数来渲染需要的 UI. 两者的区别仅在于若需要传入其他props
时用render
回调函数比较好, 否则直接传入一个组件即可. 用法大致如下:最初的实现如下:
几个点需要注意:
path
属性不是必须的, 根据官网定义: Routes without a path always match. 即当不指定path
时, 默认直接匹配当前浏览器url
, 那么给定的组件一定会被渲染matchPath
是一个外部函数, 下面会实现, 用于检查Route
组件的path
属性和当前浏览器的url
是否匹配以及具体匹配情况至此已经完成了基本框架, 即对于当前的
url
和给定的path
是否匹配, 匹配渲染 UI, 否则不做任何事情前端路由与事件
对于前端路由, 一般有 4 种方式可以改变浏览器地址, 不包括
hash
值<a>
标签进行地址跳转history.push(replace)State
函数对于目前的
Route
组件来说, 是需要感知到url
的变化来进行比较然后渲染 UI. 第一种情况其实不用考虑, 每次用户输入一遍url
之后组件都被重新mount
, 所有逻辑都会走一遍意味着匹配包括渲染等都会被执行. 现在考虑第二种情况. 浏览器提供了onpopstate
监听浏览器前进与后退. 不过需要注意的是, 调用history.push(replace)State
并不会触发onpopstate
事件.修改一下
Route
代码:这里在
mount
的时候监听onpopstate
事件, 回调函数作用是让组件重新渲染一次. 在unmount
的时候移除监听器. 这样就实现了点击浏览器前进后退按钮后, 能够重新根据变化的url
渲染 UI.路由的匹配
实现
matchPath
之前, 先考虑exact
属性. v4 中的匹配可以存在"模糊匹配"和"精确匹配". 声明exact
属性表示需要精确匹配, 即path
属性和window.location.pathname
完全一样. 例如:注意当
exact
未被声明即是false
的时候, 即使当前的url
是/one/two
,path
声明的是/one
, 也算是匹配成功. 因此对于匹配规则, 可以单纯的认为只要从开头包含部分即可.matchPath
最后返回一个match
对象, 包含 3 个属性:isExact
: 是否是精确匹配path
:Route
组件给定的路径url
: 和window.location.pathname
匹配后匹配的部分以该 demo 为例, 假设当前浏览器的路径为
/topics/react
,url
即匹配的部分为:matchPath
的完整实现如下:这里注意两个点:
path
, 默认为完全匹配, 也就是匹配成功.url
就是当前的浏览器地址exact
属性, 但是得到的结果并不是完全匹配, 也就是isExact
是false
. 说明还是匹配不成功. 直接返回nunll
Link
之前提到的改变浏览器
url
可以有 4 种方法. 前两种已经描述用于Route
组件,Link
组件则适用于后两种方法. 本质上Link
组件可以看成是<a>
标签的扩展, 只不过关于路由的跳转不再使用默认浏览器行为, 而是使用history.push(replace)State
方法Link
组件使用大致如下:两个属性:
to
属性表示将要跳转的路径replace
属性表明当前的跳转是要当做history
的添加, 也就是可以前进后退. 还是表示当前路由记录被取代. 默认为false
完整实现如下:
这里需要实现两个方法
historyPush
和historyReplace
, 是我们根据history.pushState
和history.replaceState
自定义的工具函数.至此会发现其实存在一个问题. 测试页面上点击
Link
组件虽然更新了浏览器地址, 但是组件却没有对应的进行更新. 这是因为调用history.push(replace)State
并不会触发onpopstate
事件. 为了解决这一问题需要手动在触发history.push(replace)State
时候对Route
进行渲染. 具体做法如下:维护一个数组, 该数组存放已经渲染过的
Route
组件. 当Route
组件mount
之后就将其注册进数组中, 对应提供注册和取消注册两个函数:随后在
Route
组件里, 当mount
之后就将本身注册进数组最后更新
historyPush
和historyReplace
函数, 当触发时手动更新所有注册过的Route
, 让Route
里的逻辑重新跑一遍也因此能重新渲染更新 UI至此, 当
Link
改变浏览器路径之后,Route
组件能够识别到路径的变化然后进行重新匹配并渲染对应的组件Redirect
Redirect
组件和Link
组件很相似, 唯一不同的是Redirect
不渲染任何 UI, 纯粹用于改变浏览器地址. 而Link
类似于<a>
会简单渲染一段文本, 实现如下:Hash
以上均是针对
history
路由, 相比而言hash
路由基本原理也是类似, 而且更简单的在于Link
组件不需要过多的处理只需要添加#
,Route
组件能直接通过onhashchange
识别到路由的变化前端路由
不管是基于
hash
还是history
的路由, 均是由前端来控制. 这样的好处是对于单页面应用不再需要刷新. 但是弊端也有. 比如用户进行多次跳转之后一不小心刷新了页面, 那么又会回到最开始的状态, 用户体验较差参考
The text was updated successfully, but these errors were encountered: