第9章:ReactRouter6

100 阅读11分钟

需求:

    1、点击左侧导航选项,展示对应的组件内容

    2、点击Home组件的菜单,显示二级路由的组件

    3、点击Message下的列表,显示三级路由的组件

    4、三级路由的组件同属一个,根据路由传参显示不同内容

9.1 一级路由

概要总结

    1、安装react-router-dom

    2、标签替换成

    3、标签里的component属性替换成element

一、安装react-router-dom最新版

    npm i react-router-dom

二、<Switch>标签替换成<Routes>

       在路由5的版本中,<Switch>标签的作用是优化路由匹配,从上往下匹配,匹配成功就跳出去不再往下匹配。

       在6的版本中,<Switch>标签被替换成<Routes>标签。

注意:标签不是必须使用的,而标签必须使用,否则会报错。

import {RoutesRoutefrom 'react-router-dom'
<div className="panel-body">
  {/* 注册路由 */}
  <Routes>
    <Route path="/about" component={About} />
    <Route path="/home" component={Home} />
  </Routes>
</div>

三、<Route>标签里的component属性替换成element

       component属性是用来指定组件,6的版本替换成element,而且组件的引入方式不一样,component的方式是组件名,element的方式是组件标签。例如:component={About},element={<About>}

import {RoutesRoute} from 'react-router-dom'
<div className="panel-body">
  {/* 注册路由 */}
  <Routes>
    <Route path="/about" element={<About/>} />
    <Route path="/home" element={<Home/>} />
  </Routes>
</div>

9.2 重定向

概要总结

    1、标签替换成标签

    2、标签的replace属性

    3、标签的caseSensitive属性

一、<Redirect>标签替换成<Navigate>标签

       在路由5的版本中,<Redirect>标签的作用是在没有匹配到路由的时候,跳转到指定的路由。

       在6的版本中,<Redirect>标签被替换成<Navigate>标签。它在注册路由中,跟普通的路由一致,只是在element属性里使用<Navigate>标签。

import {RoutesRouteNavigate} from 'react-router-dom'
<div className="panel-body">
  {/* 注册路由 */}
  <Routes>
    <Route path="/about" component={About} />
    <Route path="/home" component={Home} />
    <Route path="/" element={<Navigate to="/about" />} />
  </Routes>
</div>

二、<Navigate>标签的replace属性

       它有一个replace属性,默认值为false,就是默认为push模式,如果需要replace模式,可以设置为true。

import {RoutesRouteNavigatefrom 'react-router-dom'
<div className="panel-body">
  {/* 注册路由 */}
  <Routes>
    <Route path="/about" component={About} />
    <Route path="/home" component={Home} />
    <Route path="/" element={<Navigate to="/about" replace={true}/>} />
  </Routes>
</div>

三、<Route>标签的caseSensitive属性

       路由的匹配默认是不区分大小写的,如果在Route标签加上caseSensitive,那就区分了大小写,必须一致才能匹配。

import {RoutesRouteNavigate} from 'react-router-dom'
<div className="list-group">
  {/* 路由链接 */}
  <NavLink className="list-group-item" to="/about">AboutNavLink>
  <NavLink className="list-group-item" to="/home">HomeNavLink>
div>
<div className="panel-body">
  {/* 注册路由 */}
  <Routes>
    <Route path="/ABOUT" caseSensitive component={About} />
    <Route path="/home" component={Home} />
    <Route path="/" element={<Navigate to="/about" replace={true}/>} />
  </Routes>
</div>

9.3 NavLink高亮

概要总结

    1、<NavLink>的activeClassName替换成className

一、<NavLink>的activeClassName替换成className

       路由5的版本中,<NavLink>的activeClassName用来自定义高亮的class类名。在6的版本中,activeClassName取消掉,使用className的函数形式代替。

       在选中的时候,它会返回{isActive: true},非选中的时候,它会返回{isActive: false}

<div className="list-group">
  {/* 路由链接 */}
  <NavLink className={(a) => {console.log(a)}} to="/about">About</NavLink>
  <NavLink className="list-group-item" to="/home">Home</NavLink>
</div>

       我们可以通过解构赋值,根据isActive的值来动态加载class类名。

<div className="list-group">
  {/* 路由链接 */}
  <NavLink className={({isActive}) => isActive ? 'list-group-item atguigu': 'list-group-item'} to="/about">AboutNavLink>
  <NavLink className={({isActive}) => isActive ? 'list-group-item atguigu': 'list-group-item'} to="/home">HomeNavLink>
</div>

       优化代码,抽取切换className的方法。

function computedClassName({isActive}) {
  return isActive ? 'list-group-item atguigu': 'list-group-item'
}
<div className="list-group">
  {/* 路由链接 */}
  <NavLink className={computedClassName} to="/about">AboutNavLink>
  <NavLink className={computedClassName} to="/home">HomeNavLink>
</div>

9.4 useRoutes路由表

概要总结

    1、使用useRoutes

    2、独立配置路由信息

一、useRoutes路由表

       在注册路由的时候,模式是一样的,要配置的就是path和element。我们可以单独配置path和element,然后交给一个hook,由它帮我们生成注册路由的结构,这就是路由表。

    1、使用useRoutes

       useRoutes传入一个数组,数组每一项是一个对象,对象里对应的就是path和element,根据之前注册路由的写法一致即可。

import {NavLink, Navigate, useRoutes} from 'react-router-dom'
const element useRoutes([
  {path'about', element: },
  {path'home', element: },
  {path'/', element: }
])

<div className="panel-body">
  {/* 注册路由 */}
  {element}
</div>

    2、独立文件配置路由

       可以把所有关于路由配置的信息抽取到一个独立的文件,不影响具体的组件逻辑。

       routes/index.js:

import About from "../pages/About";
import Home from "../pages/Home";
import {Navigatefrom "react-router-dom";

export default [
  {path'about'element: },
  {path'home'element: },
  {path'/'element: }
]

       App.jsx:

import {NavLink, useRoutes} from 'react-router-dom'
import routes from './routes'

const element = useRoutes(routes)

<div className="panel-body">
  {/* 注册路由 */}
  {element}
</div>

9.5 嵌套路由

概要总结

    1、路由表配置子路由

    2、路由占位符

    3、子路由的to不带父级路径

    4、的end属性

一、路由表配置子路由

       在路由表配置子路由跟普通配置子路由一样,也是通过children属性,里面也是对象和path、element属性。

注意:在路由表的子路由,path不需要带上父级路由的路径,连/也不要带,直接写路由名即可。

import {Navigate} from 'react-router-dom';
import About from '../pages/About';
import Home from '../pages/Home';
import Message from '../pages/Message';
import News from '../pages/News';

export default [
  {
    path: 'about',
    element: <About/>
  },
  {
    path: 'home',
    element: <Home/>,
    children: [
      {
        path: 'news',
        element: <News/>
      },
      {
        path: 'message',
        element: <Message/>
      }
    ]
  },
  {
    path: '/',
    element: <Navigate to='/about'/>
  }
]

二、路由占位符<Outlet/>

       在路由5的版本中,路由是定义在组件里,路由定义在哪里,组件就显示在哪里。

       而版本6使用了路由表之后,需要在路由组件呈现的位置使用<Outlet/>标签。

import React from 'react';
import {NavLink, Outlet} from 'react-router-dom'

const Home = () => {
  return (
    <div>
      <h2>我是Home的内容</h2>
      <ul className="nav nav-tabs">
        <li>
          <NavLink className="list-group-item" to="/home/news">News</NavLink>
        li>
        <li>
          <NavLink className="list-group-item" to="/home/message">Message</NavLink>
        </li>
      </ul>
      {/* 指定路由组件呈现的位置 */}
      <Outlet/>
    </div>
  );
};

export default Home;

三、子路由的to不带父级路径

       在路由5的版本中,<NavLink>的子路由路径需要带上完整的路径。

       而版本6,<NavLink>子路由的to路径跟路由表的path一样,不需要带上父级路径,连/也不需要。

import React from 'react';
import {NavLink, Outlet} from 'react-router-dom'

const Home = () => {
  return (
    <div>
      <h2>我是Home的内容</h2>
      <ul className="nav nav-tabs">
        <li>
          <NavLink className="list-group-item" to="news">News</NavLink>
        </li>
        <li>
          <NavLink className="list-group-item" to="message">Message</NavLink>
        </li>
      </ul>
      {/* 指定路由组件呈现的位置 */}
      <Outlet/>
    </div>
  );
};

export default Home;

       它不带任何路径和/,其实就相当于相对路径,例如to="news"等同于to="./news"。

四、<NavLink>的end属性

       正常来说子路由高亮,父级路由也一起高亮。也可以通过<NavLink>的end属性,只让子路由高亮,父级路由不高亮。

注意:该属性设置在父级路由的标签里。

<div className="list-group">
  {/* 路由链接 */}
  <NavLink className="list-group-item" to="/about">About</NavLink>
  <NavLink className="list-group-item" end to="/home">Home</NavLink>
</div>

9.6 路由的params参数

概要总结

    1、路由params参数的传递与声明接收

    2、路由组件useParams接参

    3、路由组件useMatch接参

一、路由params参数的传递与声明接收

       在路由的params参数的传递与声明而言,版本5和6是一致的。

       路由表:

export default [
  {
    path'about',
    element<About/>
  },
  {
    path'home',
    element<Home/>,
    children: [
      {
        path'news',
        element<News/>
      },
      {
        path'message',
        element<Message/>,
        children: [
          {
            path'detail/:id/:title/:content',
            element<Detail/>
          }
        ]
      }
    ]
  },
  {
    path'/',
    element<Navigate to='/about'/>
  }
]

       pages/Message.jsx:

<div>
  <ul>
    {
      messages.map(m => {
        return (
          // 路由链接
          <li key={m.id}>
            <Link to={`detail/${m.id}/${m.title}/${m.content}`}>{m.title}</Link>
          </li>
        )
      })
    }
  </ul>
  <hr/>
  {/* 指定路由的展示位置 */}
  <Outlet/>
</div>

二、路由组件useParams接参

       在路由5的版本中,路由参数可以通过this.props里面的history、location、match里获取。

       在函数式组件里没有this,需要借助useParams来接参。这个useParams()直接返回一个对象,包含了所有的参数,直接解构赋值就可以取出来。

       pages/detail.jsx:

import React from 'react';
import {useParams} from 'react-router-dom'

const Detail = () => {

  const {id, title, content} = useParams()
  console.log(useParams())

  return (
    <ul>
      <li>消息编号:{id}</li>
      <li>消息标题:{title}</li>
      <li>消息内容:{content}</li>
    </ul>
  );
};

export default Detail;

三、路由组件useMatch接参

       除了useParams这个hook,还可以使用useMatch()获取,它必须要传一个注册路由的路径作为参数去匹配。

       pages/detail.jsx:

import React from 'react';
import {useParams, useMatch} from 'react-router-dom'

const Detail = () => {

  const {id, title, content} = useParams()
  console.log(useMatch('/home/message/detail/:id/:title/:content'))

  return (
    <ul>
      <li>消息编号:{id}</li>
      <li>消息标题:{title}</li>
      <li>消息内容:{content}</li>
    </ul>
  );
};

export default Detail;

       可以发现,它返回的对象之中,有一个params属性的值就是我们想要的params参数。

       这个相比useParams相对复杂一些,没有useParams好用。

9.7 路由的search参数

概要总结

    1、路由组件传递search参数

    2、useSearchParams钩子

    3、useLocation钩子

一、路由组件传递search参数

    1、路由链接传递search参数

       传递search参数,其实就是把参数通过?和&以键值对的方式拼在路径的后面,类似于ajax的query参数。

import React, {useState} from 'react'
import {LinkOutletfrom 'react-router-dom'

const Message = () => {

  const [messages] = useState([
    {id'001'title'消息1'content'锄禾日当午'},
    {id'002'title'消息2'content'汗滴禾下土'},
    {id'003'title'消息3'content'谁知盘中餐'},
    {id'004'title'消息4'content'粒粒皆辛苦'}
  ])

  return (
    <div>
      <ul>
        {
          messages.map(m => {
            return (
              // 路由链接
              <li key={m.id}>
                <Link to={`detail?id=${m.id}&title=${m.title}&`}>{m.title}</Link>
              </li>
            )
          })
        }
      </ul>
      <hr/>
      {/* 指定路由的展示位置 */}
      <Outlet/>
    </div>
  );
};

export default Message;

二、使用useSearchParams钩子

    1、获取search参数

       useSearchParam()返回两个值,一个是search值一个是更新search参数方法,search值通过get的方法传入键来获取对应的值。

import React from 'react';
import {useSearchParams} from "react-router-dom";

const Detail = () => {

  const [search, setSearch] = useSearchParams();
  const id = search.get('id')
  const title = search.get('title')
  const content = search.get('content')

  return (
    <ul>
      <li>消息编号:{id}</li>
      <li>消息标题:{title}</li>
      <li>消息内容:{content}</li>
    </ul>
  );
};

export default Detail;

    2、更新search参数

       更新search参数方法可以通过传入search参数进行改变。

import React from 'react';
import {useSearchParams} from "react-router-dom";

const Detail = () => {

  const [search, setSearch] = useSearchParams();

  return (
    <ul>
      <li>
        <button onClick={() => setSearch('id=008&title=哈哈&content=嘻嘻')}>点我更新一下收到的search参数</button>
      </li> ......
    </ul>
  );
};

export default Detail;

三、useLocation钩子

       在路由5的版本中,search参数是在props里的location属性里。所以在6的版本也有一个叫做useLocation的钩子给我们获取search参数。

import React from 'react';
import {useSearchParams, useLocation} from "react-router-dom";

const Detail = () => {

  ......
  console.log(useLocation())

  return (
    ......
  );
};

export default Detail;

9.8 路由的state参数

概要总结

    1、路由组件传递state参数

    2、通过useLocation钩子获取state参数

一、路由组件传递state参数

       在路由5的版本中,传递state参数,<Link>或者<NavLink>的to要传一个对象,对象里的pathname代表路由路径,而state是传参的键值对。

       6的版本中,标签有两个属性,一个是to就是路由名的字符串,state参数跟5版本一样传参数对象,不同的是,5的state是在to里面,而6的state跟to是平级的。

import React, {useState} from 'react'
import {Link, Outlet} from 'react-router-dom'

const Message = () => {

  const [messages] = useState([
    {id: '001', title: '消息1', content: '锄禾日当午'},
    {id: '002', title: '消息2', content: '汗滴禾下土'},
    {id: '003', title: '消息3', content: '谁知盘中餐'},
    {id: '004', title: '消息4', content: '粒粒皆辛苦'}
  ])

  return (
    <div>
      <ul>
        {
          messages.map(m => {
            return (
              // 路由链接
              <li key={m.id}>
                <Link
                  to="detail"
                  state={{
                    id: m.id,
                    title: m.title,
                    content: m.content
                  }}
                >
                  {m.title}
                </Link>
              </li>
            )
          })
        }
      </ul>
      <hr/>
      {/* 指定路由的展示位置 */}
      <Outlet/>
    </div>
  );
};

export default Message;

二、通过useLocation钩子获取state参数

       在路由5的版本中,可以从props里的location获取state参数。

       6的版本中,提供了useLocation钩子来获取。

import React from 'react';
import {useLocation} from 'react-router-dom'

const Detail = () => {
  const {state: {id, title, content}} = useLocation()
  console.log(useLocation())

  return (
    <ul>
      <li>消息编号:{id}</li>
      <li>消息标题:{title}</li>
      <li>消息内容:{content}</li>
    </ul>
  );
};

export default Detail;

9.9 编程式路由导航

概要总结

    1、useNavigate钩子的使用

需求:

    1、点击查看详情,可以调转路由并携带参数

    2、点击前进后退,可以实现路由的前进和后退

一、useNavigate钩子实现编程式路由导航

       编程式路由导航,简单来说就是不借助或者,而是通过代码来实现路由跳转。

       路由5的版本是用this.props.history的push和replace两个方法来实现。6的版本使用useNavigate钩子实现。

    1、实现路由跳转

       使用useNavigate()钩子跳转很简单,直接传路由路径即可。

import React, {useState} from 'react'
import {LinkOutlet, useNavigate} from 'react-router-dom'

const Message = () => {

  const [messages] = useState([
    {id: '001'title: '消息1'content: '锄禾日当午'},
    ......
  ])

  const navigate = useNavigate()
  function showDetail() {
    navigate('/about')
  }

  return (
    <div>
      ......
      <hr/>
      {/* 指定路由的展示位置 */}
      <Outlet/>
    </div>
  );
};

export default Message;

    2、实现路由传参

       useNavigate的第二个参数是用于传参的,它是一个对象,有两个参数:replace可以设置路由模式,boolean类型;state属性就是传参的,把要传的参数通过键值对传过去即可。

const Message = () => {
  const [messages] = useState([
    {id: '001'title: '消息1'content: '锄禾日当午'},
    ......
  ])
  const navigate = useNavigate()
  function showDetail(m) {
    navigate('detail', {
      replace: false,
      state: {
        id: m.id,
        title: m.title,
        content: m.content
      }
    })
  }

  return (
    <div>
      <ul>
        {
          messages.map(m => {
            return (
              // 路由链接
              <li key={m.id}>
                ......
                <button onClick={() => showDetail(m)}>查看详情</button>
              </li>
            )
          })
        }
      </ul>
      <hr/>
      {/* 指定路由的展示位置 */}
      <Outlet/>
    </div>
  );
};

export default Message;

    3、实现前进和后退

       在路由5的版本中,history提供了go、goBack和goForward三个方法,用于控制路由的前进和后退。

       在6的版本就没有那么多api,还是用这个useNavigate钩子,传一个数字过去即可实现前进后退。

import React from 'react';
import {useNavigate} from 'react-router-dom'

const Header = () => {
  const navigate = useNavigate()

  function back() {
    navigate(-1)
  }

  function forward() {
    navigate(1)
  }

  return (
    <div className="col-xs-offset-2 col-xs-8">
      <div className="page-header">
        <h2>React Router Demo</h2>
        <button onClick={back}>后退</button>
        <button onClick={forward}>前进</button>
      </div>
    </div>
  );
};

export default Header;

注意:对于一般组件,在5的版本它们是不具备路由的方法,除非使用了withRouter。在6的版本,只需要引入useNavigate钩子都可以使用。

9.10 useInRouterContext

概要总结

    1、useInRouterContext钩子:判断组件是否在路由环境

一、useInRouterContext钩子

       如果组件在<Router>的上下文中呈现,则useInRouterContext钩子返回true,否则返回false。

       简单来说,如果组件是在<BrowserRouter>或者<HashRouter>包裹下的,那么所有的子组件全都属于在<Router>的上下文,返回true。否则返回false。

       例如:<App/>组件是在<Router>里,<Demo>在<Router>外

// 引入ReactDOM的createRoot
import {createRoot} from 'react-dom/client'
// 引入App组件
import App from './App'
import {BrowserRouterfrom 'react-router-dom'
import Demo from './components/Demo'

const containter = document.getElementById('root')
const root = createRoot(containter)
// 渲染App到页面
root.render(
  <div>
    <Demo/>
    <BrowserRouter>
      <App/>
    </BrowserRouter>
  </div>
)
export default root

       Demo.jsx:

import React from 'react';
import {useInRouterContext} from 'react-router-dom'

const Demo = () => {
  console.log('Demo:' + useInRouterContext())     // Demo:false
  return (
    <div>Demo</div>
  );
};

export default Demo;

       App.jsx:

import React from 'react';
import {NavLink, useRoutes, useInRouterContext} from 'react-router-dom'
import routes from './routes'
import Header from './components/Header'

export default function App() {

  // 根据路由表生产对应的路由规则
  const element = useRoutes(routes)
  console.log('App:'useInRouterContext())     // App:true

  return (
    <div>
      ......
    </div>
  );
}

9.11 useNavigationType

概要总结

    1、useNavigationType钩子:返回当前的导航类型

一、useNavigationType钩子

    1、作用

       返回当前的导航类型(用户是如何来到当前页面的)

    2、返回

       它的返回值有POP、PUSH、REPLACE

    3、备注

       POP是指在浏览器中直接打开了这个路由组件(刷新页面)

    pages/News.jsx

import React from 'react';
import {useNavigationType} from 'react-router-dom'

const News = () => {
  console.log(useNavigationType())
  return (
    <ul>
      <li>news001</li>
      <li>news002</li>
      <li>news003</li>
    </ul>
  );
};

export default News;

       点击路由跳转:PUSH或者REPLACE

       刷新页面:POP

9.12 useOutlet

概要总结

    1、useOutlet钩子:返回当前的导航类型

一、useOutlet钩子

    1、作用

       用来呈现当前组件中渲染的嵌套路由。

    2、备注

       (1)如果嵌套路由没有挂在,则result为null

       (2)如果嵌套路由已经挂在,则暂时嵌套的路由对象

    pages/Home.jsx:

import React from 'react';
import {NavLinkOutlet, useOutlet} from 'react-router-dom'

const Home = () => {
  console.log('嵌套路由'useOutlet())
  return (
    <div>
      <h2>我是Home的内容</h2>
      <ul className="nav nav-tabs">
        <li><NavLink className="list-group-item" to="news">News</NavLink></li>
        <li><NavLink className="list-group-item" to="message">Message</NavLink></li>
      </ul>
      {/* 指定路由组件呈现的位置 */}
      <Outlet/>
    </div>
  );
};

export default Home;

    嵌套路由没加载:

    嵌套路由已加载:

9.13 useResolvedPath

概要总结

    1、useResolvedPath钩子:解析路径的path、search、hash值

一、useResolvedPath钩子

    1、作用

       给定一个URL值,解析其中的path、search、hash值

    pages/News.jsx:

import React from 'react';
import {useResolvedPath} from 'react-router-dom'

const News = () => {
  console.log('/user?id=001&name=tom#que', useResolvedPath('/user?id=001&name=tom#que'))
  return (
    <ul>
      <li>news001</li>
      <li>news002</li>
      <li>news003</li>
    </ul>
  );
};

export default News;

9.14 代码链接

gitee.com/huang_jing_…