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
中的元素当作ArticleItem
的props
传递下去。可以看到这里的ArticleItem
组件就是一个函数,它的返回值就是要渲染的内容。我个人的习惯是有组件需要复用了或者组件太大了再去提取组件,这里纯粹为了演示下函数组件写法。
路由
在开始写代码之前,让我们先来构思一下路由划分:
- 首页,展示文章列表
- 详情页,显示文章详情
- about页,展示博主信息
也就是说我们需要做三个页面,通常网站都会有个导航栏,一般来说进入这三个页面中的任意一个,导航栏都不会消失,也就是导航栏是可以复用的,而页面的主体部分,则可以动态替换。这样我们就知道,需要以下几个组件:
- App组件,主体框架
- 导航栏组件
- 文章列表组件
- 文章详情组件
- 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.js
和About.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')
);
主要看App
和Nav
两个组件,首先引入了react-router-dom
中BrowserRouter
包裹其它元素,Link
组件放在Nav中做导航链接,Switch
和Route
搭配使用,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。