iT邦幫忙

2024 iThome 鐵人賽

DAY 20
0

https://ithelp.ithome.com.tw/upload/images/20241004/20168201F3vwcHTHPz.png

今天要介紹的是 Render Props 模式,這也屬於 React 的模式之一。

Render Props 是什麼

Render Props 的目的類似 Higner-Order Component(HOC),兩者目的都是為了能在多個元件間共享、重用邏輯。
Render Prop 指的是一個元件的 prop,這個 prop 接收一個會傳回 JSX element 的函式,程式碼範例如下:

// render 這個 prop 接收 () => <h1>Hello</h1> 這個函式值,這個函式會回傳一個 JSX element
<MyComponent render={() => <h1>Hello</h1>} />

接收此 prop 的 MyComponent 元件本身就不需實作自己的渲染邏輯,而是呼叫 render 這個 prop 來渲染:

const MyComponent = ({ render }) => render(); // MyComponent 呼叫外部傳給他的 render prop,這個 render 函式會決定 MyComponent 要渲染的 element

雖然此模式稱為 Render Props,但 prop 名稱不一定要叫 render,任何能回傳 JSX element 的 prop 都可視為 render prop,且一個元件也可以接收多個 render prop,例如以下範例,Title 元件接收了 3 個 render prop。

const Title = (props) => (
  <>
    {props.renderFirstComponent()}
    {props.renderSecondComponent()}
    {props.renderThirdComponent()}
  </>
);

export default function App() {
  return (
    <div>
      <Title
        renderFirstComponent={() => <h1>✨ First render prop! ✨</h1>}
        renderSecondComponent={() => <h2>🔥 Second render prop! 🔥</h2>}
        renderThirdComponent={() => <h3>🚀 Third render prop! 🚀</h3>}
      />
    </div>
  );
}

Render Props 範例

傳遞參數給 render prop

接收 render prop 的元件通常不只是單純呼叫 render prop,而會傳遞參數給 render prop,讓 render 函式接收資料後渲染。

function MyComponent({ render }) {
  const data = { userName: "FooBar", age: 18 }; // Component 內部有需要渲染的 data

  return render(data); // MyComponent 將 data 作為參數傳給 render prop 來渲染,這樣 MyComponent 就不需自己實作渲染邏輯,只要傳遞資料
}

export default function App() {
  return (
    //  外部呼叫 MyComponent 時,由 render prop 來決定如何渲染 data 資料
    <MyComponent
      render={(data) => <h1>{`this is my data, ${JSON.stringify(data)}`}</h1>}
    />
  );
}

傳遞參數來避免提升狀態(state)

在 React 應用中,如果要讓兄弟元件能共享狀態、存取同一個值時,就需要提升狀態到共同父元件。例如以下範例,有 3 個元件共用狀態,一個是可讓使用者輸入值的 Input 元件,一個是顯示目前輸入的字數統計的 CharacterCount 元件和一個預覽輸入值的 Preview 元件,他們都需要取得使用者輸入的值,因此需將狀態提升到父元件。

import { useState } from "react";

function Input({ value, handleChange }) {
  return (
    <input
      type="text"
      value={value}
      onChange={(e) => handleChange(e.target.value)}
    />
  );
}

function CharacterCount({ value }) {
  return <p>字數統計: {value.length}</p>;
}

function Preview({ value }) {
  return <p>預覽: {value}</p>;
}

export default function App() {
  const [value, setValue] = useState("");

  return (
    <>
      <h1>Shared Value Example</h1>
      <Input value={value} handleChange={setValue} />
      <CharacterCount value={value} />
      <Preview value={value} />
    </>
  );
}

然而,提升狀態後,可能會讓不需 re-render 的子元件被重新渲染,進而影響效能。而使用 render props 模式可解決提升狀態的問題,解決方式就是讓 Input 元件接收 render prop,在 Input 元件內呼叫 render() 來渲染 CharacterCountPreview 區塊。修改後的程式碼如下,我們讓 Input 自己管理輸入值的 state,並接收 render prop,傳參數給 render prop 來讓CharacterCountPreview 元件能根據值渲染,因此避免了提升狀態到共同父元件。

import { useState } from "react";

function Input({ render }) {
  const [value, setValue] = useState("");

  return (
    <div>
      <input
        type="text"
        value={value}
        onChange={(e) => setValue(e.target.value)}
      />
      {/* 呼叫 render() 並傳遞 value */}
      {render(value)}
    </div>
  );
}

function CharacterCount({ value }) {
  return <p>字數統計: {value.length}</p>;
}

function Preview({ value }) {
  return <p>預覽: {value}</p>;
}

export default function App() {
  return (
    <>
      <h1>Shared Value Example with Encapsulated State</h1>
      <Input
        render={(value) => (
          <>
            <CharacterCount value={value} />
            <Preview value={value} />
          </>
        )}
      />
    </>
  );
}

children 作為函式

除了將要渲染的子元件傳給 render prop,也可以將值傳給 children prop 來渲染,用意相同,技術上來講這也算 render prop,這樣就不用擔心 render prop 的名稱,直接以現有的 children prop 來渲染即可,延續上面範例,可改成這樣:

import { useState } from "react";

function Input({ children }) {
  const [value, setValue] = useState("");

  return (
    <div>
      <input
        type="text"
        value={value}
        onChange={(e) => setValue(e.target.value)}
      />
      {/* 呼叫 children prop 傳入的值來渲染 */}
      {children(value)}
    </div>
  );
}

//...

export default function App() {
  return (
    <>
      <h1>Shared Value Example with Encapsulated State</h1>
      <Input>
        {(value) => (
          <>
            <CharacterCount value={value} />
            <Preview value={value} />
          </>
        )}
      </Input>
    </>
  );
}

優點

  • 可在多個元件中共享邏輯和資料
  • 接收 prop 的元件可重用,傳入不同 render prop 就可渲染不同元件
  • 將邏輯和渲染分開,接受 render prop 的有狀態元件,可以把資料傳給只負責渲染資料的無狀態元件
    • render prop 單純接受值並渲染,不在乎這個值的邏輯
    • 呼叫 render prop 的元件單純傳遞值,不在乎該怎麼渲染到畫面上
  • 解決 HOC 模式的問題
    • 解決 HOC prop 命名衝突:在 render props 中,不會有 props 自動合併的問題
    • 解決 HOC 內隱式 prop 問題:可明確看出傳遞給元件的 render prop 是什麼,能知道確切的 prop 來源

缺點

  • React Hooks 能解決 render prop 想解決的問題
    • hooks 改變為元件增加可重用性、資料共享的方式,多數情況下, hooks 可取代 render props 模式
  • 無法把生命週期方法增加到 render prop 內,render prop 只適用於不需更改資料的元件

Render prop 和 HOC 的同、異處

  • 同:都能讓多個元件共享邏輯或資料
  • 異:render prop 能解決 HOC 命名衝突、內隱式 prop 的問題

Reference


上一篇
[Day 19] HOC 模式
下一篇
[Day 21] Hooks 模式
系列文
30天的 JavaScript 設計模式之旅22
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言