转载自: 魔术师卡颂 公众号
12月21日,React
团队公布了一个新的提案Server Components
。javascript
伴随这个提案同时发出的,还有一个小时的视频讲解、可供运行的Demo、详尽的介绍。html
可见,React
团队很重视这个提案。本文会从以下方面讲解:前端
Server Components
是什么Server Components
解决了什么问题一句话归纳:java
Server Components
是在服务端运行的React
组件。git
咦?这和服务端渲染
(SSR)有什么区别?github
相比SSR
将组件在服务端渲染成填充内容的HTML
字符串,并在客户端hydrate
后使用。Server Components
更像咱们的在客户端写的普通组件同样,只不过他的运行环境是服务端。数据库
咱们能够将组件按照功能分为:segmentfault
容器组件
交互组件
举个例子,Note
组件是容器组件
,他负责请求并缓存数据。NoteEditor
是渲染note
数据并执行用户交互的交互组件
。后端
function Note(props) { const [note, setNote] = useState(null); useEffect(() => { fetchNote(props.id).then(noteData => { setNote(noteData); }); }, [props.id]); if (note == null) { return "Loading"; } else { return <NoteEditor note={note}/> } }
如例子所述,咱们能够经过在useEffect
中发起请求并将返回的数据保存在state
中。缓存
这种「请求-渲染」模式会碰见被称为waterfall
的问题:
就像一节一节的瀑布往下流水,NoteEditor
须要等待Note
请求note
成功后才能开始渲染。
当交互组件
依赖的数据源越多,waterfall
问题会更明显。
理论上,若是React
足够聪明,就能在服务端
执行容器组件
的渲染逻辑,在客户端
执行交互组件
的渲染逻辑。
按照这样的理念,以下这棵彻底在客户端渲染的组件树:
能够拆分为:在服务端
运行的容器组件
和在客户端
运行的交互组件
。
其中在服务端运行的容器组件
就是Server Component
。
既然ServerComponent
在服务端
运行,自然更接近各类IO
(请求数据库、读取文件、缓存...)。
上面的例子彻底能够直接从数据库
获取note
数据,同时借助Suspense
,采用同步的写法。
function Note(props) { const note = db.notes.get(props.id); if (note == null) { return "Loading"; } return <NoteEditor note={note}/> }
任何其余数据源只须要经过React
提供的API
简单封装,使其支持Suspense
,就能接入ServerComponent
中。自然更接近后端。
区别于SSR
传输的HTML
字符串。ServerComponent
会将Note
组件及其从IO
请求到的数据序列化为相似JSX
的数据结构,以流
的形式传递给前端:
客户端在运行时直接获取到填充了数据的流
,并借助Concurrent Mode
执行流式
渲染。
假设咱们开发一款MD
编辑器。服务端传递给前端MD
格式的字符串。
咱们须要在前端引入将MD
解析为HTML
字符串的库。这个库就有206k。
import marked from 'marked'; // 35.9K (11.2K gzipped) import sanitizeHtml from 'sanitize-html'; // 206K (63.3K gzipped) function NoteWithMarkdown({text}) { const html = sanitizeHtml(marked(text)); return (/* render */); }
经过ServerComponent
咱们怎么解决这个问题呢?
只须要简单将NoteWithMarkdown
标记为ServerComponent
,将引入并解析MD
这部分逻辑放在服务端
执行。
ServerComponent
并不会增长前端项目打包体积。这个例子中,一次性为咱们减小了前端206K (63.3K gzipped)的打包体积以及解析MD
的时间。
经过使用React.lazy
能够实现组件的动态import
。以前,这须要咱们在切换组件/路由时手动执行。在ServerComponent
中,都是自动完成的。
在上面动图中,左侧列表是ServerComponent
,当点击其中卡片时,组件对应数据会动态加载。
Vue
做为一门使用模版语言
的框架,模版语言
的固定写法使其能在编译时针对模版内容做出优化。
因为JSX
仅仅是JS
的语法糖,React
很难在编译时作出优化。
ServerComponent
对组件提出了更多限制(不能使用useState
、useEffect
...)。这些限制从侧面为AOT
提供更多优化线索。
下面咱们经过改写一个记事本
组件讲解ServerComponent
的使用:
// Note.js import fetchData from './fetchData'; import NoteEditor from './NoteEditor'; function Note(props) { const {id, isEditing} = props; const note = fetchData(id); return ( <div> <h1>{note.title}</h1> <section>{note.body}</section> {isEditing ? <NoteEditor note={note} /> : null } </div> ); }
Note
组件的主要功能是根据props
传入的id
请求对应的note
数据。
NoteEditor
用于展现及修改note
。
其中fetchData
方法用于获取数据,数据的加载中状态由组件外的Suspense
完成。
能够看到,交互部分由NoteEditor
完成,Note
主要功能是获取并传递数据。
接下来咱们将Note
变为ServerComponent
。
// 注意🙋 // Note.server.js - Server Component // 注意🙋 import db from 'db.server'; // 注意🙋 import NoteEditor from './NoteEditor.client'; function Note(props) { const {id, isEditing} = props; const note = db.posts.get(id); return ( <div> <h1>{note.title}</h1> <section>{note.body}</section> {isEditing ? <NoteEditor note={note} /> : null } </div> ); }
有3点须要注意的改动,咱们依次了解下:
Note.js
文件名改成Note.server.js
表明这是Server Component
。Note.server.js
运行于服务端,咱们不须要客户端的fetchData
方法,能够直接访问数据库,因此这里调用db.server
提供的方法NoteEditor
用于展现及修改note
。这是由客户端用户的交互控制的,因此将文件名改成NoteEditor.client
表明这是个Client Component
。太阳底下没有新鲜事。早期前端交互简单,仅仅做为服务端的View
层。
随着前端交互变复杂,出现了前端框架主导的客户端渲染(CSR)。
为了解决首屏渲染速度、SEO问题,出现了服务端渲染(SSR),又回到了曾经做为View
层的起点,只不过控制的粒度更细。
ServerComponent
提案的出现,预示着React
的长远目标:将对View
层的控制细化到组件级别。
为何是「长远目标」?ServerComponent
落地的大前提是Concurrent Mode
生产环境稳定,让咱们一块儿期待2021年吧。
[1] 视频讲解: https://www.youtube.com/watch...
[2] Demo: https://github.com/pomber/ser...