关于 React服务器端渲染(SSR)

最近经常听到其他主攻 Vue项目的前端小组提到 SSR这个名词,一直都不明白是什么东西,只以为是他们内部做的什么工具之类的东西,虽然有些好奇,但也没怎么在意,直到后来在某技术论坛闲逛的时候忽然也看到这个名词了,点进去后通篇看完才知道 ssr是个什么东西。


SSR的概念

Server Slide Rendering,缩写为 ssr,即服务器端渲染,因为是后端出身,所以其实早就明白是怎么回事,只是没这个具体名词的概念罢了,这个词被频繁提起也是拜近年来前端飞速发展所赐,主要针对 SPA应用,目的大概有以下几个:

  1. 解决单页面应用的 SEO
    单页应用页面大部分主要的 HTML并不是服务器返回,服务器只是返回一大串的 脚本,页面上看到的大部分内容都是由脚本生成,对于一般网站影响不大,但是对于一些依赖搜索引擎带来流量的网站来说则是致命的,搜索引擎无法抓取页面相关内容,也就是用户搜不到此网站的相关信息,自然也就无流量可言。
  2. 解决渲染白屏
    因为页面 HTML由服务器端返回的脚本生成,一般来说这种脚本的体积都不会太小,客户端下载需要时间,浏览器解析以生成页面元素也需要时间,这必然会导致页面的显示速度比传统服务器端渲染得要慢,很容易出现首页白屏的情况,甚至如果浏览器禁用了 JS,那么将直接导致页面连基本的元素都看不到。

VueReact是用来做单页应用最普遍的框架,因为我对 react更熟悉,所以这里我只说下 React SSR


客户端部分

新建 React页面

首先,要有一个用于展示的 React页面,例如:


// ./client/components/About/index.jsx
import React, { Component } from 'react'
import styles from './style.scss'
import './style'

export default class About extends Component {
    constructor() {
        super()
        this.state = {
            txtInfo: ''
        }
    }
    componentWillMount() {
        this.state.txtInfo = 'zhangsan'
    }
    componentDidMount() {
        this.getInfo()
    }
    render() {
        return (
            <section className={styles['about-wrapper']}>
                <p className="title">About Page</p>
                <p className="txt-info">{this.state.txtInfo}</p>
            </section>
        )
    }
    getInfo() {
        fetch('/api/user/getInfo', {
            credentials: 'include',
            headers: {
                'Access-Control-Allow-Origin': '*',
                'Content-Type': 'text/plain; application/json; charset=utf8'
            }
        }).then(res=>{
            return res.json()
        }).then(data=> {
            this.setState({
                txtInfo: data.name
            })
        })
    }
}

这里的路由使用 react-router,简便考虑,只有一个路由,创建路由:

// ./client/routes.js

