react-router-dom

前言

react-router-dom 是 react 开发中必备的路由库,使用也简单方便入手,它将我们的的页面和路由关联起来,因此也避免了很多隐性变量的使用,某种程度上降低了我们不同页面之间的依赖,并且可以直接通过路由定位到指定页面功能,可以说非常实用

下面就简单介绍下平时比较常用的功能吧 -- 简易案例demo

react-router-dom

下面会从其安装、使用、传参等功能介绍

安装

js 复制代码
yarn add react-router-dom

BrowserRouter

平时使用 router 时,外层第一步就是要将我们的根组件 app 父节点设置为 BrowserRouter,这样我们的项目才能够正常使用路由功能

我们可能除了 BrowserRouter,还会见到 HashRouter,这个主要在客户端使用的散列值方案,用的比较少,一般都是 BrowserRouter,就不多介绍了

js 复制代码
root.render(
  <React.StrictMode>
    <BrowserRouter>
      <App />
    </BrowserRouter>
  </React.StrictMode>
);

Routes、Route、Navigate

Routes、Route、Navigate 分别是 路由组、路由、导航

我们的所有路由都由 Routes 包裹,可以认为放到了一个集合中统一管理

Route 就是我们的单个路由设置信息,其主要包含了我们的路由路径 path,节点指向的组件 element,大小写区分等,并且 Route 具备子 Route 功能,即包含子路由(二级路由,三级路由等),可以让我们的项目更有层次感

Navigate 导航功能,可以配合我们的 Route 实现重定向功能,可以将某个页面路由导航到另外一个路由 path

path 路由,/ 开头的为绝对路由,无/则为相对路由,即一个根据根节点,一个根据上级节点,*为匹配所有路由,优先级比较低,一般用来编写非法路由提示页面

如下所示,加入了一些基本路由和子路由

js 复制代码
return (
    <Routes>
      <Route path='/home' element={<HomeView />} >
        <Route path='index1' element={<HomeIndex1View />} />
        <Route path='index2' element={<HomeIndex2View />} />
        <Route path='index3' element={<HomeIndex3View />} />
        <Route path='index4' element={<HomeIndex4View />} />
      </Route>

      <Route path='/login' element={<LoginView />} />
      {/* 重定向 */}
      <Route path="/" element={<Navigate to="/login" />} />

      {/* 匹配所有路由,优先级比较低 */}
      <Route path='*' element={<NotFoundView />} />
    </Routes>
  );

Outlet

Outlet 用于展示对应路由显示的组件,可以嵌入到指定组件位置,其更像是上面 Routes 一系列的替代品,如果不写他,就要在对应地点写上上面一堆重复的 Routes 一系列组件,否则是无法正常显示的,正常就一个公用的导航页面倒是有这么写的,不过不推荐,推荐使用上面那种加上 outlet

使用时,其会根据路由以及所处的层级,自动展示指定路由对应的组件

js 复制代码
// /home
<div>
       ...
    <div>
        <Outlet /> //这里及时上面 homeIndex1View 页面显示的位置
    </div>
</div>

Link、NavLink、useNavigate

他们三个都是用来跳转路由的,只不过 Link、NavLink是显示跳转组件,useNavigate 是手动跳转

Link、NavLink 两个类似,如下所示,通过 to 设置跳转路径,state 传递参数

js 复制代码
<Link to='/home/index3' state='我是link传递的信息' >link跳转</Link>

<NavLink to='/home/index4' state='我是navlink传递的信息' >navlink跳转</NavLink>

useNavigate 为手动跳转,也可以传递参数,看名字就知道是 hook 方式的写法

js 复制代码
const view = () => {
    const navigate = useNavigate()
    
    ...
    navigate(path, {
        state: '我传递的信息'
    })
}

接收参数(useLocation、useSearchParams)

前面我们通过 state 传递参数,这里可以通过 useLocation 来接收参数,一般也都是使用这个手段来传递参数

js 复制代码
const location = useLocation()

//取出里面的 state 即可
location.state

query + useSearchParams 传参

当然也有狠人,直接在路由中拼出了 query 类型的参数,我们也可以通过 useSearchParams来获取

js 复制代码
//例如传递参数是通过 query 的方式
navigate('/home?id=123')

使用 useSearchParams 来获取 query 类型数据的内容

