React 路由管理

  • 不同的路径渲染不同的组件
  • 有两种实现方式
    • HashRouter:利用hash实现路由切换
    • BrowserRouter:实现h5 Api实现路由的切换

HashRouter

  • 利用 hash 实现路由切换
    public/hash.html
    1. <!DOCTYPE html>
    2. <html lang="en">
    3. <head>
    4. <meta charset="UTF-8">
    5. <meta http-equiv="X-UA-Compatible" content="IE=edge">
    6. <meta name="viewport" content="width=device-width, initial-scale=1.0">
    7. <title>hash router</title>
    8. </head>
    9. <body>
    10. <div id="root"></div>
    11. <ul>
    12. <li><a href="#/a">/a</a></li>
    13. <li><a href="#/b">/b</a></li>
    14. </ul>
    15. <script>
    16. // 监听 hash 值变化
    17. window.addEventListener('hashchange', () => {
    18. console.log(window.location.hash);
    19. let pathname = window.location.hash.slice(1);
    20. document.getElementById('root').innerHTML = pathname;
    21. })
    22. </script>
    23. </body>
    24. </html>

BrowserRouter

  • 利用h5 Api实现路由的切换
  • 注意:前端路由完全是由前端控制的,跟后端无关。

history
  • HTML5规范给我们提供了一个 history 接口
  • HTML5 History API包括2个方法:history.pushState()history.replaceState(),和1个事件window.onpopstate

pushState
  • history.pushState(stateObject, title, url),包括三个参数
    • 第一个参数用于存储该url对应的状态对象,该对象可在onpopstate事件中获取,也可在history对象中获取
    • 第二个参数是标题,目前浏览器并未实现
    • 第三个参数则是设定的url
  • pushState 函数向浏览器的历史堆栈压入一个 url 为设定值的记录,并改变历史堆栈的当前指针至栈顶

replaceState
  • 该接口与pushState参数相同,含义也相同
  • 唯一的区别在于 replaceState 是替换浏览器历史堆栈的当前历史记录为设定的url
  • 需要注意的是 replaceState 不会改动浏览器历史堆栈的当前指针

onpopstate
  • 该事件是window的属性
  • 该事件会在调用浏览器的前进、后退以及执行 history.forwardhistory.back、和 history.go 触发,因为这些操作有一个共性,即修改了历史堆栈的当前指针
  • 在不改变 document 的前提下,一旦当前指针改变则会触发 onpopstate 事件

示例

1618903522(1).jpg
1618904825(1).jpg

  • 浏览器针对每个页面维护一个 History 栈,执行 pushState 函数可压入设定的 url 至栈顶,同时修改当前指针
  • 当执行 backforward 操作时,history 栈大小并不会改变(history.length不变),仅仅移动当前指针的位置
  • 若当前指针在 history 栈的中间位置(非栈顶),此时执行 pushState在指针当前的位置添加此条目,并成为新的栈顶

    1. <!DOCTYPE html>
    2. <html lang="en">
    3. <head>
    4. <meta charset="UTF-8">
    5. <meta http-equiv="X-UA-Compatible" content="IE=edge">
    6. <meta name="viewport" content="width=device-width, initial-scale=1.0">
    7. <title>hash router</title>
    8. </head>
    9. <body>
    10. <div id="root"></div>
    11. <script>
    12. // 这个 history 是一个全局管理
    13. var globalHistory = window.history;
    14. (function(history){
    15. let oldPushState = history.pushState;
    16. history.pushState = function (pathname, state){
    17. let result = oldPushState.apply(history, arguments);
    18. if (typeof window.onpushstate){
    19. window.onpushstate(new CustomEvent('pushstate', {detail: {pathname, state}}));
    20. }
    21. return result;
    22. }
    23. })(globalHistory);
    24. const oldHistoryLength = globalHistory.length; //历史条目的长度
    25. // 当调用 pushState 的时候,会执行这个函数。(这个浏览器不支持,需要自己处理)
    26. window.onpushstate = (event) => {
    27. console.log('push', event);
    28. }
    29. // 当回退或前进的时候,会执行这个。(这个监听是浏览器自带的,默认支持)
    30. window.onpopstate = (event) => {
    31. console.log('pop', event);
    32. }
    33. // http://localhost:3000/browser.html
    34. // ([1])添加新条目page1,指针移至page1
    35. setTimeout(() => {
    36. globalHistory.pushState({page: 1}, {title: 'page1'}, '/page1');
    37. console.log(globalHistory.length - oldHistoryLength); //1
    38. }, 1000)
    39. // ([1,2])添加新条目page2,指针移至page2
    40. setTimeout(() => {
    41. globalHistory.pushState({page: 2}, {title: 'page2'}, '/page2');
    42. console.log(globalHistory.length - oldHistoryLength); //2
    43. }, 2000)
    44. // ([1,2,3])添加新条目page3,指针移至page3
    45. setTimeout(() => {
    46. globalHistory.pushState({page: 3}, {title: 'page3'}, '/page3');
    47. console.log(globalHistory.length - oldHistoryLength); //3
    48. }, 3000)
    49. // ([1,2,3])页面后退回page2,指针移回page2。(注意:条目的数量不会因此减少1个)
    50. setTimeout(() => {
    51. globalHistory.back();
    52. console.log(globalHistory.length - oldHistoryLength); //3
    53. }, 4000)
    54. // ([1,2,4])在指针位置(page2处)添加新条目page4,指针移至page4。(page4会成为新的栈顶,page3也就不存在了)。
    55. setTimeout(() => {
    56. globalHistory.pushState({page: 4}, {title: 'page4'}, '/page4');
    57. console.log(globalHistory.length - oldHistoryLength); //3
    58. }, 5000)
    59. // ([1,2,4])没有添加新条目,指针也仍在page4(因为已经在栈顶了,所以已经没办法再往上移了)
    60. setTimeout(() => {
    61. globalHistory.go(1);
    62. console.log(globalHistory.length - oldHistoryLength); //3
    63. }, 6000)
    64. </script>
    65. </body>
    66. </html>

