基于React的PC网站前端架构分析

这篇文章是以一个实习生的视角对前端网站架构的一点分析和理解css

最开始接触前端的时候,是从简单的html、css、js开始的,当时盛行的WEB理念是结构样式行为相分离,即html、css、js分离,独立开发,互相之间经过link和script来互相调用。html

最开始我所接触到的小项目,都是直接将html、css、js等静态资源直接部署到服务器上,而后根据请求路由响应不一样的html文件便可。 前端

简单网站结构

即便学习了webpack以后,我依然认为webpack的做用只是压缩js和css文件,提升服务器响应速度,优化用户体验,而部署到服务器上的依然是压缩后的min.css和min.js文件。node

后来进入A公司实习以后,确实也是这种开发模式,当时咱们开发h5页面,都是直接书写html、css、js文件,而后部署到服务器上,直接访问html便可。react

后来进入B公司工做以后,才慢慢接触到真正的前端工程是什么样子的。webpack

先后端分离

部分传统大型PC网站的业务前端部分都是采用的MVC架构方式,也就是每次立项以后,先后端约定好接口,分别开发,开发完毕以后,前端将开发好的页面交给后端(通常是Java或者PHP),而后由后端响应客户端请求,返回具体的html页面。es6

这种开发模式的缺点在于费时费力,沟通成本和联调正本很是高,前端有一点小的改动都须要先后端一块儿联调改动上线,大大增长了总工做量。web

所以,现代大型PC网站通常都采用了先后端分离的架构方式,前端和后端的业务功能各自收敛,能够分别开发上线,互不影响,能够极大提升工做效率。数据库

先后端分离通常分为两种:express

  • 没有中间层的先后端分离;
  • 有中间层的先后端分离。 这里以目前最火的三大框架之一的react为主进行介绍。

无中间层 没有web中间层的先后端分离属于比较简单的类型,咱们将一个统一的html/pug模板和其余的css、js等静态资源放置到cdn上,每次访问页面的时候,直接将模板返回给用户,而后里面全部的dom节点和其余数据都是由js来执行生成的。

无中间层的先后端分离

然而这种没有中间层的先后端分离的又有不少劣势:

  • 首屏渲染时间过长;
  • 对seo不友好。
  • 有中间层

有中间层的先后端分离是通常大型项目采用的先后端分离方式。

自从2009年node横空出世以后,前端也逐渐承担了一些后端的业务,可是node因为自身健壮性的限制,又不适合做为大型项目的后端服务器,因此node热过一阵以后,逐渐成为了链接传统前端和后端的中间层,咱们也称这种前端+node的架构为“大前端”。

返回模板 在node层中,咱们能够作的事情就有不少了,其中最基础的就是返回不一样的前端模板。

使用过几款前端模板,其中给我感受最好的就是pug模板了(之前叫作jade)。

pug中的语法都是js语法,对前端工程师十分友好,并且pug功能很强大,能够做为html-middleware,被node完美支持,这里建议学习使用 Get Started

数据拼接

其次,当网站发展地愈来愈大,数据量愈来愈多,对服务层进行分割的时候,会产生不少的服务模块,或者咱们的数据分散在不一样的数据库服务器上的时候,或者咱们的前端页面中要嵌入第三方广告的一些api的时候,node就能够帮咱们完成数据拼接的工做。

由于服务器访问接口的速度要比浏览器快不少个数量级,所以在node中访问多个接口而且拼接起来是很是高效的,拼接后的数据咱们就能够直接传入模板中,供js使用了。

可是一般意义上来讲,访问基础服务或者从数据服务器访问数据放在后端来作比较合适,但凡事总有例外,在万不得已的状况下,咱们能够在node中间层中进行数据拼接。

数据拼接

这种模式通常不提倡使用,由于可维护性太差,并且安全性也很低,通常状况下都是后端有一个专门的数据模块去访问数据库和服务,而后将数据拼接起来,咱们只须要在node中调用后端的一个API,就能够拿到咱们想要的数据了。

监控服务

node层能够捕捉一些异常请求或者事件,上报到咱们的第三方监控平台,如Sentry等,同时node还能够承担一部分的数据统计的工做,将一些用户应为打到第三方数据统计平台,供pm和数据分析师查看。

node还能够承担对整个实例进行监控的职责,当出现异常致使cpu使用率或者内存使用率超过阈值以后就能够及时触发报警机制。

可是一样,加上node层就意味这网站又多了一层后端须要监控和oncall,工做的复杂度会上升不少。

