前端路由原理和React Router

前端路由原理

前端三大框架 Angular、React、Vue ,它们的路由解决方案 angular/router、react-router、vue-router 都是基于前端路由原理进行封装实现的,因此将前端路由原理进行了解和掌握是很有必要的。

路由的概念起源于服务端,在以前前后端不分离的时候,由后端来控制路由。但由于后端路由还存在着自己的不足,前端路由才有了发展空间。路由的映射函数通常是进行一些 DOM 的显示和隐藏操作,当访问不同的路径的时候,会显示不同的页面组件。前端路由主要有两种实现方案:Hash和History。

Hash模式

hash模式就是基于 location.hash 来实现的,原理很简单,location.hash 的值就是 URL 中 # 后面的内容。比如https://www.xxxxxx.com#search的 location.hash 的值为 '#search'

hash 有下面几个特性:

  • URL 中 hash 值只是客户端的一种状态,也就是说当向服务器端发出请求时,hash 部分不会被发送。
  • hash 值的改变,都会在浏览器的访问历史中增加一个记录。因此我们能通过浏览器的回退、前进按钮控制hash 的切换。
  • 我们可以使用 hashchange 事件来监听 hash 的变化。

那么如何来触发hash改变(hashchange 事件)呢?

  1. 通过 a 标签,并设置 href 属性,当用户点击这个标签后,URL 就会发生改变,也就会触发 hashchange 事件了:
<a href="#search">search</a>
  1. 直接使用 JavaScript来对 loaction.hash 进行赋值,从而改变 URL,触发 hashchange 事件:
location.hash="#search"

History模式

由于hash模式下使用时都需要加上 #,并不是很美观。到了 HTML5,又提供了 History API 来实现 URL 的变化。其中做最主要的 API 有以下两个:history.pushState()history.repalceState()。这两个 API可以在不进行刷新的情况下,操作浏览器的历史纪录。唯一不同的是,前者是新增一个历史记录,后者是直接替换当前的历史记录。代码格式如下:

window.history.pushState(null, null, path);
window.history.replaceState(null, null, path);

history 有下面几个特性:

  • pushState 和 repalceState 的标题(title):一般浏览器会忽略,最好传入 null ;
  • 我们可以使用 popstate 事件来监听 url 的变化;
  • history.pushState() 或 history.replaceState() 不会触发 popstate 事件,这时我们需要手动触发页面渲染;

在对前端路由原理有了基本的掌握后,就可以尝试去阅读 vue-router 和 react-router 的源码实现。下面来看下react-router。


React Router

React Router 中的组件主要分为三类:

  • 路由组件,例如 BrowserRouter和 HashRouter
  • 路由匹配器,例如 Route 和 Switch
  • 导航,例如 Link,NavLink 和 Redirect

下面逐一展开介绍:

路由组件

路由组件分为两种:BrowserRouter(对应前端路由history 模式) 和 HashRouter(对应前端路由hash 模式),用法一样,只是 url 展示不同,其中 hash 模式带有 # 号符。

BrowserRouter:http://localhost:3000/page2
HashRouter:http://localhost:3000/#/page2

// BrowserRouter:
<BrowserRouter>
    <Route path="/page1" exact component={Page1}></Route>
    <Route path="/page2/:id" exact component={Page2}></Route>
</BrowserRouter>
// HashRouter:
<HashRouter>
    <Route path="/page1" exact component={Page1}></Route>
    <Route path="/page2/:id" exact component={Page2}></Route>
</HashRouter>

路由匹配器

  1. Route
    Route 是用于声明路由映射到应用程序的组件层。
    路由Route的参数介绍如下:
  • path
    string 类型,用来指定路由跳转路径,根据 path 和 url 匹配到对应的页面;如下所示
// 路由配置
<Route path="/page1" exact component={Page1}></Route>
// 页面访问 http://localhost:3000/page1
  • exact
    boolean 类型,用来精确匹配路由,如果为 true 则精确匹配,否则为正常匹配;如下所示
// exact = true 时
<Route path="/page1" exact component={Page1}></Route>
// 浏览器通过 http://localhost:3000/page1/one 访问不到 page1 页面

// exact = fasle 时
<Route path="/page1" exact={false} component={Page1}></Route>
// 浏览器通过 http://localhost:3000/page1/one 可以访问到 page1 页面
  • sensitive
    boolean 类型,用来设置是否区分路由大小写,如果为 true 则区分大小写,否则不区分;如下所示
// sensitive = true 时
<Route path="/page1" sensitive component={Page1}></Route>
// 浏览器通过 http://localhost:3000/PAGE1 访问不到 page1 页面

// sensitive = fasle 时
<Route path="/page1" sensitive={false} component={Page1}></Route>
// 浏览器通过 http://localhost:3000/PAGE1 可以访问到 page1 页面
  • strict
    boolean 类型,对路径末尾斜杠的匹配。如果为 true,path 为 '/page1/' 将不能匹配 '/page1' 但可以匹配 '/page1/one'。;如下所示
// strict = true 时
<Route path="/page1/" strict component={Page1}></Route>
// 浏览器通过以下 url 访问不到 page1 页面
http://localhost:3000/page1
// 浏览器通过以下 url 可以访问到 page1 页面
http://localhost:3000/page1/one

// strict = fasle 时
<Route path="/page1/" strict={false} component={Page1}></Route>
// 浏览器通过以下 url 可以访问到 page1 页面
http://localhost:3000/page1
http://localhost:3000/page1/one
  • component
    设置路由对应渲染的组件,如下所示Page1为要渲染的组件
<Route path="/page1/" exact component={Page1}></Route>
  • render
    通过render函数返回路由对应渲染的组件或者dom,好处是不仅可以通过 render 方法传递 props 属性,并且可以传递自定义属性。如下所示
