把react什麼的都用起來 【3】穿越spa的路由

来源:http://www.cnblogs.com/tolg/archive/2016/03/25/5318900.html
-Advertisement-
Play Games

接著上回新聞搜索的例子。現在我們要通過路由進入一個新的頁面來查看新聞詳細內容。 react和路由並沒有什麼直接關係,用什麼路由都可以。不過使用react-router可以讓我們的代碼風格統一, 並且有些工具使用起來很方便。 先來安裝react-router庫(我目前安裝的版本是2.0.1,跟1.x版 ...


接著上回新聞搜索的例子。現在我們要通過路由進入一個新的頁面來查看新聞詳細內容。

react和路由並沒有什麼直接關係,用什麼路由都可以。不過使用react-router可以讓我們的代碼風格統一, 並且有些工具使用起來很方便。

先來安裝react-router庫(我目前安裝的版本是2.0.1,跟1.x版本區別比較大):

npm install react-router --save

從使用上來說,react-router不過是一些react組件,所以用起來特別方便。不用多說,看個例子就知道怎麼用了。 先把咱們已經做好的Login和NewsList兩個頁面放到路由里。只需修改src/index.js文件:

import React from 'react';
import { render } from 'react-dom';
import { Provider } from 'react-redux';
import { Router, Route, browserHistory } from 'react-router'
import configureStore from './stores';
import Login from './containers/Login'
import NewsList from './containers/NewsList';

const store = configureStore();

render(
  <Provider store={store}>
    <Router history={browserHistory}>
      <Route path="newslist" component={NewsList} />
      <Route path="login" component={Login} />
    </Router>
  </Provider>,
  document.getElementById('app')
);

這個文件相比以前只是把Provider標簽裡面的內容換了。以前咱們只放一個Login或者NewsList組件, 現在是放一個Router組件。Router組件只需要一個history屬性,讓我們可以選擇使用哪種歷史管理方式。 我們常用的就是browserHistory和hashHistory。browserHistory就是我們最熟悉的瀏覽器管理歷史, 使用這種歷史管理方式感覺上跟普通瀏覽網頁的方式一樣:url路徑會隨著跳轉及前進、後退按鈕而變化, 但是在react-router的browserHistory管理下,url的變化不會導致頁面刷新。 hashHsitory只控制url中#號後面的部分,這是前一段時間單頁應用比較通用的方式,但是隨著HTML5的普及, 這個方式有逐漸被淘汰的趨勢。這裡我們使用browserHistory。

現在我們已經可以通過http://localhost:8000/newslist訪問上一節做的新聞列表頁面了。

接著把新聞詳情頁做出來吧。由於我們在新聞列表介面已經取到了全部的新聞內容,也為了簡單,也為了反應快, 我們就直接用新聞列表介面提供的數據,而不再訪問伺服器了。

數據都在store里,任我們怎麼玩。新聞詳情頁訪問數據有兩種方案:一種是記錄新聞列表的index,然後直接根據index訪問列表裡相應的內容; 另一種是把要打開的新聞內容單拿出來一份另放到一個state里。我們用第二種方案。 還是先寫action,直接在src/actions/news.js裡面添加內容:

export const SET_CURRENT_NEWS = 'SET_CURRENT_NEWS'
const setCurrent = cac(SET_CURRENT_NEWS, 'news')
export const chooseNews = index => (dispatch, getState) => {
  let current = getState().news.list[index]
  dispatch(setCurrent(current))
}

setCurrentNews就是要把一個新聞對象放到相應的state中。chooseNews則是在組件里要調用的, 它根據一個index找出相應的新聞對象並放到當前新聞的state里。

然後往src/reducers/news.js添加新的reducer:

current: cr({}, {
  [SET_CURRENT_NEWS](state, {news}){return news}
})

別忘了引入新定義的的action常量。

NewsList組件得派發設置當前新聞的動作,並跳轉到新聞詳情頁面,只需要改renderList方法就行:

renderList(){
  return this.props.list.map((item, i) =>{
    item.key = item.title
    item.onGotoDetail = () => {
      this.props.dispatch(chooseNews(i))
      this.props.history.push('/newsviewer')
    }
    return React.createElement(NewsOverview, item)
  })
}

這裡給每個NewsOverview組件都傳了個onGotoDetail屬性,NewsOverview在被點擊時要調用這個屬性的函數,只需要在最外層div加個click事件處理,像這樣:

<div onClick={this.props.onGotoDetail}>

在item.onGotoDetail函數中有個this.props.history,它就是我們前面在構建路由時選擇的那個browserHistory,當我們的組件作為Route組件的屬性使用時,Route會給我們的組件註入這個history屬性,這樣用起來就比較方便了。這個history的方法和瀏覽器里的history所擁有的那幾個方法功能差不多,常用的就是go(跳轉)、goBack(回退一個歷史)、goForword(前進一個歷史)、push(跳轉到一個url並添加一個歷史狀態)、replace(跳轉到一個url並替換當前歷史狀態)。具體的可以參考專門對瀏覽器history論述的文章。如果我們想在組件之外控制歷史狀態(比如action里),從react-router里引入browserHistory或hashHsitory直接用就可以。

最後添加新聞詳情頁面的組件,這就很簡單了吧。不過這個組件跟NewsOverview比較起來實在太像,就是新聞概述和詳細內容的區別。 所以這裡我偷個懶,讓NewsOverview通過一個屬性變身為可配置成新聞詳情的組件。把NewsOverview裡面最後一個P標簽改成這樣就行:

{this.props.showDetail ?
  <p dangerouslySetInnerHTML={{__html:this.props.message}}/> :
  <p>{this.props.description}</p>
}
要在react的jsx裡面直接放數據里的html文本,只能用dangerouslySetInnerHTML屬性, 看這屬性意思就知道react是多麼不希望我們用這個屬性。所以不到萬不得已還是不用為好。誰讓現在咱們是依賴別人現成的介面呢。

然後新建個src/containers/NewsViewer.js,它就很簡單了:

import React from 'react'
import {connect} from 'react-redux'
import NewsOverview from 'components/NewsOverview'

class NewsViewer extends React.Component{
  render(){
    return (
      <div>
        {React.createElement(NewsOverview, Object.assign({
          showDetail: true
        }))}
      </div>
    )
  }
}

export default connect(state => {return {news: state.news.current}})(NewsViewer)

最後在index.js裡面再添加一個路由:

<Route path="newsviewer" component={NewsViewer} />

功能是完美地實現了,但是想一下我們為什麼要用路由?而且還要用瀏覽器管理歷史的路由? 一個很重要的原因就是網站不同於app,它要保證輸入任何一個有效的url後都要給用戶呈現出一個可用的頁面。 一個非常實用的場景就是剛纔我在新聞詳情頁里閱讀到一則很好的新聞,想給分享出去,那別人要通過這個url還能查看到這個新聞。 我們目前沒做到這個。現在我們要實現依靠id訪問到新聞。

id一定是通過url傳來的,可以用query參數,但我們用一個更簡潔的形式:“/newsviewer/30998729”,後面那串數字是新聞的id。 配置很簡單,把新聞詳情頁的路由改成這樣就行了:

<Route path="newsviewer/:id" component={NewsViewer} />

然後要修改src/containers/NewsList.js裡面路由跳轉的那句:

this.props.history.push('/newsviewer/' + item.id)

NewsViewer組件將要載入時讓它去獲取一下新聞詳細內容。還記得目前數據來源是直接從新聞列表裡拽過來的是吧, 沒關係,還讓它拽吧,這樣既能有一般情況下訪問的“唰”一下的用戶體驗,又能保證直接訪問url能獲取到內容。

給src/actions/news.js再加一個獲取數據的action:

export const fetchNewsDetail = id => dispatch => window.$.ajax({
  url: 'http://www.tngou.net/api/top/show',
  data: {id},
  dataType: 'jsonp',
  success: data => data.status && dispatch(setCurrentNews(data))
})

給src/containers/NewsViewer.js加一個componentWillMount方法,讓組件將要載入時就去獲取數據:

componentWillMount(){
  // 在react-router的幫助下,我們可以很輕鬆地拿到url路徑上的參數id
  this.props.dispatch(fetchNewsDetail(this.props.params.id))
}

現在就可以直接通過http://localhost:8000/newsviewer/3864來訪問新聞詳情頁面了。哦,可能會有找不到assets/app.js的報錯, 在index.html裡面把引用他的路徑改成絕對路徑“/assets/app.js”就行了。

我們在開發環境中直接訪問http://localhost:8000/newslist或者http://localhost:8000/newsviewer/3864 這樣的路徑都沒啥問題,但是你要嘗試一下把項目導出部署到生產環境的靜態的伺服器上,再訪問http://xxx.xxx/newslist就悲劇的404了。 因為那個伺服器真去找newslist這個文件了,哪有這個文件呀,咱只有index.html。 要想使用browserHistory只好去配置生產環境的伺服器。具體配置等到後面生產環境配置一節再說吧。

react-router的路由並不是扁平的,而是樹狀結構的,不僅路徑可以組織成樹狀結構,組件也可以組織成相應的樹狀結構。

比如我們想要個通用的header,裡面還有返回和登錄按鈕。先把header作為一個組件寫出來再說。

src/components/Header.js:

import React from 'react';
import {Link} from 'react-router'

export default class Header extends React.Component {
  render(){
    let styl = {
      textAlign:'center',
      lineHeight:'32px',
      width:'15%',
      float:'left'
    }
    return (
      <div style={{background: '#ddd', height:'32px'}}>
        <div style={styl} onClick={this.props.onGoBack}>{'<'}</div>
        <div style={Object.assign({},styl,{width:'70%'})}>{this.props.text}</div>
        <Link style={Object.assign({},styl,{float: 'right'})} to="/login">登錄</Link>
      </div>
    )
  }
}

然後再把原來那個App.js找回來吧,它作為路由中的頂層組件,對應根路徑“/”。把前面做的Header放進去:

src/containers/App.js:

import React from 'react';
import Header from 'components/Header'

class App extends React.Component {
  render() {
    return (
      <div>
        <Header onGoBack={this.goBack.bind(this)} text="歡迎訪問"/>
        <div style={{paddingTop:'10px'}}>
          {this.props.children}
        </div>
      </div>
    )
  }
  goBack(){
    this.props.history.goBack()
  }
}

export default connect()(App);

上面代碼的render方法里,除了放進去了Header,還要註意那個this.props.children,react-router就是把這個屬性所對應的組件作為App所對應路徑的下一級路由的。

再來改一下src/index.js裡面的路由。由於以後路由會越來越多,所以我打算把所有的route標簽拿出去,放到一個單獨的src/routes.js文件里,index.js里只要引入這個文件並放到原來route們的位置上就行了。

src/routes.js

import React from 'react'
import { Route } from 'react-router'
import App from './containers/App';
import Login from './containers/Login'
import NewsList from './containers/NewsList';
import NewsViewer from './containers/NewsViewer'

export default (
  <Route path="/" component={App}>
    <Route path="news" component={NewsList} />
    <Route path="news/:id" component={NewsViewer} />
    <Route path="login" component={Login} />
  </Route>
)

做一個小小的手腳,為了url簡潔,我把原來的newslist改成了news,而news後面加斜杠id的形式作為新聞詳情。這兩個url是平級的,看上去像是父子關係,其實結構上是完全平等的。別忘了NewsOverview.js里的連接也要改。

現在訪問/news可以搜索新聞,點擊新聞標題可以跳轉到/news/xxx查看詳細內容,點擊登錄可以跳轉登陸頁,可是,訪問根路徑卻只有一個帶標題的空白頁。我們可以加一個預設頁面,就是在訪問某一級帶有子路徑路由時,可以給它一個對應到這個路徑的頁面,不一定是跟路徑哦。做個索引作為預設頁面吧,src/containers/Index.js:

import React from 'react';
import {Link} from 'react-router'

class Index extends React.Component {
  render(){
    return (
      <ul>
        <li><Link to="/news">新聞</Link></li>
      </ul>
    )
  }
}

export default Index

雖然這個組件目前沒有連接到redux,我還是忍不住把它放到了containers目錄下麵,畢竟它是一個頁面級別的組件,沒準哪天產品經理有個啥想法它就要和外界打交道了。

然後添加路由,這個路由比較特殊,不是用Route,而要用個專門的組件IndexRoute,整個src/routes.js代碼如下:

import React from 'react'
import { Route, IndexRoute } from 'react-router'
import Index from './containers/Index';
import App from './containers/App';
import Login from './containers/Login'
import NewsList from './containers/NewsList';
import NewsViewer from './containers/NewsViewer'

export default (
  <Route path="/" component={App}>
    <IndexRoute component={Index} />
    <Route path="news" component={NewsList} />
    <Route path="news/:id" component={NewsViewer} />
    <Route path="login" component={Login} />
  </Route>
)

至此,我們可以用react和相關技術打造完整的單頁web應用了。

上一節 【2】非同步action和redux中間件下一節 【4】生產部署和優化
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • js中不允許出現“ - ” 頁面中改變文字大小-案例: class 點擊按鈕變成覆選框checkbox 改變DIV的浮動 判斷註意事項 所有的相對路徑都別拿來做判斷。。。 img src href="css.css" 絕對路徑可以: img src="http://........jpg" 顏色值不 ...
  • 出處:http://blog.csdn.net/dyllove98/article/details/8957232 CSS3中和動畫有關的屬性有三個 transform、 transition 和 animation。下麵來一一說明: transform 從字面來看transform的釋義為改變,使 ...
  • 1:基本雛形 <html><head> <meta charset="UTF-8"> <title></title></head><body><!--標題標簽--> <h1>11111111111111111</h1> <h2>11111111111111111</h2> <h3>111111111 ...
  • 今天編碼時遇到一個問題,通過後臺查詢的數據設置前端checkbox的選中狀態,設置選中狀態為.attr('checked','true');沒有問題,但是當數據重新載入時,checkbox應清空即所有checkbox為未選中狀態,使用.attr('checked','false');無效果,且全部為 ...
  • 一.冒泡排序 二.選擇排序 三.插入排序 四.希爾排序 五.歸併排序 六.快速排序 ...
  • 搜集了一下網上的資源和自己看過的一些書,小小總結了一波HTTP,現在也只是很膚淺的瞭解,期望以後深入理解後能寫出更有營養的筆記。 HTTP協議的主要特點 + 支持客戶/伺服器模式。+ 簡單快速:客戶向伺服器請求服務時,只需傳送請求方法和路徑。請求方法常用的有GET、HEAD、POST。每種方法規定了 ...
  • × 目錄 [1]跟隨圖標 [2]視頻提示 [3]下拉菜單[4]邊緣對齊[5]星號 [6]全屏適應[7]半區翻圖[8]九宮格[9]等高佈局[10]整體佈局 前面的話 之前的博客文章已經詳細介紹過絕對定位的基礎知識,由於它的用途實在廣泛,於是單獨為其寫這篇關於其應用的博客。關於絕對定位的基礎知識移步至此 ...
  • 做WEB項目的過程中難免涉及到表單的處理,包括:數據校驗、數據提交、返回處理、信息提示等。 下麵的代碼就是從前不久一個項目中提煉出來的,希望對大家有些幫助。 下麵是主要的代碼片段: 說明: - form必須定義一個id,在後面會用到 - submit按鈕的data-url屬性指定了後端處理程式 - ...
一周排行
    -Advertisement-
    Play Games
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...