理解 React 中的前端路由

threepi 7年前發布 | 17K 次閱讀 React 前端技術 webpack

學習前端很久了,最近被真阿當那篇總結給嚇到了,好多人都在批判阿當,雖然也有幾個人挺阿當,微博知乎上都是一片罵聲。前端現在這么火,有時候也會反思自己是不是踏進了一條渾水河,越趟越渾。

前端很火,既然我選擇了這條路,就應該放下心來,認真的學習基礎,2016 就快結束了,迎接嶄新的 2017,畢竟我還沒實現我的小目標呢!

前面已經寫過一篇關于webpack 的入門,以官方和阮大大為參考,干貨不是很多,或者說就沒干貨。這篇文章干貨應該也不是很多,也是以官方的教程為參考,唯一的干貨可能就是 react-router 的例子吧。

gulp 還是 webpack ?

到底是 gulp 還是 webpack,現在網上帖子對于這方面的疑問也很多,這兩個工具在某些功能上是有重合的,甚至有人還提出了這樣的問題:“有哪些功能是 webpack 取代不了 gulp 的”。我覺得,要真正的理解這兩者的取舍,還是要實際地去用,因為只有你使用了,才能做出正確的判斷。

gulp 是一個任務化工具,擅長流程化的一切事情,webpack 更像是一個模塊化管理工具,擅長模塊化資源的打包。但是 gulp 也可以弄模塊,webpack 也可以搞流程,甚至還有人推薦 gulp-webpack 這樣的 task。

這里面的林林總總,還是你自己去體會吧。今天撇開 gulp,來用 webpack 打造一個 react 的開發流程。

理解 react 路由

這篇文章要做到的是實現一個基本的前端路由 react-router,大概的頁面如下

這是一個多頁 app,Home 是首頁,導航欄另外有兩個頁面分別是 About 頁面和 Repos 頁面,點開之后 URL 會從 / 變到 /about 和 /repos 。

webpack-dev-server

webpack-dev-server 是 webpack 輕量級服務器, npm i webpack-dev-server --save 安裝,可以通過命令行直接來運行,也可以把它寫到 package.json 的 script 里,這樣子比較方便,有如下的配置參數 webpack-dev-server --devtool eval --progress --colors --hot ,分別表示:

  1. –devtool eval 當程序運行出錯的時候,方便查找到出錯的位置和原因
  2. –progress 打包應用的進度
  3. –colors 命令行日志變的有顏色
  4. –hot 熱更新

把它寫到 package.json 里面:

"script": {
  "start": "webpack-dev-server --devtool eval --progress --colors --hot"
}

webpack.config.js 的配置

基本的文件結構目錄如下:

public // 生成文件夾
- index.html
- index.css
- bundle.js
modules // 路由文件夾
- App.js
- Home.js
- ...
webpack.config.js
index.js // webpack 入口文件
package.json

然后就是最重要的 webpack 配置文件:

var webpack = require('webpack')
module.exports = {
  // 入口文件
  entry: './index.js',
  // 輸出文件
  output: {
    filename: 'bundle.js',
    path: 'public',
    publicPath: '/'
  },
  // babel 的加載,react 必須
  module: {
    loaders: [
      { test: /\.js$/, exclude: /node_modules/, loader: 'babel-loader?presets[]=es2015&presets[]=react' }
    ]
  },
  // 設置 product 環境變量,用于壓縮文件
  plugins: process.env.NODE_ENV = 'production'?[
    new webpack.optimize.DedupePlugin(),
    new webpack.optimize.OccurrenceOrderPlugin(),
    new webpack.optimize.UglifyJsPlugin({
      minimize: true,
      compress: {
        warnings: false,
      },
    })
  ]:[]
}

在配置文件中設置了 product 環境變量,開啟的話需要使用 NODE_ENV=production npm start ,window 用戶需要設置 SET "NODE_ENV=production" && npm start 。

什么是前端路由

對于路由,其實并不陌生,如果對 url 解析,就是 pathname,我們所要訪問那個資源文件。寫過 nodejs 服務器的同學都知道,后端路由的實現,就是解析 pathname,找到那個資源,簡單點的,直接把 html 文件 res.send 給請求者,復雜點的,是通過模版進行 html 構造來返回,如果找不到或權限其他原因,返回 404/error 碼。

前端路由和后端路由實現技術不一樣(可能前端文件只有一個 index.html),但是原理是一樣的,而且前端路由往往都是通過 hash 來實現了。比如要訪問 about 頁面,可能是這樣一個 url: 127.0.0.1:8080/#/about ,因為對于瀏覽器來說,# 后面的內容是不會被解析到,而前端 JS 可以通過 window.location.hash 讀取到,并通過一系列前端路由規則,對不同的路徑進行不同的處理。

到了 H5 之后,事情出現了變化。H5 對于 history 有了新的 api,會話歷史是可以操作的,不用 # 也可以通過 JS 操作前端路由。比如還是訪問 about 頁面,它實際上: 127.0.0.1:8080/about 。

首先從體驗上來說,前端控制路由,不用向服務器發送請求,訪問速度肯定會有非常大的提升,延遲就是前端處理的延遲。雖然有時候新打開的頁面會向服務器請求一些 ajax,但頁面的響應速度真的是非常的快,我只想說,這種感覺棒棒的。

