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。