js 复制代码
const params = useSearchParams()
...
params[0].get('id')

嵌套路由

前面讲了如何使用 Routes,如何使用 Outlet来完成我们的路由功能,然后只介绍了一级路由,如果有二级路由(路由里面还有路由,Outlet 还有 Outlet 该怎么办呢,其实逻辑跟第一个一样,就是多嵌了一个 Outlet 罢了)

如下面页面所示,做了一个有一级,一级里面一个页面还嵌套了一个二级路由的页面(页面丑了点,毕竟是案例哈),实际上逻辑并不复杂,也是提前使用 Routes 配置好路由关系,使用 Outlet 来放位置

ps:配置这种路由时,最好将我们外层的宽高都设置成 100%,否则内部可能后续很难控制,或者适配的更好(用media 写一堆的当我没说)

js 复制代码
body {
  ...
  width: 100%;
  height: 100%;
}

html {
  width: 100%;
  height: 100%;
}

/* id选择器 */
#root{
  height: 100%;
  width: 100%;
}

配置 Routes

js 复制代码
<Routes>
  <Route path='/home' element={<HomeView />} >
    <Route path='index1' element={<HomeIndex1View />} />
    <Route path='index2' element={<HomeIndex2View />} />
    <Route path='index3' element={<HomeIndex3View />} />
    <Route path='index4' element={<HomeIndex4View />} />

    //可以看到,我们在 settting 中嵌入了两个子路由,setting 也在 home 路由中
    <Route path='setting' element={<SettingView />}>
      <Route path='user' element={<SettingUserView />} />
      <Route path='love' element={<SettingLoveView />} />
    </Route>
  </Route>

  <Route path='/login' element={<LoginView />} />

  {/* 匹配所有路由,优先级比较低 */}
  <Route path='*' element={<NotFoundView />} />
</Routes>

一级路由 Home

上面导航,下面子路由 Outlet

js 复制代码
// titles path除了跳转用,也会用来对比是否是当前页面
// 对于存在二级路由的,则可以额外添加参数判断,避免不正常显示
const titles = [{
    name: '首页一',
    path: '/home/index1'
}, {
    name: '首页二',
    path: '/home/index2'
}, {
    name: '首页三',
    path: '/home/index3'
}, {
    name: '设置页面',
    path: '/home/setting/user',
    matchpath: '/home/setting' //用于处理外层包含关系的内容
}]


<div style={{ width: '100%', height: '100%', display: 'flex', flexDirection: 'column' }}>
    //上面的内容
    <div style={{ width: '100%', height: 100, display: 'flex', justifyContent: 'space-evenly', alignItems: 'center', backgroundColor: '#999' }}>
        <Link to='/home/index3' state='我是link传递的信息' >link跳转</Link>
        <NavLink to='/home/index4' state='我是navlink传递的信息' >navlink跳转</NavLink>

        {
            titles.map(function (item, index) {
                return (
                    <div 
                        key={index} 
                        style={{ 
                            cursor: 'pointer',
                            backgroundColor: pathname.includes(item.matchpath ? item.matchpath : item.path) ? 'skyblue' : '#fff'
                        }} 
                        onClick={() => {
                            navigate(item.path, {
                                    state: '我传递的信息是' + item.path
                            })
                            setPathname(item.path)
                        }} >{item.name}</div>
                )
            })
        }
        <div style={{ cursor: 'pointer', color: 'red' }} onClick={() => {
            navigate('/login')
        }}>退出登录</div>
    </div>
    //底部路由组件显示内容
    <div style={{ display: 'flex', width: '100%', height: '100%' }}>
        <Outlet />
    </div>
</div>

二级路由 Setting

上面一级路由,下面左侧二级路由,下面右侧,二级路由子页面

js 复制代码
<div style={{ flex: 1, display: 'flex' }}>
    //底部左侧导航
    <div style={{ width: 100, height: '100%', display: 'flex', flexDirection: 'column', justifyContent: 'space-evenly', alignItems: 'center', backgroundColor: '#ddd' }}>
        {
            titles.map(function (item, index) {
                return (
                    <div
                        key={index}
                        style={{
                            cursor: 'pointer',
                            backgroundColor: pathname === item.path ? 'skyblue' : '#fff'
                        }}
                        onClick={() => {
                            navigate(item.path, {
                                    state: '我传递的信息是' + item.path
                            })
                            setPathname(item.path)
                        }} >{item.name}</div>
                )
            })
        }
    </div>
    //底部右侧子路由组件显示内容
    <div style={{ display: 'flex', flex: 1, margin: 40 }}>
        <Outlet />
    </div>
