[Ch3 - Ch4] Elements as Props์ Render Props
๐ Advanced React
์ปดํฌ๋ํธ๋ฅผ ์ ์ฐํ๊ฒ ๊ตฌ์ฑํ ์ ์๋๋ก ํ๋ ํจํด๋ค ์ค์ Elements as Props๊ณผ Render Props ํจํด์ ๋ํด ์์๋ณด๋๋ก ํ์.
Ch3 Elements as Props
Button ์ปดํฌ๋ํธ๋ฅผ ๊ตฌํํ๋ค๊ณ ๊ฐ์ ์ ํ๋ฉด, ์ฌ๋ฌ ๊ฐ์ง ์ค์ ์ ํ์๋ก ํ๋ ๊ฒฝ์ฐ๋ก ๋ฐ์ํ๋ค. ์ฒ์์๋ ๊ฐ๋จํ๊ฒ isLoading, iconColor, iconSize์ ๊ฐ์ props๋ฅผ ์ถ๊ฐํ๋ ๋ฐฉ์์ผ๋ก ์๊ตฌ์ฌํญ์ ๋ฐ์ํ๊ฒ ์ง๋ง, ๊ฒฐ๊ตญ ์๊ตฌ์ฌํญ์ด ์ถ๊ฐ๋ ์๋ก ์ ์ ์ปดํฌ๋ํธ๊ฐ ์ ์ ๋ณต์กํด์ง๊ณ , ๊ด๋ฆฌ๊ฐ ์ด๋ ค์์ง๋ฉฐ, ์ฝ๋๋ฅผ ์ฝ๊ธฐ๊ฐ ํ๋ค์ด์ง๋ค.
const Button = ({ isLoading, iconColor, iconSize, ... }) => {
return <button>{isLoading ? <LoadingIcon /> : null}</button>
};
Elements as Props
์ด๋ฅผ ํด๊ฒฐํ๋ ๋ฐฉ๋ฒ์ผ๋ก Elements as Props ํจํด์ ์๊ฐํ๋๋ฐ, ํด๋น ํจํด์์๋ props๋ก ์ ๋ฌํ๋ ๊ฒ์ด Element ์์ฒด๊ฐ ๋๋ค๊ณ ํ๋ค.
<Button icon={<Loading />} />
<Button icon={<Error color="red" />} />
์๋ฅผ ๋ค์ด ์์ ๊ฐ์ด icon์ Element๋ก ์ ๋ฌํ๊ฒ๋๋ค๋ฉด, ๋ฒํผ ์ปดํฌ๋ํธ์์ ์์ด์ฝ์ ์ธ๋ถ์์ ์์ ํ ๋ ๋ฆฝ์ ์ผ๋ก ๊ด๋ฆฌ๋๋ฉฐ, ๋ฒํผ์ ์์ด์ฝ์ ์ ๋ฌ๋ฐ์ ๋ ๋๋ง ํ๋ ์ญํ ๋ง ์ํํ๊ฒ ๋๋ค.
์์ด์ฝ ์์ฑ ๋์ ์กฐํฉํ๊ธฐ
์๋ฅผ ๋ค์ด, ๋ฒํผ์์ ์์ด์ฝ์ ์ฌ์ฉํ ๋, ๋ฒํผ์ size๋ appearance์ ๋ฐ๋ผ ์์ด์ฝ์ ์์์ด๋ ํฌ๊ธฐ๊ฐ ๋ฌ๋ผ์ ธ์ผ ํ๋ค๋ฉด Elements as Props ํจํด์ ์ฌ์ฉํ์ฌ ๊ธฐ๋ณธ ์์ฑ๊ฐ์ ์ค์ ํ๊ณ , ์ด๋ฅผ ๋์ ์ผ๋ก ์์ด์ฝ์ ์ ๋ฌํ ์ ์๋ค.
const Button = ({ appearance, size, icon }) => {
const defaultIconProps = {
size: size === 'large' ? 'large' : 'medium',
color: appearance === 'primary' ? 'white' : 'black',
};
const newProps = {
...defaultIconProps,
// ์ ๋ฌ๋ ์์ด์ฝ props ์ ์ฐ์ ์์๋ฅผ ๋๊ฒํจ
...icon.props,
};
const clonedIcon = React.cloneElement(icon, newProps);
return <button>Submit {clonedIcon}</button>;
};
์์ ๋ฐฉ์์ ์์ด์ฝ์ ๊ธฐ๋ณธ ์์ฑ๊ฐ์ ๊ด๋ฆฌํ๋ฉด์, ์ธ๋ถ์์ ์ ๋ฌ๋ ์์ฑ์ ๋ฎ์ด์ธ ์ ์๊ฒ ํด ์ค๋ค.
//๊ธฐ๋ณธ์ ์ผ๋ก secondary ๋ฒํผ์ ๊ฒ์ ์ ์์ด์ฝ์ ์ฌ์ฉํ๋๋ฐ, ์ธ๋ถ์์ color="red"๋ฅผ ์ ๋ฌํ์ฌ ๋นจ๊ฐ์ ์์ด์ฝ์ผ๋ก ๋ฎ์ด์ด๋ค.
<Button appearance="secondary" icon={<Loading color="red" />} />
Button์ appearance๋ size์ ๋ง์ถฐ ์์ด์ฝ์ ์์๊ณผ ํฌ๊ธฐ๋ฅผ ์๋์ผ๋ก ์กฐ์ ํ๊ณ , ์ถ๊ฐ์ ์ผ๋ก ์ธ๋ถ์์ ํน์ ์์ฑ์ ์ ๋ฌํ์ฌ ์ด๋ฅผ ์ค๋ฒ๋ผ์ด๋ํ ์ ์๋ค.
Ch4 Render Props
Elements as Props ํจํด์ ์ ์ฐํ ์ค์ ์์๋ ์ ์ฉํ๊ฒ ์ฌ์ฉํ ์ ์์ง๋ง, ์ปดํฌ๋ํธ๊ฐ ์ํ๋ฅผ ๊ด๋ฆฌํ๊ณ ๊ทธ ์ํ๋ฅผ ์์ ์ปดํฌ๋ํธ๋ก ์ ๋ฌํด์ผ ํ ๊ฒฝ์ฐ์๋ ๋ฌธ์ ๊ฐ ๋ฐ์๋๋ค. ์๋ฅผ ๋ค์ด, Button ์ปดํฌ๋ํธ๊ฐ isHovered ์ํ๋ฅผ ๊ด๋ฆฌํ๊ณ , ์ด ์ํ๋ฅผ ์์ด์ฝ ์ปดํฌ๋ํธ๋ก ์ ๋ฌํด์ผ ํ ๊ฒฝ์ฐ๋ฅผ ์๊ฐํด ๋ณด์.
const Button = ({ renderIcon }) => {
const [isHovered, setIsHovered] = useState(false);
return <button onMouseOver={() => setIsHovered(true)}>Submit {renderIcon()}</button>;
};
๋ฒํผ์ด isHovered ์ํ๋ฅผ ๊ด๋ฆฌํ๊ณ , ์ด๋ฅผ ์์ด์ฝ์ ์ ๋ฌํด์ผ ํ๋ค๋ฉด ์ด๋ป๊ฒ ํ ์ ์์๊น?
Render Props
Render Props ํจํด์ ์ฌ์ฉํ๋ฉด, ์ํ๋ฅผ ํจ์๋ก ์ ๋ฌํ๊ณ ๊ทธ ํจ์๊ฐ ์์๋ฅผ ๋ฐํํ๊ฒ ํ ์ ์๋ค. ์ฆ ์ํ๋ฅผ ์ปดํฌ๋ํธ ์ธ๋ถ์์ ์ฒ๋ฆฌํ๋ฉด์๋, ๋ด๋ถ์์ ํ์ํ ๋ฐ์ดํฐ๋ฅผ ์์ ์ปดํฌ๋ํธ์ ์ ๋ฌํ ์ ์๊ฒ ๋๋ค.
const Button = ({ renderIcon }) => {
const [isHovered, setIsHovered] = useState(false);
const iconProps = { isHovered };
return <button onMouseOver={() => setIsHovered(true)}>Submit {renderIcon(iconProps)}</button>;
};
์ด์ renderIcon
ํจ์๋ iconProps
๋ฅผ ๋ฐ์์ ํด๋น ์ํ์ ๋ฐ๋ผ ์์ด์ฝ์ ๋ ๋๋ง ํ๊ฒ ๋๋ค.
<Button renderIcon={(props) => <HomeIcon {...props} />} />
<Button renderIcon={(props) => (
<HomeIcon {...props} size="large" color="red" />
)} />
<Button renderIcon={(props) => (
<HomeIcon fontSize={props.size} style={{ color: props.color }} />
)} />
์ด ํจํด์ ์ฅ์ ์ ์ํ์ props๋ฅผ ๋ช ํํ๊ฒ ์ ๋ฌํ๊ณ , ์ํ๊ฐ ๋ฐ๋ ๋๋ง๋ค ๊ทธ ์ํ๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ์์ด์ฝ์ด ์ ๋ฐ์ดํธ๋๊ธฐ ๋๋ฌธ์ ๋ช ์์ ์ด๊ณ ์ง๊ด์ ์ด๋ค.
Stateful Logic ๊ณต์ ํ๊ธฐ
Render Props ํจํด์ ์ํ๋ฅผ ์ฌ๋ฌ ์ปดํฌ๋ํธ ๊ฐ์ ๊ณต์ ํ ๋ ์ ์ฉํ๋ค. ์๋ฅผ ๋ค์ด, ResizeDetector๋ผ๋ ์ปดํฌ๋ํธ๊ฐ ํ๋ฉด์ ๋๋น๋ฅผ ์ถ์ ํด์ผ ํ๋ค๊ณ ๊ฐ์ ํด ๋ณด์.
const ResizeDetector = ({ children }) => {
const [width, setWidth] = useState(0);
useEffect(() => {
const listener = () => setWidth(window.innerWidth);
window.addEventListener("resize", listener);
return () => window.removeEventListener("resize", listener);
}, []);
return children(width);
};
ResizeDetector๋ ํ๋ฉด์ ๋๋น๋ฅผ ์ถ์ ํ๊ณ , ์ด๋ฅผ ์์ ์ปดํฌ๋ํธ๋ก ์ ๋ฌํ๋ค. ์์ ์ปดํฌ๋ํธ๋ ์ด ๊ฐ์ ๋ฐ์์ ์ํ๋ ๋๋ก ๋ ์ด์์์ ๋ณ๊ฒฝํ ์ ์๋ค.
<ResizeDetector>
{(width) => width > 600 ? <WideLayout /> : <NarrowLayout />}
</ResizeDetector>
Hooks๋ก ๋์ฒด๋ Render Props
ํ์ง๋ง ์ต๊ทผ์๋ Hooks๊ฐ ์ํ ๊ณต์ ์ ์ปดํฌ๋ํธ ๊ฐ ๋ฐ์ดํฐ ์ ๋ฌ์ ํจ์ฌ ๋ ๊ฐ๋จํ๊ฒ ์ฒ๋ฆฌํ ์ ์๊ฒ ๋ง๋ค์ด ์ฃผ์๋ค. ResizeDetector
๊ฐ์ ์ปดํฌ๋ํธ๋ฅผ Hook์ผ๋ก ๋ณํํ๋ฉด, ์ฝ๋๊ฐ ํจ์ฌ ๊ฐ๊ฒฐํ๊ณ ๋ช
ํํด์ง๋ค!
const useResizeDetector = () => {
const [width, setWidth] = useState(0);
useEffect(() => {
const listener = () => setWidth(window.innerWidth);
window.addEventListener("resize", listener);
return () => window.removeEventListener("resize", listener);
}, []);
return width;
};
๐ฅ ๊ฒฐ๋ก : ์ ์ ํ ํจํด ์ ํํ๊ธฐ
โ Elements as Props ํจํด์ ์ปดํฌ๋ํธ๋ฅผ ์ ์ฐํ๊ฒ ๊ตฌ์ฑํ ์ ์๋ ๊ฐ๋จํ ๋ฐฉ๋ฒ์ด๋ค. ์ฃผ๋ก ์ํ ๊ด๋ฆฌ๋ ๋ ๋๋ง ๋ก์ง์ด ๋ณต์กํ์ง ์์ ๊ฒฝ์ฐ์ ์ ์ฉํ๋ฉฐ, ์์ด์ฝ๊ณผ ๊ฐ์ ์์๋ฅผ ์ ๋ฌํ๊ณ , ๊ทธ ์์์ ์์ฑ์ ๋์ ์ผ๋ก ์ค์ ํ๋ ๋ฐ ์ ์ฉํ๋ค.
โ Render Props ํจํด์ ์ํ๋ฅผ ๋ค๋ฅธ ์ปดํฌ๋ํธ์ ๊ณต์ ํ ๋ ์ ์ฉํ๋ฉฐ, ์ํ๋ฅผ ๋ช ์์ ์ผ๋ก ์ ๋ฌํ ์ ์๋ค. ํ์ง๋ง Hooks๊ฐ ๋๋ถ๋ถ์ ๊ฒฝ์ฐ Render Props๋ฅผ ๋์ฒดํ ์ ์๊ธฐ ๋๋ฌธ์, React์ ์ต์ ๋ฒ์ ์์๋ Render Props ํจํด๋ณด๋ค๋ Hooks๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ด ์ผ๋ฐ์ ์ด๋ผ๊ณ ํ๋ค.
โ ๊ฐ ํจํด์ ์ํฉ์ ๋ฐ๋ผ ์ ์ฉํ๊ฒ ํ์ฉ๋ ์ ์๋ค. ์ปดํฌ๋ํธ๋ฅผ ํจ์จ์ ์ผ๋ก ์ค๊ณํ๊ณ ์ํ๋ฅผ ๊ด๋ฆฌํ๋ ๋ฐฉ๋ฒ์ ์ ์ ํํ๋ ๊ฒ์ด ์ค์ํ๋ค!