[태그:] React.js

  • Context API vs 상태관리 라이브러리

    상태관리는 왜 필요한가?

    리액트는 기본적으로 단방향 데이터 흐름을 가진다.

    부모 → 자식으로 props로 전달하며 UI를 구성하는데, 앱이 커질수록 이 구조에서 문제가 발생한다.

     

    여러 컴포넌트가 같은 데이터를 필요로 하거나, 중첩 전달이 반복되고 이벤트가 깊은 자식에서 발생해 상위 컴포넌트에 영향을 주는 등의 상황을 마주하게 되면 컴포넌트 간 데이터 공유를 쉽게 만드는 구조가 필요하다.

    → 상태 관리 라이브러리 도입의 필요성

     

    Context API

    리액트에서 기본으로 제공하는 전역 상태 컨텍스트로, props drilling(중첩 전달)을 줄이기 위해 설계되었다.

    리액트 내장 기능으로 편하게 사용 가능(createContext, useContext)하며, Provider → Consumer 구조로 전역 데이터를 공유한다.

    const ThemeContext = createContext();
    function App() {
      return (
        <ThemeContext.Provider value="dark">
          <Toolbar />
        </ThemeContext.Provider>
      );
    }
    function Toolbar() {
      const theme = useContext(ThemeContext);
      return <Button theme={theme} />;
    }

    장점

    • 외부 의존성 X
    • 단순한 전역 상태에 적합하다. e.g. 테마, 언어 설정 등

    단점

    • 상태가 자주 바뀌면 Provider 전체가 리렌더링 된다.
    • 규모가 커지면 성능 저하 및 관리 복잡도 증가.

     

    Redux

    상태의 중앙집중 관리 모델.

    Redux는 모든 상태를 하나의 Store에 보관, state를 오직 action과 reducer를 통해서만 수정.

    const initialState = { count: 0 };
    function counterReducer(state = initialState, action) {
      switch (action.type) {
        case 'increment':
          return { count: state.count + 1 };
        default:
          return state;
      }
    }

    장점

    • 명확한 상태 추적: action log, time travel debug 가능.
    • 대규모 팀, 복잡한 상태 흐름에서 일관성 유지 용이.
    • 미들웨어를 통한 확장성(redux-thunk, redux-saga 등).

    단점

    • 보일러플레이트 코드가 많음(action, reducer, dispatch 반복)
    • 초기 진입장벽이 높고, 단순 앱에는 과한 구조.

    → 명확한 구조와 예측 가능성이 필요한 대규모 프로젝트에 적합함.

     

    Zustand

    Redux의 철학을 유지하며 코드 복잡도를 최소화한 라이브러리.

    Hook 기반으로 Store하며 Context나 Provider 불필요,

    React의 리렌더링 성능을 최적화했다.

    const useStore = create((set) => ({
      count: 0,
      increment: () => set((state) => ({ count: state.count + 1 })),
    }));

    장점

    • 매우 간단한 API
    • 부분 구독(selectors)으로 불필요한 리렌더링 방지
    • Immer, persist(스토리지 저장) 등 기본 지원.

    단점

    • Redux처럼 상태 변경 추적(logging, time travel) 기능은 부족.
    • 너무 자유도가 높음 → 일관된 패턴 유지 어려움

    → 단일 페이지나 컴포넌트 중심의 중간 규모 프로젝트에 이상적.

     

    Recoil

    React에 최적화된 의존성 기반 상태 관리.

    Facebook이 React 전용으로 설계한 상태 그래프 기반 라이브러리로,

    각 상태 단위를 atom으로 관리하며 selector를 통해 파생 상태(derived state)를 계산한다.

    const countState = atom({ key: 'count', default: 0 });
    const doubleCount = selector({
      key: 'doubleCount',
      get: ({ get }) => get(countState) * 2,
    });

    장점

    • useState처럼 자연스러운 사용성
    • atom 간 의존성 자동 관리 → 부분 리렌더링 최소화
    • React Suspense, concurrent feature와 호환.

    단점

    • 조금 불안정한 상태.
    • 디버깅/도구 지원이 Redux만큼 성숙하지 않음.

    → React 내부 동작과 밀접하게 통합, 차세대 React 환경과 잘 맞음

     

    그래서 뭘 써야 할까?

    구분특징적합한 경우
    Context API리액트 기본 내장, 단순한 전역 데이터 공유다크 모드, 로케일, 간단한 설정 값
    Redux중앙집중 + 명확한 상태 흐름, 미들웨어 풍부대규모 SPA, 협업이 많은 프로젝트
    ZustandHook 기반, 코드 간결, 빠른 성능스타트업, MVP, UI 상태 중심 앱
    Recoilatom/selector 기반, React 친화적React 18+ 기반 신규 프로젝트
  • Core Web Vitals 대응

    Core Web Vitals?

    구글이 사용자 경험을 수치화하기 위해 제시하는 핵심 성능 지표.

    2021년부터는 검색 순위에도 직접 반영되고 있어서 단순한 성능 최적화를 넘어 비즈니스 성과와도 직결된다.

    이 지표들은 단순히 빠르고 느리고에서 결정되는 것이 아닌, 실제 사용자 경험에서 반영, 비롯된다.

    Core Web Vitals의 세 가지 주요 지표

    LCP(Largest Contentful Paint)

    사용자가 페이지에서 가장 큰 콘텐츠 요소(이미지, 동영상, 큰 텍스트 블록 등)를 보는데 소요되는 시간.

    페이지를 인식하는 순간을 의미한다.

    (권장 ≤ 2.5초)

    로딩이 늦으면 사용자는 사이트가 느리다고 느끼고,
    첫인상이 중요한 페이지(e.g. 랜딩 페이지)에서 LCP 지연은 이탈율에 직결된다.

    INP(Interaction to Next Paint)

    사용자가 페이지에서 클릭/탭/키 입력을 했을 때, 화면이 반응하는 속도.

    (권장 ≤ 200ms)

    단순히 클릭에 의한 반응 속도가 아닌, 사용자의 모든 입력을
    브라우저가 화면에 그 변화를 반영하는 데 걸린 전체 시간을 측정하는 지표.

    → 사용자가 200ms 이상 기다리면 버벅거린다고 느끼게 된다.

    CLS(Cumulative Layout Shift)

    페이지 로딩 중 예상치 못한 레이아웃 이동 정도.

    (권장 ≤ 0.1)

    페이지 로딩 중 갑자기 버튼이 아래로 밀리거나, 텍스트가 튀면서 광고가 도중에 삽입되는 현상,

    이러한 예기치 못한 움직임으로 인해 사용자 경험이 저해되는지를 측정하는 지표.

    LCP 최적화

    문제 원인

    • 첫 화면에 있는 큰 이미지나 Hero 배너 로딩이 느린 경우
    • 렌더링 차단 리소스(CSS/JS)가 많아 페인트가 지연되는 경우

    *Hero 배너

    웹 사이트 첫 화면의 가장 눈에 띄는 대형 영역(이미지, 비디오 등).

    해결 방법

    1. 이미지 최적화


      • Next.js 의 next/image 컴포넌트 활용. → 자동 압축/리사이징/WebP 변환

      • Hero 영역 이미지는 반드시 priority 속성 추가.
      • next/image는 기본적으로 이미지를 lazy loading 처리. but 첫화면 렌더 이미지는 즉시 불러와야 하는데,priority를 추가하면 네트워크 요청 우선 순위를 올려 해당 이미지를 먼저 다운로드하게 됨.

    *WebP

    구글이 개발한 차세대 이미지 포맷, JPG/PNG 대비 평균 30% ~ 40% 용량 절감.같은 화질을 더 작은 용량으로 표현 가능 → 네트워크 전송 시간을 단축함으로 LCP를 개선할 수 있다.

    대부분의 브라우저에서 지원 중.

    1. 폰트 최적화

      • next/font 를 활용하여 Google Fonts를 직접 번들에 포함시킴
      • font-display: swap으로 폰트 로딩 시 FOUT(깜빡임) 최소화
    2. Critical CSS 최소화


    페이지가 처음 그려지는데 꼭 필요한 최소한의 CSS를 Critical CSS라 한다.

    즉 필수 스타일만 먼저 로드하고, 나머지는 lazy loading 처리하는 것이 효율적.

    • 사용하지 않는 CSS 제거(Tree Shaking)
    • CSS-in-JS 라이브러리 사용 시, 서버사이드에서 Critical CSS를 추출.
    1. 서버 응답 속도 개선

    API 요청 지연을 줄이고, CDN 캐싱 적극 활용.

    INP 최적화

    문제 원인

    • 메인 스레드를 오래 점유하는 무거운 JavaScript 실행.
    • 이벤트 핸들러 내부에서 비효율적인 연산 발생.
    • 애니메이션을 GPU가 아닌 CPU로 처리, 동작.

    해결 방법

    1. JavaScript 최소화

      • dynamic import 로 페이지 진입 시 불필요한 JS 로딩 지연
      • 이벤트 핸들러에서 무거운 연산은 requestIdleCallback 또는 Web Worker로 분리.
    2. Re-render 줄이기

      • React 상태 관리 시 Context 남용 금지 → useMemo, useCallback으로 불필요한 렌더링 방지
      • Zustand, Jotai 같은 fine-grained 상태 관리 도구 고려
    3. 애니메이션 최적화

      • transform/opacity 기반 애니메이션 사용 → GPU 가속 활용
      • requestAnimationFrame으로 프레임 제어

    CLS 최적화

    문제 원인

    • 이미지/광고 영역에 고정 크기 지정 x
    • 웹 폰트 로딩 지연으로 텍스트가 갑자기 재배치.
    • 동적으로 삽입되는 배너, 알림 영역

    해결 방법

    1. 고정 크기 지정

      • 가능하면 이미지/비디오/컴포넌트에 width, height 명시

        but, 반응형 광고가 필요할 수도 있음. 최소/최대 크기 범위를 지정하는 대처가 필요.
    2. 폰트 로딩 전략

      • next/font 를 사용해 FOUT 최소화
      • 또는 fallback 폰트와 유사한 폭을 가진 폰트 선택.
    3. UI 요소 안정화

      • 레이지 로딩(Lazy loading) 요소는 placeholder/skeleton 미리 확보
      • 상단 배너 삽입 시, 최초 레이아웃에서 공간 확보.

    측정과 모니터링

    개발 단계

    Chrome DevTools → Performance 패널 / Lighthouse → 성능 시뮬레이션

    실서비스 단계

    • Google Search Console → Core Web Vitals 리포트
    • Web Vitals JS 라이브러리 → 실제 사용자 데이터(RUM, Real User Monitoring) 수집
  • Hydration과 SEO

    Hydration ?

    SSR이나 SSG를 통해 서버에서 완성된 HTML을 내려주더라도, 브라우저가 곧바로 동작 가능한

    SPA(Single Page Application)로 변환되는 것은 아니다.

    React 기반 프레임워크(Next.js 포함)에서는 이 과정을 Hydration이라고 부른다.

    • 서버에서 내려온 HTML은 정적 콘텐츠일 뿐, 이벤트 핸들러나 상태 관리 기능이 없는 “죽은 페이지”다.
    • 브라우저가 React의 JS 번들을 실행하여, 기존 HTML을 DOM과 연결하고 이벤트를 주입하는 과정이 Hydration이다.
    • 즉, Hydration이 끝나야 비로소 사용자가 버튼을 클릭하거나 상태 변경을 할 수 있다.

     

     

    Hydration 과정에서 발생할 수 있는 문제

    Hydration Mismatch

    • 서버에서 생성된 HTML과 클라이언트에서 렌더링된 결과가 다를 경우, React는 경고를 발생시키거나 DOM을 다시 그린다.
    • 이때 화면이 깜빡이거나, 의도치 않은 레이아웃 깨짐이 발생한다.
    • SEO 차원에서는 검색엔진이 본 HTML과 실제 사용자 화면이 달라질 수 있어, 콘텐츠 신뢰성에 영향을 줄 수 있다.

    JavaScript 번들 크기 문제

    • SSR/SSG를 하더라도 Hydration 단계에서는 JS 번들을 모두 다운로드하고 실행해야 한다.
    • 번들이 크면 TTI(Time To Interactive)가 늦어져 Core Web Vitals 점수가 하락하고, 이는 곧 SEO 순위에 반영된다.

    비동기 데이터 처리

    • useEffect에서 데이터를 불러오는 경우, Hydration 시점에는 아직 데이터가 없어 빈 화면이 잠깐 노출될 수 있다.
    • 구글봇은 기본적으로 HTML만 본다. Hydration 이후 동적으로 채워지는 콘텐츠는 렌더링 큐 처리를 기다려야 하므로 인덱싱이 지연될 수 있다.

     

     

    Hydration과 SEO의 관계

    Hydration 자체는 SEO에 직접적으로 반영되지 않는다.(검색엔진은 서버에서 내려온 HTML을 우선으로 보므로)

    하지만 Hydration 과정이 실패하거나 지연되면, 다음과 같은 문제가 발생할 수 있다.

    • 서버 HTML과 실제 사용자 화면 불일치 → 콘텐츠 신뢰도 저하
    • JS 실행 지연으로 인한 사용자 경험 악화 → Core Web Vitals 점수 하락 → 검색 순위에 간접적 영향
    • 구글봇이 JS 렌더링을 기다려야만 콘텐츠를 완전히 인덱싱 → 노출 지연

    따라서, Next.js에서는 Hydration을 반드시 해결하라고 권장하고 있다.

     

     

    실무에서의 Hydration

    Hydration Mismatch 최소화

    서버와 클라이언트 렌더링 로직을 일치시키고, 조건부 렌더링을 신중히 사용해야 한다.

    e.g. typeof window !== “undefined”

    번들 최적화

    dynamic import로 불필요한 JS를 분리하고, critical path에 해당하는 컴포넌트만

    우선 Hydration하도록 구성한다.

    Skeleton / Placeholder

    비동기 데이터는 빈 HTML을 노출하지 않고, 사용자 경험과 검색엔진 노출을 동시에 보완하는 UI 제공.

    e.g. Skeleton UI 등

     

     

    검색엔진 최적화 관점에서

    Hydration은 단순히 클라이언트 상호작용을 가능하게 하는 과정이 아니라,

    SEO와 성능 지표(Core Web Vitals)에도 직결되는 요소다.

    따라서 SEO를 고려하는 페이지라면 다음과 같은 전략이 필요.

    • 콘텐츠 중심 페이지 → 서버 HTML에 핵심 텍스트/메타정보를 포함하고, JS는 보조적 역할만 담당
    • 인터랙션 중심 페이지 → CSR 비중이 커도 무방하므로, Hydration 최적화보다 사용자 경험에 집중

  • Next.js(React)에서 네이버 지도 API 깔끔하게 연동하기

    안녕하세요! 이번 포스트에서는 Next.js (React) 프로젝트에서 네이버 지도 API를 연동하는 방법을 알아보겠습니다. 국내 사용자를 대상으로 하는 서비스를 개발한다면 네이버 지도 연동은 필수적인 기능 중 하나인데요. Next.js의 렌더링 방식과 React의 Hook을 활용하여 깔끔하고 효율적으로 구현하는 과정을 단계별로 소개해 드리겠습니다.

    시작하기 전에: 네이버 클라우드 플랫폼 설정

    가장 먼저 네이버 클라우드 플랫폼에서 애플리케이션을 등록하고 Client ID를 발급받아야 합니다. 이 ID는 API 인증에 사용되므로, 없다면 아래 공식 가이드를 참고하여 준비해 주세요.

    타입스크립트를 사용하는 분은 타입 정의 파일을 설치하고 코드를 작성해야 합니다.

    Step 1: 네이버 지도 API 스크립트 로드하기

    Next.js에서 외부 스크립트를 로드할 때는 next/script 컴포넌트를 사용하는 것이 가장 좋습니다. 페이지 로딩을 최적화하고 스크립트 로딩 시점을 제어할 수 있기 때문입니다.

    app/layout.tsx에 아래와 같이 <Script> 태그를 추가합니다.

    // app/contact/page.tsx 또는 지도가 필요한 다른 페이지
    
    import Script from "next/script";
    
    export default function RootLayout({ children }: Readonly<{ children: React.ReactNode }>) {
      return (
        <html lang="ko">
          <body>
            <Script strategy="beforeInteractive" src={`https://oapi.map.naver.com/openapi/v3/maps.js?ncpKeyId=${process.env.NEXT_PUBLIC_NAVER_MAP_CLIENT_ID}`} />
            {children}
          </body>
        </html>
      );
    }

    주요 포인트:

    • strategy="beforeInteractive": 페이지가 상호작용 가능해지기 전에 스크립트를 실행하여, 지도 API(window.naver)가 컴포넌트 렌더링 시점에 존재하도록 보장합니다.
    • Client ID 관리: 실제 ID를 코드에 하드코딩하는 대신, .env.local 파일에 환경변수로 저장하고 process.env를 통해 불러오는 방식을 권장합니다. (NEXT_PUBLIC_ 접두사가 있어야 클라이언트에서 접근 가능합니다.)

    Step 2: 지도 표시를 위한 React 컴포넌트 생성하기

    이제 지도를 실제로 렌더링할 컴포넌트를 만듭니다. useRef로 지도를 담을 DOM 요소를 참조하고, useEffect를 사용해 컴포넌트가 마운트된 후에 지도 생성 로직을 실행합니다.

    // components/ContactUsMap.tsx
    
    "use client";
    
    import { useRef, useEffect } from "react";
    
    export default function ContactUsMap() {
      const naver = typeof window !== "undefined" && window.naver;
    
      // 지도를 담을 DOM 요소에 대한 ref 생성
      const mapElement = useRef<HTMLDivElement | null>(null);
    
      useEffect(() => {
        // window.naver 객체가 로드되었는지, ref가 유효한지 확인
        if (!naver || !mapRef.current) return;
    
        // 위도
        const latitude = 37.5716229;
        // 경도
        const longitude = 126.9767879;
    
        // 지도의 중심 좌표 설정
        const location = new naver.maps.LatLng(latitude, longitude);
    
        // 지도 옵션 설정
        const mapOptions: naver.maps.MapOptions = {
          center: location,
          zoom: 17,
          zoomControl: true, // 줌 컨트롤 표시
          scaleControl: false, // 스케일 컨트롤 비표시
        };
    
        // 지도 인스턴스 생성
        const map = new naver.maps.Map(mapElement.current, mapOptions);
    
        // 지도 위에 마커 생성
        new naver.maps.Marker({
          position: location,
          map: map,
        });
      }, []); // 컴포넌트가 처음 마운트될 때 한 번만 실행
    
      return (
        // 지도가 렌더링될 div 요소
        <div ref={mapElement} style={{ minHeight: "400px" }} />
      );
    }

    Step 3: 코드 핵심 분석

    • "use client": useRef, useEffect와 같은 React Hook과 window 객체에 접근하려면 클라이언트 컴포넌트로 선언해야 합니다.
    • useRef<HTMLDivElement>: mapElement라는 ref를 생성하여 JSX의 <div> 요소와 연결합니다. 네이버 지도 API는 이 DOM 요소를 컨테이너로 사용해 지도를 렌더링합니다.
    • useEffect(() => { ... }, []): 이 Hook은 컴포넌트가 클라이언트에서 렌더링되고 DOM에 마운트된 직후에 실행됩니다. window.naver API와 mapElement.current(실제 <div> 요소)에 안전하게 접근할 수 있는 최적의 시점입니다. 의존성 배열을 []로 비워두어 최초 렌더링 시 한 번만 실행되도록 합니다.
    • new naver.maps.Map(...): naver.maps 라이브러리를 사용하여 지도 인스턴스를 생성합니다. 첫 번째 인자로는 지도를 담을 DOM 요소(mapElement.current)를, 두 번째 인자로는 지도 옵션(중심 좌표, 줌 레벨 등)을 전달합니다.
    • new naver.maps.Marker(...): 지도 위에 표시할 마커를 생성하고, map 속성에 지도 인스턴스를 지정하여 마커를 지도에 추가합니다.

    마치며

    지금까지 Next.js 환경에서 next/script와 React Hook을 활용해 네이버 지도 API를 연동하는 방법을 알아보았습니다. 이 패턴을 활용하면 서버사이드 렌더링(SSR)과 클라이언트 렌더링이 혼합된 Next.js 환경에서도 외부 스크립트 기반 라이브러리를 안정적으로 통합할 수 있습니다.

    여기서 더 나아가 커스텀 컨트롤 추가, 정보 창(InfoWindow) 표시 등 다양한 기능은 네이버 지도 API 공식 문서를 참고하여 구현해 보시길 바랍니다.

  • React state

    1. state

    컴포넌트 안에서 사용되는 이벤트에 의해 변경이 일어나는 동적인 값.

    값을 변경할 수 있으며 반드시 setState를 사용하여 변경한다.

    ※ state 사용할 때 주의사항

    ❗state에 저장되는 객체는 반드시 초기화(생성자에서 초기화)

    ❗state 값을 임의로 직접 변경하지 말 것, 반드시 state 값 변경시 setState() 사용.

    직접 변경하면 render 함수가 호출되지 않아 리렌더링 발생 X

    1) 사용법

    (1) 기본 사용: useState

    ① import useState

    import { useState } from 'react';

    ② declare useState()

    • useState 기본형태
    const [state, setState] = useState(initialState);
    • state: 현재 상태
    • setState: 상태를 변경하는 setState 함수 리턴
    • initialState: 상태 초기값. 생략 가능

    (2) state 값 변경: setState()

    setState() 함수를 호출하여 state 값을 변경하면 자동으로 render 함수를 호출

    다음과 같은 예시를 통해 useState와 setState 사용:

    • App.js: Increase를 클릭할때마다 1씩 증가
    import React, { useState } from 'react';
    
    const App = () => {
        const [num, setNumber] = useState(0);//초기값 0const onIncrease = () => {
            setNumber(num + 1);
        }
        return(
            <div>
                <h1>{num}</h1>
                <button onClick={onIncrease}>Increase</button>
            </div>
        );
    }
    
    export default App;

    🌟setState() 함수는 비동기 처리, 즉 작업 순서대로가 아닌 이벤트에 따라 처리.

    어떤 작업에서 시간이 오래 소요되면 해당 작업이 완료될때까지 기다려주지 않고 다음 작업을 실행…

    즉 작업의 실행 순서를 보장하지 않는다.

    🌟 setState() 함수의 인자로 함수를 전달하면 이전 state 값을 쉽게 가져올 수 있음

    state를 변경한 후 변경된 state를 사용해야 할 경우(state의 업데이트가 이전의 값에 의존):

    • Counter.js: Increase, Decrease
    import React, { useState } from 'react';
    
    function Counter() {
        const [number, setNumber] = useState(0);
    
        const onIncrease = () => {
            setNumber(preNumber => preNumber + 1);
        }
    
        const onDecrease = () => {
            setNumber(preNumber => preNumber - 1);
        }
    
        return(
            <div>
                <h2>{number}</h2>
                <button onClick={onIncrease}>Increase</button>
                <button onClick={onDecrease}>Decrease</button>
            </div>
        );
    }
    
    export default Counter;

    (3) state 값 변경: forceUpdate()

    forceUpdate() 함수를 사용하면 화면을 강제로 새로 고침하여 render() 함수 호출

    인스턴스 변수와 화면이 변경되어 출력됨.

    • ForceUpdate.js
    import React, { Component } from 'react';
    
    class ForceUpdate extends Components {
        constructor(props) {
            super(props);
            this.state = {
                stateString: 'react',
            }
        }
        StateChange = () => {
            this.state.stateString = 'React';
            this.forceUpdate();
        }
    
        render() {
            return(
                <div>
                    <button onClick={this.stateChange}>forceUpdate</button>
                    {this.state.stateString}                // react에서 React로 변경
                </div>
            );
        }
    }
    
    export default ForceUpdate;

    ※ forceUpdate()를 사용하는 것은 리액트 성능에 제약이 있으므로

    매번 화면을 새로 출력해야 하는 경우가 아니면 가급적 사용하지 않도록 함

  • React 기초

    1) 리액트의 특징

    (1) 컴포넌트(Component)

    재사용이 가능한 각각의 작고 독립적인 모듈. 마치 레고 블럭…

    이미 만들어진 컴포넌트들을 조합하여 화면을 효율적으로 구성하는 것이 리액트의 특징.

    🌟 컴포넌트의 구성요소

    • 프로퍼티(Props)
    • state
    • 컨텍스트(Context)

    (2) 가상돔(Virtual DOM)

    ✍🏻 브라우저에서 화면을 렌더링하는 과정

    서버로부터 받는 파일을 파싱하여 DOM Tree와 CSSOM Tree를 만들고 결합 : 렌더트리(Render Tree) 생성.

    생성한 Render Tree로  Layout을 계산하고 만든 후, 이 요소들을 실제로 화면을 그리는 Paint를 한다.

    DOM + CSSOM → Render Tree

    • DOM(Document Object Model) Tree

    HTML 파일을  파싱하여 구조화하여 표현한 것.

    • CSSOM(CSS Object Model) Tree

    CSS 파일을 DOM처럼 파싱하고 구조화하여 만든 것.

    DOM은 아주 작은 변경사항이 있더라도 항상 리렌더링을 하고 이때 문제점 발생

    = 화면이 커지면 커질수록 화면을 그리는 시간이 길어지는, 즉 속도가 느려지는 것.

    따라서 가상돔(Virtual DOM)을 사용.

    🌟 리액트가 가상돔(Virtual DOM)을 반영하는 절차

    ① 변경사항 발생 → 전체 UI를 Virtual DOM에 리렌더링.

    ② 이전 Virtual DOM과 현재를 비교 : 가상돔끼리 비교

    ③ 변경된 부분만 실제 DOM에 업데이트

    즉, 간단히 요약하자면:

    기존 : 화면의 일부가 수정되면 화면 전체를 업데이트.

    가상 DOM 도입 : 이전 UI에서 변경된 부분만 반영하여 업데이트

  • React props

    1. 프로퍼티(properties, props)

    상위 컴포넌트에서 하위 컴포넌트로 값을 전달할 때 사용(단방향 데이터 흐름).

    프로퍼티 값은 수정 불가능, 즉 읽기 전용 데이터.

    1) 사용법

    상위 컴포넌트에서 Props를 지정하고 하위 컴포넌트에서 받은 Props 값을 렌더링.

    • 문자열 전달: 큰 따옴표(” “) 사용
    • 숫자형, boolean 등의 값(문자열 외의 값) 전달: 중괄호( { } ) 사용

    (1) 단일값 전달

    • App.js
    import React from 'react';
    import Hello from "../src/Hello";
    
    function App() {
        return (
                <Hello name="React"></Hello>//사용할 컴포넌트 props의 name 값을 "React"로
            );
    }
    
    export default App;
    • Header.js
    import React from 'react';
    
    function Hello(props) {
        return <div>Hello, {props.name}</div>;// name 값을 조회하기 위해 props.name 이용.
    }
    
    export default Hello;

    (2) 여러값 전달

    • App.js
    import React from 'react';
    import Hello from '../src/Hello';
    
    function App() {
        return (
                <Hello name="React" color="blue"/>
            );
    }
    
    export default App;
    • Hello.js
    import React from 'react';
    
    function Hello(props){
        return(
                <div style={{ color: props.color }}>Hello, {props.name}</div>
            );
    }
    
    export default Hello;

    이런 경우 객체값을 부를 때마다 매번 props에서 불러와야 함.

    비구조화 할당 방식으로 코드를 바꾸면:

    import React from 'react';
    
    function Hello({name, color}){
        return(
                <div style={{ color: color }}>Hello, {name}</div>//동일하게 <div style={{ color }}Hello, {name}</div>
            );
    }
    
    export default Hello;

    (3) 기본값 설정: defaultProps

    • Hello.js
    import React from 'react';
    
    function Hello( {color, name} ) {
        return <div style={{ color }}>Hello, {name}</div>;
    }
    
    Hello.defaultProps = {
        name: 'noname'//defaultProps로 기본값 설정.
    }
    
    export default Hello;

    (4) 컴포넌트 태그 내부의 값 조회: props.children

    예를 들어, 스타일 정의된 파일 Wrapper.js를 App.js의 컴퍼넌트로 추가한다면,

    • Wrapper.js
    import React from "react";
    
    function Wrapper() {
        const style = {
            border: '2px solid black',
            padding: 20
        };
        return <div style={style}><div>
    }
    
    export default Wrapper;
    • App.js
    import React from "react";
    import Hello from "./Hello";
    import Wrapper from "./Wrapper";
    
    function App() {
        return (
            <Wrapper>
                <Hello name="React" color="blue"></Header>
            </Wraaper>
        );
    }
    
    export default App;

    하지만 이렇게 작성된 Wrapper 컴포넌트를 App.js에 추가하면 Wrapper 태그 내부에서 작성한 값들을 출력 X

    이럴 때, {children}을 사용하여 태그 내부를 보이도록 만들어주도록 한다.

    따라서 Wrapper.js를 다음과 같이 수정한다:

    • Wrapper.js
    import React from 'react';
    
    function Wrapper({ children }) {
        const style = {
            border: '2px solid black',
            padding: 20
        };
    
        return (
            <div style={style}>
                {children}
            </div>
        )
    }
    
    export default Wrapper;
  • React CRA

    1. CRA(create-react-app)

    React 개발 환경을 쉽게 구축해주는 도구.

    💻 CRA를 통해 자동으로 구축되는 요소:

    • webpack: 모듈 번들러
    • babel: JSX를 JavaScript로 컴파일
    • jest: 기능 테스트
    • eslint: 코드 교정 및 스타일 맞추기(형상관리)
    • polyfill: 구형 브라우저에서 지원하지 않는 문법(기능) 지원
    • HMR(Hot Module Replacement): reload 없이 변경사항 반영
    • CSS 후처리: sass 사용시 CSS 컴파일, 구형 브라우저에는 Vendor 접두사(perfix) 필요

    ※ 단점: webpack, babel, eslint 등 설정 변경하기 어려움.(설정을 변경할 경우 eject)

    1. CRA 사용
    npx create-react-app 프로젝트폴더명

    🌟 CRA는 서버사이드 렌더링(SSR)을 지원하지 않는다

    CRA에 eject하여 webpack과 babel을 설정하면 서버사이드 렌더링을 구현할 수는 있으나 효율적이지는 않으므로

    서버사이드 렌더링이 필요하면 SSR을 기본적으로 제공하는 Next.js를 사용하는 것을 권장.

    1. CRA scripts

    🌟package.json

      "scripts": {
        "start": "react-scripts start",
        "build": "react-scripts build",
        "test": "react-scripts test",
        "eject": "react-scripts eject"
      }

    1) start

    • React 개발 서버 구동.
    • 기본 http로 실행

    ✍🏻 https 실행 원하는 경우

    • CMD
    set HTTPS=true && npm start
    • Power Shell
    ($env:HTTPS = "true") -and (npm start)
    • Linux, MacOS
    HTTPS=true npm start

    2) build

    • 실제 서버 배포시 사용.
    • 빌드시 정적파일 생성(/build 파일)
    • 서버에 별도의 애플리케이션 실행 없음

    3) test

    • 테스트 실행
    • 기본으로 존재하는 App.test.hs 파일 테스트 실행.
    • 파일이름.test.js, 파일이름.spec.js 형식 테스트 파일이면 인식 가능하여 테스트실행됨.

    4) eject

    • react-scripts를 사용하지 않고 모든 설정파일 추출
    • CRA를 기본으로 직접 개발 환경 설정 및 구축하고 싶을 경우 사용
    • 한 번 실행하면 되돌릴 수 없음
    1. 환경변수

    1) NODE_ENV 환경변수

    CRA에서 기본적으로 가지고 있는 환경변수, 실행 명령어에 따라 자동으로 NODE_ENV 값이 정해짐.

    process.env.NODE_ENV

    (1) 개발 환경: development

    npm start로 실행할 경우

    (2) 테스트 환경: test

    npm test로 실행할 경우

    (3) 배포 환경: production

    npm run build로 실행할 경우

    2) 새로운 환경변수 설정

    React 환경 변수명은 생성시 REACTAPP 으로 시작.

  • React Component

    1. 컴포넌트(Component)

    재사용이 가능한 각각의 작고 독립적인 모듈.

    리액트로 개발한 앱을 이루는 가장 작은 단위, 조각.

    레고블럭으로 비유할 때, 레고블럭으로 만든 집은 리액트 앱으로 보고

    집을 구성하는 하나의 작은 블록들은 컴포넌트라고 할 수 있음.

    1) 왜 컴포넌트인가

    기존 웹 프레임워크는 MVC(Model, View, Controller) 방식으로 분리하여 관리.

    따라서 각 요소들이 독립적이지 않고 의존성이 높아서 재사용이 어려움.

    → 컴포넌트는 View를 독립적으로 구성하여 재사용 가능!

    2) 컴포넌트 사용

    컴포넌트의 이름은 항상 대문자로 시작.

    ※ React에서 소문자로 시작하는 컴포넌트는 DOM 태그로 인식함.

    🌟 컴포넌트의 구성요소

    • 프로퍼티(Props)
    • state
    • 컨텍스트(Context)
    1. 컴포넌트의 선언방식

    1) 함수형 컴포넌트(Functional Component)

    • state와 LifeCycle API 사용이 불가능했으나 Hook 기능으로 사용 가능 .
    • 클래스형 컴포넌트보다 선언하기 편함.
    • 클래스형 컴포넌트보다 메모리 자원을 덜 사용함

    (1) 함수형 컴포넌트 선언

    import React from 'react';
    
    const App = () => {
        return(
            <div></div>
        );
    }
    
    export default App;

    2) 클래스형 컴포넌트(Class Component)

    • state와 LifeCycle API 사용 가능.
    • 임의 메서드 정의 가능.

    (1) 클래스형 컴포넌트 선언

    • class 키워드 필요
    • Component를 상속 받아야 함.
    • render() 메소드 반드시 필요.
    import React, { Component } from 'react';
    
    class App extends Component {
        render() {
                return(
                <div></div>
                );
        }
    }
    
    export default App;

    ※ 클래스 컴포넌트로 개발을 진행한 프로젝트의 유지보수를 위해 알아둘 것

  • React 실전 가이드: 입문

    React의 세계에 오신 것을 환영합니다! 이 가이드는 React를 처음 시작하는 분들을 위해 만들어졌습니다. 가장 기본적인 개념부터 차근차근 알아보고, 직접 코드를 작성하며 React와 친해져 봅시다.

    목차

    1. React, 왜 배워야 할까요?

      • React란?
      • React의 핵심 장점
    2. 첫 React 프로젝트 시작하기

      • 개발 환경 준비
      • create-react-app으로 프로젝트 생성
    3. React의 핵심 문법 3가지

      • JSX: JavaScript와 HTML을 한번에
      • 컴포넌트: 재사용 가능한 UI 조각
      • Props와 State: 데이터를 다루는 방법

    4. 간단한 예제: 나만의 카운터 만들기



    1. React, 왜 배워야 할까요?

    1.1. React란?

    React는 페이스북에서 개발한 사용자 인터페이스(UI)를 만들기 위한 JavaScript 라이브러리입니다. 웹사이트의 ‘보이는 부분’을 쉽고 효율적으로 만들 수 있도록 도와주는 도구라고 생각하면 됩니다.

    1.2. React의 핵심 장점

    • 컴포넌트 기반: 레고 블록처럼 UI를 여러 개의 독립적인 ‘컴포넌트’로 나누어 만듭니다. 덕분에 코드를 재사용하기 쉽고, 유지보수가 편리해집니다.
    • 빠른 속도: 가상돔(Virtual DOM)을 사용하여 변경된 부분만 실제 화면에 업데이트하므로, 불필요한 작업을 줄여 빠른 성능을 보여줍니다.
    • 선언형 코드: “이렇게 저렇게 해서 이걸 만들어줘”가 아니라, “이러한 상태일 때, 화면은 이 모습이어야 해”라고 선언적으로 코드를 작성합니다. 코드가 훨씬 직관적이고 예측 가능해집니다.

    2. 첫 React 프로젝트 시작하기

    2.1. 개발 환경 준비

    React 개발을 위해서는 컴퓨터에 Node.jsnpm(또는 yarn)이 설치되어 있어야 합니다. Node.js를 설치하면 npm은 자동으로 함께 설치됩니다.

    2.2. create-react-app으로 프로젝트 생성

    가장 쉬운 방법은 React 팀에서 공식적으로 제공하는 create-react-app 도구를 사용하는 것입니다. 터미널(명령 프롬프트 또는 PowerShell)을 열고 다음 명령어를 순서대로 입력하세요.

    # 1. 'my-first-react-app'이라는 이름의 React 프로젝트를 생성합니다.
    npx create-react-app my-first-react-app
    
    # 2. 생성된 프로젝트 폴더로 이동합니다.
    cd my-first-react-app
    
    # 3. 개발 서버를 실행합니다.
    npm start

    npm start 명령어를 실행하면, 잠시 후 브라우저에 React 로고가 빙글빙글 돌아가는 시작 페이지가 나타날 것입니다. 첫 React 프로젝트가 성공적으로 실행된 것입니다!


    3. React의 핵심 문법 3가지

    3.1. JSX: JavaScript와 HTML을 한번에

    JSX는 JavaScript 파일 안에서 HTML과 유사한 코드를 작성할 수 있게 해주는 문법입니다.

    // 일반 JavaScript 변수
    const name = "React";
    
    // 변수를 포함한 JSX 코드
    const element = <h1>Hello, {name}!</h1>; // 결과: <h1>Hello, React!</h1>

    주의할 점:

    • HTML의 class 속성은 JSX에서 className으로 써야 합니다. (class는 JavaScript의 예약어이기 때문입니다.)
    • 모든 태그는 반드시 닫혀있어야 합니다. (예: <br> -> <br />)

    3.2. 컴포넌트: 재사용 가능한 UI 조각

    컴포넌트는 React의 심장입니다. UI를 독립적인 단위로 쪼개어 관리할 수 있게 해줍니다. 현재는 주로 함수형 컴포넌트를 사용합니다.

    // 'Welcome'이라는 이름의 간단한 컴포넌트
    function Welcome(props) {
      return <h1>Hello, {props.name}</h1>;
    }
    
    // 컴포넌트 사용하기
    function App() {
      return (
        <div>
          <Welcome name="Sara" />
          <Welcome name="Cahal" />
          <Welcome name="Edite" />
        </div>
      );
    }

    3.3. Props와 State: 데이터를 다루는 방법


    • Props (Properties): 부모 컴포넌트가 자식 컴포넌트에게 물려주는 데이터입니다. 위 예제의 name="Sara"가 바로 props입니다. Props는 자식 컴포넌트 안에서 절대 직접 수정할 수 없습니다.



    • State (상태): 컴포넌트가 자체적으로 가지는 내부 데이터입니다. 사용자의 입력이나 시간의 흐름에 따라 변할 수 있습니다. State가 변경되면, React는 컴포넌트를 화면에 다시 그려줍니다(리렌더링). State는 useState라는 Hook을 사용해 만듭니다.



    4. 간단한 예제: 나만의 카운터 만들기

    지금까지 배운 개념을 모두 활용하여 간단한 카운터 컴포넌트를 만들어 보겠습니다. src/App.js 파일의 내용을 아래 코드로 바꿔보세요.

    import React, { useState } from 'react'; // useState를 import 합니다.
    import './App.css';
    
    function App() {
      // 'count'라는 이름의 state를 만들고, 초기값을 0으로 설정합니다.
      // setCount는 count 값을 변경할 때 사용할 함수입니다.
      const [count, setCount] = useState(0);
    
      return (
        <div className="App">
          <header className="App-header">
            {/* state인 count 값을 화면에 보여줍니다. */}
            <p>You clicked {count} times</p>
    
            {/* 버튼을 클릭하면 setCount 함수를 호출하여 count 값을 1 증가시킵니다. */}
            <button onClick={() => setCount(count + 1)}>
              Click me
            </button>
          </header>
        </div>
      );
    }
    
    export default App;

    코드를 저장하면 브라우저 화면이 자동으로 새로고침되고, ‘Click me’ 버튼이 있는 카운터가 나타날 것입니다. 버튼을 누를 때마다 숫자가 올라가는 것을 확인해보세요. 이것이 바로 State가 변경될 때 React가 화면을 업데이트하는 방식입니다.

    이제 여러분은 React의 가장 중요한 첫걸음을 내디뎠습니다. 이 기본기를 바탕으로 더 복잡하고 멋진 웹 애플리케이션을 만들어 나갈 수 있을 것입니다.