學習使用React一步步搭建普通博客應用

當咱們考慮一些單頁應用的時候(SPAs),通常考慮瀏覽器,JavaScript和速度,對搜索引擎是不可見的。因爲單頁應用使用JavaScript來渲染頁面中的內容,同時web網絡爬蟲不經過瀏覽器來查看整個網頁,這樣就不能看到和索引頁面中全部的內容。或者,更好的說,其中大部分是不能。一些開發人員試圖以各類方式來解決這個問題。css

在服務器端使用Node.js在客戶端使用React,咱們能夠構建通用的JavaScript應用程序。這能夠從服務器端和瀏覽器端的渲染中提供許多好處,容許搜索引擎和人類使用瀏覽器來查看單頁應用中的內容。html

在這個教程中分爲兩個部分,我將向您展現如何經過服務器端渲染大搭建一個普通的React博客應用系統來使應用對搜素引擎可見。而後,它可以使該應用在瀏覽器中快速和響應式。前端

開始

該博客系統將使用到如下一些技術和工具:html5

  1. Node.js用於包管理和服務端渲染node

  2. React用於視圖層react

  3. Express做爲一個簡單的後端JS服務端框架jquery

  4. React Router用於路由webpack

  5. React Hot Loader 用於開發中的熱加載git

  6. Flux 用於數據流github

  7. Cosmic JS用於內容管理

開始以前,首先在命令行中運行一下內容:

mkdir react-universal-blog
cd react-universal-blog

新建一個package.json文件,在裏面添加一下內容:

{
  "name": "react-universal-blog",
  "version": "1.0.0",
  "description": "",
  "main": "app-server.js",
  "dependencies": {
    "babel": "^5.8.29",
    "babel-core": "^5.8.32",
    "babel-loader": "^5.3.2",
    "cosmicjs": "^2.0.0",
    "events": "^1.1.0",
    "express": "^4.13.3",
    "flux": "^2.1.1",
    "history": "^1.14.0",
    "hogan-express": "^0.5.2",
    "lodash": "^3.10.1",
    "react": "^0.14.1",
    "react-dom": "^0.14.1",
    "react-router": "^1.0.1",
    "webpack": "^1.12.2"
  },
  "scripts": {
    "development": "cp views/index.html public/index.html && NODE_ENV=development webpack && webpack-dev-server --content-base public/ --hot --inline --devtool inline-source-map --history-api-fallback"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "react-hot-loader": "^1.3.0",
    "webpack-dev-server": "^1.12.1"
  }
}

在這個文件中你應該已經注意到了咱們添加了一下內容:

  1. Babel 用於打包符合CommonJS模塊規範,同時將ES6和React JSX的語法格式轉換爲瀏覽器兼容的JavaScript。

  2. Cosmic JS官方的Node.js客戶端可以經過Cosmic JS雲端內容接口服務於咱們的博客內容系統。

  3. Flux用於應用的數據管理(這在React 應用程序中很是重要)。

  4. React 用於服務器和瀏覽器的視圖管理

  5. Webpack將全部文件打包成一個bundle.js文件。

同時咱們在package.json文件中加入了一個腳本文件,當咱們運行npm run development時,腳本將從咱們的views文件夾複製index.html文件到public文件夾。

配置webpack文件,webpack.config.js:

// webpack.config.js
if(process.env.NODE_ENV === 'development'){
  var loaders = ['react-hot','babel']
} else {
  var loaders = ['babel']
}
module.exports = {
  devtool: 'eval',
  entry: './app-client.js',
  output: {
    path: __dirname + '/public/dist',
    filename: 'bundle.js',
    publicPath: '/dist/'
  },
  module: {
    loaders: [{
      test: /\.js$/,
      loaders: loaders,
      exclude: /node_modules/
    }]
  }
};

注意到咱們這裏添加了一個entery屬性,屬性的值爲app-client.js。這個文件將做爲咱們應用的入口點,意味着webpack將從這個點開始打包咱們的應用,而且將其輸出到路徑/public/dist/bundle.js。同時使用加載器使Babel在包含ES6JSX的代碼中運行。

