React가 jQuery처럼 행동하기 시작할 때 (그리고 당신은 완전히 알 수 있다 😅)
Source: Dev.to
위 링크에 있는 전체 텍스트를 제공해 주시면, 해당 내용을 한국어로 번역해 드리겠습니다. (코드 블록, URL 및 마크다운 형식은 그대로 유지됩니다.)
팬이 아니라 – 단지 도구 사용자
저는 특정 프레임워크에 팬이 아닙니다. 저에게 프레임워크는 도구일 뿐입니다.
구멍이 뚫린 녹슨 망치보다 튼튼하고 좋은 망치를 선호하듯이, 유지보수가 쉽고 작업하기 즐거운 소프트웨어를 만들 수 있게 도와주는 도구를 선호합니다. 그래서 최신 정보를 유지하고, 내부가 어떻게 동작하는지 이해하려고 노력합니다. 단순히 *“모두가 쓰는 걸 쓰는 것”*에 머무르지 않으려는 것이죠.
저는 Angular를 매일 사용합니다 — 그리고 맞아요, Angular는 특정 아키텍처를 강요합니다 (하지만 믿으세요, 여전히 완전한 스파게티 코드를 만들 수 있거든요… 어떻게 아는지 물어보세요 😬).
하지만 저는 React를 진심으로 사랑합니다. 사이드 프로젝트를 할 때면 거의 항상 React를 선택합니다. JSX가 마음에 들고, 특히 Angular와 비교했을 때 React가 순수 JavaScript와 얼마나 가깝게 느껴지는지가 좋습니다. 하지만… 사람들이 말하듯이 “큰 힘엔 큰 책임이 따른다” 😎. 잘못된 손에 이 자유는 프로젝트를 망칠 수 있습니다.
저는 단순히 지저분한 프로젝트보다 더 많은 사례를 보았습니다.
누군가가 jQuery로 코드를 작성하고 $(document).ready를 삭제한 뒤, 거기에 JSX를 뿌려 넣고… 진심으로 동작하게 만들려는 모습이었습니다 🤡.
- 오래된 코드베이스에서 눈이 멀어 무작정 마이그레이션한 건가요?
- 마음속 깊은 곳에 아직도 jQuery를 놓지 못한 개발자들인가요? 😉
말하기는 어렵습니다. 어쨌든 — 여기 당신의 React 코드가 진짜 React가 아니라… JSX를 입은 jQuery일 때 나타나는 고전적인 징후 몇 가지를 소개합니다. 그리고 네, 그것은 분명히 크게 드러납니다.
1️⃣ 모든 것을 “해내는” 거대한 컴포넌트 하나
React 파일(보통 .tsx)은 기술적으로 서브 컴포넌트나 CSS 클래스 이름 유틸리티를 가지고 있을 수 있지만, 감정적으로는 거대한 <div> 태그가 들어간 구식 index.html 같은 느낌이다 😂.
거대한 블롭: 데이터 가져오기, UI 업데이트, 이벤트 관리, 레이아웃 처리, 모달일 수도 있고, 드롭다운 3개, 테이블, 사이드바… 왜 안 되겠어?
🧨 Example
function App() {
const [data, setData] = useState([]);
const [sidebarOpen, setSidebarOpen] = useState(false);
const [filter, setFilter] = useState("");
useEffect(() => {
fetch("/api/items")
.then(res => res.json())
.then(setData);
// Direct DOM manipulation – a red flag
document.getElementById("loader")?.style.display = "none";
document.getElementById("list")?.style.display = "block";
const el = document.getElementById("filter");
el?.addEventListener("input", e => setFilter(e.target.value));
}, []);
return (
<>
{/* Loading... */}
{data
.filter(d => d.includes(filter))
.map(d => (
<div key={d}>- {d}</div>
))}
<button onClick={() => setSidebarOpen(!sidebarOpen)}>Toggle</button>
{sidebarOpen && "Hello"}
</>
);
}
🤔 왜 이런 일이 발생할까?
- jQuery에서 마이그레이션하면서 모든 것을 하나의 파일에 집어넣음.
- “동작하니, 건드리지 마.”
- 초기 아키텍처 결정을 하지 않음.
- “나중에 리팩터링하자” (그 이야기가 어떻게 끝나는지는 우리 모두 알고 있다).
✅ 이렇게 해야 함
- 책임에 따라 컴포넌트를 분리 (단일 책임 원칙).
- 데이터/로직 컴포넌트.
- 프레젠테이션 컴포넌트.
- 재사용 가능한 로직은 적절할 때 커스텀 훅으로 옮기기.
- 직접 DOM 조작을 피함 – UI는 React가 관리하도록 하자.
- 기억하자: 많은 작은 컴포넌트가 하나의 거대한 컴포넌트보다 낫다.
2️⃣ 모든 것을 수행하는 거대한 useEffect 하나
useEffect(() => {
// 🤹♂️ everything happens here
}, []);
데이터를 가져오고, 이벤트 리스너를 추가하고, 클래스를 토글하고, DOM을 업데이트하고, 여러 서비스와 통신하고, 스크롤하고, 분석하고, 토스트 알림을 표시하는 등… 모든 것을 하나의 멋진 effect에 담아놓았습니다.
즉, $(document).ready를 useEffect로 바꾸고 바로 프로덕션에 배포한다* 😎.
🧨 예시
useEffect(() => {
fetch("/api/stats")
.then(res => res.json())
.then(data => {
setStats(data);
document.title = "Dashboard";
const counter = document.getElementById("counter");
if (counter) {
counter.textContent = data.users;
}
});
const resize = () => {
document.body.classList.toggle("mobile", window.innerWidth < 768);
};
window.addEventListener("resize", resize);
return () => window.removeEventListener("resize", resize);
}, []);
🤔 왜 이런 일이 발생할까요?
- 정신 모델의 부재: “effect = 마법이 일어나는 곳”.
- 구 코드에서 복사‑붙여넣기.
- “한 번만 실행되니까, 모든 걸 넣기에 딱 좋겠지!”
- effect는 집중되고 범위가 명확해야 한다는 이해 부족.
✅ 올바른 접근법
- 각 effect는 하나의 명확한 책임만 가져야 합니다.
- 거대한
useEffect를 여러 개의 집중된 effect로 분리하세요. - 렌더 단계에서 처리할 수 있는 로직을 effect에 넣지 마세요.
useEffect !== 라이프사이클 메서드임을 기억하세요.- 앱 전체 동작을 하나의 “한 번만 실행” effect에 집어넣는 것을 피하세요.
3️⃣ JSX에 해당하는 것들을 위한 useEffect
이건 조금 아프죠 😅. 효과 안에서 DOM을 조작해서 텍스트, 클래스, 가시성 등을 업데이트하는 것은 JSX가 자연스럽게 선언할 수 있는 일입니다.
대신:
if (condition) {
// change DOM
}
React가 원하는 방식:
if (condition) {
// render something else
}
🧨 예시 (명령형)
useEffect(() => {
const el = document.getElementById("message");
if (error) {
el?.classList.add("visible");
el!.textContent = error;
} else {
el?.classList.remove("visible");
}
}, [error]);
✅ 선언형 JSX 버전
{error && <div className="visible">{error}</div>}
🤔 왜 이런 일이 발생할까요?
- 여전히 명령형으로 생각함: “UI는 내가 변경하는 것이지, 설명하는 것이 아니다”.
- 오래된 패턴: “모든 것이 동적인가? →
useEffect에 넣어야 한다!”. - 오래된 습관은 쉽게 사라지지 않는다.
✅ 이렇게 해야 합니다
- UI 변화를 JSX에서 선언적으로 표현합니다.
- 조건부 렌더링을 사용해 요소를 보여주거나 숨깁니다.
- 클래스를 수동으로 토글하는 대신 상태에서 파생시킵니다.
- 생각: “상태가 변하면 → React가 다시 렌더링” 이 아니라 “상태가 변하면 → 내가 직접 DOM을 패치한다”.
TL;DR
- 컴포넌트를 작고 집중된 형태로 유지하세요.
- 재사용 가능한 로직은 커스텀 훅으로 만드세요.
useEffect는 부수 효과 만 위해 사용하세요 (데이터 가져오기, 구독 등).- JSX가 UI를 기술하도록 하고, 직접 DOM을 조작하는 것을 피하세요.
이 가이드라인을 따르면 React 코드는 다시 React답게 느껴질 것입니다 – jQuery‑스타일 프랑켄슈타인이 아니라. 🚀
“the DOM”
4️⃣ 실제 로직 대신 CSS 클래스에 대한 거대한 “스위치”
UI 상태가 저장되는 곳… 상태가 아니라
…props에도 아니라
…하지만 🥁 CSS 클래스 이름에.
애플리케이션 로직은 다음과 같이 변합니다:
- “이 클래스가 있으면 열림”
- “없으면 닫힘”
- “이것과 저것을 동시에 가지고 있으면, 이제는 아무도 완전히 이해하지 못하는 마법 같은 상태”
축하합니다, 스타일시트 안에 상태 머신을 만들었네요 🙃
🧨 예시
useEffect(() => {
const steps = document.querySelectorAll(".step");
steps.forEach(step => {
step.addEventListener("click", () => {
steps.forEach(s => s.classList.remove("active"));
step.classList.add("active");
});
});
}, []);
<button class="step">Enter fullscreen mode</button>
<button class="step">Exit fullscreen mode</button>
🤔 왜 이런 일이 발생할까?
- 레거시 사고 방식.
- “예전엔 jQuery에서 잘 돌아갔는데, 왜 바꾸나요?”
- 수년간 CSS가 상태 저장소 역할을 해왔고, 그 습관이 남아 있음.
✅ 이렇게 해야 합니다
- UI 상태를 React state 혹은 스토어에 보관합니다.
- 로직을 CSS에 인코딩하지 말고, 상태에서 UI가 파생되도록 합니다.
복잡한 흐름을 다룰 때는 다음을 고려하세요:
useReducer- 적절한 상태 머신
대략적인 규칙
- CSS = 스타일링
- React state = 로직
5️⃣ Animations as “just toggle the class in JS”
Need animation?
Add/remove class in JS. Done.
React? Oh yes, still technically there 👀 just quietly watching.
No state. No declarative transitions. No structure.
Just:
click → add class → remove class later → hope nothing breaks
🧨 Example
function Notification() {
useEffect(() => {
const btn = document.getElementById("show");
const box = document.getElementById("note");
btn!.onclick = () => {
box?.classList.add("visible");
setTimeout(() => box?.classList.remove("visible"), 2000);
};
}, []);
return (
<>
<button id="show">Show</button>
<div id="note">Hello!</div>
</>
);
}
<button id="show">Enter fullscreen mode</button>
<button id="hide">Exit fullscreen mode</button>
🤔 Why does this happen?
- 익숙한 옛 패턴.
- 빠른 해킹이 프로덕션에 배포됨.
- 프론트엔드 아키텍처에 할당된 시간이 전혀 없음.
✅ How it should be
- 기본 애니메이션은 React 상태에 의해 트리거되어야 하며, 수동 DOM이 아닙니다.
가장 간단한 옵션
- 상태 → JSX에서 클래스 토글
더 나은 옵션
- 상태에 의해 구동되는 CSS 전환
- React Transition Group
- Framer Motion
선언적으로 할 수 있을 때는 명령형으로 애니메이션을 제어하지 마세요.
6️⃣ 상태 안에 DOM 요소 보관하기 😱
드물게… 하지만 본 적 있어요. 한 번 보면 절대 잊지 못하죠 — 흔적을 남깁니다 😅
const [el, setEl] = useState<HTMLElement | null>(null);
useEffect(() => {
setEl(document.getElementById("target"));
}, []);
원본 소스에서 추가 코드는 생략되었습니다.