</div>

最后

到这里就差不多了,相信三级路由也知道怎么搞了,然后再加入一个 NotFound 组件,再加入一个重定向,避免错误路由,或者局部路由出现

我们将 / 重定向到 login 相信就知道啥意思了,默认跳转到登陆页面即可,home、setting的子页面也类似

NotFound 除了主路由,为何每个子路由中也放置了呢,因为要替换子路由没有的页面,如果使用最外层,会覆盖掉整个页面,写到里面,会在子路由显示的模块中显示我们的 NotFound 模块,这样也能知道哪个路由出错了

js 复制代码
<Routes>
  <Route path='/home' element={<HomeView />} >
    <Route path='index1' element={<HomeIndex1View />} />
    <Route path='index2' element={<HomeIndex2View />} />
    <Route path='index3' element={<HomeIndex3View />} />
    <Route path='index4' element={<HomeIndex4View />} />
    {/* 重定向 */}
    <Route path="" element={<Navigate to="index1" />} />

    <Route path='setting' element={<SettingView />}>
      <Route path='user' element={<SettingUserView />} />
      <Route path='love' element={<SettingLoveView />} />
      {/* 重定向 */}
      <Route path="" element={<Navigate to="user" />} />

      <Route path='*' element={<NotFoundView />} />
    </Route>

    <Route path='*' element={<NotFoundView />} />
  </Route>

  <Route path='/login' element={<LoginView />} />
  {/* 重定向 */}
  <Route path="/" element={<Navigate to="/login" />} />

  {/* 匹配所有路由,优先级比较低 */}
  <Route path='*' element={<NotFoundView />} />
</Routes>

历史记录 history

这个也是用的比较多的,一般用来恢复某个指定页面的显示效果,我们需要该功能来完善我们的页面使得回退重新打开指定页面也能够有一个正常的显示效果

导入history

js 复制代码
yarn add history

获取历史对象

我们需要使用 createBrowserHistory 获取历史记录对象,通过其使用历史记录功能

js 复制代码
const history = createBrowserHistory()

创建监听、销毁监听

我们通过使用 historylisten 来监听路由的变化,通过该路由变化,我们可以用来更新我们的页面状态

使用完毕记得主动销毁,否则会出现意想不到的效果,监听对象会返回取消该监听的函数,直接调用即可取消监听

如果有多级路由,那么多个地方使用 一般全局使用一个 history 对外导出即可,其他页面使用完毕后销毁即可

js 复制代码
通过保存 pathname 进行对比,来判断进入了那个路由页面
const [pathname, setPathname] = useState<string>(location.pathname)

useEffect(() => {
    //返回时会监听到
    const unlisten = history.listen((res) => {
        setPathname(res.location.pathname)
    })
    return () => {
        //销毁时记得取消监听
        unlisten()
    }
}, [])

ps:只有一个单页面不存在销毁不销毁的,不取消监听似乎也没啥问题哈

useRoutes

Routes 使用类似, 只不过是使用 hook 的方式,使用一个数组包含所有的对象,可以减少我们的代码

js 复制代码
const routes = useRoutes([
    { 
      path: '/home', 
      element: <HomeView />, 
      children: [
        {path: 'index1', element: <HomeView />},
        {path: 'index2', element: <HomeIndex2View />},
        {path: 'index3', element: <HomeIndex3View />},
        {path: 'index4', element: <HomeIndex4View />},
        {path: '', element: <Navigate to="index1" />},
    ]},
    {path: '/login', element: <LoginView />},
    {path: '/', element: <Navigate to="/login" />},
  ])

实际使用起来两个都差不太多

动态路由

简而言之就是动态的路由,上面的 Routes、useRoutes,我们看到了,我们的路由默认弄出了所有的路由

对于一些严谨的项目来说,这就出现小漏洞了,就是我们用户如果没有进入某个页面的权限,那么如果强行输入对应路由,也是可以进入的,除非内部控制进去不让出来,否则还是可以进去的,要不每个页面都判断权限跳出,这也是一笔不小的工作量