在講解與React相關的一些技術內容以前,先來看一些咱們完成整個博客將要展示的樣子。因爲次教程中咱們這裏但願你可以將更多的精力放在搭建應用的功能性上而不是博客的樣式上面,這裏咱們選擇使用已經建好的前端樣式主題,選擇Start Bootsrtap樣式裏面的Clean Blog

新建一個文件夾,命名爲views,在文件夾內新建文件index.html
在html文件中添加一下代碼:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <meta name="description" content="">
  <meta name="author" content="">
  <title>{{ site.title }}{{# page }} | {{ page.title }}{{/ page }}</title>
  <!-- Bootstrap Core CSS -->
  <link href="/css/bootstrap.min.css" rel="stylesheet">
  <!-- Custom CSS -->
  <link href="/css/clean-blog.min.css" rel="stylesheet">
  <link href="/css/cosmic-custom.css" rel="stylesheet">
  <!-- Custom Fonts -->
  <link href="//maxcdn.bootstrapcdn.com/font-awesome/4.1.0/css/font-awesome.min.css" rel="stylesheet" type="text/css">
  <link href="//fonts.googleapis.com/css?family=Lora:400,700,400italic,700italic" rel="stylesheet" type="text/css">
  <link href="//fonts.googleapis.com/css?family=Open+Sans:300italic,400italic,600italic,700italic,800italic,400,300,600,700,800" rel="stylesheet" type="text/css">
  <!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
  <!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
  <!--[if lt IE 9]>
    <script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
    <script src="https://oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js"></script>
  <![endif]-->
</head>
<body class="hidden">
  <div id="app">{{{ reactMarkup }}}</div>
  <script src="/js/jquery.min.js"></script>
  <script src="/js/bootstrap.min.js"></script>
  <script src="/js/clean-blog.min.js"></script>
  <script src="/dist/bundle.js"></script>
</body>
</html>

全部的公共JS和CSS文件下載GitHub repository點擊這裏下載

一般爲了防止使用jQuery,通常我會選擇經典的React Bootstrap。然而爲了簡潔,在主題框架中使用了部分jQuery的功能。

index.html文件中,咱們在id="app"div結點上搭建本身的React點。

在這個點上,你的應用程序擁有如下結構:

package.json
public
  |-css
    |-bootstrap.min.css
    |-cosmic-custom.css
  |-js
    |-jquery.min.js
    |-bootstrap.min.js
    |-clean-blog.min.js
views
  |-index.html
webpack.config.js

搭建好靜態目錄以後,開始創建React的組件。

博客應用的基本組價

開始搭建博客的應用界面,博客包括如下頁面:

  1. Home

  2. About

  3. Work

  4. Contact

開始創建新的文件,命名app-clietn.js,具體內容以下:

// app-client.js
import React from 'react'
import {render} from 'react-dom'
import {Route} from 'react-router'
import createBrowserHistory from 'history/lib/createBrowserHistory'
const history = createBrowerHistory()

// Routes
import routes from './routes'

const Routes = (
    <Router history={history}>
        {routes}
    </Router>
)

const app = document.getElementById('app')
render(Routes, app)

若是你想進一步瞭解有關React Router 的工做原理,能夠訪問Github 地址app-client.js文件中Router組件使得瀏覽器客戶端路由,服務器端渲染不須要瀏覽器歷史記錄,因此這裏咱們須要創建一個分開的routes.js文件用來共享服務端和客戶端的入口點。

添加routes.js文件:

// routes.js
import React, {Component} from 'react'
import { Route, IndexRoute, Link} from 'react-router'

//Main component
class App extends Component{
    componentDidMount(){
        document.body.className=''
    }
    render(){
        return (
            <div>
                <h1>React Universal Blog</h1>
                   <nav>
                      <ul>
                         <li><Link to="/">Home</Link></li>
                         <li><Link to="/about">About</Link></li>
                         <li><Link to="/work">Work</Link></li>
                         <li><Link to="/contact">Contact</Link></li>
                      </ul>
                   </nav>
                { this.props.children }
              </div>        
        )
    }
}

//Pages
class Home extends Component{
    render(){
        return (
          <div>
            <h2>Home</h2>
            <div>Some home page content</div>
          </div>
        )
    }
}
class About extends Component {
  render(){
    return (
      <div>
        <h2>About</h2>
        <div>Some about page content</div>
      </div>
    )
  }
}
class Work extends Component {
  render(){
    return (
      <div>
        <h2>Work</h2>
        <div>Some work page content</div>
      </div>
    )
  }
}
class Contact extends Component {
  render(){
    return (
      <div>
        <h2>Contact</h2>
        <div>Some contact page content</div>
      </div>
    )
  }
}
class NoMatch extends Component {
  render(){
    return (
      <div>
        <h2>NoMatch</h2>
        <div>404 error</div>
      </div>
    )
  }
}

export default (
  <Route path="/" component={App}>
    <IndexRoute component={Home}/>
    <Route path="about" component={About}/>
    <Route path="work" component={Work}/>
    <Route path="contact" component={Contact}/>
    <Route path="*" component={NoMatch}/>
  </Route>
)

到目前爲止,咱們搭建好了一個基本的包含不一樣頁面的博客應用例子。如今,讓咱們來具體運行一下應用,在終端中運行一下內容:

mkdir public
npm install
npm run development

在瀏覽器中輸入網址http://localhost:8080來查看博客運行的基本效果。

上述步驟完成以後,如今運行至服務器端,新建文件app-server.js並添加如下內容:

// app-server.js
import React from 'react'
import { match, RoutingContext } from 'react-router'
import ReactDOMServer from 'react-dom/server'
import express from 'express'
import hogan from 'hogan-express'

// Routes
import routes from './routes'

// Express
const app = express()
app.engine('html', hogan)
app.set('views', __dirname + '/views')
app.use('/', express.static(__dirname + '/public/'))
app.set('port', (process.env.PORT || 3000))

app.get('*',(req, res) => {

  match({ routes, location: req.url }, (error, redirectLocation, renderProps) => {
    
    const reactMarkup = ReactDOMServer.renderToStaticMarkup(<RoutingContext {...renderProps}/>)

    res.locals.reactMarkup = reactMarkup

    if (error) {
      res.status(500).send(error.message)
    } else if (redirectLocation) {
      res.redirect(302, redirectLocation.pathname + redirectLocation.search)
    } else if (renderProps) {
      
      // Success!
      res.status(200).render('index.html')
    
    } else {
      res.status(404).render('index.html')
    }
  })
})

app.listen(app.get('port'))

console.info('==> Server is listening in ' + process.env.NODE_ENV + ' mode')
console.info('==> Go to http://localhost:%s', app.get('port'))

app-server.js文件中,咱們載入了基本的路由文件route.js.這些都是呈現標記轉換成一個字符串,而後將它做爲變量傳遞給咱們的模板。

在接下來的步驟中,咱們將創建文件app.js這可使咱們在Node.js中使用ES6是語法格式。文件包含如下內容:

//app.js
require('babel/register')
require('./app-server.js')

咱們將從該文件啓動咱們的服務, 不過首先,讓咱們先建立一個腳本。

打開package.json文件,編輯裏面的腳本部分文件以下:

// ...
"scripts": {
  "development": "cp views/index.html public/index.html && NODE_ENV=development webpack && webpack-dev-server --content-base public/ --hot --inline --devtool inline-source-map --history-api-fallback",
  "production": "rm -rf public/index.html && NODE_ENV=production webpack -p && NODE_ENV=production node app.js",
  "start": "npm run production"
}
// ...

到目前爲止,已經部署好了生產環境的腳本代碼,咱們能夠同時在服務器d端和客戶端運行代碼,終端中運行如下內容:

npm start

在瀏覽器地址欄中輸入http://localhsot:3000.你就能夠看到你的博客單頁應用了。

在瀏覽器中點擊查看源碼。

結論

在這部分中,咱們初步瞭解了使用React和Node.js一塊兒搭建一個React普通博客應用。

相關文章
相關標籤/搜索