服务端组件的优势
Motivation
服务器组件解决了我们在广泛的应用中看到的
这些挑战主要分为两大类。首先,我们希望让开发者更容易掉进 “成功的坑”,并在默认情况下实现良好的性能。第二,我们想让
Zero-Bundle-Size Components
开发者经常要对使用第三方包做出选择。使用一个包来渲染一些标记或格式化一个日期,对我们开发者来说很方便,但它增加了代码大小,损害了用户的性能。另一方面,我们自己重写这些功能是很耗时且容易出错的。虽然树形抖动等高级功能可以提供一定的帮助,但我们最终还是要给用户运去一些额外的代码。例如,今天用
请注意,这只是你可能使用的一组库,可能还有更小或更大的替代库。即使你喜欢只有
// NoteWithMarkdown.js
// NOTE: *before* Server Components
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 */);
}
然而,应用程序的许多部分不是交互式的,不需要完全的数据一致性。例如
服务器组件允许开发人员在服务器上渲染静态内容,充分利用
// NoteWithMarkdown.server.js - Server Component === zero bundle size
import marked from "marked"; // zero bundle size
import sanitizeHtml from "sanitize-html"; // zero bundle size
function NoteWithMarkdown({ text }) {
// same as before
}
Full Access to the Backend
在编写
例如,如果您正在创建一个新的应用程序(甚至是您的第一个应用程序
// Note.server.js - Server Component
import fs from "react-fs";
function Note({ id }) {
const note = JSON.parse(fs.readFile(`${id}.json`));
return <NoteWithMarkdown note={note} />;
}
更复杂的应用程序同样可以利用直接后端访问使用数据库、内部(微)服务和其他仅有后端数据源。
// Note.server.js - Server Component
import db from "db.server";
function Note({ id }) {
const note = db.notes.get(id);
return <NoteWithMarkdown note={note} />;
}
Automatic Code Splitting
如果你已经使用
// PhotoRenderer.js
// NOTE: *before* Server Components
import React from "react";
// one of these will start loading *when rendered on the client*:
const OldPhotoRenderer = React.lazy(() => import("./OldPhotoRenderer.js"));
const NewPhotoRenderer = React.lazy(() => import("./NewPhotoRenderer.js"));
function Photo(props) {
// Switch on feature flags, logged in/out, type of content, etc:
if (FeatureFlags.useNewPhotoRenderer) {
return <NewPhotoRenderer {...props} />;
} else {
return <OldPhotoRenderer {...props} />;
}
}
代码拆分对提高性能非常有帮助,但现有的代码拆分方法有两个主要限制。首先,开发人员必须记住要做到这一点,用
服务器组件以两种方式解决这些限制。首先,它们使代码自动分割。服务器组件将所有导入的客户端组件视为潜在的代码分割点。其次,它们允许开发人员在服务器上更早地选择使用哪个组件,这样客户端就可以在渲染过程中更早地下载它。净效果是,服务器组件让开发者更专注于他们的应用程序,并编写自然的代码,框架默认优化应用程序的交付。
// PhotoRenderer.server.js - Server Component
import React from "react";
// one of these will start loading *once rendered and streamed to the client*:
import OldPhotoRenderer from "./OldPhotoRenderer.client.js";
import NewPhotoRenderer from "./NewPhotoRenderer.client.js";
function Photo(props) {
// Switch on feature flags, logged in/out, type of content, etc:
if (FeatureFlags.useNewPhotoRenderer) {
return <NewPhotoRenderer {...props} />;
} else {
return <OldPhotoRenderer {...props} />;
}
}
No Client-Server Waterfalls
当应用程序连续请求获取数据时,一个常见的性能不佳的原因就会发生。例如,数据获取的一种模式是最初渲染一个占位符,然后在
// Note.js
// NOTE: *before* Server Components
function Note(props) {
const [note, setNote] = useState(null);
useEffect(() => {
// NOTE: loads *after* rendering, triggering waterfalls in children
fetchNote(props.id).then(noteData => {
setNote(noteData);
});
}, [props.id]);
if (note == null) {
return "Loading";
} else {
return (/* render note here... */);
}
}
然而,当父组件和子组件都使用这种方法时,子组件不能开始加载任何数据,直到父组件完成加载其数据。不过这种模式也有一些积极的方面。在单个组件中获取数据的一个好处是,它允许应用程序准确地获取它所需要的数据,并避免为
服务器组件允许应用程序通过将连续的往返移动到服务器来实现这一目标。问题不在于往返,而在于它们是从客户端到服务器。通过将这些逻辑转移到服务器上,我们减少了请求延迟,提高了性能。更好的是,服务器组件允许开发人员继续直接从其组件内获取他们所需的最小数据。
// Note.server.js - Server Component
function Note(props) {
// NOTE: loads *during* render, w low-latency data access on the server
const note = db.notes.get(props.id);
if (note == null) {
// handle missing note
}
return (/* render note here... */);
}
瀑布在服务器上还是不理想,所以我们会提供一个
Avoiding the Abstraction Tax
为了解决这个挑战,我们最初尝试了一种超前(AOT)优化的方法–Prepack,但最终这个方向并没有成功。具体来说,我们意识到,很多
服务器组件通过消除服务器上的抽象成本来帮助解决这个问题。例如,如果一个具有多层包装器的服务器组件的可配置性最终渲染到一个单一的元素,那么这就是所有将被发送到客户端的元素。在下一个例子中,<div>
,因此
// Note.server.js
// ...imports...
function Note({id}) {
const note = db.notes.get(id);
return <NoteWithMarkdown note={note} />;
}
// NoteWithMarkdown.server.js
// ...imports...
function NoteWithMarkdown({note}) {
const html = sanitizeHtml(marked(note.text));
return <div ... />;
}
// client sees:
<div>
<!-- markdown output here -->
</div>
Distinct Challenges, Unified Solution
如上所述,这些挑战的一个主题是
最终,我们意识到,无论是纯服务器渲染还是纯客户端渲染都是不够的。服务器渲染可以让应用程序轻松地访问服务器端数据源,并快速显示静态内容,而客户端渲染对于丰富的、交互式的功能至关重要,因为用户希望得到即时反馈。但混合服务器和客户端渲染往往意味着混合技术:用两种语言编写代码,使用两种框架,牢记两套习惯和生态系统。它还意味着要处理跨语言的数据传输,并且经常需要重复逻辑,一次用于服务器渲染的视图,一次用于交互式客户端预览。
服务器组件允许