几个月前,
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 =>
Our React Router 3 App
-
Home
-
User
{props.children}
;
const HomePage = () => Home Page
;
const UsersPage = () => User Page
;
const App = () =>
;
render( , document.getElementById("root"));
上篇文章给大家推荐了一个在线
react
编译器 stackblitz,本篇文章再给大家推荐一个不错的,codesandbox,专门针对react
且开源,正所谓,实践是检验真理的唯一标准
,这也是一种良好的学习习惯
上面代码中有几个关键的点在 V4
中就不复存在了
集中式
router
通过
嵌套,实现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 = () =>
Our React Router 4 App
-
Home
-
User
;
const HomePage = () => Home Page
;
const UsersPage = () => User Page
;
const App = () =>
;
render( , document.getElementById("root"));
注意,我们现在
import
的是BrowserRouter
,而且是从react-router-dom
引入,而不是react-router
接下来,我们用肉眼就能看出很多的变化,首先,V3
中的 router
不在了,在 V3
中,我们是将整个庞大的 router
直接丢给 DOM
,而在 V4
中,除了 BrowserRouter
, 我们丢给 DOM
的是我们的应用程序本身
另外,V4
中,我们不再使用 {props.children}
来嵌套组件了,替代的
,当 route
匹配时,子组件会被渲染到
书写的地方
Inclusive Routing
在上面的 example
中,读者可能注意到 V4
中有 exact
这么一个 props
,那么,这个 props
有什么用呢? V3
中的 routing
规则是 exclusive
,意思就是最终只获取一个 route
,而 V4
中的 routes
默认是 inclusive
的,这就意味着多个
可以同时匹配
和呈现
还是使用上面的 example
,如果我们调皮地删除 exact
这个 props
,那么我们在访问 /user
的时候,Home
和 User
两个 Page
都会被渲染,是不是一下就明白了
为了更好地理解
V4
的匹配逻辑,可以查看 path-to-regexp,就是它决定routes
是否匹配URL
为了演示 inclusive routing
的作用,我们新增一个 UserMenu
组件如下
const PrimaryLayout = () =>
Our React Router 4 App
;
现在,当访问 /user
时,两个组价都会被渲染,在 V3
中存在一些模式也可以实现,但过程实在是复杂,在 V4
中,是不是感觉轻松了很多
Exclusive Routing
如果你只想匹配一个 route
,那么你也可以使用
来 exclusive routing
const PrimaryLayout = () =>
;
在
中只有一个
会被渲染,另外,我们还是要给 HomePage
所在
添加 exact
,否则,在访问 /user
或 /user/add
的时候还是会匹配到 /
,从而,只渲染 HomePage
。同理,不知有没同学注意到,我们将 /user/add
放在 /user
前面是保证正确匹配
的很有策略性
的一步,因为,/user/add
会同时匹配 /user
和 /user/add
,如果不这么做,大家可以尝试交换它们两个的位置,看下会发生什么
当然,如果我们给每一个
都添加一个 exact
,那就不用考虑上面的 策略
了,但不管怎样,现在至少知道了我们还有其它选择
组件不用多说,执行浏览器重定向,但它在
中时,
组件只会在 routes
匹配不成功的情况下渲染,另外,要想了解
如何在 non-switch
环境下使用,可以参考下面的 Authorized Route
"Index Routes" 和 "Not Found"
V4
中也没有
,但
可以实现相同的功能,或者
和
重定向到默认的有效路径,甚至一个找不到的页面
嵌套布局
接下来,你可能很想知道 V4
中是如何实现 嵌套布局
的,V4
确实给我们了很多选择,但这并不一定是好事,表面上,嵌套布局
微不足道,但选择的空间越大,出现的问题也就可能越多
现在,我们假设我们要增加两个 user
相关的页面,一个 browse user
,一个 user profile
,对 product
我们也有相同的需求,实现的方法可能并不少,但有的仔细思考后可能并不想采纳
第一种,如下修改 PrimaryLayout
const PrimaryLayout = props => {
return (
);
};
虽然这种方法可以实现,但仔细观察下面的两个 user
页面,就会发现有点潜在的 问题
const BrowseUsersPage = () => (
)
const UserProfilePage = props => (
)
userId
通过props.match.params
获取,props.match
赋予给了中的任何组件。除此之外,如果组件不通过
来渲染,要访问
props.match
,可以使用withRouter()
高阶组件来实现
估计大家都发现了吧,两个 user
页面中都有一个
,这明显会导致不必要的请求
,以上只是一个简单实例,如果是在真实的项目中,不知道会重复消耗多少的流量,然而,这就是由我们以上方式使用路由引起的
接下来,我们再看看另一种实现方式
const PrimaryLayout = props => {
return (
);
};
我们用 2 个 routes
替换之前的 4 个 routes
注意,这里我们没有再使用
exact
,因为,我们希望/user
可以匹配任何以/user
开始的route
,products
同理
使用这种策略,子布局也开始承担起了渲染 routes
的责任,现在,UserSubLayout
长这样
const UserSubLayout = () =>
;
现在是不是解决了第一种方式中的生命周期,重复渲染
的问题呢?
但有一点值得注意的是,routes
需要识别它的完整路径才能匹配,为了减少我们的重复输入,我们可以使用 props.match.path
来代替
const UserSubLayout = props =>
;
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 (
)
}
match
在组件的参数中被解构,意思就是我们可以使用match.path
代替props.match.path
虽然我们看不到什么明显的差异,但需要明白的是 match.url
是浏览器 URL
的一部分,match.path
是我们为 router
书写的路径
如何选择
如果我们是构建 route
路径,那么肯定使用 match.path
为了说明问题,我们创建两个子组件,一个 route
路径来自 match.url
,一个 route
路径来自 match.path
const UserComments = ({ match }) =>
UserId: {match.params.userId}
;
const UserSettings = ({ match }) =>
UserId: {match.params.userId}
;
const UserProfilePage = ({ match }) =>
User Profile:
;
然后,我们按下面方式来访问
/user/5/comments
/user/5/settings
实践后,我们发现,访问 comments
返回 undefined
,访问 settings
返回 5
正如 API
所述
match
:path
- (string) The path pattern used to match. Useful for building nesteds url
- (string) The matched portion of the URL. Useful for building nested s
避免 Match Collisions
假设我们的 App
是一个仪表盘,我们希望访问 /user/add
和 /user/5/edit
添加和编辑 user
。使用上面的实例,user/:userId
已经指向 UserProfilePage
,我们这是需要在 UserProfilePage
中再添加一层 routes
么?显示不是这样的
const UserSubLayou = ({ match }) =>
;
现在,看清楚这个策略
了么
另外,我们使用 ${match.path}/:userId(\\d+)
作为 UserProfilePage
对应的 path
,保证 :userId
是一个数字,可以避免与 /users/add
的冲突,这样,将其所在的
丢到最前面去也能正常访问 add
页面,这一招,就是我在 path-to-regexp 学的
Authorized Route
在应用程序中限制未登录的用户访问某些路由
是非常常见的,还有对于授权
和未授权
的用户 UI
也可能大不一样,为了解决这样的需求,我们可以考虑为应用程序设置一个主入口
class App extends React.Component {
render() {
return (
)
}
}
现在,我们首先会去选择应用程序在哪个顶级布局
中,比如,/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 (
{
if (pending) return Loading...;
return logged
?
: ;
}}
/>
);
}
}
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)