前端路由原理
前端三大框架 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 事件)呢?
- 通过 a 标签,并设置 href 属性,当用户点击这个标签后,URL 就会发生改变,也就会触发 hashchange 事件了:
<a href="#search">search</a>
- 直接使用 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>
路由匹配器
- 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"
}
}
- 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 页面的内容
导航
- 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 时,表示用新地址替换掉上一次访问的地址;
- NavLink
这是 Link 的特殊版,顾名思义这就是为页面导航准备的,因为导航需要有 “激活状态”。除Link的固定参数外,还有如下可选参数:
- activeClassName: string,导航选中激活时候应用的样式名
- activeStyle: object,如果不想使用样式名就直接写 style,
- exact: bool,若为 true,只有当访问地址严格匹配时激活样式才会应用
- strict: bool,若为 true,只有当访问地址后缀斜杠严格匹配(有或无)时激活样式才会应用
- isActive: func,决定导航是否激活,或者在导航激活时候做点别的事情。不管怎样,它不能决定对应页面是否可以渲染。
- 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);
参数传递
传递参数有三种方式:
- 动态路由的方式
假如/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"}
- 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}
- 隐式传参
所传递的参数不可见
//数据定义
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 路由