iT邦幫忙

2023 iThome 鐵人賽

DAY 10
0
Modern Web

30 天淺入淺出 Next.js 13系列 第 10

Day10 - Client Component

  • 分享至 

  • xImage
  •  

這篇文章將簡單的介紹如何定義、使用 client component,以及使用 client compnent 不可不知的事。

這篇文章會介紹:

  1. Next 的兩種運行環境
  2. client component 是什麼
  3. 'use client' 的影響
  4. 怎麼在 client component 中使用 server component
  5. 'use client' 小技巧 - 處理沒有加上 use client 的第三方套件

運行環境(runtime)

Next 的運行環境主要有兩個:client 及 server。

這兩個環境都有自己特殊的功能及限制,雖然都是撰寫 javascript,但兩邊的程式碼無法互相兼容,所以必須要把 server 與 client 的運行區塊隔開。

在 Next 當中區分這兩個環境的就是 server component 與 client component。

關於 server component 的介紹可以看我昨天的鐵人賽文章,底下會著重介紹 client component。

Client Component

client component 就是運行在 client 的 React component。

兩大好處:

  • 互動性:可以綁定事件、使用 React hook,讓我們可以做出具有互動性的 UI
  • 使用 Browser API:可以使用 localStorage, geolocation 等 browser API

一大壞處:

  • JS Bundle:client component 是會把 javascript 傳到 client 端執行的,這會增加 js bundle 大小

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'

'use client' 實際上是在聲明server component 與 client component 的邊界(boundary)

在探討 'use client' 的影響前,我們先來看看這個 boundary 是什麼。

Network 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

那有沒有其他方法可以在 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文章

  • server component
    使用 childrenprops 傳遞 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
    </>
  )
}
  • client component
// 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。

參考資料


上一篇
Day9 - Server Component - Next13 的渲染策略
下一篇
Day11 - Component Composition Pattern
系列文
30 天淺入淺出 Next.js 1321
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言