
import { useState } from "react";
import "./styles.css";
export default function App() {
const [selectedTerms, setSelectedTerms] = useState<Set<number>>(new Set());
const terms = [
{ text: "약관 1", id: 1 },
{ text: "약관 2", id: 2 },
{ text: "약관 3", id: 3 },
{ text: "약관 4", id: 4 },
{ text: "약관 5", id: 5 },
];
function handleOnClick(id: number) {
const newTerms = new Set(selectedTerms);
if (selectedTerms.has(id)) {
newTerms.delete(id);
} else {
newTerms.add(id);
}
setSelectedTerms(newTerms);
}
return (
<div className="App">
<h1>약관 동의 UI</h1>
<div className="container">
{terms.map((item) => (
<div className="termbox">
<div
className={
selectedTerms.has(item.id) ? "checkbox-checked" : "checkbox"
}
onClick={() => handleOnClick(item.id)}
></div>
<p>{item.text}</p>
</div>
))}
</div>
</div>
);
}
selectedTerms 라는 상태값은 Set 객체 형태로 되어있다.handleOnClick이 실행된다. 새로운 newTerms 라는 복제 Set을 하나 만든다.selectedTerms에 파라미터로 넘어온 id 값이 있다면 삭제하고, 없다면 추가한다. 그리고 newTerms 로 상태값을 업데이트한다.selectedTerms 를 JSX 에 map 메소드를 이용해 나열한다.
import { useState } from "react";
import TagComponent from "./TagComponent";
import "./styles.css";
type tagDataType = {
text: string;
id: number;
};
export default function App() {
const tagData = [
{ text: "Tag1", id: 1 },
{ text: "Tag2", id: 2 },
{ text: "Tag3", id: 3 },
{ text: "Tag4", id: 4 },
{ text: "Tag5", id: 5 },
{ text: "Tag6", id: 6 },
{ text: "Tag7", id: 7 },
{ text: "Tag8", id: 8 },
];
const [selectedTags, setSelectedTags] = useState<Set<number>>(new Set());
const filteredTags = tagData.filter((item) => selectedTags.has(item.id));
//useMemo 사용하여 개선 가능
function handleOnClick(id: number) {
const items = new Set(selectedTags);
if (selectedTags.has(id)) {
items.delete(id);
} else {
items.add(id);
}
setSelectedTags(items);
}
return (
<div className="App">
<h1>Set UI</h1>
<h2>Selected Tags</h2>
<div className="tagList">
{filteredTags.map((item) => (
<div className={selectedTags.has(item.id) ? "tag-selected" : "tag"}>
{item.text}
</div>
))}
</div>
<h2>All Tags</h2>
<div className="tagList">
{tagData.map((item) => (
<div
className={selectedTags.has(item.id) ? "tag-selected" : "tag"}
onClick={() => handleOnClick(item.id)}
>
{item.text}
</div>
))}
</div>
</div>
);
}
filteredTags 라는 배열을 새로이 만들어서 map 메소드를 이용해 나열하고 있다.export default function App() {
//생략
const [selectedTags, setSelectedTags] = useState<Set<number>>(new Set());
const [trigger, setTrigger] = useState<boolean>(false); //새로운 상태값
const filteredTags = tagData.filter((item) => selectedTags.has(item.id));
function handleButtonClick() {
setTrigger((prev) => !prev);
}
function handleOnClick(id: number) {
//생략
}
return (
<div className="App">
<h1>Set UI</h1>
<button onClick={handleButtonClick}>클릭</button> //추가된 라인
<h2>Selected Tags</h2>
<div className="tagList">
{filteredTags.map((item) => (
<div className={selectedTags.has(item.id) ? "tag-selected" : "tag"}>
{item.text}
</div>
))}
</div>
<h2>All Tags</h2>
<div className="tagList">
{tagData.map((item) => (
<div
className={selectedTags.has(item.id) ? "tag-selected" : "tag"}
onClick={() => handleOnClick(item.id)}
>
{item.text}
</div>
))}
</div>
</div>
);
}
setTrigger 가 발생할 때마다 컴포넌트 전체가 다시 렌더링 되면서, const filteredTags = tagData.filter((item) => selectedTags.has(item.id)) 라인이 다시 실행되며 필터 메소드가 tagData 를 처음부터 끝까지 한 번 순회할 것이다.
하지만 태그 선택을 새로이 하지 않았음에도 이 라인이 실행되는 것은 불필요하다.
➡️ 이럴 경우, useMemo 로 코드를 개선할 수 있다
useMemo 는 컴포넌트가 처음 렌더링이 될 때 계산된 값을 메모리에 저장하여(Memoization), 이후 컴포넌트가 다시 렌더링 될 때 저장된 값을 불러온다. 따라서 useMemo 를 잘 활용한다면 불필요한 재계산을 줄여 낭비를 방지할 수 있다.
🚨 주의할 점
useMemo가 굳이 필요하지 않을 때에도 데이터를 memoize 하게 되면 오히려 성능이 더 떨어질 수 있으므로, 필요에 따른 적절한 사용이 요구된다.
import { useMemo, useState } from "react";
import TagComponent from "./TagComponent";
import "./styles.css";
type tagDataType = {
text: string;
id: number;
};
export default function App() {
const tagData = [
{ text: "Tag1", id: 1 },
{ text: "Tag2", id: 2 },
{ text: "Tag3", id: 3 },
{ text: "Tag4", id: 4 },
{ text: "Tag5", id: 5 },
{ text: "Tag6", id: 6 },
{ text: "Tag7", id: 7 },
{ text: "Tag8", id: 8 },
];
const [selectedTags, setSelectedTags] = useState<Set<number>>(new Set());
const [trigger, setTrigger] = useState<boolean>(false);
const filteredTags = useMemo(
() => tagData.filter((item) => selectedTags.has(item.id)),
[selectedTags]
); //selectedTags 가 변경될 때에만 filter 메소드가 실행되도록 memoization
function handleButtonClick() {
setTrigger((prev) => !prev);
}
function handleOnClick(id: number) {
const items = new Set(selectedTags);
if (selectedTags.has(id)) {
items.delete(id);
} else {
items.add(id);
}
setSelectedTags(items);
}
return (
<div className="App">
<h1>Set UI</h1>
<button onClick={handleButtonClick}>클릭</button>
<h2>Selected Tags</h2>
<div className="tagList">
{filteredTags.map((item) => (
<div className={selectedTags.has(item.id) ? "tag-selected" : "tag"}>
{item.text}
</div>
))}
</div>
<h2>All Tags</h2>
<div className="tagList">
{tagData.map((item) => (
<div
className={selectedTags.has(item.id) ? "tag-selected" : "tag"}
onClick={() => handleOnClick(item.id)}
>
{item.text}
</div>
))}
</div>
</div>
);
}