构建一个React Universal Blog App:分步指南

当单页应用程序(SPA)的主题出现时,我们倾向于考虑浏览器,JavaScript,速度和对搜索引擎的隐蔽性。 这是因为SPA使用JavaScript渲染页面的内容,并且由于网络搜寻器不使用浏览器来查看网页,因此它们无法查看和索引内容-或至少大多数不能。

这是一些开发人员试图以各种方式解决的问题:

  1. 添加一个转义的网站片段版本,该版本要求所有页面均以静态形式可用,并增加了许多额外的工作( 现已弃用 )。
  2. 使用付费服务将SPA取消浏览化为静态标记,以供搜索引擎爬网程序抓取。
  3. 相信搜索引擎现在已经足够先进,可以读取我们仅JavaScript的内容。 (我还不会。)

使用服务器上的Node.js和客户端上的React,我们可以将我们的JavaScript应用构建为通用 (或同构 )的。 这可以从服务器端和浏览器端渲染中提供一些好处,从而使搜索引擎和使用浏览器的人员都可以查看我们的SPA内容。

在本循序渐进的教程中,我将向您展示如何构建React Universal Blog App,该应用程序将首先在服务器端呈现标记,以使我们的内容可用于搜索引擎。 然后,它将使浏览器接管快速且响应迅速的单个页面应用程序。

构建一个React Universal Blog App

入门

我们的通用博客应用将使用以下技术和工具:

  1. 用于包管理和服务器端渲染的Node.js
  2. 对UI视图做出反应
  3. Express提供简单的后端JS服务器框架
  4. React Router进行路由
  5. React Hot Loader用于开发中的热加载
  6. 通量数据流
  7. 用于内容管理的Cosmic JS

首先,运行以下命令:

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

现在创建一个package.json文件并添加以下内容:

{
  "name": "react-universal-blog",
  "version": "1.0.0",
  "engines": {
    "node": "4.1.2",
    "npm": "3.5.2"
  },
  "description": "",
  "main": "app-server.js",
  "dependencies": {
    "babel-cli": "^6.26.0",
    "babel-loader": "^7.1.2",
    "babel-preset-es2015": "^6.24.1",
    "babel-preset-es2017": "^6.24.1",
    "babel-preset-react": "^6.24.1",
    "babel-register": "^6.26.0",
    "cosmicjs": "^2.4.0",
    "flux": "^3.1.3",
    "history": "1.13.0",
    "hogan-express": "^0.5.2",
    "html-webpack-plugin": "^2.30.1",
    "path": "^0.12.7",
    "react": "^15.6.1",
    "react-dom": "^15.6.1",
    "react-router": "1.0.1",
    "webpack": "^3.5.6",
    "webpack-dev-server": "^2.7.1"
  },
  "scripts": {
    "webpack-dev-server": "NODE_ENV=development PORT=8080 webpack-dev-server --content-base public/ --hot --inline --devtool inline-source-map --history-api-fallback",
    "development": "cp views/index.html public/index.html && NODE_ENV=development webpack && npm run webpack-dev-server"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "react-hot-loader": "^1.3.0"
  }
}

在此文件中,您会注意到我们添加了以下内容:

  1. Babel打包我们的CommonJS模块并将ES6和React JSX转换为与浏览器兼容的JavaScript
  2. Cosmic JS官方Node.js客户端,可轻松通过Cosmic JS云托管内容API提供我们的博客内容
  3. 用于应用程序数据管理的Flux(这是我们React应用程序中非常重要的元素)。
  4. 对服务器和浏览器上的UI管理做出反应
  5. React Router在服务器和浏览器上的路由
  6. 的WebPack为将一切汇集成一个bundle.js文件。

我们还在package.json文件中添加了一个脚本。 当我们运行npm run development ,脚本npm run development index.html文件从我们的views文件夹复制到我们的public文件夹中。 然后,它将webpack-dev-server的内容库设置为public/并启用热重载(保存.js文件)。 最后,它可以帮助我们从源头调试组件,并为我们找不到的页面提供后备(后退到index.html )。