基本路由

安装
  1. cnpm i -S react-router-dom

src/index.js
  1. import React from 'react';
  2. import ReactDOM from 'react-dom';
  3. import { HashRouter as Router, Route} from 'react-router-dom';
  4. import Home from './components/Home'
  5. import User from './components/User'
  6. import Profile from './components/Profile'
  7. // Router 路由容器,Route 路由规则
  8. ReactDOM.render(
  9. <Router>
  10. <Route exact path="/" component={Home} />
  11. <Route path="/user" component={User} />
  12. <Route path="/profile" component={Profile} />
  13. <Route render={props => <div>未发现路由页面</div>} />
  14. </Router>,
  15. document.getElementById('root')
  16. );

src/components/Home.js
  1. const Home = (ps) => {
  2. console.log(ps);
  3. return (<div>Home</div>)
  4. }
  5. export default Home;

Router 路由容器

Router 是所有路由组件共用的底层接口。通常,我们的应用程序将使用其中一个高级路由器代替:

最常见的使用底层的的情形就是用来与 Redux 或者 Mobx 之类的状态管理库的定制的 history 保持同步。注意不是说使用状态管理库就必须使用 React Router ,它仅用作于深度集成。

  1. import { Router } from 'react-router';
  2. import { HashRouter, BrowserRouter } from 'react-router-dom';
  3. import createHashHistory from 'history/createHashHistory';
  4. import createBrowserHistory from 'history/createBrowserHistory'
  5. const history = createBrowserHistory()
  6. <Router history={history}>
  7. <App/>
  8. </Router>

属性
  • history 用来导航的history对象。
  • children 需要渲染的单一组件。

Route 路由规则

Route 组件也许是 React Router 中最重要的组件,它可以让你理解并学习如何使用它。它最基本的职责是在location与 Route 的 path 匹配时呈现一些 UI。

Route 传递给组件的参数

所有三种渲染方法都将通过相同的三个 Route 属性。

history

location

location 代表应用程序现在在哪,你想让它去哪,或者甚至它曾经在哪,它看起来就像:
image.png
router 将在这几个地方为您提供一个 location 对象:

注意:它也可以在history.location找到,但是你不应该使用它,因为它是可变的。

location 对象永远不会发生变化,因此你可以在生命周期钩子中使用它来确定何时导航,这对数据抓取和动画非常有用。

match

一个match 对象中包涵了有关如何匹配 URL 的信息。match对象中包涵以下属性:

  • params - (object) key/value 与动态路径的 URL 对应解析
  • isExact - (boolean)true 如果匹配整个 URL (没有结尾字符)
  • path - (string) 用于匹配的路径模式。被嵌套在中使用
  • url - (string) 用于匹配部分的 URL 。被嵌套在中使用

