Paul Scanlon使用Waku展示了RSCs如何让React开发人员在组件级别访问异步服务器端请求和数据。
哇,最近关于React服务器组件(RSCs)的讨论声浪很大,但大部分时间,在阅读了互联网上最聪明的人的解释后,我并没有真正理解任何内容。但我后来花时间尝试了Waku,现在我觉得RSCs比我最初想的要简单得多。
什么是Waku?
Waku(wah-ku)或 わく 在日语中意味着“框架”。作为一个最小的React框架,它旨在加速初创公司和代理商构建中小型React项目的开发人员的工作。根据Waku网站的介绍,这些项目包括营销网站、轻量级电子商务和Web应用程序。
然而,网站介绍中遗漏的是,Waku支持React服务器组件——因此,如果你想亲自尝试它们,你不需要使用Next.js(对此我个人感到很感激)。值得一提的是,目前Waku正在快速开发中,应仅用于非生产项目。
React服务器组件简介
所以这是我的看法:RSCs让React开发人员可以在组件级别访问异步服务器端请求和结果数据。
在RSCs之前,像Next.js、Gatsby、Remix和Astro等框架都要求你在路由级别进行服务器端请求。
以下是上述每个框架中实现此目的的一些示例。
Next.js路由(应用路由器)
在此路由中,有一个名为getData
的函数,该函数对GitHub API进行异步请求并返回响应,然后可以使用getData
函数将其提取并提供给路由或页面。
// app/page.jsx
import ParentComponent from '../components/parent-component';
const Page = async () => {
const data = await getData();
return <ParentComponent data={data} />;
};
async function getData() {
const response = await fetch('https://api.github.com/repos/vercel/next.js');
const data = await response.json();
return data;
}
export default Page;
Next.js路由(页面路由器)
在路由中,有一个名为getServerSideProps
的函数,该函数对GitHub API进行异步请求并将响应返回给路由或页面通过data
属性。
// pages/index.js
import ParentComponent from '../components/parent-component';
const Page = ({ data }) => {
return <ParentComponent data={data} />;
};
export async function getServerSideProps() {
const response = await fetch('https://api.github.com/repos/vercel/next.js');
const data = await response.json();
return { props: { data } };
}
export default Page;
Gatsby路由
在此路由中,有一个名为getServerData
的函数,该函数对GitHub API进行异步请求并将响应返回给路由或页面通过data
属性。
// src/pages/index.js
import ParentComponent from '../components/parent-component';
const Page = ({ data }) => {
return <ParentComponent data={data} />;
};
export async function getServerData() {
const response = await fetch('https://api.github.com/repos/gatsbyjs/gatsby');
const data = await response.json();
return { props: { data } };
}
export default Page;
Remix路由
在此路由中,有一个名为loader
的函数,该函数对GitHub API进行异步请求并返回响应,然后可以使用useLoaderData
钩子将其提取并使其可用于页面。
// app/routes/_index.jsx
import { useLoaderData } from '@remix-run/react';
import { json } from '@remix-run/node';
import ParentComponent from '../components/parent-component';
const Page = () => {
const { data } = useLoaderData();
return <ParentComponent data={data} />;
};
export const loader = async () => {
const response = await fetch('https://api.github.com/repos/remix-run/remix');
const data = await response.json();
return json({
data,
});
};
export default Page;
Astro路由
// src/pages/index.astro
---
const response = await fetch('https://api.github.com/repos/withastro/astro');
const data = await response.json();
import ParentComponent from '../components/parent-component';
---
<ParentComponent data={data} />;
属性传递
你会注意到在所有这些示例中,数据通过名为data
的属性传递给名为ParentComponent的组件。
ParentComponent
ParentComponent可能是这样的,其中数据再次通过名为ChildComponent的另一个组件传递。
// components/parent-component.js
import ChildComponent from './child-component';
const ParentComponent = ({ data }) => {
return <ChildComponent data={data} />;
};
export default ParentComponent;
view raw
ChildComponent
最后,在ChildComponent中,你可能希望对这些数据进行一些操作;正如你所看到的,数据必须在到达目的地之前经过一些旅程。
// components/child-component.js
const ChildComponent = ({ data }) => {
return <pre>{JSON.stringify(data, null, 2)}</pre>;
};
export default ChildComponent;
组件级数据获取
你可能知道,如果要重构此应用程序或移动Parent或Child组件,则还需要重新连接数据路径。
在应用程序的生命周期中,这种情况并
不罕见,取决于你的应用程序有多复杂,你需要在数据到达目的地之前走多远。
这就是RSCs真正有用的地方。这是我使用Waku时的方法。
Waku路由
使用Waku,我仍然有一个路由,但在此级别不会发生数据获取。
// src/pages/index.jsx
import ParentComponent from '../components/parent-component.js';
const Page = async () => {
return <ParentComponent />;
};
export default Page;
export const getConfig = async () => {
return {
render: 'dynamic',
};
};
Waku ParentComponent
ParentComponent仍然导入并返回ChildComponent,但没有属性,也没有将任何内容传递给ChildComponent。
// src/components/parent-component.jsx
import ChildComponent from './child-component.js';
const ParentComponent = () => {
return <ChildComponent />;
};
export default ParentComponent;
Waku ChildComponent
这是ChildComponent;再次,没有通过属性传递数据。相反,所有数据获取都在组件内部、服务器端进行。
// src/components/child-component.tsx
const ChildComponent = async () => {
const response = await fetch('https://api.github.com/repos/dai-shi/waku');
const data = await response.json();
return <pre>{JSON.stringify(data, null, 2)}</pre>;
};
export default ChildComponent;
对一些人来说很熟悉
这种在组件级别访问数据的方法可能对一些人来说很熟悉。对我来说也是如此,因为我曾是Gatsby的忠实用户。
Gatsby的useStaticQuery钩子
在2019年2月,Gatsby引入了useStaticQuery钩子,虽然获取数据的方法大不相同(我并不打算将其与RSCs进行比较),但理论上有点类似,原因如下。
// src/components/child-component.js
import { useStaticQuery, graphql } from 'gatsby';
const ChildComponent = () => {
const data = useStaticQuery(graphql`
query {
github {
id
owner {
login
url
}
description
}
}
`);
return <pre>{JSON.stringify(data, null, 2)}</pre>;
};
export default ChildComponent;
在Gatsby中,你从未使用GraphQL来获取数据(这是一个常见的误解);相反,你是在查询数据。数据获取是在构建时进行的,但使用useStaticQuery
钩子,你能够从任何组件中、任何级别访问数据,而无需通过属性传递它。
对于RSCs,数据获取是在运行时进行的,因此虽然在RSCs和Gatsby的useStaticQuery
钩子之间获取数据的方法有所不同,但当你能够从任何组件中访问数据时,你可以做出架构选择。
数据获取需要思考
然而,对于RSCs,你仍然需要考虑在哪些情况下在组件级别进行数据获取,而不是在路由级别进行数据获取。
一方面,是的,在组件所需的地方获取数据并访问数据很方便;但另一方面,如果在同一路由上有几个组件都在独立获取数据,这会对性能产生负面影响吗?
在某些情况下,可能仍然有意义在单个路由级别进行请求,并通过属性将结果数据传递给需要它的组件,而不是多个组件级别数据请求。值得在这里提到的是,采用明智的缓存策略可能会限制多个组件级别数据请求的影响。
最后的思考
在我看来,RSCs只是在构建数据密集型React应用程序时可供选择的另一个选项。我不认为它们能解决每个用例,也不打算如此。在许多情况下,它们可能不是正确的选择,但没关系。
正如每个开发人员在他们的职业生涯中多次说过的那样,这取决于情况。
从我使用Gatsby的经验来看,从组件中轻松访问数据有很多优势。因为逻辑、数据和最终的用户界面元素都清晰地位于同一个文件中,与追踪属性并尝试跟随数据路径相比,开发人员的体验通常更好。
总之,我真的很喜欢RSCs,我认为随着时间的推移,我们都会发现在开发过程中的最佳实践和需要注意的事项。但目前,我认为它们是一个非常酷的进步,并且我期待进一步的实验。如果你对自己尝试RSCs感兴趣,请尝试Waku。