一、React-router V4版本修改内容及一些坑 1、所有组件更改为从react-router-dom导入
import {Router ,Route ,hashHistory} from 'react-router' ;import {Route ,BrowserRouter as Router , Switch } from 'react-router-dom' ;
2、将所有替换为
<Router history={hashHistory}> <Route path ="/" component ={PCIndex} > </Route > <Route path ="/details/:uniqueky" component ={PCNewsDetails} > </Route > <Route path ="/usercenter" component ={PCUserCenter} > </Route > </Router >
现在需要更改为BrowserRouter
这里的代码不仅仅是将Router替换为BrowserRouter,而且还把所有的Route中用Switch包裹起来.
<BrowserRouter > <Switch > <Route exact path ="/" component ={MobileIndex} > </Route > <Route path ="/details/:uniqueky" component ={MobileNewsDetails} > </Route > <Route path ="/usercenter" component ={MobileUserCenter} > </Route > </Switch > </BrowserRouter >
3、只能有一个子节点
<BroserRouter>只能有一个子节点,所以官网建议的是使用<Switch>进行包裹
<Route path='/' component={App }> <IndexRoute component ={Home} /> <Route path ='about' component ={About} /> <Route path ='contact' component ={Contact} /> </Route >
const App = ( ) => ( <Switch > <Route exact path ='/' component ={Home} /> <Route path ='/about' component ={About} /> <Route path ='/contact' component ={Contact} /> </Switch > )
4、最坑的地方:在当前目录下的文件路径不再使用./, 而是直接用/.
在进行文件引用的时候 ,./src/js的写法需要更改文’/src/js‘, 这是更改之后最坑的地方
二、安装
react-router-dom暴露出react-router中暴露的对象与方法,因此你只需要安装并引用react-router-dom即可
npm install --save react-router-dom
三、路由器(Router)
在你开始项目前,你需要决定你使用的路由器的类型。对于网页项目,存在<BrowserRouter>与<HashRouter>两种组件。当存在服务器来管理动态请求时,需要使用<BrowserRouter>组件,而<HashRouter>被用于静态网站。通常,我们更倾向选择<BrowserRouter>,但如果你的网站仅用来呈现静态文件,那么<HashRouter>将会是一个好选择
四、历史(History)
每个路由器都会创建一个history对象并用其保持追踪当前location[注1]并且在有变化时对网站进行重新渲染。这个history对象保证了React Router提供的其他组件的可用性,所以其他组件必须在router内部渲染。一个React Router组件如果向父级上追溯却找不到router组件,那么这个组件将无法正常工作
五、渲染
路由器组件无法接受两个及以上的子元素。基于这种限制的存在,创建一个<App>组件来渲染应用其余部分是一个有效的方法
import { BrowserRouter } from 'react-router-dom' ReactDOM .render (( <BrowserRouter > <App /> </BrowserRouter > ), document .getElementById ('root' ))
六、<App>
应用通过<App>组件定义。简化一下,我们将应用拆分成两个部分。<Header>组件包含网站的导航链接。<Main>组件则呈现其余内容
const App = ( ) => ( <div > <Header /> <Main /> </div > )
七、路由(Route)
<Route>组件是React Router中主要的结构单元。在任意位置只要匹配了URL的路径名(pathname)你就可以创建<Route>元素进行渲染
1、路径(Path)
<Route>接受一个数为string类型的path,该值路由匹配的路径名的类型。例如:<Route path='/roster'/>会匹配以/roster开头的路径名。在当前path参数与当前location的路径相匹配时,路由就会开始渲染React元素。若不匹配,路由不会进行任何操作
<Route path='/roster' /> <Route exact path ='/roster' />
注意:在匹配路由时,React Router只关注location的路径名。当URL如下时
React Router去匹配的只是'/my-projects/one'这一部分
2、匹配路径
path-to-regexp包用来决定route元素的path参数与当前location是否匹配。它将路径字符串编译成正则表达式,并与当前location的路径名进行匹配比较
当路由地址匹配成功后,会创建一个含有以下属性的match对象:
url :与当前location路径名所匹配部分
path:路由的地址
isExact :path 是否等于 pathname
params:从path-to-regexp获取的路径中取出的值都被包含在这个对象中
使用route tester 这款工具来对路由与URL进行检验
3、创建你的路由
可以在路由器(router)组件中的任意位置创建多个<Route>,但通常我们会把它们放在同一个位置。使用<Switch>组件来包裹一组<Route>。<Switch>会遍历自身的子元素(即路由)并对第一个匹配当前路径的元素进行渲染
我们希望匹配一下路径
/:主页
/roster: 团体列表
/roster/:number:运动员页面,使用运动员的编号作为标识
/schedule:团队的赛程表
为了在应用中能匹配路径,在创建<Route>元素时必须带有需要匹配的path作为参数
<Switch > <Route exact path ='/' component ={Home}/ > {} <Route path='/roster' component={Roster }/> <Route path ='/schedule' component ={Schedule}/ > </Switch >
4、<Route>是如何渲染的?
当一个路由的path匹配成功后,路由用来确定渲染结果的参数有三种。只需要提供其中一个即可
component : 一个React组件。当带有component参数的route匹配成功后,route会返回一个新的元素,其为component参数所对应的React组件(使用React.createElement创建)。
render : 一个返回React element的函数。当匹配成功后调用该函数。该过程与传入component参数类似,并且对于行级渲染与需要向元素传入额外参数的操作会更有用。
children : 一个返回React element的函数。与上述两个参数不同,无论route是否匹配当前location,其都会被渲染
<Route path='/page' component={Page } /> const extraProps = { color : 'red' }<Route path='/page' render={(props ) => ( <Page {...props } data ={extraProps}/ > )}/> <Route path ='/page' children ={(props) => ( props.match ? <Page {...props }/> : <EmptyPage {...props }/> )}/>
通常component参数与render参数被更经常地使用。children参数偶尔会被使用,它更常用在path无法匹配时呈现的’空’状态。在本例中并不会有额外的状态,所以我们将使用<Route>的component参数
通过<Route>渲染的元素会被传入一些参数。分别是match对象,当前location对象以及history对象(由router创建)
5、<Main>
现在我们清楚了根路由的结构,我们需要实际渲染我们的路由。对于这个应用,我们将会在<Main>组件中渲染<Switch>与<Route>,这一过程会将route匹配生成的HTML放在<main>节点中
import { Switch , Route } from 'react-router-dom' const Main = ( ) => ( <main > <Switch > <Route exact path ='/' component ={Home}/ > <Route path ='/roster' component ={Roster}/ > <Route path ='/schedule' component ={Schedule}/ > </Switch > </main > )
注意 :主页路由包含额外参数。该参数用来保证路由能准确匹配path
6、嵌套路由
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" ));
上面代码中有几个关键的点在 V4 中就不复存在了
集中式 router
通过 <Route> 嵌套,实现 Layout 和 page 嵌套
Layout 和 page 组件 是作为 router 的一部分
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" ));
首先,V3 中的 router 不在了,在 V3 中,我们是将整个庞大的 router 直接丢给 DOM,而在 V4 中,除了 BrowserRouter, 我们丢给 DOM 的是我们的应用程序本身
react-router 4.0 对于接受参数采用 { this.props.match.params.id } 如下例子:<Route path="list/:id"></Router> 、 <Link to="list/123456"></Link>
import { HashRouter , Route , Link , Switch } from ‘react-router-dom‘; class App extends Component { render ( ) { return ( <div > <h1 > App</h1 > <ul > <li > <Link to ="/" > Home</Link > </li > <li > <Link to ="/about" > About</Link > </li > <li > <Link to ="/inbox" > Inbox</Link > </li > </ul > {this.props.children} </div > ); } } const About = ( ) => ( <div > <h3 > About</h3 > </div > ) const Home = ( ) => ( <div > <h3 > Home</h3 > </div > ) const Message = ({ match } ) => ( <div > <h3 > new messages</h3 > <h3 > {match.params.id}</h3 > </div > ) const Inbox = ({ match } ) => ( <div > <h2 > Topics</h2 > <Route path ={ `${match.url }/messages /:id `} component ={Message}/ > </div > ) ReactDOM .render ( (<HashRouter > <App > <Route exact path ="/" component ={Home} /> <Route path ="/about" component ={About} /> <Route path ="/inbox" component ={Inbox} /> </App > </HashRouter > ), document .getElementById (‘root‘) );
方式一
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 > ); };
userId 通过 props.match.params 获取
props.match 赋予给了 <Route> 中的任何组件。
除此之外,如果组件不通过 <Route> 来渲染,要访问 props.match,可以使用 withRouter() 高阶组件来实现
方式二
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 > ;
7、路径参数
有时路径名中存在我们需要获取的参数。例如,在运动员界面,我们需要获取运动员的编号。我们可以向route的路径字符串中添加path参数
如'/roster/:number'中:number这种写法意味着/roster/后的路径名将会被获取并存在match.params.number中。例如,路径名'/roster/6'会获取到一个对象
<Player>组件可以使用props.match.params对象来确定需要被渲染的运动员的数据
import PlayerAPI from './PlayerAPI' const Player = (props ) => { const player = PlayerAPI .get ( parseInt (props.match .params .number , 10 ) ) if (!player) { return <div > Sorry, but the player was not found</div > } return ( <div > <h1 > {player.name} (#{player.number})</h1 > <h2 > {player.position}</h2 > </div > )
除了<Player>组件,我们的页面还包含<FullRoster>, <Schedule>以及 <Home>组件
const FullRoster = ( ) => ( <div > <ul > { PlayerAPI.all().map(p => ( <li key ={p.number} > <Link to ={ `/roster /${p.number }`}> {p.name}</Link > </li > )) } </ul > </div > ) const Schedule = ( ) => ( <div > <ul > <li > 6/5 @ Evergreens</li > <li > 6/8 vs Kickers</li > <li > 6/14 @ United</li > </ul > </div > ) const Home = ( ) => ( <div > <h1 > Welcome to the Tornadoes Website!</h1 > </div > )
八、inclusive routing
当访问 /user 时,两个组价都会被渲染
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>;
九、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 匹配不成功的情况下渲染
十、Index Routes” 和 “Not Found”
V4 中也没有 <IndexRoute>,但 <Route exact> 可以实现相同的功能,或者 <Switch> 和 <Redirect> 重定向到默认的有效路径,甚至一个找不到的页面
十一、Link
现在,我们应用需要在各个页面间切换。如果使用锚点元素(就是)实现,在每次点击时页面将被重新加载。React Router提供了<Link>组件用来避免这种状况的发生。当你点击<Link>时,URL会更新,组件会被重新渲染,但是页面不会重新加载
import { Link } from 'react-router-dom' const Header = ( ) => ( <header > <nav > <ul > <li > <Link to ='/' > Home</Link > </li > <li > <Link to ='/roster' > Roster</Link > </li > <li > <Link to ='/schedule' > Schedule</Link > </li > </ul > </nav > </header > )
<Link>使用'to'参数来描述需要定位的页面。它的值即可是字符串也可是location对象(包含pathname,search,hash与state属性)。如果其值为字符床将会被转换为location对象。
<Link to ={{ pathname: '/roster /7 ' }}> Player #7</Link >
十二、Match
正如我们上面看到的那样,props.match 可以帮我们获取 userId 和 routes
match 对象为我们提供了 match.params,match.path,和 match.url 等属性
1、match.path vs match.url
最开始,可能觉得这两者的区别并不明显,控制台经常出现相同的输出,比如,访问 /user
const UserSubLayout = ({ match } ) => { console .log (match.url ) console .log (match.path ) 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 书写的路径
2、如何选择
如果我们是构建 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
3、避免 Match 冲突
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 页面
十三、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 开头的页面但并没有登录,我们将会重定向到登录页面
十四、<Link>
和之前版本没太大区别,重点看下组件属性
to(string/object):要跳转的路径或地址;
replace(bool):为 true 时,点击链接后将使用新地址替换掉访问历史记录里面的原地址;为 false 时,点击链接后将在原有访问历史记录的基础上添加一个新的纪录。默认为 false
<Link to="/about" >关于</Link > <Link to ={{ pathname: '/courses ', search: '?sort =name ', hash: '#the-hash ', state: { fromDashboard: true } }}/> <Link to ="/courses" replace />
<NavLink>
<NavLink>是<Link> 的一个特定版本, 会在匹配上当前 URL 的时候会给已经渲染的元素添加样式参数,组件属性
activeClassName(string):设置选中样式,默认值为 active;
activeStyle(object):当元素被选中时, 为此元素添加样式;
exact(bool):为 true 时, 只有当地址完全匹配 class 和 style 才会应用;
strict(bool):为 true 时,在确定位置是否与当前 URL 匹配时,将考虑位置 - pathname 后的斜线;
isActive(func):判断链接是否激活的额外逻辑的功能
<NavLink to="/faq" activeClassName="selected" >FAQs </NavLink > <NavLink to ="/faq" activeStyle ={{ fontWeight: 'bold ', color: 'red ' }} > FAQs</NavLink > const oddEvent = (match, location ) => { if (!match) { return false } const eventID = parseInt (match.params .eventID ) return !isNaN (eventID) && eventID % 2 === 1 } <NavLink to="/events/123" isActive={oddEvent} >Event 123 </NavLink >
十五、<Switch>
该组件用来渲染匹配地址的第一个<Route>或者<Redirect>。那么它与使用一堆route又有什么区别呢
<Switch>的独特之处是独它仅仅渲染一个路由。相反地,每一个包含匹配地址(location)的<Route>都会被渲染
<Switch > <Route exact path ="/" component ={Home}/ > <Route path ="/about" component ={About}/ > <Route path ="/:user" component ={User}/ > <Route component ={NoMatch}/ > </Switch >
现在,如果我们处于/about,<Switch>将开始寻找匹配的<Route>。<Route path="/about"/> 将被匹配, <Switch>将停止寻找匹配并渲染<About>。同样,如果我们处于/michael,<User>将被渲染
十六、更多参考