使用React Router6.x实现简单博客

136 阅读6分钟

本文章将在React应用中使用react-router v6.x实现一个简单博客,包含概念安装路由导航404页面等知识来快速入门; 项目完整版地址

目标效果:

React-Router 是什么?

Raact-Router是一个解决React应用的路由库,允许在单页面应用(SPA)中实现客户端路由;

  • 传统的多页面应用中,每次导航都会向服务器发送请求并刷新浏览器重新加载整个页面
  • SPA中,react-router根据URL值的变化动态渲染对应的组件,无需重新加载整个页面;

安装 React-Router

使用Vite构建工具创建项目,选择React

npm create vite@latest

在项目中安装react-router

npm i react-router@6

基本使用

配置路由

创建一个router.jsx文件存放基本路由架构,其中createBrowserRouter函数是一种路由器模式,支持HTML5 History API管理URL和浏览器历史记录栈,并支持浏览器前进/后退导航;它包含了一组路由配置route,每个route对象由path属性配置路径,element属性指定路由对应的组件;

router.jsx

import { createBrowserRouter } from 'react-router-dom'

const router = createBrowserRouter([
  {
    path: '/',
    element: <div>Hello, react-rouer</div>,
  },
])

export default router

main.jsx 使用RouterProvider组件将路由表作为props传递

import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import { RouterProvider } from 'react-router-dom'
import router from './router.jsx'

createRoot(document.getElementById('root')).render(
  <StrictMode>
    <RouterProvider router={router} />
  </StrictMode>
)

运行项目浏览器显示

实现首页

src下创建一个page文件夹存储对应的页面组件,在之下创建一个Home.jsx,在router.jsx将之前的div替换成Home组件

const Home = () => {
  return (
    <div>
      <h3>首页</h3>
      <p>Lorem ipsum dolor</p>
    </div>
  )
}

现在运行项目后,访问URL会发现页面显示Home组件:

实现文章及关于页面

创建Article.jsx

const Articles = () => {
  return (
    <div>
      <h3>文章列表</h3>
    </div>
  )
}

export default Articles

创建About.jsx

const About = () => {
  return (
    <div>
      <h3>About</h3>
      <p>这里有关于该网站的信息</p>
    </div>
  )
}

export default About

router.jsx中配置路由;

import { createBrowserRouter } from 'react-router-dom'
import Home from './page/Home'
import Articles from './page/Articles'
import About from './page/About'

const router = createBrowserRouter([
  {
    path: '/',
    element: <Home />,
  },
  {
    path: '/articles',
    element: <Articles />,
  },
  {
    path: '/about',
    element: <About />,
  },
])

export default router

在浏览器URL中后输入/about,即可访问到对应的About组件

配置导航

输入网址的方式很不方便,因此可以使用Link组件实现一个导航栏,点击访问到对应的网址,且不会刷新整个网页(默认的a标签会刷新整个页面重新请求);

创建导航栏组件

import { Link } from 'react-router-dom'

const NavBar = () => {
  return (
    <nav>
      <Link to='/' style={{ padding: '10px' }}>
        首页
      </Link>
      <Link to='/articles' style={{ padding: '10px' }}>
        文章
      </Link>
      <Link to='/about' style={{ padding: '10px' }}>
        关于
      </Link>
    </nav>
  )
}

export default NavBar

根路径对应的组件将充当UI的根布局,在page/创建一个Root.jsx作为根布局,将现有的路由配置给注释;

router.jsx

import { createBrowserRouter } from 'react-router-dom'
import Home from './page/Home'
import Articles from './page/Articles'
import About from './page/About'
import Root from './page/Root'

const router = createBrowserRouter([
  {
    path: '/',
    element: <Root />,
  },
  // {
  //   path: '/',
  //   element: <Home />,
  // },
  // {
  //   path: '/articles',
  //   element: <Articles />,
  // },
  // {
  //   path: '/about',
  //   element: <About />,
  // },
])

export default router

Navbar组件放入Root.jsx

import Navbar from './Navbar'

const Root = () => {
  return (
    <>
      <Navbar />
    </>
  )
}

export default Root

现在的效果是如下所示,目前点击文章关于链接会发现页面url发生变化,但是页面内容会出现404 Not Found等文字;

处理错误

page/下创建ErrorPage.jsx文件

export default function ErrorPage() {
  return <div>404 页面</div>
}

修改router.jsx,添加一个由路由发生错误时页面显示的组件errorElement属性

import { createBrowserRouter } from 'react-router-dom'
import Home from './page/Home'
import Articles from './page/Articles'
import About from './page/About'
import Root from './page/Root'
import ErrorPage from './page/ErrorPage'

const router = createBrowserRouter([
  {
    path: '/',
    element: <Root />,
    errorElement: <ErrorPage />,
  },
  // {
  //   path: '/',
  //   element: <Home />,
  // },
  // {
  //   path: '/articles',
  //   element: <Articles />,
  // },
  // {
  //   path: '/about',
  //   element: <About />,
  // },
])

export default router

现在点击文章关于链接可以看见页面显示配置的错误组件

嵌套路由

嵌套路由:当想实现网页某个部分不变,只有网页的子部分发生变化

希望将首页等内容和导航栏都显示在一个页面中,可以将它们设置为根路由的子路由,增加一个children属性

  • index: true相当于path: ''即根父路由路径一致
  • path: '/'表示根路径,子路由未添加/表示相对于父路由解析
import { createBrowserRouter } from 'react-router-dom'
import Home from './page/Home'
import Articles from './page/Articles'
import About from './page/About'
import Root from './page/Root'
import ErrorPage from './page/ErrorPage'

