์ฃผ์˜ํ•˜์„ธ์š”!

cloneElement๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์€ ํ”ํ•˜์ง€ ์•Š์œผ๋ฉฐ, ๋ถˆ์•ˆ์ •ํ•œ ์ฝ”๋“œ๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ผ๋ฐ˜์ ์ธ ๋Œ€์•ˆ์„ ํ™•์ธํ•˜์„ธ์š”.

cloneElement๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด element๋ฅผ ๊ธฐ์ค€์œผ๋กœ ์ƒˆ๋กœ์šด React ์—˜๋ฆฌ๋จผํŠธ๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

const clonedElement = cloneElement(element, props, ...children)

๋ ˆํผ๋Ÿฐ์Šค

cloneElement(element, props, ...children)

์ƒˆ๋กœ์šด React ์—˜๋ฆฌ๋จผํŠธ๋ฅผ ๋งŒ๋“ค๊ธฐ ์œ„ํ•ด element๋ฅผ ๊ธฐ์ค€์œผ๋กœ ํ•˜๊ณ , props์™€ children์„ ๋‹ค๋ฅด๊ฒŒ ํ•˜์—ฌ cloneElement๋ฅผ ํ˜ธ์ถœํ•˜์„ธ์š”.

import { cloneElement } from 'react';

// ...
const clonedElement = cloneElement(
<Row title="Cabbage">
Hello
</Row>,
{ isHighlighted: true },
'Goodbye'
);

console.log(clonedElement); // <Row title="Cabbage">Goodbye</Row>

์•„๋ž˜์—์„œ ๋” ๋งŽ์€ ์˜ˆ์ œ๋ฅผ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋งค๊ฐœ๋ณ€์ˆ˜

  • element: element ์ธ์ž๋Š” ์œ ํšจํ•œ React ์—˜๋ฆฌ๋จผํŠธ์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, <Something />๊ณผ ๊ฐ™์€ JSX ๋…ธ๋“œ, createElement๋กœ ํ˜ธ์ถœํ•ด ์–ป์€ ๊ฒฐ๊ณผ๋ฌผ ๋˜๋Š” ๋‹ค๋ฅธ cloneElement๋กœ ํ˜ธ์ถœํ•ด ์–ป์€ ๊ฒฐ๊ณผ๋ฌผ์ด ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  • props: props ์ธ์ž๋Š” ๊ฐ์ฒด ๋˜๋Š” null์ด์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. null์„ ์ „๋‹ฌํ•˜๋ฉด ๋ณต์ œ๋œ ์—˜๋ฆฌ๋จผํŠธ๋Š” ์›๋ณธ element.props๋ฅผ ๋ชจ๋‘ ์œ ์ง€ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ ‡์ง€ ์•Š์œผ๋ฉด props ๊ฐ์ฒด์˜ ๊ฐ prop์— ๋Œ€ํ•ด ๋ฐ˜ํ™˜๋œ ์—˜๋ฆฌ๋จผํŠธ๋Š” element.props์˜ ๊ฐ’๋ณด๋‹ค props์˜ ๊ฐ’์„ โ€œ์šฐ์„ โ€ํ•ฉ๋‹ˆ๋‹ค. ๋‚˜๋จธ์ง€ props๋Š” ์›๋ณธ element.props์—์„œ ์ฑ„์›Œ์ง‘๋‹ˆ๋‹ค. props.key ๋˜๋Š” props.ref๋ฅผ ์ „๋‹ฌํ•˜๋ฉด ์›๋ณธ์˜ ๊ฒƒ์„ ๋Œ€์ฒดํ•ฉ๋‹ˆ๋‹ค.

  • (์„ ํƒ์‚ฌํ•ญ) ...children: 0๊ฐœ ์ด์ƒ์˜ ์ž์‹ ๋…ธ๋“œ๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. React ์—˜๋ฆฌ๋จผํŠธ, ๋ฌธ์ž์—ด, ์ˆซ์ž, portals, ๋นˆ ๋…ธ๋“œ (null, undefined, true, false) ๋ฐ React ๋…ธ๋“œ ๋ฐฐ์—ด์„ ํฌํ•จํ•œ ๋ชจ๋“  React ๋…ธ๋“œ๊ฐ€ ํ•ด๋‹นํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ...children ์ธ์ž๋ฅผ ์ „๋‹ฌํ•˜์ง€ ์•Š์œผ๋ฉด ์›๋ณธ element.props.children์ด ์œ ์ง€๋ฉ๋‹ˆ๋‹ค.

