路由的概念,起初来源于服务端,就是当浏览器访问一个网站的不同页面时,服务端能够正确的返回页面的内容。当访问首页时,它能返回首页的内容,访问关于我们页面时,返回关于我们的内容。可以看到路由就是一种控制和匹配,从而保证页面内容和页面的地址一一对应的关系。但是每次页面地址发生变化,服务端都会返回一个新的页面,从而导致整个页面重新加载,用户体验不好。所以就兴起了单页应用,所有的内容都在一个页面上进行展示,页面中的变化也是局部变化,不用再刷新整个页面。那局部变化是怎么实现的呢?也就是说,当点击Home时,怎么才能正确的渲染出Home的内容,而不是其它的内容, 当点击about时,怎么才能渲染出About 内容。
其实这里和服务端路由是一个道理,在服务端路由时,点击home页面时,我们是向服务端发送一个请求,告诉服务端,我们需要home页面。在单页应用中,我们虽然不用向服务器发送请求,但我们还是要标识一下我们的请求,我们想要home的内容。这也很好理解,如果我们都不知道需要什么内容,那还怎么展示内容。 怎么标识我们的请求呢? 这里还是和服务器路由一样,浏览器的地址栏,就是改变window.location 的值。但这里要注意的是,地址栏的变化,不应该导致向服务器发送请求。这里有两种方法可以实现,一种是hash, location 对象有一个hash属性,hash值变化了,location 也就发生变化了,但hash的变化不会向服务器发生请求,这符合我们和要求。如在浏览器地址栏中显示#home, 则表示我们想进入home 的内容。 另一种是h5 的history API, history 对象一个pushState 方法,它可以改变location 对象的值,而不会向服务器发送请求。history.pushState({url: ‘home’}), 浏览器地址发生变化,但没有像服务器发送请求。
现在我们标识了请求,那就要处理请求,就像服务器返回页面一样,我们也要正确渲染相对应的内容,所以就要做控制和匹配,保证标识的请求和渲染的内容一致。由于这些匹配和控制是在客户端完成的,所以叫做客户端路由。
首先要做的就是匹配, 当用户标识请求时,返回对应的内容。所以一个路由要满足两个基本的条件。一个匹配用户的标识,一个是匹配成功的内容。对于react-router来说,它的路由有点不同,它不是配置型的,而是使用组件,对于路由所要满足的两个基本条件,它就是给组件定义两个属性。它提供的路由组件是Route, 对于匹配它是path属性,对于内容,则是component 属性,定义home 的路由,就可以这么写。
<Route path=’/home’, component= {home} /> // 当然,我们这里要写好Home 组件。
定义成功后,用户如果访问home, 则展示home 组件的内容。其实, 我们只是写好了一条路由,当用户访问About呢?那肯定渲染About的内容,那就要再写一条匹配路由
<Route path=’/about’, component= {About} />
如果页面中还要有其它内容访问,那么我们就要依次写好上面的路由。我们这样一条条的路由是定义好了,还需要进行控制和管理。当有户进行访问的时候,它要去查找匹配的路由,React-Router 提供了一个hashRouter, 和browserRouter组件,我们只要把这一条条的路由放到它下面的,它就会自动管理了。hashRouter 是根据hash 值的变化进行管理,browserRouter 则是根据h5 的历史管理api 进行管理。
理论知识讲的差不多了,我们实战体验一下react-router 吧。 直接使用create-react-app 创建项目,cnpm install react-router --save 就可以了。 项目就是一个简单的企业网站,它有5个部分:首页,关于我们,企业事件,联系我们,公司产品。 每一个部分占据一个页面区域,看起来是一个多页应用,其实它是一个单页应用。 对应这几个部分, 我们分别写几个组件: home, about, events, contact, products. 组件内部随便返回点内容,表示不同的页面。新建一个pages.js文件,内容如下:
import React from 'react' // 首页内容 export const Home = () => ( <section className="home"> <h1>企业网站</h1> <p>首页内容</p> </section> ) // 企业事件内容 export const Events = () => ( <section className="events"> <h1>企业大事件</h1> </section> ) // 公司产品 export const Products = () => ( <section className="products"> <h1>公司产品:手机、电脑</h1> </section> ) // 联系我们 export const Contact = () => ( <section className="contact"> <h1>联系我们</h1> <p>公司电话:0755 - 12345678</p> </section> ) // 关于我们 export const About = () => ( <section className="about"> <h1>公司理念</h1> <p>公司以人为本</p> </section> )
组件写完了,我们就要定义路由,好让用户访问时返回正确的内容。路由的定义用到了Route 组件,它有两个属性:path和compoent. path, 路径的意思,表示用户怎么访问,component 就是很简单了,表示渲染的组件。因为有5个部分,所以要定义5条路由
<Route path='/' component={Home}/> <Route path='/about' component={About}/> <Route path='/contact' component={Contact}/> <Route path='/products' component={Products}/> <Route path='/events' component={Events}/>
路由是需要管理的,所以我们要把上面定义好的路由放到Router下面,react-router 提供了hashRouter 和brwoserRouter 组件,我们在这里使用hashRouter。不过要注意的是,无论是hashRouter 还是brwoserRouter,它都只接受一个元素,所以我们还要使用一个div把上面的5个路由包括起来放到hashRouter下面。
<Router> <div> <Route path='/' component={Home}/> <Route path='/about' component={About}/> <Route path='/contact' component={Contact}/> <Route path='/products' component={Products}/> <Route path='/events' component={Events}/> </div> </Router>
我们把整个路由配置放到什么地方呢?放到根组件下,在这里,就是放到app 组件下面, 这样,Router 就可以管理 我们整个应用的路由了。整 个app.js 文件如下:
import React from 'react' // 引入hashRouter Route 组件 import { HashRouter, Route } from 'react-router-dom' // 引入5个组件 import { About, Contact, Home, Products, Events } from './pages'; function App() { return ( <HashRouter> <div> <Route path='/' component={Home}/> <Route path='/about' component={About}/> <Route path='/contact' component={Contact}/> <Route path='/products' component={Products}/> <Route path='/events' component={Events}/> </div> </HashRouter> ) } export default App
现在配置完成了, npm start 启动服务看一看效果, 看到首页内容了。
那我们怎么访问其它页面的内容呢?在介绍理论知识内容的时候说了,那就要标识请求,标识请求的地方在于浏览器的地址栏中。这时我们在地址栏的后面输入about, 显示About组件的内容,输入events, 显示event 组件的内容。但这时你会发现一个问题,无论在哪个路径下,它都用home组件的内容,这是为什么呢?原来是path匹配的问题。
地址栏中的地址和path属性值的匹配是使用的正则表达式进行匹配,所以/about 和 / 都会匹配到 /, 只要匹配成功,对应的组件就会渲染出来,所以总会有home 组件进行展示, 这肯定不是我们想要的,必须要一一对应。这时要用到一个属性exact, 只要把它加到<Route path=’/’ component = ‘home’> 上,就表示严格匹配,这时/about 就不会匹配到/, 只有/ 才能成功匹配/, 所以当/about页面时,就不会显示home 页面。home 组件路由改成如下就可以了。
<Route path='/' exact component={Home}/>
这里还会遇到一个问题, 如果用户随便输入一个路径,我们没有设置对应的路由进行匹配,怎么办?如输入hello, 页面一片空白, 用户体验肯定不好。就和服务器会返回一个404页面一样,我们肯定要写一个组件,告诉用户没有匹配成功。那就在pages.js 下面再写一个组件,
export const NotFound404 = () =>(
<div className="whoops-404">
<h1>没有页面可以匹配</h1>
</div>
)
现在就要把这个组件用路由进行管理,再写一条路由, 但path 属性要怎么写呢?因为用户任意输入,我们是不可能提前知道的,那就直接不写路径了, 不写路径,就表示这个组件可以匹配任意路径,这又会遇到另外一个问题,我们只想在没有匹配的时候,显示该组件, React-Router 提供了switch 组件来解次这个问题。我们把每一条路由用switch 组件包起来。switch 组件的作用,就像js 中switch 条件判断功能一样,它从上到下依次匹配,如果成功就会渲染组件,如果不成功,接着继续向下找,直到找到一个匹配的为止,所以没有匹配成功的路径都会到 NotFound404 组件,每一条有path属性的Route相当于一个case, 没有path的Route 则相当提供了一个default。
<HashRouter> <div> <Switch> <Route path='/' exact component={Home}/> <Route path='/about' component={About}/> <Route path='/contact' component={Contact}/> <Route path='/products' component={Products}/> <Route path='/events' component={Events}/> <Route component={NotFound404}/> </Switch> </div> </HashRouter>
现在路由功能正常了,但是是通过改变地址栏来进行导航的,对于验证功能是没有什么问题的, 用户使用,问题就大了,没有人愿意手动输入地址来进行页面导航。有没有更好的办法来解决这个问题,React-Router 提供了link, navLink 组件, 它们功能是一样的,都是用于导航,只是使用的场景不一样。navLink 组件是Link 组件的一种特殊化、如果是使用文字,按钮进行导航的话,就需要高亮显示当前我们在哪个位置以进行区分,navLink 提供了一个activeClass 或activeStyle 属性,可以直接设置高亮样式。但 如果点击图片进行导航,那就不需要对图片进行高亮,那就用link.
现在我们在home 组件中增加四个link用于导航,它有一个to属性,表示到什么地方去,最基本的用法就是提供一个字符串路径,这个路径和Route中的path属性一一对应就可以了。如<Link to=’/about’>关于我们</Link>,就表示要到/about 下面。
import React from 'react' // 引入link 组件 import { Link } from "react-router-dom"; import './pages.css'; // 首页内容 export const Home = () => ( <section className="home"> <h1>企业网站</h1> <nav> {/* 添加了四个导航组件Link */} <Link to='/about'>关于我们</Link> <Link to='/events'>企业事件</Link> <Link to='/products'>公司产品</Link> <Link to='/contact'>联系我们</Link> </nav> </section> )
这时,我们点击不同的link 就可以进行页面导航,这时你会发现点击link, 它还是改变地址栏,和我们手动改变地址栏是一模一样的。 其实link的本质是a 标签,to属性变成了它的href属性, 打开控制台,查看element 元素就可以看到。
这时,我们想要给link 添加样式,实际上就是给a 标签添加样式。nav a 就可以了。pages.css 内容如下:
html, body, #root { height: 100%; } h1 { font-size: 3em; color: slategray; } /* home 组件 */ .home { height: 100%; display: flex; flex-direction: column; align-items: center; } .home > nav { display: flex; justify-content: space-around; padding: 1em; width: calc(100% - 2em); border-top: dashed 0.5em ghostwhite; border-bottom: dashed 0.5em ghostwhite; background-color: slategray; } .home > nav a { font-size: 2em; color: ghostwhite; flex-basis: 200px; } /* 其它组件 */ section.events, section.products, section.contact { flex-grow: 1; margin: 1em; display: flex; justify-content: center; align-items: center; } /* 404页面 */ .whoops-404 { position: fixed; top: 0; left: 0; z-index: 99; display: flex; width: 100%; height: 100%; margin: 0; justify-content: center; align-items: center; background-color: darkred; color: ghostwhite; font-size: 1.5em; }
这就是react-router 最基本的用法, Link 用于导航或跳转,Route 用于定义一条一条的路由,Router 则管理路由,进行匹配。我们每次都是定义一条一条的路由,其实就是告诉Router, 当浏览器的地址栏中的地址发生变化时,哪个组件会被渲染出来。每一条路由都有两个path, component 两个属性,当浏览器地址栏中的地址,能够匹配path时,相应的component 就会显示出来。当地址是/ 时,router 会去渲染相应的home 组件。当地址是/about 时,router 会去渲染相应的Products组件。
其实在Router渲染相应组件的时候,它会向组件props属性中添加3个属性history, location, match. history和window.history对象一样,也可以用于导航,它有push, go等方法,这可以用于编程式导航. location则是浏览器地址栏对象,可以获取到当前路径什么的, match,则用表示匹配,可以获取到匹配的参数等。我们在404NotFound组件中把这三个属性打印出来。
export const NotFound404 = (props) =>(
console.log(props.location),
console.log(props.match),
console.log(props.history),
<div className="whoops-404">
<h1>没有页面可以匹配</h1>
</div>
)