image.png

你将会在这些地方用到match对象:

如果 Route 没有path,那么将会一直与他最近的父级匹配。这也同样适用于withRouter。

Route 渲染组件的方法

component

值是一个组件,不能写自定义的逻辑。

当您使用component(而不是render或children)Route 使用从给定组件React.createElement创建新的React element。这意味着,如果您为component道具提供了内联功能,则每次渲染都会创建一个新组件。这会导致现有组件卸载和安装新组件,而不是仅更新现有组件。当使用内联函数进行内联渲染时,使用render或者children(如下所示)。

  1. <Route path="/user/:username" component={User}/>
  2. const User = ({ history, location, match }) => {
  3. return <h1>Hello {match.params.username}!</h1>
  4. }

render: func

值是一个函数,如果路径匹配的话,会渲染这个函数的返回值。(匹配才渲染,不匹配不渲染)

这允许方便的内联渲染和包裹,而不是上面那种不想要的重新安装的解释。您可以传递一个在位置匹配时调用的函数,而不是使用属性为您创建新的React elementcomponent,该render属性接收所有相同的route props的component渲染属性。

PS:优先于因此不要在同一个使用两者。

  1. // convenient inline rendering
  2. <Route path="/home" render={() => <div>Home</div>}/>
  3. // wrapping/composing
  4. const FadingRoute = ({ component: Component, ...rest }) => (
  5. <Route {...rest} render={props => (
  6. <FadeIn>
  7. <Component {...props}/>
  8. </FadeIn>
  9. )}/>
  10. )
  11. <FadingRoute path="/cool" component={Something}/>

children: func

与 render 差不多。(不管匹配不匹配都渲染)

有时你需要渲染路径是否匹配位置。在这些情况下,您可以使用函数children属性,它的工作原理与渲染完全一样,不同之处在于它是否存在匹配。

children渲染道具接收所有相同的route props作为component和render方法,如果 Route 与 URL 不匹配,match则为null,这允许你动态调整你的 UI 界面,基于路线是否匹配,如果路线匹配我们则添加一个active类

警告:优先于,因此不要在同一个中使用多个。

  1. <ul>
  2. <ListItemLink to="/somewhere"/>
  3. <ListItemLink to="/somewhere-else"/>
  4. </ul>
  5. const ListItemLink = ({ to, ...rest }) => (
  6. <Route path={to} children={({ match }) => (
  7. <li className={match ? 'active' : ''}>
  8. <Link to={to} {...rest}/>
  9. </li>
  10. )}/>
  11. )
  12. <Route children={({ match, ...rest }) => (
  13. {/* Animate will always render, so you can use lifecycles
  14. to animate its child in and out */}
  15. <Animate>
  16. {match && <Something {...rest}/>}
  17. </Animate>
  18. )}/>

render 与 children 两种方式对比

相同点
  • children 与 render 都是函数
  • 都会接收路由属性对象,返回一个虚拟DOM进行渲染
  • 都可以写一些逻辑判断

    不同点
  • render 是匹配才渲染,不匹配不渲染

  • children 是不管匹不匹配,都会渲染。如果匹配有match属性;如果不匹配,没有match属性

Route props属性

  1. export interface RouteProps<
  2. Path extends string = string,
  3. Params extends { [K: string]: string | undefined } = ExtractRouteParams<Path, string>
  4. > {
  5. location?: H.Location;
  6. component?: React.ComponentType<RouteComponentProps<any>> | React.ComponentType<any>;
  7. render?: (props: RouteComponentProps<Params>) => React.ReactNode;
  8. children?: ((props: RouteChildrenProps<Params>) => React.ReactNode) | React.ReactNode;
  9. path?: Path | readonly Path[];
  10. exact?: boolean;
  11. sensitive?: boolean;
  12. strict?: boolean;
  13. }

path 匹配路径

任何path-to-regexp可以解析的有效的 URL 路径。

  1. <Router basename="/m">
  2. <Route path="/" exact component={Home} />
  3. <Route path="/user/:id" component={User} /> {/*动态路由(参数路由)*/}
  4. <Route path="/profile/phone" component={Profile} />
  5. </Router>

exact 是否精确匹配(是否匹配整个字符串,后面不能跟子路径)

path location.pathname exact matches?
/one /one/two true no
/one /one/two false yes
  1. <Route path="/" exact component={Home} />
  2. <Route path="/one" exact component={One} />

