๐Ÿ“š Books/๐Ÿ“• Advanced React

[Ch3 - Ch4] Elements as Props์™€ Render Props

nalong 2025. 2. 2. 20:55

๐Ÿ“š 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๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ผ๋ฐ˜์ ์ด๋ผ๊ณ  ํ•œ๋‹ค.

 

โœ… ๊ฐ ํŒจํ„ด์€ ์ƒํ™ฉ์— ๋”ฐ๋ผ ์œ ์šฉํ•˜๊ฒŒ ํ™œ์šฉ๋  ์ˆ˜ ์žˆ๋‹ค. ์ปดํฌ๋„ŒํŠธ๋ฅผ ํšจ์œจ์ ์œผ๋กœ ์„ค๊ณ„ํ•˜๊ณ  ์ƒํƒœ๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์ž˜ ์„ ํƒํ•˜๋Š” ๊ฒƒ์ด ์ค‘์š”ํ•˜๋‹ค!