๋ฐ˜ํ™˜๊ฐ’

cloneElement๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ํ”„๋กœํผํ‹ฐ๋ฅผ ๊ฐ€์ง„ React ์—˜๋ฆฌ๋จผํŠธ ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

  • type: element.type๊ณผ ๋™์ผํ•ฉ๋‹ˆ๋‹ค.
  • props: element.props์™€ ์ „๋‹ฌํ•œ props๋ฅผ ์–•๊ฒŒ ๋ณ‘ํ•ฉํ•œ ๊ฒฐ๊ณผ์ž…๋‹ˆ๋‹ค.
  • ref: props.ref์— ์˜ํ•ด ์žฌ์ •์˜๋˜์ง€ ์•Š์€ ๊ฒฝ์šฐ ์›๋ณธ element.ref์ž…๋‹ˆ๋‹ค.
  • key: props.key์— ์˜ํ•ด ์žฌ์ •์˜๋˜์ง€ ์•Š์€ ๊ฒฝ์šฐ ์›๋ณธ element.key์ž…๋‹ˆ๋‹ค.

์ผ๋ฐ˜์ ์œผ๋กœ ์ปดํฌ๋„ŒํŠธ์—์„œ ์—˜๋ฆฌ๋จผํŠธ๋ฅผ ๋ฐ˜ํ™˜ํ•˜๊ฑฐ๋‚˜ ๋‹ค๋ฅธ ์—˜๋ฆฌ๋จผํŠธ์˜ ์ž์‹์œผ๋กœ ๋งŒ๋“ญ๋‹ˆ๋‹ค. ์—˜๋ฆฌ๋จผํŠธ์˜ ํ”„๋กœํผํ‹ฐ๋ฅผ ์ฝ์„ ์ˆ˜ ์žˆ์ง€๋งŒ, ์ƒ์„ฑ๋œ ํ›„์—๋Š” ๋ชจ๋“  ์—˜๋ฆฌ๋จผํŠธ์˜ ํ”„๋กœํผํ‹ฐ๋ฅผ ์ฝ์„ ์ˆ˜ ์—†๋Š” ๊ฒƒ์ฒ˜๋Ÿผ ์ทจ๊ธ‰ํ•˜๊ณ  ๋ Œ๋”๋งํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.

์ฃผ์˜

  • ์—˜๋ฆฌ๋จผํŠธ๋ฅผ ๋ณต์ œํ•ด๋„ ์›๋ณธ ์—˜๋ฆฌ๋จผํŠธ๋Š” ์ˆ˜์ •๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

  • ์ž์‹์ด ๋ชจ๋‘ ์ •์ ์ธ ๊ฒฝ์šฐ์—๋งŒ cloneElement(element, null, child1, child2, child3)์™€ ๊ฐ™์ด ์ž์‹์„ ์—ฌ๋Ÿฌ ๊ฐœ์˜ ์ธ์ž๋กœ ์ „๋‹ฌํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ž์‹์ด ๋™์ ์œผ๋กœ ์ƒ์„ฑ๋˜์—ˆ๋‹ค๋ฉด cloneElement(element, null, listItems)์™€ ๊ฐ™์ด ์ „์ฒด ๋ฐฐ์—ด์„ ์„ธ ๋ฒˆ์งธ ์ธ์ž๋กœ ์ „๋‹ฌํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด React๊ฐ€ ๋ชจ๋“  ๋™์  ๋ฆฌ์ŠคํŠธ์— ๋Œ€ํ•ด key๊ฐ€ ๋ˆ„๋ฝ๋˜์—ˆ๋‹ค๋Š” ๊ฒฝ๊ณ ๋ฅผ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค. ์ •์  ๋ฆฌ์ŠคํŠธ์˜ ๊ฒฝ์šฐ๋Š” ์ˆœ์„œ๊ฐ€ ๋ณ€๊ฒฝ๋˜์ง€ ์•Š์œผ๋ฏ€๋กœ ์ด ์ž‘์—…์€ ํ•„์š”ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

  • cloneElement๋Š” ๋ฐ์ดํ„ฐ ํ๋ฆ„์„ ์ถ”์ ํ•˜๊ธฐ ์–ด๋ ต๊ธฐ ๋•Œ๋ฌธ์— ๋‹ค์Œ ๋Œ€์•ˆ์„ ์‚ฌ์šฉํ•ด ๋ณด์„ธ์š”.


