• Hello World

首先Node环境肯定要有,然后下面这一堆

npm i react
npm i react-dom
# 让他支持es6
npm install babel-cli -g
npm install babel-preset-react -S
npm install babel-preset-env -S 
npm install babel-plugin-transform-decorators-legacy -S

npm install express --save
npm install --save-dev cross-env  # 跨平台设置环境变量
npm install -g nodemon  # 监视文件的改变并重新运行命令

装完后,package.json中设置运行命令:

"scripts": {
  "serve": "cross-env NODE_ENV=test nodemon --exec babel-node src/server.js"
}

项目根目录新建.babelrc,配置插件这些的

{
  "presets": [
    "env",
    "react"
  ],
  "plugins": [
    "transform-decorators-legacy"
  ]
}

src/server.js

import express from 'express'

var app = express()

app.get('/', (req, res) => {
  res.send('<h1>hello world</h1>')
})

app.listen(4000, () => {
  console.log('server started')
})

npm run serve 后localhost:4000就能看到Hello World了

  • react服务器端渲染的实现

react-dom/server包里有两个方法renderToString和renderToStaticMarkup,这个两个方法都是React Comonent转化为HTML字符串。React 15中有如下区别,但是React 16中已经废弃了data-react-checksum等属性。

区别在于renderToString生成的HTML的DOM会带有额外属性:各个DOM都会有data-react-id属性,第一个DOM会有data-checksum属性。renderToStaticMarkup不会有这些额外属性,从而节省HTML字符串大小

15中当重新渲染节点时,ReactDOM.render()方法执行与服务端生成的字符挨个比对。如果一旦有不匹配的,不论什么原因,替换整个服务端的节点数。这样使得性能损耗很大。

16开始的检查变宽松了,他只替换他认为不一致的地方,单独替换。仅仅尝试修改不匹配的html子树,而不是修改整个HTML树。

src/components/App.js

import React, { Component } from 'react'

export default class App extends Component {
  render() {
    return (
      <div>
        <h1>Hello React</h1>
      </div>
    )
  }
}

server.js

...
import React from 'react'
import { renderToString } from 'react-dom/server'

app.get('/', (req, res) => {
  const html = renderToString(<App />)
  res.send(html)  // 这里返回的是组件转成的html,而不是React组件
})
...

这样就能看到Hello React了

  • React同构

如果在上面的App中写个button并绑定onClick,会发现这并不会有什么作用。服务器端渲染是不能做行为的,行为和交互这些的需要客户端处理。

所谓React同构就是客户端代码和服务端代码保持一致。用create-react-app生成的项目build后,将renderToString出的html嵌入到build目录下的index.html的root下

const html = fs.readFileSync('./build/index.html')
const content = renderToString(<App list={list}/>)
res.send(html.toString().replace('<div id="root"></div>', `<div id="root">${content}</div>`))

将build的文件夹通过

app.use('/', express.static('build'))

返回给客户端。这样之前的onClick就能用了。查看浏览器查看DOM元素就能看到下面有script标签了。里面的js会去检查root下的代码跟它要生成的DOM结构是不是一致,如果一致就不管了,否则覆盖root下DOM,因此要保持两端的代码一致。因此,如果此时改了App.js,但是没有重新build,会发现页面不会改变,就是因为客户端发现DOM结构跟他加载的js代码要渲染的结构不一样,然后给覆盖了。

而Next.js帮我们处理了这些问题。