把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
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...