从零开始的野路子React/Node(2)路由与页面跳转
这两天主要摸索了一下React的路由做法,网上看了一些资料,但总觉得比较零散,所以自己稍微总结一下,好让自己明白一些。
首先,对于一个稍微复杂一点的网站来说,一般都有多个页面,通常通过点击链接或者其他操作,我们可以在页面间跳转。而跳转的过程基本上就通过路由来实现。
React的路由主要依靠react-router-dom这个库(当然,其他就算有我也不知道……)来实现。下面我们来看个简单的例子:
1、准备各个页面
我们假设我们需要三个页面(Home, Overview, Chapters),都在components目录下,各自的内容也非常简单。
Home是个主页,我们放一些欢迎信息:
import React from 'react'; export default function Home() { return ( <div> <h1>欢迎光临,随意挑选</h1> </div> ); }
Overview是个概览页面:
import React from 'react'; export default function Overview() { return ( <div> <h1>梗概</h1> <div>话说天下大势,分久必合,合久必分。</div> </div> ); }
Chapters是个章节内容页面,看起来比较复杂,其实只是根据ChapDict的内容罗列第x章,以及对应的标题:
import React from 'react'; const ChapDict = { 1:'宴桃园豪杰三结义 斩黄巾英雄首立功', 2:'张翼德怒鞭督邮 何国舅谋诛宦竖', 3:'议温明董卓叱丁原 馈金珠李肃说吕布', 4:'废汉帝陈留践位 谋董贼孟德献刀', 5:'未完待续'}; export default function Chapters () { var chapnum = Object.keys(ChapDict) return ( <div> { chapnum.map( (item, idx) => ( <div> <h2>第{ item }章</h2> <p>{ ChapDict[item] }</p> </div> ) ) } </div> ); };
接下来,我们把Home组件放到主文件App.js中,毕竟这是我们启动后第一眼就会看到的页面:
import React from 'react'; import './App.css'; import Home from './components/Home'; function App() { return ( <div> <Home/> </div> ); } export default App;
通过npm install后npm start启动一下,我们可以看到一个极为粗放的主页:
2、添加路由
接下来,我们需要加入路由元素了。我们把三个页面组件都导入进来,并且从react-router-dom中导入BrowserRouter(名字太长,所以之后用Router代替)和Route。
现在App.js变成了:
import React from 'react'; import './App.css'; import { BrowserRouter as Router, Route } from 'react-router-dom'; import Home from './components/Home'; import Overview from './components/Overview'; import Chapters from './components/Chapters'; function App() { return ( <div> <Router> <Route path='/' component={ Home }/> <Route path='/overview' component={ Overview }/> <Route path='/chapters' component={ Chapters }/> </Router> </div> ); } export default App;
每一条路由设置,我们都用Route来设定之,它有两个参数,分别是对应的URL地址(path)和对应调用的组件(component)。我们将其依次设定。最后把这几条路由都放在Router里面就行了。
现在我们的主页看上去并没有什么变化,但是我们输入不同的URL已经能看到不同的内容了。但是有个问题是主页的内容为什么一直会显示呢?
因为路由会逐条匹配,匹配到有对应的内容显示并继续向下再匹配。而我们的主页“/”永远会第一个被匹配到,所以永远会显示。解决的方法是:
(1)path前加上exact,这样一来,一定要URL完全匹配为“/”才会显示主页,否则不会显示;
(2)再加上Switch,Switch的作用是一旦匹配到一条就不再继续向下匹配了。
所以我们把App.js改成:
import React from 'react'; import './App.css'; import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'; import Home from './components/Home'; import Overview from './components/Overview'; import Chapters from './components/Chapters'; function App() { return ( <div> <Router> <Switch> <Route exact path='/' component={ Home }/> <Route path='/overview' component={ Overview }/> <Route path='/chapters' component={ Chapters }/> </Switch> </Router> </div> ); } export default App;
现在一切都正常多了:
然而,还有个问题就是,如果用户输入了一个我们没有定义过的网址,比如http://localhost:3000/blahblah页面就没法正常显示,怎么办呢?我们可以通过Redirect把这些乱七八糟的输入都重定向到主页(自动跳回主页)。注意Redirect一定要放在最后!设定完from和to就大功告成了:
import React from 'react'; import './App.css'; import { BrowserRouter as Router, Route, Switch, Redirect } from 'react-router-dom'; import Home from './components/Home'; import Overview from './components/Overview'; import Chapters from './components/Chapters'; function App() { return ( <div> <Router> <Switch> <Route exact path='/' component={ Home }/> <Route path='/overview' component={ Overview }/> <Route path='/chapters' component={ Chapters }/> <Redirect from='/*' to='/'/> </Switch> </Router> </div> ); } export default App;
3、来个导航栏吧
到现在为止,我们已经可以通过输入不同的网址来访问不同的页面,但我们还没有做页面跳转。不如我们加个导航栏吧。我们在components目录下新建一个Navi组件:
import React from 'react'; import { Link } from 'react-router-dom'; export default function Navi() { return ( <div> <Link to='/'> <p>首页</p> </Link> <Link to='/overview'> <p>梗概</p> </Link> <Link to='/chapters'> <p>章节</p> </Link> </div> ); }
Navi的内容很简单,仅仅是三个段落:首页、梗概、章节而已,但是每个段落都被包含在Link当中,Link的作用就是跳转,我们设定了参数to就是需要跳转到的页面。接下来,我们需要把导航栏也加入到App.js中:
import React from 'react'; import './App.css'; import { BrowserRouter as Router, Route, Switch, Redirect } from 'react-router-dom'; import Navi from './components/Navi'; import Home from './components/Home'; import Overview from './components/Overview'; import Chapters from './components/Chapters'; function App() { return ( <div> <Router> <Navi/> <Switch> <Route exact path='/' component={ Home }/> <Route path='/overview' component={ Overview }/> <Route path='/chapters' component={ Chapters }/> <Redirect from='/*' to='/'/> </Switch> </Router> </div> ); } export default App;
由于导航栏每个页面都会出现,所以不需要添加路由,我们把它放在最前面,Switch之外就行了。再来看看:
我们点击对应的部分就可以跳转到对应了页面啦~
4、再增加点难度?
以上都是静态的URL,要不我们试试动态的?比如每个章节都能点击,然后可以进入到那个章节的页面怎么样?
我们先在components目录下做个Chap组件,这跟之前初体验中提到的方法一样,非常简单:
import React from 'react'; export default function Chap (props) { return ( <div> <h2>第{ props.chap }章</h2> <p>{ props.title }</p> </div> ); }
props的chap属性决定显示第几章,而title属性决定显示什么标题。
我们随便加个 <Chap chap='2' title='月明星稀,乌鹊南飞'/> 试试:
红框部分就是Chap组件显示的内容。然后我们再加入一条路由通往Chap组件,这里注意Chapters对应的path前要加上exact防止提前被匹配掉:
import React from 'react'; import './App.css'; import { BrowserRouter as Router, Route, Switch, Redirect } from 'react-router-dom'; import Navi from './components/Navi'; import Home from './components/Home'; import Overview from './components/Overview'; import Chapters from './components/Chapters'; import Chap from './components/Chap'; function App() { return ( <div> <Router> <Navi/> <Switch> <Route exact path='/' component={ Home }/> <Route path='/overview' component={ Overview }/> <Route exact path='/chapters' component={ Chapters }/> <Route path='/chapters/:chap' component={ Chap }/> <Redirect from='/*' to='/'/> </Switch> </Router> </div> ); } export default App;
好了,现在我们随便在”/chapters/”后面输个数字都会进入一个新的页面,但问题是还没有内容传入进去:
这里我们要完成两个工作,一个是在Chapters中用Link做链接跳转。另一个则是通过Link传递对应的内容到Chap组件中进行显示。我们修改一下Chapters组件,加入Link,通过占位符来跳转到动态的地址:
import React from 'react'; import { Link } from 'react-router-dom'; const ChapDict = { 1:'宴桃园豪杰三结义 斩黄巾英雄首立功', 2:'张翼德怒鞭督邮 何国舅谋诛宦竖', 3:'议温明董卓叱丁原 馈金珠李肃说吕布', 4:'废汉帝陈留践位 谋董贼孟德献刀', 5:'未完待续'}; export default function Chapters () { var chapnum = Object.keys(ChapDict) return ( <div> { chapnum.map( (item, idx) => ( <div> <Link to={ `/chapters/${ item }` }> <h2>第{ item }章</h2> </Link> <p>{ ChapDict[item] }</p> </div> ) ) } </div> ); };
现在每个“第x章”上都有了链接,点击即可跳转到对应的页面” http://localhost:3000/chapters/x”,但依然没有内容。
我们可以先修改一下Chap组件,我们把props.chap改为props.match.params.chap,这样可以匹配URL中的通配网址(还记得我们在Route中指定了Chap组件的path='/chapters/:chap'吗),这样chap的值就能被传入了:
第x章有了,那么对应的标题呢?难道要复制一个ChapDict过来匹配吗?这样修改起来很不方便,而且不够偷懒。
办法还是有的,我们分别修改一下Chapters组件里Link的参数,以及Chap组件中title的获取方式就行。
import React from 'react'; import { Link } from 'react-router-dom'; const ChapDict = { 1:'宴桃园豪杰三结义 斩黄巾英雄首立功', 2:'张翼德怒鞭督邮 何国舅谋诛宦竖', 3:'议温明董卓叱丁原 馈金珠李肃说吕布', 4:'废汉帝陈留践位 谋董贼孟德献刀', 5:'未完待续'}; export default function Chapters () { var chapnum = Object.keys(ChapDict) return ( <div> { chapnum.map( (item, idx) => ( <div> <Link to={{ pathname:`/chapters/${ item }`, query:{ title:ChapDict[item] } }}> <h2>第{ item }章</h2> </Link> <p>{ ChapDict[item] }</p> </div> ) ) } </div> ); };
对于Chapters组件,我们修改了Link的to参数,它的内容变成了一个Object(类似python的dict),pathname即跳转的URL地址,而query则可以放置我们想要传递的内容。这里我们把title的内容传进去。
再修改一下Chap组件:
import React from 'react'; export default function Chap (props) { let title = props.location.query !== undefined ? props.location.query.title : '未完待续' return ( <div> <h2>第{ props.match.params.chap }章</h2> <p>{ title }</p> </div> ); }
我们通过props.location.query.title(而不是原来的props.title)来获取title的内容。
需要注意的是,这里建立了一个临时变量title,并通过一个条件判断来获取值,这样做的原因是因为我们的ChapDict只有五个章节的内容,也就是说我们章节的URL只能支持到” http://localhost:3000/chapters/5”,如果用户输入一个” http://localhost:3000/chapters/36”怎么办?直接使用props.location.query.title的话会因为props.location.query是undefined而报错。因此我们加了一步条件判断,如果query是undefined的话,我们的标题就是“未完待续”。
现在所有的内容都妥了~
代码见:
https://github.com/SilenceGTX/react_basic_route