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。