打造可降级的React服务端同构框架

栏目: 服务器 · 发布时间: 7年前

内容简介:这里指的可降级与服务器降级意思相近(主逻辑失败采用备用逻辑的过程),考虑到服务端渲染并发及高负载的问题,在主服务器无法提供正常服务的时候可降级为"低功耗模式",采用客户端渲染方式来减轻服务器负载。我看了一下next.js,他们的开发方式很新颖,尤其是路由配置,完全不用自己配,按照他的规则来做就行了。但我这个人比较求稳,这种约定大于配置的方式对于我来说总觉得会有坑,无法自己掌控,找不到解决办法还得看源码才知道。还是求稳的我,毕竟服务端渲染是需要消耗服务器性能的,尤其是高并发和内存溢出,可能会导致服务器响应变慢

这里指的可降级与服务器降级意思相近(主逻辑失败采用备用逻辑的过程),考虑到服务端渲染并发及高负载的问题,在主服务器无法提供正常服务的时候可降级为"低功耗模式",采用客户端渲染方式来减轻服务器负载。

框架选型

next.js

我看了一下next.js,他们的开发方式很新颖,尤其是路由配置,完全不用自己配,按照他的规则来做就行了。但我这个人比较求稳,这种约定大于配置的方式对于我来说总觉得会有坑,无法自己掌控,找不到解决办法还得看源码才知道。

还是求稳的我,毕竟服务端渲染是需要消耗服务器性能的,尤其是高并发和内存溢出,可能会导致服务器响应变慢甚至挂掉,所以我希望我的框架还能降级,这个降级与服务器降级类似,在服务器高负载的时候,切换另一台机器,启动"低功耗模式"(也就是客户端渲染的单页应用)来继续运作,我查了next.js好像没有这个功能(如果有请在评论告知)。

cra-ssr

实在找不到既能服务端渲染又能客户端渲染的框架,最后我就找了这个项目https://github.com/cereallarceny/cra-ssr 进行改造,改造还是蛮成功的,过程遇到很多坑,在这里分享一些过程步骤。

改造之旅

其实cra-ssr这个项目本身已经做好了服务端同构渲染,在改造之前最好先理解cra-ssr实现同构渲染的流程原理再看下面的步骤,下面是cra-ssr实现同构渲染的流程图

打造可降级的React服务端同构框架

下图是降级改造后的流程图:

打造可降级的React服务端同构框架

上图里黄色区域的frontload是用在服务端渲染时预先处理的接口请求预取数据,然后放入准备好的global_state,global_state是一个store,作用是存放预取数据再connect给组件渲染,同时也可以作为服务端与客户端都可用的全局变量;

蓝色节点是服务器环境,橙色节点是客户端环境,当服务器渲染响应给客户端之后,用户进行了路由跳转,这时前端无刷新跳转到新页面,再走黄色区域取到页面数据,交给client_render渲染出新页面

环境区分

同构框架由于是同一套代码,但环境不同有些对象是不自带的,我们要加以区分避免出现问题,这个项目有提供一个isServer变量用来判断是服务端环境还是客户端环境,而我们做降级了就还要加多一个环境判断是否是客户端渲染的模式来区分,后面就知道为什么要做区分了。

我们需要做3个环境的区分 1.服务端渲染环境; 2.服务端渲染完成后的客户端环境;3.客户端渲染环境; 我是这么做环境区分的:

// 通过node环境独有的process和具体的环境变量区分isServer
export const isServer = !!process&&!!process.env&&(process.env.RENDER_ENV == 'server');
// 只要不是服务端渲染环境都属于isClient
export const isClient = !isServer;
// 区分降级后的客户端渲染isCSR,CSR是没有服务端渲染过后填充的数据__PRELOADED_STATE__的
export const isCSR = !isServer && !window.__PRELOADED_STATE__;
复制代码

首屏接口数据预取

cra-ssr是使用react-frontload来做首屏异步处理的,用法看这个文件 src/app/routes/profile/index.js ,在服务端通过预取数据交给状态管理redux的store,插入到 window.__PRELOADED_STATE__ 作为初始store,在客户端拿 window.__PRELOADED_STATE__ 作为初始store,connect组件得到数据填充,

而客户端渲染是没有预取数据 window.__PRELOADED_STATE__ 的,所以frontload要同时满足下面3个条件:

①frontload在服务端渲染完之后到客户端首屏不能重复执行发请求

②降级后客户端渲染模式能执行frontload发请求拉取数据并保证跟服务端同样的写法

③服务端渲染完之后到客户端经过用户点击路由跳转又要能像客户端一样请求拉取数据

为了保证同样的写法,服务端渲染与客户端渲染必须统一用状态管理的store,需要封装一个特定的global_state用来传首屏数据的store,再封装一下frontload,写了个Adapter:

import { frontloadConnect } from 'react-frontload';
import {isServer, isCSR} from 'xxx/xxx';
import {change_state, get_state} from 'xxx/global_state';
import {connect} from 'dva';

export const frontload = (frontload_fn) => {
  return (Target) => {
    // 为组件默认注入global_state
    return connect(({global}) => ({global}))(frontloadConnect(async (props) => {
      // get_state('client_load') => 在客户端触发路由跳转的时候为true
      if (get_state('client_load') || isServer || isCSR) {
        await frontload_fn(props)
      }
    })(Target))
  }
}
复制代码

用这个frontload来代替原来的frontloadConnect函数,不仅简化了写法,还保持了同一套代码满足上面三个条件。

在组件里可以这么写:

//(本项目引入了dva、Typescript、scss和react-css-modules)
import * as React from 'react';
import { isCSR } from 'xxx/xxx';
const styles = require('./index.scss');
const CSSModules = require('react-css-modules');
import { frontload } from 'xxx/adapter'; 
import { API } from 'xxx/http';
import {config} from 'src/utils/config';


@frontload(async (props) => {
    const res = await API.get('/getNews');
    // 通过更新store的global达到统一服务端与客户端的写法。
    props.dispatch({type: 'global/set', payload: {data_list: res.data}})
})
@CSSModules(styles)
export default class News extends React.PureComponent<any,any>{
    constructor(props){
        super(props);
        this.state = {
            news_list: [], 
        }
    }
    componentDidMount() {
        this.setState({
            news_list: this.props.global.data_list,
        })
    }
    render() {
        const news_list = this.props.global.data_list;
        return (
          news_list.map((item) => {
            return <section>
              <p>标题:{item.title}</p>
              <p>摘要:{item.abstract}</p>
            </section>
          })
          
        )
    }
}
复制代码

添加客户端渲染启动服务器:sunglasses:

还要添加一个启动客户端渲染的服务器文件,比如csr_server.js

var path = require('path')
var express = require('express')
var compression = require('compression')
var app = express()
var data = require('./data_config.json');
app.use(compression());
// 构建后的资源文件夹
app.use(express.static(path.join(__dirname, '../dist/')));

port = 8080
app.listen(port, function () {
  console.log('The app server is working at ' + port)
})
复制代码

这就是我改造服务端同构框架降级的一些步骤,具体还是要自己好好理解才行,根据自身项目需求调整(比如我的项目还引入了dva、typescript之类的),会遇到很多坑的:joy:

这篇文章只是在前端的角度讲降级的,至于具体在服务器上怎么降级,就得从运维角度看怎样的规则适合降级,怎么降级这些问题了,就不在此文讲述了。


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

自己动手做iOS App

自己动手做iOS App

张子怡 / 电子工业出版社 / 2017-8 / 69.00

《自己动手做iOS App:从设计开发到上架App Store》为想要接触iOS 应用设计、开发的读者提供了由浅入深的详细指导。从iOS 应用制作的步骤是什么,应该使用什么软件,如何发布应用到App Store,到iOS 的设计理念是什么,如何正确书写Swift 语言,再到后端和客户端是如何交互运作的等,本书配合图示,精辟、直观地阐明了iOS 应用制作中的种种疑问。 如果你是一位第一次接触i......一起来看看 《自己动手做iOS App》 这本书的介绍吧!

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具