那么动态路由怎么做呢,很简单,根据用户所拥有的权限,来动态调整更换整个 Routes

其一般有两个操作方案,一个是后端给好权限类型,前端根据不同权限筛选出应当存在的页面路由(实际上相对于第二步多了个翻译过程),第二个是后台直接给出该用户能访问的整个路由结构,然后前端根据返回的指定路由集合来配置路由

以上面项目页面为样本,使用第一种方案,设置一个需求:游客只有基础功能(home), 普通用户多了登录(home + login),没有设置,admin账户有设置(home + login + /home/setting),对于两者后台返回权限 1、3(1 + 2)、7(1 + 2 + 4),设置权限是2,也有可能返回数组,就以前面位操作为例

js 复制代码
//home
const routes1 = () => {
    return { 
      path: '/home', 
      element: <HomeView />, 
      children: [
        {path: 'index1', element: <HomeIndex1View />},
        {path: 'index2', element: <HomeIndex2View />},
        {path: 'index3', element: <HomeIndex3View />},
        {path: 'index4', element: <HomeIndex4View />},
        {path: '', element: <Navigate to="index1" />},
    ]}
}
//login
const routes2 = () => {
    return {
        path: '/login', 
        element: <LoginView />
    }
}
// home/setting
const routes3 = () => {
    return { 
      path: 'setting', 
      element: <SettingView />, 
      children: [
        {path: 'user', element: <SettingUserView />},
        {path: 'love', element: <SettingLoveView />},
        {path: '', element: <Navigate to="index1" />},
    ]},
}


const setupPermission = () => {
    //后端返回权限 permission
    const permission = ...

    const routeNames = []
    if (permission & 1) {
        //获取home
        const routes = routes1()
        routeNames.push(routes)
        //满足设置我们需要再子路由加入setting
        if (permission & 4) {
            routes.children.push(routes3())
        }
    }
    //加入登录
    if (permission & 2) {
         routeNames.push(routes2())
    }
    setRouteNames(retes[2])
}


const getRoutes = (children: any[]) => {
    return children.map((e, index) => {
      return (
        <Route key={index} path={e.path} element={e.element} >
          {
            e.children && getRoutes(e.children)
          }
        </Route>
      )
    })
  }

<Routes>
    {
        getRoutes(routeNames)
    }
</Routes>

ps:除了上面这些,我们除了更新根部路由之外,最好加上 notfound,或者没有权限之类的提示,另外我们一些路由的页面也是会跟随权限变动的,可以直接根据更新后的用户权限判断好,也可以将处理好的方便外部使用的数据结构,通过 context 传递给子节点们使用

就这样,就完成了一个简易版动态路由了,如果是后台使用第二种方案怎么样,那就更好了,设置权限那一步不用我们做了,他直接整理好了,我们直接用后台给我们返回处理好的对象,调用 getRoutes 传递对应的方法即可,也可以整理权限相关,按照同样逻辑处理

最后

上面的基本上已经足够我们日常使用了,日常学习记录,也是我们进步的源泉,加油!

相关推荐
用户305875848912529 分钟前
Connected-react-router核心思路实现
react.js
哑巴语天雨15 小时前
React+Vite项目框架
前端·react.js·前端框架
初遇你时动了情15 小时前
react 项目打包二级目 使用BrowserRouter 解决页面刷新404 找不到路由
前端·javascript·react.js
码农老起15 小时前
掌握 React:组件化开发与性能优化的实战指南
react.js·前端框架
前端没钱16 小时前
从 Vue 迈向 React:平滑过渡与关键注意点全解析
前端·vue.js·react.js
高山我梦口香糖19 小时前
[react] <NavLink>自带激活属性
前端·javascript·react.js
撸码到无法自拔19 小时前
React:组件、状态与事件处理的完整指南
前端·javascript·react.js·前端框架·ecmascript
高山我梦口香糖19 小时前
[react]不能将类型“string | undefined”分配给类型“To”。 不能将类型“undefined”分配给类型“To”
前端·javascript·react.js
乐闻x1 天前
VSCode 插件开发实战(四):使用 React 实现自定义页面
ide·vscode·react.js
irisMoon061 天前
react项目框架了解
前端·javascript·react.js