分享个人 Full-Stack JavaScript 项目开发经验
在单页应用中,由于内容载入和 UI 修改都是通过 JavaScript 完成的,所以它可以避免页面重载,提高用户的使用体验。但如果没有路由转发解决方案,浏览器的历史纪录、书签、前进和后退等功能就无法正常工作,无法找回特定端点的请求内容。对于 React 应用程序, React Router 作为路由转发解决方案被广泛使用。下面介绍React Router v4.3.1的应用例子。
React Router 提供五种高级路由。基于 DOM 创建的路由有:浏览器端使用的 HashRouter 和 BrowserRouter;服务器端渲染可以使用 StaticRouter。
安装 react-router-dom:
yarn add react-router-dom
哈希路由会在每个路由路径前添加井号(#)。一般来说,地址栏上的 # 用于定义锚点链接,不会向服务器发送请求。哈希路由可以理解为仅在浏览器端作用的路由。React Router 会根据 # 后的路由路径渲染指定的 React 组件。
由于哈希路由会占用了 URL 哈希字符串的一部分,若果在 #/clear 路由下,想使用浏览器锚点跳转到 #jump 的位置,则 id 就需要写成 #/clear#jump。
本博客的后台管理系统使用的就是哈希路由。
React Router 提供了 RESTful 风格的浏览器路由支持,同时允许使用查询字符串。它可配合服务器端渲染,实现同构性。使用 React Router 提供的链接组件,可以实现跟哈希路由一样的用户体验。当浏览器重新载入该路径并向服务器端发送一个 GET 请求时(如用户点击收藏的书签),服务器端可以获取相应的 URL 参数,并基于请求地址渲染路由组件 HTML,最后返回给客户端。由于组件预先在服务器端渲染,所以可以支持 SEO。
静态路由不会去改变 location,它用于服务器端渲染,根据请求地址或重写过的请求地址渲染路由组件。
本博客的前台使用的就是浏览器路由/静态路由的同构性实现。
但在代码编写上,这两种高级路由组件的用法基本相同。
首先把 HashRouter/BrowserRouter 作为应用程序的根组件渲染,若果项目使用了 react-redux 则需再使用 Provider 组件包裹。
import React from "react";
import {render} from "react-dom";
import {HashRouter} from "react-router-dom";
import {Provider} from "react-redux";
import App from "./App";
import configureStore from "./store";
const store = configureStore();
render(
<Provider store={store}>
<HashRouter>
<App/>
</HashRouter>
</Provider>
, document.getElementById('react-container')
);
可以在 APP 组件的一个用于渲染路由组件的 div 内使用 Route 组件,并定义每个路径匹配规则和匹配时要渲染的组件。其中的 Switch 组件使其只会显示首个匹配的路由,如果没有任何路径匹配,则显示最后一个没有定义 path 的404组件。
import React from "react";
import {Route, Switch} from "react-router-dom";
const App = () => (
<div id="app">
<OtherComponent/>
<div className="router-container">
<Switch>
<Route exact path="/" component={WebsiteMessageContainer}/>
<Route exact path="/clear" component={ClearContainer}/>
<Route exact path="/write/:article_id?" component={WriteContainer}/>
<Route exact path="/404" component={Whoop404}/>
<Route component={Whoop404}/>
</Switch>
</div>
</div>
);
export default App;
Route 组件的属性说明如下:
exact
该属性表示只有精准匹配时才会显示对应组件。如果不添加属性,则 /clear/123 这样的路径也会匹配 path="/clear",并显示 ClearContainer 组件。
path
设置匹配的路由路径。要了解更多的路由路径匹配语法,请点击这里。
component
该属性用于传入路由匹配后要渲染的 React 组件,一般为容器组件。
<Route> 组件会将路由参数属性传递给它渲染的组件。
可以注意到上面的路径 /write/:article_id?,它表示该路径有一个可选的参数段 article_id。它可以通过 props.match.params.article_id 在对应的容器组件连接的 UI 组件中获取。React Router 还会传递 props.history 和 props.location 等属性,帮助组件获取更多路由相关信息。下面列出一些常用的路由属性:
match.params
【Object】用于获取路由参数段的参数值。
match.isExact
【Boolean】表示路由路径是否完全匹配。如果 Route 组件使用了 exact 属性,则它始终为 true。
match.path
【String】获取 Route 组件定义的 path 的值。
match.url
【String】获取 path 值所匹配的 url 结果。
location.hash
【String】路由路径的哈希字符串部分。
location.pathname
【String】获取实际的 url。
location.search
【String】获取路径的查询字符串部分。
location.state
【Object】获取保存在当前 location 中的 state。
history
【Object】封装的 history 对象。你可以使用 history.push、history.replace 等方法修改 history 记录,还可以使用 history.listen 监听记录变化。在 history.action 中还给出了路由改变的操作类型,PUSH 为点击路由链接或调用 push 方法,POP 为浏览器的前进后退,REPLACE 为调用 replace 方法。下面是一个 history.push 的使用例子:
this.props.history.push({
pathname: '/search.html',
search: `keyword=${encodeURIComponent(keyword)}&page=${page}`,
state: {keyword, page}
});
对于同一组件,当路由参数发生变化时,可以在 componentDidUpdate 生命周期内检查前后 props 的路由属性来更新组件到对应状态。
我们不需要在地址栏手动输入地址才能实现页面导航,React Router 提供了 Link 和 NavLink 组件,让用户可以通过点击链接直接访问任意内部页面,而无需重载整个网页。
下面是 Link 组件的使用例子:
import {Link} from 'react-router-dom';
export const Example = () =>
<nav>
<Link to={{
pathname: '/clear',
search: '?query=1&name=2',
hash: '#the-hash',
state: { page: 1 }
}}
>导航链接</Link>
</nav>
其中 to 属性接收的对象字段与 props.location 中的对应,也可以简单地传入一个路径字符串:
<Link to="/clear">导航链接</Link>
NavLink 组件与 Link 组件用法基本相同,一般用于需要根据路由路径控制 a 链接样式的情况:
<NavLink
exact
to="/clear"
activeClassName={ ...}
activeStyle={...}
isActive={ (match, location) => {} }
>导航链接</NavLink>
其中 exact 属性用于设置当路径精确匹配时,才激活样式。isActive 回调函数用于具体判断激活样式的条件,下面是博客中的一个使用例子:
<NavLink className="blog-nav-item"
activeClassName="active"
isActive={(match, location) => {
return /^\/list_hot_[\d+].html$/.test(location.pathname);
}}
to="/list_hot_1.html"
title="最热文章"
>最热文章</NavLink>
为了实现同构性,我们需要对 App 组件作如下修改:
// ...
const App = ({store, url, context}) => (
(typeof document === 'undefined') ?
<Provider store={store}>
<StaticRouter location={url} context={context}>
<Root/>
</StaticRouter>
</Provider>
:
<Provider store={store}>
<BrowserRouter>
<Root/>
</BrowserRouter>
</Provider>
);
export {App};
koa2 的服务器端控制器响应示例如下:
const {renderToString} = require("react-dom/server");
const {buildHtmlPage} = require("../../../views/front/bulidHhtmlPage");
// ......
const context = {};
const bodyHtml = renderToString(App({store, url: ctx.url, context}));
// ......
if (context.url) {
ctx.redirect(context.url);
} else {
ctx.body = buildHtmlPage({
bodyHtml,
state: JSON.stringify(store.getState()).replace(/</g, '\\u003c')
});
}
一般传入客户端请求地址给 location 属性,作为路由组件的渲染地址。当请求地址被如反向代理服务器重写过的,应使用重写前的请求路径。context 对象用于接收匹配的 <Redirect> 组件的重定向路由地址。
以上就是 React Router 在 React 应用程序中的基本应用。
要了解浏览器原生 history API,请点击这里。