Render Props 的另一種變形,FaCC (Function as Child Component)

Ken
8 min readFeb 15, 2021

--

歷史的眼淚,鑑往知來

Photo by National Cancer Institute on Unsplash

本篇目的在梳理 React 元件複用性 (Code reuse) 曾出現過的用法、Pattern。FaCC 過去常見於許多 library 如 React 官方本身的 Context Consumer, React router 內的 <Route>,以及一些表單類元件。

如上一篇 Reactjs 不同 UI 元件卻擁有相似行為,如何重複使用? 什麼是 Render Props

const App = () => {  
return (<div className="App">
<Mouse render={({ x, y }) => <h2>{`Current x:${x} ,y:${y}`}</h2>} />

</div>);
};

我們藉由 props ,傳遞一個 callback function 使底下的 <Mouse> 這個元件能控制自己內部的狀態、行為(此例為坐標),同時又能在 父層 決定<Mouse>要渲染什麼元件(畫面),達到行為共用。

Children 在 React 中,也是一種 props

假設有個元件叫做 <CoolCard>

const CoolCard = ({ children }) => (
<div>
<h2>So cool</h2>
<p>some content but not important....</p>
{children}

</div>);

我們一樣想在父層決定 <CoolCard> 的某些部分,要渲染什麼元件,因此我們可以這樣做:

(可參考 https://codesandbox.io/s/children-prop-od35z?file=/src/App.js

那麼除了用 children 決定渲染什麼元件之外,我們是否也一樣能把 <CoolCard> 內部的一些狀態,拿出來給父層使用呢?

比如說,我想要動態決定是否隱藏 <CoolCard> 裡的內文,但是按鈕的樣式又是被父層決定的!?

我們一樣可以透過 Render Props 的方式,將子元件的狀態透過 callback 的方式在父層拿到,只不過這次的 props 是 children!

所以我們的 children 就不再傳入一組元件了,而是傳入一組會回傳元件 的 callback function!

const CoolCard = ({ children }) => {
const [show, setShow] = useState(true);
const toggle = () => setShow(!show);

return (<div>
<h2>So cool</h2>

{ show && <p>some content but not important....</p> }

{ children(show, toggle) }
</div>);
};

此時 App 底下 <CoolCard> 的 children 就不再是放入元件了,而是放入一個 function !

// App
<CoolCard>
{
(show, toggle) => (
<button onClick={toggle}>{show ? "x" : "read"}</button>)
}
</CoolCard>

(可參考 https://codesandbox.io/s/facc-7fq29?file=/src/App.js:127-267)

Function as Child Component 的好處是,我們一樣能將某些狀態封裝進元件內部,且決定要釋出哪些狀態給外面;而外面除了可以決定這個元件某部份的樣式之外,也能取得一些元件內的狀態

也因此在過去 hook 尚未產生前,這種 pattern 也一樣被許多 library 使用,如官方自己的 context api

以及 React Router Dom

FaCC 的認知成本心智負擔太重了!

一般的 Render Prop 至少還能透過 props 名稱的命名( render={…}),使人能預測要傳入一組 function ;然而 FaCC 這種 pattern 要如何讓人得知 children 底下要傳入 function 還是 Component?一不小心沒用好,你的 App 就掛掉了。

有人透過 Type Guards, PropTypes, TypeScript 預防或是內部文件記載。而現實世界當中,變化總是非常快速,文件更新的速度及頻率 很容易跟不上 元件結構的改變 或 refactor 。

因此也有人將 FaCC 視為一種 anti-pattern 。有人說,就乾脆點!直接使用一般的 Reder Props ,大家也容易分得清楚;也有人提出透過 Component Injection 得方式解決,比 Render Props 更具描述性、容易看懂!

const App = () => (<div><CoolCard render={(show,toggle)=>(<button onClick={toggle}>
{show ? "x" : "read"}</button>))} />
</div>);const CoolCard = ({ render}) => {
const [show, setShow] = useState(true);
const toggle = () => setShow(!show);
return ( <div> <h2>So cool</h2> {show && <p>some content but not important....</p>} { render(show,toggle) }</div>);};

Component Injection :

const FancyButton = ({ show, toggle }) => (
<button onClick={toggle}>{show ? "x" : "read"}</button>);
const App = () => (
<div>
<CoolCard CustomButton={FancyButton} /> </div>);const CoolCard = ({ CustomButton }) => {
const [show, setShow] = useState(true);
const toggle = () => setShow(!show);
return ( <div> <h2>So cool</h2> {show && <p>some content but not important....</p>} <CustomButton show={show} toggle={toggle} /></div>);};

接下來,會介紹 custom hooks 的寫法、好處,以及上一篇 <Mouse> 的範例如何用 custom hooks 解決!

參考

Function as Child Components
Function as Child Components Are an Anti-Pattern
React Patterns
(幾篇文章都有點舊了,但可見到過去許多種不同寫法)

Web 實驗室 render props & FaCC(Function as Children Component)

如果這篇文章對你有幫助,請幫我拍手一下
你的拍手及分享是對我最大鼓勵與書寫下篇文章的動力

1下 閱。
5–10下 你認為這篇文章還不錯。
11–20下 你認為這篇貼文對你很有幫助。
21–40下 你非常喜歡這篇貼文,覺得實用。
40–50下 希望我可以多分享關於 React 相關的文章

--

--