++ 리액트를 더 잘 쓰기 위한 정리 시리즈
리액트의 큰 장점 중 하나는 컴포넌트의 재사용성이다. 같은 내용의 코드를 반복하지 않고도 커스텀 컴포넌트를 내장된 요소들처럼 사용할 수 있다.
하지만 이런 컴포넌트를 얼마나 쪼개서 써야 좋을까?
만약 App 컴포넌트 안에 여러 컴포넌트들을 다 둔다면 전체 코드 길이도 길어지고, 가독성도 떨어진다. 더 중요한 것은 예상치 못한 side effect가 발생할 수 있다.
예를 들어, 아래 코드처럼 topic을 useState 훅으로 관리할 경우 선택한 탭이 바뀌면 App 컴포넌트 전체가 리렌더링 된다.
변경될 필요가 없는 부분까지 영향을 주는 것이다. 이런 상황을 방지하기 위해서라도 탭과 관련된 부분을 쪼개보자!
import { useState } from 'react';
function App() {
return (
<div>
<Header />
<main>
<section id="core-concepts">
<h2>Core Concepts</h2>
<ul>
{CORE_CONCEPTS.map((conceptItem) => (
<CoreConcept key={conceptItem.title} {...conceptItem} />
))}
</ul>
</section>
<section id="examples">
<h2>Examples</h2>
<menu>
<TabButton
isSelected={selectedTopic === 'components'}
onSelect={() => handleSelect('components')}
>
Components
</TabButton>
<TabButton
isSelected={selectedTopic === 'jsx'}
onSelect={() => handleSelect('jsx')}
>
JSX
</TabButton>
... 생략
</menu>
{tabContent}
</section>
</main>
</div>
);
}
export default App;
App 컴포넌트에서 설명 부분인 CoreConcepts와 탭과 관련된 내용인 Examples라는 컴포넌트로 분리했다. 그 결과 App 컴포넌트는 엄청 간단해졌고, 더이상 탭을 변경해도 앱 전체에 영향을 주지 않는다!
App.jsx
import Header from "./components/Header/Header.jsx";
import Examples from "./components/Examples.jsx";
import CoreConcepts from "./components/CoreConcepts.jsx";
function App() {
return (
<div>
<Header />
<main>
<CoreConcepts />
<Examples />
</main>
</div>
);
}
export default App;
CoreConcepts.jsx
import { CORE_CONCEPTS } from "../data.js";
import CoreConcept from "./CoreConcept.jsx";
export default function CoreConcepts() {
return (
<section id="core-concepts">
<h2>Core Concepts</h2>
<ul>
{CORE_CONCEPTS.map((conceptItem) => (
<CoreConcept key={conceptItem.title} {...conceptItem} />
))}
</ul>
</section>
);
}
Examples.jsx
import { useState } from "react";
import TabButton from "./TabButton.jsx";
import { EXAMPLES } from "../data.js";
export default function Examples() {
const [selectedTopic, setSelectedTopic] = useState();
function handleSelect(selectedButton) {
// selectedButton => 'components', 'jsx', 'props', 'state'
setSelectedTopic(selectedButton);
// console.log(selectedTopic);
}
console.log("APP COMPONENT EXECUTING");
let tabContent = <p>Please select a topic.</p>;
if (selectedTopic) {
tabContent = (
<div id="tab-content">
<h3>{EXAMPLES[selectedTopic].title}</h3>
<p>{EXAMPLES[selectedTopic].description}</p>
<pre>
<code>{EXAMPLES[selectedTopic].code}</code>
</pre>
</div>
);
}
return (
<section id="examples">
<h2>Examples</h2>
<menu>
<TabButton
isSelected={selectedTopic === "components"}
onSelect={() => handleSelect("components")}
>
Components
</TabButton>
<TabButton
isSelected={selectedTopic === "jsx"}
onSelect={() => handleSelect("jsx")}
>
JSX
</TabButton>
... 생략
</menu>
{tabContent}
</section>
);
}
Examples과 CoreConcepts 컴포넌트를 보면 <section /> 안에 title & content 크게 두 개의 내용을 담는 구조다. 이런 구조는 흔히 볼 수 있기 때문에 구조 자체를 컴포넌트화하는 것도 재사용성을 높일 수 있다.
따라서 아래 코드처럼 <Section> 컴포너트를 만들 수 있다.
export default function Section({ title, children, ...props }) {
return (
<section {...props}>
<h2>{title}</h2>
{children}
</section>
);
}
이 때, title, children을 각각 props로 받았는데, 나머지 항목을 스프레드 형식으로 props를 가져온 이유가 있다.
만약 저 Section 컴포넌트를 적용할 경우 각각 다른 id, className, event 같은 내장된 속성들을 사용할 수 있다. 하지만 많은 built-in 속성들을 하나하나 props 값으로 적어준다면 너무 길어져 가독성이 떨어질 것이다.
그래서 내장된 속성들을 한데로 모아 section 요소에 속성들을 뿌려주면 된다!
Examples.jsx
import TabButtons from "./TabButtons.jsx";
import Section from "./Section.jsx";
export default function Examples() {
let tabContent = <p>Please select a topic.</p>;
return (
<Section id="examples" title="Examples">
<TabButtons Tab="menu">{tabContent}</TabButtons>
</Section>
);
}
Section 컴포넌트를 Examples에 적용해보면 위 코드처럼 쓸 수 있다.
CoreConcepts 컴포넌트도 마찬가지다!
CoreConcepts.jsx
import { CORE_CONCEPTS } from "../data.js";
import CoreConcept from "./CoreConcept.jsx";
import Section from "./Section.jsx";
export default function CoreConcepts() {
return (
<Section id="core-concepts" title="Core Concepts">
<ul>
{CORE_CONCEPTS.map((conceptItem) => (
<CoreConcept key={conceptItem.title} {...conceptItem} />
))}
</ul>
</Section>
);
}
강의를 보면서, 저렇게까지 컴포넌트를 쪼개어 사용하는 이유가 잘 와닿지 않았었는데 블로그에 내용을 정리하면서 이해할 수 있었다👍
📌 불필요한 컴포넌트의 업데이트를 최소화하고 컴포넌트 자체의 독립성을 갖춘다.
📌 비슷한 구조의 작업들을 효율적으로 재사용성을 높이고, 일관성 있게 코드를 짤 수 있다.
막상 공부하면 이해가 됐지만 프로젝트에 직접 적용하는 것은 또 다른 것 같다..
프로젝트 규모가 클수록 이러한 컴포넌트의 장점을 적극적으로 쓰려면 작은 규모부터 하나씩 쪼개보는 연습을 할 필요가 있어보인다.
너무 좋은 글입니다!
생각보다 많은 사람들이 컴포넌트를 쪼개지 않아요 ㅠㅠ
하지만 렌더링에 대해 잘 이해를 하고, 코드의 가독성까지 챙기려면 컴포넌트를 많이 분리하는게 좋다고 생각합니다.