但是有一個問題,前端路由控制的 about 頁面,其實后端是不存在的,如果我們要直接訪問這個頁面,或者在這個頁面刷新一下,會出現 404 的情況。這個問題也很好解決,服務器后端讓 /about 資源請求返回的資源是 / 資源即可,前端路由通過 url 判斷 /about 在前端跳轉到這個頁面。即無論你訪問哪個頁面,只要是我前端路由的范圍之內,我都給你返回 index.html 。

如果用的是 express 服務器,可以這樣:

import { match, RouterContext } from 'react-router'

var app = express()

app.get('*', (req, res) => {
  match({ routes: routes, location: req.url }, (err, redirect, props) => {
    // in here we can make some decisions all at once
    if (err) {
      // there was an error somewhere during route matching
      res.status(500).send(err.message)
    } else if (redirect) {
      // we haven't talked about `onEnter` hooks on routes, but before a
      // route is entered, it can redirect. Here we handle on the server.
      res.redirect(redirect.pathname + redirect.search)
    } else if (props) {
      // if we got props then we matched a route and can render
      const appHtml = renderToString(<RouterContext {...props}/>)
      res.send(renderPage(appHtml))
    } else {
      // no errors, no redirect, we just didn't match anything
      res.status(404).send('Not Found')
    }
  })
})

function renderPage(appHtml) {
  return `
    <!doctype html public="storage">
    <html>
    <meta charset=utf-8/>
    <title>My First React Router App</title>
    <link rel=stylesheet href=/index.css>
    <div id=app>${appHtml}</div>
    <script src="/bundle.js"></script>
   `
}

全是 ES6 的寫法。

hash 路由

先來介紹一下 hash 路由,現在還能看到很多 hash 路由的頁面,速度很快。

在根目錄下的 index.js 中,建立一個基本的 render 組件:

import App from './modules/App'
import About from './modules/About'
import Repos from './modules/Repos'
import { Router, Route, hashHistory } from 'react-router'

render((
  <Router history={hashHistory}>
    <Route path="/" component={App}>
      <Route path="/repos" component={Repos}/>
      <Route path="/about" component={About}/>
    </Route>
  </Router>
), document.getElementById('app'))

在 App.js 中,從 react-router 引入 Link

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

export default React.createClass({
  render() {
    return (
      <div>
        <h1>React Router Tutorial</h1>
        <ul role="nav">
          <li><Link to="/about">About</Link></li>
          <li><Link to="/repos">Repos</Link></li>
        </ul>
      </div>
    )
  }
})

About 和 Repos 頁面返回一個 React Component ,這樣子可以訪問通過主頁訪問這兩個頁面。

瀏覽器歷史記錄

前面配置的路由是 hash,使用的是 react-router 的 hashHistory,而并非 H5 的瀏覽器歷史記錄。

那么,如何使用瀏覽器的歷史記錄呢。萬能的 react-router 提供了 browserHistory ,把 hashHistory 替換成 browserHistory 即可:

import { Router, Route, browserHistory, IndexRoute } from 'react-router'

render((
  <Router history={browserHistory}>
    {/* ... */}
  </Router>
), document.getElementById('app'))

在 webpack-dev-server 中需要添加參數 --history-api-fallback ,但因為 webpack-dev-server 是一個輕量級的服務器,如果使用可能還需要用 Express 這樣的服務器,前面也介紹了如何配置。配置好之后,就可以直接通過 url: 127.0.0.1:8080/about 來訪問這個關于頁面。

為這個前端路由添加功能

現在路由的功能是有了,要達到效果,還是需要進一步美化的,在美化的過程中,還是可以學到一些知識的。

目前這個路由很亂,我們需要重新建立一個 Routes 文件,用來作為路由文件,以后修改的時候,也方便一些,畢竟我們是用 webpack 打包,文件多,不用怕!

修改根目錄下 index.js 文件:

import routes from './modules/routes'

render(
  <Router routes={routes} history={browserHistory}/>,
  document.getElementById('app')
)

在 modules 目錄下添加 routes.js 文件,其實就是把之前 index.js 少的東西復制過去:

module.exports = (
  <Route path="/" component={App}>
    <IndexRoute component={Home}/>
    <Route path="/repos" component={Repos}>
      <Route path="/repos/:userName/:repoName" component={Repo}/>
    </Route>
    <Route path="/about" component={About}/>
  </Route>
)

這里呢,我們在 repos 下又添加了一個 repo 文件,用來讀取 pathname,算是一個小擴展吧,使用上需要對路徑名前加個冒號,表示調用,然后在 repo.js 文件:

import React from 'react'

export default React.createClass({
  render(){
    return (
      <div>
        // 這里通過 params 獲取定義的路徑名
        <h2>{this.props.params.repoName}</h2>
      </div>
    )
  }
})

還可以向路由中動態添加 router,

React.createClass({
  contextTypes:{
    router: React.PropTypes.object
  },

  handSubmit(event){
    event.preventDefault();
    const userName = event.target.elements[0].value
    const repo = event.target.elements[1].value
    const path = `/repos/${userName}/${repo}`
    console.log(path)
    this.context.router.push(path)
  },
  render(){
    <form onSubmit={this.handSubmit}>
      <input type="text" placeholder="userName"/> / {' '}
      <input type="text" placeholder="repo"/>{' '}
      <button type="submit">Go</button>
    </form>
  }
})

 

來自:http://yuren.space/blog/2016/12/25/react-webpack/

 

 本文由用戶 threepi 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!