[譯] 如何向帶有插槽的 React 元件傳遞多個 Children

zheng7426發表於2018-08-14

[譯] 如何向帶有插槽的 React 元件傳遞多個 Children

假如你需要寫一個可以重複使用的元件。可是呢,名為 children 的 prop 不能解決這個需求。這個元件得有能力接收不止一個 children,而且這些 children 的放置還不是相鄰的,而是按照需求而定。

可能你在寫的是帶有一個標題、一個邊欄和一個內容區塊的名為 Layout 的元件。又或者你正巧在寫一個帶有左右兩側動態邊欄的 NavBar 元件。

以上這些任務都可以輕鬆地藉助 “插槽” 模式完成,換言之就是傳遞 JSX 到一個 prop 中去。

小結:你可以把 JSX 傳向任何而不只是叫 children 的prop,也並不侷限於通過在一個元件的標籤裡嵌入 JSX —— 從而在簡化資料傳遞的同時呢,也讓元件有更多被重複使用的價值。

快速回顧 React 裡的 Children

我們們得先共同瞭解這樣一個事實:React 能夠讓你通過在 JSX 標籤之間巢狀 children 的方式來向元件傳遞 children。這些元素 (零個、一個或多個)在那個元件裡將以名為 children 的 prop 的形式存在。 React 的名為 children 的 prop 其實相當於 Angular 的 transclusion 還有 Vue 的 <slot>

這裡有一個向 Button 元件傳遞 children 的例子:

<Button>
  <Icon name="dollars"/>
  <span>BUY NOW</span>
</Button>
複製程式碼

我們們詳細地探究一下 Button 這個元件的實現,並看看它對 children 都做了什麼:

function Button(props) {
    return (
    <button>
        {props.children}
    </button>
    );
}
複製程式碼

Button 有效地將你之前傳的東西用一個 button 元素封裝了起來。到這一步雖然沒有什麼石破天驚的,但這卻是一個實用的能力。它讓接收資訊的元件得以把 children 放置在佈局的任何位置,或是為了變換風格樣式而將其封裝在一個The className 裡。被渲染之後的 HTML 程式碼看起來會是這樣子:

<button>
    <i class="fa fa-dollars"></i>
    <span>BUY NOW</span>
</button>
複製程式碼

(順便一提,這裡我們們假設 Icon 元件渲染出了 <i> 標籤。)

Children 也是一個普通的 Prop

React 針對 children 的這個用法挺炫的:被巢狀的元素可以指定成名為 children 的 prop,然而這並不是一個神奇而特殊的 prop 。你可以給一個 prop 指定任何其他的元素。且看:

// 這行程式碼其實 ——
<Button children={<span>Click Me</span>} />

// 相當於以下的程式碼。
<Button>
  <span>Click Me</span>
</Button>
複製程式碼

所以你不僅能夠像傳遞一個普通的 prop 那樣去傳遞 children,還可以往 prop 裡傳遞 JSX 程式碼。意不意外?沒錯,這個功能並不專屬於名為“children”的 prop!

以命名的卡槽來使用 Props

如果我告訴你,你可以向任何 prop 傳遞 JSX 程式碼,你會咋想?

(你已經知道這個祕密了,不是嗎?) 這裡有一個使用這些 “卡槽” 的 props 的例子 —— 以 3 個 props 呼叫一個名為 Layout 的元件:

<Layout
  left={<Sidebar/>}
  top={<NavBar/>}
  center={<Content/>}
/>
複製程式碼

在這個名為 Layout 的元件之中,它可以隨心所欲地使用 lefttop 以及 center 這三個 prop。以下是一個簡單的例子:

function Layout(props) {
  return (
    <div className="layout">
      <div className="top">{props.top}</div>
      <div className="left">{props.left}</div>
      <div className="center">{props.center}</div>
    </div>
  );
}
複製程式碼

試想一下, Layout 元件內的程式碼可以變得十分複雜,它可以擁有很多巢狀的 div 或 Bootstrap 類名等等。這個元件亦可以給更細緻的元件傳遞資訊。 無論 Layout 需要做什麼,它的使用者只需要知道如何傳遞 lefttop 以及 center 這三個 prop 就夠了。

