当我说
Client Components
-> 我的意思是在其顶部使用 use client
的文件,以及 Server Components
— 相应使用服务器的文件
没有人说,如何在服务器端获取数据并将其直接传递给客户端组件(或者我不知何故没有找到) 更重要的是,-
async/await
客户端组件中禁止使用函数和组件,如果您尝试获取服务器数据来预渲染客户端组件,您将收到错误:
Error: async/await is not yet supported in Client Components, only Server Components. This error is often caused by accidentally adding `'use client'` to a module that was originally written for the server.
NextJS 文档说客户端获取数据应该这样处理:
useEffect(() => {
fetch('/api/profile-data')
.then((res) => res.json())
.then((data) => {
setData(data)
setLoading(false)
})
}, [])
但这根本不能满足我,我想获取我的数据并在服务器端用它填充
client components
getStaticProps
/getServerSideProps
也不再可用,所以...
所以,我得到的解决方案 应用程序目录:
/app
page.tsx
clientPage.tsx
layout.tsx
page.tsx
// That's used by default, but anyway
"use server"
import React from 'react'
import { ClientPage } from './clientPage'
const getServerDataForClient = async () => {
console.log('That was executed on the server side')
const serverData = await new Promise(resolve => {
setTimeout(() => resolve({ someData: 'secret' }), 3000)
})
return serverData
}
const Page = async () => {
console.log('That also was executed on the server side')
const serverData = await getServerDataForClient()
return <ClientPage serverData={serverData} />
}
export default Page
clientPage.tsx
'use client'
import React, { FC, useEffect, useState } from 'react'
type TClientPageProps = {
serverData: {
someData: string
}
}
// This function will be executed firstly on the server side
// It will ignore all dynamic logic that works in the browser (like useEffect, useLayoutEffect and etc.)
// And just will return the html with rendered data (including initial state of `useState`, and `useRef`, if we would use that)
export const ClientPage: FC<TClientPageProps> = ({ serverData }) => {
// first we will get `server state` in the server and browser and as an initial state
const [state, setState] = useState('server state')
console.log("This log will be shown in the server logs on component's first mount and browsers on all mounts")
useEffect(() => {
// that won't be called on the server side
// it will be changed only when the component will be mounted in the browser
setState('browser state')
}, [])
return (
<main>
<div>State is: {state}</div>
<div>Server data is: {serverData.someData}</div>
</main>
)
}
所以,NextJS为我们提供了一种更好的处理服务器端功能的方法(我真的很喜欢我们摆脱
getServerSideProps
/getStaticProps
),但是现在 - 我必须有两个文件:page.tsx
服务器逻辑和 clientPage.tsx
提供所有这些逻辑
我用得对吗? 或者我错过了什么? 我脑海中的另一个问题是,如果我在代码中嵌套了组件,那么我必须通过道具钻探传递数据,我无法解决这种混乱。
P.S. 如果您尝试将这两个文件
page.tsx
和 clientPage.tsx
合并为一个 — 您将收到错误 ("useState" is not allowed in Server Components.
)
另外,如果您尝试制作类似的东西并使用 Server Actions
中的 Client Components
来获取服务器端的数据以预渲染它:
const getServerDataForClient = async () => {
'use server'
console.log('That was executed on the server side')
const serverData = await new Promise(resolve => {
setTimeout(() => resolve({ someData: 'secret' }), 3000)
})
return serverData
}
您还会收到错误,因为
Client Components
无法使用 async/await
等待服务器操作
或者,我想到的另一个选项 - 也许将
use client
添加到所有页面组件
我的意思是,
/src/components/Partners
"use client"
export const Partners: FC = () =>
// some code here
但是 App Router 会变得不太舒服,因为如果我需要在同一页面上使用其中的几个组件,我将需要创建另一个连接组件
一般来说,最好让服务器来获取数据,而不是客户端。在某些情况下,您可能需要客户端获取,例如无限滚动。
您使用具有 UI 逻辑的客户端组件和用于获取数据并呈现客户端组件的服务器组件的方法是正确的,并且大多数时候这就是您开发 Next.js 应用程序的方式。这种区别并没有什么问题,事实上,它很好,因为它强制执行“单一责任原则”,例如,您的组件应该只做一件事,并且做好一件事。
在这种情况下,您的服务器组件将是获取数据的组件,然后该组件将委托给其余 UI 组件来呈现该数据。如果您将组件构建为尽可能模块化,那么“道具钻探”不会成为太大的问题,因为您只需将必要的数据传递给那些可以在应用程序中的任何位置工作的组件。举以下例子:
interface Props {
user: User
}
const AvatarImage: React.FC<Props> = ({ user }) => {
return (
<img src={user.image} />
);
});
还有,
interface Props {
image: string
}
const AvatarImage: React.FC<Props> = ({ image }) => {
return (
<img src={image} />
);
});
虽然这个例子过于做作和简单,但哪一个看起来更好?第一个选项仅适用于用户
,而第二个选项适用于任何东西。如果此组件要使用 User
接口的更多属性,您可能会想通过上下文获取数据,以避免从顶部进行道具钻取,例如:
interface Props {
}
const AvatarImage: React.FC<Props> = () => {
const { image } = useUserContext();
return (
<img src={image} />
);
});
但这迫使
AvatarImage
只对用户有效。当您解决了“支柱钻孔”问题时,您通过增加
AvatarImage
和 User
之间的耦合并降低组件的灵活性,创建了一个新问题。
另一方面,将页面组件设置为客户端组件将不允许您使用像 这样需要在服务器内运行的导出。TL;DR:是的,有多个组件来做一件事是合适的。获取数据并将 UI 渲染委托给客户端组件的服务器组件绝对是