从Next.js到服务端渲染的学习

3,561 阅读5分钟

上周利用Next.js开发了一周,下面是一些开发和学习心得。

一、getInitialProps

什么是getInitialProps

在next.js里提供了一个静态异步方法:getInitialProps,它能异步获取JS普通对象并绑定在props上,通过这个方法我们就可以在服务的获取接口数据。

  static async getInitialProps({ req }) {
    console.log(req)
    return {}
  }

观察上面代码,会发现还有一个req对象,它指代的为服务的的request对象,但是作为一个前后端分离的独立前端工程,并没有与服务端有这方面的互动。但是在传统的react项目中,获取接口数据一般使用componetDidMounted,在Next.js我们仍然可以使用它,由于这个函数只存在于客户端,继续使用componetDidMounted可能会失去SSR应用的灵魂。

Next.js + Mobx ?

如果我们项目中使用了Mobx进行状态管理,如何在getInitialProps里面进行"dispatch"呢,注入到this.props呢,当然这是不可以的,因为getInitialProps是一个静态方法,无法使用this这一对象。这时候若你非要结合Next.jsMobx,那你可能不能使用getInitialProps,而使用componetDidMounted了,或者你只能单页面单store。

二、Next.js怎么配置

Next.js 是react进行服务端渲染的一个工具,默认以pages为渲染路由的路径。 如何对next.js做一些配置呢?有两种方式,方法一:在根目录的next.config.js里进行配置;方法二:自定义一个server.js

next.config.js

可以使用BundleAnalyzerPlugin对webpack进行相关配置,如

const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer')
...
module.exports = BundleAnalyzerPlugin({
...
})
server.js

新建Next的入口文件server.js里,实现app.prepare方法,利用该方法可以做一些前置工作。如果服务端使用的是express,可以自己控制路由:

const app = next({ dev })
...
app.prepare()
  .then(() => {
    const server = express()
    server.get('/page/:id', (req, res) => {
          const page = '/pageXX'
          const params = { id: req.params.id }
          app.render(req, res, page, params)
        })
    })

或者使用http.createServer创建一个服务器,做一些前置配置工作,如多语言配置。 其实,在Next.js内部也是新建了一个Node Server,利用这个Server类完成完成大部分的渲染工作,不仅仅如此,这个类还有组件渲染,路由匹配,错误处理,缓存处理等多个环节。当然要理解源码的渲染机制,Node知识必不可少,这里就不深入(其实不会)了。

三、自己怎么实现一个服务端渲染

这里是仓库地址:最简单服务端渲染项目

这里是仓库地址:react服务端渲染

这里是仓库地址:最简单的react,客户端服务端同构

1.构建编译与运行环境

安装babel-node:npm install bable-cli -g; 安装编译的react需要的组件:

npm install babel-preset-react -S
npm install babel-preset-env -S
npm install babel-plugin-transform-decorators-legacy -S

在package.json设置命令启动命令:

corss-env NODE_ENV=test nodemon --exec babel-node src/server.js

其中cross-env勇于设置环境变量;nodemon监视文件的改变并重新运行命令。 在.babelrc配置Babel(基本用法和格式),那么可以做以下配置:

{
    "presets": [
        "env",
        "react"
    ],
    "plugins": [
        "transform-decorators-legacy"
    ]
}
2.书写服务server.js

新建server.js,完成第一个实验:

import express from 'express'
var app = express()
app.get('/', (req, res) => {
    res.send('<h1>hello express</h1>')
})
app.listen(3000, function() {
    console.log('server started !')
})

启动命令,访问localhost:3000

npm run server

这里是仓库地址:最简单服务端渲染项目

3.React组件如何实现渲染

首先有两个方法了解以下

  1. renderToString:将 React Component 转换为HTML字符串,生成的DOM会带有额外的属性:每个DOM会有data-react-id属性,第一个DOM会有data-checksum属性。
  2. renderToStatucMarkup:将 React Component 转换为HTML字符串,但不会有额外的属性。

例如想要对React进行渲染,需要把组件转换为HTML,使用renderToString方法:

server.js:

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

var app = express();

app.get("/", (req, res) => {
  const list = [1, 2, 3, 4, 5]
  const html = renderToString(<App list={list} />)
  res.send(html)
})
app.listen(3000, function () {
  console.log('server start !')
})

App.js:

import React from 'react'

export default class App extends React.Component {
  render() {
    return(
      <div>
        <ul>
          {
            this.props.list.map(item => {
              return <li>{item}</li>
            })
          }
        </ul>
      </div>
    )
  }
}

访问localhost:3000即可得到下面结果:

页面效果

这里是仓库地址:react服务端渲染

4.react同构渲染

做到以上React的渲染,只能做到服务端渲染,客户端的渲染还没有实现。而同构的含义是:客户端和服务端使用相同的组件,服务端负责首次渲染,行为和交互由客户端进行。

接下来我们使用React同构渲染一次:

  1. 使用create-ract-app脚手架创建react工程
  2. 结合express和create-react-app的配置文件
  3. 将create-react-app编译打包后的文件通过express公开出来:app.use('/', express.static("build"))

import express from "express"
import App from './App'
import React from 'react'
import {renderToString} from 'react-dom/server'
import fs from 'fs'

var app = express();

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

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

app.listen(3000, function () {
  console.log('server start !')
})

上述描述的步骤是先生成build文件,然后服务器将生成的build文件替换成已有的html,发送到客户端,这样客户端与服务端内容将保持一致。

如何得到结果:首先执行npm run build生成build文件,然后运行npm run server访问localhost:3000

这里是仓库地址:最简单的react,客户端服务端同构

四、最后

Next.js 有一系列优势,我们不用再自己实现一个服务端渲染了。现在赶紧学好它,运用它。