使用 Children 來直接傳遞 Props

關於傳遞 children還有一個很不錯的特性(無論使用的 prop 是否為 children ):當你打算傳遞 child prop 的時候,正好在 parent 的作用域內,這時你可以向下傳遞任何你所需要的資訊。

這就好比 躍過了一個層級。舉個例子:與其去傳遞一個 "user" 給 Layout 元件,然後讓 Layout 再將 "user" 傳向 NavBar 元件,還不如直接建立一個 NavBar(已經設好 user)然後把整個 NavBar 傳向 Layout。 這可以幫助避免 “prop 鑽井” 的問題,你不必再費心地將一個 prop 放進好多個元件的層級。

function App({ user }) {
  return (
    <div className="app">
      <Nav>
        <UserAvatar user={user} size="small" />
      </Nav>
      <Body
        sidebar={<UserStats user={user} />}
        content={<Content />}
      />
    </div>
  );
}

// 接收 children 並渲染它(們)
const Nav = ({ children }) => (
  <div className="nav">
    {children}
  </div>
);

// Body 需要一個側邊欄和內容,如果像下面這樣寫的話,
// 他們可以做任何事
const Body = ({ sidebar, content }) => (
  <div className="body">
    <Sidebar>{sidebar}</Sidebar>
    {content}
  </div>
);

const Sidebar = ({ children }) => (
  <div className="sidebar">
    {children}
  </div>
);

const Content = () => (
  <div className="content">main content here</div>
);
複製程式碼

現在和下面這個寫法比較一下,Nav 和 Body 都接受名為 user 的 prop,然後它們負責手動將 prop 傳遞給 children。在那之後,它們的 children 必須給更細一層的 children 傳遞下去……

function App({ user }) {
  return (
    <div className="app">
      <Nav user={user} />
      <Body user={user} />
    </div>
  );
}

const Content = () => <div className="content">main content here</div>;

const Sidebar = ({ user }) => (
  <div className="sidebar">
    <UserStats user={user} />
  </div>
);

const Body = ({ user }) => (
  <div className="body">
    <Sidebar user={user} />
    <Content user={user} />
  </div>
);

const Nav = ({ user }) => (
  <div className="nav">
    <UserAvatar user={user} size="small" />
  </div>
);
複製程式碼

沒有之前那個寫法方便,不是嗎?用這種方法向下傳遞 prop (又稱 “prop 鑽井”)會讓元件之間被太多你可能不想要的麻煩所羈絆 —— 並不一定總是壞事,但你最好搞清楚它們之間都是怎麼連線的。上面第一種使用 children 的寫法可以避免對更復雜解決辦法的需求,比如說可能要用到 context, Redux 或 MobX (還有好多別的)。

要當心 PureComponent 或 shouldComponentUpdate

如果你需要在一個帶有 children 的元件上實現 shouldComponentUpdate (抑或 PureComponent ),用來防止二次渲染,同時 children 也無法被渲染出來。所以要注意這一點。在實際操作中,帶有 “插槽” 的元件很有可能體積小而且渲染速度快,所以不太會需要效能方面的調整。

不過如果你遇到了確實需要調整 “插槽” 元件的效能的情況,那麼可以考慮把表現效能過慢的部分程式碼提取出來,單獨放進一個元件,然後進行調整。

學習 React 有時會很痛苦 —— 程式碼庫和工具實在太多啦! 想聽我的意見嗎?那就是將那些程式碼庫和工具通通忽略掉 :) 若閣下想要步步為營的引導,就請閱讀我寫的書吧 —— Pure React

如果發現譯文存在錯誤或其他需要改進的地方,歡迎到 掘金翻譯計劃 對譯文進行修改並 PR,也可獲得相應獎勵積分。文章開頭的 本文永久連結 即為本文在 GitHub 上的 MarkDown 連結。


掘金翻譯計劃 是一個翻譯優質網際網路技術文章的社群,文章來源為 掘金 上的英文分享文章。內容覆蓋 AndroidiOS前端後端區塊鏈產品設計人工智慧等領域,想要檢視更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章