[Web 前端] 你不知道的 React Router 4
cp from https://segmentfault.com/a/1190000010718620
几个月前,
React Router 4
发布,我能清晰地感觉到来自大量的修改
的不同声音。诚然,我在学习React Router 4
的第一天,也是非常痛苦
的,但是,这并不是因为看它的API
,而是反复思考使用它的模式
和策略
,因为V4
的变化确实有点大,V3
的功能它都有,除此之外,还增加了一些特性
,我不能直接将使用V3
的心得直接迁移过来,现在,我必须重新审视router
和layout components
之间的关系
本篇文章不是把 React Router 4
的 API
再次呈现给读者看,而是简单介绍其中最常用的几个概念,和重点讲解我在实践的过程中发现的比较好的 模式
和 策略
不过,在阅读下文之前,你得首先保证以下的 概念
对你来说 并不陌生
React stateless(Functional) 组件
ES6 的
箭头函数
和它的隐式返回
ES6 的
解构
ES6 的
模板字符串
如果你就是那 万中无一
的绝世高手,那么你也可以选择直接 view demo
一个全新的 API
React Router
的早期版本是將 router
和 layout components
分开,为了彻底搞清楚 V4
究竟有什么不同,我们来写两个简单的 example
就明白了
example app
就两个 routes
,一个 home
,一个 user
在 V3
中
import React from "react";
import { render } from "react-dom";
import { Router, Route, IndexRoute, Link, browserHistory } from "react-router"; const PrimaryLayout = props =>
<div className="primary-layout">
<header>Our React Router 3 App</header>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/user">User</Link>
</li>
</ul>
<main>
{props.children}
</main>
</div>; const HomePage = () => <h1>Home Page</h1>;
const UsersPage = () => <h1>User Page</h1>; const App = () =>
<Router history={browserHistory}>
<Route path="/" component={PrimaryLayout}>
<IndexRoute component={HomePage} />
<Route path="/user" component={UsersPage} />
</Route>
</Router>; render(<App />, document.getElementById("root"));
上篇文章给大家推荐了一个在线
react
编译器 stackblitz,本篇文章再给大家推荐一个不错的,codesandbox,专门针对react
且开源,正所谓,实践是检验真理的唯一标准
,这也是一种良好的学习习惯
上面代码中有几个关键的点在 V4
中就不复存在了
集中式
router
通过
<Route>
嵌套,实现Layout
和page 嵌套
Layout
和page 组件
是作为router
的一部分
我们使用 V4
来实现相同的应用程序对比一下
import React from "react";
import { render } from "react-dom";
import { BrowserRouter, Route, Link } from "react-router-dom"; const PrimaryLayout = () =>
<div className="primary-layout">
<header>Our React Router 4 App</header>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/User">User</Link>
</li>
</ul>
<main>
<Route path="/" exact component={HomePage} />
<Route path="/user" component={UsersPage} />
</main>
</div>; const HomePage = () => <h1>Home Page</h1>;
const UsersPage = () => <h1>User Page</h1>; const App = () =>
<BrowserRouter>
<PrimaryLayout />
</BrowserRouter>; render(<App />, document.getElementById("root"));
注意,我们现在
import
的是BrowserRouter
,而且是从react-router-dom
引入,而不是react-router
接下来,我们用肉眼就能看出很多的变化,首先,V3
中的 router
不在了,在 V3
中,我们是将整个庞大的 router
直接丢给 DOM
,而在 V4
中,除了 BrowserRouter
, 我们丢给 DOM
的是我们的应用程序本身
另外,V4
中,我们不再使用 {props.children}
来嵌套组件了,替代的 <Route>
,当 route
匹配时,子组件会被渲染到 <Route>
书写的地方
Inclusive Routing
在上面的 example
中,读者可能注意到 V4
中有 exact
这么一个 props
,那么,这个 props
有什么用呢? V3
中的 routing
规则是 exclusive
,意思就是最终只获取一个 route
,而 V4
中的 routes
默认是 inclusive
的,这就意味着多个 <Route>
可以同时匹配
和呈现
还是使用上面的 example
,如果我们调皮地删除 exact
这个 props
,那么我们在访问 /user
的时候,Home
和 User
两个 Page
都会被渲染,是不是一下就明白了
为了更好地理解
V4
的匹配逻辑,可以查看 path-to-regexp,就是它决定routes
是否匹配URL
为了演示 inclusive routing
的作用,我们新增一个 UserMenu
组件如下
const PrimaryLayout = () =>
<div className="primary-layout">
<header>
Our React Router 4 App
<Route path="/user" component={UsersMenu} />
</header>
<main>
<Route path="/" exact component={HomePage} />
<Route path="/user" component={UsersPage} />
</main>
</div>;
现在,当访问 /user
时,两个组价都会被渲染,在 V3
中存在一些模式也可以实现,但过程实在是复杂,在 V4
中,是不是感觉轻松了很多
Exclusive Routing
如果你只想匹配一个 route
,那么你也可以使用 <Switch>
来 exclusive routing
const PrimaryLayout = () =>
<div className="primary-layout">
<PrimaryHeader />
<main>
<Switch>
<Route path="/" exact component={HomePage} />
<Route path="/user/add" component={UserAddPage} />
<Route path="/user" component={UsersPage} />
<Redirect to="/" />
</Switch>
</main>
</div>;
在 <Switch>
中只有一个 <Route>
会被渲染,另外,我们还是要给 HomePage
所在 <Route>
添加 exact
,否则,在访问 /user
或 /user/add
的时候还是会匹配到 /
,从而,只渲染 HomePage
。同理,不知有没同学注意到,我们将 /user/add
放在 /user
前面是保证正确匹配
的很有策略性
的一步,因为,/user/add
会同时匹配 /user
和 /user/add
,如果不这么做,大家可以尝试交换它们两个的位置,看下会发生什么
当然,如果我们给每一个 <Route>
都添加一个 exact
,那就不用考虑上面的 策略
了,但不管怎样,现在至少知道了我们还有其它选择
<Redirect>
组件不用多说,执行浏览器重定向,但它在 <Switch>
中时,<Redirect>
组件只会在 routes
匹配不成功的情况下渲染,另外,要想了解 <Redirect>
如何在 non-switch
环境下使用,可以参考下面的 Authorized Route
"Index Routes" 和 "Not Found"
V4
中也没有 <IndexRoute>
,但 <Route exact>
可以实现相同的功能,或者 <Switch>
和 <Redirect>
重定向到默认的有效路径,甚至一个找不到的页面
嵌套布局
接下来,你可能很想知道 V4
中是如何实现 嵌套布局
的,V4
确实给我们了很多选择,但这并不一定是好事,表面上,嵌套布局
微不足道,但选择的空间越大,出现的问题也就可能越多
现在,我们假设我们要增加两个 user
相关的页面,一个 browse user
,一个 user profile
,对 product
我们也有相同的需求,实现的方法可能并不少,但有的仔细思考后可能并不想采纳
第一种,如下修改 PrimaryLayout
const PrimaryLayout = props => {
return (
<div className="primary-layout">
<PrimaryHeader />
<main>
<Switch>
<Route path="/" exact component={HomePage} />
<Route path="/user" exact component={BrowseUsersPage} />
<Route path="/user/:userId" component={UserProfilePage} />
<Route path="/products" exact component={BrowseProductsPage} />
<Route path="/products/:productId" component={ProductProfilePage} />
<Redirect to="/" />
</Switch>
</main>
</div>
);
};
虽然这种方法可以实现,但仔细观察下面的两个 user
页面,就会发现有点潜在的 问题
const BrowseUsersPage = () => (
<div className="user-sub-layout">
<aside>
<UserNav />
</aside>
<div className="primary-content">
<BrowseUserTable />
</div>
</div>
) const UserProfilePage = props => (
<div className="user-sub-layout">
<aside>
<UserNav />
</aside>
<div className="primary-content">
<UserProfile userId={props.match.params.userId} />
</div>
</div>
)
userId
通过props.match.params
获取,props.match
赋予给了<Route>
中的任何组件。除此之外,如果组件不通过<Route>
来渲染,要访问props.match
,可以使用withRouter()
高阶组件来实现
估计大家都发现了吧,两个 user
页面中都有一个<UserNav />
,这明显会导致不必要的请求
,以上只是一个简单实例,如果是在真实的项目中,不知道会重复消耗多少的流量,然而,这就是由我们以上方式使用路由引起的
接下来,我们再看看另一种实现方式
const PrimaryLayout = props => {
return (
<div className="primary-layout">
<PrimaryHeader />
<main>
<Switch>
<Route path="/" exact component={HomePage} />
<Route path="/user" component={UserSubLayout} />
<Route path="/products" component={ProductSubLayout} />
<Redirect to="/" />
</Switch>
</main>
</div>
);
};
我们用 2 个 routes
替换之前的 4 个 routes
注意,这里我们没有再使用
exact
,因为,我们希望/user
可以匹配任何以/user
开始的route
,products
同理
使用这种策略,子布局也开始承担起了渲染 routes
的责任,现在,UserSubLayout
长这样
const UserSubLayout = () =>
<div className="user-sub-layout">
<aside>
<UserNav />
</aside>
<div className="primary-content">
<Switch>
<Route path="/user" exact component={BrowseUsersPage} />
<Route path="/user/:userId" component={UserProfilePage} />
</Switch>
</div>
</div>;
现在是不是解决了第一种方式中的生命周期,重复渲染
的问题呢?
但有一点值得注意的是,routes
需要识别它的完整路径才能匹配,为了减少我们的重复输入,我们可以使用 props.match.path
来代替
const UserSubLayout = props =>
<div className="user-sub-layout">
<aside>
<UserNav />
</aside>
<div className="primary-content">
<Switch>
<Route path={props.match.path} exact component={BrowseUsersPage} />
<Route
path={`${props.match.path}/:userId`}
component={UserProfilePage}
/>
</Switch>
</div>
</div>;
Match
正如我们上面看到的那样,props.match
可以帮我们获取 userId
和 routes
match
对象为我们提供了 match.params
,match.path
,和 match.url
等属性
match.path vs match.url
最开始,可能觉得这两者的区别并不明显,控制台经常出现相同的输出,比如,访问 /user
const UserSubLayout = ({ match }) => {
console.log(match.url) // output: "/user"
console.log(match.path) // output: "/user"
return (
<div className="user-sub-layout">
<aside>
<UserNav />
</aside>
<div className="primary-content">
<Switch>
<Route path={match.path} exact component={BrowseUsersPage} />
<Route path={`${match.path}/:userId`} component={UserProfilePage} />
</Switch>
</div>
</div>
)
}
match
在组件的参数中被解构,意思就是我们可以使用match.path
代替props.match.path
虽然我们看不到什么明显的差异,但需要明白的是 match.url
是浏览器 URL
的一部分,match.path
是我们为 router
书写的路径
如何选择
如果我们是构建 route
路径,那么肯定使用 match.path
为了说明问题,我们创建两个子组件,一个 route
路径来自 match.url
,一个 route
路径来自 match.path
const UserComments = ({ match }) =>
<div>
UserId: {match.params.userId}
</div>; const UserSettings = ({ match }) =>
<div>
UserId: {match.params.userId}
</div>; const UserProfilePage = ({ match }) =>
<div>
User Profile:
<Route path={`${match.url}/comments`} component={UserComments} />
<Route path={`${match.path}/settings`} component={UserSettings} />
</div>;
然后,我们按下面方式来访问
/user/5/comments
/user/5/settings
实践后,我们发现,访问 comments
返回 undefined
,访问 settings
返回 5
正如 API
所述
match
:path
- (string) The path pattern used to match. Useful for building nested <Route>surl
- (string) The matched portion of the URL. Useful for building nested <Link>s
避免 Match Collisions
假设我们的 App
是一个仪表盘,我们希望访问 /user/add
和 /user/5/edit
添加和编辑 user
。使用上面的实例,user/:userId
已经指向 UserProfilePage
,我们这是需要在 UserProfilePage
中再添加一层 routes
么?显示不是这样的
const UserSubLayou = ({ match }) =>
<div className="user-sub-layout">
<aside>
<UserNav />
</aside>
<div className="primary-content">
<Switch>
<Route exact path={match.path} component={BrowseUsersPage} />
<Route path={`${match.path}/add`} component={AddUserPage} />
<Route path={`${match.path}/:userId/edit`} component={EditUserPage} />
<Route path={`${match.path}/:userId`} component={UserProfilePage} />
</Switch>
</div>
</div>;
现在,看清楚这个策略
了么
另外,我们使用 ${match.path}/:userId(\\d+)
作为 UserProfilePage
对应的 path
,保证 :userId
是一个数字,可以避免与 /users/add
的冲突,这样,将其所在的 <Route>
丢到最前面去也能正常访问 add
页面,这一招,就是我在 path-to-regexp 学的
Authorized Route
在应用程序中限制未登录的用户访问某些路由
是非常常见的,还有对于授权
和未授权
的用户 UI
也可能大不一样,为了解决这样的需求,我们可以考虑为应用程序设置一个主入口
class App extends React.Component {
render() {
return (
<Provider store={store}>
<BrowserRouter>
<Switch>
<Route path="/auth" component={UnauthorizedLayout} />
<AuthorizedRoute path="/app" component={PrimaryLayout} />
</Switch>
</BrowserRouter>
</Provider>
)
}
}
现在,我们首先会去选择应用程序在哪个顶级布局
中,比如,/auth/login
和 /auth/forgot-password
肯定在 UnauthorizedLayout
中,另外,当用户登陆时,我们将判断所有的路径都有一个 /app
前缀以确保是否登录。如果用户访问 /app
开头的页面但并没有登录,我们将会重定向
到登录页面
下面就是我写的 AuthorizedRoute
组件,这也是 V4
中一个惊奇的特性,可以为了满足某种需要而书写自己的路由
class AuthorizedRoute extends React.Component {
componentWillMount() {
getLoggedUser();
} render() {
const { component: Component, pending, logged, ...rest } = this.props;
return (
<Route
{...rest}
render={props => {
if (pending) return <div>Loading...</div>;
return logged
? <Component {...this.props} />
: <Redirect to="/auth/login" />;
}}
/>
);
}
} const stateToProps = ({ loggedUserState }) => ({
pending: loggedUserState.pending,
logged: loggedUserState.logged
}); export default connect(stateToProps)(AuthorizedRoute);
点击 这里 可以查看的我的整个 Authentication
总结
React Router 4
相比 V3
,变化很大,若是之前的项目使用的 V3
,不建议立即升级,但 V4
比 V3
确实存在较大的优势
原文链接:
All About React Router 4 (BRAD WESTFALL)
[Web 前端] 你不知道的 React Router 4的更多相关文章
- 部署web前端的react项目到linux服务器
部署web前端的react项目到linux服务器 项目的目录结构 ``` ├─dlls #dlls编译后的问题 ├─doc #帮助文件入口 │ ├─src │ ├─apps #各个功能模块放在这里 │ ...
- web前端 在react中使用移动端事件,学习笔记
一 移动端事件的使用: onTouchStartCapture onTouchStart onTouchMoveCapture on ...
- [Web 前端] React Router v4 入坑指南
cp from : https://www.jianshu.com/p/6a45e2dfc9d9 万恶的根源 距离React Router v4 正式发布也已经过去三个月了,这周把一个React的架子 ...
- React 还是 Vue: 你应该选择哪一个Web前端框架?
学还是要学的,用的多了,也就有更多的认识了,开发中遇到选择的时候也就简单起来了. 本文作者也做了总结: 如果你喜欢用(或希望能够用)模板搭建应用,请使用Vue 如果你喜欢简单和“能用就行”的东西 ...
- web前端入坑第五篇:秒懂Vuejs、Angular、React原理和前端发展历史
秒懂Vuejs.Angular.React原理和前端发展历史 2017-04-07 小北哥哥 前端你别闹 今天来说说 "前端发展历史和框架" 「前端程序发展的历史」 「 不学自知, ...
- web前端学习路线与书籍推荐
什么是web前端? 在以前,通俗的讲是网页制作,在现在,哼哼,可以参考这篇文章 http://tieba.baidu.com/p/4817153404 那么如果高效优雅的学习web呢? 注:以下纯属个 ...
- 前端基于react,后端基于.net core2.0的开发之路(1) 介绍
文章提纲目录 1.前端基于react,后端基于.net core2.0的开发之路(1) 介绍 2.前端基于react,后端基于.net core2.0的开发之路(2) 开发环境的配置,注意事项,后端数 ...
- React Router 4.x 开发,这些雷区我们都帮你踩过了
前言 在前端框架层出不穷的今天,React 以其虚拟 DOM .组件化开发思想等特性迅速占据了主流位置,成为前端开发工程师热衷的 Javascript 库.作为 React 体系中的重要组成部分:Re ...
- 我的web前端整理和学习
知识点收藏:(边看.边记录.边写) 开直播学习:虎牙 待办事理>> 练习自我表达(把文章做成视频).技术学习总结(博客与公众号).跳出舒适圈. 前端知识体系:https://www.cnb ...
随机推荐
- kafka一直rebalance故障,重复消费
今天我司线上kafka消息代理出现错误日志,异常rebalance,而且平均间隔2到3分钟就会rebalance一次,分析日志发现比较严重.错误日志如下 08-09 11:01:11 131 pool ...
- python-tkinter学习实例
在好友的邀请下,尝试用tkinter做一个卡牌的普通界面显示,正好练习下python的写法. 花了两天学习,写了两天代码,做了个最基本的demo.显示如下: 其中需要引入的第三方库主要有,PIL.P ...
- 设计模式-装饰者模式(Decorator Pattern)
本文由@呆代待殆原创,转载请注明出处. 此设计模式遵循的设计原则之一:类应该支持扩展,而拒绝修改(Open-Closed Principle) 装饰者模式简述 装饰者模式通过组合的方式扩展对象的特性, ...
- Codeforces.314E.Sereja and Squares(DP)
题目链接 http://www.cnblogs.com/TheRoadToTheGold/p/8443668.html \(Description\) 给你一个擦去了部分左括号和全部右括号的括号序列, ...
- 得到某个android应用 的POST和GET请求的 网址和参数
两种思路: 1.用笔记本建个wifi热点,然后抓包. 2. 把APK装到模拟器上 会在任务管理中有个exe进程,用一个软件可以抓取这个进程的所有请求 .
- IEEEXtreme Practice Community Xtreme9.0 - Dictionary Strings
Dictionary Strings 题目连接: https://www.hackerrank.com/contests/ieeextreme-challenges/challenges/dictio ...
- java读取记事本文件第一个字符遇到的一个坑
记事本数据是这样的: Faq_faqTitle=常见问题_标题Faq_faqKeyword=关键字Faq_faqDescription=FAQ描述...... 文件编码:utf-8有签名 然后用jav ...
- spring data jpa在使用PostgreSQL表名大小写的问题解决
国内的文章看了一遍,其实没找到根本问题解决方法,下面将列举这一系列的问题解决方法: 1.在配置文件增加如下配置: spring.jpa.hibernate.naming.physical-strate ...
- JSP Servlet学习笔记——使用fileupload上传文件
关键代码如下: index.jsp <body> <center> <h3>文件上传</h3> <font color="red&quo ...
- HTML解析利器HtmlAgilityPack
一个.NET下的HTML解析类库HtmlAgilityPack.HtmlAgilityPack是一个支持用XPath来解析HTML的类库,在花了一点时间学习了解HtmlAgilityPack的API和 ...