這篇文章將簡單的介紹如何定義、使用 client component,以及使用 client compnent 不可不知的事。
這篇文章會介紹:
'use client'
的影響'use client'
小技巧 - 處理沒有加上 use client
的第三方套件Next 的運行環境主要有兩個:client 及 server。
這兩個環境都有自己特殊的功能及限制,雖然都是撰寫 javascript,但兩邊的程式碼無法互相兼容,所以必須要把 server 與 client 的運行區塊隔開。
在 Next 當中區分這兩個環境的就是 server component 與 client component。
關於 server component 的介紹可以看我昨天的鐵人賽文章,底下會著重介紹 client component。
client component 就是運行在 client 的 React component。
兩大好處:
一大壞處:
Next 預設是使用 server component,若使用 client component 就必須在 component 的最上方加上 'use client'
,這樣 client component 就定義好了。
// 直接在檔案最上方宣告 'use client'
'use client'
import { useState } from 'react';
const ClientComponent = ({children}) => {
// 1. 可以使用 react hook
const [state, useState] = useState(0)
return (
<>
<div>{state}</div>
<button
// 2. 可以綁定事件
onClick={() => setState(state + 1)}
>
plus one
</button>
</>
)
}
export default RootClient
'use client'
實際上是在聲明server component 與 client component 的邊界(boundary)
在探討 'use client'
的影響前,我們先來看看這個 boundary 是什麼。
network boundary 代表 server 與 client 的邊界,跨越這條邊界傳遞資料,會需要透過 network 傳遞。
這也是為什麼 server component 傳遞 props 給 client component 時,資料必須要是「可序列化」的,因為他們傳輸的媒介就是透過 network。
// in server component
// import client component
import ClientComponent from './client-component.tsx';
export default function ServerComponent() {
return(
<>
// props 必須要是可序列化的
<ClientComponent
name="name" // OK
function={function(){ console.log('foo') }} // error: function 不可序列化
/>
</>
)
}
'use client'
的影響當在 component 上方聲明了 'use client'
後,所有 import 進這支檔案的 component 都會被轉成 client component,即使傳進來的 component 並沒有宣告 'use client'
。
// in client component
'use client'
import NotUseClient from "./no-use-client";
import OtherComponent from "./other-component";
const ClientComponent = ({children}) => {
return (
<>
// 即使下方兩個 component沒有定義 use client
// 還是會被當作 client component
<NotUseClient />
<OtherComponent />
</>
)
}
export default ClientComponent
那有沒有其他方法可以在 client component 中使用 server component?
答案是有,可以透過 props
將 server component 傳遞給 client component 使用。
因為 network boundary 的關係,只能傳遞「可序列化」的資料,所以是沒辦法直接將 server component 傳給 client component(因為 server component 是 function),但可以傳送 server component 的執行結果。
底下將示範 server component 透過 props 的方式傳遞給 client component。
react component 執行後的結果為一個物件,詳細的資訊可以參考 react 官網的 createElement文章
children
或 props
傳遞 server component。// in server component
import ClientComponent from './client-component';
import ServerComponent from './server-component';
export default function OtherServerComponent() {
return (
<>
<ClientComponent
// error: network boundary 不可傳送 function 或是其他不可序列化的資料
ServerComponent={ServerComponent}
// success: ServerComponent 執行後的結果可序列化
serverComponent={<ServerComponent />}
>
// success: 原因同上
<ServerComponent />
</ClientComponent
</>
)
}
// in client component
'use client'
import NotUseClient from "./no-use-client";
import OtherComponent from "./other-component";
const ClientComponent = ({
children,
// 注意下方兩個 props 分別是 ServerComponent 本身與其執行後的結果
serverComponent, // 執行後的結果,可序列化
ServerComponent // ServerComponent 本身為 function,不可序列化
}) => {
return (
<>
{children}
{serverComponent}
<ServerComponent />
</>
)
}
export default ClientComponent
'use client'
小技巧上面講到 'use client'
會把所有 import 進來的 component 都轉換成 client component,運用這個特性我們可以解決套件支援度的問題。
雖然大部分的 library 已經陸續支援 Next13,在檔案內加上了 'use client'
、'use server'
的聲明。
但有些 library 沒有聲明 'use client'
但做了 client component 限定的操作。這時我們直接把它 import 進 server component 是會出錯的,因為 Next 並不知道這個組件需要運行在 client。
// in server component
import InteractiveUIComponent from 'library/without/use-client';
export default function ServerComponent() {
<>
// error: 當第三方套件沒加上 use client
<InteractiveUIComponent />
</>
}
此時我們可以使用 'use client'
的特性,將這個互動性 UI 重新 export 出去,就可以將其轉換為 client component,避免掉其沒有宣告 'use client'
的錯誤。
'use client'
import InteractiveUIComponent from 'library/without/use-client';
export default InteractiveUIComponent
接下來就可以自由的 import 這個我們包裝過的 UI 到 server component 裡,因為 Next 已經知道這是一個 client component。