strict 是否严格匹配(是否允许结尾有一个可选的/ )

如果为true当真实的路径具有一个斜线将只匹配一个斜线location.pathname,如果有更多的URL段location.pathname,将不起作用。

  1. <Route path="/one/" strict component={One} />
path location.pathname matches?
/one/ /one no
/one/ /one/ yes
/one/ /one/two yes (不起作用了)

PS:可以使用strict来强制执行location.pathname没有结尾斜杠,但为了执行此操作,strict和exact必须都是true。

  1. <Route path="/one" exact strict component={One}/>
path location.pathname matches?
/one /one yes
/one /one/ no
/one /one/two no

sensitive 是否大小写敏感

如果路径区分大小写,则为true,则匹配。

  1. <Route sensitive path="/one" component={One}/>
path location.pathname sensitive matches?
/one /one true yes
/One /one true no
/One /one false yes

location: object

一个元素尝试其匹配path到当前的历史位置(通常是当前浏览器 URL )。但是,也可以通过location一个不同pathname的匹配。
如果您需要将与当前历史记录位置以外的位置相匹配,则此功能非常有用,如Animated Transitions示例中所示。
如果元素包裹在中,并且匹配地址传递给(或者当前的历史位置),那么传递给的地址属性将会被传递给的地址属性覆盖(在这里给出

Switch

渲染与该地址匹配的第一个子节点或者。也就是说,第一个匹配到了,就不再往下匹配了。

这与仅使用一堆有什么区别?
的独特之处在于它专门呈现路由。相比之下,与位置匹配的每个都已包含方式呈现。请考虑以下代码。 jsx <Router> <Route path="/about" component={About}/> <Route path="/:user" component={User}/> <Route component={NoMatch}/> </Router> 如果 URL 是/about, 那么将全部渲染,因为他们都与路径匹配。这是通过设计实现的,允许我们以多种方式将组合到应用程序中,类似侧边栏(sidebars)和面包屑导航(breadcrumbs), bootstrap 标签等等,但是有时候我们只想选择一条进行渲染,如果我们在/about,我们又不想匹配/:user(或者显示404)。以下是如何使用Switch执行此操作: jsx import { Switch, Route } from 'react-router'; <Router> <Switch> <Route exact path="/" component={Home}/> <Route path="/about" component={About}/> <Route path="/:user" component={User}/> <Route component={NoMatch}/> </Switch> </Router> 现在,如果我们在/about,将开始查找匹配的,<Route path=”/about”/>将停止查找匹配项并渲染,同样,如果是/Michael,则< User >将渲染。 这对于动画过渡效果也很有用,因为匹配的被渲染到与前一个位置相同的位置。 # Redirect 渲染将使导航到一个新的地址。这个新的地址会覆盖 history 栈中的当前地址(替换),类似服务器端(HTTP 3xx)的重定向。 ##### exact 完全匹配from ##### strict 严格匹配from ##### push 当true时,重定向会将新地址推入 history 中(新增),而不是替换当前地址。 jsx <Redirect push to="/somewhere/else" /> ##### to 重定向到的 URL 或 location,可以是任何path-to-regexp能够理解有效 URL 路径。在to中使用的 URL 参数必须由from覆盖。 javascript <Router> <Switch> <Route exact path="/" component={Home} /> <Route path="/user/:id" component={User} /> <Route path="/profile" component={Profile} /> {/*如果上面链接都不匹配,将自动跳转到 /bb */} <Redirect to="/bb" /> </Switch> </Router> jsx <Redirect to={{ pathname: "/login", search: "?utm=your+face", state: { referrer: currentLocation } }} /> > state对象可以通过重定向到组件中的this.props.locations.state来访问。这个新的referrer键(它不是一个特殊的名字)将通过路径名’/login’指向Login组件中的this.props.locations.state.referrer来访问。 ##### from 重定向 from 的路径名。可以是任何path-to-regexp能够识别的有效的 URL 路径。所有匹配的 URL 参数都提供给to中的模式。必须包含在to中使用的所有参数。to未使用的其他参数将被忽略。
这只能用于在内部渲染时匹配地址。有关更多详情,请查阅参阅。 ```jsx {/ 输入路径 /old-path 将自动跳转到 /new-path /}

{/ Redirect with matched parameters /}

  1. <a name="y9zeB"></a>
  2. # Link
  3. 在应用程序周围提供声明式的,可访问的导航。
  4. <a name="uEvBG"></a>
  5. ##### to: string
  6. 链接位置的字符串表示,通过连接位置的路径名,搜索和 hash 属性创建。
  7. ```javascript
  8. import { Link } from 'react-router-dom'
  9. <Link to="/about">About</Link>
  10. <Link to='/courses?sort=name' replace />

to: object

一个可以具有以下任何属性的对象:

  • pathname: 表示要链接到的路径的字符串。
  • search: 表示查询参数的字符串形式。
  • hash: 放入网址的 hash,例如#a-hash。
  • state: 状态持续到location。
    1. <Link to={{
    2. pathname: '/courses',
    3. search: '?sort=name',
    4. hash: '#the-hash',
    5. state: { fromDashboard: true }
    6. }}/>
    replace: bool
    如果为true,则单击链接将替换历史堆栈中的当前入口,而不是添加新入口。
    innerRef: function
    允许访问ref组件的底层。 ``jsx const refCallback = node => { console.log(node); // <a href="/m/profile">用户</a> //node` refers to the mounted DOM element or null when unmounted }

用户

  1. <a name="iqG8N"></a>
  2. ##### others
  3. 还可以传递您想要放在<a>上的属性,例如标题,ID,className等。
  4. <a name="bI6vP"></a>
  5. ##### Link标签和a标签有什么区别?
  6. 从最终渲染的DOM来看,这两者都是链接,都是a标签,区别是: Link标签是react-router里实现路由跳转的链接,一般配合Route使用,react-router接下了其默认的链接跳转行为,区别于传统的页面跳转,Link标签的"跳转"行为只会触发相匹配的Route对应的页面内容更新,而不会刷新整个页面<br />Link标签做的三件事情:
  7. - 1.有onclick那就执行onclick
  8. - 2.click的时候阻止a标签默认事件
  9. - 3.根据跳转href(即使是to),用history(web前端路由两种方式之一,history&hash)跳转,此时只是链接变了,并没有刷新页面
  10. 而标签就是普通的超链接了,用于从当前页面跳转到href指向的里一个页面(非锚点情况)
  11. <a name="NKi6y"></a>
  12. # 嵌套路由
  13. ![artew.gif](https://cdn.nlark.com/yuque/0/2021/gif/139415/1619677375263-be79f463-c8de-47db-879b-538f881cae78.gif#clientId=ub86ddfb5-b716-4&crop=0&crop=0&crop=1&crop=1&from=drop&id=ue8616235&margin=%5Bobject%20Object%5D&name=artew.gif&originHeight=275&originWidth=488&originalType=binary&ratio=1&rotation=0&showTitle=false&size=235029&status=done&style=stroke&taskId=u2a3683aa-d422-4f96-9597-a6ee91c2ef8&title=)
  14. <a name="PvrN4"></a>
  15. ##### src/index.js 一级路由
  16. ```jsx
  17. import React from 'react';
  18. import ReactDOM from 'react-dom';
  19. import { BrowserRouter as Router, Route, Switch, Link} from 'react-router-dom';
  20. import Home from './components/Home'
  21. import User from './components/User'
  22. import Profile from './components/Profile'
  23. ReactDOM.render(
  24. <Router basename="/m">
  25. <ul>
  26. <li><Link to="/">首页</Link></li>
  27. <li><Link to="/user">用户管理</Link></li>
  28. <li><Link to="/profile">个人中心</Link></li>
  29. </ul>
  30. <Switch>
  31. <Route exact path="/" component={Home} />
  32. <Route path="/user" component={User} />
  33. <Route path="/profile" component={Profile} />
  34. <Route render={props => <div>未发现路由页面</div>} />
  35. </Switch>
  36. </Router>,
  37. document.getElementById('root')
  38. );

src/components/User.js 二级路由
  1. import { Redirect, Route, Switch, Link} from 'react-router-dom';
  2. import UserList from './UserList';
  3. import UserAdd from './UserAdd';
  4. import UserDetail from './UserDetail';
  5. const User = (ps) => {
  6. console.log('User', ps);
  7. return (
  8. <div>
  9. <ul>
  10. <li><Link to="/user/list">用户列表</Link></li>
  11. <li><Link to="/user/add">添加用户</Link></li>
  12. </ul>
  13. <Switch>
  14. <Route path="/user/list" component={UserList} />
  15. <Route path="/user/add" component={UserAdd} />
  16. <Route path="/user/detail/:id" component={UserDetail} />
  17. <Redirect to="/user/list" />
  18. </Switch>
  19. </div>
  20. )
  21. }
  22. export default User;

src/utils.js 模拟数据
  1. export const UserAPI = {
  2. list(){
  3. let usersString = localStorage.getItem('users');
  4. let users = usersString ? JSON.parse(usersString) : [];
  5. return users;
  6. },
  7. add(user){
  8. let users = UserAPI.list();
  9. users.push(user);
  10. localStorage.setItem('users', JSON.stringify(users));
  11. },
  12. find(id){
  13. let users = UserAPI.list();
  14. return users.find(item => item.id === id);
  15. },
  16. }

src/components/UserList.js
  1. import React from 'react'
  2. import { Link} from 'react-router-dom';
  3. import { UserAPI } from '../utils'
  4. class UserList extends React.Component {
  5. state = {users: []}
  6. componentDidMount(){
  7. let users = UserAPI.list();
  8. this.setState({users});
  9. }
  10. render (){
  11. return (
  12. <ul>
  13. {this.state.users.map(item => (
  14. <li key={item.id}>
  15. <Link to={{
  16. pathname: `/user/detail/${item.id}`,
  17. state: item,
  18. }}>{item.name}</Link>
  19. </li>
  20. ))}
  21. </ul>
  22. )
  23. }
  24. }
  25. export default UserList;

src/components/UserAdd.js
  1. import React from 'react'
  2. import { UserAPI } from '../utils'
  3. class UserAdd extends React.Component {
  4. nameRef = React.createRef();
  5. handleSubmiit = (event) => {
  6. event.preventDefault();
  7. let name = this.nameRef.current.value;
  8. UserAPI.add({ id: Date.now() + '', name });
  9. this.props.history.push('/user/list');
  10. }
  11. render (){
  12. return (
  13. <form onSubmit={this.handleSubmiit}>
  14. <input type="text" ref={this.nameRef} />
  15. <button type="submit">提交</button>
  16. </form>
  17. )
  18. }
  19. }
  20. export default UserAdd;

src/components/UserDetail.js
  1. import React from 'react'
  2. import { UserAPI } from '../utils';
  3. class UserDetail extends React.Component {
  4. state = {user: {}}
  5. componentDidMount(){
  6. let user = this.props.location.state;
  7. if (!user){
  8. let id = this.props.match.params.id;
  9. user = UserAPI.find(id);
  10. }
  11. if (user) this.setState({user});
  12. }
  13. render (){
  14. let { user } = this.state;
  15. return (
  16. <div>
  17. <p>ID: {user.id}</p>
  18. <p>name: {user.name}</p>
  19. </div>
  20. )
  21. }
  22. }
  23. export default UserDetail;

受保护路由

进入需要权限的路由页面a,没权限的话(比如没登录),先去登录,登录后再跳回之前的路由页面a。

src/index.js
  1. import React from 'react';
  2. import ReactDOM from 'react-dom';
  3. import { BrowserRouter as Router, Route, Switch, Redirect, Link} from 'react-router-dom';
  4. import Home from './components/Home'
  5. import User from './components/User'
  6. import Profile from './components/Profile'
  7. import Protected from './components/Protected'
  8. import Login from './components/Login'
  9. ReactDOM.render(
  10. <Router basename="/m">
  11. <ul>
  12. <li><Link to="/">首页</Link></li>
  13. <li><Link to="/user">用户管理</Link></li>
  14. <li><Link to="/profile">个人中心</Link></li>
  15. </ul>
  16. <Switch>
  17. <Route exact path="/" component={Home} />
  18. <Route path="/user" component={User} />
  19. - {/* <Route path="/profile" component={Profile} /> */}
  20. + <Protected path="/profile" component={Profile} /> {/*如果用户登录了,才可以访问 profile 页面*/}
  21. + <Route path="/login" component={Login} />
  22. <Route render={props => <div>未发现路由页面</div>} />
  23. </Switch>
  24. </Router>,
  25. document.getElementById('root')
  26. );

src/components/Protected.js
  1. import { Route, Redirect } from 'react-router-dom'
  2. const Protected = (ps) => {
  3. let {path, component: RouteComponent} = ps;
  4. return (
  5. <Route path={path} render={routeProps => (
  6. localStorage.getItem('login') ? <RouteComponent {...routeProps} />
  7. : <Redirect to={{pathname: "/login", state: {from: path}}} />
  8. )} />
  9. )
  10. }
  11. export default Protected;

src/components/Login.js
  1. import React from 'react'
  2. class UserAdd extends React.Component {
  3. login = () => {
  4. let {props, state} = this;
  5. localStorage.setItem('login', 'true');
  6. let to = '/';
  7. if (props.location.state) to = props.location.state.from || '/';
  8. props.history.push(to);
  9. }
  10. render (){
  11. return (<button onClick={this.login}>登录</button>)
  12. }
  13. }
  14. export default UserAdd;

NavLink

一个特殊版本的Link,当它与当前 URL 匹配时,为其渲染元素添加样式属性。

activeClassName: string

要给出的元素的类处于活动状态时。默认的给定类是active。它将与className属性一起使用。

activeStyle: object

当元素处于active时应用于元素的样式。

exact

strict

isActive: func

一个为了确定链接是否处于活动状态而添加额外逻辑的函数,如果你想做的不仅仅是验证链接的路径名与当前 URL 的pathname是否匹配,那么应该使用它。

location

isActive比较当前的历史 location(通常是当前的浏览器 URL )。为了与不同的位置进行比较,可以传递一个location

示例
  1. <ul>
  2. <li>
  3. <NavLink
  4. className="nav-link"
  5. activeClassName="nav-link-active"
  6. style={{color: 'grey'}}
  7. activeStyle={{color: 'red'}}
  8. exact
  9. to="/">首页</NavLink>
  10. </li>
  11. <li>
  12. <NavLink
  13. className="nav-link"
  14. activeClassName="nav-link-active"
  15. style={{color: 'grey'}}
  16. activeStyle={{color: 'red'}}
  17. to="/user">用户管理</NavLink>
  18. </li>
  19. <li>
  20. <NavLink
  21. className="nav-link"
  22. activeClassName="nav-link-active"
  23. style={{color: 'grey'}}
  24. activeStyle={{color: 'red'}}
  25. to="/profile">个人中心</NavLink>
  26. </li>
  27. </ul>

image.png

withRouter

通过withRouter高阶组件访问history对象的属性和最近的match。 当路由渲染时,withRouter会将已经更新的match,location和history属性传递给被包裹的组件。

src/index.js
  1. import React from 'react';
  2. import ReactDOM from 'react-dom';
  3. import { BrowserRouter as Router, Route, Switch} from 'react-router-dom';
  4. import Home from './components/Home'
  5. // import User from './components/User'
  6. import Profile from './components/Profile'
  7. import NavBar from './components/NavBar'
  8. ReactDOM.render(
  9. <Router basename="/m">
  10. <NavBar title="返回首页" />
  11. <Switch>
  12. <Route exact path="/" component={Home} />
  13. {/* <Route path="/user" component={User} /> */}
  14. <Route path="/profile" component={Profile} />
  15. <Route render={props => <div>未发现路由页面</div>} />
  16. </Switch>
  17. </Router>,
  18. document.getElementById('root')
  19. );

src/components/NavBar.js
  1. import React from 'react';
  2. import {withRouter} from 'react-router-dom'
  3. function NavBar(props){
  4. console.log(props);
  5. return (
  6. <div onClick={() => props.history.push('/')}>
  7. {props.title}
  8. </div>
  9. )
  10. }
  11. export default withRouter(NavBar);

image.png

Prompt 跳转拦截

  1. export interface PromptProps {
  2. message: string | ((location: H.Location, action: H.Action) => string | boolean);
  3. when?: boolean;
  4. }
  5. export class Prompt extends React.Component<PromptProps, any> {}

src/components/Profile.js
  1. import { Prompt } from 'react-router-dom';
  2. const Profile = (ps) => {
  3. return (
  4. <div>
  5. <span>Profile</span>
  6. {/* Prompt 的 when 属性为true就阻止;否则不阻止 */}
  7. <Prompt when={true} message={(location, action) => {
  8. return `请问你真的要跳到${location.pathname}去吗?`;
  9. }} />
  10. </div>
  11. )
  12. }
  13. export default Profile;

image.png

hooks

  1. import React from 'react';
  2. import ReactDOM from 'react-dom';
  3. import { BrowserRouter as Router, Route, Switch, Link,
  4. useParams, useLocation, useHistory, useRouteMatch
  5. } from 'react-router-dom';
  6. function UserDetail(){
  7. let params = useParams(); //获取路径参数 {id: '1'}
  8. let history = useHistory(); //获取历史对象(history对象)
  9. let location = useLocation(); //获取路径对象 {pathname, search, hash, state, key}
  10. console.log(params, history, location,);
  11. return <div>{`id: ${params.id} -- name: ${location.state.name}`}</div>;
  12. }
  13. function Post(){
  14. //获取路由匹配match对象 {isExact, params, path, url}
  15. let match = useRouteMatch({
  16. path: '/post/:id',
  17. strict: false, //是否严格匹配
  18. sensitive: false, //大小写是否敏感
  19. });
  20. return match ? <div>{`id: ${match.params.id}`}</div> : <div>not found</div>
  21. }
  22. ReactDOM.render(
  23. <Router basename="/m">
  24. <ul>
  25. <li><Link to="/">首页</Link></li>
  26. <li><Link to={{pathname: `/user/detail/1`, state: {id:1, name: '张三'}}}>用户详情</Link></li>
  27. <li><Link to="/post/1">帖子</Link></li>
  28. </ul>
  29. <Switch>
  30. <Route exact path="/" component={Home} />
  31. <Route path="/user/detail/:id" component={UserDetail} />
  32. <Route path="/post" component={Post} />
  33. </Switch>
  34. </Router>,
  35. document.getElementById('root')
  36. );

路由懒加载 Suspense、lazy

Suspense

在动态导入的帮助下,React Suspense让我们轻松定义延迟加载的组件。

  1. // OtherComponent是通过懒加载加载进来的,所以渲染页面的时候可能会有延迟,
  2. // 但使用了Suspense之后,可优化交互。
  3. const OtherComponent = React.lazy(() => import('./OtherComponent'));
  4. // 在 <OtherComponent /> 外面使用Suspense标签,
  5. // 并在fallback中声明OtherComponent加载完成前做的事,即可优化整个页面的交互
  6. function MyComponent() {
  7. return (
  8. <div>
  9. <Suspense fallback={<div>Loading...</div>}>
  10. <OtherComponent />
  11. </Suspense>
  12. </div>
  13. );
  14. }

fallback 属性接受任何在组件加载过程中你想展示的 React 元素。你可以将 Suspense 组件置于懒加载组件之上的任何位置。你甚至可以用一个 Suspense 组件包裹多个懒加载组件。

  1. const OtherComponent = React.lazy(() => import('./OtherComponent'));
  2. const AnotherComponent = React.lazy(() => import('./AnotherComponent'));
  3. function MyComponent() {
  4. return (
  5. <div>
  6. <Suspense fallback={<div>Loading...</div>}>
  7. <section>
  8. <OtherComponent />
  9. <AnotherComponent />
  10. </section>
  11. </Suspense>
  12. </div>
  13. );
  14. }

lazy

示例1:
  1. import React, {Suspense, lazy} from 'react';
  2. import ReactDOM from 'react-dom';
  3. import { BrowserRouter as Router, Route, Switch} from 'react-router-dom';
  4. ReactDOM.render(
  5. <Router>
  6. <Switch>
  7. <Suspense fallback={<div>loading...</div>}>
  8. <Route exact path="/" component={lazy(() => import('./components/Home'))} />
  9. <Route path="/profile" component={lazy(() => import('./components/Profile'))} />
  10. </Suspense>
  11. </Switch>
  12. </Router>,
  13. document.getElementById('root')
  14. );

示例2:
  1. import React, {Suspense} from 'react';
  2. import ReactDOM from 'react-dom';
  3. import { BrowserRouter as Router, Route, Switch} from 'react-router-dom';
  4. const LazyHome = React.lazy(() => import('./components/Home'));
  5. const LazyProfile = React.lazy(() => import('./components/Profile'));
  6. function Loading(){
  7. return <div>loading...</div>;
  8. }
  9. ReactDOM.render(
  10. <Router basename="/m">
  11. <Switch>
  12. <Route exact path="/" component={(props) => (
  13. <Suspense fallback={<Loading />}>
  14. <LazyHome {...props} />
  15. </Suspense>
  16. )} />
  17. <Route path="/profile" component={(props) => (
  18. <Suspense fallback={<Loading />}>
  19. <LazyProfile {...props} />
  20. </Suspense>
  21. )} />
  22. </Switch>
  23. </Router>,
  24. document.getElementById('root')
  25. );