react-router가 업그레이드 되는 바람에,
기존 코드의 import가 문제되어 끝내 수정하는 수고를 하고 만당.
아니 또 누군가는 Tree 컴포넌트를 쓰고 싶단다.
jstree는 jquery가 필요한 jquery 플러그인이닝, React를 쓴다면 과감없잉 버릴깡?
rc-tree란 게 있당. 중국말 설명이 중국산인가 보당.
아래 npmjs에 가면 api가 테이블로 정리 되어 있당. 지금은 대략 눈치껏 확인 정도만...
https://www.npmjs.com/package/rc-tree
아래 링크 가면, 실행 예제와 소스를 볼 수 있어, 어쩔 또 올 수 밖에 없낭? 하는 느낌이당.
https://tree-react-component.vercel.app/demo/draggable
시작하면 절반이라 했던강... 밧다리 드러가장.
vite로 필요한 프로젝트 맹글었다 치공, 설치 들어가장.
npm i rc-tree
실행 모습을 보려면 어쩔! 더미 데이터가 필요하당.~~ ㅠㅠ
key, title, children 이 기본속성 값으로 필요하당.(복사/붙여넣기 하장장)
sampleData.js
const treeData = [
{
key: "E7E",
title: "DDIT 배움 즐김사",
children: [
{
key: "F41",
title: "F1 GIRLS",
children: [
{ key: "f41-1", title: "혜선" },
{ key: "f41-2", title: "시현" },
{ key: "f41-3", title: "승아" },
{ key: "f41-4", title: "민경" },
{ key: "f41-5", title: "예원" },
],
},
],
},
{
key: "SM",
title: "SM 엔터테인먼트",
children: [
{
key: "aespa",
title: "에스파",
children: [
{ key: "aespa-1", title: "닝닝" },
{ key: "aespa-2", title: "카리나" },
{ key: "aespa-3", title: "지젤" },
{ key: "aespa-4", title: "윈터" },
],
},
{
key: "redvelvet",
title: "레드벨벳",
children: [
{ key: "redvel-1", title: "조이" },
{ key: "redvel-2", title: "슬기" },
{ key: "redvel-3", title: "아이린" },
{ key: "redvel-4", title: "웬디" },
{ key: "redvel-5", title: "메리" },
],
},
],
},
{
key: "YG",
title: "YG 엔터테인먼트",
children: [
{
key: "blackpink",
title: "블랙핑크",
children: [
{ key: "blink-1", title: "로제" },
{ key: "blink-2", title: "제니" },
{ key: "blink-3", title: "리사" },
{ key: "blink-4", title: "지수" },
],
},
],
},
{
key: "JYP",
title: "JYP 엔터테인먼트",
children: [
{
key: "itzy",
title: "있지",
children: [
{ key: "itzy-1", title: "예지" },
{ key: "itzy-2", title: "채령" },
{ key: "itzy-3", title: "류진" },
{ key: "itzy-4", title: "리아" },
{ key: "itzy-5", title: "유나" },
],
},
],
},
{
key: "STAR",
title: "스타쉽 엔터테인먼트",
children: [
{
key: "ive",
title: "IVE",
children: [
{ key: "ive-1", title: "안유진" },
{ key: "ive-2", title: "가을" },
{ key: "ive-3", title: "레이" },
{ key: "ive-4", title: "장원영" },
{ key: "ive-5", title: "리즈" },
{ key: "ive-6", title: "이서" },
],
},
],
},
];
export default treeData;
이전에 이와 비슷한 Tree 컴포넌트를 사용해 본 사람이라면
데이터 구조에서 눈에 파박 튀는 부분이 있을 건데.. children이다.
데이터 구조에 parent를 사용하는가?, children을 사용하는가?에 따라
장단점이 많이 다르당.(언제 한번 느껴보길 바란당)
나중에 Backend 서버에서 이런 형태의 데이터를 직접 가져오든가?
아니면 받은 데이터를 이런 형태가 되도록 Transform 하는
코드를 짜야 할 거시당. (일단 지금은 배째하공 무시하장. 내일 일은 내일로)
그라믄 위의 데이터를 이용하야 기본 Tree를 맹글어보당.
제공 되는 Tree 컴포넌트의 props를 잘 눈길에 발자국 찍듯이 힘을 빼고 본당.
아이콘 이미지는 귀간지러서 api를 사용해서 넣었당.
그래야 소스 가져간 사람이 귀안케 이미지 다운로드 안받아도 되니켕
select 이벤트도 onSelect 되어 이쓰미, 기대 반 우려 반 반반핸들추가당.
MyTree.jsx
import Tree from "rc-tree";
import "rc-tree/assets/index.css";
import treeData from "./sampleData";
function MyTree() {
const handleIcon = (obj) => {
//console.log("체킁",obj)
let imgURL = "";
const hypCnt = obj.pos.split("-").length;
// 더미이미지 와 아바타 이미지 API를 괘니 사용해 봄
if (hypCnt == 2)
imgURL = `https://dummyimage.com/160x160/000/fff.png&text=${obj.data.key}`;
else if (hypCnt == 3)
imgURL = `https://dummyimage.com/160x160/000/ff0.png&text=${obj.data.key}`;
else if (hypCnt == 4)
imgURL = `https://api.dicebear.com/9.x/adventurer/svg?seed=${obj.title}`;
return <img src={imgURL} width={16} height={16} />;
};
const handleSelect = (skey, info) => {
console.log("체킁 1: ", skey, info.node.title);
console.log("체킁 2: ", info);
};
return (
<Tree
treeData={treeData}
showIcon
icon={handleIcon}
selectable
expandAction={"click"}
onSelect={handleSelect}
/>
);
}
export default MyTree;
아마도 아래 코드가 궁금 할거시당.
const hypCnt = obj.pos.split("-").length;
rc-tree는 tree node에 pos란 속성을 가질 수도 있는데, sampleData.js를 보장
최상위 Root가 0번 (여기선 treeData 배열 자체) 이 되고
그 다음 배열의 첫번째 key E7E가 0-0, 두번째 SM이 0-1
SM의 children 첫번째 aespa가 0-1-0, 두번째가 0-1-1 식의 값을 가지게 된당.
[ 사실 이 부분은 sampleData에 type등 본인이 원하는 이름으로
추가 속성을 부여한다면 더 깔금하게 해결될 수 있다. 그냥 그렇다 ]
App.jsx
import MyTree from "./MyTree";
function App() {
return (
<>
<h2>RC 카 아니랑 RC-TREE랑</h2>
<hr />
<MyTree />
</>
);
}
export default App;
실행 화면으로 시선을 도려내 보장. 난 다이어트 신을 선택하였당.
음... 먼가 써볼까 하는 마음이 들었지만, RC 카를 사면 더 재밌을 꺼 같당.

