重学React之react-router

633 阅读6分钟

我们知道,现在众多框架都有自己的路由设计。但是实现思路都是两种模式。hash和history模式。在vue-router中写过,在这里就不在赘述。需要的话请访问这里。

react-router升级后的变化

我们知道,自从react-router更新到v6,发生了不小的变化。

  • 内置组件的变化:移除<Switch/> ,新增 <Routes/>等。
  • 语法的变化:component={About} 变为 element={<About/>}等。
  • 新增多个hook:useParamsuseNavigateuseMatch等。
  • NavLink组件修改默认活跃className值。
  • 重定向组件。移除<Redirect />, 新增<Navigate />
  • 等等...

内置组件API的介绍

下面,我们来介绍一下基本的API用法。

<BrowserRouter>

  • <BrowserRouter>用于包裹整个应用。
    import React from "react";
    import ReactDOM from "react-dom";
    import { BrowserRouter } from "react-router-dom";
    
    ReactDOM.render(
      <BrowserRouter>
        {/* 整体结构(通常为App组件) */}
      </BrowserRouter>,root
    );

<HashRouter>

  • 作用与<BrowserRouter>一样,但<HashRouter>修改的是地址栏的hash值。官方明确说明We strongly recommend you do not use HashRouter unless you absolutely have to.所以一般情况不会去使用它。
  • 6.x版本中<HashRouter><BrowserRouter> 的用法与 5.x 相同。

<Routes/> 与 <Route/>

  • v6版本中移出了先前的<Switch>,引入了新的替代者:<Routes>
  • <Routes><Route>要配合使用,且必须要用<Routes>包裹<Route>
  • <Route> 相当于一个 if 语句,如果其路径与当前 URL 匹配,则呈现其对应的组件。
  • <Route caseSensitive> 属性用于指定:匹配时是否区分大小写(默认为 false)。
  • 当URL发生变化时,<Routes>都会查看其所有子<Route> 元素以找到最佳匹配并呈现组件 (从上往下匹配)。从上到下优先级一次降低。所以一般设置最后一个route为*,然后设置错误页面。用于匹配未设置的页面。
  • <Route> 也可以嵌套使用,且可配合useRoutes()配置 “路由表” ,但需要通过 <Outlet> 组件来渲染其子路由。
    <Routes>
        {/*path属性用于定义路径,element属性用于定义当前路径所对应的组件*/}
        <Route path="/login" element={<Login />}></Route>
        
        {/*用于定义嵌套路由,home是一级路由,对应的路径/home*/}
        <Route path="home" element={<Home />}>
           {/*test1 和 test2 是二级路由,对应的路径是/home/test1 或 /home/test2*/}
          <Route path="test1" element={<Test/>}></Route>
          <Route path="test2" element={<Test2/>}></Route>
        </Route>

        {/*Route也可以不写element属性, 这时就是用于展示嵌套的路由 .所对应的路径是/users/xxx */}
        <Route path="users">
           <Route path="xxx" element={<Demo />} />
        </Route>
    </Routes>

<Link>

  • 修改URL,且不发送网络请求(路由链接)。
  • 外侧需要用<BrowserRouter><HashRouter>包裹。
    import { Link } from "react-router-dom";
    
    function Test() {
      return (
        <div>
          <Link to="/路径">按钮</Link>
        </div>
      );
    }

<NavLink>

  • <Link>组件类似,且可实现导航的“高亮”效果。
  • className内部维护了一个isAction属性。当前活跃的路由为true,其他路由为false。每次点击NavLink,都会执行className传入的回调函数。不管点击的是哪一个NavLink。
  • end属性,表示只会匹配一个高亮路由,当子路由匹配。添加在父路由上。
// 注意: NavLink默认类名是active,下面是指定自定义的class

//自定义样式
<NavLink
    to="login"
    className={({ isActive }) => { // 如果匹配到当前路由,那么isActive为true。否则为false。
        return isActive ? 'login_active' : ''
    }}
>login</NavLink>

/*
  默认情况下,当Home的子组件匹配成功,Home的导航也会高亮,
  当NavLink上添加了end属性后,若Home的子组件匹配成功,则Home的导航没有高亮效果。
*/
<NavLink to="home" end >home</NavLink>

<Navigate>

  • 只要<Navigate>组件被渲染,就会修改路径,切换视图。取代了V5中的<Redirect />
  • replace属性用于控制跳转模式(push 或 replace,默认是push)。表示是否会将当前路由状态放历史记录中。
    import React,{useState} from 'react'
    import {Navigate} from 'react-router-dom'
    
    export default function Home() {
      const [sum,setSum] = useState(1)
      return (
        <div>
          <h3>我是Home的内容</h3>
          {/* 根据sum的值决定是否切换视图 */}
          {sum === 1 ? <h4>sum的值为{sum}</h4> : <Navigate to="/about" replace={true}/>}
          <button onClick={()=>setSum(2)}>点我将sum变为2</button>
        </div>
      )
    }

<Outlet>

  • <Route>产生嵌套时,渲染其对应的后续子路由。
    //根据路由表生成对应的路由规则
    const element = useRoutes([
      {
        path:'/about',
        element:<About/>
      },
      {
        path:'/home',
        element:<Home/>,
        children:[
          {
            path:'news',
            element:<News/>
          },
          {
            path:'message',
            element:<Message/>,
          }
        ]
      }
    ])
    
    //Home.js
    import React from 'react'
    import {NavLink,Outlet} from 'react-router-dom'
    
    export default function Home() {
      return (
        <div>
          <h2>Home组件内容</h2>
          <div>
            <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>
        </div>
      )
    }