์‚ฌ์šฉ๋ฒ•

์—˜๋ฆฌ๋จผํŠธ์˜ props ์žฌ์ •์˜ํ•˜๊ธฐ

์ผ๋ถ€ React ์—˜๋ฆฌ๋จผํŠธ์˜ props๋ฅผ ์žฌ์ •์˜ํ•˜๋ ค๋ฉด ์žฌ์ •์˜ํ•˜๋ ค๋Š” props๋ฅผ cloneElement์— ์ „๋‹ฌํ•˜์„ธ์š”.

import { cloneElement } from 'react';

// ...
const clonedElement = cloneElement(
<Row title="Cabbage" />,
{ isHighlighted: true }
);

clonedElement์˜ ๊ฒฐ๊ณผ๋Š” <Row title="Cabbage" isHighlighted={true} />๊ฐ€ ๋ฉ๋‹ˆ๋‹ค.

์–ด๋–ค ๊ฒฝ์šฐ์— ์œ ์šฉํ•œ์ง€ ์˜ˆ์ œ๋ฅผ ํ†ตํ•ด ์•Œ์•„๋ณด๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

children์„ ์„ ํƒํ•  ์ˆ˜ ์žˆ๋Š” ํ–‰ ๋ชฉ๋ก์œผ๋กœ ๋ Œ๋”๋งํ•˜๊ณ , ์„ ํƒ๋œ ํ–‰์„ ๋ณ€๊ฒฝํ•˜๋Š” โ€œ๋‹ค์Œโ€ ๋ฒ„ํŠผ์ด ์žˆ๋Š” List ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ƒ์ƒํ•ด ๋ณด์„ธ์š”. List ์ปดํฌ๋„ŒํŠธ๋Š” ์„ ํƒ๋œ ํ–‰์„ ๋‹ค๋ฅด๊ฒŒ ๋ Œ๋”๋งํ•ด์•ผ ํ•˜๋ฏ€๋กœ ์ „๋‹ฌ๋ฐ›์€ ๋ชจ๋“  <Row> ์ž์‹ ์š”์†Œ๋ฅผ ๋ณต์ œํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  isHighlighted: true ๋˜๋Š” isHighlighted: false์ธ prop์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.