분명 요기까지만 해도 만족하는 사라미 있겠지만, 경미니(?)라면 만족하지 아니할게당.
그래서 금수저(?)를 위해 쪼메 더 가보도록 하장.
MUI 와 라우터 모듈을 괘니 추카해 주장!( 라우터가 관심!)
npm install @mui/material @emotion/react @emotion/styled
npm i react-router
라우터 설정 해볼까낭! 초간단 설정!
main.jsx
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import "./index.css";
import App from "./App";
import { BrowserRouter, Route, Routes } from "react-router";
import Idol from "./Idol";
const style = {
color: "red",
};
createRoot(document.getElementById("root")).render(
<StrictMode>
<BrowserRouter>
<Routes>
<Route path="/" element={<App />}>
<Route index element={<Idol />} />
<Route path="/:name" element={<Idol />} />
</Route>
<Route path="*" element={<h1 style={style}>404</h1>} />
</Routes>
</BrowserRouter>
</StrictMode>
);
Idol 컴포넌트가 나올 자리 Outlet 설정
useNavigate 훅 이용, 값 전달도 해봄
선택여부와 children여부를 이용, leaf인 경우만 navigate
MyTree.jsx
import Tree from "rc-tree";
import "rc-tree/assets/index.css";
import treeData from "./sampleData";
import { Box } from "@mui/material";
import { Outlet, useNavigate } from "react-router";
function MyTree() {
const navigate = useNavigate();
const handleIcon = (obj) => {
//console.log("체킁",obj)
let imgURL = "";
const hypCnt = obj.pos.split("-").length;
if (hypCnt == 2)
imgURL = `https://dummyimage.com/160x160/000/fff.png&text=${obj.data.key}`;
else if (hypCnt == 3)
imgURL = `https://dummyimage.com/160x160/000/ff0.png&text=${obj.data.key}`;
else if (hypCnt == 4)
imgURL = `https://api.dicebear.com/9.x/adventurer/svg?seed=${obj.title}`;
return <img src={imgURL} width={16} height={16} />;
};
const handleSelect = (skey, info) => {
console.log("1: ", skey, info.node.title);
console.log("2: ", info.node);
const selNode = info.node;
if (!selNode.selected && !selNode.children) {
navigate(`/${selNode.title}`, { state: selNode });
}
};
return (
<Box display={"flex"} sx={{ justifyContent: "space-between" }}>
<Tree
treeData={treeData}
showIcon
icon={handleIcon}
selectable
expandAction={"click"}
onSelect={handleSelect}
/>
<Box sx={{ width: "50%" }}>
<Outlet />
</Box>
</Box>
);
}
export default MyTree;
useLocation 훅 이용, 넘겨 받은 node값 활용
괘니 mui 컴포넌트 몇개 써봄.....
값이 없을 때 곧 router index인 경우 기본값 e7e 세팅
Idol.jsx
import { Avatar, Box, Typography } from "@mui/material";
import React from "react";
import { useLocation } from "react-router";
function Idol() {
const location = useLocation();
const { title = "예원", key = "f41-5" } = { ...location.state };
console.log("체킁2: ", key);
return (
<Box
display={"flex"}
sx={{
border: "10px groove gold",
justifyContent: "center",
alignItems: "center",
}}
>
<Avatar
sx={{ width: "30%", height: "30%" }}
src={`https://api.dicebear.com/9.x/adventurer/svg?seed=${key}`}
/>
<Typography variant="h3">{title}</Typography>
</Box>
);
}
export default Idol;
결과는? 음 디자인은 안 좋으나, 그냥 쓸만~~ (자기만족!~~ )

