React children as props

Description
ADVANCED REACT 從 react children as props pattern 到 react hooks
前言
上集內容跟這一集沒有任何關係,但有興趣還是可以看一下初探 React render 的機制
此篇是 “ADVANCED REACT” 這本書中講解 children as props 的一段,這個 pattern 在 React hooks 出現後已經不常用了,但是有些特定情境會用到,同時也會增加你對 React pattern 的更深一層認識。
children as props? 聽起來有點厲害
先拆解名詞:
- children:指的是在 component tag 中間的內容,
<Parent>
<Child />
</Parent>
<Parent children={<Child />} />
<Parent children={() => <Child />} />
以上三段程式碼結果是一樣的。
- props: 指的是 component 的屬性,可以是 javascript 的任何資料型態。你傳什麼進去都可以。
那麼我們把兩段結合起來, children as props 就是把 children 當作 props 傳進去,好處是可以在父層 component 中控制 children 的內容。
children as props 的使用情境
借用書裡的例子,我們要實作一個監聽 resize 事件並且回傳當前 innerWidth 的 component。 第一個比較亂但直覺的寫法是,
- 在父層建立 state,
- 把 setState 的方法傳給子層,
- 子層在 resize 時呼叫 setState,這樣就可以在父層控制子層的 render。
const ResizeDetector = ({ onWidthChange }) => {
const [width, setWidth] = useState();
useEffect(() => {
const listener = () => {
const width = window.innerWidth;
setWidth(width);
// trigger onWidthChange prop here
onWidthChange(width);
}
window.addEventListener("resize", listener);
// the rest of the code
}, [])
return ...
}
const Layout = () => {
const [windowWidth, setWindowWidth] = useState(0);
return (
<>
<ResizeDetector onWindowWidth={setWindowWidth} />
{windowWidth > 600 ? <WideLayout /> : <NarrowLayout />}
</>
);
};
這樣寫完全可以運作,但在每個父層要用的時候都要在父層管理一個 state,這樣違反了不要重複這個原則。 我們想一下,有什麼部分可以省略的?一定要在父層管理 state 嗎?
書裡面的另一種解法:
const ResizeDetector = ({ children }) => {
const [width, setWidth] = useState();
// same code as before
// pass width to children
return children(width);
};
const Layout = () => {
return (
<ResizeDetector>
{(windowWidth) => {
// no more state! Get it directly from the resizer
return windowWidth > 600 ? <WideLayout /> : <NarrowLayout />;
}}
</ResizeDetector>
);
};
我們成功的用 render props 的方式,把原本在父層的 state,變成 render children 的 function props, 要使用的 component 不再需要額外管理 state。
Hooks 出現
用 hooks 改寫:
const useResizeDetector = () => {
const [width, setWidth] = useState();
useEffect(() => {
const listener = () => {
Page 65
const width = ... // get window width here
setWidth(width);
}
window.addEventListener("resize", listener);
// the rest of the code
}, [])
return width;
}
這個 state 的現在不在父層或子層了,要用他的地方直接 call hooks 拿 state。
- Hooks 的精神是 declarative,使用時不必關心實現方式。
- Render props 的 pattern 在會產生 nested 的 jsx 程式碼,相較於 hooks 的可讀性比較差。
const Layout = () => {
const windowWidth = useResizeDetector();
return windowWidth > 600 ? <WideLayout /> : <NarrowLayout />;
};
剛剛講這麼多,其實 hooks 出來後幾乎都轉投 hooks 懷抱。但是作者有提到:
- Legacy 的 React,也就是在 hooks 出來前的React@16.8.0,這種 render props 隨處可見。
- 在某些情境下,例如有些 listener 是掛在 DOM 上的 element,這個時候還要用 ref 然後 props 傳下去,然後判斷的時候 ref.current(),這時候 hooks 比傳統的 render children props 麻煩許多。
const ScrollDetector = ({ children }) => {
const [scroll, setScroll] = useState();
return <div onScroll={(e) => setScroll(e.currentTarget.scrollTop)}>{children}</div>;
};
const Layout = () => {
return (
<ScrollDetector>
{(scroll) => {
return <>{scroll > 30 ? <SomeBlock /> : null}</>;
}}
</ScrollDetector>
);
};
下一篇文章會講解 React.memo (跟你們一樣,我從來沒用過)。