现在,通过编辑文件webpack.config.js设置我们的webpack配置文件:

// webpack.config.js
var webpack = require('webpack')

module.exports = {
  devtool: 'eval',
  entry: './app-client.js',
  output: {
    path: __dirname + '/public/dist',
    filename: 'bundle.js',
    publicPath: '/dist/'
  },
  module: {
    loaders: [
      { test: /\.js$/, loaders: 'babel-loader', exclude: /node_modules/ },
      { test: /\.jsx$/, loaders: 'babel-loader', exclude: /node_modules/ }
    ]
  },
  plugins: [
    new webpack.DefinePlugin({
      'process.env.COSMIC_BUCKET': JSON.stringify(process.env.COSMIC_BUCKET),
      'process.env.COSMIC_READ_KEY': JSON.stringify(process.env.COSMIC_READ_KEY),
      'process.env.COSMIC_WRITE_KEY': JSON.stringify(process.env.COSMIC_WRITE_KEY)
    })
 ]
};

您会注意到,我们添加了一个值为app-client.jsentry属性。 该文件用作我们的应用程序客户端入口点,这意味着webpack将捆绑我们的应用程序并将其输出到/public/dist/bundle.js (在output属性中指定)。 我们还使用加载程序让Babel在我们的ES6和JSX代码上发挥其魔力。 React Hot Loader在开发过程中用于热加载(不刷新页面!)。

在进入与React相关的内容之前,让我们准备好我们博客的外观。 由于我希望您在本教程中更多地关注功能而不是样式,因此在此我们将使用预先构建的前端主题。 我从Start Bootstrap中选择了一个称为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>

要获取所有public包含的JS和CSS文件,可以从GitHub存储库中获取它们。 单击此处下载文件

通常,我会使用出色的React Bootstrap软件包,并避免使用jQuery。 但是,为了简洁起见,我们将保留主题的预构建jQuery功能。

在我们的index.html文件中,我们将在div id="app"处设置我们的React挂载点。 模板变量{{{ reactMarkup }}}将被转换为服务器渲染的标记,然后一旦浏览器启动,我们的React应用程序将接管并使用id="app"挂载至div 。 为了改善我们的JavaScript加载所有内容时的用户体验,我们在正文中添加了class="hidden" 。 然后,一旦安装了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组件。

我们的博客应用程序组件(基本示例)

让我们通过设置博客页面开始为我们的应用程序构建UI。 因为这将成为创意专业人士的投资组合博客,所以我们的博客将包含以下页面:

  1. 关于
  2. 工作
  3. 联系

让我们开始创建一个名为app-client.js的文件,并向其中添加以下内容:

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

// Routes
import routes from './routes'

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

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

为了更好地了解React Router的工作原理,您可以访问其GitHub repo 。 要点在于,我们在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 ,我们正在加载已设置的基本路由。 这些将渲染的标记转换为字符串,然后将其作为变量传递给我们的模板。

我们已经准备好启动服务器并查看其上的代码,但首先,让我们创建一个脚本来执行此操作。

打开package.json文件,然后编辑script部分,如下所示:

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

现在我们已经建立了production脚本,我们可以在服务器端和客户端上运行代码。 在您的终端中执行:

npm start

在浏览器中导航到http:// localhost:3000 。 您应该看到简单的博客内容,并且能够在SPA模式下快速轻松地浏览页面。

继续并点击view source 。 请注意,我们的SPA代码也可供所有机器人找到。 我们两全其美!

结论

在第一部分中,我们开始研究React的世界,并了解如何与Node.js一起使用它来构建React Universal Blog App。

如果您希望将博客带入一个新的水平,并且知道如何添加和编辑内容,请不要忘记阅读第二部分“ 构建React Universal Blog App:实现Flux ”。 我们将深入探讨如何使用React组织概念和Flux模式轻松扩展React Universal Blog App。

我们已经与Open SourceCraft合作,为您带来了来自React Developers的6个专业提示 有关更多开源内容,请查看Open SourceCraft

From: https://www.sitepoint.com/building-a-react-universal-blog-app-a-step-by-step-guide/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值