여기서 만족할 수도 물론 있겠지만, 이왕 한 거 쪼메만 더 걸어보장.
Tree 컴포넌트를 사용하다보면 열린 Tree Node가 많으면
아무래도 눈이 뱅뱅 어지러울 수 있다.(특히 늙으면 말이다.. ㅠㅠ)
선택한 Node만 열리고, 나머지는 닫히도록 해보장
RC-TREE에선 정말 쉽당.(MK는 나만 보면 기침이 나온당)
MyTree.jsx 를 아래 코드로 복사/붙여넣기 하장
/* eslint-disable no-unused-vars */
import Tree from "rc-tree";
import "rc-tree/assets/index.css";
import treeData from "./sampleData";
import { Box } from "@mui/material";
import { Outlet, useNavigate } from "react-router";
import { useState } from "react";
function MyTree() {
const navigate = useNavigate();
const handleIcon = (obj) => {
//console.log("체킁",obj)
let imgURL = "";
const hypCnt = obj.pos.split("-").length;
if (hypCnt == 2)
imgURL = `https://dummyimage.com/160x160/000/fff.png&text=${obj.data.key}`;
else if (hypCnt == 3)
imgURL = `https://dummyimage.com/160x160/000/ff0.png&text=${obj.data.key}`;
else if (hypCnt == 4)
imgURL = `https://api.dicebear.com/9.x/adventurer/svg?seed=${obj.title}`;
return <img src={imgURL} width={16} height={16} />;
};
const handleSelect = (skey, info) => {
console.log("1: ", skey, info.node.title);
console.log("2: ", info.node);
const selNode = info.node;
if (!selNode.selected && !selNode.children) {
navigate(`/${selNode.title}`, { state: selNode });
}
};
const [expandedKeys, setExpandedKeys] = useState([]);
const handleExpand = (expandedKeys, { _expanded, node, _nativeEvent }) => {
setExpandedKeys([node.key]);
};
return (
<Box display={"flex"} sx={{ justifyContent: "space-between" }}>
<Tree
treeData={treeData}
showIcon
icon={handleIcon}
selectable
expandAction={"click"}
onSelect={handleSelect}
autoExpandParent
expandedKeys={expandedKeys}
onExpand={handleExpand}
/>
<Box sx={{ width: "50%" }}>
<Outlet />
</Box>
</Box>
);
}
export default MyTree;
분명 잘 동작할 거당. 캡처는 생략!
뽀인또는 Tree 컴포넌트에 에 추가된 아래 속성(expanedKeys가 핵심)과
autoExpandParent
expandedKeys={expandedKeys}
onExpand={handleExpand}
아래 코드당.
const [expandedKeys, setExpandedKeys] = useState([]);
const handleExpand = (expandedKeys, { _expanded, node, _nativeEvent }) => {
setExpandedKeys([node.key]);
};
잘 만들었당. 이제 백엔드에서 데이터를 가져와서 뿌리는 건
본인이 한번 맹글어 보는 수고를 해보장. 어쩌면 생각보다 기쁠거시당.
아! 그리고 이런 외부 라이브러리는 Typescript로 한번 더 해보면 좋은데...
Typescript 사용 패턴 연습이 Typescript가 낯선자에게 꽤나 된당.
@types/rc-tree 형식으로 @types/패키지명의 패키지도 대부분 지원된단 사실도...
혹 도우미 될까나 시퍼 typescript로 맹근 파일을 첨부해 본당
압축 풀공 터미널에 아래 명령어를 넣고 엔터를 빡 강하게 친다면
npm i && npm run dev
아래 화면을 보게 될꺼이당.

이모티콘 부자는 널렸으나
이모티콘 선택 센스 초능력은
그저 단지 소수 몇명에게만 주어졌나니...
그중에도 탁월한 이가 있었으니
그 사람 금수저였당.
말과 상황에 대처하는 센스는 소심하였당.
금수저 하면 맘에 미안함이 출렁인당.
언제나 잔잔해 지려낭....
구지 사과주러 가지 않았다면....
찌질한 말을 훅 뱉지 않았다면...
달리는 울음소리는 시간에 없었을텐데....
금수저가 보내주는 스토리 있는
연속 파노라마 이모티콘이 문득 그립당.
하여간 그냥 던 던 댄스당.~~
https://www.youtube.com/watch?v=HzOjwL7IP_o
| React(리액트) 티키타카 38 (19버젼에 추가된 form action) (1) | 2025.12.26 |
|---|---|
| React Spinner(react-loader-spinner) (8) | 2025.11.07 |
| React(리액트) 티키타카 33 SVAR Gantt(업그레이드) (3) | 2025.11.03 |
| React With TypeScript 체킁1 (2) | 2025.10.21 |
| 템플릿 (6) | 2025.06.27 |