React渲染优化策略

126 阅读5分钟

背景

React 于 2013 年 5 月 29 日 在 JSConfUS 会议上首次亮相,并于同一天正式开源,到目前已经有11年的历史。从发布以来React团队不断针对渲染性能做出优化,那在11年的时间里React团队都做过哪些优化呢?接下来我将根据版本顺序一一讲解。

问题:React为什么快,直接操作原生Dom不更快吗?

答:React官方从未说过React比操作原生Dom快,React的推出,是为了替代旧的开发模式。在Jquery时代,就是通过操作原生Dom来更新视图,但随着项目越来越庞大,项目变得难以维护。React提出数据视图分离,数据驱动视图,很好地解决了这个问题。

React15版本渲染流程

首次渲染

更新渲染

存在的问题

由于js是单线程的,在执行react更新任务时占用了太多时间,导致在一帧时间里其他任务得不到执行,例如渲染动画,事件回调等,从而导致掉帧。

正常情况下浏览器在一帧时间里执行任务的顺序如下:click等事件->js代码->scroll/resize 等事件回调->RequestAnimationFrame->渲染界面->requestIdleCallback

react更新任务在js代码阶段执行,如果占用超出16.7ms就会导致后面渲染任务得不到执行,从而导致掉帧。

React16版本的渲染流程

为了解决15版本同步更新导致掉帧的问题,16版本推出了fiber架构,使得更新任务可以中断,优先执行渲染动画,事件回调等高优先级任务。

虽然异步更新能够解决掉帧问题,但在更新的过程中,需要解决很多技术和实践中的挑战,直到 React 18 版本,异步更新才正式变得稳定和可用。在16、17版本如果需要开启异步更新,需要使用ReactDOM.unstable_createRoot来开启,代码如下:

import React from 'react'; 
import ReactDOM from 'react-dom'; 
 
const root = ReactDOM.unstable_createRoot(document.getElementById('root'));  
root.render(<App />);

React18版本的渲染流程

前面我们说过,react16、17版本仍然默认是同步更新,从React18开始,createRoot已去除了前缀unstable_,可以稳定使用,目前大多数应用都使用了createRoot来开启并发更新,代码如下:

import { createRoot } from 'react-dom/client';

const domNode = document.getElementById('root');
const root = createRoot(domNode);
root.render(<App />);

React为这种渲染模式起名为concurrent mode(并发模式),实际同react16的异步更新是一个概念。那“并发”二字如何理解呢,低优先级任务与高优先级任务交替执行,因此可以称为并发。

React Router 6.4版本的新思路

React Router 6.4版本于2023年12月15日发布,推出了数据路由。在解释数据理由之前,我们先回顾一下react如何请求数据。

可以看到前后渲染了2次,为了解决这个问题,我们可以使用React Router 的数据路由,在组件加载前请求数据。

在组件内部通过useLoaderData获取数据,如下图:

使用数据路由后渲染流程如下图,从图中可见,组件仅渲染了1次。

服务端渲染(SSR)

客户端请求html文档->在服务端渲染整个html文档->直接返回整个文档->客户端水合,这样做有2个好处:

  1. 加快首屏渲染速度
  2. 搜索引擎爬虫能够轻松抓取 SSR 应用程序的内容

暂时无法在悟空文档外展示此内容

虽然服务端渲染能加快首屏渲染速度,但每次请求在服务端动态渲染内容,还是需要花费一定时间,因此nextjs团队后面推出了静态生成(SSG)

静态生成(SSG)

静态生成大部分思路与服务端渲染一致,只不过将渲染html放在了构建阶段执行,这样客户端请求时直接将html反回即可,流程如下:

客户端请求html文档->直接返回整个文档->客户端水合

静态生成虽然很大程度上加快了响应速度,但使用存在局限性,大多数场景只能应用于官网、博客等静态内容页面。

无论是服务端渲染还是静态生成,都需要等待服务端传输整个HTML文档,再进行水合,用户还是需要等待一段时间才看到完整页面,因此2023年nextjs团队推出了13版本,引入了流式渲染。

nextjs13版本流式渲染

前面说到服务端渲染与静态生成都需要服务端返回完整的HTML,为了加快响应速度,可以将HTML一段一段的返回,渲染完成一个组件,就返回一段HTML,用户可以第一时间看到渲染内容,这就是流式渲染。

该功能得益于React团队发布的服务端组件,早在18版本就可以使用这项实验性功能,直到今年react即将发布的19版本才正式推出服务端组件。

在nextjs中可以将客户端组件与服务端组件放在一起共同使用,二者功能如下:

  • 服务端组件:请求数据、渲染HTML代码片段、传输给客户端,由于没有事件绑定,无需水合
  • 客户端组件:使用hooks、绑定事件、渲染代码片段

Astro 岛屿架构

为了解决服务端渲染整个页面的组件都需要进行水合的问题,Astro 开创并推广了一种叫做岛屿的前端架构。其核心思想为:将页面划分为静态部分和动态部分,其中静态部分在服务器端渲染,而动态部分(即“岛屿”)则在客户端进行水合(hydration)和交互。这种架构方式有效地减少了 JavaScript 的加载和执行,提高了页面加载速度。

参考文章

www.zhihu.com/question/43…