学习使用React一步步搭建普通博客应用

当我们考虑一些单页应用的时候(SPAs),一般考虑浏览器,JavaScript和速度,对搜索引擎是不可见的。由于单页应用使用JavaScript来渲染页面中的内容,同时web网络爬虫不通过浏览器来查看整个网页,这样就不能看到和索引页面中所有的内容。或者,更好的说,其中大部分是不能。一些开发人员试图以各种方式来解决这个问题。

在服务器端使用Node.js在客户端使用React,我们可以构建通用的JavaScript应用程序。这可以从服务器端和浏览器端的渲染中提供许多好处,允许搜索引擎和人类使用浏览器来查看单页应用中的内容。

在这个教程中分为两个部分,我将向您展示如何通过服务器端渲染大搭建一个普通的React博客应用系统来使应用对搜素引擎可见。然后,它能够使该应用在浏览器中快速和响应式。

开始

该博客系统将使用到以下一些技术和工具:

  1. Node.js用于包管理和服务端渲染

  2. React用于视图层

  3. Express作为一个简单的后端JS服务端框架

  4. React Router用于路由

  5. React Hot Loader 用于开发中的热加载

  6. Flux 用于数据流

  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,600italic,700italic,800italic,400,300,600,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普通博客应用。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


react 中的高阶组件主要是对于 hooks 之前的类组件来说的,如果组件之中有复用的代码,需要重新创建一个父类,父类中存储公共代码,返回子类,同时把公用属性...
我们上一节了解了组件的更新机制,但是只是停留在表层上,例如我们的 setState 函数式同步执行的,我们的事件处理直接绑定在了 dom 元素上,这些都跟 re...
我们上一节了解了 react 的虚拟 dom 的格式,如何把虚拟 dom 转为真实 dom 进行挂载。其实函数是组件和类组件也是在这个基础上包裹了一层,一个是调...
react 本身提供了克隆组件的方法,但是平时开发中可能很少使用,可能是不了解。我公司的项目就没有使用,但是在很多三方库中都有使用。本小节我们来学习下如果使用该...
mobx 是一个简单可扩展的状态管理库,中文官网链接。小编在接触 react 就一直使用 mobx 库,上手简单不复杂。
我们在平常的开发中不可避免的会有很多列表渲染逻辑,在 pc 端可以使用分页进行渲染数限制,在移动端可以使用下拉加载更多。但是对于大量的列表渲染,特别像有实时数据...
本小节开始前,我们先答复下一个同学的问题。上一小节发布后,有小伙伴后台来信问到:‘小编你只讲了类组件中怎么使用 ref,那在函数式组件中怎么使用呢?’。确实我们...
上一小节我们了解了固定高度的滚动列表实现,因为是固定高度所以容器总高度和每个元素的 size、offset 很容易得到,这种场景也适合我们常见的大部分场景,例如...
上一小节我们处理了 setState 的批量更新机制,但是我们有两个遗漏点,一个是源码中的 setState 可以传入函数,同时 setState 可以传入第二...
我们知道 react 进行页面渲染或者刷新的时候,会从根节点到子节点全部执行一遍,即使子组件中没有状态的改变,也会执行。这就造成了性能不必要的浪费。之前我们了解...
在平时工作中的某些场景下,你可能想在整个组件树中传递数据,但却不想手动地通过 props 属性在每一层传递属性,contextAPI 应用而生。
楼主最近入职新单位了,恰好新单位使用的技术栈是 react,因为之前一直进行的是 vue2/vue3 和小程序开发,对于这些技术栈实现机制也有一些了解,最少面试...
我们上一节了了解了函数式组件和类组件的处理方式,本质就是处理基于 babel 处理后的 type 类型,最后还是要处理虚拟 dom。本小节我们学习下组件的更新机...
前面几节我们学习了解了 react 的渲染机制和生命周期,本节我们正式进入基本面试必考的核心地带 -- diff 算法,了解如何优化和复用 dom 操作的,还有...
我们在之前已经学习过 react 生命周期,但是在 16 版本中 will 类的生命周期进行了废除,虽然依然可以用,但是需要加上 UNSAFE 开头,表示是不安...
上一小节我们学习了 react 中类组件的优化方式,对于 hooks 为主流的函数式编程,react 也提供了优化方式 memo 方法,本小节我们来了解下它的用...
开源不易,感谢你的支持,❤ star me if you like concent ^_^
hel-micro,模块联邦sdk化,免构建、热更新、工具链无关的微模块方案 ,欢迎关注与了解
本文主题围绕concent的setup和react的五把钩子来展开,既然提到了setup就离不开composition api这个关键词,准确的说setup是由...
ReactsetState的执行是异步还是同步官方文档是这么说的setState()doesnotalwaysimmediatelyupdatethecomponent.Itmaybatchordefertheupdateuntillater.Thismakesreadingthis.staterightaftercallingsetState()apotentialpitfall.Instead,usecom