5640.com管家婆高手论坛,香港管家婆玄机彩图,1491马会资料,4766香港老地方百度,宝宝平特图热2017,97903.com,www.50884.com
所在位置:主页 > 1491马会资料 >

React server rending —— 网易美学主站同构实录

发布日期:2019-07-20 02:45   来源:未知   阅读:

  网易美学主站在最初开发时,因为各种历史原因,引入了例如JQuery,Bootstrop,Angular, React等框架,代码结构比较混乱,给后续的开发和维护带来了很大的不便。所以对它进行了重构。下面,我会从以下三个方面对主站的重构方案进行介绍:

  早期的主站使用Express作为Node层路由的同时,使用了类似于Jinja的Nunjucks作为javascript 模板引擎,进行HTML文件的渲染,也就是说,我们的网站是一个多页应用,Nunjucks渲染满足了SEO的需求。之后出于封装和组件的管理引入了Reactjs,对于一个页面的开发,往往需要两步:

  对于每个页面,在引入的js文件中,对DOM节点进行替换,以CommentBox组件为例:

  React组件初始化时,需要把一些数据作为props传递进去。例如isLogin属性,对于一个有登录功能的网站,是否处于登录状态,影响了组件的展示。但是isLogin这个状态如何拿到呢,我们只能在Nunjucks模板中进行书写:

  我们的应用中,有一些状态需要在不同组件间共享。比如登陆状态isLogin,一些应用的做法是弹窗登陆后,强制刷新页面,使各个组件刷新状态。但是强制刷新页面会影响用户体验,这里,产品的需求是这样的:

  点击点赞按钮,弹出登录框,进行登陆后,进行主动点赞,其他与登录状态有关的组件,检测到登录状态改变后,进行数据获取和显示刷新。

  由于我们的组件,是根据id直接挂在在DOM节点上的,这些组件之间没有嵌套关系,不能通过props去传递状态。只能通过基于发布-订阅者模式的全局事件处理。在每个组件进行登录状态的trigger和监听。组件间需要共享的状态不仅仅只有isLogin,这样可以预见,我们需要在React组件的事件上,绑定大量的全局监听和触发事件。这样增加了组件之间的耦合,不利于代码的维护。

  同构(Isomorphic)并不是一个新鲜的概念。一些团队已经基于他们的业务实现了同构直出(参考[1])。

  这里再简单介绍一下,根据自己理解,同构可以看成,只需要维护一份代码,client side(Browser端)和server side(Nodejs端)都可以共用。

  这样,在获取数据后,server side可以返回已经渲染好的html文件,满足SEO需要的同时,相比纯client rendering,也减少了响应时间,对于用户来说,就是减少了白屏这样不好的体验。

  有Next.js这样的服务端渲染框架,提供了脚手架,生成同构网站。我们没有直接采用Next.js,主要是出于以下几方面的考虑:

  这样,以Express为例,对于一个请求,server side可以这样返回:

  抛弃了Nunjucks后,重构后的主站是一个单页应用,从index.html渲染所需要的页面。路由的引入是不可缺少的,这里使用了react-router, 对于4.x以前的版本,通过配置嵌套的, 很容易实现一个单页应用的路由

  对于一个不需要同构的React 应用来说,我们通常选择把获取数据这一步放在componentDidMount方法中,在获取数据后,使用getState触发render。但是对于server rendering,并不会执行到componentDidMount这个方法。所以,我们需要在调用renderToString前,进行数据的获取,并将获取后的数据放置在组件可以访问到的store中,供组件渲染。

  server side进行数据获取的方法很多,比如说通过代理转发请求。此外,已经有各种第三方库,提供了在server side和client side 发送请求的通用方法。isomorphic-fetch和axios都可以满足我们的需求。通过封装第三方库,我们抹平了在前后端发送请求书写上的不同。对于某一个页面来说,不管是server side还是client side,可以通过同一个fetchData方法获取初始数据。

  下面的问题,就是这个fetchData方法放在哪儿。可以选择一个文件,集中管理所有页面的fetchData方法。一些参考资料中,会选择把fetchData放置在页面组件的静态方法上:ES6中,提供了class中static方法,我们都知道class只是ES6提供的以一个语法糖,并没有改变JS基于原型的本质。class中定义的static方法,并没有放置在原型链上,可以直接通过类名进行调用。

  我们的项目也选择把fetchData放置在页面static 方法中,主要是考虑到fetchData和业务逻辑放置在一起,维护起来更加方便和直观。 如此,About,用伪代码可以这样书写:

  static方法fetchData并不是在组件About实例的生命周期里面,所以对于fetchData中获取的方法,我们需要先构建一个全局的Store单例,用来set获取的数据。在About组件的初始化render中,则可以使用Store.get方法获取这些数据进行渲染。

  之前提到了,server side 需要在renderToString之前,就进行数据的获取。对于页面组件上的静态方法fetchData,如何进行调用呢?

  react-router 提供的match方法的回调中,ponents即为对应页面的组件。可以直接调用这些组件的fetchData方法。client side 在获取到server side响应后,要进行渲染,也需要两部分:使用React框架的App代码;从后台服务器获取的请求数据。代码部分,可以打包成js文件引入到返回的html中,而请求数据,可以转化为字符串写入全局对象window上:

  之前提到,所以对于fetchData中获取的方法,我们需要先构建一个全局的Store单例,用来set获取的数据。在组件的初始化render中,则可以使用Store.get方法获取这些数据进行渲染。听起来很熟悉是不是,Redux中的Store可以完全满足我们的需求,而不用自己构建一个全局的Store单例。但是对于大部分工程来说,Redux并不是非用不可,Redux的引入在使数据流更加清晰的同时,也会使组件的结构更加复杂,增加开发的工作量,对于一个setState操作,需要

  我们的应用中,有一些组件的状态需要共享。比如isLogin状态,这个状态改变,会许多组件的状态

  建立开发环境和上线环境,实现模块的打包,前端常用的工具有很多: 例如webpack,gulp, grunt, browerify等。具体的打包方法就不在这里赘述。

  之前参考的资料中,已经有了比较完备的server rendering方案。但是具体的项目实践中,也遇到了一些问题,在解决这些问题的时候,积累了写经验,希望能给之后也有需要进行React 前后端同构的项目一些参考。

  通过封装第三方库,我们抹平了在前后端发送请求书写上的不同。对于某一个页面来说,不管是server side还是client side,可以通过同一个fetchData方法获取初始数据。fetchData是页面元素的一个static方法。 fetchData中,基于业务需求,可能不仅仅有一个获取数据的方法。比如/about请求,react-router路由匹配到了About组件, 在这个组件中,需要获取两部分数据:

  那怎么办呢?一个比较容易想到的办法是,在server side,将/about请求的请求头取出来,然后放到/api/content, /api/user这两个请求头上。

  对封装了axios库的Fetch方法进行改写,读取请求头信息,并且发送。

  这样做的好处是,每个server side的请求,都有对应的请求头,并且与浏览器发送的请求头一致。但是,也带来了一些不便:每个页面的fetchData中,都要重复从store中获取请求头--将请求头放在Fetch方法参数这个操作,处理上有一些冗余。这里,如果大家有什么更好的解决方法,欢迎联系我~

  React 会将所有要显示到 DOM 的字符串转义,避免出现XSS的风险。

  上述的代码,大家应该已经察觉到问题了。对于store中的state,我们使用了JSON.stringify进行序列化, 它将一个Javascript value转化成一个JSON字符串,这样就出现了XSS的风险。试想,如果store.getState()是下列的结果:

  问了避免这样的问题,我们需要对state其中的特殊html标签进行转义。 Git上有许多第三方库可以帮助我们解决这个问题。例如serialize-javascript。它也是一个序列化的工具,提供了serialize API,可以自动地对HTML字符进行转义:

  在Redux store中,我们维护了一个isLogin状态,对于某些页面,只有在登录状态才可见,如果没有登录,直接在地址栏中输入对应的url,则会跳转至其他页面;如果在这些页面中点击退出登录,也会跳转至其他页面。

  高阶组件的本质是生成组件的函数,使用起来也非常简单,只需要在需要登录检测的页面组件上,用@CheckLoginEnhance进行包裹即可。

  我们这里的登陆检测,都是在client side进行的,如果能在server side进行检测,直接进行跳转。对于用户来说,体验更加友好。

  为了实现这个需求,我们可以在serverEntry.js中获取isLogin,然后使用res.redirect进行跳转。此外react-router v4采用了动态路由,不需要额外的配置,很容易地能够实现这个功能,我们在后续的文章中会进行讲解。

  对于一个较为复杂的应用,在使用Redux时,都需要进行Reducer的拆分,拆分后的每个Reducer函数独立负责该特定切片state的更新。Redux提供了combineReducer函数,将拆分后的Reducer函数合并成一个Reducer函数,最后使用这个Reducer进行store的创建。

  我们项目中,对于每一个页面,拆分一个单独的Reducer,对应单独的state。对于一些公共的state,比如说用户信息,错误处理,导航信息,则从各个页面的state中抽离出来,统一处理。

  与此同时,我们面临了一个问题,这个问题也是刚接触Redux进行项目开发时,经常会遇到的,在单个页面中,哪些组件要使用Redux进行管理state,哪些使用setState进行维护?

  之前提到,引入Redux的原因,就是它提供了一个上下文都可以访问的store,存储的数据既可以用于server rendering也可以用于client rendering。所以对于server rendering所需要的初始化的数据,需要使用Redux进行管理。此外,那些与server rendering无关的状态呢?比如说,某个Button的显示和隐藏。如果由Redux进行管理,固然数据流向更加清晰,但是也可以预见我们需要维护巨大的reduce方法和复杂的state结构,但是如果不由Redux进行管理,则是否会出现React state和Redux共存,导致数据流混乱的问题。

  对于这三类组件。按照容器组件和展示组件相分离的思想,我们使用高阶函数connect将页面组件进行包裹,形成容器组件。容器组件监听Redux state,并且向Redux派发actions。对于从Redux中获取的state,通过props向子组件传递。而子组件,通过props获取数据外,自身可以维护与展示相关的state。

  对于某些公共组件,当然也可以像普通的子组件一样,获取页面组件的props。但是这样一来,一则嵌套太深,二则与页面代码耦合性太高,不利于组件的复用,也违背了我们使用Redux管理状态的初衷。所以这里也允许这些组件通过connect生成容器组件,直接与Redux通信。

  网易美学主站上线已经四个多月了。在这个过程中,我们一直在持续维护周边的构建,使整个网站架构更加完备和和合理。但是一直有一个问题没有得到解决,那就是Code-splitting,目前client side所有的代码都打成一个包,没有实现代码分隔和按需加载。在使用react-router同时进行代码分隔和server rendering时,遇到了一些问题。react-router是这样解释的:

  #重磅消息#搬瓦工新上线G流量/KVM架构/46.87美元/年 附测评数据 (7月12日限量补货 先到先得-售罄)

  如果有服务商和本站有合作意向,可以联系站长,邮箱:br>

  了解到有朋友期望能在易学教程发布文章,奈何用户发布系统不完善,所以开通了码农岛,以供发布文章的需求

  本站部分内容来自互联网,其发布内容言论不代表本站观点,如果其链接、内容的侵犯您的权益,烦请联系我们,我们将及时予以处理。