服务器端渲染

SSR(server-side-render)能够说是很是重要的功能之一了,它能够帮助咱们解决以前提到的首屏渲染时间过长和对seo支持较低的问题。

现代seo爬虫通常分为两种:

  • 支持解析js的爬虫,这类数量较少,以Google为表明;
  • 不支持解析js的爬虫,大部分都是这类,基本上都是非Google的搜索引擎的爬虫了。 对于Google的爬虫来讲,是否使用SSR在seo方面可有可无,由于最终均可以爬取到正常的页面。

而对于非Google的搜索引擎来讲,咱们就须要利用SSR,先将具体的dom节点渲染出来,供爬虫爬取。

并且这样同时还有一个优势:用户在网页loading的过程就中能够看到页面内容了,而不是一个空白页面。若是不使用SSR的话,在网页loading资源的过程当中,一直呈现给用户一片空白,这就有可能形成用户的流失。

B站SSR

这张图是我在50kb/s的网速下,访问b站第一秒钟看到的内容,如果b站不使用SSR技术的话,可能等到用户可以看到首屏内容以后,时间都过去了五六秒。

这里是一个简单的使用SSR的Node层代码:

// 代码中使用了es6语法,不懂的能够先学习一下阮一峰老师的《ES6入门》
// 这个地方node若是没有使用babel的话,import会报错,能够直接使用require方法替换
import { renderToString } from 'react-dom/server';
import DemoContainer from 'containers/demo';
// 以koa2框架为例
module.exports = (ctx) => {
    const props = {...};
    // 这里的html就存放着咱们组件render完以后的dom节点。
    const html = renderToString(<Demo >); // 这里以返回pug模板为例,第二个参数是要传入pug模板中的数据 ctx.render('demo.pug', { __props: JSON.stringify(props), html }); }; 复制代码

这里是pug的代码片断:

// pug代码片断
body
    #root
        !{ html }
    script.
        window.__props = '!{ __props }'
复制代码

使用SSR的时候要切记保证前端和服务器端的组件props保持一致,所以这里个人习惯是在node层将props直接传入window对象上,而后前端的组件直接从window对象获取props便可。

SSR的时候,React组件只会执行componentWillMount和render两个生命周期用来生成dom结构,其余的生命周期以及方法挂载都是在前端完成的。

node层的功能不止以上这些,这里就不过多展开介绍了。

虽然SSR的有点不少,可是仍是有自身的弊端的。使用SSR就意味着你用本身服务器代替了一部分本来属于用户客户端的功能,所以会形成服务器性能下降,成本增高的可能,相对于小团队或者资金不算充裕的团队,要谨慎选择是否使用SSR。

先后端同构

提及SSR,就不得不提一下先后端同构问题。 同构的意思为前端和node用执行同一套代码,首屏使用服务端渲染,将渲染好的html直接交给浏览器去渲染,客户端负责加载js,执行组件剩余生命周期,并挂载自定义事件等。 一套好的先后端同构代码能够大幅减小咱们维护代码的工做量,而且有十分高效的执行效率,如何优雅地书写先后端同构的代码也是一项技术活,须要咱们提早规划好一套前端架构。

例如:
import React, { Component } from "react";
import { Provider } from "react-redux";
import ReactDOM from "react-dom";
import Loadable from "react-loadable";
import { BrowserRouter, StaticRouter } from "react-router-dom";

// server side render
const SSR = App =>
  class SSR extends Component<{
    store: any;
    url: string;
  }> {
    render() {
      const context = {};
      return (
        <Provider store={this.props.store} context={context}> <StaticRouter location={this.props.url}> <App /> </StaticRouter> </Provider>
      );
    }
  };

// client side render
const CLIENT = configureState => Component => {
  const initStates = window.__INIT_STATES__;
  const store = configureState(initStates);
  Loadable.preloadReady().then(() => {
    ReactDOM.hydrate(
      <Provider store={store}> <BrowserRouter> <Component /> </BrowserRouter> </Provider>,
      document.getElementById("root")
    );
  });
};

export default function entry(configureState) {
  return IS_NODE ? SSR : CLIENT(configureState);
}
复制代码

而且在同构方面,阿里有一套降级策略。当服务器压力正常时,由服务器进行SSR,提升用户体验,当用户访问量激增,如双十一时,服务器会自动进行降级处理,node不进行SSR,所有转换成客户端渲染,减轻服务器的压力。

选择框架

了解了先后端分离以后,咱们就要对node层进行框架选择了。

目前比较主流的框架有三款:Express、koa1.0、koa2.0。

对于初学者来讲,建议直接使用koa2.0进行中间层的学习和开发。

express的缺点在于:

  • 过重,有不少模块咱们可能都不会用到; 回调地狱,即便使用Promise也只能缓解。 koa1.0的缺点在于:

  • 必须配合co库和generator来使用,配置繁琐。 而自从node升级到7.6版本以上,增长了async/await语法糖以后,咱们就能够不须要任何三方库,直接在原生node中使用koa2的语法。

koa2是express的升级版框架,里面不少模块是直接从express中迁移过来的,可是又将之前不常常用到的模块删除,只有开发者在须要使用的时候采起引入那么模块。

而且koa2使用async/await语法糖以后,代码看似变成了同步执行,很是适合前端工程师的逻辑思惟。

这里是express、promise、koa2的样例代码:

// express版本
module.exports = (req, res) => {
    const data1 = request.get('/api/demo1', (err, res) => {
        const data2 = request.get('/api/demo2', (err, res) => {
            const data3 = request.get('/api/demo3', (err, res) => {
                res.send(data1 + data2 + data3);
            })
        })
    })
}
// promise版本
module.exports = (req, res) => {
    new Promise((resolve, reject) => {
        request.get('/api/demo1', (err, res) => {
            resolve(res);
        }).then(res => {
            request.get('/api/demo2', (err, res2) => res + res2 );
        }).then(res2 => {
            request.get('/api/demo3', (err, res3) => res2 + res3)
        }).then((data) => {
            res.send(data);
        });
    })
}
复制代码

看起来虽然整齐了一些,可是依然十分繁琐。

// koa1和koa2在写法上基本相同,区别在于koa1在使用以前要对co库和generator进行繁琐的配置。
// 每个await的时候最好加上try-catch,防止由于一个异步请求失败而致使node进程崩溃,这里简化了写法。
module.exports = async (ctx) => {
    const data1 = await request.get('/api/demo1');
    const data2 = await request.get('/api/demo2');
    const data3 = await request.get('api/demo3');
    ctx.body = {
        data: data1 + data2 + data3
    };
}
复制代码

koa2用起来很是的舒服,很适合前端工程师的思惟逻辑。

虽然koa2的代码看起来像同步执行,但其实在编译以后只是变成了promise函数,await后面全部的代码都放到了promise的回调中执行了。

开发结构

选择好了框架以后,剩下的就只有开发了,通常的node层都遵循一下的目录结构:

node

  • lib // 存放第三方插件
  • util // 存放本身编写的工具函数
  • middleware // 存放中间件
  • routes // 存放路由
  • controller // 存放路由处理函数
  • app.js // node层入口文件 基本的node层架构这里就介绍差很少了,剩下的前端部分也通常是你们熟悉的东西。 前端目录结构:

public

  • static
  • src
  • js
  • components
  • containers
  • routes
  • stores
  • actions
  • reducers
  • pages
  • css/scss/less
  • static 这里按照正常的React开发逻辑去走便可。

最后还有一些其余的文件夹能够自由发挥,好比template存放模板,scripts存放平时写的脚本等。

配置

一个线上项目要拥有两套模式——生产模式和开发模式。

生产模式即咱们线上运行环境。

开发模式即咱们平时本地开发环境。

若是有须要的话甚至能够配置更多的环境。

这两种环境的要求不同,所以咱们会有两套配置文件,将不一样的配置文件传入node和webpack中,就能够根据配置的不一样启动不一样环境了。

配置

自动化测试

自动化测试在一个成熟的大型网站中必不可少。

虽然目前由于前端领域的快速增加,业务层的自动化测试也由于业务的快速迭代而变得不稳定,可是一些基础的测试仍是颇有必要作的。

平时开发的时候要作好类库单元测试的自动化以及UI组件的单元测试的自动化。

这些测试文件最好存放在单独的test目录下,或者在每个基础UI组件目录下加上component.test.js文件,这样启动测试的时候会自动找到.test文件进行测试。

每次项目上线以前都要进行一次集成测试,测试路由是否正常,webpack打包和node模块安装是否正常,模拟用户登陆访问等操做是否正常。

偶尔咱们还须要作压力测试和容灾测试等。

对于初学者来讲,测试是一个很重要的概念和习惯,平时要多写一写单元测试。

相关文章
相关标签/搜索