内置hooks的介绍

useRoutes()

  • 根据路由表,动态创建<Routes><Route>
    //路由表配置:src/routes/index.js
    import About from '../pages/About'
    import Home from '../pages/Home'
    import {Navigate} from 'react-router-dom'
    
    export default [
      {
        path:'/about',
        element:<About/>
      },
      {
        path:'/home',
        element:<Home/>
      },
      {
        path:'/',
        element:<Navigate to="/about"/>
      }
    ]
    
    //App.jsx
    import React from 'react'
    import {NavLink,useRoutes} from 'react-router-dom'
    import routes from './routes'
    
    export default function App() {
      //根据路由表生成对应的路由规则
      const element = useRoutes(routes)
      return (
        <div>
          ......
          {/* 注册路由 */}
          {element}
          ......
        </div>
      )
    }
    
    // index.js
    import {BrowserRouter} from 'react-router-dom'

    ReactDOM.render(
        <BrowserRouter>
            <App/>
        </BrowserRouter>, 
        document.getElementById('root')
    )

如果使用了路由映射表方式渲染路由,那么我们需要将根组件包裹<BrowserRouter />,不然会报这种错误。他的意思是我们使用路由映射表映射的路由都必须包裹在同一个<BrowserRouter />下。 image.png

useNavigate()

  • 返回一个函数用来实现编程式导航。
  • 第一个参数表示跳转的地址,第二个参数表示匹配项。(replace?: boolean; state?: any)。
import React from 'react'
import {useNavigate} from 'react-router-dom'

export default function Demo() {
  const navigate = useNavigate()
  const handle = () => {
    //第一种使用方式:指定具体的路径
    navigate('/login', {
      replace: false,
      state: {a:1, b:2}
    }) 
    //第二种使用方式:传入数值进行前进或后退,类似于5.x中的 history.go()方法
    navigate(-1)
  }

  return (
    <div>
      <button onClick={handle}>按钮</button>
    </div>
  )
}

useParams()

  • 获取当前匹配路由的params参数,类似于5.x中的match.params
    import React from 'react';
    import { Routes, Route, useParams } from 'react-router-dom';
    import User from './pages/User.jsx'

    function ProfilePage() {
      // 获取URL中携带过来的params参数
      let { id } = useParams();
    }

    function App() {
      return (
        <Routes>
          <Route path="profilePage/:id" element={<ProfilePage />}/>
        </Routes>
      );
    }

useSearchParams()

  • 用于读取和修改当前的 URL 中的查询字符串。
  • 返回一个包含两个值的数组,内容分别为:当前的seaech参数、更新search的函数。
  • 当我们修改当前url的查询字符时,他不会在执行获取表达式。
    import React from 'react'
    import {useSearchParams} from 'react-router-dom'

    export default function Detail() {
      const [search,setSearch] = useSearchParams()
      const id = search.get('id')
      const title = search.get('title')
      const content = search.get('content')
      return (
        <ul>
          <li>
            <button onClick={()=>setSearch('id=008&title=哈哈&content=嘻嘻')}>点我更新一下收到的search参数</button>
          </li>
          <li>消息编号:{id}</li>
          <li>消息标题:{title}</li>
          <li>消息内容:{content}</li>
        </ul>
      )
    }

useLocation()

  • 获取当前 location 信息,对标5.x中的路由组件的location属性。
    import React from 'react'
    import {useLocation} from 'react-router-dom'

    export default function Detail() {
      const x = useLocation()
      console.log(x) // x就是location对象 
      /*
        {
          hash: "",
          key: "ah9nv6sz",
          pathname: "/login",
          search: "?name=zs&age=18",
          state: {a: 1, b: 2}
        }
      */
      return (
        <ul>
          <li>消息编号:{id}</li>
          <li>消息标题:{title}</li>
          <li>消息内容:{content}</li>
        </ul>
      )
    }

useMatch()

  • 返回当前匹配信息,对标5.x中的路由组件的match属性。
  • 需要指定详细的url,用于获取对应的url匹配信息。
<Route path="/login/:page/:pageSize" element={<Login />}/>
<NavLink to="/login/1/10">登录</NavLink>

export default function Login() {
  const match = useMatch('/login/:x/:y')
  console.log(match) //输出match对象
  //match对象内容如下:
  /*
    {
      params: {x: '1', y: '10'}
      pathname: "/LoGin/1/10"  
      pathnameBase: "/LoGin/1/10"
      pattern: {
        path: '/login/:x/:y', 
        caseSensitive: false, 
        end: false
      }
    }
  */
  return (
    <div>
      <h1>Login</h1>
    </div>
  )
}

useInRouterContext()

  • 如果组件在 <Router> 的上下文中呈现,则 useInRouterContext 钩子返回 true,否则返回 false。
  • 一般情况下,我们给根组件包裹BrowerRouter后,不管在哪个组件中打印useInRouterContext(),都会返回true。

useNavigationType()

  • 返回当前的导航类型(用户是如何来到当前页面的)。
  • 返回值:POPPUSHREPLACE
  • POP是指在浏览器中直接打开了这个路由组件(刷新页面)。

useOutlet()

  • 用来呈现当前组件中渲染的嵌套路由。
    const result = useOutlet()
    console.log(result)
    // 如果嵌套路由没有挂载,则result为null
    // 如果嵌套路由已经挂载,则展示嵌套的路由对象

useResolvedPath()

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

学习自尚硅谷的react课程。