const router = createBrowserRouter([
  {
    path: '/',
    element: <Root />,
    errorElement: <ErrorPage />,
    children: [
      {
        index: true,
        element: <Home />,
      },
      {
        path: 'articles',
        element: <Articles />,
      },
      {
        path: 'about',
        element: <About />,
      },
    ],
  },
])

export default router

但现在首页或其它页都不显示内容,此时需要一个Outlet组件它告诉了父路由要在哪里显示子路由;

修改Root.jsx

import { Outlet } from 'react-router-dom'
import Navbar from './Navbar'

const Root = () => {
  return (
    <>
      <Navbar />
      <Outlet />
    </>
  )
}

export default Root

现在可以看到首页的内容在导航栏下显示

实现文章

1、先将文章列表展示:

src下创建一个constants.js文件,存储模拟的文章数据

export const articles = [
  {
    id: '1',
    title: 'react-router v6指南',
    description: '在这里我要通过 简单博客 讲解 react-router v6',
  },
  {
    id: '2',
    title: 'redux-toolkit 指南',
    description: '在这里我用通过 todolist 讲解 redux-toolkit',
  },
]

修改Articles.jsx展示文章列表

import { Outlet } from 'react-router-dom'
import { Link } from 'react-router-dom'
import { articles } from '../constants'

const Articles = () => {
  return (
    <div>
      <h3>文章列表</h3>
      <ul>
        {articles.map((article) => {
          return (
            <li key={article.id}>
             {article.title}
            </li>
          )
        })}
      </ul>
    </div>
  )
}

export default Articles

2、添加子路由

路由可以嵌套在父路由内,修改router.jsxindex表示共用父组件的路由地址(默认子路由),展示InitArticle.jsx

const InitArticle = () => {
  return <div>这是一篇初始文章</div>
}

export default InitArticle

import { createBrowserRouter } from 'react-router-dom'
import Home from './page/Home'
import Articles from './page/Articles'
import About from './page/About'
import Root from './page/Root'
import InitArticle from './page/InitArticle'
import ErrorPage from './page/ErrorPage'

const router = createBrowserRouter([
  {
    path: '/',
    element: <Root />,
    errorElement: <ErrorPage />,
    children: [
      {
        path: '',
        element: <Home />,
      },
      {
        path: 'articles',
        element: <Articles />,
        children: [
          {
            index: true,
            element: <InitArticle />,
          },
        ],
      },
      {
        path: 'about',
        element: <About />,
      },
    ],
  },
])

export default router

此时发现访问/articles无法显示InitArticle组件,在Articles.jsx添加Outlet组件用于子路由在父路由中渲染;

import { Outlet } from 'react-router-dom'
import { Link } from 'react-router-dom'
import { articles } from '../constants'

const Articles = () => {
  return (
    <div>
      <h3>文章列表</h3>
      <ul>
        {articles.map((article) => {
          return (
            <li key={article.id}>
              {article.title}
            </li>
          )
        })}
      </ul>
      <Outlet />
    </div>
  )
}

export default Articles

可以看到ArtilceList组件渲染在Articles组件内,也就是Outlet的位置,效果图:

动态路由

实现在文章中点击文章的标题,在下方显示对应的文章内容,由于每篇文章的id都是不同的,因此需要动态的配置路由,path:开头表示动态段,紧跟一个自命名参数;现在单个文章 article.jsx的路径为/articles/:id

router.jsx

import { createBrowserRouter } from 'react-router-dom'
import Home from './page/Home'
import Articles from './page/Articles'
import About from './page/About'
import Root from './page/Root'
import InitArticle from './page/InitArticle'
import Article from './page/Article'
import ErrorPage from './page/ErrorPage'

const router = createBrowserRouter([
  {
    path: '/',
    element: <Root />,
    errorElement: <ErrorPage />,
    children: [
      {
        path: '',
        element: <Home />,
      },
      {
        path: 'articles',
        element: <Articles />,
        children: [
          {
            index: true,
            element: <InitArticle />,
          },
          {
            path: ':id',
            element: <Article />,
          },
        ],
      },
      {
        path: 'about',
        element: <About />,
      },
    ],
  },
])

export default router

修改Articles.jsx,给每个li添加Link to={/article/${article.id}},根据文章的id设置路径;

import { Outlet } from 'react-router-dom'
import { Link } from 'react-router-dom'
import { articles } from '../constants'

const Articles = () => {
  return (
    <div>
      <h3>文章列表</h3>
      <ul>
        {articles.map((article) => {
          return (
            <li key={article.id}>
              <Link to={`/articles/${article.id}`}>{article.title}</Link>
            </li>
          )
        })}
      </ul>
      <Outlet />
    </div>
  )
}

export default Articles

Article.jsx中,获取动态路径的参数,使用useParams钩子,该钩子返回一个对象包含所有的动态参数,通过解构获取刚刚设置的:id

import { useParams } from 'react-router-dom'
import { articles } from '../constants'

const Article = () => {
  const { id } = useParams()
  const article = articles.find((item) => item.id === id)
  // 如果文章列表没有该文章则返回提示
  if (!article) {
    return <div>没有找到你想要的文章! QAQ</div>
  }
  return <div>{article.description}</div>
}

export default Article

点击redux-toolkit指南,将访问id=2的文章,在下方显示文章内容

手动访问一个URL``/articles/333,会发现文章列表中没有,因此显示如下:

最后的想法

本文只讲到了react-router的基础使用,其中还有更多进阶的内容,比如表单处理loaderaction路由鉴权等需要继续探索;

总结:

  • pathLink to配置路径时不以/开头是相对于父路由的路径
  • useParams钩子获取动态参数,它返回的是一个对象,其中的属性是设置动态参数的名字
  • Outlet组件是子路由组件在父路由组件显示的位置