Django+React全栈开发:路由

创建于更新于

react-router

现在的网站一般来讲很少只有单个“页面”,对于我们的博客来说,除了文章列表的界面,起码还得得有个文章详情页才行。

单页应用(SPA):可能你在官方介绍create-react-app这个脚手架时已经看到了这个名词,但千万不要误以为单页面的意思是没有“可以点击的链接”的。在这里所说的单页应用实际上就是:既然我们将一个网页应用看作一堆组件的组合,那么动态的页面其实只需要动态更新显示部分组件就行,而不是像传统做法那样,服务端提供完整的新页面,所有资源都重新加载。

好了,看到这里你应该明白,create-react-app是一个适于构建单页应用的脚手架,但不意味着想要做一个文章详情页就要再次yarn create react-app新建一个项目了吧。

好了,说了这么多,开始写代码吧。这里我们需要学习一个新东西:react-router-dom。首先进入我们的frontend目录,终端运行yarn add react-router-dom来安装依赖。

函数组件

之前我们已经讲过了类组件,在React中我们也可以创建函数形式的组件,函数组件又称为无状态组件,它可以接收一个props作为参数,但是不可以使用state它没有状态,也没有生命周期函数(在本教程介绍React Hooks之前这句话是正确的)。

为了介绍函数组件,这里先拆分一下组件,从ArticleList拆出一个ArticleItem

const ArticleItem = props => {
  const {title, created, updated} = props.item;
  
  return (
    <div className="py-3">
      <div className="text-2xl font-semibold">{title}</div>
      <div className="space-x-2">
        <span>创建时间:<time title={created}>{dayjs().to(dayjs(created))}</time></span>
        <span>更新时间:<time title={updated}>{dayjs().to(dayjs(updated))}</time></span>
      </div>
    </div>
  )
}

函数组件顾名思义就是一个函数,只要它返回一个JSX元素,就可以被当作组件使用,这里使用了箭头函数,要了解这些基础知识的细节,推荐去看MDN、阮一峰的ES6入门教程或者现代JavaScript教程

ArticleItem组件的内容是从ArticleList复制过来的,那么现在去修改ArticleList的内容:

     ......
      <div className="font-sans">
          {articleList.map(item =>
            <ArticleItem key={item.id} item={item}/>
          )}
      </div>
      ......

我们将aritcleList中的元素当作ArticleItemprops传递下去。可以看到这里的ArticleItem组件就是一个函数,它的返回值就是要渲染的内容。我个人的习惯是有组件需要复用了或者组件太大了再去提取组件,这里纯粹为了演示下函数组件写法。

路由

在开始写代码之前,让我们先来构思一下路由划分:

  1. 首页,展示文章列表
  2. 详情页,显示文章详情
  3. about页,展示博主信息

也就是说我们需要做三个页面,通常网站都会有个导航栏,一般来说进入这三个页面中的任意一个,导航栏都不会消失,也就是导航栏是可以复用的,而页面的主体部分,则可以动态替换。这样我们就知道,需要以下几个组件:

  1. App组件,主体框架
  2. 导航栏组件
  3. 文章列表组件
  4. 文章详情组件
  5. About组件

首先改写App.js

import {
  BrowserRouter as Router,
  Switch,
  Route,
} from "react-router-dom";
import ArticleList from "./ArticleList";
import About from "./About";
import Nav from "./Nav";

function App() {
  return (
    <Router>
      <div>
        <Nav />

        <Switch>
          <Route path="/about">
            <About />
          </Route>
          <Route path="/">
            <ArticleList />
          </Route>
        </Switch>
      </div>
    </Router>
  )
}

export default App;

同时还要新建Nav.jsAbout.js

import {Link} from "react-router-dom";

const Nav = () => {
  return (
    <nav>
      <ul>
        <li>
          <Link to="/">Home</Link>
        </li>
        <li>
          <Link to="/about">About</Link>
        </li>
      </ul>
    </nav>
  )
}

export default Nav
// 这个组件主要就是个人简介,读者自由发挥就好
const About = () => {
  return (
    <div>hello world</div>
  )
}

export default About

别忘了在之前的index.js中我们渲染的是ArticleList,现在去更改它:

......
ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  document.getElementById('root')
);

主要看AppNav两个组件,首先引入了react-router-domBrowserRouter包裹其它元素,Link组件放在Nav中做导航链接,SwitchRoute搭配使用,Switch会搜索子元素Route,当找到其路径与当前url相匹配的Route时,则渲染此Route内容,并忽略其它的Route。例如当前url为根路径/,那么就会渲染这里最后一个Route中的ArticleList,这样我们点击不同的Link,Switch组件渲染的内容就会切换,达到换页面的目的。如果按F12打开查看元素,你会发现点击不同导航链接,App组件内的元素会切换,而NetWork中则显示并没有发送任何网络请求。

详情页

现在还剩最后一个页面需要完成,就是文章详情页。现在去修改ArticleList.js,让其根据文章ID创建不同的Link

......
import { Link } from "react-router-dom";

......

const ArticleItem = props => {
  const {title, created, updated, id} = props.item;

  return (
    <div className="py-3">
      <Link to={`/articles/${id}`}>
        <div className="text-2xl font-semibold">{title}</div>
      </Link>
      ......
    </div>
  )
}

class ArticleList extends Component {
......
}

我们使用ES6语法的模板字符串,注意<Link to={...}>里的不是单引号,而是键盘左上角esc键下面那个反引号。这和Python中的f字符串有些类似,都允许在字符串中嵌入变量,但是ES6的写起来有点麻烦。JSX的实现也离不开模板字符串哦。

OK,现在让我们在src目录下新建一个ArticleDetail.js

// ArticleDetail.js
import React from "react";
import { useParams } from 'react-router-dom';

const ArticleDetail = () => {
// 取出url中的参数
  const { articleId } = useParams();

  return (
    <div>
      article {articleId}
    </div>
  )
}

export default ArticleDetail

对应的,在App.js中添加一个匹配项:

// 注意要把根路径放在最后面
<Switch>
    <Route path="/about">
        <About />
    </Route>
    <Route path="/articles/:articleId">
        <ArticleDetail />
    </Route>
    <Route path="/">
        <ArticleList />
    </Route>
</Switch>

现在在网页上点击文章标题或者导航栏的链接试试看吧。

练习

现在我们的文章详情组件只是简单地显示了article + id,可以尝试重写组件以显示真正的文章详情。之前说过函数组件又叫无状态组件,没有state,也没有生命周期,这里暂时先不讲Hooks(其实我们已经不知不觉中使用过了),所以你可能要将ArticleDetail改写为类组件,并通过props传递文章id并在componentDidMount中请求API。

Copyright © 2020-2021 公子政的宅日常