export default function List({ children }) {
const [selectedIndex, setSelectedIndex] = useState(0);
return (
<div className="List">
{Children.map(children, (child, index) =>
cloneElement(child, {
isHighlighted: index === selectedIndex
})
)}

๋‹ค์Œ๊ณผ ๊ฐ™์ด List์—์„œ ์ „๋‹ฌ๋ฐ›์€ ์›๋ณธ JSX๊ฐ€ ์žˆ๋‹ค๊ณ  ๊ฐ€์ •ํ•ฉ์‹œ๋‹ค.

<List>
<Row title="Cabbage" />
<Row title="Garlic" />
<Row title="Apple" />
</List>

์ž์‹ ์š”์†Œ๋ฅผ ๋ณต์ œํ•จ์œผ๋กœ์จ List๋Š” ๋ชจ๋“  Row ์•ˆ์— ์ถ”๊ฐ€์ ์ธ ์ •๋ณด๋ฅผ ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ฒฐ๊ณผ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

<List>
<Row
title="Cabbage"
isHighlighted={true}
/>
<Row
title="Garlic"
isHighlighted={false}
/>
<Row
title="Apple"
isHighlighted={false}
/>
</List>

โ€œ๋‹ค์Œโ€ ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๋ฉด List์˜ state๊ฐ€ ์—…๋ฐ์ดํŠธ๋˜๊ณ  ๋‹ค๋ฅธ ํ–‰์ด ํ•˜์ด๋ผ์ดํŠธ ํ‘œ์‹œ๊ฐ€ ๋˜๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

import { Children, cloneElement, useState } from 'react';

export default function List({ children }) {
  const [selectedIndex, setSelectedIndex] = useState(0);
  return (
    <div className="List">
      {Children.map(children, (child, index) =>
        cloneElement(child, {
          isHighlighted: index === selectedIndex 
        })
      )}
      <hr />
      <button onClick={() => {
        setSelectedIndex(i =>
          (i + 1) % Children.count(children)
        );
      }}>
        ๋‹ค์Œ
      </button>
    </div>
  );
}

์š”์•ฝํ•˜์ž๋ฉด, List๋Š” ์ „๋‹ฌ๋ฐ›์€ <Row /> ์—˜๋ฆฌ๋จผํŠธ๋ฅผ ๋ณต์ œํ•˜๊ณ  ์ถ”๊ฐ€๋กœ ๋“ค์–ด์˜ค๋Š” prop ๋˜ํ•œ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.

์ฃผ์˜ํ•˜์„ธ์š”!

์ž์‹ ์š”์†Œ๋ฅผ ๋ณต์ œํ•˜๋Š” ๊ฒƒ์€ ์•ฑ์—์„œ ๋ฐ์ดํ„ฐ๊ฐ€ ์–ด๋–ป๊ฒŒ ํ˜๋Ÿฌ๊ฐ€๋Š”์ง€ ํŒŒ์•…ํ•˜๊ธฐ ์–ด๋ ต๊ธฐ ๋•Œ๋ฌธ์— ๋‹ค์Œ ๋Œ€์•ˆ์„ ์‚ฌ์šฉํ•ด ๋ณด์„ธ์š”.


๋Œ€์•ˆ

render prop์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ์ „๋‹ฌํ•˜๊ธฐ

cloneElement๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋Œ€์‹ ์— renderItem๊ณผ ๊ฐ™์€ render prop์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์„ ๊ณ ๋ คํ•ด ๋ณด์„ธ์š”. ๋‹ค์Œ ์˜ˆ์ œ์˜ List๋Š” renderItem์„ prop์œผ๋กœ ๋ฐ›์Šต๋‹ˆ๋‹ค. List๋Š” ๋ชจ๋“  item์— ๋Œ€ํ•ด renderItem์„ ํ˜ธ์ถœํ•˜๊ณ  isHighlighted๋ฅผ ์ธ์ž๋กœ ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค.

export default function List({ items, renderItem }) {
const [selectedIndex, setSelectedIndex] = useState(0);
return (
<div className="List">
{items.map((item, index) => {
const isHighlighted = index === selectedIndex;
return renderItem(item, isHighlighted);
})}

renderItem prop์€ ๋ Œ๋”๋ง ๋ฐฉ๋ฒ•์„ ์ง€์ •ํ•˜๋Š” prop์ด๊ธฐ ๋•Œ๋ฌธ์— โ€œrender propโ€์ด๋ผ๊ณ  ๋ถˆ๋ฆฝ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ์ฃผ์–ด์ง„ isHighlighted ๊ฐ’์œผ๋กœ <Row>๋ฅผ ๋ Œ๋”๋งํ•˜๋Š” renderItem์„ ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

<List
items={products}
renderItem={(product, isHighlighted) =>
<Row
key={product.id}
title={product.title}
isHighlighted={isHighlighted}
/>
}
/>

์ตœ์ข…์ ์œผ๋กœ cloneElement์™€ ๊ฐ™์€ ๊ฒฐ๊ณผ๊ฐ€ ๋ฉ๋‹ˆ๋‹ค.

<List>
<Row
title="Cabbage"
isHighlighted={true}
/>
<Row
title="Garlic"
isHighlighted={false}
/>
<Row
title="Apple"
isHighlighted={false}
/>
</List>

ํ•˜์ง€๋งŒ isHighlighted ๊ฐ’์˜ ์ถœ์ฒ˜๋ฅผ ๋ช…ํ™•ํ•˜๊ฒŒ ์ถ”์ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

import { useState } from 'react';

export default function List({ items, renderItem }) {
  const [selectedIndex, setSelectedIndex] = useState(0);
  return (
    <div className="List">
      {items.map((item, index) => {
        const isHighlighted = index === selectedIndex;
        return renderItem(item, isHighlighted);
      })}
      <hr />
      <button onClick={() => {
        setSelectedIndex(i =>
          (i + 1) % items.length
        );
      }}>
        ๋‹ค์Œ
      </button>
    </div>
  );
}

์ด๋Ÿฌํ•œ ํŒจํ„ด์€ ๋” ๋ช…์‹œ์ ์ด๊ธฐ ๋•Œ๋ฌธ์— cloneElement ๋ณด๋‹ค ์„ ํ˜ธ๋ฉ๋‹ˆ๋‹ค.


Context๋ฅผ ํ†ตํ•ด ๋ฐ์ดํ„ฐ ์ „๋‹ฌํ•˜๊ธฐ

cloneElement์˜ ๋˜ ๋‹ค๋ฅธ ๋Œ€์•ˆ์œผ๋กœ๋Š” Context๋ฅผ ํ†ตํ•ด ๋ฐ์ดํ„ฐ๋ฅผ ์ „๋‹ฌํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

์˜ˆ๋ฅผ ๋“ค์–ด, createContext๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ HighlightContext๋ฅผ ์ •์˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

export const HighlightContext = createContext(false);

List ์ปดํฌ๋„ŒํŠธ๋Š” ๋ Œ๋”๋งํ•˜๋Š” ๋ชจ๋“  item์„ HighlightContext.Provider๋กœ ๊ฐ์Œ€ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

export default function List({ items, renderItem }) {
const [selectedIndex, setSelectedIndex] = useState(0);
return (
<div className="List">
{items.map((item, index) => {
const isHighlighted = index === selectedIndex;
return (
<HighlightContext.Provider key={item.id} value={isHighlighted}>
{renderItem(item)}
</HighlightContext.Provider>
);
})}

์ด๋Ÿฌํ•œ ์ ‘๊ทผ ๋ฐฉ์‹์œผ๋กœ ์ธํ•ด Row๋Š” isHighlighted prop์„ ๋ฐ›์„ ํ•„์š”๊ฐ€ ์—†์–ด์ง‘๋‹ˆ๋‹ค. ๋Œ€์‹  context๋ฅผ ์ฝ์Šต๋‹ˆ๋‹ค.

export default function Row({ title }) {
const isHighlighted = useContext(HighlightContext);
// ...

์ด์— ๋”ฐ๋ผ isHighlighted๋ฅผ <Row>๋กœ ์ „๋‹ฌํ•˜๋Š” ๊ฒƒ์— ๋Œ€ํ•ด ํ˜ธ์ถœ๋œ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์•Œ๊ฑฐ๋‚˜ ๊ฑฑ์ •ํ•˜์ง€ ์•Š์•„๋„ ๋ฉ๋‹ˆ๋‹ค.

<List
items={products}
renderItem={product =>
<Row title={product.title} />
}
/>

๋Œ€์‹ ์— List์™€ Row๋Š” context๋ฅผ ํ†ตํ•ด ํ•˜์ด๋ผ์ดํŒ… ๋กœ์ง์„ ์กฐ์ •ํ•ฉ๋‹ˆ๋‹ค.

import { useState } from 'react';
import { HighlightContext } from './HighlightContext.js';

export default function List({ items, renderItem }) {
  const [selectedIndex, setSelectedIndex] = useState(0);
  return (
    <div className="List">
      {items.map((item, index) => {
        const isHighlighted = index === selectedIndex;
        return (
          <HighlightContext.Provider
            key={item.id}
            value={isHighlighted}
          >
            {renderItem(item)}
          </HighlightContext.Provider>
        );
      })}
      <hr />
      <button onClick={() => {
        setSelectedIndex(i =>
          (i + 1) % items.length
        );
      }}>
        ๋‹ค์Œ
      </button>
    </div>
  );
}

context๋ฅผ ํ†ตํ•ด ๋ฐ์ดํ„ฐ๋ฅผ ์ „๋‹ฌํ•˜๋Š” ๊ฒƒ์— ๋Œ€ํ•˜์—ฌ ์ž์„ธํžˆ ์•Œ์•„๋ณด์„ธ์š”.


Custom Hook์œผ๋กœ ๋กœ์ง ์ถ”์ถœํ•˜๊ธฐ

๋‹ค๋ฅธ ์ ‘๊ทผ ๋ฐฉ์‹์œผ๋กœ๋Š” ์ž์ฒด hook์„ ํ†ตํ•ด โ€œ๋น„์‹œ๊ฐ์ ์ธโ€ ๋กœ์ง์„ ์ถ”์ถœํ•˜๋Š” ๊ฒƒ์„ ์‹œ๋„ํ•ด ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  hook์— ์˜ํ•ด์„œ ๋ฐ˜ํ™˜๋œ ์ •๋ณด๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ Œ๋”๋งํ•  ๋‚ด์šฉ์„ ์ •ํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด ๋‹ค์Œ๊ณผ ๊ฐ™์ด useList ๊ฐ™์€ custom hook์„ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

import { useState } from 'react';

export default function useList(items) {
const [selectedIndex, setSelectedIndex] = useState(0);

function onNext() {
setSelectedIndex(i =>
(i + 1) % items.length
);
}

const selected = items[selectedIndex];
return [selected, onNext];
}

๊ทธ๋Ÿฌ๋ฏ€๋กœ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

export default function App() {
const [selected, onNext] = useList(products);
return (
<div className="List">
{products.map(product =>
<Row
key={product.id}
title={product.title}
isHighlighted={selected === product}
/>
)}
<hr />
<button onClick={onNext}>
๋‹ค์Œ
</button>
</div>
);
}

๋ฐ์ดํ„ฐ ํ๋ฆ„์€ ๋ช…์‹œ์ ์ด์ง€๋งŒ state๋Š” ๋ชจ๋“  ์ปดํฌ๋„ŒํŠธ์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” useList custom hook ๋‚ด๋ถ€์— ์žˆ์Šต๋‹ˆ๋‹ค.

import Row from './Row.js';
import useList from './useList.js';
import { products } from './data.js';

export default function App() {
  const [selected, onNext] = useList(products);
  return (
    <div className="List">
      {products.map(product =>
        <Row
          key={product.id}
          title={product.title}
          isHighlighted={selected === product}
        />
      )}
      <hr />
      <button onClick={onNext}>
        ๋‹ค์Œ
      </button>
    </div>
  );
}

์ด๋Ÿฌํ•œ ์ ‘๊ทผ ๋ฐฉ์‹์€ ๋‹ค๋ฅธ ์ปดํฌ๋„ŒํŠธ ๊ฐ„์— ํ•ด๋‹น ๋กœ์ง์„ ์žฌ์‚ฌ์šฉํ•˜๊ณ  ์‹ถ์„ ๋•Œ ํŠนํžˆ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค.