9. 前端分頁

在這個文章中,我們將探討單頁應用(spa Single-page application)和用react-router來實現前端分頁。最後我們將實現自己的前端分頁方法。

A single-page application (SPA) is a web application or web site that fits on a single web page with the goal of providing a user experience similar to that of a desktop application. In an SPA, either all necessary code – HTML, JavaScript, and CSS – is retrieved with a single page load, or the appropriate resources are dynamically loaded and added to the page as necessary, usually in response to user actions. The page does not reload at any point in the process, nor does control transfer to another page, although the location hash can be used to provide the perception and navigability of separate logical pages in the application,as can the HTML5 pushState() API. Interaction with the single page application often involves dynamic communication with the web server behind the scenes.

翻譯:單頁應用程序(SPA)是適合單個網頁的web應用程序或網站,目的是提供類似於桌面應用程序的用戶體驗。 在SPA中,通過單個頁面載入來檢索所有必要的代碼(HTML,JavaScript和CSS),或者通常響應於用戶動作,根據需要動態載入適當的資源並將其添加到頁面。 儘管位置散列可以用於在應用程序中提供單獨的邏輯頁面的感知和可導航性,但頁面不會在進程中的任何點重新載入,也不會控制轉移到另一個頁面,HTML5 pushState)API。 與單頁應用程序的交互通常涉及與幕後的web伺服器的動態通信。

相比較傳統的web app開發,伺服器對於客戶端的每一次請求,一般都會返回一個新的頁面。數據都是在後台被拼接到新頁面中的。而spa應用,一般只需要請求一次頁面,之後就不再從伺服器進行頁面的請求。伺服器和瀏覽器也只限於基於ajax的數據交換。

一個應用不可能只包含一個簡單的頁面,那麼spa是如何只請求一次伺服器就能得到有所頁面呢?

關鍵在於前台分頁(將MV*的機制應用到瀏覽器端),即所有的頁面在第一次伺服器請求的時候,已經下載到了瀏覽器,分頁相關的操作不再和後台交互,而是通過瀏覽器訪問的url中的hash變化(可以通過onhashchange來實現,在本文最後,我們就會利用這個事件處理函數來進行前端分頁的實現)來提供不同的頁面。

url中的hash指包括#在內的部分,我們可以通過window.location.hash來獲取。如url「127.0.0.1:8888/#」中的hash就是「#/chapter7」。

React-router

當然我們可以通過onhashchange方法來實現自己的路由(分頁)控制。但是就像我們用React簡便了純html和js的開發,我們可以用React-router組件來方便我們的react程序的前端分頁功能。

首先讓我們安裝React-router模塊:

npm install react-router --save;

然後我們需要創建一個root.jsx,將原本的App.jsx作為這個root頁面的一部分。

import React from react;import { Router, Route, IndexRoute, hashHistory } from react-routerimport App from ./app.jsx;import Chapter6 from ./components/chapter6/Chapter6;import Chapter5 from ./components/chapter5/Chapter5;export default class Root extends React.Component { render() { return ( <Router history={hashHistory}> <Route path="/" component={App} > <IndexRoute component={Chapter5} /> <Route path="chapter6" component={Chapter6} /> </Route> </Router> ) }}

在上面的例子中,如果我們訪問「127.0.0.1:8888/」,就可以訪問到App組加和Chapter5組件。而當我們訪問到「http://127.0.0.1:8888/#/chapter6」的時候,就可以看到App組件和Chapter6組件了。由於App組件是它們的父組件,不管訪問那個頁面,都可以看到App組件。因此我們可以在App組件裡面放點有用的東西,一般來說的話,我們可以放置導航條。

React-router既可以用html標籤疊加的形式,也可以用js對象來進行配置。和上面的例子同樣效果的js代碼如下所示:

import React from react;import { Router, Route, IndexRoute, hashHistory } from react-routerimport App from ./app.jsx;import Chapter5 from ./components/chapter5/Chapter5;import Chapter6 from ./components/chapter6/Chapter6;export default class Root extends React.Component { render() { const routes = { path: /, component: App, indexRoute: { component: Chapter6 }, childRoutes: [ { path: about, component: Chapter6 }, { path: chapter5, component: Chapter5 }, ] } return ( <Router history={hashHistory} routes={routes} /> ) }}

我們通過定義了一個routes對象,然後將它作為props傳給了Router組件,就實現了用XML標籤書寫同樣的邏輯。

在上面的兩個例子中,我們主要用到了Router組件、Route組件、IndexRoute組件和history對象。前三個看名字就很容易理解了。對於最後一個history對象的話,它封裝了一些html5關於window.history的一些方法。讓我們開看看有哪些函數。修改root.jsx如下:

import React from react;import { Router, Route, IndexRoute, hashHistory } from react-router;import App from ./app.jsx;import Chapter5 from ./components/chapter5/Chapter5;import Chapter6 from ./components/chapter6/Chapter6;export default class Root extends React.Component { render() { return ( <Chapter5 history={hashHistory} /> ) }}

我們為Chatper5組件傳遞了一個history屬性,然後我們需要在Chapter5的render方法中列印"this.props"。

我們可以用它提供的方法來進行url的跳轉(在不刷新頁面的情況下)。

對於本章最開始的例子,我們也列印Chatper5中的props:

紅框中的,就是react-router會為每一個Route中的組件添加的props,其中的router欄位所對應的對象就是之前的history對象。讓我們繼續在我們的項目中集成react-router。我們需要在index.jsx中用Root.jsx替換掉App.jsx.

修改index.jsx,在index.jsx中引入路由組件Root.jsx

```JavaScript

import React from react;import { render } from react-dom;import { AppContainer } from react-hot-loader;import Root from ./root.jsx;render( <AppContainer><Root/></AppContainer>, document.querySelector("#app"));if (module.hot) { module.hot.accept(./root.jsx, () => { const Root = require(./root.jsx).default; render( <AppContainer> <Root/> </AppContainer>, document.querySelector("#app") ); });}

最後我們還要修改App.jsx。

import React from react;export default class App extends React.Component { render() { const { children } = this.props; return ( <div> <div>This is the header</div> <br /> {children} </div> ) }}

上面代碼中的children就是每一個路由對象傳遞過去的頁面。不管我們訪問那一個路由,我們都可以看到「<div>This is the header</div>」。我們可以在這裡加入一些導航信息。讓我們繼續完善我們的程序。

我們將對本書的代碼的位置進行重新安排,用來適應react-router的引入。

我們通過查看不同的開源項目發現,在引入react-router和redux(將在後面的文章中間介紹)之後,很多項目的目錄安排都有所不同。

一般可以根據是否建立單獨的routes目錄(並把路由對應的頁面相關的container、components和redux的actions/reducers都放在routes的子目錄下)分為兩類:

1. 會有一個page目錄,page目錄裡面有所有的路由對應的頁面的目錄,然後在每一個子目錄中放置container、components和redux的actions/reducers文件;

2. 所有的container都放在container目錄里,所有的component都放在component文件夾里,等等。在這種情況下,不會為每一個路由單獨見一個文件夾統一放置對應的文件。

由於我們的頁面都很簡單,因此我們會在components目錄下為每個章節的代碼建立一個文件夾,然後建立一個index.jsx()來導出章節的入口組件和其他通用的組件。

同時附上index.jsx中的代碼:

const chapters = [3, 4, 5, 6, 7];const routers = chapters.map((chapter)=>{ const component = require(./chapter+chapter+/Chapter+chapter).default return { key: chapter+chapter, component };})export default routers;

我們可以在chapters數組中放置存在代碼的章節號碼,然後通過map生成一個含有所有章節入口組件的對象,然後這個對象會在root.jsx中使用。

```javascript...import routers from ./components;...export default class Root extends React.Component { render() { return ( <Provider store={store}> <Router history={history} > <Route path="/" component={App} > <IndexRoute component={routers[0].component} /> {routers.map((route)=><Route path={route.key} component={route.component} />)} </Route> </Router> </Provider> ) }}```

最後我們會在App.jsx里設置一個列表列出所有的可以訪問的前端分頁頁面,然後用react-router中的Link對象來對所有章節的頁面進行頁面超鏈接的創建。

import React from react;import { Link } from react-routerimport routers from ./components;export default class App extends React.Component { render() { const { children } = this.props; return ( <div> <div>This is the header</div> <ul> {routers.map((route, i)=><Route key={i} path={route.key} component={route.component} />)} </ul> <hr /> {children} </div> ) }}

我們的Route組件需要有一個key屬性,這個是react要求的,用來生成唯一的id(data-reactid)。現在在頁面中審查元素,已經看不到這個屬性了。

讓我們看一下,現在項目的界面:

我們可以通過點擊chapter列表來訪問具體每章的代碼了。

關於react-router的使用就到這裡了。當然我們只用了react-router中一小部分的功能。我們的實例只用了一層的子路由,react-router還只是多層次的子路由的嵌套。更多內容大家可以參考下面的文檔:

ReactTraining/react-router

實現一個前端分頁的功能

在集成了react-router後,我們也要思考如何實現前端分頁。接下來,讓我們自己來實現一個前端分頁的功能。

import React from react;import { Router, Route, IndexRoute, hashHistory } from react-router;import routers from ./components;export default class Root extends React.Component { constructor(props) { super(props); this.state = { hash: chapter5 }; } componentDidMount() { window.addEventListener("hashchange", this.doRouterInFront); } componentWillUnmount() { document.removeEventListener(hashchange, this.doRouterInFront); } doRouterInFront = () => { const { hash } = window.location; console.log(hash.substr(2)) this.setState({hash: hash.substr(2)}); } getCompByHash(hash) { const myComponent = routers.filter((route)=>route.key===hash)[0].component; console.log(myComponent, myComponent) return <div><myComponent /></div>; } render() { const { hash } = this.state; return ( <div> <div>This is the header</div> <ul> {routers.map(((route, i)=><li key={i}><a href={#/+route.key}>{route.key}</a></li>))} </ul> <hr /> {this.getCompByHash(hash)} </div> ) }}

上面的代碼不能很好的工作,getCompByHash函數並沒有返回我們需要的組件。原因是對於自定義的組件,react要求我們用大寫開頭的。如果小寫開頭,它會認為是一個普通的html標籤。因此我們可以將myComponent用大寫變數來代替,或者我們也可以用下面的方法來代替。

修改getCompByHash函數

getCompByHash(hash) { const myComponent = routers.filter((route)=>route.key===hash)[0]; console.log(myComponent, myComponent) return <div><myComponent.component /></div>; }

總結我們自己實現的前端分頁功能:

我們通過註冊了onhashchange事件,在每一次的事件回調中設置不同的hash,然後在render方法中通過hash來找到對應的組件。再渲染到頁面中。

現在的這個頁面真的是蠻簡潔的,哈哈。

我們將會在下一章節引入bootstrap來美化它(之後的的代碼都會使用react-router來進行每章的分頁,同時會為新的章節在導航中提供一個link)。

推薦閱讀:

Parcel,零配置開發 React 應用!
react-router browserHistory刷新頁面404問題解決
setState何時同步更新狀態
React 事件系統分析與最佳實踐
基於 Nest.js (nodejs 版的 spring ) 的 Notadd 2.0 Beta1 發布

TAG:reactrouter | 前端開發 | React |