路由的概念,起初來源於服務端,就是當瀏覽器訪問一個網站的不一樣頁面時,服務端可以正確的返回頁面的內容。當訪問首頁時,它能返回首頁的內容,訪問關於咱們頁面時,返回關於咱們的內容。能夠看到路由就是一種控制和匹配,從而保證頁面內容和頁面的地址一一對應的關係。可是每次頁面地址發生變化,服務端都會返回一個新的頁面,從而致使整個頁面從新加載,用戶體驗很差。因此就興起了單頁應用,全部的內容都在一個頁面上進行展現,頁面中的變化也是局部變化,不用再刷新整個頁面。那局部變化是怎麼實現的呢?也就是說,當點擊Home時,怎麼才能正確的渲染出Home的內容,而不是其它的內容, 當點擊about時,怎麼才能渲染出About 內容。css
其實這裏和服務端路由是一個道理,在服務端路由時,點擊home頁面時,咱們是向服務端發送一個請求,告訴服務端,咱們須要home頁面。在單頁應用中,咱們雖然不用向服務器發送請求,但咱們仍是要標識一下咱們的請求,咱們想要home的內容。這也很好理解,若是咱們都不知道須要什麼內容,那還怎麼展現內容。 怎麼標識咱們的請求呢? 這裏仍是和服務器路由同樣,瀏覽器的地址欄,就是改變window.location 的值。但這裏要注意的是,地址欄的變化,不該該致使向服務器發送請求。這裏有兩種方法能夠實現,一種是hash, location 對象有一個hash屬性,hash值變化了,location 也就發生變化了,但hash的變化不會向服務器發生請求,這符合咱們和要求。如在瀏覽器地址欄中顯示#home, 則表示咱們想進入home 的內容。 另外一種是h5 的history API, history 對象一個pushState 方法,它能夠改變location 對象的值,而不會向服務器發送請求。history.pushState({url: ‘home’}), 瀏覽器地址發生變化,但沒有像服務器發送請求。html
如今咱們標識了請求,那就要處理請求,就像服務器返回頁面同樣,咱們也要正確渲染相對應的內容,因此就要作控制和匹配,保證標識的請求和渲染的內容一致。因爲這些匹配和控制是在客戶端完成的,因此叫作客戶端路由。react
首先要作的就是匹配, 當用戶標識請求時,返回對應的內容。因此一個路由要知足兩個基本的條件。一個匹配用戶的標識,一個是匹配成功的內容。對於react-router來講,它的路由有點不一樣,它不是配置型的,而是使用組件,對於路由所要知足的兩個基本條件,它就是給組件定義兩個屬性。它提供的路由組件是Route, 對於匹配它是path屬性,對於內容,則是component 屬性,定義home 的路由,就能夠這麼寫。正則表達式
<Route path=’/home’, component= {home} /> // 固然,咱們這裏要寫好Home 組件。
定義成功後,用戶若是訪問home, 則展現home 組件的內容。其實, 咱們只是寫好了一條路由,當用戶訪問About呢?那確定渲染About的內容,那就要再寫一條匹配路由npm
<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文件,內容以下:api
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 文件以下:react-router
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>
)