<Route path='/about' exact render={(props) => {
    return <Page1 {...props} name={'name1'} />
}}></Route>

class Page1 extends React.Component {
  componentDidMount() {
    console.log(this.props) 
    // this.props:
    // history: {length: 9, action: "POP", location: {…}, createHref: ƒ, push: ƒ, …}
    // location: {pathname: "/home", search: "", hash: "", state: undefined, key: "ad7bco"}
    // match: {path: "/home", url: "/home", isExact: true, params: {…}}
    // name: "name1"
  }
}
  1. Switch
    如果路由 Route 外部包裹 Switch 时,路由匹配到对应的组件后,就不会继续渲染其他组件了。但是如果外部不包裹 Switch 时,所有路由组件会先渲染一遍,然后选择所有匹配的路由进行显示。
// 当没有使用 Switch 时
<BrowserRouter>
  <Route path="/page1" component={Page1}></Route>
  <Route path="/" component={Page2}></Route>
  <Route path="/page3/:id" exact component={Page3}></Route>
</BrowserRouter>
// 当面访问 http://localhost:3000/page1 时,浏览器会同时显示 page1 和 page2 页面的内容 

// 当使用 Switch 时
<BrowserRouter>
  <Switch>
    <Route path="/page1" component={Page1}></Route>
    <Route path="/" component={Page2}></Route>
    <Route path="/page3/:id" exact component={Page3}></Route>
  </Switch>
</BrowserRouter>
// 当面访问 http://localhost:3000/page1 时,浏览器只会显示 page1 页面的内容 

导航

  1. Link
    用来指定路由跳转,Link的参数介绍如下:
  • to
    可以通过字符串执行路由跳转,也可以通过对象指定路由跳转,如下所示
// 通过字符串
<Link to='/page2'>
  <span>跳转到 page2</span>
</Link>  

// 通过对象
<Link to={{
    pathname: '/page2',
    search: '?name=name1',
    hash: '#someHash',
    state: { age: 11 }
  }}>
  <span>跳转到 page2</span>
</Link>

通过对象指定路由跳转时,各字段的含义如下:
pathname: 表示跳转的页面路由 path
search: 表示查询参数的字符串形式,即等同于 location 中的 search
hash: 放入网址的 hash,即等同于 location 中的 hash
state: 可以通过这个属性,向新的页面隐式传参,在上面的例子中page2 中可以通过 this.props.location.state 可以拿到 age: 11;

  • replace
    如果设置 replace 为 true 时,表示用新地址替换掉上一次访问的地址;
  1. NavLink
    这是 Link 的特殊版,顾名思义这就是为页面导航准备的,因为导航需要有 “激活状态”。除Link的固定参数外,还有如下可选参数:
  • activeClassName: string,导航选中激活时候应用的样式名
  • activeStyle: object,如果不想使用样式名就直接写 style,
  • exact: bool,若为 true,只有当访问地址严格匹配时激活样式才会应用
  • strict: bool,若为 true,只有当访问地址后缀斜杠严格匹配(有或无)时激活样式才会应用
  • isActive: func,决定导航是否激活,或者在导航激活时候做点别的事情。不管怎样,它不能决定对应页面是否可以渲染。
  1. Redirect
    Redirect用于路由的重定向,它会执行跳转到对应的to路径中,其中参数to的规则同Link一样。
<Redirect to='/' />

withRouter

withRouter 可以将一个非路由组件包裹为路由组件,使这个非路由组件也能访问到当前路由的 match, location, history对象。

class Component1 extends React.Component<any> {
  handleClick () {
    this.props.history.push('/page2');
  }
  render () {
    return <div onClick={() => this.handleClick()}>this is component1</div>;
  }
}

export default withRouter(Component1);

参数传递

传递参数有三种方式:

  1. 动态路由的方式
    假如/detail的路径对应 一个组件Detail,若将路径中的Route匹配时写成/detail/:id,那么/detail/123,/detail/xyz都可以匹配到该Route并进行显示
// 路由配置
<Route path="/detail/:id" exact component={Detail}></Route>
// 路由跳转
this.props.history.push('/detail/1000');
// 参数获取
this.props.match.params;  // {id: "1000"}
  1. search传递参数
    根据参数to的不同书写格式,search传参可以分为两种形式:
<Link to="/detail2?name=boge&age=20">详情2</Link>
<Link to={{ pathname: '/detail2', search: '?name=boge&age=20'}}>详情2</Link>

// 参数获取
this.props.location.state;  // {name:'boge', age:20}
  1. 隐式传参
    所传递的参数不可见
//数据定义
const data = {id:3,name:sam,age:36};
const path = {
    pathname: '/user',
    query: data,
}
this.props.history.push(path);

// 参数获取
this.props.location.query;   // {id:3,name:sam,age:36};

嵌套路由

嵌套路由就是在子页面中再设置一层新的路由导航规则。重点在于不能在父级加 exact(精准匹配),因为先要匹配父级然后才能匹配子集。

// appRouter.js
<BrowserRouter>
    <Route path="/" exact component={Index} />
    <Route path="/video/" component={Video} />
    <Route path="/workplace/" component={Workplace} />
</BrowserRouter>

// video.js
<div className="videoContent">
    <h3>视频教程</h3>
    <Route path="/video/react/" component={React} />
    <Route path="/video/vue/" component={Vue} />
</div>

为什么我能够看得更远,那是因为我站在巨人的肩上,以上内容来源:
深度剖析:前端路由原理
一文搞定 React 路由

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,311评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,339评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,671评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,252评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,253评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,031评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,340评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,973评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,466评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,937评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,039评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,701评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,254评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,259评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,485评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,497评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,786评论 2 345

推荐阅读更多精彩内容