React가 jQuery처럼 행동하기 시작할 때 (그리고 당신은 완전히 알 수 있다 😅)

발행: (2026년 1월 8일 오후 10:00 GMT+9)
13 min read
원문: Dev.to

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).readyuseEffect로 바꾸고 바로 프로덕션에 배포한다* 😎.

🧨 예시

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"));
}, []);

원본 소스에서 추가 코드는 생략되었습니다.

Back to Blog

관련 글

더 보기 »

React란 무엇인가?

!What is React?에 대한 표지 이미지 https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amaz...