export default const routes = {
    childRoutes: [{
        path: '/',
        component: require('./components/app.jsx'),
        indexRoute: {
            getComponent(nextState, callback) {
                require.ensure([], require => {
                    callback(null, require('./components/About/index.jsx'))
                }, 'about')
            }
        }
}

其中 ./components/app.jsx的结构很简单,主要只有一个 render方法:

render() {
  const {children, ...props} = this.props
   return (
     <div>
       {React.Children.map(children, child =>
         React.cloneElement(child, {...props})
       )}
     </div>
   )
 }

最主要的是入口组件 index.js

// ./client/index.js

import React from 'react'
import { render } from 'react-dom'
import { Router, match, browserHistory } from 'react-router'
import routes from './routes'

match({ history: browserHistory, routes }, (error, redirectLocation, renderProps) => {
    render(
        <Router {...renderProps}/>,
        document.getElementById('root')
    )
})

其中,有个 match方法,这是 react-router提供的方法,作用在于在渲染之前根据URL匹配路由组件,更多详情可看 这里

客户端就这些了,下面开始服务端。


服务端

服务端使用 koa框架,安装配置什么的我就不多说了,不知道的可见 这里

// ./server/app.js

import Koa from 'koa'
const app = new Koa()
export default app

配置服务端路由:

// ./server/routes/index.js

import Router from 'koa-router'
const router = new Router({prefix: '/api/user'})

router.get('/getInfo', async(ctx, next)=> {
    ctx.body = {
        name: 'xiaoming',
    age: 18
  }
})
export default router

配置了一个 /api/user/getInfo的路由

但是,服务端只有服务端路由还不行,因为是服务器端渲染,所以还必须在服务端配置客户端路由,因为只有知道客户端的路由,服务器才知道该向客户端传送什么页面的 HTML字符串,如下:

// ./server/clientRoutes.js

import React from 'react'
import { renderToString } from 'react-dom/server'
import { match, RouterContext } from 'react-router'
import routes from '../../client/routes'

async function clientRoute(ctx, next) {
    let _renderProps

    match({ routes, location: ctx.url }, (error, redirectLocation, renderProps) => {
        _renderProps = renderProps
    })

    if (_renderProps) {
        await ctx.render('index', {
            root: renderToString(
                <RouterContext {..._renderProps}/>
            ),
            info: { name: '小明' }
        })
    } else {
        await next()
    }
}

export default clientRoute

除了 match之外,这次又多了个 RouterContext,此 API也是属于 react-router,它的作用是以同步的方式渲染路由组件,不然你想啊,一个 react组件,有很多 API钩子函数,若是在这些钩子函数还没执行完之前,服务器就把 HTML渲染到客户端了,那肯定会缺斤少两啊,所以这个时候此 API就派上了用场。

下面就只剩下启动服务渲染页面了。

这个稍微复杂一点,因为需要考虑到很多问题,比如jsx语法的转码、css样式文件的打包,HTML的注入等,不仅需要考虑浏览器端,还要考虑 服务端,一个都不能少。

// 转码器 babel
require('babel-polyfill')
// react 的转码 hook
require('babel-register')({
    presets: ['es2015', 'react', 'stage-0'],
    plugins: ['add-module-exports']
})

// css 的转码 hook
require('css-modules-require-hook')({
    extensions: ['.scss'],
    preprocessCss: (data, filename) =>
        require('node-sass').renderSync({
            data,
            file: filename
        }).css,
    camelCase: true,
    generateScopedName: '[name]__[local]__[hash:base64:8]'
})


const webpack = require('webpack'),
    app = require('./app'),
    convert = require('koa-convert'),
    fs = require('fs'),
    path = require('path'),
    devMiddleware = require('koa-webpack-dev-middleware'),
    views = require('koa-views'),
    router = require('./routes'),
    clientRoute = require('./clientRoute'),
    config = require('../webpack.dev.config'),
    port = process.env.port || 3000
    compiler = webpack(config)

app.use(views(path.resolve(__dirname, '../views/dev'), { map: { html: 'ejs' } }))
app.use(clientRoute)
app.use(router.routes())
app.use(router.allowedMethods())
console.log(`Listening on port ${port}`)
app.use(convert(devMiddleware(compiler, {
    noInfo: true,
    publicPath: config.output.publicPath
})))
app.listen(port)

基本上就是这样了,打开页面,是这个效果:

这里写图片描述

同时如果使用 Restlet Client这样的工具请求页面,也能够看到返回的 HTML字符串与浏览器实际渲染后的页面HTML完全一致,这说明服务器端渲染的目的已经达到了。


有关项目

上面代码还有一些我都没贴出来,我也不想贴项目地址,因为没必要了。
如果是在一年前,关于 react服务器端渲染还没什么标准,社区各路大神各显神通的话,那么现在再提起 react server slide rendering的话,就没必要再自己吃力不讨好的瞎折腾了,你应该想起这个脚手架:Next.js

next.js

Next.js 是一个用于在服务端渲染 React 应用程序的简单框架,2016 年 10 月 25 日由 zeit.co 背后的团队发布。

React服务端渲染 SSR应用框架,支持可选的服务端与客户端渲染功能,简单易用,安装这个框架会搭建一个基于React、WebpackBabel的构建过程,也就是说脚手架已经预设了配置,开发人员不必在搭建WebpackBabel配置上。

此项目算是众多 react server slide rendering方案中最受欢迎的一个,如果现在考虑 react服务端渲染,此方案算是最佳选择。

另外,不仅是 reactvue同样有类似对标的库:Nuxt.js,作用和功能几乎和 Next.js一致,有意思的是,Nuxt.js的发布时间仅在 Next.js宣告发布后的几个小时内,算是同一天发布,如今在各自领域内的影响力不分伯仲。

猜你喜欢

转载自blog.csdn.net/deeplies/article/details/78007731