<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>odada</title>
    <link>https://oddcode.tistory.com/</link>
    <description>#프론트앤드 #Interactive Web #WebDesign #웹표준 # 웹접근성</description>
    <language>ko</language>
    <pubDate>Wed, 13 May 2026 09:33:57 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>oodada</managingEditor>
    <image>
      <title>odada</title>
      <url>https://tistory1.daumcdn.net/tistory/3618661/attach/f3993e295915473cb5acd629fca8dda8</url>
      <link>https://oddcode.tistory.com</link>
    </image>
    <item>
      <title>프론트엔드 개발 월별 계획</title>
      <link>https://oddcode.tistory.com/371</link>
      <description>&lt;h2&gt;Phase 1: 프로젝트 초기 설정 (1월 3주차)&lt;/h2&gt;
&lt;h3&gt;1주차: 개발 환경 구성&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Next.js 프로젝트 생성 및 기본 설정&lt;/li&gt;
&lt;li&gt;TypeScript, ESLint, Prettier 설정&lt;/li&gt;
&lt;li&gt;shadcn/ui, Tailwind CSS 설정&lt;/li&gt;
&lt;li&gt;기본 디렉토리 구조 설정&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2주차: 기본 컴포넌트 및 상태관리 설정&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;공통 컴포넌트 개발&lt;ul&gt;
&lt;li&gt;Layout (Header, Sidebar)&lt;/li&gt;
&lt;li&gt;Button, Input, Table 등 기본 UI 컴포넌트&lt;/li&gt;
&lt;li&gt;Form 컴포넌트&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Zustand 상태관리 설정&lt;ul&gt;
&lt;li&gt;인증 상태 관리&lt;/li&gt;
&lt;li&gt;UI 상태 관리&lt;/li&gt;
&lt;li&gt;기본 데이터 상태 관리&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Phase 2: 페이지 구현 (2월 ~ 4월)&lt;/h2&gt;
&lt;h3&gt;2월: 인증 및 기본 페이지&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;로그인/회원가입 페이지&lt;/li&gt;
&lt;li&gt;대시보드 레이아웃&lt;/li&gt;
&lt;li&gt;My Page 구현&lt;/li&gt;
&lt;li&gt;메인 대시보드 UI&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3월: Order &amp;amp; RMA 페이지&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Order 관련 페이지&lt;ul&gt;
&lt;li&gt;제품 리스트 페이지&lt;/li&gt;
&lt;li&gt;주문 프로세스 페이지&lt;/li&gt;
&lt;li&gt;주문 이력 페이지&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;RMA 관련 페이지&lt;ul&gt;
&lt;li&gt;RMA 신청 페이지&lt;/li&gt;
&lt;li&gt;RMA 상태 조회 페이지&lt;/li&gt;
&lt;li&gt;RMA 이력 페이지&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;4월: Inventory &amp;amp; Training 페이지&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Inventory 관련 페이지&lt;ul&gt;
&lt;li&gt;재고 조회 페이지&lt;/li&gt;
&lt;li&gt;입/출고 관리 페이지&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Training 관련 페이지&lt;ul&gt;
&lt;li&gt;교육 과정 목록 페이지&lt;/li&gt;
&lt;li&gt;교육 신청 페이지&lt;/li&gt;
&lt;li&gt;수료증 발급 페이지&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Phase 3: 기능 고도화 (5월 ~ 6월)&lt;/h2&gt;
&lt;h3&gt;5월: 검색 및 필터링&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;검색 기능 구현&lt;ul&gt;
&lt;li&gt;전역 검색&lt;/li&gt;
&lt;li&gt;페이지별 상세 검색&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;필터링 기능 구현&lt;ul&gt;
&lt;li&gt;날짜 필터&lt;/li&gt;
&lt;li&gt;상태 필터&lt;/li&gt;
&lt;li&gt;카테고리 필터&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;정렬 기능 구현&lt;/li&gt;
&lt;li&gt;페이지네이션 구현&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;6월: 데이터 관리 및 사용자 경험&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;데이터 테이블 기능 확장&lt;ul&gt;
&lt;li&gt;데이터 정렬&lt;/li&gt;
&lt;li&gt;컬럼 커스터마이징&lt;/li&gt;
&lt;li&gt;CSV 내보내기&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;차트 및 데이터 시각화&lt;/li&gt;
&lt;li&gt;사용자 설정 기능&lt;/li&gt;
&lt;li&gt;알림 시스템 구현&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Phase 4: API 통신 및 안정화 (7월)&lt;/h2&gt;
&lt;h3&gt;7월 1-2주차: API 통합&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Mock API를 실제 API로 교체&lt;/li&gt;
&lt;li&gt;SAP 연동&lt;ul&gt;
&lt;li&gt;Customer Code 검증&lt;/li&gt;
&lt;li&gt;Part Number 검증&lt;/li&gt;
&lt;li&gt;Order 상태 연동&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;MES/PLM 연동&lt;ul&gt;
&lt;li&gt;DOA 처리&lt;/li&gt;
&lt;li&gt;Part 사양 조회&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;에러 처리 구현&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;7월 3-4주차: 테스트 및 안정화&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;단위 테스트 작성&lt;/li&gt;
&lt;li&gt;통합 테스트&lt;/li&gt;
&lt;li&gt;성능 최적화&lt;/li&gt;
&lt;li&gt;버그 수정&lt;/li&gt;
&lt;li&gt;문서화&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;세부 작업 우선순위&lt;/h2&gt;
&lt;h3&gt;1순위: 핵심 기능&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;로그인/인증&lt;/li&gt;
&lt;li&gt;Order 시스템&lt;/li&gt;
&lt;li&gt;RMA 관리&lt;/li&gt;
&lt;li&gt;Inventory 조회&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2순위: 확장 기능&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Training 시스템&lt;/li&gt;
&lt;li&gt;상세 검색/필터링&lt;/li&gt;
&lt;li&gt;데이터 시각화&lt;/li&gt;
&lt;li&gt;사용자 설정&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3순위: 부가 기능&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;알림 시스템&lt;/li&gt;
&lt;li&gt;데이터 내보내기&lt;/li&gt;
&lt;li&gt;리포트 생성&lt;/li&gt;
&lt;li&gt;관리자 기능&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;협업 포인트&lt;/h2&gt;
&lt;h3&gt;백엔드 팀과의 협업&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;API 스펙 논의: 2월 말&lt;/li&gt;
&lt;li&gt;연동 테스트: 6월 초&lt;/li&gt;
&lt;li&gt;통합 테스트: 7월 초&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;디자인 팀과의 협업&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;디자인 시스템 협의: 1월 말&lt;/li&gt;
&lt;li&gt;컴포넌트 디자인 검토: 2월 초&lt;/li&gt;
&lt;li&gt;UI/UX 피드백: 매월 말&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;기획팀과의 협업&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;요구사항 구체화: 프로젝트 시작 전&lt;/li&gt;
&lt;li&gt;기능 우선순위 조정: 매월 초&lt;/li&gt;
&lt;li&gt;사용성 테스트: 6월 말&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;위험 요소 및 대응 계획&lt;/h2&gt;
&lt;h3&gt;예상 위험&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;API 연동 지연&lt;/li&gt;
&lt;li&gt;복잡한 비즈니스 로직&lt;/li&gt;
&lt;li&gt;성능 이슈&lt;/li&gt;
&lt;li&gt;브라우저 호환성&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;대응 방안&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;Mock 데이터를 활용한 병행 개발&lt;/li&gt;
&lt;li&gt;단계적 기능 구현&lt;/li&gt;
&lt;li&gt;초기부터 성능 고려&lt;/li&gt;
&lt;li&gt;크로스 브라우저 테스트 자동화&lt;/li&gt;
&lt;/ol&gt;</description>
      <author>oodada</author>
      <guid isPermaLink="true">https://oddcode.tistory.com/371</guid>
      <comments>https://oddcode.tistory.com/371#entry371comment</comments>
      <pubDate>Wed, 15 Jan 2025 13:52:14 +0900</pubDate>
    </item>
    <item>
      <title>Next.js와 MongoDB 연동하여 CRUD API 만들기</title>
      <link>https://oddcode.tistory.com/370</link>
      <description>&lt;h2&gt;0. 주요 변경사항 정리&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;MongoDB 연결 설정 추가&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;환경 변수 (&lt;code&gt;MONGODB_URI&lt;/code&gt;) 설정&lt;/li&gt;
&lt;li&gt;데이터베이스 연결 유틸리티 생성&lt;/li&gt;
&lt;li&gt;Mongoose 모델 정의&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;API 라우트 변경&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;MongoDB CRUD 작업으로 변경&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;클라이언트 컴포넌트 수정&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;id&lt;/code&gt; 대신 &lt;code&gt;_id&lt;/code&gt; 사용&lt;/li&gt;
&lt;li&gt;날짜 형식 처리 추가&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;1. MongoDB 설정&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://www.mongodb.com/products/platform/atlas-database&quot;&gt;MongoDB Atlas&lt;/a&gt; 가입하기&lt;/p&gt;
&lt;h3&gt;1-1. MongoDB Atlas 설정&lt;/h3&gt;
&lt;h4&gt;1. MongoDB Atlas에서 연결 문자열을 가져오는 방법:&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&amp;quot;Connect&amp;quot; 버튼을 클릭합니다&lt;/li&gt;
&lt;li&gt;&amp;quot;Connect your application&amp;quot;을 선택합니다&lt;/li&gt;
&lt;li&gt;Driver는 &amp;quot;Node.js&amp;quot;를 선택합니다&lt;/li&gt;
&lt;li&gt;거기서 보이는 연결 문자열을 복사합니다 (예: &lt;code&gt;mongodb+srv://username:&amp;lt;password&amp;gt;@cluster0...&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/odada/post/1b090da8-7b9a-40a3-82a1-3ff572b558f6/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/odada/post/80f0272a-121f-408b-91dc-e416aefbce43/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/odada/post/d34ee5b7-79e7-4391-a8cc-22a5a07c44b2/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h4&gt;2. 프로젝트에 환경 변수 설정:&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;프로젝트 루트 폴더에 &lt;code&gt;.env&lt;/code&gt; 파일을 생성합니다&lt;/li&gt;
&lt;li&gt;아래와 같이 작성합니다:&lt;pre&gt;&lt;code&gt;MONGODB_URI=mongodb+srv://username:&amp;lt;password&amp;gt;@cluster0.xxxxx.mongodb.net/&amp;lt;database&amp;gt;?retryWrites=true&amp;amp;w=majority&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;password&amp;gt;&lt;/code&gt;는 MongoDB Atlas에서 생성한 데이터베이스 사용자의 실제 비밀번호로 교체&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;database&amp;gt;&lt;/code&gt;는 사용하고 싶은 데이터베이스 이름으로 교체 (예: &amp;quot;blog&amp;quot;)&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;3. Network Access 설정:&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;왼쪽 메뉴에서 &amp;quot;Network Access&amp;quot;를 클릭합니다&lt;/li&gt;
&lt;li&gt;&amp;quot;Add IP Address&amp;quot; 버튼 클릭&lt;/li&gt;
&lt;li&gt;개발 중이므로 &amp;quot;Allow Access from Anywhere&amp;quot;를 선택하고 &amp;quot;0.0.0.0/0&amp;quot; 입력&lt;/li&gt;
&lt;li&gt;Confirm 버튼 클릭&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/odada/post/e9799c56-f9ac-41c0-975d-bdb0db34992d/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h4&gt;4. Database Access 설정:&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;왼쪽 메뉴에서 &amp;quot;Database Access&amp;quot;를 클릭합니다&lt;/li&gt;
&lt;li&gt;&amp;quot;Add New Database User&amp;quot; 버튼 클릭&lt;/li&gt;
&lt;li&gt;Username과 Password를 설정합니다 (이 정보는 연결 문자열에 사용됩니다)&lt;/li&gt;
&lt;li&gt;&amp;quot;Add User&amp;quot; 버튼 클릭&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/odada/post/72976a08-0686-49d2-91e9-3523d668c9ee/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h3&gt;1-2. 프로젝트에 MongoDB 설치&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm install mongodb mongoose&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;1-3. 환경 변수 설정&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;.env&lt;/code&gt; 파일 생성:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;MONGODB_URI=mongodb+srv://&amp;lt;username&amp;gt;:&amp;lt;password&amp;gt;@cluster0.xxxxx.mongodb.net/&amp;lt;database&amp;gt;?retryWrites=true&amp;amp;w=majority&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;2. MongoDB 연결 설정&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;연결 유틸리티는 &lt;strong&gt;&amp;quot;데이터베이스로 가는 도로&amp;quot;&lt;/strong&gt; 같은 것&lt;/li&gt;
&lt;li&gt;모델은 &lt;strong&gt;&amp;quot;데이터를 담을 상자의 설계도&amp;quot;&lt;/strong&gt; 같은 것&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2-1. MongoDB 연결 유틸리티 생성&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;역할 : 데이터 베이스 연결을 관리&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;주요 기능&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;MongoDB와의 연결 상태를 관리&lt;/strong&gt; : 매 요청마다 새로운 연결을 만들지 않음&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;연결이 이미 있으면 재사용(캐싱)&lt;/strong&gt; : 연결을 재사용하여 성능 향상&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;없으면 새로 연결&lt;/strong&gt; : 안정적인 데이터 베이스 연결 유지&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;작동방식&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;if (이미_연결되어있나?) {
기존_연결_재사용();
} else {
새로운_연결_생성();
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href=&quot;https://mongoosejs.com/docs/connections.html&quot;&gt;moongose를 사용하여 MongoDB와 연결 Doc&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;src/lib/mongodb.js&lt;/code&gt; 파일 생성:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;import mongoose from &amp;#39;mongoose&amp;#39;;

// MongoDB 연결 문자열을 환경변수에서 가져옴
const MONGODB_URI = process.env.MONGODB_URI;

// 연결 문자열이 없으면 에러 발생
if (!MONGODB_URI) {
  throw new Error(&amp;#39;MONGODB_URI must be defined&amp;#39;);
}

// 전역 변수에 mongoose 연결 정보를 저장
// 이렇게 하면 서버가 재시작되어도 연결이 유지됨
let cached = global.mongoose;

// 처음 실행될 때는 cached가 없으므로 초기화
if (!cached) {
  cached = global.mongoose = {
    conn: null,     // 현재 연결 객체
    promise: null   // 연결 시도중인 Promise
  };
}

async function connectDB() {
  // 이미 연결되어 있다면 그 연결을 재사용
  if (cached.conn) {
    return cached.conn;
  }

  // 연결 시도중이 아니라면 새로운 연결 시도
  if (!cached.promise) {
    const opts = {
      bufferCommands: false,  // 연결되기 전에 명령어 버퍼링 비활성화
    };

    // MongoDB 연결 시도
    cached.promise = mongoose.connect(MONGODB_URI, opts).then((mongoose) =&amp;gt; {
      return mongoose;
    });
  }

  try {
    // 연결이 완료될 때까지 대기
    cached.conn = await cached.promise;
  } catch (e) {
    // 연결 실패시 promise 초기화하고 에러 던지기
    cached.promise = null;
    throw e;
  }

  return cached.conn;
}

export default connectDB;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2-2. Post 모델 생성&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;역할 : MongoDB의 데이터를 다루는 객체&lt;/li&gt;
&lt;li&gt;주요 기능&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;게시글의 데이터 구조 정의 (제목, 내용, 날짜 등)&lt;/strong&gt; : 일관된 데이터 구조 유지&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;필수 입력값 지정&lt;/strong&gt; : 데이터 유효성 검사 자동화&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;데이터 유효성 검사 규칙 설정&lt;/strong&gt; : MongoDB 작업을 더 쉽게 수행&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;예시&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;{
title: { // 제목 필드
  type: String,        // 문자열 타입
  required: true,      // 필수 입력
  trim: true          // 앞뒤 공백 제거
},
content: { // 내용 필드
  type: String,
  required: true
}
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;models/Post.js&lt;/code&gt; 파일 생성:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;import mongoose from &amp;#39;mongoose&amp;#39;;

// 이미 모델이 있다면 그것을 사용, 없다면 새로 생성
const PostSchema = new mongoose.Schema({
  title: {
    type: String,
    required: [true, &amp;#39;제목을 입력해주세요.&amp;#39;], // 필수 입력
    trim: true, // 앞뒤 공백 제거
  },
  content: {
    type: String,
    required: [true, &amp;#39;내용을 입력해주세요.&amp;#39;],
  },
  createdAt: {
    type: Date,
    default: Date.now, // 기본값은 현재 시간
  }
});

// 모델이 이미 있다면 그것을 사용, 없다면 새로 생성
export default mongoose.models.Post || mongoose.model(&amp;#39;Post&amp;#39;, PostSchema);&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;3. API 라우트 수정&lt;/h2&gt;
&lt;h3&gt;3-1. 전체 게시글 API (&lt;code&gt;app/api/posts/route.js&lt;/code&gt;)&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;import { NextResponse } from &amp;#39;next/server&amp;#39;;
// MongoDB 연결을 위한 유틸리티 함수 가져오기
import connectDB from &amp;#39;@/lib/mongodb&amp;#39;;
// MongoDB의 Post 모델 가져오기 
import Post from &amp;#39;@/models/Post&amp;#39;;

export async function GET() {
 try {
   // MongoDB 연결
   await connectDB();
   // Post 모델을 사용해 모든 게시글을 찾고, 생성일 기준 내림차순 정렬
   const posts = await Post.find({}).sort({ createdAt: -1 });
   return NextResponse.json(posts);
 } catch (error) {
   return NextResponse.json(
     { error: &amp;#39;게시글을 불러오는데 실패했습니다.&amp;#39; },
     { status: 500 }
   );
 }
}

export async function POST(req) {
 try {
   // MongoDB 연결
   await connectDB();
   const data = await req.json();

   if (!data.title || !data.content) {
     return NextResponse.json(
       { error: &amp;#39;제목과 내용은 필수입니다.&amp;#39; },
       { status: 400 }
     );
   }

   // Post 모델을 사용해 새 게시글 생성
   const post = await Post.create(data);
   return NextResponse.json(post, { status: 201 });
 } catch (error) {
   return NextResponse.json(
     { error: &amp;#39;게시글 작성에 실패했습니다.&amp;#39; },
     { status: 500 }
   );
 }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3-2. 개별 게시글 API (&lt;code&gt;app/api/posts/[id]/route.js&lt;/code&gt;)&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;import { NextResponse } from &amp;#39;next/server&amp;#39;;
import Post from &amp;#39;@/models/Post&amp;#39;;
// MongoDB의 ObjectId 검증을 위한 mongoose import
import mongoose from &amp;#39;mongoose&amp;#39;;
// MongoDB 연결을 위한 유틸리티 함수 가져오기
import connectDB from &amp;#39;@/lib/mongodb&amp;#39;;

// MongoDB의 ObjectId가 유효한지 검사하는 함수
const isValidObjectId = (id) =&amp;gt; mongoose.Types.ObjectId.isValid(id);

export async function GET(req, { params }) {
 try {
   // MongoDB 연결
   await connectDB();

   // Next.js 13에서는 params를 비동기로 처리해야 함
   const resolvedParams = await Promise.resolve(params);

   // 게시글 ID가 유효한 MongoDB ObjectId 형식인지 검사
   if (!isValidObjectId(resolvedParams.id)) {
     return NextResponse.json(
       { error: &amp;#39;유효하지 않은 게시글 ID입니다.&amp;#39; },
       { status: 400 }
     );
   }

   // Post 모델을 사용해 특정 ID의 게시글 찾기
   const post = await Post.findById(resolvedParams.id);
   if (!post) {
     return NextResponse.json(
       { error: &amp;#39;게시글을 찾을 수 없습니다.&amp;#39; },
       { status: 404 }
     );
   }

   return NextResponse.json(post);
 } catch (error) {
   return NextResponse.json(
     { error: &amp;#39;게시글을 불러오는데 실패했습니다.&amp;#39; },
     { status: 500 }
   );
 }
}

export async function PUT(req, { params }) {
 try {
   // MongoDB 연결
   await connectDB();
   const resolvedParams = await Promise.resolve(params);

   // ID 유효성 검사
   if (!isValidObjectId(resolvedParams.id)) {
     return NextResponse.json(
       { error: &amp;#39;유효하지 않은 게시글 ID입니다.&amp;#39; },
       { status: 400 }
     );
   }

   const data = await req.json();
   // findByIdAndUpdate: ID로 게시글을 찾아 업데이트
   // $set: MongoDB 업데이트 연산자, new: true는 업데이트된 문서 반환
   const post = await Post.findByIdAndUpdate(
     resolvedParams.id,
     { $set: data },
     { new: true, runValidators: true }
   );

   if (!post) {
     return NextResponse.json(
       { error: &amp;#39;게시글을 찾을 수 없습니다.&amp;#39; },
       { status: 404 }
     );
   }

   return NextResponse.json(post);
 } catch (error) {
   return NextResponse.json(
     { error: &amp;#39;게시글 수정에 실패했습니다.&amp;#39; },
     { status: 500 }
   );
 }
}

export async function DELETE(req, { params }) {
 try {
   // MongoDB 연결
   await connectDB();
   const resolvedParams = await Promise.resolve(params);

   // ID 유효성 검사
   if (!isValidObjectId(resolvedParams.id)) {
     return NextResponse.json(
       { error: &amp;#39;유효하지 않은 게시글 ID입니다.&amp;#39; },
       { status: 400 }
     );
   }

   // findByIdAndDelete: ID로 게시글을 찾아 삭제
   const post = await Post.findByIdAndDelete(resolvedParams.id);
   if (!post) {
     return NextResponse.json(
       { error: &amp;#39;게시글을 찾을 수 없습니다.&amp;#39; },
       { status: 404 }
     );
   }

   return NextResponse.json({ message: &amp;#39;게시글이 삭제되었습니다.&amp;#39; });
 } catch (error) {
   return NextResponse.json(
     { error: &amp;#39;게시글 삭제에 실패했습니다.&amp;#39; },
     { status: 500 }
   );
 }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;4. 클라이언트 컴포넌트 수정&lt;/h2&gt;
&lt;p&gt;기존 클라이언트 컴포넌트의 대부분은 그대로 사용할 수 있습니다. MongoDB의 &lt;code&gt;_id&lt;/code&gt;를 사용하도록 수정이 필요한 부분만 변경하면 됩니다.&lt;/p&gt;
&lt;h3&gt;4-1. 게시글 목록 페이지 수정 (&lt;code&gt;app/posts/page.js&lt;/code&gt;)&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// posts.map() 부분 수정
// mongodb는 자동으로 생성하는 고유 식별자를 _id로 사용
{posts.map((post) =&amp;gt; (
  &amp;lt;Link
      key={post._id} // id 대신 _id 사용
      href={`/posts/${post._id}`} // id 대신 _id 사용
      className=&amp;quot;cursor-pointer block&amp;quot;  // block 추가하여 전체 영역 클릭 가능하게
    &amp;gt;
    &amp;lt;h2&amp;gt;{post.title}&amp;lt;/h2&amp;gt;
    &amp;lt;p&amp;gt;{post.content}&amp;lt;/p&amp;gt;
    &amp;lt;span&amp;gt;{new Date(post.createdAt).toLocaleDateString()}&amp;lt;/span&amp;gt;
  &amp;lt;/Link&amp;gt;
))}&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4-2. 게시글 상세 페이지 수정 (&lt;code&gt;app/posts/[id]/page.js&lt;/code&gt;)&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// app/posts/[id]/edit/page.js
import { use } from &amp;quot;react&amp;quot;;
import EditForm from &amp;quot;./editForm&amp;quot;;

export default function EditPage({ params }) {
  // next.js 13부터 params가 promise로 전달됨 (비동기 데이터)
  // Promise는 바로 사용할 수 없음
  // `use()` 훅을 사용하여 unwrap 해야 함
  const resolvedParams = use(params);
  return &amp;lt;EditForm postId={resolvedParams.id} /&amp;gt;;
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;axios&lt;/code&gt;로 받은 응답에는 여러 정보가 포함되어 있는데, 실제 데이터는 &lt;code&gt;data&lt;/code&gt; 속성에 들어있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;{
  data: {
    // 실제 서버에서 받은 데이터
    title: &amp;quot;게시글 제목&amp;quot;,
    content: &amp;quot;게시글 내용&amp;quot;
  },
  status: 200,        // HTTP 상태 코드
  statusText: &amp;quot;OK&amp;quot;,   // 상태 메시지
  headers: {},        // 응답 헤더
  config: {}          // 요청 설정
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// app/posts/[id]/editForm.js
&amp;#39;use client&amp;#39;;

// ... imports 동일

export default function EditForm({ postId }) {
  // ... router와 state 설정 동일

  useEffect(() =&amp;gt; {
    const fetchPost = async () =&amp;gt; {
      try {
        // MongoDB에 저장된 특정 게시글을 ID로 조회
        const { data } = await axios.get(`/api/posts/${postId}`);
        setTitle(data.title);
        setContent(data.content);
      } catch (error) {
        console.error(&amp;#39;Error fetching post:&amp;#39;, error);
        alert(&amp;#39;게시글을 불러올 수 없습니다.&amp;#39;);
        router.push(&amp;#39;/posts&amp;#39;);
      }
    };

    fetchPost();
  }, [postId, router]);

  const handleSubmit = async (e) =&amp;gt; {
    e.preventDefault();

    try {
      // MongoDB의 게시글 데이터 업데이트
      await axios.put(`/api/posts/${postId}`, { title, content });
      router.push(&amp;#39;/posts&amp;#39;);
    } catch (error) {
      console.error(&amp;#39;Error updating post:&amp;#39;, error);
      alert(&amp;#39;수정에 실패했습니다.&amp;#39;);
    }
  };

  // ... return 부분 동일
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4-2. 날짜 형식 수정&lt;/h3&gt;
&lt;p&gt;날짜 포맷팅 함수는 여러 컴포넌트에서 재사용할 수 있으므로, &lt;code&gt;utils&lt;/code&gt; 폴더에 따로 만드는 것이 좋습니다&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// utils/formatDate.js
export const formatDate = (date) =&amp;gt; {
  // MongoDB의 Date 객체를 로컬 시간으로 변환
  return new Date(date).toLocaleDateString(&amp;#39;ko-KR&amp;#39;, {
    year: &amp;#39;numeric&amp;#39;,
    month: &amp;#39;long&amp;#39;,
    day: &amp;#39;numeric&amp;#39;,
    hour: &amp;#39;2-digit&amp;#39;,
    minute: &amp;#39;2-digit&amp;#39;
  });
};&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// app/posts/page.js 또는 다른 컴포넌트
import { formatDate } from &amp;#39;@/utils/formatDate&amp;#39;;

// 컴포넌트 내부에서 사용
&amp;lt;span&amp;gt;{formatDate(post.createdAt)}&amp;lt;/span&amp;gt;
))}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Front/Node.js</category>
      <author>oodada</author>
      <guid isPermaLink="true">https://oddcode.tistory.com/370</guid>
      <comments>https://oddcode.tistory.com/370#entry370comment</comments>
      <pubDate>Thu, 26 Dec 2024 09:22:29 +0900</pubDate>
    </item>
    <item>
      <title>프론트엔드 개발자를 위한 MongoDB &amp;amp; Mongoose 가이드</title>
      <link>https://oddcode.tistory.com/369</link>
      <description>&lt;h1&gt;프론트엔드 개발자를 위한 MongoDB &amp;amp; Mongoose 가이드&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;MongoDB 공식 문서: &lt;a href=&quot;https://www.mongodb.com/docs/&quot;&gt;https://www.mongodb.com/docs/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Mongoose 공식 문서: &lt;a href=&quot;https://mongoosejs.com/docs/&quot;&gt;https://mongoosejs.com/docs/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;1. 데이터베이스가 뭔가요?&lt;/h2&gt;
&lt;p&gt;프론트엔드에서 우리는 보통 이런 형태로 데이터를 다룹니다:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;const posts = [
  {
    id: 1,
    title: &amp;quot;첫 번째 글&amp;quot;,
    content: &amp;quot;내용입니다&amp;quot;
  },
  {
    id: 2,
    title: &amp;quot;두 번째 글&amp;quot;,
    content: &amp;quot;내용입니다&amp;quot;
  }
];&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;하지만 이렇게 하면 서버를 재시작할 때마다 데이터가 사라지죠. 이 데이터를 영구적으로 저장하고 관리하는 곳이 바로 &amp;#39;데이터베이스&amp;#39;입니다.&lt;/p&gt;
&lt;h2&gt;2. MongoDB는 무엇인가요?&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;a href=&quot;https://www.mongodb.com/products/platform/atlas-database&quot;&gt;MongoDB&lt;/a&gt;는 JavaScript 객체와 매우 비슷한 형태로 데이터를 저장하는 데이터베이스입니다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;h3&gt;React의 state와 비교&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// React에서 state 사용
const [post, setPost] = useState({
  title: &amp;quot;제목&amp;quot;,
  content: &amp;quot;내용&amp;quot;
});

// MongoDB에 저장된 데이터
{
  _id: &amp;quot;자동생성되는ID&amp;quot;,
  title: &amp;quot;제목&amp;quot;,
  content: &amp;quot;내용&amp;quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;React에서 state를 사용하는 것처럼, MongoDB는 데이터를 저장하고 관리합니다. 단지 서버를 껐다 켜도 데이터가 유지된다는 점이 다릅니다!&lt;/p&gt;
&lt;h2&gt;3. Mongoose는 무엇인가요?&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;a href=&quot;https://mongoosejs.com/&quot;&gt;Mongoose&lt;/a&gt;는 MongoDB를 더 쉽게 사용할 수 있게 해주는 도구입니다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;h3&gt;PropTypes와 비교&lt;/h3&gt;
&lt;p&gt;React에서 props의 타입을 체크하기 위해 PropTypes를 사용하듯이, Mongoose는 데이터의 형태를 미리 정의하고 체크합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// React PropTypes
Post.propTypes = {
  title: PropTypes.string.isRequired,
  content: PropTypes.string.isRequired,
  views: PropTypes.number
};

// Mongoose Schema
const postSchema = new mongoose.Schema({
  title: { type: String, required: true },
  content: { type: String, required: true },
  views: { type: Number, default: 0 }
});&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;4. 실제 사용 예시&lt;/h2&gt;
&lt;h3&gt;4-1. 데이터베이스 연결&lt;/h3&gt;
&lt;p&gt;마치 React에서 context를 설정하는 것처럼, 데이터베이스 연결을 설정합니다&lt;/p&gt;
&lt;p&gt;환경 변수 파일에 MongoDB URI를 추가합니다:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// .env
MONGODB_URI=mongodb+srv://&amp;lt;username&amp;gt;:&amp;lt;password&amp;gt;@cluster0.xxxxx.mongodb.net/&amp;lt;database&amp;gt;?retryWrites=true&amp;amp;w=majority&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;연결 유틸리티 함수를 작성합니다:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// src/lib/mongodb.js
import mongoose from &amp;#39;mongoose&amp;#39;;

async function connectDB() {
  // MongoDB에 연결
  await mongoose.connect(process.env.MONGODB_URI);
}

export default connectDB;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4-2. 포스트 모델 정의하기&lt;/h3&gt;
&lt;p&gt;React 컴포넌트를 만드는 것처럼, 데이터의 형태를 정의합니다:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// models/Post.js
import mongoose from &amp;#39;mongoose&amp;#39;;

// 데이터 형태 정의 (마치 interface나 type 정의하듯이)
const postSchema = new mongoose.Schema({
  title: {
    type: String,
    required: true    // 필수값 (props의 isRequired와 비슷)
  },
  content: {
    type: String,
    required: true
  },
  createdAt: {
    type: Date,
    default: Date.now  // 기본값 설정
  }
});

// 모델 생성 (마치 컴포넌트를 export 하듯이)
// 이미 생성된 모델이 있다면 재사용
const Post = mongoose.models.Post || mongoose.model(&amp;#39;Post&amp;#39;, postSchema);
export default Post;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4-3. API 라우트에서 사용하기&lt;/h3&gt;
&lt;p&gt;프론트엔드에서 axios로 API를 호출하면, 이렇게 처리합니다:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// app/api/posts/route.js
import { NextResponse } from &amp;#39;next/server&amp;#39;;
import connectDB from &amp;#39;@/lib/mongodb&amp;#39;;
import Post from &amp;#39;@/models/Post&amp;#39;;

// GET 요청 처리 (/api/posts)
export async function GET() {
  try {
    // 1. 데이터베이스 연결
    await connectDB();

    // 2. 전체 게시글 가져오기 (마치 setState 하듯이)
    // sort()로 최신 게시글부터 가져오기
    const posts = await Post.find().sort({ createdAt: -1 });

    // 3. 클라이언트에 데이터 반환
    return NextResponse.json(posts);
  } catch (error) {
    return NextResponse.json(
      { error: &amp;#39;게시글을 불러오는데 실패했습니다.&amp;#39; },
      { status: 500 }
    );
  }
}

// POST 요청 처리 (/api/posts)
export async function POST(req) {
  try {
    // 1. 데이터베이스 연결
    await connectDB();

    // 2. 클라이언트가 보낸 데이터 가져오기
    const data = await req.json();

    // 3. 새 게시글 생성 (마치 setState 하듯이)
    const post = await Post.create(data);

    // 4. 생성된 게시글 반환
    return NextResponse.json(post, { status: 201 });
  } catch (error) {
    return NextResponse.json(
      { error: &amp;#39;게시글 작성에 실패했습니다.&amp;#39; },
      { status: 500 }
    );
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4-4. 프론트엔드에서 사용하기&lt;/h3&gt;
&lt;p&gt;이제 평소처럼 axios로 API를 호출하면 됩니다:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// pages/posts.js
import axios from &amp;#39;axios&amp;#39;;

// 게시글 목록 가져오기
const fetchPosts = async () =&amp;gt; {
  const { data } = await axios.get(&amp;#39;/api/posts&amp;#39;);
  setPosts(data);
};

// 새 게시글 작성하기
const createPost = async (postData) =&amp;gt; {
  const { data } = await axios.post(&amp;#39;/api/posts&amp;#39;, postData);
  setPosts(prev =&amp;gt; [...prev, data]);
};&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;5. 정리&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;브라우저 → (프론트엔드 async) → 서버 → (라우트 async) → 데이터베이스&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;MongoDB = 데이터를 저장하는 창고&lt;/li&gt;
&lt;li&gt;Mongoose = 그 창고를 더 잘 사용할 수 있게 도와주는 도구&lt;/li&gt;
&lt;li&gt;프론트엔드 = 평소처럼 API를 호출&lt;/li&gt;
&lt;li&gt;서버 = 이 호출을 받아서 MongoDB에 저장하거나 가져오는 작업을 수행&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;이제 서버를 껐다 켜도 데이터가 사라지지 않고, 여러 사용자가 같은 데이터를 공유할 수 있게 됩니다!&lt;/p&gt;</description>
      <category>Front/Node.js</category>
      <author>oodada</author>
      <guid isPermaLink="true">https://oddcode.tistory.com/369</guid>
      <comments>https://oddcode.tistory.com/369#entry369comment</comments>
      <pubDate>Thu, 26 Dec 2024 09:21:38 +0900</pubDate>
    </item>
    <item>
      <title>프론트엔드 개발자를 위한 데이터베이스 초보자 가이드</title>
      <link>https://oddcode.tistory.com/368</link>
      <description>&lt;h1&gt;프론트엔드 개발자를 위한 데이터베이스 초보자 가이드&lt;/h1&gt;
&lt;h2&gt;1. 우리가 아는 것에서 시작하기&lt;/h2&gt;
&lt;p&gt;프론트엔드에서 데이터를 다루는 법부터 시작해볼게요:&lt;/p&gt;
&lt;h3&gt;1-1. React에서의 데이터 저장&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// React에서 게시글 목록 관리하기
const [posts, setPosts] = useState([
  {
    id: 1,
    title: &amp;quot;첫 번째 글&amp;quot;,
    content: &amp;quot;안녕하세요&amp;quot;
  },
  {
    id: 2,
    title: &amp;quot;두 번째 글&amp;quot;,
    content: &amp;quot;반갑습니다&amp;quot;
  }
]);&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;1-2. 근데 여기에는 문제가 있어요&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;새로고침하면 데이터가 사라져요&lt;/li&gt;
&lt;li&gt;다른 사용자와 데이터를 공유할 수 없어요&lt;/li&gt;
&lt;li&gt;데이터가 많아지면 관리가 어려워요&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;이런 문제를 해결하기 위해 &lt;strong&gt;데이터베이스&lt;/strong&gt;가 필요합니다!&lt;/p&gt;
&lt;h2&gt;2. 데이터베이스의 두 가지 타입&lt;/h2&gt;
&lt;h3&gt;2-1. MySQL (엄격한 규칙의 데이터베이스)&lt;/h3&gt;
&lt;p&gt;엑셀 스프레드시트를 생각해보세요:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;모든 열(column)이 미리 정의되어 있어요&lt;/li&gt;
&lt;li&gt;각 행(row)은 같은 구조를 가져야 해요&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// MySQL 테이블 구조
const posts = {
  // 이 구조에서 벗어날 수 없어요!
  columns: {
    id: &amp;#39;번호&amp;#39;,
    title: &amp;#39;제목&amp;#39;,
    content: &amp;#39;내용&amp;#39;,
    author: &amp;#39;작성자&amp;#39;
  },
  // 모든 데이터는 위 구조를 따라야 해요
  rows: [
    [1, &amp;#39;안녕하세요&amp;#39;, &amp;#39;내용입니다&amp;#39;, &amp;#39;김철수&amp;#39;],
    [2, &amp;#39;반갑습니다&amp;#39;, &amp;#39;두번째 글&amp;#39;, &amp;#39;이영희&amp;#39;]
  ]
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2-2. MongoDB (자유로운 규칙의 데이터베이스) : nosql&lt;/h3&gt;
&lt;p&gt;JavaScript 객체와 매우 비슷해요:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;원하는 대로 데이터 구조를 만들 수 있어요&lt;/li&gt;
&lt;li&gt;각 문서(document)마다 다른 형태를 가질 수 있어요&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// MongoDB 컬렉션 구조
const posts = [
  {
    _id: 1,
    title: &amp;quot;안녕하세요&amp;quot;,
    content: &amp;quot;첫 번째 글입니다&amp;quot;,
    // 여기에 원하는 필드를 자유롭게 추가할 수 있어요
    tags: [&amp;quot;인사&amp;quot;, &amp;quot;소개&amp;quot;],
    comments: []
  },
  {
    _id: 2,
    title: &amp;quot;반갑습니다&amp;quot;,
    content: &amp;quot;두 번째 글입니다&amp;quot;,
    // 다른 문서와 다른 구조를 가질 수 있어요
    author: {
      name: &amp;quot;이영희&amp;quot;,
      email: &amp;quot;lee@test.com&amp;quot;
    }
  }
];&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;3. 실제 프론트엔드 개발에서는 어떻게 쓰나요?&lt;/h2&gt;
&lt;h3&gt;3-1. MySQL을 사용할 때&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// 프론트엔드에서 API 호출
const getPosts = async () =&amp;gt; {
  // 서버에서는 SQL 쿼리로 데이터를 가져옴
  // SELECT * FROM posts WHERE author = &amp;#39;김철수&amp;#39;;
  const response = await axios.get(&amp;#39;/api/posts&amp;#39;);
  setPosts(response.data);
};&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3-2. MongoDB를 사용할 때&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// 프론트엔드에서 API 호출
const getPosts = async () =&amp;gt; {
  // 서버에서는 MongoDB 쿼리로 데이터를 가져옴
  // Post.find({ author: &amp;#39;김철수&amp;#39; });
  const response = await axios.get(&amp;#39;/api/posts&amp;#39;);
  setPosts(response.data);
};&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3-3. 프론트엔드 입장에서는...&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;API 호출하는 방식은 동일해요&lt;/li&gt;
&lt;li&gt;받는 데이터 형태가 조금 달라요&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// MySQL에서 받은 데이터
const mysqlData = [
{
 id: 1,
 title: &amp;quot;제목&amp;quot;,
 content: &amp;quot;내용&amp;quot;,
 // 정해진 필드만 옴
}
];
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;// MongoDB에서 받은 데이터&lt;br&gt;const mongoData = [&lt;br&gt;  {&lt;br&gt;    _id: &amp;quot;507f1f77bcf86cd799439011&amp;quot;, // MongoDB의 특별한 ID 형식&lt;br&gt;    title: &amp;quot;제목&amp;quot;,&lt;br&gt;    content: &amp;quot;내용&amp;quot;,&lt;br&gt;    // 추가 필드들이 있을 수 있음&lt;br&gt;    comments: [],&lt;br&gt;    tags: []&lt;br&gt;  }&lt;br&gt;];&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
## 4. 어떤 걸 선택해야 할까요?

### 4-1. MySQL이 좋을 때
1. 데이터 구조가 명확할 때
   - 예: 회원가입 정보 (이름, 이메일, 비밀번호...)
   - 예: 주문 정보 (상품, 수량, 가격...)

2. 데이터 간의 관계가 복잡할 때
   - 예: 쇼핑몰 (주문-상품-사용자-리뷰...)

### 4-2. MongoDB가 좋을 때
1. 데이터 구조가 자주 바뀔 때
   - 예: 블로그 포스트 (글마다 다른 형식)
   - 예: SNS 게시물 (사진, 동영상, 링크 등 다양한 형식)

2. 빠른 개발이 필요할 때
   - JavaScript 객체처럼 자유롭게 데이터 저장 가능
   - 스키마 변경이 쉬움

## 5. 실제 프로젝트 예시

### 5-1. 블로그 프로젝트
MongoDB가 좋아요:
```javascript
const blogPosts = [
  {
    title: &amp;quot;일반 글&amp;quot;,
    content: &amp;quot;텍스트만 있는 글&amp;quot;
  },
  {
    title: &amp;quot;사진 글&amp;quot;,
    content: &amp;quot;text&amp;quot;,
    images: [&amp;quot;image1.jpg&amp;quot;, &amp;quot;image2.jpg&amp;quot;],
    imageLayout: &amp;quot;gallery&amp;quot;
  },
  {
    title: &amp;quot;비디오 글&amp;quot;,
    content: &amp;quot;text&amp;quot;,
    video: &amp;quot;video.mp4&amp;quot;,
    thumbnail: &amp;quot;thumb.jpg&amp;quot;
  }
];&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;5-2. 쇼핑몰 프로젝트&lt;/h3&gt;
&lt;p&gt;MySQL이 좋아요:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// 주문 정보는 항상 같은 구조를 가져요
const orders = {
  columns: {
    id: &amp;#39;주문번호&amp;#39;,
    userId: &amp;#39;사용자ID&amp;#39;,
    productId: &amp;#39;상품ID&amp;#39;,
    quantity: &amp;#39;수량&amp;#39;,
    price: &amp;#39;가격&amp;#39;,
    status: &amp;#39;주문상태&amp;#39;
  }
};&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;정리&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;MySQL은 엑셀처럼 정해진 형식이 있어요&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;데이터가 정형화되어 있을 때 좋아요&lt;/li&gt;
&lt;li&gt;은행, 쇼핑몰 같은 서비스에 적합해요&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;MongoDB는 JavaScript 객체처럼 자유로워요&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;데이터 구조가 유연해야 할 때 좋아요&lt;/li&gt;
&lt;li&gt;블로그, SNS 같은 서비스에 적합해요&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;프론트엔드 개발자 입장에서는&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;API 호출 방식은 동일해요&lt;/li&gt;
&lt;li&gt;받는 데이터 형식만 조금 달라요&lt;/li&gt;
&lt;li&gt;MongoDB가 JavaScript와 비슷해서 더 친숙할 수 있어요&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;</description>
      <category>Front/Node.js</category>
      <author>oodada</author>
      <guid isPermaLink="true">https://oddcode.tistory.com/368</guid>
      <comments>https://oddcode.tistory.com/368#entry368comment</comments>
      <pubDate>Thu, 26 Dec 2024 09:20:46 +0900</pubDate>
    </item>
    <item>
      <title>Node.js 모듈과 객체</title>
      <link>https://oddcode.tistory.com/366</link>
      <description>&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/odada/post/8f0ffe72-39aa-46bf-a39b-fac21d39f07e/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h2&gt;1. 모듈(Module)이란?&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;프로그램을 기능별 단위로 분리한 코드 모음&lt;/li&gt;
&lt;li&gt;함수, 변수, 객체 등을 포함&lt;/li&gt;
&lt;li&gt;하나의 &lt;code&gt;.js&lt;/code&gt; 파일이 하나의 모듈&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;- 모듈의 장점&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;재사용성&lt;/strong&gt;: 한 번 만든 모듈을 여러 프로젝트에서 재사용&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;유지보수&lt;/strong&gt;: 기능별로 분리되어 있어 관리가 용이&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;캡슐화&lt;/strong&gt;: 관련 코드를 하나의 단위로 묶어서 관리&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;의존성 관리&lt;/strong&gt;: 필요한 기능만 선택적으로 가져와 사용&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;- 대표적인 웹서비스 공통 모듈 예시&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;회원가입 모듈&lt;/li&gt;
&lt;li&gt;로그인 모듈&lt;/li&gt;
&lt;li&gt;게시판 모듈&lt;/li&gt;
&lt;li&gt;댓글 모듈&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;- 모듈 사용법&lt;/h3&gt;
&lt;h4&gt;모듈 내보내기 (Export)&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// module.js
const greeting = &amp;#39;Hello, Module!&amp;#39;;
const sayHi = () =&amp;gt; console.log(greeting);

// module.exports = { 내보낼 내용 }
module.exports = {
    greeting,
    sayHi
};&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;모듈 가져오기 (Import)&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// main.js
// require(&amp;#39;가져올 파일 경로&amp;#39;)
const myModule = require(&amp;#39;./module&amp;#39;);

console.log(myModule.greeting); // Hello, Module!
myModule.sayHi(); // Hello, Module!&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;실행&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# node 파일 이름
$ node main.js&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- 모듈 사용 시나리오&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;프로젝트/
├── modules/
│   ├── auth.js        (인증 관련 모듈)
│   ├── board.js       (게시판 관련 모듈)
│   └── comment.js     (댓글 관련 모듈)
└── app.js             (메인 애플리케이션)&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;2. 모듈의 종류&lt;/h2&gt;
&lt;p&gt;위에서 설명한 모듈처럼 require, exports를 통해 원하는 모듈을 개인적으로 만들어 사용해도 되지만, Node.js에서는 이미 많은 모듈들이 내장되어 있고 npm을 통해 다른 모듈들을 설치하여 사용할 수 있다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;기본(코어)모듈 : Node.js 설치 시 기본적으로 제공되는 모듈&lt;/li&gt;
&lt;li&gt;외부 모듈(서드 파티 모듈) : npm을 통해 설치한 모듈&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;- 전역 객체(Global Object)&lt;/h3&gt;
&lt;p&gt;Node.js에서는 전역 객체를 사용할 수 있다. 전역 객체는 모든 모듈에서 사용할 수 있는 객체로, &lt;code&gt;global&lt;/code&gt;이라는 이름으로 사용할 수 있다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// global 객체 사용
global.console.log(&amp;#39;Hello, Global Object!&amp;#39;);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;require(), setTimeout(), console.log() 등의 함수들도 전역 객체에 해당하며 앞에 &lt;code&gt;global&lt;/code&gt;을 붙이지 않아도 사용할 수 있다.&lt;/p&gt;
&lt;h3&gt;- 기본(코어)모듈&lt;/h3&gt;
&lt;p&gt;Node.js에서 기본적으로 제공하는 모듈로, 별도의 설치 없이 바로 사용할 수 있다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;fs (File System) : 파일 시스템 관련 기능&lt;/li&gt;
&lt;li&gt;http : 웹 서버 생성 및 요청, 응답 처리&lt;/li&gt;
&lt;li&gt;os (Operating System) : 운영체제 정보&lt;/li&gt;
&lt;li&gt;path : 파일 경로 관련 기능&lt;/li&gt;
&lt;li&gt;url : URL 처리 기능&lt;/li&gt;
&lt;li&gt;util : 각종 편의 기능&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;- 외부 모듈(서드 파티 모듈)&lt;/h3&gt;
&lt;p&gt;npm(Node Package Manager)을 통해 설치한 모듈로, 다른 개발자들이 만든 모듈을 사용할 수 있다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;express : 웹 프레임워크&lt;/li&gt;
&lt;li&gt;mongoose : MongoDB ODM(Object Data Modeling)&lt;/li&gt;
&lt;li&gt;nodemailer : 이메일 전송&lt;/li&gt;
&lt;li&gt;socket.io : 웹 소켓 통신&lt;/li&gt;
&lt;li&gt;request : HTTP 요청&lt;/li&gt;
&lt;li&gt;cheerio : 웹 스크래핑&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;3. ES Modules의 사용&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;최신 JavaScript에서 도입된 표준 모듈 시스템으로, 주로 웹 개발에서 사용된다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.mjs&amp;#39; 확장자를 사용하거나, package.json 파일에&lt;/code&gt;&amp;quot;type&amp;quot;: &amp;quot;module&amp;quot;`을 추가하여 사용한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;// module.js
export default function hello() {
    console.log(&amp;#39;hello&amp;#39;);
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;// main.js

// import 모듈명 from &amp;#39;모듈의 경로&amp;#39;;
import hello from &amp;#39;./module.js&amp;#39;;

hello(); // hello&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;모듈의 내보내기(export), 가져오기(import)&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;모듈을 내보내기 위해서는 export 키워드를 사용한다.&lt;/li&gt;
&lt;li&gt;모듈을 가져오기 위해서는 import 키워드를 사용한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;// module.js

// 기본(default)  내보내기
export default 123;

// 이름 붙여서 내보내기
export const str = &amp;#39;abc&amp;#39;; // 문자열
export const arr = []; // 배열
export const obj = () =&amp;gt; {}; // 화살표 함수
export function obj() {} // 일반 함수&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;// main.js

// 기본(default) 가져오기
// import 모듈명 from &amp;#39;모듈의 경로&amp;#39;;
import num from &amp;#39;./module.js&amp;#39;;

// 이름 붙여서 가져오기
// import { 이름 } from &amp;#39;모듈의 경로&amp;#39;;
import { str as xyz, arr, obj } from &amp;#39;./module.js&amp;#39;;

console.log(num); // 123
console.log(xyz); // abc (str을 xyz로 가져옴)
console.log(arr); // []
console.log(obj); // [Function: obj]&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;모든 내용을 가져오기&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;// main.js

// 모든 내용 가져오기
// import * as 모듈명 from &amp;#39;모듈의 경로&amp;#39;;
import * as abc from &amp;#39;./module.js&amp;#39;;

console.log(abc); // { default: 123, str: &amp;#39;abc&amp;#39;, arr: [], obj: [Function: obj] }&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;모듈을 한번에 가져와 사용하기&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;// a.js
export const a = () =&amp;gt; 123;&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;// b.js
export const b = () =&amp;gt; 456;&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;// main.js

import { a } from &amp;#39;./a.js&amp;#39;;
import { b } from &amp;#39;./b.js&amp;#39;;

console.log(a()); // 123
console.log(b()); // 456&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;위와 같이 모듈을 가져오는 것은 번거롭다.&lt;/li&gt;
&lt;li&gt;모듈을 한번에 가져오기 위해서는 index.js 파일을 만들어 사용한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;// index.js
export { a } from &amp;#39;./a.js&amp;#39;;
export { b } from &amp;#39;./b.js&amp;#39;;&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;// main.js
import { a, b } from &amp;#39;./index.js&amp;#39;;

console.log(a()); // 123
console.log(b()); // 456&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;React에서의 모듈 사용&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;React에서는 컴포넌트를 모듈로 사용한다.&lt;/li&gt;
&lt;li&gt;컴포넌트를 내보내기, 가져오기하여 사용한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;// Button.js
import React from &amp;#39;react&amp;#39;;

const Button = () =&amp;gt; {
    return &amp;lt;button&amp;gt;Button&amp;lt;/button&amp;gt;;
};

export default Button;&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;// App.js
import React from &amp;#39;react&amp;#39;;
import Button from &amp;#39;./Button&amp;#39;;

const App = () =&amp;gt; {
    return (
        &amp;lt;div&amp;gt;
            &amp;lt;h1&amp;gt;App&amp;lt;/h1&amp;gt;
            &amp;lt;Button /&amp;gt;
        &amp;lt;/div&amp;gt;
    );
};

export default App;&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;// index.js
import React from &amp;#39;react&amp;#39;;
import ReactDOM from &amp;#39;react-dom&amp;#39;;
import App from &amp;#39;./App&amp;#39;;

ReactDOM.render(&amp;lt;App /&amp;gt;, document.getElementById(&amp;#39;root&amp;#39;));&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Front/Node.js</category>
      <author>oodada</author>
      <guid isPermaLink="true">https://oddcode.tistory.com/366</guid>
      <comments>https://oddcode.tistory.com/366#entry366comment</comments>
      <pubDate>Wed, 25 Dec 2024 18:11:57 +0900</pubDate>
    </item>
    <item>
      <title>자바스크립트 비동기 처리</title>
      <link>https://oddcode.tistory.com/365</link>
      <description>&lt;h2&gt;콜 스택(Call Stack) &amp;amp; 이벤트 루프(Event Loop) &amp;amp; 콜백 큐(Callback Queue)&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/odada/post/8fabded2-d8c6-4a48-88b8-2534d056b416/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h3&gt;콜 스택(Call Stack)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;자바스크립트는 싱글 스레드 언어이기 때문에, 콜 스택이 하나만 존재한다.&lt;/li&gt;
&lt;li&gt;콜 스택은 함수가 호출되면, 해당 함수를 콜 스택에 쌓아놓고 실행한다.&lt;/li&gt;
&lt;li&gt;함수가 실행이 끝나면, 콜 스택에서 해당 함수를 제거한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;function first() {
  console.log(&amp;#39;첫번째 함수&amp;#39;);
  second();
}

function second() {
  console.log(&amp;#39;두번째 함수&amp;#39;);
}

first();

// 실행 순서
// 첫번째 함수
// 두번째 함수&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;이벤트 루프(Event Loop)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;자바스크립트는 이벤트 중심 언어이기 때문에, 이벤트 루프가 존재한다.&lt;/li&gt;
&lt;li&gt;이벤트 루프는 콜 스택과 콜백 큐를 감시하면서, 콜 스택이 비어있을 때 콜백 큐에 있는 함수를 콜 스택에 넣어 실행한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;콜백 큐(Callback Queue)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;콜백 큐는 비동기 함수의 콜백 함수를 담아두는 큐이다.&lt;/li&gt;
&lt;li&gt;콜백 큐에 있는 함수는 콜 스택이 비어있을 때, 이벤트 루프에 의해 콜 스택에 넣어 실행된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;console.log(&amp;#39;시작&amp;#39;);
setTimeout(() =&amp;gt; {
    console.log(&amp;#39;타이머 완료&amp;#39;);
}, 0);
console.log(&amp;#39;끝&amp;#39;);

// 출력: 시작 -&amp;gt; 끝 -&amp;gt; 타이머 완료&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;실제 동작 과정&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;동기 코드는 즉시 콜 스택에서 실행됩니다&lt;/li&gt;
&lt;li&gt;setTimeout 같은 비동기 작업은 Web API로 보내집니다&lt;/li&gt;
&lt;li&gt;Web API에서 작업이 완료되면 콜백은 콜백 큐로 이동합니다&lt;/li&gt;
&lt;li&gt;이벤트 루프는 콜 스택이 비었는지 확인하고, 비었다면 콜백 큐의 첫 번째 작업을 콜 스택으로 가져옵니다&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&amp;quot;자바스크립트는 마치 멀티태스킹을 하는 것처럼 여러 작업을 처리할 수 있습니다. 시간이 오래 걸리는 작업(예: 서버에서 데이터 가져오기)을 실행하더라도, 그동안 다른 작업들을 계속할 수 있습니다. 마치 라면을 끓이면서 그동안 핸드폰을 하는 것처럼...&amp;quot;&lt;/p&gt;
&lt;h2&gt;콜백 함수&lt;/h2&gt;
&lt;p&gt;콜백(Callback) 함수는 영문 그대로, 나중에 실행되는 함수를 뜻한다. &lt;/p&gt;
&lt;h3&gt;콜백 함수의 비동기 처리&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;자바스크립트는 이벤트 중심 언어이기 때문에&lt;/li&gt;
&lt;li&gt;특정 이벤트가 발생하고, 그에 대한 결과가 나올 때까지 기다리지 않고 다음 코드를 실행한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// 먼저 호출했지만 3초 뒤에 실행되기 때문에 2초 뒤에 실행되는 함수보다 나중에 실행된다.
setTimeout(() =&amp;gt; {
  console.log(&amp;#39;첫번째 실행&amp;#39;);
}, 3000);

setTimeout(() =&amp;gt; {
  console.log(&amp;#39;두번째 실행&amp;#39;);
}, 2000);

// 실행 순서
// 두번째 실행
// 첫번째 실행&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;콜백 함수의 동기 처리&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;첫번째를 먼저 실행하고 두번째를 실행하고 싶으면&lt;/li&gt;
&lt;li&gt;&amp;#39;콜백 함수&amp;#39;를 이용해 비동기 작업을 동기적으로 처리해주어야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;setTimeout(() =&amp;gt; {
  setTimeout(() =&amp;gt; {
    console.log(&amp;#39;두번째 실행&amp;#39;);
  }, 2000);
  console.log(&amp;#39;첫번째 실행&amp;#39;);
}, 3000);


// 실행 순서
// 첫번째 실행
// 두번째 실행&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;사용자 정의 함수의 동기 처리&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;아래 예제를 실행해보면, &lt;code&gt;첫번째 실행&lt;/code&gt; -&amp;gt; &lt;code&gt;두번째 실행&lt;/code&gt; -&amp;gt; &lt;code&gt;세번째 실행&lt;/code&gt; 순으로 실행되고&lt;/li&gt;
&lt;li&gt;동기적으로 처리된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;function faker(callback) {
  callback();
}

console.log(&amp;#39;첫번째 실행&amp;#39;);

faker(() =&amp;gt; {
  console.log(&amp;#39;두번째 실행&amp;#39;);
});

console.log(&amp;#39;세번째 실행&amp;#39;);

// 실행 순서
// 첫번째 실행
// 두번째 실행
// 세번째 실행&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;위 세 가지 실행부는 모두 동기적이기 때문에, 콜백 큐를 사용하지 않고 모두 콜 스택을 거쳐 실행된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/odada/post/7a62bbd6-aa3a-46e0-929b-64cdfbd0377b/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h3&gt;API 비동기 처리&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;console.log(&amp;#39;첫번째 실행&amp;#39;);

setTimeout(() =&amp;gt; {
  console.log(&amp;#39;세번째 실행&amp;#39;);
}, 0);

console.log(&amp;#39;두번째 실행&amp;#39;);

// 실행 순서
// 첫번째 실행
// 두번째 실행
// 세번째 실행&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/odada/post/a55ab5f3-036a-48db-9e9c-8079d9fd0ab2/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;console.log(&amp;#39;첫번째 실행&amp;#39;)&lt;/code&gt;이 콜 스택에 들어가서 실행됩니다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;setTimeout&lt;/code&gt;이 콜 스택에 들어가면서&lt;ul&gt;
&lt;li&gt;콜백 함수는 &lt;code&gt;Web APIs&lt;/code&gt;로 보내집니다&lt;/li&gt;
&lt;li&gt;타이머가 완료되면 콜백 함수는 &lt;code&gt;콜백 큐&lt;/code&gt;로 이동합니다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;console.log(&amp;#39;두번째 실행&amp;#39;)&lt;/code&gt;이 콜 스택에 들어가서 실행됩니다.&lt;/li&gt;
&lt;li&gt;콜 스택이 비워지면, 이벤트 루프가 콜백 큐의 &lt;code&gt;콜백 함수를 콜 스택&lt;/code&gt;으로 가져옵니다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;console.log(&amp;#39;세번째 실행&amp;#39;)&lt;/code&gt;이 마지막으로 실행됩니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;콜백 지옥(Callback Hell)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;콜백 함수를 중첩해서 사용하다 보면, 코드가 복잡해지고 가독성이 떨어지는 현상을 말한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;setTimeout(() =&amp;gt; {
  console.log(&amp;#39;첫번째 실행&amp;#39;);
  setTimeout(() =&amp;gt; {
    console.log(&amp;#39;두번째 실행&amp;#39;);
    setTimeout(() =&amp;gt; {
      console.log(&amp;#39;세번째 실행&amp;#39;);
    }, 1000);
  }, 1000);
}, 1000);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/odada/post/358a4755-7d6d-4cd3-868f-2dbf6d194740/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;이러한 콜백 지옥을 해결하기 위해, &lt;code&gt;Promise&lt;/code&gt;나 &lt;code&gt;async/await&lt;/code&gt;을 사용한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Promise&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Promise&lt;/code&gt;는 코드의 중첩이 발생하는 콜백 지옥을 해결하기 위한 객체이다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Promise&lt;/code&gt;는 단어 그대로 &amp;#39;약속&amp;#39;을 의미하며, 비동기 작업이 완료되었을 때, 성공했는지 실패했는지 알려주는 객체이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;커피 주문 시나리오로 보는 Promise&lt;/h3&gt;
&lt;h4&gt;1. 커피 주문&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;function order(sec, callback) {
  setTimeout(() =&amp;gt; {
    callback(new Date().toISOString());
  }, sec * 1000);
}

order(1, (time) =&amp;gt; {
  console.log(`커피 주문`, time);
});

order(2, (time) =&amp;gt; {
  console.log(`시럽 추가 주문`, time);
});

order(3, (time) =&amp;gt; {
  console.log(`휘핑 추가 주문`, time);
});

// 실행 순서
// 동시에 실행&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;위 코드는 동시에 실행되기 때문에, 순서가 보장되지 않는다.&lt;/li&gt;
&lt;li&gt;실행 순서를 보장하기 위해 비동기 처리를 해야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;function order(sec, callback) {
  setTimeout(() =&amp;gt; {
    callback(new Date().toISOString());
  }, sec * 1000);
}

order(1, (time) =&amp;gt; {
  console.log(`커피 주문`, time);
  order(2, (time) =&amp;gt; {
    console.log(`시럽 추가 주문`, time);
    order(3, (time) =&amp;gt; {
      console.log(`휘핑 추가 주문`, time);
    });
  });
});

// 실행 순서
// 커피 주문 -&amp;gt; 시럽 추가 주문 -&amp;gt; 휘핑 추가 주문&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;이런 콜백 지옥을 해결하기 위해 &lt;code&gt;Promise&lt;/code&gt;를 사용한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;1. 커피 주문 = Promise 생성&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;const orderCoffee = new Promise((resolve, reject) =&amp;gt; {
  // 바리스타가 커피 만드는 과정
});&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;커피를 주문하면 &lt;strong&gt;진동벨(Promise)&lt;/strong&gt;을 줍니다&lt;/li&gt;
&lt;li&gt;이 진동벨로 커피가 완성되었는지 알 수 있어요&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;2. Promise의 3가지 상태&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;대기중(Pending)&lt;/strong&gt;: &amp;quot;커피 제조중입니다&amp;quot; (진동벨 대기중)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;성공(Fulfilled)&lt;/strong&gt;: &amp;quot;삐삐! 커피가 준비되었습니다&amp;quot; (진동벨 울림)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;실패(Rejected)&lt;/strong&gt;: &amp;quot;죄송합니다. 머신 고장으로 제조가 불가능합니다&amp;quot; (진동벨 오류)&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;3. 실제 코드로 보는 커피 주문&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;const orderCoffee = new Promise((resolve, reject) =&amp;gt; {
  console.log(&amp;quot;바리스타가 커피를 만들기 시작합니다!&amp;quot;);

  setTimeout(() =&amp;gt; {
    const isSuccess = true;  // 커피가 잘 만들어졌다고 가정

    if(isSuccess) {
      resolve(&amp;quot;주문하신 아메리카노 나왔습니다! ☕&amp;quot;);
    } else {
      reject(&amp;quot;죄송합니다. 머신 고장으로 제조가 불가능합니다  &amp;quot;);
    }
  }, 3000);  // 3초 동안 커피 제조중
});

// 커피 주문 결과 처리
orderCoffee
  .then((result) =&amp;gt; {
    console.log(result);  // &amp;quot;주문하신 아메리카노 나왔습니다! ☕&amp;quot;
  })
  .catch((error) =&amp;gt; {
    console.log(error);  // &amp;quot;죄송합니다. 머신 고장으로 제조가 불가능합니다  &amp;quot;
  });&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;4. Promise의 장점&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;콜백&lt;/strong&gt;: &amp;quot;커피 주문하고, 시럽 추가하고, 휘핑 추가하고...&amp;quot; (복잡)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Promise&lt;/strong&gt;: 진동벨 하나로 모든 과정을 깔끔하게 처리 (.then 체이닝)&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;5. Promise 체이닝&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Promise&lt;/code&gt;는 &lt;code&gt;.then&lt;/code&gt;을 이용해 여러 개의 비동기 작업을 순차적으로 처리할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// 콜백 지옥 버전
orderCoffee(function(coffee) {
  addSyrup(coffee, function(withSyrup) {
    addWhippedCream(withSyrup, function(complete) {
      console.log(&amp;quot;주문 완료!&amp;quot;);
    });
  });
});

// Promise 버전 (깔끔!)
orderCoffee()
  .then(coffee =&amp;gt; addSyrup(coffee))
  .then(withSyrup =&amp;gt; addWhippedCream(withSyrup))
  .then(() =&amp;gt; console.log(&amp;quot;주문 완료!&amp;quot;))
  .catch(error =&amp;gt; console.log(&amp;quot;주문 실패:&amp;quot;, error));&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;async/await&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;async/await&lt;/code&gt;는 &lt;code&gt;Promise&lt;/code&gt;를 더 쉽게 사용할 수 있도록 ES8에서 도입된 문법이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;무인 카페 주문 시나리오로 보는 async/await&lt;/h3&gt;
&lt;h4&gt;1. 기본 개념&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// 기존 Promise 방식 (진동벨)
orderCoffee()
  .then(coffee =&amp;gt; console.log(&amp;quot;커피 완성!&amp;quot;))
  .catch(error =&amp;gt; console.log(&amp;quot;주문 실패&amp;quot;));

// async/await 방식 (셀프 주문기)
async function orderCoffee() {
  try {
    const coffee = await makeCoffee();  // 커피가 완성될 때까지 기다림
    console.log(&amp;quot;커피 완성!&amp;quot;);
  } catch(error) {
    console.log(&amp;quot;주문 실패&amp;quot;);
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;2. Promise vs async/await 주문 비교&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// 진동벨(Promise) 방식
const orderByBell = () =&amp;gt; {
  getCoffee()
    .then(coffee =&amp;gt; addSyrup(coffee))
    .then(withSyrup =&amp;gt; addWhippedCream(withSyrup))
    .then(() =&amp;gt; console.log(&amp;quot;주문 완성!&amp;quot;))
    .catch(error =&amp;gt; console.log(&amp;quot;주문 실패&amp;quot;));
}

// 셀프 주문기(async/await) 방식
const orderBySelf = async () =&amp;gt; {
  try {
    const coffee = await getCoffee();        // 1. 커피 추출 기다리기
    const withSyrup = await addSyrup(coffee);     // 2. 시럽 추가 기다리기
    const completed = await addWhippedCream(withSyrup); // 3. 휘핑 추가 기다리기
    console.log(&amp;quot;주문 완성!&amp;quot;);
  } catch(error) {
    console.log(&amp;quot;주문 실패&amp;quot;);
  }
}

// 실행
orderByBell();  // 진동벨 주문
orderBySelf();  // 셀프 주문기 주문&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;3. async/await 장점&lt;/h4&gt;
&lt;p&gt;쉽고 직관적인 코드 작성이 가능하다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;async&lt;/code&gt;: &amp;quot;셀프 주문기를 사용하겠습니다&amp;quot;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;await&lt;/code&gt;: &amp;quot;이 작업이 끝날 때까지 기다립니다&amp;quot;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;try-catch&lt;/code&gt;: &amp;quot;주문 실패시 환불해드립니다&amp;quot;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Front/Node.js</category>
      <author>oodada</author>
      <guid isPermaLink="true">https://oddcode.tistory.com/365</guid>
      <comments>https://oddcode.tistory.com/365#entry365comment</comments>
      <pubDate>Wed, 25 Dec 2024 18:11:31 +0900</pubDate>
    </item>
    <item>
      <title>next.js로 CRUD API 서버 만들기</title>
      <link>https://oddcode.tistory.com/364</link>
      <description>&lt;p&gt;&lt;code&gt;my-next-server&lt;/code&gt;라는 이름으로 next.js 서버를 만들어보자.&lt;/p&gt;
&lt;p&gt;github 주소 : &lt;a href=&quot;https://github.com/odada-o/-template-next-js-crud&quot;&gt;https://github.com/odada-o/-template-next-js-crud&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;1. Next.js 설치&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npx create-next-app ./&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;2. 간단한 서버 만들기&lt;/h2&gt;
&lt;p&gt;브라우저에서 &lt;a href=&quot;http://localhost:3000/api/hello%EB%A1%9C&quot;&gt;http://localhost:3000/api/hello로&lt;/a&gt; 접속했을 때, &lt;strong&gt;안녕하세요!&lt;/strong&gt;라는 메시지를 JSON 형식으로 응답하는 서버를 만들어봅시다.&lt;/p&gt;
&lt;h4&gt;API Route 파일 생성&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;app/api/hello/route.ts&lt;/code&gt; 파일을 생성합니다.&lt;/li&gt;
&lt;li&gt;API Route 파일은 &lt;code&gt;GET()&lt;/code&gt;, &lt;code&gt;POST()&lt;/code&gt;, &lt;code&gt;PUT()&lt;/code&gt;, &lt;code&gt;DELETE()&lt;/code&gt; 함수를 내보내는 파일입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;my-next-server/
├── app/
│   └── api/
│       └── hello/
│           └── route.js
│   └── hello/
│       └── page.js&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;GET()&lt;/code&gt; 함수는 HTTP GET 요청을 처리하는 함수입니다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;NextResponse&lt;/code&gt;는 Next.js에서 응답을 생성하는 함수입니다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;NextResponse.json()&lt;/code&gt; 함수는 JSON 형식의 응답을 생성하는 함수입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// src/app/api/hello/route.ts
import { NextResponse } from &amp;#39;next/server&amp;#39;;

export const helloPosts = [
  { id: 1, title: &amp;quot;안녕1&amp;quot; },
  { id: 2, title: &amp;quot;안녕2&amp;quot; }
];

// GET /api/hello 주소로 요청이 오면 실행되는 함수
// async 키워드를 사용하여 비동기 함수로 만듭니다
export async function GET() {
  // 클라이언트에게 JSON 응답을 반환합니다
  // return NextResponse.json({ message: &amp;#39;안녕하세요!&amp;#39; });
  return NextResponse.json(helloPosts);
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;test&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;http://localhost:3000/api/hello&lt;/code&gt;로 GET 요청을 보내면, &lt;code&gt;{&amp;quot;message&amp;quot;:&amp;quot;안녕하세요!&amp;quot;}&lt;/code&gt;가 출력됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;3. CRUD API 만들기&lt;/h2&gt;
&lt;h3&gt;3-1. 파일 구조&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;my-next-server/
├── app/
│   ├── api/
│   │   ├── posts/
│   │   │   ├── route.js        # 전체 게시글 API
│   │   │   └── [id]/
│   │   │       └── route.js    # 개별 게시글 API
│   ├── posts/
│   │   ├── page.js             # 게시글 목록
│   │   ├── write/
│   │   │   └── page.js         # 글쓰기 페이지
│   │   └── [id]/
│   │       ├── page.js         # 상세 페이지
│   │       └── edit/
│   │           └── page.js     # 수정 페이지
└── data/
    └── posts.js                # 임시 데이터 저장소&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3-2. 게시글 API 만들기&lt;/h3&gt;
&lt;p&gt;먼저 axios를 설치합니다:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm install axios&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;데이터 저장소 생성&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// data/posts.js
export const posts = [
  { id: 1, title: &amp;#39;첫 번째 글&amp;#39;, content: &amp;#39;안녕하세요!&amp;#39;, createdAt: &amp;#39;2024-01-01&amp;#39; },
  { id: 2, title: &amp;#39;두 번째 글&amp;#39;, content: &amp;#39;반갑습니다!&amp;#39;, createdAt: &amp;#39;2024-01-02&amp;#39; }
];&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;API Route 파일 생성&lt;/h4&gt;
&lt;p&gt;이제 axios를 사용하여 게시글을 관리하는 API를 만들어봅시다.&lt;br&gt;axios는 fetch보다 더 간단하게 HTTP 요청을 처리할 수 있게 해주는 라이브러리입니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// app/api/posts/route.js
import { NextResponse } from &amp;#39;next/server&amp;#39;;
import axios from &amp;#39;axios&amp;#39;;
import posts from &amp;#39;@/data/posts&amp;#39;;

// 전체 게시글 조회 - GET 요청 처리
// 게시글 목록 페이지로 이동하면 실행됨
export async function GET() {
  try {
    // 만약 api 서버로 요청을 보내서 게시글 목록을 가져오고 싶다면
    // const response = await axios.get(&amp;#39;https://jsonplaceholder.typicode.com/posts&amp;#39;);
    // const posts = response.data;

    // 로컬 데이터를 바로 반환합니다
    return NextResponse.json(posts);
  } catch (error) {
    // 에러가 발생하면 에러 메시지와 함께 500 상태 코드 반환
    return NextResponse.json(
      { error: &amp;#39;게시글을 불러오는데 실패했습니다.&amp;#39; },
      { status: 500 }
    );
  }
}

// 새 게시글 작성 - POST 요청 처리
// 글쓰기 페이지에서 제출하면 실행됨
export async function POST(req) {

  // 글을 작성하면 req 객체에는 다음과 같은 정보가 들어있습니다
  // {
  //   headers: Headers { host: &amp;#39;localhost:3000&amp;#39;, &amp;#39;content-type&amp;#39;: &amp;#39;application/json&amp;#39;, ... },
  //   method: &amp;#39;POST&amp;#39;,
  //   url: &amp;#39;http://localhost:3000/api/posts&amp;#39;,
  //   body: { title: &amp;#39;새 글&amp;#39;, content: &amp;#39;내용입니다&amp;#39; }
  //   (단, 직접 접근은 불가능하며 req.json()으로 파싱해야 함)
  // }

  try {
    // 요청 본문에서 데이터 추출
    // data = { title: &amp;#39;새 글&amp;#39;, content: &amp;#39;새 글 내용입니다&amp;#39; }
    const data = await req.json();

    // 제목이나 내용이 없으면 400 에러 반환
    if (!data.title || !data.content) {
      return NextResponse.json(
        { error: &amp;#39;제목과 내용은 필수입니다.&amp;#39; },
        { status: 400 } // 400: Bad Request
      );
    }

    // newPost 객체 생성
    const newPost = {
      id: posts.length + 1,
      title: data.title,
      content: data.content,
      createdAt: new Date().toLocaleDateString()
    };

    // 서버의 데이터 베이스(posts)에 새 게시글 추가
    posts.push(newPost);

    // 클라이언트에게 새 게시글 반환
    return NextResponse.json(newPost, { status: 201 });

  } catch (error) {
    return NextResponse.json(
      { error: &amp;#39;게시글 작성에 실패했습니다.&amp;#39; },
      { status: 500 }
    );
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next.js의 &lt;code&gt;req&lt;/code&gt; 객체는 다음과 같은 주요 정보를 포함합니다:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;headers&lt;/code&gt;: 요청 헤더 정보 (Content-Type 등)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;method&lt;/code&gt;: HTTP 메서드 (POST)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;url&lt;/code&gt;: 요청 URL&lt;/li&gt;
&lt;li&gt;&lt;code&gt;body&lt;/code&gt;: 요청 본문 (단, 직접 접근은 불가능하며 req.json()으로 파싱해야 함)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;query&lt;/code&gt;: 쿼리 스트링 정보&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;Request headers: Headers {
  host: &amp;#39;localhost:3000&amp;#39;,
  &amp;#39;content-type&amp;#39;: &amp;#39;application/json&amp;#39;,
  ...
}
Request method: POST
Request URL: http://localhost:3000/api/posts
Parsed data: { title: &amp;#39;새 글&amp;#39;, content: &amp;#39;새 글 내용입니다&amp;#39; }&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3-3. Thunder Client로 API 테스트하기&lt;/h3&gt;
&lt;h4&gt;1. Thunder Client 설치&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;VSCode의 확장 프로그램에서 Thunder Client 설치&lt;/li&gt;
&lt;li&gt;왼쪽 사이드바에 번개 모양 아이콘이 생성됩니다&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;2. GET 요청 테스트&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;Thunder Client 아이콘 클릭&lt;/li&gt;
&lt;li&gt;&amp;#39;New Request&amp;#39; 클릭&lt;/li&gt;
&lt;li&gt;&lt;code&gt;GET http://localhost:3000/api/posts&lt;/code&gt; 입력&lt;/li&gt;
&lt;li&gt;Send 버튼 클릭&lt;/li&gt;
&lt;li&gt;응답으로 게시글 목록이 표시됩니다&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;3. POST 요청 테스트&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;&amp;#39;New Request&amp;#39; 클릭&lt;/li&gt;
&lt;li&gt;&lt;code&gt;POST http://localhost:3000/api/posts&lt;/code&gt; 입력&lt;/li&gt;
&lt;li&gt;Body 탭 선택 후 JSON 형식 선택&lt;/li&gt;
&lt;li&gt;아래 내용 입력:&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
&amp;quot;title&amp;quot;: &amp;quot;세 번째 게시글&amp;quot;,
&amp;quot;content&amp;quot;: &amp;quot;반가워요!&amp;quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;Send 버튼 클릭&lt;/li&gt;
&lt;li&gt;새로운 게시글이 생성되고 응답으로 반환됩니다&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;4. 게시글 상세 API 추가&lt;/h2&gt;
&lt;p&gt;이제 개별 게시글에 대한 조회/수정/삭제 API를 구현해봅시다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;GET /api/posts/[id]&lt;/code&gt;: 특정 게시글 조회&lt;/li&gt;
&lt;li&gt;&lt;code&gt;PUT /api/posts/[id]&lt;/code&gt;: 게시글 수정&lt;/li&gt;
&lt;li&gt;&lt;code&gt;DELETE /api/posts/[id]&lt;/code&gt;: 게시글 삭제&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;&lt;code&gt;params&lt;/code&gt; 객체&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;params&lt;/code&gt; 객체는 URL에서 동적으로 변하는 값을 담는 객체입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;/api/posts/1  → params.id는 &amp;quot;1&amp;quot;
/api/posts/2  → params.id는 &amp;quot;2&amp;quot;
/api/posts/99 → params.id는 &amp;quot;99&amp;quot;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;우리의 파일 구조를 보면&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;/api/posts/[id]/route.js&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;여기서 [id]라는 폴더 이름이면 Next.js는 대괄호([]) 안에 있는 이름을 params의 속성으로 만들어줍니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;export async function GET({ params }) {
    console.log(params);  // { id: &amp;#39;1&amp;#39; } 이런 식으로 출력됨
    console.log(params.id);  // &amp;#39;1&amp;#39; 처럼 해당 값만 출력

    // 문자열로 오기 때문에 숫자로 변환해서 사용
    // posts 배열에서 id와 일치하는 게시글 찾기
    const post = posts.find(post =&amp;gt; post.id === parseInt(params.id));
    ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;쉽게 말해서 &lt;code&gt;params&lt;/code&gt;는 URL의 변하는 부분을 손쉽게 가져다 쓸 수 있게 해주는 도구입니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// app/api/posts/[id]/route.js
import { NextResponse } from &amp;#39;next/server&amp;#39;;
import posts from &amp;#39;@/data/posts&amp;#39;;

// 특정 게시글 조회 - GET 요청 처리
// 게시글 상세 페이지로 이동하면 실행됨
// response 인수 대신 params 인수를 사용하여 URL 파라미터를 전달받음
export async function GET(request, { params }) {
  // params = { id: &amp;#39;1&amp;#39; }
 try {
   // URL 파라미터로 전달된 id 값과 일치하는 게시글 찾기
   const post = posts.find(post =&amp;gt; post.id === parseInt(params.id));

   // 게시글이 없을 경우 404 응답
   if (!post) {
     return NextResponse.json(
       { error: &amp;#39;게시글을 찾을 수 없습니다.&amp;#39; },
       { status: 404 }
     );
   }

   return NextResponse.json(post);
 } catch (error) {
   return NextResponse.json(
     { error: &amp;#39;게시글을 불러오는데 실패했습니다.&amp;#39; },
     { status: 500 }
   );
 }
}

// 게시글 수정 - PUT 요청 처리
// 수정할 내용을 입력하고 PUT 요청을 보내면 실행됨
export async function PUT(req, { params }) {
  try {
    const data = await req.json();
    // data = { title: &amp;#39;수정된 제목&amp;#39;, content: &amp;#39;수정된 내용&amp;#39; }

    // id와 일치하는 게시글의 인덱스 찾기
    const index = posts.findIndex(post =&amp;gt; post.id === parseInt(params.id));
    if (index === -1) {
      return NextResponse.json(
        { error: &amp;#39;게시글을 찾을 수 없습니다.&amp;#39; },
        { status: 404 }
      );
    }

    // posts = [
    //   { id: 1, title: &amp;#39;첫글&amp;#39; },    // p.id === 1 비교 -&amp;gt; true
    //   { id: 2, title: &amp;#39;둘째글&amp;#39; },  // 여기까지 안 감
    //   { id: 3, title: &amp;#39;셋째글&amp;#39; }   // 여기까지 안 감
    // ]

    // 첫번째 요소 에서 p.id === 1 비교 -&amp;gt; true 가 되므로
    // index = 0 이 됨
    posts[index] = {
      ...posts[index],
      title: data.title || posts[index].title,
      content: data.content || posts[index].content
    };

    // 게시글 업데이트 - 제목이나 내용이 없으면 기존 값 유지
    // posts[0] = 
    // { 
    // id: 1, title: &amp;#39;첫 번째 글&amp;#39;, content: &amp;#39;안녕하세요!&amp;#39;, createdAt: &amp;#39;2024-01-01&amp;#39;, 
    // title: &amp;#39;수정된 제목&amp;#39;, 
    // content: &amp;#39;수정된 내용&amp;#39; 
    // }

    // 클라이언트에게 수정된 게시글 (post[0]) 반환
    return NextResponse.json(posts[index]);
  } catch (error) {
    return NextResponse.json(
      { error: &amp;#39;게시글 수정에 실패했습니다.&amp;#39; },
      { status: 500 }
    );
  }
}

// 게시글 삭제 - DELETE 요청 처리
export async function DELETE(req, { params }) {
  try {
    // id와 일치하는 게시글의 인덱스 찾기
    const index = posts.findIndex(p =&amp;gt; p.id === parseInt(params.id));
    if (index === -1) {
      return NextResponse.json(
        { error: &amp;#39;게시글을 찾을 수 없습니다.&amp;#39; },
        { status: 404 }
      );
    }

    // 게시글 삭제
    // slice() 함수는 배열의 일부를 추출하여 새로운 배열을 만듭니다
    // splice(시작 인덱스, 삭제할 요소 개수) 함수는 배열에서 요소를 삭제합니다
    posts.splice(index, 1);
    return NextResponse.json({ message: &amp;#39;게시글이 삭제되었습니다.&amp;#39; });
  } catch (error) {
    return NextResponse.json(
      { error: &amp;#39;게시글 삭제에 실패했습니다.&amp;#39; },
      { status: 500 }
    );
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Thunder Client로 테스트하기&lt;/h3&gt;
&lt;h4&gt;GET 요청&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;GET http://localhost:3000/api/posts/1&lt;/code&gt; 요청으로 특정 게시글 조회&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;PUT 요청&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;PUT http://localhost:3000/api/posts/1
Body:
{
  &amp;quot;title&amp;quot;: &amp;quot;수정된 제목&amp;quot;,
  &amp;quot;content&amp;quot;: &amp;quot;수정된 내용&amp;quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;DELETE 요청&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;DELETE http://localhost:3000/api/posts/1&lt;/code&gt; 요청으로 게시글 삭제&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;5. 클라이언트 페이지 구현하기&lt;/h2&gt;
&lt;p&gt;이제 axios를 사용하여 API와 통신하는 클라이언트 페이지들을 만들어봅시다.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://velog.io/@odada/%EB%8F%99%EA%B8%B0Synchronous%EC%99%80-%EB%B9%84%EB%8F%99%EA%B8%B0Asynchronous&quot;&gt;동기와 비동기&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;5-0. API Route(/api/posts) 와 데이터 파일(/data/posts.js)의 차이점&lt;/h3&gt;
&lt;h4&gt;&lt;code&gt;/data/posts.js&lt;/code&gt; 로 직접 접근하면&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;이는 실제 소스 코드 파일입니다&lt;/li&gt;
&lt;li&gt;보안상 클라이언트에서 직접 접근할 수 없습니다&lt;/li&gt;
&lt;li&gt;서버의 파일 시스템에 있는 실제 파일입니다&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;&lt;code&gt;/api/posts&lt;/code&gt; API Route 로 접근하면&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;이는 서버에서 실행되는 엔드포인트입니다&lt;/li&gt;
&lt;li&gt;API Route 내부에서 &lt;code&gt;posts.js&lt;/code&gt; 데이터를 안전하게 불러와서 클라이언트에 전달합니다&lt;/li&gt;
&lt;li&gt;데이터 처리, 필터링, 보안 검사 등을 수행할 수 있습니다&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// data/posts.js (서버의 데이터 파일)
export const posts = [
  { id: 1, title: &amp;quot;글1&amp;quot; },
  { id: 2, title: &amp;quot;글2&amp;quot; }
];

// api/posts/route.js (API Route)
import { posts } from &amp;#39;@/data/posts&amp;#39;;

// 이 파일이 `/api/posts` 엔드포인트로 요청되면
// 데이터 파일에서 데이터를 불러와서 클라이언트에 전달합니다
export async function GET() {
  return NextResponse.json(posts);
}

// 클라이언트 컴포넌트
// 브라우저에서 `/api/posts` 로 GET요청을 보냅니다.
// 위 요청이 위의 GET함수로 전달됩니다.
axios.get(&amp;#39;/api/posts&amp;#39;)&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;5-1. 글 목록 페이지 (&lt;code&gt;/posts&lt;/code&gt;)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;axios&lt;/code&gt;로 받은 응답에는 여러 정보가 포함되어 있는데, 실제 데이터는 &lt;code&gt;data&lt;/code&gt; 속성에 들어있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;{
  data: {
    // 실제 서버에서 받은 데이터
    title: &amp;quot;게시글 제목&amp;quot;,
    content: &amp;quot;게시글 내용&amp;quot;
  },
  status: 200,        // HTTP 상태 코드
  statusText: &amp;quot;OK&amp;quot;,   // 상태 메시지
  headers: {},        // 응답 헤더
  config: {}          // 요청 설정
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// app/posts/page.js
&amp;#39;use client&amp;#39;;
import { useState, useEffect } from &amp;#39;react&amp;#39;;
import Link from &amp;#39;next/link&amp;#39;;
import { useRouter } from &amp;#39;next/navigation&amp;#39;; // 페이지 이동을 위한 라우터
import axios from &amp;#39;axios&amp;#39;;

export default function PostsPage() {
  const router = useRouter(); // 라우터 객체
  const [posts, setPosts] = useState([]); // 게시글 상태
  const [loading, setLoading] = useState(true); // 로딩 상태

  useEffect(() =&amp;gt; {
    // axios.get().then().catch()으로 비동기 처리
    axios
      .get(&amp;#39;/api/posts&amp;#39;) // 브라우저에서 /api/posts로 GET 요청을 보냅니다
      .then((res) =&amp;gt; {
        setPosts(res.data); // 데이터를 상태에 저장
        setLoading(false); // 로딩 시 false로 변경
      })
      .catch((error) =&amp;gt; {
        console.error(&amp;#39;Error:&amp;#39;, error);
        setLoading(false);
      });
  }, []);

  const handleDelete = async (id) =&amp;gt; {
    // 삭제를 취소하면 함수 종료
    if (!confirm(&amp;#39;정말 삭제하시겠습니까?&amp;#39;)) return;

    try {
      const res = await axios.delete(`/api/posts/${id}`); // 브라우저에서 /api/posts/1로 DELETE 요청을 보냅니다
      // 서버에서 응답이 오면
      if (res.status === 200) {
        setPosts(posts.filter((post) =&amp;gt; post.id !== id)); // 삭제된 게시글 제외
      } else {
        alert(&amp;#39;삭제에 실패했습니다.&amp;#39;); 
      }
    } catch (error) {
      alert(&amp;#39;오류가 발생했습니다.&amp;#39;);
    }
  };

  // 상세 페이지로 이동하는 함수
  const handlePostClick = (id) =&amp;gt; {
    router.push(`/posts/${id}`);
  };

  if (loading) return &amp;lt;div&amp;gt;로딩 중...&amp;lt;/div&amp;gt;;

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h1&amp;gt;게시글 목록&amp;lt;/h1&amp;gt;
      &amp;lt;Link href=&amp;quot;/posts/write&amp;quot;&amp;gt;글쓰기&amp;lt;/Link&amp;gt;

      &amp;lt;div&amp;gt;
        {posts.map((post) =&amp;gt; (
          &amp;lt;Link
              key={post.id}
              href={`/posts/${post.id}`}
              className=&amp;quot;cursor-pointer block&amp;quot;  // block 추가하여 전체 영역 클릭 가능하게
            &amp;gt;
            &amp;lt;h2&amp;gt;{post.title}&amp;lt;/h2&amp;gt;
            &amp;lt;p&amp;gt;{post.content}&amp;lt;/p&amp;gt;
            &amp;lt;span&amp;gt;{new Date(post.createdAt).toLocaleDateString()}&amp;lt;/span&amp;gt;
          &amp;lt;/Link&amp;gt;
        ))}
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;5-2. 글쓰기 페이지 (&lt;code&gt;/posts/write&lt;/code&gt;)&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// app/posts/write/page.js
&amp;#39;use client&amp;#39;;
import { useState } from &amp;#39;react&amp;#39;;
import { useRouter } from &amp;#39;next/navigation&amp;#39;;
import axios from &amp;#39;axios&amp;#39;;

export default function WritePage() {
  const router = useRouter();
  const [title, setTitle] = useState(&amp;#39;&amp;#39;);
  const [content, setContent] = useState(&amp;#39;&amp;#39;);

  const handleSubmit = async (e) =&amp;gt; {
    e.preventDefault();

    try {
      const res = await axios.post(&amp;#39;/api/posts&amp;#39;, { title, content });

      if (res.status === 201) { // HTTP 201 Created
        router.push(&amp;#39;/posts&amp;#39;);
      } else {
        alert(&amp;#39;글 작성에 실패했습니다.&amp;#39;);
      }
    } catch (error) {
      console.error(&amp;#39;Error:&amp;#39;, error);
      alert(&amp;#39;오류가 발생했습니다.&amp;#39;);
    }
  };

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h1&amp;gt;글쓰기&amp;lt;/h1&amp;gt;
      &amp;lt;form onSubmit={handleSubmit}&amp;gt;
        &amp;lt;div&amp;gt;
          &amp;lt;label&amp;gt;제목&amp;lt;/label&amp;gt;
          &amp;lt;input
            type=&amp;quot;text&amp;quot;
            value={title}
            onChange={(e) =&amp;gt; setTitle(e.target.value)}
            required
          /&amp;gt;
        &amp;lt;/div&amp;gt;
        &amp;lt;div&amp;gt;
          &amp;lt;label&amp;gt;내용&amp;lt;/label&amp;gt;
          &amp;lt;textarea
            value={content}
            onChange={(e) =&amp;gt; setContent(e.target.value)}
            required
          /&amp;gt;
        &amp;lt;/div&amp;gt;
        &amp;lt;button type=&amp;quot;button&amp;quot; onClick={() =&amp;gt; router.back()}&amp;gt;취소&amp;lt;/button&amp;gt;
        &amp;lt;button type=&amp;quot;submit&amp;quot;&amp;gt;등록&amp;lt;/button&amp;gt;
      &amp;lt;/form&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;5-3. 글 상세 페이지 (&lt;code&gt;/posts/[id]&lt;/code&gt;)&lt;/h3&gt;
&lt;h4&gt;const resolvedParams = use(params);&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;params&lt;/code&gt;의 상태&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// pages/posts/[id]/page.jsx 파일에서
// URL이 /posts/1 이라면
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;// params는 이런 형태의 Promise&lt;br&gt;params = Promise.resolve({ id: &amp;#39;1&amp;#39; })&lt;br&gt;// 바로 params.id 접근 불가&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
2. use() 훅의 역할
```javascript
// use() 훅이 Promise를 풀어서(unwrap) 일반 객체로 변환
const resolvedParams = use(params);

// resolvedParams는 이제 일반 객체가 됨
resolvedParams = { id: &amp;#39;1&amp;#39; }
// 이제 resolvedParams.id 접근 가능&lt;/code&gt;&lt;/pre&gt;&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;코드 예시&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// ❌ 잘못된 방법
console.log(params.id)  // undefined
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;// ✅ 올바른 방법&lt;br&gt;const resolvedParams = use(params);&lt;br&gt;console.log(resolvedParams.id)  // &amp;#39;1&amp;#39;&lt;/p&gt;
&lt;p&gt;// API 호출할 때도&lt;br&gt;axios.get(&lt;code&gt;/api/posts/${resolvedParams.id}&lt;/code&gt;)  // OK!&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
```typescript
// app/posts/[id]/page.js
&amp;#39;use client&amp;#39;;
import { useState, useEffect, use } from &amp;#39;react&amp;#39;;
import { useRouter } from &amp;#39;next/navigation&amp;#39;;
import Link from &amp;#39;next/link&amp;#39;;
import axios from &amp;#39;axios&amp;#39;;

export default function PostDetailPage({ params }) {
  const router = useRouter();
  const [post, setPost] = useState(null);
  const [loading, setLoading] = useState(true);
  const resolvedParams = use(params); // params 객체를 풀어서 사용

  useEffect(() =&amp;gt; {
    axios
      .get(`/api/posts/${resolvedParams.id}`)
      .then((res) =&amp;gt; {
        setPost(res.data);
        setLoading(false);
      })
      .catch((error) =&amp;gt; {
        console.error(&amp;#39;Error:&amp;#39;, error);
        setLoading(false);
        alert(&amp;#39;게시글을 불러올 수 없습니다.&amp;#39;);
        router.push(&amp;#39;/posts&amp;#39;);
      });
  }, [resolvedParams.id, router]);

  const handleDelete = async () =&amp;gt; {
    if (!confirm(&amp;#39;정말 삭제하시겠습니까?&amp;#39;)) return;

    try {
      const res = await axios.delete(`/api/posts/${resolvedParams.id}`);
      if (res.status === 200) {
        router.push(&amp;#39;/posts&amp;#39;);
      } else {
        alert(&amp;#39;삭제에 실패했습니다.&amp;#39;);
      }
    } catch (error) {
      alert(&amp;#39;오류가 발생했습니다.&amp;#39;);
    }
  };

  if (loading) return &amp;lt;div&amp;gt;로딩 중...&amp;lt;/div&amp;gt;;
  if (!post) return &amp;lt;div&amp;gt;게시글을 찾을 수 없습니다.&amp;lt;/div&amp;gt;;

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h1&amp;gt;{post.title}&amp;lt;/h1&amp;gt;
      &amp;lt;p&amp;gt;작성일: {post.createdAt}&amp;lt;/p&amp;gt;
      &amp;lt;div&amp;gt;
        &amp;lt;p&amp;gt;{post.content}&amp;lt;/p&amp;gt;
      &amp;lt;/div&amp;gt;
      &amp;lt;div&amp;gt;
        &amp;lt;Link href=&amp;quot;/posts&amp;quot;&amp;gt;목록&amp;lt;/Link&amp;gt;
        &amp;lt;Link href={`/posts/${resolvedParams.id}/edit`}&amp;gt;수정&amp;lt;/Link&amp;gt;
        &amp;lt;button onClick={handleDelete}&amp;gt;삭제&amp;lt;/button&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;5-4. 글 수정 페이지 (&lt;code&gt;/posts/[id]/edit&lt;/code&gt;)&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// app/posts/[id]/edit/page.js
import EditForm from &amp;quot;./editForm&amp;quot;;

export default function EditPage({ params }) {
  return &amp;lt;EditForm postId={params.id} /&amp;gt;;
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// app/posts/[id]/edit/EditForm.js
&amp;#39;use client&amp;#39;;

import { useState, useEffect } from &amp;#39;react&amp;#39;;
import { useRouter } from &amp;#39;next/navigation&amp;#39;;
import axios from &amp;#39;axios&amp;#39;;

export default function EditForm({ postId }) {
  const router = useRouter();
  const [title, setTitle] = useState(&amp;#39;&amp;#39;);
  const [content, setContent] = useState(&amp;#39;&amp;#39;);

  useEffect(() =&amp;gt; {
    const fetchPost = async () =&amp;gt; {
      try {
        const { data } = await axios.get(`/api/posts/${postId}`);
        setTitle(data.title);
        setContent(data.content);
      } catch (error) {
        console.error(&amp;#39;Error fetching post:&amp;#39;, error);
        alert(&amp;#39;게시글을 불러올 수 없습니다.&amp;#39;);
        router.push(&amp;#39;/posts&amp;#39;);
      }
    };

    fetchPost();
  }, [postId, router]);

  const handleSubmit = async (e) =&amp;gt; {
    e.preventDefault();

    try {
      await axios.put(`/api/posts/${postId}`, { title, content });
      router.push(&amp;#39;/posts&amp;#39;);
    } catch (error) {
      console.error(&amp;#39;Error updating post:&amp;#39;, error);
      alert(&amp;#39;수정에 실패했습니다.&amp;#39;);
    }
  };

  return (
    &amp;lt;div className=&amp;quot;p-4 max-w-xl mx-auto&amp;quot;&amp;gt;
      &amp;lt;h1 className=&amp;quot;text-2xl font-bold mb-4&amp;quot;&amp;gt;글 수정&amp;lt;/h1&amp;gt;
      &amp;lt;form onSubmit={handleSubmit} className=&amp;quot;space-y-4&amp;quot;&amp;gt;
        &amp;lt;div className=&amp;quot;space-y-2&amp;quot;&amp;gt;
          &amp;lt;label className=&amp;quot;block font-medium&amp;quot;&amp;gt;제목&amp;lt;/label&amp;gt;
          &amp;lt;input
            type=&amp;quot;text&amp;quot;
            value={title}
            onChange={(e) =&amp;gt; setTitle(e.target.value)}
            required
            className=&amp;quot;w-full p-2 border rounded&amp;quot;
          /&amp;gt;
        &amp;lt;/div&amp;gt;
        &amp;lt;div className=&amp;quot;space-y-2&amp;quot;&amp;gt;
          &amp;lt;label className=&amp;quot;block font-medium&amp;quot;&amp;gt;내용&amp;lt;/label&amp;gt;
          &amp;lt;textarea
            value={content}
            onChange={(e) =&amp;gt; setContent(e.target.value)}
            required
            className=&amp;quot;w-full p-2 border rounded h-32&amp;quot;
          /&amp;gt;
        &amp;lt;/div&amp;gt;
        &amp;lt;div className=&amp;quot;flex gap-2&amp;quot;&amp;gt;
          &amp;lt;button 
            type=&amp;quot;button&amp;quot; 
            onClick={() =&amp;gt; router.back()}
            className=&amp;quot;px-4 py-2 bg-gray-200 rounded hover:bg-gray-300&amp;quot;
          &amp;gt;
            취소
          &amp;lt;/button&amp;gt;
          &amp;lt;button 
            type=&amp;quot;submit&amp;quot;
            className=&amp;quot;px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600&amp;quot;
          &amp;gt;
            수정
          &amp;lt;/button&amp;gt;
        &amp;lt;/div&amp;gt;
      &amp;lt;/form&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Front/Node.js</category>
      <author>oodada</author>
      <guid isPermaLink="true">https://oddcode.tistory.com/364</guid>
      <comments>https://oddcode.tistory.com/364#entry364comment</comments>
      <pubDate>Mon, 9 Dec 2024 22:54:30 +0900</pubDate>
    </item>
    <item>
      <title>node.js로 API 통신 구현하기</title>
      <link>https://oddcode.tistory.com/363</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;ㅇㅇ&lt;/p&gt;</description>
      <category>Front/Node.js</category>
      <author>oodada</author>
      <guid isPermaLink="true">https://oddcode.tistory.com/363</guid>
      <comments>https://oddcode.tistory.com/363#entry363comment</comments>
      <pubDate>Mon, 9 Dec 2024 22:54:04 +0900</pubDate>
    </item>
    <item>
      <title>Express 모듈을 사용하여 서버 만들기</title>
      <link>https://oddcode.tistory.com/362</link>
      <description>&lt;h1&gt;Express 모듈을 사용하여 서버 만들기&lt;/h1&gt;
&lt;h2&gt;1. Express 란?&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;http&lt;/code&gt;, &lt;code&gt;url&lt;/code&gt;, &lt;code&gt;fs&lt;/code&gt; 등 Node.js의 기본 모듈을 사용하여 서버를 만들 수 있지만, &lt;code&gt;Express&lt;/code&gt; 모듈을 사용하면 더 쉽고 강력한 서버를 만들 수 있습니다.&lt;br&gt;자바의 Spring, Python의 Django와 같이 Node.js의 대표적인 웹 프레임워크로 &lt;code&gt;Request&lt;/code&gt;, &lt;code&gt;Response&lt;/code&gt; 객체를 사용하여 HTTP 요청을 처리할 수 있습니다.&lt;/p&gt;
&lt;h3&gt;- Express를 사용하는 이유&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;간단하고 직관적인 API&lt;/li&gt;
&lt;li&gt;미들웨어를 통한 유연한 기능 확장&lt;/li&gt;
&lt;li&gt;강력한 라우팅 시스템&lt;/li&gt;
&lt;li&gt;큰 커뮤니티와 풍부한 생태계&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;- 참조&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;프레임워크&lt;/code&gt;란 개발자가 웹 개발에 필요한 기능을 미리 구현해 놓은 것으로, 개발자가 필요한 기능을 사용하기만 하면 되기 때문에 개발 시간을 단축할 수 있습니다. 웹을 만드는 회사는 대부분 프레임워크를 사용하여 개발합니다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;라이브러리&lt;/code&gt;란 개발자가 필요한 기능을 직접 구현해야 하는 것으로, 개발자가 직접 구현해야 하기 때문에 개발 시간이 더 오래 걸릴 수 있습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;자바스크립트의 클라이언트 사이드 프레임워크: React, Vue, Angular&lt;/li&gt;
&lt;li&gt;자바스크립트의 서버 사이드 프레임워크: Express, Koa, Nest.js&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;2. Express 설치와 사용&lt;/h2&gt;
&lt;h3&gt;- Express 설치&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 프로젝트 초기화
npm init -y

# Express 설치
npm install express&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- nodemon 설치&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;nodemon&lt;/code&gt;은 파일이 수정될 때마다 서버를 자동으로 재시작해주는 패키지로 개발 시간을 단축할 수 있음.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# nodemon 설치
npm install -g nodemon&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- package.json 수정&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &amp;quot;name&amp;quot;: &amp;quot;my-express-app&amp;quot;,
  &amp;quot;version&amp;quot;: &amp;quot;1.0.0&amp;quot;,
  &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,
  &amp;quot;main&amp;quot;: &amp;quot;index.js&amp;quot;,
  &amp;quot;scripts&amp;quot;: {
    &amp;quot;dev&amp;quot;: &amp;quot;nodemon index.js&amp;quot; // nodemon으로 서버 실행
  },
  &amp;quot;keywords&amp;quot;: [],
  &amp;quot;author&amp;quot;: &amp;quot;&amp;quot;,
  &amp;quot;license&amp;quot;: &amp;quot;ISC&amp;quot;,
  &amp;quot;dependencies&amp;quot;: {
    &amp;quot;express&amp;quot;: &amp;quot;^4.17.1&amp;quot;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;3. Express 서버 만들기&lt;/h2&gt;
&lt;h3&gt;- Express 서버 만들기&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;get() 메서드: 해당 경로로 GET 요청이 오면 콜백 함수를 실행.&lt;/li&gt;
&lt;li&gt;send() 메서드: 클라이언트에 응답을 보냄.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// index.js
const express = require(&amp;#39;express&amp;#39;);
const app = express();

// 기본 라우트에 GET 요청이 오면 콜백 함수 실행
app.get(&amp;#39;/&amp;#39;, (req, res) =&amp;gt; {
  // send() 메서드: 클라이언트에 응답을 보냄.
  // send() 메서드 하나로 응답을 보내고 종료하는 것까지 가능
  res.send(&amp;#39;Hello, Express!&amp;#39;);
});

// 서버 실행
app.listen(8080, () =&amp;gt; {
  console.log(&amp;#39;서버가 http://localhost:8080 에서 실행 중입니다.&amp;#39;);
});&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- Express 서버 실행&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;nodemon index.js&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;터미널에서 &lt;code&gt;nodemon index.js&lt;/code&gt; 명령어를 실행 후, 크롬의 주소창에 &lt;code&gt;http://localhost:8080&lt;/code&gt;을 입력하면, &amp;quot;Hello, Express!&amp;quot;가 출력됩니다.&lt;/p&gt;
&lt;h3&gt;- Express 서버 파일 읽어오기&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ touch index.html&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;!-- index.html --&amp;gt;
&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html lang=&amp;quot;en&amp;quot;&amp;gt;
&amp;lt;head&amp;gt;
  &amp;lt;title&amp;gt;Express Server&amp;lt;/title&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
  &amp;lt;h1&amp;gt;Express Server&amp;lt;/h1&amp;gt;
  &amp;lt;p&amp;gt;파일을 읽어와 응답하는 서버&amp;lt;/p&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// index.js
const express = require(&amp;#39;express&amp;#39;);

const app = express();
// 서버가 실행될 포트 설정
// 클라우드 배포에 환경변수의 PORT 사용하거나 로컬 서버 사용
app.set(&amp;#39;port&amp;#39;, process.env.PORT || 8080);

// 데이터를 get 요청으로 받아올 때
// &amp;#39;/&amp;#39; 경로로 GET 요청이 오면 index.html 파일을 응답으로 보냄
app.get(&amp;#39;/&amp;#39;, (req, res) =&amp;gt; {
  // sendFile() 메서드: 파일을 응답 본문으로 보냄
  res.sendFile(__dirname + &amp;#39;/index.html&amp;#39;);
});

// 서버 실행
// app.get(&amp;#39;port&amp;#39;)로 설정한 포트에서 서버 실행
app.listen(app.get(&amp;#39;port&amp;#39;), () =&amp;gt; {
  console.log(&amp;#39;서버가 http://localhost:8080 에서 실행 중입니다.&amp;#39;);
});&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;3. http 요청 메서드&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;클라이언트(프론트엔드)가 서버(백엔드)에 요청을 보낼 때 사용하는 메서드로 요청의 목적을 명시하기 위해 사용됨.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;ul&gt;
&lt;li&gt;GET: 데이터 가져올 때 사용&lt;/li&gt;
&lt;li&gt;HEAD: 문서 정보를 가져올 때 사용&lt;/li&gt;
&lt;li&gt;POST: 데이터 전송할 때 사용&lt;/li&gt;
&lt;li&gt;PUT: 데이터 업데이트할 때 사용&lt;/li&gt;
&lt;li&gt;PATCH: 데이터 일부 업데이트할 때 사용&lt;/li&gt;
&lt;li&gt;DELETE: 데이터 삭제할 때 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;4. Express 서버에 라우트 추가하기&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;post() 메서드: 해당 경로로 POST 요청이 오면 콜백 함수를 실행.&lt;/li&gt;
&lt;li&gt;URL 파라미터: &lt;code&gt;:&lt;/code&gt;를 사용하여 동적인 URL을 사용할 수 있음.&lt;/li&gt;
&lt;li&gt;req.params: URL 파라미터를 조회할 수 있음.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// index.js
const express = require(&amp;#39;express&amp;#39;);
const app = express();

app.set(&amp;#39;port&amp;#39;, process.env.PORT || 8080);

// 전체 게시글 목록 조회
app.get(&amp;#39;/posts&amp;#39;, (req, res) =&amp;gt; {
   res.send(&amp;#39;전체 게시글 목록&amp;#39;);
});

// 새 게시글 작성
app.post(&amp;#39;/posts&amp;#39;, (req, res) =&amp;gt; {
   res.send(&amp;#39;새 게시글이 작성되었습니다.&amp;#39;);
});

// 특정 게시글 조회
app.get(&amp;#39;/posts/:id&amp;#39;, (req, res) =&amp;gt; {
   res.send(`${req.params.id}번 게시글 내용`);
});

// 특정 게시글 수정
app.put(&amp;#39;/posts/:id&amp;#39;, (req, res) =&amp;gt; {
   res.send(`${req.params.id}번 게시글이 수정되었습니다.`);
});

// 특정 게시글 삭제
app.delete(&amp;#39;/posts/:id&amp;#39;, (req, res) =&amp;gt; {
   res.send(`${req.params.id}번 게시글이 삭제되었습니다.`);
});

// 서버 실행
app.listen(app.get(&amp;#39;port&amp;#39;), () =&amp;gt; {
   console.log(&amp;#39;서버가 http://localhost:8080 에서 실행 중입니다.&amp;#39;);
});&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;전체 게시글 목록 보기 : GET &lt;a href=&quot;http://localhost:8080/posts&quot;&gt;http://localhost:8080/posts&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;특정 게시글 보기 : GET &lt;a href=&quot;http://localhost:8080/posts/1&quot;&gt;http://localhost:8080/posts/1&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;새 게시글 작성 : POST &lt;a href=&quot;http://localhost:8080/posts&quot;&gt;http://localhost:8080/posts&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;게시글 수정 : PUT &lt;a href=&quot;http://localhost:8080/posts/1&quot;&gt;http://localhost:8080/posts/1&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;게시글 삭제 : DELETE &lt;a href=&quot;http://localhost:8080/posts/1&quot;&gt;http://localhost:8080/posts/1&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;5. Express 서버에 미들웨어 추가하기&lt;/h2&gt;
&lt;h3&gt;- 미들웨어란?&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;요청(req)과 응답(res) 사이에 실행되는 함수.&lt;/li&gt;
&lt;li&gt;요청과 응답을 조작하거나, 요청에 대한 응답을 보내기 전에 중간 처리를 할 수 있음.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;- 미들웨어 추가하기&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;app.use()&lt;/code&gt; 메서드를 사용하여 미들웨어를 추가.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// index.js
const express = require(&amp;#39;express&amp;#39;);
const app = express();

app.set(&amp;#39;port&amp;#39;, process.env.PORT || 8080);

// 1. 로깅 미들웨어 - 어떤 요청이 왔는지 확인
app.use((req, res, next) =&amp;gt; {
    console.log(&amp;#39;-------------------&amp;#39;);
    console.log(&amp;#39;새로운 요청이 왔어요!&amp;#39;);
    console.log(&amp;#39;요청 종류:&amp;#39;, req.method);
    console.log(&amp;#39;요청 주소:&amp;#39;, req.url);
    console.log(&amp;#39;-------------------&amp;#39;);
    next(); // 
});

// 게시글 목록 조회
app.get(&amp;#39;/posts&amp;#39;, (req, res) =&amp;gt; {
    res.send(&amp;#39;전체 게시글 목록&amp;#39;);
});

// 게시글 작성
app.post(&amp;#39;/posts&amp;#39;, (req, res) =&amp;gt; {
    console.log(&amp;#39;작성된 글:&amp;#39;, req.body);  // JSON 미들웨어 덕분에 사용 가능
    res.send(&amp;#39;게시글이 작성되었습니다.&amp;#39;);
});

app.listen(app.get(&amp;#39;port&amp;#39;), () =&amp;gt; {
    console.log(&amp;#39;서버가 http://localhost:8080 에서 실행 중입니다.&amp;#39;);
});&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;localhost:3000&lt;/code&gt;에 접속하면, 콘솔에 요청 방식(GET)과 요청 주소(/)가 출력됨.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;localhost:3000/users&lt;/code&gt;에 접속하면, 콘솔에 요청 방식(GET)과 요청 주소(/users)가 출력됨.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;6. Express 서버에 정적 파일 제공하기&lt;/h2&gt;
&lt;h3&gt;- 정적 파일이란?&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;서버에서 변경 없이 그대로 클라이언트에 전달되는 파일.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;ul&gt;
&lt;li&gt;html 내부에 이미지를 띄우려면 이미지 파일을 서버에서 클라이언트로 전달해야 함.&lt;/li&gt;
&lt;li&gt;html은 이미지 파일이 있는 경로를 참조하여 이미지를 띄움.&lt;/li&gt;
&lt;li&gt;HTML, CSS, JavaScript, 이미지, 폰트 등.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;- 정적 파일 제공하기&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;public&lt;/code&gt; 폴더에 &lt;code&gt;cat.jpg&lt;/code&gt; 파일을 생성.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;my-express-app/
├── public/                # 정적 파일들이 위치할 폴더
│   ├── images/           # 이미지 파일들
│   │   └── cat.jpg
│   ├── css/             # CSS 파일들
│   │   └── style.css
│   └── index.html       # 메인 HTML 파일
└── index.js             # 서버 파일&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// index.js
const express = require(&amp;#39;express&amp;#39;);
const app = express();

// 정적 파일 제공
app.use(express.static(&amp;#39;public&amp;#39;));

// 서버 실행
app.listen(8080, () =&amp;gt; {
  console.log(&amp;#39;서버가 http://localhost:8080 에서 실행 중입니다.&amp;#39;);
});&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;!-- index.html --&amp;gt;
&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html lang=&amp;quot;en&amp;quot;&amp;gt;
&amp;lt;head&amp;gt;
  &amp;lt;title&amp;gt;Express Static&amp;lt;/title&amp;gt;
  &amp;lt;link rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;/css/style.css&amp;quot;&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
  &amp;lt;h1&amp;gt;Express Static&amp;lt;/h1&amp;gt;
  &amp;lt;img src=&amp;quot;/images/cat.jpg&amp;quot; alt=&amp;quot;Cat&amp;quot;&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;localhost:8080&lt;/code&gt;에 접속하면, &lt;code&gt;public/index.html&lt;/code&gt; 파일이 출력됨.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;localhost:8080/images/cat.jpg&lt;/code&gt;에 접속하면, &lt;code&gt;public/images/cat.jpg&lt;/code&gt; 파일이 출력됨.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;localhost:8080/css/style.css&lt;/code&gt;에 접속하면, &lt;code&gt;public/css/style.css&lt;/code&gt; 파일이 출력됨.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Front/Node.js</category>
      <author>oodada</author>
      <guid isPermaLink="true">https://oddcode.tistory.com/362</guid>
      <comments>https://oddcode.tistory.com/362#entry362comment</comments>
      <pubDate>Mon, 9 Dec 2024 22:51:43 +0900</pubDate>
    </item>
    <item>
      <title>node.js로 서버 만들기</title>
      <link>https://oddcode.tistory.com/361</link>
      <description>&lt;h1&gt;node.js로 서버 만들기 - node.js 배우기&lt;/h1&gt;
&lt;h2&gt;1. 서버란?&lt;/h2&gt;
&lt;h3&gt;서버와 클라이언트&lt;/h3&gt;
&lt;p&gt;서버는 요청을 받는 &lt;strong&gt;응답자&lt;/strong&gt;이고, 클라이언트는 요청을 보내는 &lt;strong&gt;요청자&lt;/strong&gt;입니다.  &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;클라이언트&lt;/strong&gt;: 웹 브라우저(Chrome, Safari).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;서버&lt;/strong&gt;: 네이버, 구글 같은 웹사이트를 운영하는 컴퓨터.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;쉽게 서버의 동작 이해하기&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;브라우저에서 &amp;quot;구글 검색&amp;quot;을 입력(요청)하면,&lt;/li&gt;
&lt;li&gt;구글의 서버가 요청을 받아서 검색 결과를 준비(처리)한 뒤,&lt;/li&gt;
&lt;li&gt;준비된 검색 결과를 브라우저로 보냄(응답).&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;서버의 예&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;웹 서버&lt;/strong&gt;: 웹사이트를 보여주는 서버.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;예: 네이버, 구글, 유튜브.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;파일 서버&lt;/strong&gt;: 파일을 저장하고 전송하는 서버.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;예: Google Drive, Dropbox.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;게임 서버&lt;/strong&gt;: 온라인 게임에서 플레이어 간 연결을 관리하는 서버.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;예: LOL, PUBG.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;호스팅 서버&lt;/strong&gt;: 웹사이트를 저장하고 관리하는 서버.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;예: AWS, Azure.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;2. 프로젝트 시작&lt;/h2&gt;
&lt;h3&gt;- 프로젝트 생성&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href=&quot;https://velog.io/@odada/NPM-%EC%A3%BC%EC%9A%94-%EB%AA%85%EB%A0%B9%EC%96%B4-%EC%A0%95%EB%A6%AC&quot;&gt;NPM 명령어&lt;/a&gt; 로 프로젝트를 생성합니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;node-server&lt;/code&gt; 프로젝트 폴더를 생성하고, &lt;code&gt;npm init&lt;/code&gt; 명령어로 &lt;code&gt;package.json&lt;/code&gt; 파일을 생성합니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ mkdir node-server
$ cd node-server
$ npm init -y&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;2. 서버 만들기&lt;/h2&gt;
&lt;p&gt;서버 만들기 전에 w3schools의 &lt;a href=&quot;https://www.w3schools.com/nodejs/nodejs_http.asp&quot;&gt;Node.js HTTP 모듈&lt;/a&gt;을 참고하세요.&lt;/p&gt;
&lt;h3&gt;- 간단한 HTTP 서버 만들기&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;HTTP 서버는 클라이언트(브라우저)의 요청을 받고, 이에 응답하는 프로그램입니다.&lt;br&gt;Node.js는 http 모듈을 사용해 쉽게 서버를 생성할 수 있습니다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;http.createServer()&lt;/code&gt; 메서드로 서버를 생성합니다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;server.listen()&lt;/code&gt; 메서드로 서버를 실행합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// server.js
// Node.js의 기본 내장 모듈인 &amp;#39;http&amp;#39; 모듈을 불러옵니다.
const http = require(&amp;#39;http&amp;#39;);

// http 서버를 생성하는 메서드
// 콜백 함수로 request(요청)과 response(응답) 객체를 매개변수로 받습니다.
const server = http.createServer((req, res) =&amp;gt; {
    // 요청이 들어오면 응답합니다.
    res.writeHead(200, { &amp;#39;Content-Type&amp;#39;: &amp;#39;text/html&amp;#39; }); // 응답 헤더 설정
    res.write(&amp;#39;&amp;lt;h1&amp;gt;Hello, Node.js!&amp;lt;/h1&amp;gt;&amp;#39;); // 응답 본문
    res.end(&amp;#39;&amp;lt;p&amp;gt;http 모듈 공부 중...&amp;lt;/p&amp;gt;&amp;#39;); // 응답 종료
});

// 서버가 8080 포트에서 실행되도록 설정합니다.
// .listen() : 특정 포트에서 서버를 실행하고 클라이언트의 요청을 기다립니다.
server.listen(8080, () =&amp;gt; {
    console.log(&amp;#39;8080 포트에서 서버가 실행 중입니다.&amp;#39;);
});&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;localhost는 자신의 컴퓨터를 가리키는 주소입니다.&lt;/li&gt;
&lt;li&gt;8080은 서버가 실행될 포트 번호입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ node server.js
8080 포트에서 서버가 실행 중입니다.&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;내장 모듈 : &lt;a href=&quot;https://www.w3schools.com/nodejs/nodejs_modules.asp&quot;&gt;https://www.w3schools.com/nodejs/nodejs_modules.asp&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;- http 상태 코드&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;HTTP 상태 코드는 클라이언트의 요청에 대한 서버의 응답 상태를 나타내는 코드입니다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;ul&gt;
&lt;li&gt;1xx: 처리중(Informational) / 요청을 받았으며 프로세스를 계속합니다.&lt;/li&gt;
&lt;li&gt;2xx: 성공(Success) / 요청을 성공적으로 받았으며 이해했고 수용했습니다.&lt;/li&gt;
&lt;li&gt;3xx: 리다이렉션(Redirection) / 요청을 완료하기 위해 추가 동작이 필요합니다.&lt;/li&gt;
&lt;li&gt;4xx: 클라이언트 오류(Client Error) / 요청의 문법이 잘못되었거나 요청을 처리할 수 없습니다.&lt;/li&gt;
&lt;li&gt;5xx: 서버 오류(Server Error) / 서버가 유효한 요청에 대해 충족을 실패했습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;참고: &lt;a href=&quot;https://developer.mozilla.org/ko/docs/Web/HTTP/Status&quot;&gt;MDN HTTP 상태 코드&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;주요 상태 코드&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;200: OK / 요청이 성공했습니다.&lt;/li&gt;
&lt;li&gt;404: Not Found / 요청한 페이지를 찾을 수 없습니다.&lt;/li&gt;
&lt;li&gt;500: Internal Server Error / 서버에 오류가 발생했습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;- 리스닝 이벤트(이벤트 리스너)&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;Node.js에서 리스닝 이벤트는 특정 상황(이벤트)이 발생했을 때 실행되는 코드&lt;br&gt;서버가 클라이언트의 요청을 받아들이기 시작할 때 발생하는 이벤트입니다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;server.listen()&lt;/code&gt; 메서드는 서버를 실행하고 클라이언트의 요청을 기다리는 메서드입니다.&lt;/li&gt;
&lt;li&gt;서버가 정상적으로 실행되면 &lt;code&gt;listening&lt;/code&gt; 이벤트가 발생합니다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;server.on(&amp;#39;listening&amp;#39;, 콜백함수)&lt;/code&gt;로 리스닝 이벤트를 등록할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;server.on(&amp;#39;error&amp;#39;, 콜백함수)&lt;/code&gt;로 에러 이벤트를 등록할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// server.js

const http = require(&amp;#39;http&amp;#39;);

// 서버 생성
const server = http.createServer((req, res) =&amp;gt; {
  res.writeHead(200, { &amp;#39;Content-Type&amp;#39;: &amp;#39;text/html&amp;#39; });
    res.write(&amp;#39;&amp;lt;h1&amp;gt;Hello, Node.js!&amp;lt;/h1&amp;gt;&amp;#39;);
    res.end(&amp;#39;&amp;lt;p&amp;gt;http 모듈 공부 중...&amp;lt;/p&amp;gt;&amp;#39;);
});

// `listening` 이벤트 등록
server.on(&amp;#39;listening&amp;#39;, () =&amp;gt; {
  console.log(&amp;#39;서버가 정상적으로 실행되었습니다!&amp;#39;);
});

// `error` 이벤트 등록
server.on(&amp;#39;error&amp;#39;, (error) =&amp;gt; {
  console.error(&amp;#39;서버 실행 중 오류 발생:&amp;#39;, error.message);
});

// 서버 실행
server.listen(8080, () =&amp;gt; {
  console.log(&amp;#39;8080 포트에서 서버가 실행 중입니다.&amp;#39;);
});&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ node server.js
8080 포트에서 서버가 실행 중입니다.
서버가 정상적으로 실행되었습니다!&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;크롬의 주소창에 &lt;code&gt;http://localhost:8080&lt;/code&gt;을 입력하면, &amp;quot;Hello, Node.js!&amp;quot;가 출력됩니다.&lt;/p&gt;
&lt;h4&gt;참고&lt;/h4&gt;
&lt;p&gt;터미널에서는 메시지가 보이지만, 크롬의 개발자 도구 콘솔창에서 메시지가 보이지 않는 이유는 &lt;strong&gt;터미널 출력(console.log)&lt;/strong&gt;와 클라이언트(브라우저) 출력이 서로 다른 개념이기 때문입니다.&lt;/p&gt;
&lt;p&gt;이유&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;서버 측 로그&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;console.log는 Node.js 서버에서 실행되는 코드에서 발생한 로그를 출력하기 때문에&lt;/li&gt;
&lt;li&gt;위 메시지는 서버의 터미널에서만 확인할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;클라이언트(브라우저) 로그&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;브라우저의 개발자 도구 콘솔창은 서버에서 받은 HTML, JavaScript 등의 응답에서 발생한 로그를 보여주는 곳입니다.&lt;/li&gt;
&lt;li&gt;현재 서버가 브라우저로 응답한 HTML에는 클라이언트 측 로그를 출력하는 코드가 없기 때문에 크롬 콘솔창에서 메시지가 보이지 않습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;참고&lt;/h4&gt;
&lt;p&gt;파일을 수정할 때마다 서버를 재시작하는 것이 번거롭다면, &lt;code&gt;nodemon&lt;/code&gt; 패키지를 사용해보세요.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ npm install -g nodemon
$ nodemon server.js&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-package.json&quot;&gt;{
  &amp;quot;scripts&amp;quot;: {
    &amp;quot;start&amp;quot;: &amp;quot;nodemon server.js&amp;quot;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ npm start&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- 파일을 보내는 응답 코드&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;응답 콜백에 html을 직접 넣어주는 것이 아니라 파일을 따로 만들어 파일시스템(fs)을 이용해 읽어서 보내는 방법&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;fs&lt;/code&gt; 파일을 읽어오는 모듈&lt;/li&gt;
&lt;li&gt;&lt;code&gt;fs.promises.readFile()&lt;/code&gt; 파일을 읽어오는 메서드&lt;/li&gt;
&lt;li&gt;&lt;code&gt;res.end(data)&lt;/code&gt;로 파일을 응답 본문으로 보내면서 요청 종료&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ touch index.html&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;!-- index.html --&amp;gt;
&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html lang=&amp;quot;ko&amp;quot;&amp;gt;
&amp;lt;head&amp;gt;
    &amp;lt;meta charset=&amp;quot;UTF-8&amp;quot;&amp;gt;
    &amp;lt;title&amp;gt;Node.js로 서버 만들기&amp;lt;/title&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
    &amp;lt;h1&amp;gt;Node.js로 서버 만들기&amp;lt;/h1&amp;gt;
    &amp;lt;p&amp;gt;파일을 읽어와 응답하는 서버&amp;lt;/p&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;aync/await&lt;/code&gt; 문법을 사용하면 비동기 처리를 동기 처리처럼 작성할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;try-catch&lt;/code&gt; 문법을 사용하면 에러 처리를 쉽게 할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// fs-test.js
const http = require(&amp;#39;http&amp;#39;);
const fs = require(&amp;#39;fs&amp;#39;);

const server = http.createServer(async (req, res) =&amp;gt; {
    try {
        // fs.promises.readFile() : 파일을 읽어오는 메서드
        const data = await fs.promises.readFile(&amp;#39;./index.html&amp;#39;);
        // 200이면 성공
        res.writeHead(200, { &amp;#39;Content-Type&amp;#39;: &amp;#39;text/html&amp;#39; });
        // 파일을 읽어온 data를 응답 본문으로 보내면서 요청 종료
        res.end(data);
    } catch (error) {
        console.error(error);
        // 500이면 서버 오류
        res.writeHead(500, { &amp;#39;Content-Type&amp;#39;: &amp;#39;text/plain&amp;#39; });
        // 에러 메시지를 응답 본문으로 보내면서 요청 종료
        res.end(error.message);
    }
});

// 8080 서버 실행
server.listen(8080, () =&amp;gt; {
    console.log(&amp;#39;8080 포트에서 서버가 실행 중입니다.&amp;#39;);
});&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;3. 요청 객체(req), 응답 객체(res)&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;Node.js의 HTTP 모듈을 사용하면 서버에서 요청 객체(req)와 응답 객체(res)를 사용할 수 있습니다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;h3&gt;- 요청 객체(req)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;req.url&lt;/code&gt; : 클라이언트가 요청한 URL 주소&lt;/li&gt;
&lt;li&gt;&lt;code&gt;req.method&lt;/code&gt; : 클라이언트가 요청한 HTTP 메서드&lt;/li&gt;
&lt;li&gt;&lt;code&gt;req.headers&lt;/code&gt; : 클라이언트의 요청 헤더 정보&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;- 응답 객체(res)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;res.writeHead(상태코드, 헤더객체)&lt;/code&gt; : 응답 헤더 설정&lt;/li&gt;
&lt;li&gt;&lt;code&gt;res.write(데이터)&lt;/code&gt; : 응답 본문 작성&lt;/li&gt;
&lt;li&gt;&lt;code&gt;res.end(데이터)&lt;/code&gt; : 응답 종료&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;- REST를 통한 페이지 라우팅&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;REST(Representational State Transfer)는 자원을 이름(자원의 표현)으로 구분하여 해당 자원의 상태(정보)를 주고 받는 모델입니다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;req.url&lt;/code&gt;로 요청한 URL 주소를 확인하고, 해당 URL에 따라 다른 응답을 보내는 방식&lt;/li&gt;
&lt;li&gt;&lt;code&gt;if문&lt;/code&gt;을 사용해 URL에 따라 다른 응답을 보내는 방식을 &lt;strong&gt;라우팅&lt;/strong&gt;이라고 합니다.&lt;/li&gt;
&lt;li&gt;이렇게 URL에 따라 다른 응답을 보내는 것을 &lt;strong&gt;RESTful API&lt;/strong&gt;라고 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;!-- index.html --&amp;gt;
&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html lang=&amp;quot;ko&amp;quot;&amp;gt;
&amp;lt;head&amp;gt;
    &amp;lt;meta charset=&amp;quot;UTF-8&amp;quot;&amp;gt;
    &amp;lt;title&amp;gt;Node.js로 서버 만들기&amp;lt;/title&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
    &amp;lt;h1&amp;gt;Main&amp;lt;/h1&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;!-- about.html --&amp;gt;
&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html lang=&amp;quot;ko&amp;quot;&amp;gt;
&amp;lt;head&amp;gt;
    &amp;lt;meta charset=&amp;quot;UTF-8&amp;quot;&amp;gt;
    &amp;lt;title&amp;gt;About : Node.js로 서버 만들기&amp;lt;/title&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
    &amp;lt;h1&amp;gt;About&amp;lt;/h1&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// rest.js
const http = require(&amp;#39;http&amp;#39;);
const fs = require(&amp;#39;fs&amp;#39;);

const server = http.createServer(async (req, res) =&amp;gt; {
    try {
        if (req.url === &amp;#39;/&amp;#39;) {
            const data = await fs.promises.readFile(&amp;#39;./index.html&amp;#39;);
            res.writeHead(200, { &amp;#39;Content-Type&amp;#39;: &amp;#39;text/html&amp;#39; });
            res.end(data);
        } else if (req.url === &amp;#39;/about&amp;#39;) {
            const data = await fs.promises.readFile(&amp;#39;./about.html&amp;#39;);
            res.writeHead(200, { &amp;#39;Content-Type&amp;#39;: &amp;#39;text/html&amp;#39; });
            res.end(data);
        } else {
            res.writeHead(404, { &amp;#39;Content-Type&amp;#39;: &amp;#39;text/html&amp;#39; });
            res.end(&amp;#39;&amp;lt;h1&amp;gt;404 Not Found&amp;lt;/h1&amp;gt;&amp;#39;);
        }
    } catch (error) {
        console.error(error);
        res.writeHead(500, { &amp;#39;Content-Type&amp;#39;: &amp;#39;text/plain&amp;#39; });
        res.end(error.message);
    }
});

server.listen(8080, () =&amp;gt; {
    console.log(&amp;#39;8080 포트에서 서버가 실행 중입니다.&amp;#39;);
});&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;서버를 실행하고, 크롬의 주소창에 &lt;code&gt;http://localhost:8080&lt;/code&gt;, &lt;code&gt;http://localhost:8080/about&lt;/code&gt;를 입력해보세요.&lt;/p&gt;
&lt;p&gt;크롬 브라우저에서 개발자 도구를 열고 &lt;code&gt;Network&lt;/code&gt; 탭을 확인하면, 요청과 응답 상태 코드를 확인할 수 있습니다.&lt;/p&gt;
&lt;h2&gt;4. 쿼리스트링(Query String)&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;쿼리스트링(Query String)은 URL 주소에 데이터를 포함하여 서버로 전달하는 방식입니다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;req.url&lt;/code&gt;로 요청한 URL 주소를 확인하고, URL에 포함된 쿼리스트링을 분석하는 방식&lt;/li&gt;
&lt;li&gt;URL에 &lt;code&gt;?&lt;/code&gt;를 사용해 쿼리스트링을 추가하고, &lt;code&gt;&amp;amp;&lt;/code&gt;로 여러 개의 쿼리스트링을 구분합니다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;url.parse()&lt;/code&gt; 메서드로 URL을 분석하고, &lt;code&gt;querystring&lt;/code&gt; 모듈로 쿼리스트링을 분석합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;- 기본 구조&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;http://localhost:8080/?name=Node.js&amp;amp;age=20&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;?&lt;/code&gt; : 쿼리스트링의 시작&lt;/li&gt;
&lt;li&gt;&lt;code&gt;name=Node.js&lt;/code&gt; : name이 Node.js인 쿼리스트링&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;amp;&lt;/code&gt; : 쿼리스트링 구분자&lt;/li&gt;
&lt;li&gt;&lt;code&gt;size=230&lt;/code&gt; : size가 230인 쿼리스트링&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;- url.parse() 메서드&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;url.parse()&lt;/code&gt; 메서드로 URL을 분석하면, URL의 여러 정보를 객체로 반환합니다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;url.parse(주소)&lt;/code&gt; : URL을 분석하여 URL 객체를 반환합니다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;url.parse(주소, true)&lt;/code&gt; : URL을 분석하여 URL 객체와 쿼리스트링 객체를 반환합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// 이런 URL이 있다면
&amp;#39;http://localhost:8080/search?category=shoes&amp;amp;color=black&amp;amp;size=260&amp;#39;

// url.parse()로 분석하면 이렇게 분리됩니다
{
  protocol: &amp;#39;http:&amp;#39;,
  host: &amp;#39;localhost:8080&amp;#39;,
  pathname: &amp;#39;/search&amp;#39;,
  query: &amp;#39;category=shoes&amp;amp;color=black&amp;amp;size=260&amp;#39;
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- querystring 모듈&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;querystring&lt;/code&gt; 모듈은 URL의 쿼리스트링을 객체로 변환하거나, 객체를 쿼리스트링으로 변환하는 모듈입니다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;querystring.parse(쿼리스트링)&lt;/code&gt; : 쿼리스트링을 객체로 변환합니다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;querystring.stringify(객체)&lt;/code&gt; : 객체를 쿼리스트링으로 변환합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;const querystring = require(&amp;#39;querystring&amp;#39;);
const parsedQuery = querystring.parse(parsedUrl.query);
// 이제 parsedQuery는 객체 형태가 됩니다
{
  category: &amp;#39;shoes&amp;#39;,
  color: &amp;#39;black&amp;#39;,
  size: &amp;#39;260&amp;#39;
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- 실제 사용 예시&lt;/h3&gt;
&lt;h4&gt;1) URL 주소에 쿼리스트링 추가&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;https://shop.com/?category=shoes&amp;amp;color=black&amp;amp;size=260&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이는 다음을 의미합니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;카테고리 : 신발&lt;/li&gt;
&lt;li&gt;색상 : 검정&lt;/li&gt;
&lt;li&gt;사이즈 : 260&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// querystring.js
const http = require(&amp;#39;http&amp;#39;);
const url = require(&amp;#39;url&amp;#39;);
// querystring 모듈을 불러옵니다.
const querystring = require(&amp;#39;querystring&amp;#39;);

const server = http.createServer((req, res) =&amp;gt; {
    // URL 문자열을 URL 객체로 변환
    const parsedUrl = url.parse(req.url);
    // URL 객체에서 쿼리스트링을 객체로 변환
    const query = querystring.parse(parsedUrl.query);

    // 상품 검색 처리
    if (query.category || query.color || query.size) {
        // 검색 조건 메시지 생성
        let searchMessage = &amp;#39;&amp;lt;h2&amp;gt;검색 조건:&amp;lt;/h2&amp;gt;&amp;#39;;
        if (query.category) searchMessage += `&amp;lt;p&amp;gt;카테고리: ${query.category}&amp;lt;/p&amp;gt;`;
        if (query.color) searchMessage += `&amp;lt;p&amp;gt;색상: ${query.color}&amp;lt;/p&amp;gt;`;
        if (query.size) searchMessage += `&amp;lt;p&amp;gt;사이즈: ${query.size}&amp;lt;/p&amp;gt;`;

        // 검색 결과 메시지 (실제로는 데이터베이스 조회 결과가 들어갈 부분)
        const resultMessage = `
            &amp;lt;h2&amp;gt;검색 결과:&amp;lt;/h2&amp;gt;
            &amp;lt;div&amp;gt;
                &amp;lt;h3&amp;gt;상품명: ${query.color} ${query.category}&amp;lt;/h3&amp;gt;
                &amp;lt;p&amp;gt;사이즈: ${query.size}&amp;lt;/p&amp;gt;
                &amp;lt;p&amp;gt;가격: 89,000원&amp;lt;/p&amp;gt;
            &amp;lt;/div&amp;gt;
        `;

        // HTML 응답
        res.writeHead(200, { &amp;#39;Content-Type&amp;#39;: &amp;#39;text/html; charset=utf-8&amp;#39; });
        res.end(`
            &amp;lt;h1&amp;gt;쇼핑몰 상품 검색&amp;lt;/h1&amp;gt;
            ${searchMessage}
            ${resultMessage}
        `);
    } else {
        // 검색 조건이 없을 때
        res.writeHead(200, { &amp;#39;Content-Type&amp;#39;: &amp;#39;text/html; charset=utf-8&amp;#39; });
        res.end(`
            &amp;lt;h1&amp;gt;상품을 검색해주세요&amp;lt;/h1&amp;gt;
            &amp;lt;p&amp;gt;예시: /?category=shoes&amp;amp;color=black&amp;amp;size=260&amp;lt;/p&amp;gt;
        `);
    }
});

// 서버 실행
server.listen(8080, () =&amp;gt; {
    console.log(&amp;#39;쇼핑몰 서버가 8080 포트에서 실행 중입니다.&amp;#39;);
});&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;서버를 실행하고, 크롬의 주소창에 &lt;code&gt;http://localhost:8080/?category=shoes&amp;amp;color=black&amp;amp;size=260&lt;/code&gt;를 입력해보세요.&lt;/p&gt;</description>
      <category>Front/Node.js</category>
      <author>oodada</author>
      <guid isPermaLink="true">https://oddcode.tistory.com/361</guid>
      <comments>https://oddcode.tistory.com/361#entry361comment</comments>
      <pubDate>Mon, 9 Dec 2024 22:50:38 +0900</pubDate>
    </item>
    <item>
      <title>Node.js 기본 개념과 특징</title>
      <link>https://oddcode.tistory.com/360</link>
      <description>&lt;h1&gt;Node.js 기본 개념과 특징&lt;/h1&gt;
&lt;h2&gt;Node.js란?&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;Node.js는 Chrome V8 JavaScript 엔진으로 구축된 JavaScript 런타임입니다. 이는 브라우저 밖에서도 JavaScript를 실행할 수 있게 해주는 환경입니다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;h2&gt;주요 개념과 특징&lt;/h2&gt;
&lt;h3&gt;1. 모듈 시스템&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;Node.js의 모듈 시스템은 필요한 기능을 &amp;quot;블록&amp;quot;처럼 가져다 사용하는 구조입니다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;ul&gt;
&lt;li&gt;요리시 레시피를 가져와 사용하는 것과 같아요.&lt;/li&gt;
&lt;li&gt;필요한 재료를 가져와 사용하고, 요리를 시작합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// fs 모듈을 가져와 사용합니다
// 재료를 가져옵니다 (require)
const fs = require(&amp;#39;fs&amp;#39;);

// 요리를 시작합니다 (파일 생성)
fs.writeFileSync(&amp;#39;example.txt&amp;#39;, &amp;#39;Hello, Node.js!&amp;#39;);
console.log(&amp;#39;파일이 생성되었습니다!&amp;#39;);&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;비동기와 콜백&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;Node.js는 시간이 오래 걸리는 작업을 &amp;quot;대기&amp;quot;하지 않고, 작업이 끝나면 알려주는 방식입니다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;ul&gt;
&lt;li&gt;음식점 홀직원은 한 명이지만, 주방에 요리사가 따로 있어요.&lt;/li&gt;
&lt;li&gt;직원은 주문을 받고 주방에 요리를 맡기고 다음 손님을 받을 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// 음식점에 직원이 한명입니다 (단일 스레드)
console.log(&amp;quot;직원: 1번 손님 주문 받기 시작&amp;quot;);

// 주문을 주방(다른 작업자)에 넘깁니다 (비동기 작업)
setTimeout(() =&amp;gt; {
    console.log(&amp;#39;요리사: 1번 손님 요리 시작&amp;#39;);
}, 2000); // 2초 뒤에 완료

// 직원은 기다리지 않고 다음 손님 받습니다
console.log(&amp;quot;직원: 2번 손님 주문 받기 시작&amp;quot;);&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3. 이벤트 기반 프로그래밍&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;Node.js는 &amp;quot;어떤 일이 발생하면 처리&amp;quot;하는 방식을 채택하고 있습니다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;ul&gt;
&lt;li&gt;음식점에서 손님이 오면 직원이 처리하는 방식과 같아요.&lt;/li&gt;
&lt;li&gt;손님이 오면 직원이 처리하고, 다음 손님이 오면 다시 처리합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;const button = document.querySelector(&amp;#39;button&amp;#39;);

// 클릭 이벤트를 처리하는 코드
button.addEventListener(&amp;#39;click&amp;#39;, () =&amp;gt; {
    console.log(&amp;#39;버튼이 클릭되었습니다!&amp;#39;);
});&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4. npm으로 패키지 사용하기&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;npm은 Node.js의 패키지 관리자로, 다른 개발자가 만든 유용한 도구를 가져다 쓸 수 있게 해줍니다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;ul&gt;
&lt;li&gt;요리할 때, &amp;quot;소스 만들기&amp;quot; 대신 마트에서 이미 만들어진 소스를 사는 것과 같아요.&lt;/li&gt;
&lt;li&gt;npm 패키지는 필요한 소스를 쉽게 가져다 쓸 수 있게 해줍니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;npm install swiper
npm install axios&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;5. 파일 시스템 작업&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;Node.js는 fs 모듈을 사용해 파일을 읽고 쓰는 작업을 쉽게 할 수 있습니다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;ul&gt;
&lt;li&gt;컴퓨터를 사용하는 것처럼, 파일을 만들고, 내용을 읽거나 쓸 수 있어요.&lt;/li&gt;
&lt;li&gt;예를 들어, &amp;quot;오늘의 일기장&amp;quot;을 작성하는 작업을 할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// fs 모듈을 가져와 사용합니다
const fs = require(&amp;#39;fs&amp;#39;);

// 파일 생성
// writeFile(파일명, 내용, 콜백함수)
fs.writeFile(&amp;#39;diary.txt&amp;#39;, &amp;#39;오늘 Node.js를 배웠다!&amp;#39;, (err) =&amp;gt; {
    if (err) throw err;
    console.log(&amp;#39;일기가 저장되었습니다!&amp;#39;);

    // 파일 읽기
    // readFile(파일명, 인코딩, 콜백함수)
    fs.readFile(&amp;#39;diary.txt&amp;#39;, &amp;#39;utf8&amp;#39;, (err, data) =&amp;gt; {
        if (err) throw err;
        console.log(&amp;#39;읽은 내용:&amp;#39;, data);
    });
});&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;6. 웹 서버 만들기&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;Node.js는 http 모듈을 사용해 간단한 웹 서버를 만들 수 있습니다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;ul&gt;
&lt;li&gt;가게를 열고 손님이 오면 주문을 받고 음식을 제공하는 것과 같아요.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// http 모듈을 가져와 사용합니다
const http = require(&amp;#39;http&amp;#39;);

// 웹 서버 생성
// createServer(요청, 응답)
const server = http.createServer((req, res) =&amp;gt; {
    res.writeHead(200, { &amp;#39;Content-Type&amp;#39;: &amp;#39;text/plain&amp;#39; });
    res.end(&amp;#39;어서 오세요! Node.js 식당입니다!&amp;#39;);
});

// 서버 실행
// listen(포트번호, 콜백함수)
server.listen(3000, () =&amp;gt; {
    console.log(&amp;#39;서버가 http://localhost:3000 에서 실행 중입니다.&amp;#39;);
});&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Front/Node.js</category>
      <author>oodada</author>
      <guid isPermaLink="true">https://oddcode.tistory.com/360</guid>
      <comments>https://oddcode.tistory.com/360#entry360comment</comments>
      <pubDate>Mon, 9 Dec 2024 22:50:08 +0900</pubDate>
    </item>
    <item>
      <title>Next.js + TypeScript 핵심 예제</title>
      <link>https://oddcode.tistory.com/359</link>
      <description>&lt;h1&gt;Next.js + TypeScript 핵심 가이드&lt;/h1&gt;
&lt;h2&gt;1. 기본 타입 정의와 인터페이스&lt;/h2&gt;
&lt;h3&gt;타입 정의의 기본&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;type Todo = {
  id: number;
  text: string;
  done: boolean;
  createdAt: Date;
}

// 인터페이스 정의
interface User {
  id: number;
  name: string;
  email: string;
  role: &amp;#39;admin&amp;#39; | &amp;#39;user&amp;#39;;  // 리터럴 타입
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;2. 컴포넌트 Props 타입 지정&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;// props 타입 정의
interface ButtonProps {
  text: string;
  onClick: () =&amp;gt; void;
  variant?: &amp;#39;primary&amp;#39; | &amp;#39;secondary&amp;#39;;
}

// 컴포넌트에서 사용
const Button: React.FC&amp;lt;ButtonProps&amp;gt; = ({ text, onClick, variant = &amp;#39;primary&amp;#39; }) =&amp;gt; {
  return (
    &amp;lt;button onClick={onClick} className={variant}&amp;gt;
      {text}
    &amp;lt;/button&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;3. 페이지 컴포넌트와 라우팅 타입&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;// pages/users/[id].tsx
interface UserPageProps {
  user: User;  // 위에서 정의한 User 인터페이스 사용
}

export default function UserPage({ user }: UserPageProps) {
  return &amp;lt;div&amp;gt;{user.name}&amp;lt;/div&amp;gt;
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;4. API 응답 타입 정의&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;// api 응답 타입
interface ApiResponse&amp;lt;T&amp;gt; {
  data: T;
  message: string;
  status: number;
}

// 사용 예시
async function fetchUser(id: number): Promise&amp;lt;ApiResponse&amp;lt;User&amp;gt;&amp;gt; {
  const response = await fetch(`/api/users/${id}`);
  return response.json();
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;5. 이벤트 핸들링&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;// 이벤트 타입 지정
const handleChange = (e: React.ChangeEvent&amp;lt;HTMLInputElement&amp;gt;) =&amp;gt; {
  setValue(e.target.value);
};

const handleSubmit = (e: React.FormEvent&amp;lt;HTMLFormElement&amp;gt;) =&amp;gt; {
  e.preventDefault();
  // 제출 로직
};&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;6. 상태 관리 (useState)&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;// 상태 타입 지정
const [user, setUser] = useState&amp;lt;User | null&amp;gt;(null);
const [todos, setTodos] = useState&amp;lt;Todo[]&amp;gt;([]);&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;7. Context API 타입&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;// context 타입 정의
interface ThemeContextType {
  theme: &amp;#39;light&amp;#39; | &amp;#39;dark&amp;#39;;
  toggleTheme: () =&amp;gt; void;
}

const ThemeContext = createContext&amp;lt;ThemeContextType | null&amp;gt;(null);&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Front/TypeScript</category>
      <author>oodada</author>
      <guid isPermaLink="true">https://oddcode.tistory.com/359</guid>
      <comments>https://oddcode.tistory.com/359#entry359comment</comments>
      <pubDate>Tue, 26 Nov 2024 15:55:36 +0900</pubDate>
    </item>
    <item>
      <title>대한민국 공공데이터 포털 사용 가이드</title>
      <link>https://oddcode.tistory.com/358</link>
      <description>&lt;h1&gt;대한민국 공공데이터 포털 사용 가이드&lt;/h1&gt;
&lt;h2&gt;목차&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;#1-%ED%9A%8C%EC%9B%90%EA%B0%80%EC%9E%85-%EB%B0%8F-%EB%A1%9C%EA%B7%B8%EC%9D%B8&quot;&gt;회원가입 및 로그인&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#2-api-%ED%82%A4-%EB%B0%9C%EA%B8%89&quot;&gt;API 키 발급&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#3-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EA%B2%80%EC%83%89&quot;&gt;데이터 검색&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#4-%ED%99%9C%EC%9A%A9%EC%8B%A0%EC%B2%AD&quot;&gt;활용신청&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#5-%EC%9D%B8%EA%B8%B0-api-%EC%98%88%EC%8B%9C&quot;&gt;인기 API 예시&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#6-nextjs-%ED%99%9C%EC%9A%A9-%EC%98%88%EC%8B%9C&quot;&gt;Next.js 활용 예시&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#7-%EC%A3%BC%EC%9D%98%EC%82%AC%ED%95%AD&quot;&gt;주의사항&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;1. 회원가입 및 로그인&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.data.go.kr&quot;&gt;https://www.data.go.kr&lt;/a&gt; 접속&lt;/li&gt;
&lt;li&gt;상단 회원가입 클릭&lt;/li&gt;
&lt;li&gt;일반회원 또는 기업회원으로 가입&lt;/li&gt;
&lt;li&gt;이메일 인증 완료&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;2. API 키 발급&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;로그인 후 마이페이지 접속&lt;/li&gt;
&lt;li&gt;&amp;quot;API 키 발급/관리&amp;quot; 메뉴 선택&lt;/li&gt;
&lt;li&gt;&amp;quot;일반 인증키&amp;quot; 또는 &amp;quot;서비스 인증키&amp;quot; 발급&lt;/li&gt;
&lt;li&gt;인증키 발급 후 활성화까지 1-2시간 소요될 수 있음&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;3. 데이터 검색&lt;/h2&gt;
&lt;h3&gt;방법 1: 검색창 이용&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;상단 검색창에 키워드 입력&lt;/li&gt;
&lt;li&gt;&amp;#39;OpenAPI&amp;#39; 항목 체크하여 검색&lt;/li&gt;
&lt;li&gt;필터 기능을 통해 원하는 조건으로 검색 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;방법 2: 카테고리 이용&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;분류별 검색&lt;/li&gt;
&lt;li&gt;주제별, 기관별 등 원하는 카테고리 선택&lt;/li&gt;
&lt;li&gt;새로운 데이터 추천 서비스 활용&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;4. 활용신청&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// 기본 API 호출 구조
const API_KEY = &amp;#39;발급받은_인증키&amp;#39;;
const url = `http://api.data.go.kr/...?serviceKey=${API_KEY}`;

// 데이터 가져오기 기본 함수
async function fetchData() {
  try {
    const response = await fetch(url);
    const data = await response.json();
    return data;
  } catch (error) {
    console.error(&amp;#39;Error:&amp;#39;, error);
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;5. 인기 API 예시&lt;/h2&gt;
&lt;h3&gt;기상청 날씨 API&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;const WEATHER_API = `http://apis.data.go.kr/1360000/VilageFcstInfoService_2.0/getUltraSrtNcst?serviceKey=${API_KEY}&amp;amp;numOfRows=10&amp;amp;pageNo=1&amp;amp;base_date=20240320&amp;amp;base_time=0600&amp;amp;nx=55&amp;amp;ny=127`;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;대중교통 API&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;const BUS_API = `http://ws.bus.go.kr/api/rest/buspos/getBusPosByRtid?serviceKey=${API_KEY}&amp;amp;busRouteId=100100118`;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;미세먼지 API&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;const DUST_API = `http://apis.data.go.kr/B552584/ArpltnInforInqireSvc/getCtprvnRltmMesureDnsty?serviceKey=${API_KEY}&amp;amp;returnType=json&amp;amp;numOfRows=100&amp;amp;pageNo=1&amp;amp;sidoName=서울&amp;amp;ver=1.0`;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;6. Next.js 활용 예시&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;&amp;quot;use client&amp;quot;
import { useEffect, useState } from &amp;#39;react&amp;#39;;

export default function DataPage() {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() =&amp;gt; {
    const API_KEY = process.env.NEXT_PUBLIC_DATA_API_KEY;
    const url = `http://apis.data.go.kr/...?serviceKey=${API_KEY}`;

    async function fetchData() {
      try {
        setLoading(true);
        const response = await fetch(url);
        const result = await response.json();
        setData(result);
      } catch (error) {
        console.error(&amp;#39;Error:&amp;#39;, error);
      } finally {
        setLoading(false);
      }
    }

    fetchData();
  }, []);

  if (loading) return &amp;lt;div&amp;gt;로딩중...&amp;lt;/div&amp;gt;;
  if (!data) return &amp;lt;div&amp;gt;데이터가 없습니다&amp;lt;/div&amp;gt;;

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h1&amp;gt;공공데이터 결과&amp;lt;/h1&amp;gt;
      {/* 데이터 표시 로직 */}
    &amp;lt;/div&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;7. 주의사항&lt;/h2&gt;
&lt;h3&gt;보안&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;API 키는 반드시 .env 파일에 보관&lt;/li&gt;
&lt;li&gt;.env 파일은 .gitignore에 추가하여 보호&lt;/li&gt;
&lt;li&gt;공개 저장소에 API 키 노출 주의&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;기술적 고려사항&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;CORS 이슈 발생 시 프록시 서버 사용 검토&lt;/li&gt;
&lt;li&gt;일일 트래픽 제한 확인 필수&lt;/li&gt;
&lt;li&gt;데이터 업데이트 주기 확인&lt;/li&gt;
&lt;li&gt;에러 처리 로직 구현&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;활용&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;서비스 활용 전 라이선스 확인&lt;/li&gt;
&lt;li&gt;적절한 에러 처리 구현&lt;/li&gt;
&lt;li&gt;캐싱 전략 수립&lt;/li&gt;
&lt;li&gt;사용량 모니터링&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;참고 링크&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.data.go.kr&quot;&gt;공공데이터 포털&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.data.go.kr/ugs/selectPublicDataUseGuide.do&quot;&gt;공공데이터 활용가이드&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.data.go.kr/ugs/selectDevGuide.do&quot;&gt;API 활용가이드&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Front/Node.js</category>
      <author>oodada</author>
      <guid isPermaLink="true">https://oddcode.tistory.com/358</guid>
      <comments>https://oddcode.tistory.com/358#entry358comment</comments>
      <pubDate>Wed, 20 Nov 2024 13:42:48 +0900</pubDate>
    </item>
    <item>
      <title>React Native 심화</title>
      <link>https://oddcode.tistory.com/357</link>
      <description>&lt;h1&gt;React Native 심화  &lt;/h1&gt;
&lt;h2&gt;1. 네비게이션&lt;/h2&gt;
&lt;h3&gt;Stack Navigation&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;// 기본 스택 네비게이션 설정
import { NavigationContainer } from &amp;#39;@react-navigation/native&amp;#39;;
import { createStackNavigator } from &amp;#39;@react-navigation/stack&amp;#39;;

const Stack = createStackNavigator();

function App() {
  return (
    &amp;lt;NavigationContainer&amp;gt;
      &amp;lt;Stack.Navigator&amp;gt;
        &amp;lt;Stack.Screen
          name=&amp;quot;Home&amp;quot;
          component={HomeScreen}
        /&amp;gt;
        &amp;lt;Stack.Screen
          name=&amp;quot;Details&amp;quot;
          component={DetailsScreen}
        /&amp;gt;
      &amp;lt;/Stack.Navigator&amp;gt;
    &amp;lt;/NavigationContainer&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Tab Navigation&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;import { createBottomTabNavigator } from &amp;#39;@react-navigation/bottom-tabs&amp;#39;;

const Tab = createBottomTabNavigator();

function App() {
  return (
    &amp;lt;NavigationContainer&amp;gt;
      &amp;lt;Tab.Navigator&amp;gt;
        &amp;lt;Tab.Screen
          name=&amp;quot;Home&amp;quot;
          component={HomeScreen}
        /&amp;gt;
        &amp;lt;Tab.Screen
          name=&amp;quot;Profile&amp;quot;
          component={ProfileScreen}
        /&amp;gt;
      &amp;lt;/Tab.Navigator&amp;gt;
    &amp;lt;/NavigationContainer&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;2. API 연동&lt;/h2&gt;
&lt;h3&gt;Fetch 사용하기&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;function DataFetching() {
  const [data, setData] = useState([]);

  useEffect(() =&amp;gt; {
    fetch(&amp;#39;https://api.example.com/data&amp;#39;)
      .then(response =&amp;gt; response.json())
      .then(json =&amp;gt; setData(json))
      .catch(error =&amp;gt; console.error(error));
  }, []);

  return (
    &amp;lt;FlatList
      data={data}
      renderItem={({ item }) =&amp;gt; &amp;lt;Text&amp;gt;{item.title}&amp;lt;/Text&amp;gt;}
    /&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Axios 사용하기&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;import axios from &amp;#39;axios&amp;#39;;

function DataFetching() {
  const [data, setData] = useState([]);

  useEffect(() =&amp;gt; {
    async function fetchData() {
      try {
        const response = await axios.get(&amp;#39;https://api.example.com/data&amp;#39;);
        setData(response.data);
      } catch (error) {
        console.error(error);
      }
    }
    fetchData();
  }, []);
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;3. 로컬 데이터 저장&lt;/h2&gt;
&lt;h3&gt;AsyncStorage&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;import AsyncStorage from &amp;#39;@react-native-async-storage/async-storage&amp;#39;;

// 데이터 저장
const storeData = async value =&amp;gt; {
  try {
    await AsyncStorage.setItem(&amp;#39;my-key&amp;#39;, JSON.stringify(value));
  } catch (e) {
    console.error(e);
  }
};

// 데이터 불러오기
const getData = async () =&amp;gt; {
  try {
    const value = await AsyncStorage.getItem(&amp;#39;my-key&amp;#39;);
    return value != null ? JSON.parse(value) : null;
  } catch (e) {
    console.error(e);
  }
};&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;4. 푸시 알림&lt;/h2&gt;
&lt;h3&gt;Expo Notifications&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;import * as Notifications from &amp;#39;expo-notifications&amp;#39;;

// 알림 권한 요청
async function registerForPushNotifications() {
  const { status } = await Notifications.requestPermissionsAsync();
  if (status !== &amp;#39;granted&amp;#39;) {
    alert(&amp;#39;알림 권한이 필요합니다!&amp;#39;);
    return;
  }

  const token = (await Notifications.getExpoPushTokenAsync()).data;
  return token;
}

// 로컬 알림 보내기
async function scheduleNotification() {
  await Notifications.scheduleNotificationAsync({
    content: {
      title: &amp;#39;알림 제목&amp;#39;,
      body: &amp;#39;알림 내용입니다.&amp;#39;,
    },
    trigger: { seconds: 2 },
  });
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;5. 성능 최적화&lt;/h2&gt;
&lt;h3&gt;React.memo 사용&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;const MyComponent = React.memo(function MyComponent(props) {
  return (
    &amp;lt;View&amp;gt;
      &amp;lt;Text&amp;gt;{props.text}&amp;lt;/Text&amp;gt;
    &amp;lt;/View&amp;gt;
  );
});&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;useCallback 활용&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;function ParentComponent() {
  const [count, setCount] = useState(0);

  const handlePress = useCallback(() =&amp;gt; {
    setCount(c =&amp;gt; c + 1);
  }, []);

  return &amp;lt;ChildComponent onPress={handlePress} /&amp;gt;;
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;6. 앱 배포&lt;/h2&gt;
&lt;h3&gt;Android 배포 준비&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;앱 서명 키 생성&lt;/li&gt;
&lt;li&gt;build.gradle 설정&lt;/li&gt;
&lt;li&gt;릴리즈 빌드 생성&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;iOS 배포 준비&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;Apple Developer 계정 설정&lt;/li&gt;
&lt;li&gt;인증서 및 프로비저닝 프로파일 설정&lt;/li&gt;
&lt;li&gt;Xcode에서 빌드 및 아카이브&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;7. 테스트&lt;/h2&gt;
&lt;h3&gt;Jest 사용하기&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;import renderer from &amp;#39;react-test-renderer&amp;#39;;

test(&amp;#39;컴포넌트 렌더링 테스트&amp;#39;, () =&amp;gt; {
  const tree = renderer.create(&amp;lt;MyComponent /&amp;gt;).toJSON();
  expect(tree).toMatchSnapshot();
});&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;8. 애니메이션&lt;/h2&gt;
&lt;h3&gt;Animated 사용&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;import { Animated } from &amp;#39;react-native&amp;#39;;

function FadeInView() {
  const fadeAnim = useRef(new Animated.Value(0)).current;

  useEffect(() =&amp;gt; {
    Animated.timing(fadeAnim, {
      toValue: 1,
      duration: 1000,
      useNativeDriver: true,
    }).start();
  }, []);

  return (
    &amp;lt;Animated.View style={{ opacity: fadeAnim }}&amp;gt;
      &amp;lt;Text&amp;gt;페이드인 효과&amp;lt;/Text&amp;gt;
    &amp;lt;/Animated.View&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;9. 상태 관리&lt;/h2&gt;
&lt;h3&gt;Recoil 사용&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;import { atom, selector, useRecoilState, useRecoilValue } from &amp;#39;recoil&amp;#39;;

// 아톰 생성
const counterState = atom({
  key: &amp;#39;counterState&amp;#39;,
  default: 0,
});

// 셀렉터 생성
const doubledCounterState = selector({
  key: &amp;#39;doubledCounterState&amp;#39;,
  get: ({ get }) =&amp;gt; {
    const count = get(counterState);
    return count * 2;
  },
});

function Counter() {
  const [count, setCount] = useRecoilState(counterState);
  const doubledCount = useRecoilValue(doubledCounterState);

  return (
    &amp;lt;View&amp;gt;
      &amp;lt;Text&amp;gt;Count: {count}&amp;lt;/Text&amp;gt;
      &amp;lt;Text&amp;gt;Doubled Count: {doubledCount}&amp;lt;/Text&amp;gt;
      &amp;lt;Button
        onPress={() =&amp;gt; setCount(count + 1)}
        title=&amp;quot;Increment&amp;quot;
      /&amp;gt;
    &amp;lt;/View&amp;gt;
  );
}

function App() {
  return (
    &amp;lt;RecoilRoot&amp;gt;
      &amp;lt;Counter /&amp;gt;
    &amp;lt;/RecoilRoot&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Front/React</category>
      <author>oodada</author>
      <guid isPermaLink="true">https://oddcode.tistory.com/357</guid>
      <comments>https://oddcode.tistory.com/357#entry357comment</comments>
      <pubDate>Sat, 2 Nov 2024 22:52:31 +0900</pubDate>
    </item>
    <item>
      <title>React Native 시작하기</title>
      <link>https://oddcode.tistory.com/356</link>
      <description>&lt;h1&gt;React Native 시작하기  &lt;/h1&gt;
&lt;h2&gt;목차&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;#1-react-native%EB%9E%80&quot;&gt;React Native 소개&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#2-%EA%B0%9C%EB%B0%9C-%ED%99%98%EA%B2%BD-%EC%84%A4%EC%A0%95&quot;&gt;개발 환경 설정&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#3-%EA%B8%B0%EB%B3%B8-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8&quot;&gt;기본 컴포넌트&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#4-%EA%B8%B0%EB%B3%B8-%EA%B8%B0%EB%8A%A5-%EA%B5%AC%ED%98%84&quot;&gt;기본 기능 구현&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#5-%EC%8B%A4%EC%8A%B5-%EC%98%88%EC%A0%9C-%EA%B0%84%EB%8B%A8%ED%95%9C-%ED%95%A0%EC%9D%BC-%EB%AA%A9%EB%A1%9D-%EC%95%B1&quot;&gt;실습 예제&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#6-%EB%8B%A4%EC%9D%8C-%ED%95%99%EC%8A%B5-%EC%A3%BC%EC%A0%9C&quot;&gt;다음 학습 주제&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;학습 팁  &lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;공식 문서를 자주 참고하세요&lt;/li&gt;
&lt;li&gt;Expo Snack으로 코드를 실험해보세요&lt;/li&gt;
&lt;li&gt;작은 프로젝트부터 시작하세요&lt;/li&gt;
&lt;li&gt;컴포넌트를 재사용 가능하게 설계하세요&lt;/li&gt;
&lt;li&gt;스타일링에 시간을 투자하세요&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;1. React Native란?&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;a href=&quot;https://reactnative.dev/&quot;&gt;https://reactnative.dev/&lt;/a&gt;&lt;br&gt;&lt;a href=&quot;https://expo.dev/&quot;&gt;https://expo.dev/&lt;/a&gt;&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;React Native는 페이스북이 개발한 모바일 앱 개발 프레임워크입니다. React의 문법을 사용하여 iOS와 Android 앱을 동시에 개발할 수 있습니다.&lt;/p&gt;
&lt;h3&gt;장점&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;하나의 코드로 iOS/Android 개발 가능&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;JavaScript/React 지식 활용 가능&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;빠른 개발 속도와 실시간 미리보기 (Hot Reload)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;네이티브 성능 제공&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;React vs React Native&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// React (웹)
&amp;lt;div&amp;gt;
  &amp;lt;h1&amp;gt;안녕하세요&amp;lt;/h1&amp;gt;
  &amp;lt;p&amp;gt;환영합니다&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;

// React Native (모바일)
&amp;lt;View&amp;gt;
  &amp;lt;Text&amp;gt;안녕하세요&amp;lt;/Text&amp;gt;
  &amp;lt;Text&amp;gt;환영합니다&amp;lt;/Text&amp;gt;
&amp;lt;/View&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;2. 개발 환경 설정&lt;/h2&gt;
&lt;h3&gt;필수 설치 항목&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;a href=&quot;https://docs.expo.dev/get-started/create-a-project/&quot;&gt;https://docs.expo.dev/get-started/create-a-project/&lt;/a&gt;&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;ol&gt;
&lt;li&gt;Node.js&lt;/li&gt;
&lt;li&gt;npm 또는 yarn&lt;/li&gt;
&lt;li&gt;Expo CLI 또는 React Native CLI&lt;/li&gt;
&lt;li&gt;Android Studio (안드로이드 개발시)&lt;/li&gt;
&lt;li&gt;Xcode (iOS 개발시, Mac 필수)&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;Expo CLI로 시작하기&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Expo CLI 설치
npm install -g expo-cli

# 프로젝트 생성
expo init MyFirstApp

# 프로젝트 실행
cd MyFirstApp
npm start&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;3. 기본 컴포넌트&lt;/h2&gt;
&lt;h3&gt;주요 컴포넌트&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;a href=&quot;https://reactnative.dev/docs/components-and-apis&quot;&gt;https://reactnative.dev/docs/components-and-apis&lt;/a&gt;&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;ul&gt;
&lt;li&gt;View: 레이아웃을 구성하는 컨테이너&lt;/li&gt;
&lt;li&gt;Text: 텍스트 표시&lt;/li&gt;
&lt;li&gt;Image: 이미지 표시&lt;/li&gt;
&lt;li&gt;ScrollView: 스크롤 가능한 영역&lt;/li&gt;
&lt;li&gt;TextInput: 입력 필드&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;import { View, Text, Image, ScrollView, TextInput } from &amp;#39;react-native&amp;#39;;

function App() {
  return (
    &amp;lt;View style={{ flex: 1, padding: 20 }}&amp;gt;
      {/* 텍스트 표시 */}
      &amp;lt;Text&amp;gt;안녕하세요!&amp;lt;/Text&amp;gt;

      {/* 이미지 표시 */}
      &amp;lt;Image
        source={require(&amp;#39;./assets/icon.png&amp;#39;)}
        style={{ width: 100, height: 100 }}
      /&amp;gt;

      {/* 입력 필드 */}
      &amp;lt;TextInput
        style={{ height: 40, borderColor: &amp;#39;gray&amp;#39;, borderWidth: 1 }}
        placeholder=&amp;quot;여기에 입력하세요&amp;quot;
      /&amp;gt;

      {/* 스크롤 가능한 영역 */}
      &amp;lt;ScrollView&amp;gt;
        &amp;lt;Text&amp;gt;스크롤 내용...&amp;lt;/Text&amp;gt;
      &amp;lt;/ScrollView&amp;gt;
    &amp;lt;/View&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;기본 스타일링&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;a href=&quot;https://reactnative.dev/docs/style&quot;&gt;https://reactnative.dev/docs/style&lt;/a&gt;&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;import { StyleSheet } from &amp;#39;react-native&amp;#39;;

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: &amp;#39;#fff&amp;#39;,
    alignItems: &amp;#39;center&amp;#39;,
    justifyContent: &amp;#39;center&amp;#39;,
  },
  text: {
    fontSize: 20,
    color: &amp;#39;blue&amp;#39;,
    marginBottom: 10,
  },
});

function App() {
  return (
    &amp;lt;View style={styles.container}&amp;gt;
      &amp;lt;Text style={styles.text}&amp;gt;스타일 예시&amp;lt;/Text&amp;gt;
    &amp;lt;/View&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;4. 기본 기능 구현&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;컴포넌트 : &lt;a href=&quot;https://reactnative.dev/docs/components-and-apis&quot;&gt;https://reactnative.dev/docs/components-and-apis&lt;/a&gt;&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;h3&gt;버튼과 터치 이벤트&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;a href=&quot;https://reactnative.dev/docs/button&quot;&gt;https://reactnative.dev/docs/button&lt;/a&gt;&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;import { TouchableOpacity, Alert } from &amp;#39;react-native&amp;#39;;

function MyButton() {
  return (
    &amp;lt;TouchableOpacity
      style={{ padding: 10, backgroundColor: &amp;#39;blue&amp;#39; }}
      onPress={() =&amp;gt; Alert.alert(&amp;#39;버튼이 눌렸습니다!&amp;#39;)}
    &amp;gt;
      &amp;lt;Text style={{ color: &amp;#39;white&amp;#39; }}&amp;gt;눌러보세요&amp;lt;/Text&amp;gt;
    &amp;lt;/TouchableOpacity&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;상태 관리&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;a href=&quot;https://reactnative.dev/docs/intro-react#state&quot;&gt;https://reactnative.dev/docs/intro-react#state&lt;/a&gt;&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;import { useState } from &amp;#39;react&amp;#39;;

function Counter() {
  const [count, setCount] = useState(0);

  return (
    &amp;lt;View&amp;gt;
      &amp;lt;Text&amp;gt;카운트: {count}&amp;lt;/Text&amp;gt;
      &amp;lt;TouchableOpacity onPress={() =&amp;gt; setCount(count + 1)}&amp;gt;
        &amp;lt;Text&amp;gt;증가&amp;lt;/Text&amp;gt;
      &amp;lt;/TouchableOpacity&amp;gt;
    &amp;lt;/View&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;5. 실습 예제: 간단한 할일 목록 앱&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;import React, { useState } from &amp;#39;react&amp;#39;;
import { View, Text, TextInput, TouchableOpacity, FlatList, StyleSheet } from &amp;#39;react-native&amp;#39;;

export default function TodoApp() {
  const [task, setTask] = useState(&amp;#39;&amp;#39;);
  const [tasks, setTasks] = useState([]);

  const addTask = () =&amp;gt; {
    if (task.trim().length &amp;gt; 0) {
      setTasks([...tasks, { id: Math.random().toString(), text: task }]);
      setTask(&amp;#39;&amp;#39;);
    }
  };

  return (
    &amp;lt;View style={styles.container}&amp;gt;
      &amp;lt;Text style={styles.title}&amp;gt;할일 목록&amp;lt;/Text&amp;gt;

      &amp;lt;View style={styles.inputContainer}&amp;gt;
        &amp;lt;TextInput
          style={styles.input}
          value={task}
          onChangeText={setTask}
          placeholder=&amp;quot;할일을 입력하세요&amp;quot;
        /&amp;gt;
        &amp;lt;TouchableOpacity
          style={styles.addButton}
          onPress={addTask}
        &amp;gt;
          &amp;lt;Text style={styles.buttonText}&amp;gt;추가&amp;lt;/Text&amp;gt;
        &amp;lt;/TouchableOpacity&amp;gt;
      &amp;lt;/View&amp;gt;

      &amp;lt;FlatList
        data={tasks}
        keyExtractor={item =&amp;gt; item.id}
        renderItem={({ item }) =&amp;gt; (
          &amp;lt;View style={styles.task}&amp;gt;
            &amp;lt;Text&amp;gt;{item.text}&amp;lt;/Text&amp;gt;
          &amp;lt;/View&amp;gt;
        )}
      /&amp;gt;
    &amp;lt;/View&amp;gt;
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 20,
    backgroundColor: &amp;#39;#fff&amp;#39;,
  },
  title: {
    fontSize: 24,
    fontWeight: &amp;#39;bold&amp;#39;,
    marginBottom: 20,
  },
  inputContainer: {
    flexDirection: &amp;#39;row&amp;#39;,
    marginBottom: 20,
  },
  input: {
    flex: 1,
    borderWidth: 1,
    borderColor: &amp;#39;#ddd&amp;#39;,
    padding: 10,
    marginRight: 10,
    borderRadius: 5,
  },
  addButton: {
    backgroundColor: &amp;#39;blue&amp;#39;,
    padding: 10,
    borderRadius: 5,
    justifyContent: &amp;#39;center&amp;#39;,
  },
  buttonText: {
    color: &amp;#39;white&amp;#39;,
  },
  task: {
    padding: 15,
    borderBottomWidth: 1,
    borderColor: &amp;#39;#ddd&amp;#39;,
  },
});&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Front/React</category>
      <author>oodada</author>
      <guid isPermaLink="true">https://oddcode.tistory.com/356</guid>
      <comments>https://oddcode.tistory.com/356#entry356comment</comments>
      <pubDate>Sat, 2 Nov 2024 22:49:41 +0900</pubDate>
    </item>
    <item>
      <title>React Recoil 로 todolist 제작하기</title>
      <link>https://oddcode.tistory.com/355</link>
      <description>&lt;h1&gt;React Recoil&lt;/h1&gt;
&lt;p&gt;리액트의 하나의 컴포넌트에서 데이터를 생성하거나 업데이트하거나 다른 컴포넌트와 데이터를 공유해서 사용하는 여러 방법이 있습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;state 와 props를 사용해서 컴포넌트 간에 데이터를 전달&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/odada/post/585c33a0-8964-4709-afe9-38fa866782a1/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;React Context API를 사용해서 컴포넌트 간에 데이터를 전달&lt;/li&gt;
&lt;li&gt;Redux, MobX, Recoil 등의 상태 관리 라이브러리를 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Recoil이란&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Recoil은 React 상태 관리를 위한 라이브러리로, 전역 상태를 관리하기 위한 간단한 방법입니다.&lt;/li&gt;
&lt;li&gt;Recoil은 atom과 selector를 사용하여 상태를 관리합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/odada/post/9fa25149-1950-40fb-95fa-1938e41ddfe9/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h2&gt;Recoil 문법&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;atom&lt;/code&gt;을 사용하여 상태를 &lt;strong&gt;생성&lt;/strong&gt;합니다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;useRecoilState&lt;/code&gt;를 사용하여 상태를 &lt;strong&gt;사용&lt;/strong&gt;합니다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;selector&lt;/code&gt;를 사용하여 파생된 상태를 &lt;strong&gt;생성&lt;/strong&gt;합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Recoil 사용하기&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;atom&lt;/code&gt;을 사용하여 상태를 생성합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;import { atom } from &amp;#39;recoil&amp;#39;;

const myState = atom({
  key: &amp;#39;myState&amp;#39;, // unique ID (with respect to other atoms/selectors)
  default: defaultValue, // default value (aka initial value)
});&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;useRecoilState&lt;/code&gt;를 사용하여 상태를 사용합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;import { useRecoilState } from &amp;#39;recoil&amp;#39;;

const [state, setState] = useRecoilState(myState);&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Recoil을 사용한 Theme 예제&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;import React from &amp;#39;react&amp;#39;;
import { atom, useRecoilState } from &amp;#39;recoil&amp;#39;;

const themes = {
  light: {
    foreground: &amp;#39;#000000&amp;#39;,
    background: &amp;#39;#eeeeee&amp;#39;,
  },
  dark: {
    foreground: &amp;#39;#ffffff&amp;#39;,
    background: &amp;#39;#222222&amp;#39;,
  },
};

const themeState = atom({
  key: &amp;#39;themeState&amp;#39;,
  default: themes.light,
});

function App() {
  return (
    &amp;lt;ThemeProvider&amp;gt;
      &amp;lt;Toolbar /&amp;gt;
    &amp;lt;/ThemeProvider&amp;gt;
  );
}

function ThemeProvider({ children }) {
  const [theme, setTheme] = useRecoilState(themeState);

  return &amp;lt;div style={{ background: theme.background, color: theme.foreground }}&amp;gt;{children}&amp;lt;/div&amp;gt;;
}

function Toolbar() {
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;ThemedButton /&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}

function ThemedButton() {
  const [theme] = useRecoilState(themeState);
  return &amp;lt;button style={{ background: theme.background, color: theme.foreground }}&amp;gt;I am styled by theme context!&amp;lt;/button&amp;gt;;
}

export default App;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Recoil을 이용한 앱 만들기&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;주문 페이지&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;여러 컴포넌트의 가격과 옵션을 공유하여 총액을 계산하여 보여주는 페이지를 만들어보겠습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;import React from &amp;#39;react&amp;#39;;
import { atom, useRecoilState } from &amp;#39;recoil&amp;#39;;

const orderState = atom({
  key: &amp;#39;orderState&amp;#39;,
  default: {
    price: 0,
    option: &amp;#39;none&amp;#39;,
  },
});

function App() {
  return (
    &amp;lt;OrderProvider&amp;gt;
      &amp;lt;Order /&amp;gt;
      &amp;lt;Total /&amp;gt;
    &amp;lt;/OrderProvider&amp;gt;
  );
}

function OrderProvider({ children }) {
  return &amp;lt;div&amp;gt;{children}&amp;lt;/div&amp;gt;;
}

function Order() {
  const [order, setOrder] = useRecoilState(orderState);

  const handleChange = e =&amp;gt; {
    let price = 0;
    switch (e.target.value) {
      case &amp;#39;sugar&amp;#39;:
        price = 1000;
        break;
      case &amp;#39;milk&amp;#39;:
        price = 1500;
        break;
      case &amp;#39;none&amp;#39;:
      default:
        price = 0;
        break;
    }
    setOrder({ ...order, option: e.target.value, price: price });
  };

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h1&amp;gt;Order&amp;lt;/h1&amp;gt;
      &amp;lt;select
        value={order.option}
        onChange={handleChange}
      &amp;gt;
        &amp;lt;option value=&amp;quot;none&amp;quot;&amp;gt;None&amp;lt;/option&amp;gt;
        &amp;lt;option value=&amp;quot;sugar&amp;quot;&amp;gt;Sugar&amp;lt;/option&amp;gt;
        &amp;lt;option value=&amp;quot;milk&amp;quot;&amp;gt;Milk&amp;lt;/option&amp;gt;
      &amp;lt;/select&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}

function Total() {
  const [order] = useRecoilState(orderState);

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h1&amp;gt;Total&amp;lt;/h1&amp;gt;
      &amp;lt;p&amp;gt;Price: {order.price}원&amp;lt;/p&amp;gt;
      &amp;lt;p&amp;gt;Option: {order.option}&amp;lt;/p&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}

export default App;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- 할 일 관리 앱 Recoil 사용하기&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;// src/context/TodoContext.js
import React from &amp;#39;react&amp;#39;;
import { atom, useRecoilState } from &amp;#39;recoil&amp;#39;;

const initialState = [
  {
    id: 1,
    isDone: false,
    task: &amp;#39;고양이 밥주기&amp;#39;,
    createdDate: new Date().getTime(),
  },
  {
    id: 2,
    isDone: false,
    task: &amp;#39;감자 캐기&amp;#39;,
    createdDate: new Date().getTime(),
  },
  {
    id: 3,
    isDone: false,
    task: &amp;#39;고양이 놀아주기&amp;#39;,
    createdDate: new Date().getTime(),
  },
];

const todoState = atom({
  key: &amp;#39;todoState&amp;#39;,
  default: initialState,
});

export function useTodoState() {
  return useRecoilState(todoState);
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;// src/components/App.js
import React from &amp;#39;react&amp;#39;;
import TodoHd from &amp;#39;./TodoHd&amp;#39;;
import TodoEditor from &amp;#39;./TodoEditor&amp;#39;;
import TodoList from &amp;#39;./TodoList&amp;#39;;
import { RecoilRoot } from &amp;#39;recoil&amp;#39;;

function App() {
  return (
    &amp;lt;RecoilRoot&amp;gt;
      &amp;lt;div&amp;gt;
        &amp;lt;TodoHd /&amp;gt;
        &amp;lt;TodoEditor /&amp;gt;
        &amp;lt;TodoList /&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/RecoilRoot&amp;gt;
  );
}

export default App;&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;// src/components/TodoList.js
import React, { useState } from &amp;#39;react&amp;#39;;
import TodoItem from &amp;#39;./TodoItem&amp;#39;;
import { useTodoState } from &amp;#39;../context/TodoContext&amp;#39;;

export default function TodoList() {
  const [todo, setTodo] = useTodoState();
  const [search, setSearch] = useState(&amp;#39;&amp;#39;);

  const onChangeSearch = e =&amp;gt; {
    setSearch(e.target.value);
  };

  const filteredTodo = () =&amp;gt; {
    return todo.filter(item =&amp;gt; item.task.toLowerCase().includes(search.toLowerCase()));
  };

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h2&amp;gt;할 일 목록  &amp;lt;/h2&amp;gt;
      &amp;lt;input
        value={search}
        onChange={onChangeSearch}
        placeholder=&amp;quot;검색어를 입력하세요&amp;quot;
      /&amp;gt;
      &amp;lt;ul&amp;gt;
        {filteredTodo().map(item =&amp;gt; (
          &amp;lt;TodoItem
            key={item.id}
            {...item}
          /&amp;gt;
        ))}
      &amp;lt;/ul&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;// src/components/TodoEditor.js
import React, { useState } from &amp;#39;react&amp;#39;;
import { useTodoState } from &amp;#39;../context/TodoContext&amp;#39;;

export default function TodoEditor() {
  const [todo, setTodo] = useTodoState();
  const [task, setTask] = useState(&amp;#39;&amp;#39;);

  const onChangeTask = e =&amp;gt; {
    setTask(e.target.value);
  };

  const onSubmit = () =&amp;gt; {
    if (!task) {
      return;
    }
    setTodo([{ id: Date.now(), isDone: false, task, createdDate: new Date().getTime() }, ...todo]);
    setTask(&amp;#39;&amp;#39;);
  };

  const onKeyDown = e =&amp;gt; {
    if (e.key === &amp;#39;Enter&amp;#39;) {
      onSubmit();
    }
  };

  return (
    &amp;lt;div className=&amp;quot;TodoEditor&amp;quot;&amp;gt;
      &amp;lt;h2&amp;gt;새로운 Todo 작성하기 ✏ &amp;lt;/h2&amp;gt;
      &amp;lt;div&amp;gt;
        &amp;lt;input
          value={task}
          onChange={onChangeTask}
          onKeyDown={onKeyDown}
          placeholder=&amp;quot;할 일을 추가로 입력해주세요.&amp;quot;
        /&amp;gt;
        &amp;lt;button onClick={onSubmit}&amp;gt;추가&amp;lt;/button&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;// src/components/TodoItem.js
import React from &amp;#39;react&amp;#39;;
import { useTodoState } from &amp;#39;../context/TodoContext&amp;#39;;

export default function TodoItem({ id, isDone, task, createdDate }) {
  const [todo, setTodo] = useTodoState();

  const onUpdate = () =&amp;gt; {
    setTodo(todo.map(it =&amp;gt; (it.id === id ? { ...it, isDone: !it.isDone } : it)));
  };

  const onDelete = () =&amp;gt; {
    setTodo(todo.filter(it =&amp;gt; it.id !== id));
  };

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;li key={id}&amp;gt;
        &amp;lt;input
          type=&amp;quot;checkbox&amp;quot;
          checked={isDone}
          onChange={onUpdate}
        /&amp;gt;
        &amp;lt;span style={{ textDecoration: isDone ? &amp;#39;line-through&amp;#39; : &amp;#39;none&amp;#39; }}&amp;gt;{task}&amp;lt;/span&amp;gt;
        &amp;lt;span&amp;gt;{new Date(createdDate).toLocaleDateString()}&amp;lt;/span&amp;gt;
        &amp;lt;button onClick={onDelete}&amp;gt;삭제&amp;lt;/button&amp;gt;
      &amp;lt;/li&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Front/React</category>
      <author>oodada</author>
      <guid isPermaLink="true">https://oddcode.tistory.com/355</guid>
      <comments>https://oddcode.tistory.com/355#entry355comment</comments>
      <pubDate>Sat, 2 Nov 2024 20:57:14 +0900</pubDate>
    </item>
    <item>
      <title>조건부 렌더링 (Conditional Rendering) - React 배우기</title>
      <link>https://oddcode.tistory.com/354</link>
      <description>&lt;h1&gt;조건부 렌더링 (Conditional Rendering)&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;조건부 렌더링은 특정 조건에 따라 다른 결과를 보여주는 것을 말합니다.&lt;/li&gt;
&lt;li&gt;React에서는 삼항 연산자나 &amp;amp;&amp;amp; 연산자를 사용하여 조건부 렌더링을 할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;1. 삼항 연산자&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;삼항 연산자는 조건에 따라 다른 값을 반환하는 연산자입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;import React from &amp;#39;react&amp;#39;;

function App() {
  const isLogin = true;

  return &amp;lt;div className=&amp;quot;App&amp;quot;&amp;gt;{isLogin ? &amp;lt;p&amp;gt;로그인 중&amp;lt;/p&amp;gt; : &amp;lt;p&amp;gt;로그인 해주세요&amp;lt;/p&amp;gt;}&amp;lt;/div&amp;gt;;
}

export default App;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;2. &amp;amp;&amp;amp; 연산자&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&amp;amp;&amp;amp; 연산자는 조건이 참일 때만 결과를 반환하는 연산자입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;import React from &amp;#39;react&amp;#39;;

function App() {
  const isLogin = true;

  return &amp;lt;div className=&amp;quot;App&amp;quot;&amp;gt;{isLogin &amp;amp;&amp;amp; &amp;lt;p&amp;gt;로그인 중&amp;lt;/p&amp;gt;}&amp;lt;/div&amp;gt;;
}

export default App;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;삼항 연산자와 &amp;amp;&amp;amp; 연산자는 각각의 상황에 따라 사용하면 됩니다.&lt;/li&gt;
&lt;li&gt;삼항 연산자는 조건이 참일 때와 거짓일 때 모두 결과를 반환할 때 사용하고, &amp;amp;&amp;amp; 연산자는 조건이 참일 때만 결과를 반환할 때 사용합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;3. 조건부 렌더링 예제&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;아래 코드는 isLogin 상태에 따라 다른 결과를 보여주는 예제입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;import React, { useState } from &amp;#39;react&amp;#39;;

function App() {
  const [isLogin, setIsLogin] = useState(false);

  const handleLogin = () =&amp;gt; {
    setIsLogin(true);
  };

  const handleLogout = () =&amp;gt; {
    setIsLogin(false);
  };

  return (
    &amp;lt;div className=&amp;quot;App&amp;quot;&amp;gt;
      {isLogin ? (
        &amp;lt;div&amp;gt;
          &amp;lt;p&amp;gt;로그인 중&amp;lt;/p&amp;gt;
          &amp;lt;button onClick={handleLogout}&amp;gt;로그아웃&amp;lt;/button&amp;gt;
        &amp;lt;/div&amp;gt;
      ) : (
        &amp;lt;div&amp;gt;
          &amp;lt;p&amp;gt;로그인 해주세요&amp;lt;/p&amp;gt;
          &amp;lt;button onClick={handleLogin}&amp;gt;로그인&amp;lt;/button&amp;gt;
        &amp;lt;/div&amp;gt;
      )}
    &amp;lt;/div&amp;gt;
  );
}

export default App;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;isLogin 상태가 true이면 &amp;quot;로그인 중&amp;quot;과 &amp;quot;로그아웃&amp;quot; 버튼을 보여주고, false이면 &amp;quot;로그인 해주세요&amp;quot;와 &amp;quot;로그인&amp;quot; 버튼을 보여줍니다.&lt;/li&gt;
&lt;li&gt;&amp;quot;로그인&amp;quot; 버튼을 클릭하면 handleLogin 함수가 호출되어 isLogin 상태를 true로 변경하고, &amp;quot;로그아웃&amp;quot; 버튼을 클릭하면 handleLogout 함수가 호출되어 isLogin 상태를 false로 변경합니다.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Front/React</category>
      <author>oodada</author>
      <guid isPermaLink="true">https://oddcode.tistory.com/354</guid>
      <comments>https://oddcode.tistory.com/354#entry354comment</comments>
      <pubDate>Sat, 2 Nov 2024 19:50:41 +0900</pubDate>
    </item>
    <item>
      <title>React의 핵심 개념</title>
      <link>https://oddcode.tistory.com/353</link>
      <description>&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/odada/post/7a1fb08c-8369-45b4-bb35-fea25c4018a2/image.png&quot; alt=&quot;react&quot;&gt;&lt;/p&gt;
&lt;h1&gt;React의 핵심 개념&lt;/h1&gt;
&lt;p&gt;React를 시작하는 개발자들을 위한 핵심 개념들을 정리해보았습니다. react의 핵심 개념을 이해하고, 실제 프로젝트에 적용해보면서 경험을 쌓는 것이 중요합니다.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://ko.react.dev/&quot;&gt;리액트 홈&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;1. React란?&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;a href=&quot;https://odada.me/250&quot;&gt;https://odada.me/250&lt;/a&gt;&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;React는 페이스북에서 개발한 UI 라이브러리로, 사용자 인터페이스를 만들기 위한 JavaScript 라이브러리입니다.&lt;/p&gt;
&lt;p&gt;리액트는 하나의 html 페이지만 존재하는 웹사이트(웹애플리케이션)으로 다른 컨텐츠 페이지를 불러들어오는 구조입니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/odada/post/bc613399-cfa4-4a0c-9763-66ac10282439/image.png&quot; alt=&quot;SPA(Single Page Application)&quot;&gt;&lt;br&gt;이미지출처: 소플의 리액트&lt;/p&gt;
&lt;h3&gt;- 설치&lt;/h3&gt;
&lt;p&gt;node.js 설치 후, 터미널에서 아래 명령어로 설치합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npx create-react-app@latest ./&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- 실행&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm start&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;1. 컴포넌트 (Components)&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;a href=&quot;https://odada.me/254&quot;&gt;https://odada.me/254&lt;/a&gt;&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;React의 가장 기본이 되는 개념으로, UI를 구성하는 블록입니다.&lt;br&gt;재사용 가능하며, 독립적으로 동작합니다.&lt;/p&gt;
&lt;h3&gt;- 함수형 컴포넌트 예시&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;함수형 컴포넌트는 순수 함수로, 입력값(props)에 따라 UI를 반환합니다.&lt;/li&gt;
&lt;li&gt;함수형 컴포넌트는 간단하고 가독성이 좋으며, React Hooks를 사용할 수 있습니다.&lt;/li&gt;
&lt;li&gt;클래스 컴포넌트보다 성능이 좋기 때문에 최근에는 함수형 컴포넌트를 권장합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;function SignupForm(props) {
  return &amp;lt;h1&amp;gt;Hello, {props.name}&amp;lt;/h1&amp;gt;;
}

function App() {
  return &amp;lt;SignupForm name=&amp;quot;odada&amp;quot; /&amp;gt;;
}

export default App;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- 클래스형 컴포넌트 예시&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;클래스형 컴포넌트는 &lt;code&gt;React.Component&lt;/code&gt; 클래스를 상속받아 구현합니다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;render&lt;/code&gt; 메서드를 구현하여 UI를 반환합니다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;this.props&lt;/code&gt;로 부모 컴포넌트에서 전달받은 데이터를 사용할 신₩ 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;class SignupForm extends React.Component {
  render() {
    return &amp;lt;h1&amp;gt;Hello, {this.props.name}&amp;lt;/h1&amp;gt;;
  }
}

class App extends React.Component {
  render() {
    return &amp;lt;SignupForm name=&amp;quot;odada&amp;quot; /&amp;gt;;
  }
}

export default App;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;2. JSX&lt;/h2&gt;
&lt;p&gt;JavaScript XML의 약자로, React에서 UI를 표현할 때 사용하는 문법입니다.&lt;/p&gt;
&lt;h3&gt;- JSX 규칙&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;반드시 하나의 부모 요소로 감싸져야 함&lt;/li&gt;
&lt;li&gt;모든 태그는 닫혀있어야 함&lt;/li&gt;
&lt;li&gt;JavaScript 표현식은 중괄호 &lt;code&gt;{}&lt;/code&gt; 안에 작성&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;const element = ({ name }) =&amp;gt; {
  return (
    &amp;lt;&amp;gt;
      &amp;lt;h1&amp;gt;Hello! {name}&amp;lt;/h1&amp;gt;
      &amp;lt;p&amp;gt;Good to see you here.&amp;lt;/p&amp;gt;
    &amp;lt;/&amp;gt;
  );
};&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- JSX 사용 예시&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;function App() {
  return (
    &amp;lt;ul&amp;gt;
      &amp;lt;NewsItem /&amp;gt;
      &amp;lt;NewsItem /&amp;gt;
      &amp;lt;NewsItem /&amp;gt;
    &amp;lt;/ul&amp;gt;
  );
}

function NewsItem() {
  return (
    &amp;lt;li&amp;gt;
      &amp;lt;strong&amp;gt;제목 1&amp;lt;/strong&amp;gt;
      &amp;lt;p&amp;gt;내용 1&amp;lt;/p&amp;gt;
      &amp;lt;LikeButton /&amp;gt;
    &amp;lt;/li&amp;gt;
  );
}

function LikeButton() {
  return &amp;lt;button&amp;gt;좋아요&amp;lt;/button&amp;gt;;
}

export default App;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;3. Props&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;a href=&quot;https://odada.me/271&quot;&gt;https://odada.me/271&lt;/a&gt;&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;컴포넌트 간에 데이터를 전달하는 방법입니다.&lt;/p&gt;
&lt;h3&gt;- Props의 특징&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;읽기 전용 (불변성)&lt;/li&gt;
&lt;li&gt;부모에서 자식으로만 전달 가능&lt;/li&gt;
&lt;li&gt;객체, 함수 등 모든 JavaScript 값을 전달할 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;// 부모 컴포넌트
function ParentComponent() {
  return &amp;lt;ChildComponent message=&amp;quot;Hello from parent&amp;quot; /&amp;gt;;
}

// 자식 컴포넌트
function ChildComponent(props) {
  return &amp;lt;div&amp;gt;{props.message}&amp;lt;/div&amp;gt;;
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- Props 사용 예시&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;function App() {
  return (
    &amp;lt;ul&amp;gt;
      &amp;lt;NewsItem
        title=&amp;quot;제목1&amp;quot;
        content=&amp;quot;내용1&amp;quot;
      /&amp;gt;
      &amp;lt;NewsItem
        title=&amp;quot;제목2&amp;quot;
        content=&amp;quot;내용2&amp;quot;
      /&amp;gt;
      &amp;lt;NewsItem
        title=&amp;quot;제목3&amp;quot;
        content=&amp;quot;내용3&amp;quot;
      /&amp;gt;
    &amp;lt;/ul&amp;gt;
  );
}

function NewsItem({ title, content }) {
  return (
    &amp;lt;li&amp;gt;
      &amp;lt;strong&amp;gt;{title}&amp;lt;/strong&amp;gt;
      &amp;lt;p&amp;gt;{content}&amp;lt;/p&amp;gt;
      &amp;lt;LikeButton /&amp;gt;
    &amp;lt;/li&amp;gt;
  );
}

function LikeButton() {
  return &amp;lt;button&amp;gt;좋아요&amp;lt;/button&amp;gt;;
}

export default App;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;4. 이벤트 처리&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;a href=&quot;https://odada.me/273&quot;&gt;https://odada.me/273&lt;/a&gt;&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;React의 이벤트는 카멜케이스를 사용하며, JSX에 함수로 전달합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;function handleClick(e) {
  e.preventDefault();
  console.log(&amp;#39;버튼이 클릭되었습니다.&amp;#39;);
}

return &amp;lt;button onClick={handleClick}&amp;gt;Click me&amp;lt;/button&amp;gt;;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;5. 조건부 렌더링&lt;/h2&gt;
&lt;p&gt;조건에 따라 다른 내용을 렌더링하는 방법입니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;function Greeting({ isLogin }) {
  return isLogin ? &amp;lt;p&amp;gt;로그인되었습니다.&amp;lt;/p&amp;gt; : &amp;lt;p&amp;gt;로그인이 필요합니다.&amp;lt;/p&amp;gt;;
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;6. Hooks&lt;/h2&gt;
&lt;p&gt;함수형 컴포넌트에서 상태 관리와 생명주기 기능을 사용할 수 있게 해주는 기능입니다.&lt;/p&gt;
&lt;h3&gt;- 주요 Hook들&lt;/h3&gt;
&lt;h4&gt;useState&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;상태를 추가하고, 상태를 변경할 수 있게 해줍니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;const [state, setState] = useState(initialState);&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;useEffect&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;컴포넌트가 렌더링될 때, 업데이트될 때, 언마운트될 때 등 특정 시점에 작업을 수행할 수 있게 해줍니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;useEffect(() =&amp;gt; {
  // 컴포넌트가 마운트될 때 실행
  return () =&amp;gt; {
    // 클린업 함수
  };
}, [dependencies]);&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;useContext&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;컴포넌트 트리 전체에서 전역적으로 사용할 수 있는 값을 공유할 수 있게 해줍니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;const value = useContext(MyContext);&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;useRef&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;DOM 요소에 직접 접근하거나, 컴포넌트의 인스턴스 변수를 사용할 수 있게 해줍니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;const inputRef = useRef();&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;useMemo&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;연산량이 많은 함수의 반환값을 캐싱하여 성능을 최적화할 수 있게 해줍니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;const memoizedValue = useMemo(() =&amp;gt; computeExpensiveValue(a, b), [a, b]);&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;7. State&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;a href=&quot;https://odada.me/274&quot;&gt;https://odada.me/274&lt;/a&gt;&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;컴포넌트 내부에서 관리되는 데이터입니다.&lt;/p&gt;
&lt;h3&gt;- State 사용 규칙&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;setState로만 값을 변경해야 함&lt;/li&gt;
&lt;li&gt;비동기적으로 처리됨&lt;/li&gt;
&lt;li&gt;이전 상태를 기반으로 업데이트할 때는 함수형 업데이트 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;function Counter() {
  const [count, setCount] = useState(0);
  // const result = useState(0);
  // const count = result[0];
  // const setCount = result[1];

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;p&amp;gt;현재 카운트: {count}&amp;lt;/p&amp;gt;
      &amp;lt;button onClick={() =&amp;gt; setCount(count + 1)}&amp;gt;증가&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- props 사용 예시&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;function App() {
  return (
    &amp;lt;ul&amp;gt;
      &amp;lt;NewsItem
        title=&amp;quot;제목1&amp;quot;
        content=&amp;quot;내용1&amp;quot;
      /&amp;gt;
      &amp;lt;NewsItem
        title=&amp;quot;제목2&amp;quot;
        content=&amp;quot;내용2&amp;quot;
      /&amp;gt;
      &amp;lt;NewsItem
        title=&amp;quot;제목3&amp;quot;
        content=&amp;quot;내용3&amp;quot;
      /&amp;gt;
    &amp;lt;/ul&amp;gt;
  );
}

function NewsItem({ title, content }) {
  return (
    &amp;lt;li&amp;gt;
      &amp;lt;strong&amp;gt;{title}&amp;lt;/strong&amp;gt;
      &amp;lt;p&amp;gt;{content}&amp;lt;/p&amp;gt;
      &amp;lt;LikeButton /&amp;gt;
    &amp;lt;/li&amp;gt;
  );
}

function LikeButton() {
  const [like, setLike] = useState(false);

  return &amp;lt;button onClick={() =&amp;gt; setLike(prevLike =&amp;gt; !prevLike)}&amp;gt;{like ? &amp;#39;좋아요 취소&amp;#39; : &amp;#39;좋아요&amp;#39;}&amp;lt;/button&amp;gt;;
}

export default App;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;8. 생명주기 (Lifecycle)&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;a href=&quot;https://odada.me/313&quot;&gt;https://odada.me/313&lt;/a&gt;&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;컴포넌트의 생성부터 소멸까지의 과정을 말합니다.&lt;/p&gt;
&lt;h3&gt;- 주요 생명주기&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;마운트 (Mount) : 컴포넌트가 생성되어 DOM에 추가될 때&lt;/li&gt;
&lt;li&gt;업데이트 (Update) : 컴포넌트의 상태나 props가 변경될 때&lt;/li&gt;
&lt;li&gt;언마운트 (Unmount) : 컴포넌트가 DOM에서 제거될 때&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;function LifecycleComponent() {
  useEffect(() =&amp;gt; {
    console.log(&amp;#39;컴포넌트 마운트&amp;#39;);

    return () =&amp;gt; {
      console.log(&amp;#39;컴포넌트 언마운트&amp;#39;);
    };
  }, []);

  useEffect(() =&amp;gt; {
    console.log(&amp;#39;컴포넌트 업데이트&amp;#39;);
  });

  return &amp;lt;div&amp;gt;컴포넌트 생명주기&amp;lt;/div&amp;gt;;
}

export default LifecycleComponent;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- 배경색 변경 예시&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;import React, { useState, useEffect } from &amp;#39;react&amp;#39;;

function BackgroundColor({ color }) {
  useEffect(() =&amp;gt; {
    document.body.style.backgroundColor = color; // 마운트 시 배경색 변경
    return () =&amp;gt; {
      document.body.style.backgroundColor = &amp;#39;&amp;#39;; // 언마운트 시 배경색 초기화
    };
  }, [color]); // color 값이 변경될 때만 실행

  return null;
}

function App() {
  const [color, setColor] = useState(&amp;#39;yellow&amp;#39;);

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;BackgroundColor color={color} /&amp;gt;
      &amp;lt;button onClick={() =&amp;gt; setColor(&amp;#39;red&amp;#39;)}&amp;gt;빨간색&amp;lt;/button&amp;gt;
      &amp;lt;button onClick={() =&amp;gt; setColor(&amp;#39;blue&amp;#39;)}&amp;gt;파란색&amp;lt;/button&amp;gt;
      &amp;lt;button onClick={() =&amp;gt; setColor(&amp;#39;green&amp;#39;)}&amp;gt;초록색&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}

export default App;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;9. 리스트와 키&lt;/h2&gt;
&lt;p&gt;배열 데이터를 렌더링할 때는 각 항목을 구분할 수 있는 key가 필요합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;const mockupNews = [
  { id: 1, title: &amp;#39;제목1&amp;#39;, content: &amp;#39;내용1&amp;#39; },
  { id: 2, title: &amp;#39;제목2&amp;#39;, content: &amp;#39;내용2&amp;#39; },
  { id: 3, title: &amp;#39;제목3&amp;#39;, content: &amp;#39;내용3&amp;#39; },
];

function NewsList() {
  return (
    &amp;lt;ul&amp;gt;
      {mockupNews.map(news =&amp;gt; (
        &amp;lt;li key={news.id}&amp;gt;
          &amp;lt;strong&amp;gt;{news.title}&amp;lt;/strong&amp;gt;
          &amp;lt;p&amp;gt;{news.content}&amp;lt;/p&amp;gt;
        &amp;lt;/li&amp;gt;
      ))}
    &amp;lt;/ul&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;10. useContext&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;a href=&quot;https://odada.me/319&quot;&gt;https://odada.me/319&lt;/a&gt;&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;컴포넌트 트리 전체에서 전역적으로 사용할 수 있는 값을 공유할 수 있게 해줍니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;const MyContext = React.createContext(defaultValue);

function App() {
  return (
    &amp;lt;MyContext.Provider value={/* 어떤 값 */}&amp;gt;
      &amp;lt;ChildComponent /&amp;gt;
    &amp;lt;/MyContext.Provider&amp;gt;
  );
}

function ChildComponent() {
  const value = useContext(MyContext);
  return &amp;lt;div&amp;gt;{value}&amp;lt;/div&amp;gt;;
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- 뉴스 useContext 사용 예시&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;// context/NewsContext.js
import React from &amp;#39;react&amp;#39;;

const NewsContext = React.createContext();

export default NewsContext;&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;// data/mockupNews.js
const mockupNews = [
  { id: 1, title: &amp;#39;제목1&amp;#39;, content: &amp;#39;내용1&amp;#39; },
  { id: 2, title: &amp;#39;제목2&amp;#39;, content: &amp;#39;내용2&amp;#39; },
  { id: 3, title: &amp;#39;제목3&amp;#39;, content: &amp;#39;내용3&amp;#39; },
];

export default mockupNews;&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;// components/NewsItem.js
import React, { useContext } from &amp;#39;react&amp;#39;;
import NewsContext from &amp;#39;../context/NewsContext&amp;#39;;

function NewsItem() {
  const news = useContext(NewsContext);

  return (
    &amp;lt;li&amp;gt;
      &amp;lt;strong&amp;gt;{news.title}&amp;lt;/strong&amp;gt;
      &amp;lt;p&amp;gt;{news.content}&amp;lt;/p&amp;gt;
    &amp;lt;/li&amp;gt;
  );
}

export default NewsItem;&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;// App.js
import React from &amp;#39;react&amp;#39;;
import NewsContext from &amp;#39;./context/NewsContext&amp;#39;;
import mockupNews from &amp;#39;./data/mockupNews&amp;#39;;
import NewsItem from &amp;#39;./components/NewsItem&amp;#39;;

function App() {
  return (
    &amp;lt;NewsContext.Provider value={mockupNews}&amp;gt;
      &amp;lt;ul&amp;gt;
        &amp;lt;NewsItem /&amp;gt;
        &amp;lt;NewsItem /&amp;gt;
        &amp;lt;NewsItem /&amp;gt;
      &amp;lt;/ul&amp;gt;
    &amp;lt;/NewsContext.Provider&amp;gt;
  );
}

export default App;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- Recoil 사용 예시&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;// atoms/newsAtom.js
import { atom } from &amp;#39;recoil&amp;#39;;

export const newsState = atom({
  key: &amp;#39;newsState&amp;#39;,
  default: [
    { id: 1, title: &amp;#39;제목1&amp;#39;, content: &amp;#39;내용1&amp;#39; },
    { id: 2, title: &amp;#39;제목2&amp;#39;, content: &amp;#39;내용2&amp;#39; },
    { id: 3, title: &amp;#39;제목3&amp;#39;, content: &amp;#39;내용3&amp;#39; },
  ],
});&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;// components/NewsItem.js
import React from &amp;#39;react&amp;#39;;
import { useRecoilValue } from &amp;#39;recoil&amp;#39;;
import { newsState } from &amp;#39;../atoms/newsAtom&amp;#39;;

function NewsItem({ id }) {
  const news = useRecoilValue(newsState).find(item =&amp;gt; item.id === id);

  return (
    &amp;lt;li&amp;gt;
      &amp;lt;strong&amp;gt;{news.title}&amp;lt;/strong&amp;gt;
      &amp;lt;p&amp;gt;{news.content}&amp;lt;/p&amp;gt;
    &amp;lt;/li&amp;gt;
  );
}

export default NewsItem;&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;// App.js
import React from &amp;#39;react&amp;#39;;
import { RecoilRoot } from &amp;#39;recoil&amp;#39;;
import NewsItem from &amp;#39;./components/NewsItem&amp;#39;;

function App() {
  return (
    &amp;lt;RecoilRoot&amp;gt;
      &amp;lt;ul&amp;gt;
        &amp;lt;NewsItem id={1} /&amp;gt;
        &amp;lt;NewsItem id={2} /&amp;gt;
        &amp;lt;NewsItem id={3} /&amp;gt;
      &amp;lt;/ul&amp;gt;
    &amp;lt;/RecoilRoot&amp;gt;
  );
}

export default App;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;11. API 통신&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;a href=&quot;https://odada.me/309&quot;&gt;https://odada.me/309&lt;/a&gt; , &lt;a href=&quot;https://odada.me/319&quot;&gt;https://odada.me/319&lt;/a&gt;&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;실무에서는 API 통신을 위한 코드를 별도의 파일이나 폴더로 분리하여 관리하는 것이 일반적입니다. 이렇게 하면 코드의 재사용성과 유지보수성이 높아집니다. 예를 들어, &lt;code&gt;data&lt;/code&gt; 폴더를 만들어 API 통신 관련 코드를 작성할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://newsapi.org/&quot;&gt;https://newsapi.org/&lt;/a&gt; 에서 API 키를 발급받아 사용할 수 있습니다.&lt;/p&gt;
&lt;h3&gt;- API 통신 예시&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;data&lt;/code&gt; 폴더를 만들고, &lt;code&gt;api.js&lt;/code&gt; 파일을 생성합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;// data/api.js
import axios from &amp;#39;axios&amp;#39;;

export const fetchNews = async () =&amp;gt; {
  try {
    const response = await axios.get(&amp;#39;https://jsonplaceholder.typicode.com/posts&amp;#39;);
    return response.data;
  } catch (error) {
    console.error(&amp;#39;Error fetching news:&amp;#39;, error);
    throw error;
  }
};&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- 뉴스 컴포넌트에서 API 호출&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;// .env
NEWS_API_KEY = YOUR_API_KEY;&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;// data/api.js
import axios from &amp;#39;axios&amp;#39;;

const API_URL = `https://newsapi.org/v2/top-headlines?country=kr&amp;amp;apiKey=${process.env.NEWS_API_KEY}`;

export const fetchNews = async () =&amp;gt; {
  try {
    const response = await axios.get(API_URL);
    return response.data.articles;
  } catch (error) {
    console.error(&amp;#39;Error fetching news:&amp;#39;, error);
    throw error;
  }
};&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;// components/NewsList.js
import React, { useEffect } from &amp;#39;react&amp;#39;;
import { useRecoilState } from &amp;#39;recoil&amp;#39;;
import { newsState } from &amp;#39;../atoms/newsAtom&amp;#39;;
import { fetchNews } from &amp;#39;../data/api&amp;#39;;

function NewsList() {
  const [news, setNews] = useRecoilState(newsState);

  useEffect(() =&amp;gt; {
    const getNews = async () =&amp;gt; {
      try {
        const articles = await fetchNews();
        setNews(articles);
      } catch (error) {
        console.error(&amp;#39;Error fetching news:&amp;#39;, error);
      }
    };

    getNews();
  }, [setNews]);

  return (
    &amp;lt;ul&amp;gt;
      {news.map(item =&amp;gt; (
        &amp;lt;li key={item.id}&amp;gt;
          &amp;lt;strong&amp;gt;{item.title}&amp;lt;/strong&amp;gt;
          &amp;lt;p&amp;gt;{item.description}&amp;lt;/p&amp;gt;
        &amp;lt;/li&amp;gt;
      ))}
    &amp;lt;/ul&amp;gt;
  );
}

export default NewsList;&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;// App.js
import React from &amp;#39;react&amp;#39;;
import { RecoilRoot } from &amp;#39;recoil&amp;#39;;
import NewsList from &amp;#39;./components/NewsList&amp;#39;;

function App() {
  return (
    &amp;lt;RecoilRoot&amp;gt;
      &amp;lt;NewsList /&amp;gt;
    &amp;lt;/RecoilRoot&amp;gt;
  );
}

export default App;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;12. Virtual DOM&lt;/h2&gt;
&lt;p&gt;React가 실제 DOM 업데이트를 효율적으로 처리하기 위해 사용하는 개념입니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;실제 DOM과 가상의 DOM을 비교&lt;/li&gt;
&lt;li&gt;변경된 부분만 실제 DOM에 적용&lt;/li&gt;
&lt;li&gt;성능 최적화에 도움&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;실무에서 알면 좋은 추가 개념들&lt;/h2&gt;
&lt;h3&gt;1. 상태 관리 라이브러리&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://ko.redux.js.org/introduction/getting-started/&quot;&gt;Redux&lt;/a&gt; : 가장 많이 사용되는 상태 관리 라이브러리&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://recoiljs.org/ko/&quot;&gt;Recoil&lt;/a&gt; : Facebook에서 만든 상태 관리 라이브러리&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://mobx.js.org/README.html&quot;&gt;MobX&lt;/a&gt; : 상태 관리 라이브러리&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://zustand-demo.pmnd.rs/&quot;&gt;Zustand&lt;/a&gt; : 간단하고 빠른 상태 관리 라이브러리&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2. 라우팅&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;React Router : React에서 라우팅을 구현할 수 있는 라이브러리&lt;/li&gt;
&lt;li&gt;Next.js : React 프레임워크로, SSR(Server Side Rendering)을 지원&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;a href=&quot;https://odada.me/256&quot;&gt;https://odada.me/256&lt;/a&gt;&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;import { BrowserRouter, Route, Switch } from &amp;#39;react-router-dom&amp;#39;;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3. 스타일링&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;a href=&quot;https://odada.me/324&quot;&gt;https://odada.me/324&lt;/a&gt;&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://styled-components.com/&quot;&gt;styled-components&lt;/a&gt; :&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://emotion.sh/docs/introduction&quot;&gt;Emotion&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://tailwindcss.com/&quot;&gt;Tailwind&lt;/a&gt; CSS&lt;/li&gt;
&lt;li&gt;CSS Modules&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;4. 성능 최적화&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;a href=&quot;https://odada.me/315&quot;&gt;https://odada.me/315&lt;/a&gt;&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;const MemoizedComponent = React.memo(function MyComponent(props) {
  // ... 컴포넌트 로직
});&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;5. Custom Hooks 작성&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;커스텀 로직을 재사용할 수 있게 해주는 기능입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;function useCustomHook() {
  const [value, setValue] = useState(null);
  // ... 커스텀 로직
  return value;
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;6. 폼 핸들링&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;React Hook Form&lt;/li&gt;
&lt;li&gt;Formik&lt;/li&gt;
&lt;li&gt;Yup (유효성 검사)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;7. API 통신&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;axios&lt;/li&gt;
&lt;li&gt;React Query (TanStack Query)&lt;/li&gt;
&lt;li&gt;SWR&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;import { useState, useEffect } from &amp;#39;react&amp;#39;;
import axios from &amp;#39;axios&amp;#39;;

function PostList() {
  const [posts, setPosts] = useState([]);
  const [loading, setLoading] = useState(false);

  // 게시글 목록 조회
  useEffect(() =&amp;gt; {
    const fetchPosts = async () =&amp;gt; {
      try {
        setLoading(true);
        const response = await axios.get(&amp;#39;https://jsonplaceholder.typicode.com/posts&amp;#39;);
        setPosts(response.data.slice(0, 5)); // 5개만 가져오기
      } catch (error) {
        console.error(&amp;#39;Error fetching posts:&amp;#39;, error);
      } finally {
        setLoading(false);
      }
    };

    fetchPosts();
  }, []);

  if (loading) return &amp;lt;div&amp;gt;로딩중...&amp;lt;/div&amp;gt;;

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h1&amp;gt;게시글 목록&amp;lt;/h1&amp;gt;
      {posts.map(post =&amp;gt; (
        &amp;lt;div
          key={post.id}
          style={{ margin: &amp;#39;20px 0&amp;#39;, padding: &amp;#39;10px&amp;#39;, border: &amp;#39;1px solid #ddd&amp;#39; }}
        &amp;gt;
          &amp;lt;h2&amp;gt;{post.title}&amp;lt;/h2&amp;gt;
          &amp;lt;p&amp;gt;{post.body}&amp;lt;/p&amp;gt;
        &amp;lt;/div&amp;gt;
      ))}
    &amp;lt;/div&amp;gt;
  );
}

export default PostList;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;마치며&lt;/h2&gt;
&lt;p&gt;위의 개념들을 하나씩 학습하고 실제 프로젝트에 적용해보면서 경험을 쌓는 것이 중요합니다. 특히 초기에는 기본적인 개념들(컴포넌트, props, state)을 확실히 이해하는 것에 집중하시기 바랍니다.&lt;/p&gt;
&lt;p&gt;관련 문서나 자세한 내용은 &lt;a href=&quot;https://react.dev&quot;&gt;React 공식 문서&lt;/a&gt;를 참고하시면 도움이 됩니다.&lt;/p&gt;</description>
      <category>Front/React</category>
      <author>oodada</author>
      <guid isPermaLink="true">https://oddcode.tistory.com/353</guid>
      <comments>https://oddcode.tistory.com/353#entry353comment</comments>
      <pubDate>Sun, 27 Oct 2024 21:09:42 +0900</pubDate>
    </item>
    <item>
      <title>github 협업하기</title>
      <link>https://oddcode.tistory.com/352</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://devfancy.github.io/Technology-GitHub-Branch-Protection-Rule/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://devfancy.github.io/Technology-GitHub-Branch-Protection-Rule/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1729599770268&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;GitHub에 올라간 Branch에 Protection Rule 적용하기&quot; data-og-description=&quot;Index&quot; data-og-host=&quot;devfancy.github.io&quot; data-og-source-url=&quot;https://devfancy.github.io/Technology-GitHub-Branch-Protection-Rule/&quot; data-og-url=&quot;https://devfancy.github.io/Technology-GitHub-Branch-Protection-Rule/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/XomBc/hyXlUA8cFu/RC0gKfz5y1chDwQARMD1r1/img.png?width=1512&amp;amp;height=1470&amp;amp;face=0_0_1512_1470,https://scrap.kakaocdn.net/dn/bAZSps/hyXlPGzWFG/XsvWwUukJCc6lJhTAhOr30/img.png?width=1420&amp;amp;height=1310&amp;amp;face=0_0_1420_1310,https://scrap.kakaocdn.net/dn/WqAAF/hyXlSQQqDh/ZtQkB3yZ2IGbSp3KFboSYK/img.png?width=1410&amp;amp;height=602&amp;amp;face=0_0_1410_602&quot;&gt;&lt;a href=&quot;https://devfancy.github.io/Technology-GitHub-Branch-Protection-Rule/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://devfancy.github.io/Technology-GitHub-Branch-Protection-Rule/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/XomBc/hyXlUA8cFu/RC0gKfz5y1chDwQARMD1r1/img.png?width=1512&amp;amp;height=1470&amp;amp;face=0_0_1512_1470,https://scrap.kakaocdn.net/dn/bAZSps/hyXlPGzWFG/XsvWwUukJCc6lJhTAhOr30/img.png?width=1420&amp;amp;height=1310&amp;amp;face=0_0_1420_1310,https://scrap.kakaocdn.net/dn/WqAAF/hyXlSQQqDh/ZtQkB3yZ2IGbSp3KFboSYK/img.png?width=1410&amp;amp;height=602&amp;amp;face=0_0_1410_602');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub에 올라간 Branch에 Protection Rule 적용하기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Index&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;devfancy.github.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작성중...&lt;/p&gt;</description>
      <category>TOOL/Github</category>
      <author>oodada</author>
      <guid isPermaLink="true">https://oddcode.tistory.com/352</guid>
      <comments>https://oddcode.tistory.com/352#entry352comment</comments>
      <pubDate>Tue, 22 Oct 2024 21:22:56 +0900</pubDate>
    </item>
    <item>
      <title>사전수업</title>
      <link>https://oddcode.tistory.com/351</link>
      <description>&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;웹사이트 제작 기본 프로세스&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;a href=&quot;https://odada.me/291&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://odada.me/291&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;퍼블리싱 기초&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;a href=&quot;https://odada.me/290&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://odada.me/290&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;부트스트랩으로 빠르게 포폴 만들기&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;a href=&quot;https://odada.me/350&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://odada.me/350&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.w3schools.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.w3schools.com/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1724720144643&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;W3Schools.com&quot; data-og-description=&quot;W3Schools offers free online tutorials, references and exercises in all the major languages of the web. Covering popular subjects like HTML, CSS, JavaScript, Python, SQL, Java, and many, many more.&quot; data-og-host=&quot;www.w3schools.com&quot; data-og-source-url=&quot;https://www.w3schools.com/&quot; data-og-url=&quot;https://www.w3schools.com/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/b9wjc5/hyWSaFoE6F/dKuOwtg7XvbPmoV29b6Bfk/img.png?width=436&amp;amp;height=228&amp;amp;face=0_0_436_228,https://scrap.kakaocdn.net/dn/bwh0hl/hyWSoRbt2R/KH1hbPEKThK4zFVn9qG0K0/img.png?width=944&amp;amp;height=448&amp;amp;face=0_0_944_448&quot;&gt;&lt;a href=&quot;https://www.w3schools.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.w3schools.com/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/b9wjc5/hyWSaFoE6F/dKuOwtg7XvbPmoV29b6Bfk/img.png?width=436&amp;amp;height=228&amp;amp;face=0_0_436_228,https://scrap.kakaocdn.net/dn/bwh0hl/hyWSoRbt2R/KH1hbPEKThK4zFVn9qG0K0/img.png?width=944&amp;amp;height=448&amp;amp;face=0_0_944_448');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;W3Schools.com&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;W3Schools offers free online tutorials, references and exercises in all the major languages of the web. Covering popular subjects like HTML, CSS, JavaScript, Python, SQL, Java, and many, many more.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.w3schools.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.mozilla.org/ko/docs/Web&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://developer.mozilla.org/ko/docs/Web&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1724720212680&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;개발자를 위한 웹 기술 | MDN&quot; data-og-description=&quot;웹의 개방성은 개발자들에게 많은 기회를 제공합니다. 하지만 웹 기술을 잘 활용하려면 우선 그 사용 방법을 잘 알아야 합니다. 아래의 링크들을 확인하여 다양한 웹 기술을 배워보세요.&quot; data-og-host=&quot;developer.mozilla.org&quot; data-og-source-url=&quot;https://developer.mozilla.org/ko/docs/Web&quot; data-og-url=&quot;https://developer.mozilla.org/ko/docs/Web&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dlk1Im/hyWVTIKOL2/OKUSsQmkeKAcRAft0jYTkk/img.png?width=1920&amp;amp;height=1080&amp;amp;face=0_0_1920_1080&quot;&gt;&lt;a href=&quot;https://developer.mozilla.org/ko/docs/Web&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.mozilla.org/ko/docs/Web&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dlk1Im/hyWVTIKOL2/OKUSsQmkeKAcRAft0jYTkk/img.png?width=1920&amp;amp;height=1080&amp;amp;face=0_0_1920_1080');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;개발자를 위한 웹 기술 | MDN&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;웹의 개방성은 개발자들에게 많은 기회를 제공합니다. 하지만 웹 기술을 잘 활용하려면 우선 그 사용 방법을 잘 알아야 합니다. 아래의 링크들을 확인하여 다양한 웹 기술을 배워보세요.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developer.mozilla.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://zoom.us/j/98343749057?pwd=AELWF5sJqJb5LEyd0t8BrT2SCFbrvQ.1&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://zoom.us/j/98343749057?pwd=AELWF5sJqJb5LEyd0t8BrT2SCFbrvQ.1&lt;/a&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #fdf6e3; color: #657b83;&quot;&gt;
&lt;div&gt;&lt;span style=&quot;color: #93a1a1;&quot;&gt;&amp;lt;!&lt;/span&gt;&lt;span style=&quot;color: #268bd2;&quot;&gt;DOCTYPE&lt;/span&gt;&lt;span style=&quot;color: #657b83;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #93a1a1;&quot;&gt;html&amp;gt;&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #93a1a1;&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color: #268bd2;&quot;&gt;html&lt;/span&gt;&lt;span style=&quot;color: #657b83;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #93a1a1;&quot;&gt;lang&lt;/span&gt;&lt;span style=&quot;color: #657b83;&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #2aa198;&quot;&gt;&quot;en&quot;&lt;/span&gt;&lt;span style=&quot;color: #93a1a1;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #657b83;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #93a1a1;&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color: #268bd2;&quot;&gt;head&lt;/span&gt;&lt;span style=&quot;color: #93a1a1;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #657b83;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #93a1a1;&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color: #268bd2;&quot;&gt;title&lt;/span&gt;&lt;span style=&quot;color: #93a1a1;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: #657b83;&quot;&gt;정미애의 포트폴리오&lt;/span&gt;&lt;span style=&quot;color: #93a1a1;&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: #268bd2;&quot;&gt;title&lt;/span&gt;&lt;span style=&quot;color: #93a1a1;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #657b83;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #93a1a1;&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: #268bd2;&quot;&gt;head&lt;/span&gt;&lt;span style=&quot;color: #93a1a1;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #657b83;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #93a1a1;&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color: #268bd2;&quot;&gt;body&lt;/span&gt;&lt;span style=&quot;color: #93a1a1;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #657b83;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #93a1a1;&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color: #268bd2;&quot;&gt;h1&lt;/span&gt;&lt;span style=&quot;color: #93a1a1;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #657b83;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #93a1a1;&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color: #268bd2;&quot;&gt;span&lt;/span&gt;&lt;span style=&quot;color: #93a1a1;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: #657b83;&quot;&gt;RORTFOLIO&lt;/span&gt;&lt;span style=&quot;color: #93a1a1;&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: #268bd2;&quot;&gt;span&lt;/span&gt;&lt;span style=&quot;color: #93a1a1;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #657b83;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #93a1a1;&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color: #268bd2;&quot;&gt;span&lt;/span&gt;&lt;span style=&quot;color: #93a1a1;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: #657b83;&quot;&gt;odada&lt;/span&gt;&lt;span style=&quot;color: #93a1a1;&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: #268bd2;&quot;&gt;span&lt;/span&gt;&lt;span style=&quot;color: #93a1a1;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #657b83;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #93a1a1;&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: #268bd2;&quot;&gt;h1&lt;/span&gt;&lt;span style=&quot;color: #93a1a1;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #657b83;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #93a1a1;&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color: #268bd2;&quot;&gt;p&lt;/span&gt;&lt;span style=&quot;color: #93a1a1;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: #657b83;&quot;&gt;I'M HERE&lt;/span&gt;&lt;span style=&quot;color: #93a1a1;&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: #268bd2;&quot;&gt;p&lt;/span&gt;&lt;span style=&quot;color: #93a1a1;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #657b83;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #93a1a1;&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color: #268bd2;&quot;&gt;p&lt;/span&gt;&lt;span style=&quot;color: #93a1a1;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: #657b83;&quot;&gt;Did you looking for me?&lt;/span&gt;&lt;span style=&quot;color: #93a1a1;&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: #268bd2;&quot;&gt;p&lt;/span&gt;&lt;span style=&quot;color: #93a1a1;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;&lt;span style=&quot;color: #657b83;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #93a1a1;&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color: #268bd2;&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color: #93a1a1;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: #657b83;&quot;&gt;2024 - 2025&lt;/span&gt;&lt;span style=&quot;color: #93a1a1;&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: #268bd2;&quot;&gt;div&lt;/span&gt;&lt;span style=&quot;color: #93a1a1;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;&lt;span style=&quot;color: #657b83;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #93a1a1;&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color: #268bd2;&quot;&gt;dl&lt;/span&gt;&lt;span style=&quot;color: #93a1a1;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #657b83;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #93a1a1;&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color: #268bd2;&quot;&gt;dt&lt;/span&gt;&lt;span style=&quot;color: #93a1a1;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: #657b83;&quot;&gt;CONTACT&lt;/span&gt;&lt;span style=&quot;color: #93a1a1;&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: #268bd2;&quot;&gt;dt&lt;/span&gt;&lt;span style=&quot;color: #93a1a1;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #657b83;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #93a1a1;&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color: #268bd2;&quot;&gt;dd&lt;/span&gt;&lt;span style=&quot;color: #93a1a1;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #657b83;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #93a1a1;&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color: #268bd2;&quot;&gt;a&lt;/span&gt;&lt;span style=&quot;color: #657b83;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #93a1a1;&quot;&gt;href&lt;/span&gt;&lt;span style=&quot;color: #657b83;&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #2aa198;&quot;&gt;&quot;tel:010-1234-5678&quot;&lt;/span&gt;&lt;span style=&quot;color: #93a1a1;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: #657b83;&quot;&gt;010-1234-5678&lt;/span&gt;&lt;span style=&quot;color: #93a1a1;&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: #268bd2;&quot;&gt;a&lt;/span&gt;&lt;span style=&quot;color: #93a1a1;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #657b83;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #93a1a1;&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: #268bd2;&quot;&gt;dd&lt;/span&gt;&lt;span style=&quot;color: #93a1a1;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #657b83;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #93a1a1;&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color: #268bd2;&quot;&gt;dd&lt;/span&gt;&lt;span style=&quot;color: #93a1a1;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #657b83;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #93a1a1;&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color: #268bd2;&quot;&gt;a&lt;/span&gt;&lt;span style=&quot;color: #657b83;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #93a1a1;&quot;&gt;href&lt;/span&gt;&lt;span style=&quot;color: #657b83;&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color: #2aa198;&quot;&gt;&quot;mailto:mail@mail.com&quot;&lt;/span&gt;&lt;span style=&quot;color: #93a1a1;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span style=&quot;color: #657b83;&quot;&gt;mail@mail.com&lt;/span&gt;&lt;span style=&quot;color: #93a1a1;&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: #268bd2;&quot;&gt;a&lt;/span&gt;&lt;span style=&quot;color: #93a1a1;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #657b83;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #93a1a1;&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: #268bd2;&quot;&gt;dd&lt;/span&gt;&lt;span style=&quot;color: #93a1a1;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #657b83;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #93a1a1;&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: #268bd2;&quot;&gt;dl&lt;/span&gt;&lt;span style=&quot;color: #93a1a1;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #657b83;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #93a1a1;&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: #268bd2;&quot;&gt;body&lt;/span&gt;&lt;span style=&quot;color: #93a1a1;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span style=&quot;color: #93a1a1;&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color: #268bd2;&quot;&gt;html&lt;/span&gt;&lt;span style=&quot;color: #93a1a1;&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;</description>
      <author>oodada</author>
      <guid isPermaLink="true">https://oddcode.tistory.com/351</guid>
      <comments>https://oddcode.tistory.com/351#entry351comment</comments>
      <pubDate>Tue, 27 Aug 2024 09:57:54 +0900</pubDate>
    </item>
    <item>
      <title>부트스트랩으로 빠르게 포트폴리오 만들기</title>
      <link>https://oddcode.tistory.com/350</link>
      <description>&lt;h1&gt;부트스트랩으로 빠르게 포트폴리오 만들기&lt;/h1&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/odada/post/aaea4216-647f-4029-afff-e27d1e282ff8/image.webp&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/odada/post/471c0e3d-800e-4559-99ce-f6f2f2b4142c/image.webp&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h2&gt;1. 포트폴리오란?&lt;/h2&gt;
&lt;p&gt;포트폴리오는 자신의 업무 능력과 경험을 보여주는 문서입니다. 개인의 업무 능력과 경험을 보여주기 위해 만들어지는 것이기 때문에 자신의 업무 능력과 경험을 잘 보여줄 수 있어야 합니다.&lt;/p&gt;
&lt;h2&gt;2. 부트스트랩이란?&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;a href=&quot;https://getbootstrap.kr/&quot;&gt;https://getbootstrap.kr/&lt;/a&gt;&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/odada/post/b5a770a1-883e-42a7-96f9-0f796d860cfe/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;부트스트랩은 웹 개발을 위한 오픈 소스 프론트엔드 프레임워크입니다. 부트스트랩은 HTML, CSS, JavaScript를 사용하여 웹 페이지를 만들 때 사용할 수 있는 여러 가지 디자인 요소와 기능을 제공합니다.&lt;/p&gt;
&lt;p&gt;부트스트랩을 사용하면 웹 페이지를 쉽고 빠르게 만들 수 있습니다. 부트스트랩은 반응형 웹 디자인을 지원하기 때문에 모바일 환경에서도 웹 페이지가 잘 보이도록 만들 수 있습니다.&lt;/p&gt;</description>
      <category>etc</category>
      <author>oodada</author>
      <guid isPermaLink="true">https://oddcode.tistory.com/350</guid>
      <comments>https://oddcode.tistory.com/350#entry350comment</comments>
      <pubDate>Mon, 26 Aug 2024 12:15:07 +0900</pubDate>
    </item>
    <item>
      <title>about</title>
      <link>https://oddcode.tistory.com/pages/about</link>
      <description>&lt;!doctype html&gt;
&lt;html lang=&quot;en&quot;&gt;
    &lt;head&gt;
        &lt;!-- Theme Made By www.w3schools.com - No Copyright --&gt;
        &lt;title&gt;odada&lt;/title&gt;
        &lt;meta charset=&quot;utf-8&quot; /&gt;
        &lt;meta
            name=&quot;viewport&quot;
            content=&quot;width=device-width, initial-scale=1&quot;
        /&gt;
        &lt;meta
            http-equiv=&quot;X-UA-Compatible&quot;
            content=&quot;IE=edge&quot;
        /&gt;
        &lt;meta
            name=&quot;description&quot;
            content=&quot;odada는 많은 경험을 바탕으로 반응형웹과 검색엔진최적화 홈페이지를 제작하는 전문 프리랜서 입니다.&quot;
        /&gt;
        &lt;meta
            name=&quot;keywords&quot;
            content=&quot;워드프레스, SEO, 반응형웹, 스튜디오 제이티, 워드프레스 홈페이지, 병원 워드프레스, 홈페이지제작,부산 워드프레스,울산 워드프레스,부산 홈페이지, 울산 홈페이지, 워드프레스 홈페이지 제작, 모바일웹 제작, 검색엔진최적화, 워드프레스 컨설팅, 워드프레스 플러그인 개발, 부산 홈페이지 제작, 웹에이젼시, 웹에이전시&quot;
        /&gt;
        &lt;meta
            property=&quot;og:type&quot;
            content=&quot;website&quot;
        /&gt;
        &lt;meta
            property=&quot;og:title&quot;
            content=&quot;mintpink&quot;
        /&gt;
        &lt;meta
            property=&quot;og:description&quot;
            content=&quot;odada는 많은 경험을 바탕으로 반응형웹과 검색엔진최적화 홈페이지를 제작하는 전문 프리랜서 입니다.&quot;
        /&gt;
        &lt;meta
            property=&quot;og:image:secure_url&quot;
            content=&quot;logo-flato.png&quot;
        /&gt;
        &lt;meta
            property=&quot;og:image&quot;
            content=&quot;logo-flato.png&quot;
        /&gt;
        &lt;meta
            property=&quot;og:url&quot;
            content=&quot;https://odada-o.github.io//odada/&quot;
        /&gt;
        &lt;link
            rel=&quot;shortcut icon&quot;
            type=&quot;image/png&quot;
            href=&quot;http://flato.papo.co.kr/imgs/favicon.ico&quot;
        /&gt;
        &lt;link
            rel=&quot;stylesheet&quot;
            href=&quot;https://maxst.icons8.com/vue-static/landings/line-awesome/line-awesome/1.3.0/css/line-awesome.min.css&quot;
        /&gt;
        &lt;link
            rel=&quot;stylesheet&quot;
            as=&quot;style&quot;
            crossorigin
            href=&quot;https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/static/pretendard.min.css&quot;
        /&gt;
        &lt;link
            href=&quot;https://fonts.googleapis.com/css2?family=Comfortaa:wght@300..700&amp;family=Noto+Sans+KR:wght@100..900&amp;display=swap&quot;
            rel=&quot;stylesheet&quot;
        /&gt;
        &lt;link
            rel=&quot;stylesheet&quot;
            href=&quot;https://odada-o.github.io//odada/dist/css/comm2.css&quot;
        /&gt;
    &lt;/head&gt;
    &lt;body id=&quot;myPage&quot;&gt;
        &lt;link
            rel=&quot;stylesheet&quot;
            as=&quot;style&quot;
            crossorigin
            href=&quot;https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/static/pretendard.min.css&quot;
        /&gt;
        &lt;link
            href=&quot;https://fonts.googleapis.com/css2?family=Comfortaa:wght@300..700&amp;family=Noto+Sans+KR:wght@100..900&amp;display=swap&quot;
            rel=&quot;stylesheet&quot;
        /&gt;
        &lt;link
            rel=&quot;stylesheet&quot;
            href=&quot;https://odada-o.github.io//odada/dist/css/comm2.css&quot;
        /&gt;
        &lt;div
            id=&quot;about&quot;
            class=&quot;wrap&quot;
        &gt;
            &lt;div id=&quot;profile&quot;&gt;
                &lt;div class=&quot;container text-center&quot;&gt;
                    &lt;h2 class=&quot;tit-name&quot;&gt;odada (정미애)&lt;/h2&gt;
                    &lt;ul class=&quot;lst-profile&quot;&gt;
                        &lt;li&gt;
                            &lt;a
                                href=&quot;https://github.com/oodada?tab=repositories&quot;
                                class=&quot;thumbnail&quot;
                            &gt;
                                &lt;span class=&quot;image oddodd&quot;&gt; od&lt;span&gt;d:o&lt;/span&gt;dd &lt;/span&gt;
                                &lt;h3&gt;od&lt;span&gt;d:o&lt;/span&gt;dd&lt;/h3&gt;
                                &lt;p&gt;
                                    프론트엔드, 인터랙션, 웹접근성, &lt;br /&gt;
                                    반응형, UI/UX, 웹퍼블리싱
                                &lt;/p&gt;
                                &lt;div class=&quot;card__hashtags&quot;&gt;
                                    &lt;span class=&quot;hashtag&quot;&gt;#Next.js&lt;/span&gt;
                                    &lt;span class=&quot;hashtag&quot;&gt;#React&lt;/span&gt;
                                    &lt;span class=&quot;hashtag&quot;&gt;#Javascript&lt;/span&gt;
                                    &lt;span class=&quot;hashtag&quot;&gt;#Responsive&lt;/span&gt;
                                    &lt;span class=&quot;hashtag&quot;&gt;#Gnuboard&lt;/span&gt;
                                    &lt;span class=&quot;hashtag&quot;&gt;#WordPress&lt;/span&gt;
                                    &lt;span class=&quot;hashtag&quot;&gt;#SASS&lt;/span&gt;
                                    &lt;span class=&quot;hashtag&quot;&gt;#Figma&lt;/span&gt;
                                    &lt;span class=&quot;hashtag&quot;&gt;#Photoshop&lt;/span&gt;
                                    &lt;span class=&quot;hashtag&quot;&gt;#Illustrator&lt;/span&gt;
                                &lt;/div&gt;
                            &lt;/a&gt;
                        &lt;/li&gt;
                        &lt;li&gt;
                            &lt;a
                                href=&quot;portfolio-flato.html#p2&quot;
                                class=&quot;thumbnail&quot;
                            &gt;
                                &lt;span class=&quot;image daum&quot;&gt;
                                    &lt;svg
                                        xmlns=&quot;http://www.w3.org/2000/svg&quot;
                                        width=&quot;213&quot;
                                        height=&quot;23&quot;
                                        fill=&quot;none&quot;
                                    &gt;
                                        &lt;g clip-path=&quot;url(#a)&quot;&gt;
                                            &lt;path
                                                fill=&quot;#231F1E&quot;
                                                d=&quot;M5.833 17.307q1.1 0 2.32-.933V9.249Q7.1 9.011 6.385 9.01q-2.87 0-2.87 4.305 0 3.993 2.32 3.993M5.93 6.31q1.265 0 2.224.594V1.292L11.62.668v19.054H9.158l-.384-1.124A6.9 6.9 0 0 1 6.98 19.7q-.931.382-1.745.382-2.583 0-3.909-1.767T0 13.162q0-1.575.407-2.841.406-1.265 1.185-2.16a5.25 5.25 0 0 1 1.863-1.373Q4.543 6.31 5.93 6.31m15.753 6.478 4.472 5.308-2.678 2.08-5.428-6.694v6.24h-3.49V1.29l3.49-.621v11.978l4.974-6.743 2.653 1.818zM28.977 7.1l2.03-.287V3.968l2.225-.48v3.323h4.065v1.696h-4.065v6.695q0 1.65.407 2.296t1.364.647a5 5 0 0 0 1.242-.156q.622-.156 1.029-.275l.477 1.531q-.429.263-1.326.515a6.8 6.8 0 0 1-1.852.251q-1.795 0-2.679-1.112-.885-1.11-.884-3.574V8.512h-2.03v-1.41zM44.76 8.152q-1.577 0-2.415 1.075-.836 1.075-.884 3.155h6.36q0-2.175-.74-3.204-.742-1.027-2.32-1.028m.072-1.766q2.437 0 3.79 1.607 1.35 1.606 1.35 4.556 0 .433-.05.852a7 7 0 0 1-.095.635h-8.344q.12 2.165 1.075 3.185.956 1.022 2.941 1.022.765 0 1.769-.228a8.6 8.6 0 0 0 1.768-.586l.55 1.545q-.91.496-2.02.765a9.6 9.6 0 0 1-2.283.27q-2.988 0-4.53-1.713-1.543-1.711-1.543-5.042c0-2.22.493-3.839 1.482-5.054q1.483-1.819 4.137-1.82M61.27 8.94a6.7 6.7 0 0 0-1.505-.526 7.4 7.4 0 0 0-1.65-.191q-1.864-.001-2.88 1.184t-1.017 3.886q-.002 2.534 1.017 3.707 1.016 1.172 2.904 1.172a7.2 7.2 0 0 0 1.9-.251 9.4 9.4 0 0 0 1.615-.587l.598 1.627q-.812.479-1.935.766-1.125.286-2.416.286-2.94 0-4.434-1.769t-1.494-4.972q0-1.554.384-2.82.38-1.267 1.135-2.164a5 5 0 0 1 1.89-1.387q1.134-.49 2.666-.49 1.148 0 2.164.274 1.015.276 1.78.728l-.717 1.531zm3.699-7.697 2.175-.477v6.98a8.4 8.4 0 0 1 1.936-.919 6.9 6.9 0 0 1 2.2-.37q2.103 0 3.238 1.28t1.135 3.597v8.39h-2.198v-8.416q.001-1.505-.694-2.236-.694-.729-2.031-.728-.839 0-1.84.251-1.004.252-1.722.682v10.448h-2.2zm16.855 18.479H79.65V6.812h2.175zM79.027 2.268 80.75.5l1.722 1.768-1.722 1.745zm6.75 4.544h1.627l.335 1.123a8.2 8.2 0 0 1 2.08-1.051q1.172-.406 2.271-.407 2.105-.001 3.239 1.28c.756.85 1.135 2.052 1.135 3.597v8.367h-2.198v-8.39q.001-1.506-.694-2.237-.694-.729-2.031-.728-.407 0-.884.072-.48.073-.957.19-.478.12-.919.287-.441.169-.8.382V19.72h-2.199V6.81z&quot;
                                            /&gt;
                                            &lt;path
                                                fill=&quot;#9FA0A0&quot;
                                                d=&quot;M105.759 15.956h1.468v-.538c0-1.261-.466-1.81-1.503-1.81-.601 0-1.285.184-1.811.428l-.221-.586a4.66 4.66 0 0 1 2.067-.49c1.479 0 2.212.82 2.212 2.472v4.305h-.586l-.123-.82c-.636.538-1.529.88-2.25.88-1.138 0-1.836-.696-1.836-1.773 0-1.357.905-2.066 2.58-2.066m1.471 2.322v-1.75h-1.382c-1.322 0-1.883.428-1.883 1.406 0 .819.391 1.224 1.161 1.224.698 0 1.555-.38 2.104-.882m14.828-1.097q0-1.441.965-2.199.964-.757 2.806-.756h1.843v-.607q0-2.206-1.947-2.206-.626 0-1.31.174-.687.174-1.261.435l-.47-1.13a6.6 6.6 0 0 1 1.538-.617 6.3 6.3 0 0 1 1.605-.216q3.372 0 3.372 3.597v6.08h-1.182l-.191-1.043a5.8 5.8 0 0 1-1.52.887 4.2 4.2 0 0 1-1.538.312q-1.266 0-1.989-.722-.72-.72-.721-1.989m2.999 1.424q.329.001.682-.088.355-.087.709-.226.357-.139.668-.34.311-.199.554-.426v-2.224h-1.661q-1.263 0-1.843.435-.58.433-.58 1.373-.001 1.494 1.471 1.494m8.584 1.131h-1.599V6.304l1.599-.347zm4.274-9.747 1.252.903-3.074 3.855 3.56 4.275-1.233.956-4.205-5.142zm3.153 7.194q0-1.441.963-2.199.966-.757 2.806-.756h1.841v-.607q0-2.206-1.945-2.206-.625 0-1.313.174-.686.174-1.259.435l-.47-1.13a6.66 6.66 0 0 1 3.144-.833q3.37 0 3.371 3.597v6.08h-1.182l-.19-1.043a5.7 5.7 0 0 1-1.52.887 4.2 4.2 0 0 1-1.538.312c-.844 0-1.51-.24-1.989-.722q-.723-.72-.722-1.989m3.004 1.424q.329.001.682-.088.356-.087.71-.226t.668-.34q.31-.199.553-.426v-2.224h-1.661q-1.263 0-1.843.435-.579.433-.579 1.373 0 1.494 1.47 1.494m10.568-8.529q1.98 0 3.109 1.285 1.13 1.286 1.13 3.683c0 1.598-.372 2.785-1.121 3.632q-1.121 1.268-3.118 1.268c-1.331 0-2.357-.423-3.109-1.268q-1.13-1.268-1.13-3.632c0-1.575.379-2.825 1.137-3.683q1.138-1.285 3.102-1.285m0 1.285q-1.253.001-1.92.937-.67.938-.67 2.746c0 1.205.224 2.094.67 2.701q.669.913 1.92.913c.833 0 1.491-.303 1.938-.913q.67-.91.668-2.701c0-1.194-.223-2.12-.668-2.746q-.67-.938-1.938-.937m12.945 2.627c-.44-.233-1.052-.368-1.603-.368-1.42 0-2.227.845-2.227 2.85 0 1.848.759 2.727 2.141 2.727.735 0 1.37-.207 1.859-.451l.244.598c-.54.305-1.247.526-2.129.526-1.92 0-2.899-1.236-2.899-3.425s.991-3.486 2.997-3.486c.672 0 1.382.184 1.882.477l-.27.552zm7.063 2.447c0 2.238-1.063 3.436-2.862 3.436s-2.862-1.198-2.862-3.436 1.089-3.46 2.862-3.46 2.862 1.175 2.862 3.46m-4.942-.012c0 1.822.747 2.802 2.08 2.802s2.08-.98 2.08-2.802-.747-2.801-2.08-2.801-2.08.99-2.08 2.801m6.64-3.241h.586l.124.489c.698-.5 1.454-.673 1.994-.673.795 0 1.393.305 1.689.88a3.08 3.08 0 0 1 2.115-.88c1.273 0 2.02.66 2.02 2.08v4.66h-.747V15.26c0-.917-.221-1.615-1.322-1.615-.698 0-1.394.342-1.822.745v5.345h-.747v-4.782c0-.733-.342-1.308-1.259-1.308-1.014 0-1.749.538-1.882.636v5.456h-.747V13.18zm11.286 6.422v2.703h-.747V13.18h.586l.11.477c.574-.391 1.296-.684 2.08-.684 1.626 0 2.422 1.26 2.422 3.313 0 2.227-1.075 3.583-2.899 3.583-.552 0-1.078-.072-1.555-.27m.003-5.33v4.71c.5.195.954.244 1.394.244 1.528 0 2.264-1.015 2.264-2.937 0-1.724-.515-2.667-1.736-2.667-.722 0-1.443.305-1.92.65m8.296 1.687h1.468v-.538c0-1.261-.465-1.81-1.503-1.81-.6 0-1.284.184-1.81.428l-.219-.586a4.66 4.66 0 0 1 2.067-.49c1.479 0 2.212.82 2.212 2.472v4.305h-.586l-.123-.82c-.636.538-1.529.88-2.25.88-1.138 0-1.836-.696-1.836-1.773 0-1.357.905-2.066 2.58-2.066m1.468 2.322v-1.75h-1.382c-1.321 0-1.882.428-1.882 1.406 0 .819.391 1.224 1.161 1.224.696 0 1.554-.38 2.103-.882m2.741-5.098h.586l.123.489c.624-.391 1.518-.673 2.299-.673 1.406 0 2.213.917 2.213 2.446v4.293h-.758v-4.319c0-1.21-.563-1.773-1.541-1.773-.698 0-1.626.256-2.178.635v5.457h-.744v-6.557zm9.679 6.152c-.537 1.431-1.296 2.544-2.178 3.167l-.502-.465c.623-.452 1.419-1.454 1.773-2.336l-2.471-6.422.733-.221 2.14 5.784 2.153-5.784.709.22-2.362 6.055zm-95.186.402h-1.599V6.304l1.599-.347zm4.277-9.747 1.249.903-3.076 3.855 3.562 4.275-1.233.956-4.204-5.142z&quot;
                                            /&gt;
                                        &lt;/g&gt;
                                        &lt;defs&gt;
                                            &lt;clipPath id=&quot;a&quot;&gt;
                                                &lt;path
                                                    fill=&quot;#fff&quot;
                                                    d=&quot;M0 .5h212.175v22H0z&quot;
                                                /&gt;
                                            &lt;/clipPath&gt;
                                        &lt;/defs&gt;
                                    &lt;/svg&gt;
                                    &lt;!-- &lt;img
                                        src=&quot;https://odada-o.github.io//odada/imgs/intro/logo-daumkakao.png&quot;
                                        alt=&quot;daumkakao&quot;
                                    /&gt; --&gt;
                                &lt;/span&gt;
                                &lt;h3&gt;dk techin a kakao company&lt;/h3&gt;
                                &lt;p&gt;kakao 프론트엔드&lt;/p&gt;
                                &lt;div class=&quot;card__hashtags&quot;&gt;
                                    &lt;span class=&quot;hashtag&quot;&gt;#Next.js&lt;/span&gt;
                                    &lt;span class=&quot;hashtag&quot;&gt;#React&lt;/span&gt;
                                    &lt;span class=&quot;hashtag&quot;&gt;#Javascript&lt;/span&gt;
                                    &lt;span class=&quot;hashtag&quot;&gt;#Responsive&lt;/span&gt;
                                    &lt;span class=&quot;hashtag&quot;&gt;#웹표준&lt;/span&gt;
                                    &lt;span class=&quot;hashtag&quot;&gt;#웹접근성&lt;/span&gt;
                                &lt;/div&gt;
                            &lt;/a&gt;
                        &lt;/li&gt;
                        &lt;li&gt;
                            &lt;a
                                href=&quot;http://cafe.naver.com/mocoding&quot;
                                class=&quot;thumbnail&quot;
                                target=&quot;_blank&quot;
                            &gt;
                                &lt;span class=&quot;image&quot;&gt;
                                    &lt;img
                                        src=&quot;https://odada-o.github.io//odada/imgs/intro/logo-green.png&quot;
                                        alt=&quot;그린컴퓨터 아카데미&quot;
                                    /&gt;
                                &lt;/span&gt;
                                &lt;h3&gt;
                                    이젠컴퓨터아카데미,&lt;br /&gt;
                                    그린컴퓨터 아카데미, 더조은아카데미,
                                &lt;/h3&gt;
                                &lt;p&gt;UIUX 디자인 &amp;#38; 프론트앤드&lt;/p&gt;
                                &lt;div class=&quot;card__hashtags&quot;&gt;
                                    &lt;span class=&quot;hashtag&quot;&gt;#Node.js&lt;/span&gt;
                                    &lt;span class=&quot;hashtag&quot;&gt;#AWS&lt;/span&gt;
                                    &lt;span class=&quot;hashtag&quot;&gt;#Next.js&lt;/span&gt;
                                    &lt;span class=&quot;hashtag&quot;&gt;#React&lt;/span&gt;
                                    &lt;span class=&quot;hashtag&quot;&gt;#Javascript&lt;/span&gt;
                                    &lt;span class=&quot;hashtag&quot;&gt;#CSS&lt;/span&gt;
                                    &lt;span class=&quot;hashtag&quot;&gt;#HTML&lt;/span&gt;
                                    &lt;span class=&quot;hashtag&quot;&gt;#Figma&lt;/span&gt;
                                &lt;/div&gt;
                            &lt;/a&gt;
                        &lt;/li&gt;
                        &lt;li&gt;
                            &lt;a
                                href=&quot;./portfolio-woongjin.html&quot;
                                class=&quot;thumbnail&quot;
                            &gt;
                                &lt;span class=&quot;image&quot;
                                    &gt;&lt;img
                                        src=&quot;https://odada-o.github.io//odada/imgs/intro/logo-woongjin.png&quot;
                                        alt=&quot;웅진패스원&quot;
                                /&gt;&lt;/span&gt;
                                &lt;h3&gt;웅진패스원(KG패스원)&lt;/h3&gt;
                                &lt;p&gt;농협, 동국제강, SK네트웍스, 대한병원협회, &lt;br /&gt;아주그룹 직무교육 사이트 제작&lt;/p&gt;
                                &lt;div class=&quot;card__hashtags&quot;&gt;
                                    &lt;span class=&quot;hashtag&quot;&gt;#html5&lt;/span&gt;
                                    &lt;span class=&quot;hashtag&quot;&gt;#scss&lt;/span&gt;
                                    &lt;span class=&quot;hashtag&quot;&gt;#responsive&lt;/span&gt;
                                    &lt;span class=&quot;hashtag&quot;&gt;#javascript&lt;/span&gt;
                                    &lt;span class=&quot;hashtag&quot;&gt;#mobile 최적화&lt;/span&gt;
                                    &lt;span class=&quot;hashtag&quot;&gt;#웹표준&lt;/span&gt;
                                    &lt;span class=&quot;hashtag&quot;&gt;#웹접근성&lt;/span&gt;
                                &lt;/div&gt;
                            &lt;/a&gt;
                        &lt;/li&gt;
                        &lt;li&gt;
                            &lt;a
                                href=&quot;./portfolio-paxnet.html&quot;
                                class=&quot;thumbnail&quot;
                            &gt;
                                &lt;span class=&quot;image&quot;&gt;
                                    &lt;img
                                        src=&quot;https://odada-o.github.io//odada/imgs/intro/logo-paxnet.png&quot;
                                        alt=&quot;paxnet&quot;
                                    /&gt;
                                &lt;/span&gt;
                                &lt;h3&gt;No.1 재테크포털 모네타/팍스넷&lt;/h3&gt;
                                &lt;p&gt;
                                    사이트 내 UI/UX, 웹디자인, 웹퍼블리싱&lt;br /&gt;
                                    모의투자대회
                                &lt;/p&gt;
                                &lt;div class=&quot;card__hashtags&quot;&gt;
                                    &lt;span class=&quot;hashtag&quot;&gt;#HTML&lt;/span&gt;
                                    &lt;span class=&quot;hashtag&quot;&gt;#CSS&lt;/span&gt;
                                    &lt;span class=&quot;hashtag&quot;&gt;#Photoshop&lt;/span&gt;
                                    &lt;span class=&quot;hashtag&quot;&gt;#Illustrator&lt;/span&gt;
                                &lt;/div&gt;
                            &lt;/a&gt;
                        &lt;/li&gt;
                        &lt;li&gt;
                            &lt;a
                                href=&quot;portfolio-flash.html&quot;
                                class=&quot;thumbnail&quot;
                                target=&quot;_blank&quot;
                            &gt;
                                &lt;span class=&quot;image&quot;
                                    &gt;&lt;img
                                        src=&quot;https://odada-o.github.io//odada/imgs/intro/logo-flash.png&quot;
                                        alt=&quot;플래시책저&quot;
                                /&gt;&lt;/span&gt;
                                &lt;h3&gt;플래시 책 저&lt;/h3&gt;
                                &lt;p&gt;
                                    플래시8 Motion+Action &lt;br /&gt;
                                    플래시MX 모든걸 가르쳐주마
                                &lt;/p&gt;
                                &lt;div class=&quot;card__hashtags&quot;&gt;
                                    &lt;span class=&quot;hashtag&quot;&gt;#제우미디어&lt;/span&gt;
                                    &lt;span class=&quot;hashtag&quot;&gt;#flash&lt;/span&gt;
                                    &lt;span class=&quot;hashtag&quot;&gt;#Motion&lt;/span&gt;
                                    &lt;span class=&quot;hashtag&quot;&gt;#Action&lt;/span&gt;
                                &lt;/div&gt;
                            &lt;/a&gt;
                        &lt;/li&gt;
                    &lt;/ul&gt;
                &lt;/div&gt;
            &lt;/div&gt;

            &lt;!-- 진짜 탭 --&gt;
            &lt;div
                id=&quot;portfolio&quot;
                class=&quot;container text-center&quot;
            &gt;
                &lt;div
                    class=&quot;tab-content&quot;
                    id=&quot;myTabContent&quot;
                &gt;
                    &lt;div
                        class=&quot;tab-pane fade show active&quot;
                        id=&quot;home&quot;
                        role=&quot;tabpanel&quot;
                        aria-labelledby=&quot;home-tab&quot;
                    &gt;
                        &lt;h2&gt;odd:odd&lt;/h2&gt;
                        &lt;div class=&quot;wrap-explain&quot;&gt;
                            &lt;!-- ste --&gt;
                            &lt;div class=&quot;wrap-project&quot;&gt;
                                &lt;img
                                    src=&quot;https://odada-o.github.io//odada/images/ste/pc-1.png?raw=true&quot;
                                    alt=&quot;BOAspace&quot;
                                /&gt;
                                &lt;div class=&quot;box-explain&quot;&gt;
                                    &lt;dl&gt;
                                        &lt;dt&gt;작업명&lt;/dt&gt;
                                        &lt;dd&gt;&lt;strong&gt;[PC, M] STE&lt;/strong&gt;&lt;/dd&gt;

                                        &lt;dt&gt;키워드&lt;/dt&gt;
                                        &lt;dd&gt;#React, #styledComponent #Chakra-UI&lt;/dd&gt;
                                    &lt;/dl&gt;
                                    &lt;dl&gt;
                                        &lt;dt&gt;작업url&lt;/dt&gt;
                                        &lt;dd&gt;
                                            &lt;a
                                                href=&quot;https://github.com/oddoddo/--ste-react&quot;
                                                target=&quot;_blank&quot;
                                                &gt;https://github.com/oddoddo/--ste-react&lt;/a
                                            &gt;
                                        &lt;/dd&gt;
                                    &lt;/dl&gt;
                                &lt;/div&gt;
                                &lt;div class=&quot;row text-center box-img&quot;&gt;
                                    &lt;div class=&quot;col-md-6&quot;&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/images/ste/pc-1.png?raw=true&quot;
                                            alt=&quot;BOAspace&quot;
                                        /&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/images/ste/pc-3.png?raw=true&quot;
                                            alt=&quot;BOAspace&quot;
                                        /&gt;
                                    &lt;/div&gt;
                                    &lt;div class=&quot;col-md-6&quot;&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/images/ste/pc-2.png?raw=true&quot;
                                            alt=&quot;BOAspace&quot;
                                        /&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/images/ste/pc-4.png?raw=true&quot;
                                            alt=&quot;BOAspace&quot;
                                        /&gt;
                                    &lt;/div&gt;
                                &lt;/div&gt;
                                &lt;div class=&quot;row text-center box-img&quot;&gt;
                                    &lt;div class=&quot;col-md-4&quot;&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/images/ste/m-2.png?raw=true&quot;
                                            alt=&quot;BOAspace&quot;
                                        /&gt;
                                    &lt;/div&gt;
                                    &lt;div class=&quot;col-md-4&quot;&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/images/ste/m-3.png?raw=true&quot;
                                            alt=&quot;BOAspace&quot;
                                        /&gt;
                                    &lt;/div&gt;
                                    &lt;div class=&quot;col-md-4&quot;&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/images/ste/m-4.png?raw=true&quot;
                                            alt=&quot;BOAspace&quot;
                                        /&gt;
                                    &lt;/div&gt;
                                &lt;/div&gt;
                            &lt;/div&gt;

                            &lt;!-- BOAspace --&gt;
                            &lt;div class=&quot;wrap-project&quot;&gt;
                                &lt;img
                                    src=&quot;https://odada-o.github.io//odada/images/boaspace1.jpg&quot;
                                    alt=&quot;BOAspace&quot;
                                /&gt;
                                &lt;div class=&quot;box-explain&quot;&gt;
                                    &lt;dl&gt;
                                        &lt;dt&gt;작업명&lt;/dt&gt;
                                        &lt;dd&gt;&lt;strong&gt;[PC, M] NFT Market BOAspace&lt;/strong&gt;&lt;/dd&gt;

                                        &lt;dt&gt;키워드&lt;/dt&gt;
                                        &lt;dd&gt;#React, #styledComponent #Chakra-UI&lt;/dd&gt;
                                    &lt;/dl&gt;
                                    &lt;dl&gt;
                                        &lt;dt&gt;작업url&lt;/dt&gt;
                                        &lt;dd&gt;
                                            &lt;a
                                                href=&quot;https://boaspace.io/&quot;
                                                target=&quot;_blank&quot;
                                                &gt;https://boaspace.io/&lt;/a
                                            &gt;
                                            &lt;a
                                                href=&quot;https://github.com/oddoddo/boa-space-frontend-&quot;
                                                target=&quot;_blank&quot;
                                                &gt;https://github.com/oddoddo/boa-space-frontend-&lt;/a
                                            &gt;
                                        &lt;/dd&gt;
                                    &lt;/dl&gt;
                                &lt;/div&gt;
                                &lt;div class=&quot;row text-center box-img&quot;&gt;
                                    &lt;div class=&quot;col-md-6&quot;&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/images/boaspace1.jpg&quot;
                                            alt=&quot;BOAspace&quot;
                                        /&gt;
                                    &lt;/div&gt;
                                    &lt;div class=&quot;col-md-6&quot;&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/images/boaspace.jpg&quot;
                                            alt=&quot;BOAspace&quot;
                                        /&gt;
                                    &lt;/div&gt;
                                &lt;/div&gt;
                            &lt;/div&gt;
                            &lt;!-- bosagora --&gt;
                            &lt;div class=&quot;wrap-project&quot;&gt;
                                &lt;img
                                    src=&quot;https://odada-o.github.io//odada/images/bosagora.jpg&quot;
                                    alt=&quot;BOSagora&quot;
                                /&gt;
                                &lt;div class=&quot;box-explain&quot;&gt;
                                    &lt;dl&gt;
                                        &lt;dt&gt;작업명&lt;/dt&gt;
                                        &lt;dd&gt;&lt;strong&gt;[PC, M] BOSagora&lt;/strong&gt;&lt;/dd&gt;
                                        &lt;dt&gt;키워드&lt;/dt&gt;
                                        &lt;dd&gt;#React, #styledComponent #Chakra-UI&lt;/dd&gt;
                                    &lt;/dl&gt;
                                    &lt;dl&gt;
                                        &lt;dt&gt;작업url&lt;/dt&gt;
                                        &lt;dd&gt;
                                            &lt;a
                                                href=&quot;https://bosagora.io/ko/&quot;
                                                target=&quot;_blank&quot;
                                                &gt;https://bosagora.io/ko/&lt;/a
                                            &gt;
                                            &lt;a
                                                href=&quot;https://github.com/oddoddo/BOSagora&quot;
                                                target=&quot;_blank&quot;
                                                &gt;https://github.com/oddoddo/BOSagora&lt;/a
                                            &gt;
                                        &lt;/dd&gt;
                                    &lt;/dl&gt;
                                &lt;/div&gt;
                                &lt;div class=&quot;row text-center box-img&quot;&gt;
                                    &lt;div class=&quot;col-md-6&quot;&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/images/bosagora.jpg&quot;
                                            alt=&quot;BOSagora&quot;
                                        /&gt;
                                    &lt;/div&gt;
                                    &lt;div class=&quot;col-md-6&quot;&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/images/bosagora1.jpg&quot;
                                            alt=&quot;BOSagora&quot;
                                        /&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/images/bosagora3.jpg&quot;
                                            alt=&quot;BOSagora&quot;
                                        /&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/images/bosagora2.jpg&quot;
                                            alt=&quot;BOSagora&quot;
                                        /&gt;
                                    &lt;/div&gt;
                                &lt;/div&gt;
                            &lt;/div&gt;
                            &lt;!-- fmschool --&gt;
                            &lt;div class=&quot;wrap-project&quot;&gt;
                                &lt;img
                                    src=&quot;https://odada-o.github.io//odada/images/fmschool.png&quot;
                                    alt=&quot;fm스쿨&quot;
                                /&gt;
                                &lt;div class=&quot;box-explain&quot;&gt;
                                    &lt;dl&gt;
                                        &lt;dt&gt;작업명&lt;/dt&gt;
                                        &lt;dd&gt;&lt;strong&gt;[PC, M] fmschool&lt;/strong&gt;&lt;/dd&gt;
                                    &lt;/dl&gt;
                                    &lt;dl&gt;
                                        &lt;dt&gt;작업url&lt;/dt&gt;
                                        &lt;dd&gt;
                                            &lt;a
                                                href=&quot;https://fmschool.co.kr/&quot;
                                                target=&quot;_blank&quot;
                                                &gt;https://fmschool.co.kr/&lt;/a
                                            &gt;
                                        &lt;/dd&gt;
                                        &lt;dt&gt;키워드&lt;/dt&gt;
                                        &lt;dd&gt;#React, #styledComponent&lt;/dd&gt;
                                    &lt;/dl&gt;
                                &lt;/div&gt;
                                &lt;div class=&quot;row text-center box-img&quot;&gt;
                                    &lt;div class=&quot;col-md-10 col-md-offset-1&quot;&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/images/fmschool.png&quot;
                                            alt=&quot;fm스쿨&quot;
                                        /&gt;
                                    &lt;/div&gt;
                                &lt;/div&gt;
                            &lt;/div&gt;
                            &lt;!-- 한국전자인증 --&gt;
                            &lt;div class=&quot;wrap-project&quot;&gt;
                                &lt;img
                                    src=&quot;https://odada-o.github.io//odada/images/2021/210100-crosscert.jpeg&quot;
                                    alt=&quot;&quot;
                                /&gt;
                                &lt;div class=&quot;box-explain&quot;&gt;
                                    &lt;dl&gt;
                                        &lt;dt&gt;작업명&lt;/dt&gt;
                                        &lt;dd&gt;&lt;strong&gt;[PC, M] 한국전자인증 웹접근성 평가인증&lt;/strong&gt;&lt;/dd&gt;
                                    &lt;/dl&gt;
                                    &lt;dl&gt;
                                        &lt;dt&gt;작업url&lt;/dt&gt;
                                        &lt;dd&gt;
                                            &lt;a
                                                href=&quot;https://www.crosscert.com/&quot;
                                                target=&quot;_blank&quot;
                                                &gt;https://www.crosscert.com/&lt;/a
                                            &gt;
                                        &lt;/dd&gt;
                                        &lt;dt&gt;키워드&lt;/dt&gt;
                                        &lt;dd&gt;#웹접근성평가인증, #WA&lt;/dd&gt;
                                    &lt;/dl&gt;
                                &lt;/div&gt;
                                &lt;div class=&quot;row text-center box-img&quot;&gt;
                                    &lt;div class=&quot;col-md-10 col-md-offset-1&quot;&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/images/2021/210100-crosscert.jpeg&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                    &lt;/div&gt;
                                &lt;/div&gt;
                            &lt;/div&gt;
                            &lt;!-- 용인시 교통약자지원센터 --&gt;
                            &lt;div class=&quot;wrap-project&quot;&gt;
                                &lt;img
                                    src=&quot;https://odada-o.github.io//odada/images/2021/210601-damsyn-1.jpg&quot;
                                    alt=&quot;&quot;
                                /&gt;
                                &lt;div class=&quot;box-explain&quot;&gt;
                                    &lt;dl&gt;
                                        &lt;dt&gt;작업명&lt;/dt&gt;
                                        &lt;dd&gt;&lt;strong&gt;[PC] 용인시 교통약자 이동지원센터&lt;/strong&gt;&lt;/dd&gt;
                                    &lt;/dl&gt;
                                    &lt;dl&gt;
                                        &lt;dt&gt;작업url&lt;/dt&gt;
                                        &lt;dd&gt;
                                            &lt;a
                                                href=&quot;https://odada-o.github.io//Dams-yn&quot;
                                                target=&quot;_blank&quot;
                                                &gt;https://odada-o.github.io//Dams-yn&lt;/a
                                            &gt;
                                        &lt;/dd&gt;
                                        &lt;dt&gt;키워드&lt;/dt&gt;
                                        &lt;dd&gt;#react #html5 #sass&lt;/dd&gt;
                                    &lt;/dl&gt;
                                &lt;/div&gt;
                                &lt;div class=&quot;row text-center box-img&quot;&gt;
                                    &lt;div class=&quot;col-md-10 col-md-offset-1&quot;&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/images/2021/210601-damsyn-1.jpg&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                    &lt;/div&gt;
                                &lt;/div&gt;
                            &lt;/div&gt;

                            &lt;!-- 카스카디아 --&gt;
                            &lt;div class=&quot;wrap-project&quot;&gt;
                                &lt;img
                                    src=&quot;https://odada-o.github.io//odada/images/2021/210729-cascadia-1.jpg&quot;
                                    alt=&quot;&quot;
                                /&gt;
                                &lt;div class=&quot;box-explain&quot;&gt;
                                    &lt;dl&gt;
                                        &lt;dt&gt;작업명&lt;/dt&gt;
                                        &lt;dd&gt;&lt;strong&gt;[반응형] 카스카디아&lt;/strong&gt;&lt;/dd&gt;
                                    &lt;/dl&gt;
                                    &lt;dl&gt;
                                        &lt;dt&gt;작업URL&lt;/dt&gt;
                                        &lt;dd&gt;
                                            &lt;a
                                                href=&quot;https://odada-o.github.io//golfclub/&quot;
                                                target=&quot;_blank&quot;
                                                rel=&quot;noopener noreferrer&quot;
                                            &gt;
                                                desktop
                                            &lt;/a&gt;
                                            &lt;br /&gt;
                                            &lt;a
                                                href=&quot;https://odada-o.github.io//golfclub/m&quot;
                                                target=&quot;_blank&quot;
                                                rel=&quot;noopener noreferrer&quot;
                                            &gt;
                                                mobile
                                            &lt;/a&gt;
                                        &lt;/dd&gt;
                                        &lt;dt&gt;키워드&lt;/dt&gt;
                                        &lt;dd&gt;#html5 #sass #javascript #slick&lt;/dd&gt;
                                    &lt;/dl&gt;
                                &lt;/div&gt;
                                &lt;div class=&quot;row text-center box-img&quot;&gt;
                                    &lt;div class=&quot;col-md-6&quot;&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/images/2021/210729-cascadia-1.jpg&quot;
                                            alt=&quot;&quot;
                                        /&gt;&lt;br /&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/images/2021/210729-cascadia-2.jpg&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                    &lt;/div&gt;
                                    &lt;div class=&quot;col-md-6&quot;&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/images/2021/210729-cascadia-3.jpg&quot;
                                            alt=&quot;&quot;
                                        /&gt;&lt;br /&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/images/2021/210729-cascadia-4.jpg&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                    &lt;/div&gt;
                                &lt;/div&gt;
                            &lt;/div&gt;

                            &lt;!-- 어린이병원 --&gt;
                            &lt;div class=&quot;wrap-project&quot;&gt;
                                &lt;img
                                    src=&quot;https://odada-o.github.io//odada/images/2021/210902-punhkids-1.jpg&quot;
                                    alt=&quot;&quot;
                                /&gt;
                                &lt;div class=&quot;box-explain&quot;&gt;
                                    &lt;dl&gt;
                                        &lt;dt&gt;작업명&lt;/dt&gt;
                                        &lt;dd&gt;&lt;strong&gt;[pc] 부산대학교 어린이병원&lt;/strong&gt;&lt;/dd&gt;
                                    &lt;/dl&gt;
                                    &lt;dl&gt;
                                        &lt;dt&gt;작업URL&lt;/dt&gt;
                                        &lt;dd&gt;
                                            &lt;a
                                                href=&quot;https://odada-o.github.io//pnuh-kids/pnuh-kids/&quot;
                                                target=&quot;_blank&quot;
                                                rel=&quot;noopener noreferrer&quot;
                                                &gt;https://odada-o.github.io//pnuh-kids/pnuh-kids/&lt;/a
                                            &gt;
                                        &lt;/dd&gt;
                                        &lt;dt&gt;키워드&lt;/dt&gt;
                                        &lt;dd&gt;#html5 #sass #javascript #slick&lt;/dd&gt;
                                    &lt;/dl&gt;
                                &lt;/div&gt;
                                &lt;div class=&quot;row text-center box-img&quot;&gt;
                                    &lt;div class=&quot;col-md-6&quot;&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/images/2021/210902-punhkids-1.jpg&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                    &lt;/div&gt;
                                    &lt;div class=&quot;col-md-6&quot;&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/images/2021/210902-punhkids-2.jpg&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                    &lt;/div&gt;
                                &lt;/div&gt;
                            &lt;/div&gt;

                            &lt;!-- 센터 --&gt;
                            &lt;div class=&quot;wrap-project&quot;&gt;
                                &lt;img
                                    src=&quot;https://odada-o.github.io//odada/images/2021/210908-punhcenter-1.jpg&quot;
                                    alt=&quot;&quot;
                                /&gt;
                                &lt;div class=&quot;box-explain&quot;&gt;
                                    &lt;dl&gt;
                                        &lt;dt&gt;작업명&lt;/dt&gt;
                                        &lt;dd&gt;&lt;strong&gt;[pc] 부산대학교 재활센터&lt;/strong&gt;&lt;/dd&gt;
                                    &lt;/dl&gt;
                                    &lt;dl&gt;
                                        &lt;dt&gt;작업URL&lt;/dt&gt;
                                        &lt;dd&gt;
                                            &lt;a
                                                href=&quot;https://odada-o.github.io//pnuh-center/&quot;
                                                target=&quot;_blank&quot;
                                                rel=&quot;noopener noreferrer&quot;
                                                &gt;https://odada-o.github.io//pnuh-center/&lt;/a
                                            &gt;
                                        &lt;/dd&gt;
                                        &lt;dt&gt;키워드&lt;/dt&gt;
                                        &lt;dd&gt;#html5 #sass #css3 #javascript #slick&lt;/dd&gt;
                                    &lt;/dl&gt;
                                &lt;/div&gt;
                                &lt;div class=&quot;row text-center box-img&quot;&gt;
                                    &lt;div class=&quot;col-md-6&quot;&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/images/2021/210908-punhcenter-1.jpg&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                    &lt;/div&gt;
                                    &lt;div class=&quot;col-md-6&quot;&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/images/2021/210908-punhcenter-2.jpg&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                    &lt;/div&gt;
                                &lt;/div&gt;
                            &lt;/div&gt;

                            &lt;!-- lh사전접수 --&gt;
                            &lt;div class=&quot;wrap-project&quot;&gt;
                                &lt;img
                                    src=&quot;https://odada-o.github.io//odada/images/2021/210820-lhtablet-1.jpg&quot;
                                    alt=&quot;&quot;
                                /&gt;
                                &lt;div class=&quot;box-explain&quot;&gt;
                                    &lt;dl&gt;
                                        &lt;dt&gt;작업명&lt;/dt&gt;
                                        &lt;dd&gt;&lt;strong&gt;[tablet] LH 사전 접수&lt;/strong&gt;&lt;/dd&gt;
                                    &lt;/dl&gt;
                                    &lt;dl&gt;
                                        &lt;dt&gt;작업URL&lt;/dt&gt;
                                        &lt;dd&gt;
                                            &lt;a
                                                href=&quot;https://odada-o.github.io//lh-t/&quot;
                                                target=&quot;_blank&quot;
                                                rel=&quot;noopener noreferrer&quot;
                                            &gt;
                                                https://odada-o.github.io//lh-t/&lt;/a
                                            &gt;
                                        &lt;/dd&gt;
                                        &lt;dt&gt;키워드&lt;/dt&gt;
                                        &lt;dd&gt;#html5 #sass #javascript #slick&lt;/dd&gt;
                                    &lt;/dl&gt;
                                &lt;/div&gt;
                                &lt;div class=&quot;row text-center box-img&quot;&gt;
                                    &lt;div class=&quot;col-md-6&quot;&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/images/2021/210820-lhtablet-1.jpg&quot;
                                            alt=&quot;&quot;
                                        /&gt;&lt;br /&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/images/2021/210820-lhtablet-2.jpg&quot;
                                            alt=&quot;&quot;
                                        /&gt;&lt;br /&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/images/2021/210820-lhtablet-3.jpg&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                    &lt;/div&gt;
                                    &lt;div class=&quot;col-md-6&quot;&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/images/2021/210820-lhtablet-4.jpg&quot;
                                            alt=&quot;&quot;
                                        /&gt;&lt;br /&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/images/2021/210820-lhtablet-5.jpg&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                    &lt;/div&gt;
                                &lt;/div&gt;
                            &lt;/div&gt;

                            &lt;!-- 다줌간편청구 --&gt;
                            &lt;div class=&quot;wrap-project&quot;&gt;
                                &lt;img
                                    src=&quot;https://odada-o.github.io//odada/images/2021/210914-dazum-1.jpg&quot;
                                    alt=&quot;&quot;
                                /&gt;
                                &lt;div class=&quot;box-explain&quot;&gt;
                                    &lt;dl&gt;
                                        &lt;dt&gt;작업명&lt;/dt&gt;
                                        &lt;dd&gt;&lt;strong&gt;[반응형] 다줌 간편청구&lt;/strong&gt;&lt;/dd&gt;
                                    &lt;/dl&gt;
                                    &lt;dl&gt;
                                        &lt;dt&gt;작업URL&lt;/dt&gt;
                                        &lt;dd&gt;
                                            &lt;a
                                                href=&quot;https://odada-o.github.io//dazum-easy/&quot;
                                                target=&quot;_blank&quot;
                                                rel=&quot;noopener noreferrer&quot;
                                                &gt;https://odada-o.github.io//dazum-easy/&lt;/a
                                            &gt;
                                        &lt;/dd&gt;
                                        &lt;dt&gt;키워드&lt;/dt&gt;
                                        &lt;dd&gt;#html5 #sass #css3 #javascript #slick&lt;/dd&gt;
                                    &lt;/dl&gt;
                                &lt;/div&gt;
                                &lt;div class=&quot;row text-center box-img&quot;&gt;
                                    &lt;div class=&quot;col-md-6&quot;&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/images/2021/210914-dazum-1.jpg&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                    &lt;/div&gt;
                                    &lt;div class=&quot;col-md-6&quot;&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/images/2021/210914-dazum-3.jpg&quot;
                                            alt=&quot;&quot;
                                        /&gt;&lt;br /&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/images/2021/210914-dazum-4.jpg&quot;
                                            alt=&quot;&quot;
                                        /&gt;&lt;br /&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/images/2021/210914-dazum-5.jpg&quot;
                                            alt=&quot;&quot;
                                        /&gt;&lt;br /&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/images/2021/210914-dazum-6.jpg&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                    &lt;/div&gt;
                                &lt;/div&gt;
                            &lt;/div&gt;

                            &lt;!-- 독서논술이벤트 --&gt;
                            &lt;div class=&quot;wrap-project&quot;&gt;
                                &lt;img
                                    src=&quot;https://odada-o.github.io//odada/images/2021/211018-hb-1.jpg&quot;
                                    alt=&quot;&quot;
                                /&gt;
                                &lt;div class=&quot;box-explain&quot;&gt;
                                    &lt;dl&gt;
                                        &lt;dt&gt;작업명&lt;/dt&gt;
                                        &lt;dd&gt;&lt;strong&gt;[반응형] 해법 독서논술 이벤트&lt;/strong&gt;&lt;/dd&gt;
                                    &lt;/dl&gt;
                                    &lt;dl&gt;
                                        &lt;dt&gt;작업URL&lt;/dt&gt;
                                        &lt;dd&gt;
                                            &lt;a
                                                href=&quot;https://odada-o.github.io//hb-event/&quot;
                                                target=&quot;_blank&quot;
                                                rel=&quot;noopener noreferrer&quot;
                                                &gt;https://odada-o.github.io//hb-event/&lt;/a
                                            &gt;
                                            &gt;
                                        &lt;/dd&gt;
                                        &lt;dt&gt;키워드&lt;/dt&gt;
                                        &lt;dd&gt;#html5 #sass #css3 #jquery #slick&lt;/dd&gt;
                                    &lt;/dl&gt;
                                &lt;/div&gt;
                                &lt;div class=&quot;row text-center box-img&quot;&gt;
                                    &lt;div class=&quot;col-md-7&quot;&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/images/2021/211018-hb-1.jpg&quot;
                                            alt=&quot;&quot;
                                        /&gt;&lt;br /&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/images/2021/211018-hb-3.jpg&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                    &lt;/div&gt;
                                    &lt;div class=&quot;col-md-5&quot;&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/images/2021/211018-hb-2.jpg&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                    &lt;/div&gt;
                                &lt;/div&gt;
                            &lt;/div&gt;

                            &lt;!-- 드림픽쳐스21 --&gt;
                            &lt;div class=&quot;wrap-project&quot;&gt;
                                &lt;img
                                    src=&quot;https://odada-o.github.io//odada/images/2021/210919-dp21.png&quot;
                                    alt=&quot;&quot;
                                /&gt;
                                &lt;div class=&quot;box-explain&quot;&gt;
                                    &lt;dl&gt;
                                        &lt;dt&gt;작업명&lt;/dt&gt;
                                        &lt;dd&gt;&lt;strong&gt;[반응형] 드림픽쳐스21&lt;/strong&gt;&lt;/dd&gt;
                                    &lt;/dl&gt;
                                    &lt;dl&gt;
                                        &lt;dt&gt;작업URL&lt;/dt&gt;
                                        &lt;dd&gt;
                                            &lt;a
                                                href=&quot;https://odada-o.github.io//dp21/&quot;
                                                target=&quot;_blank&quot;
                                                rel=&quot;noopener noreferrer&quot;
                                                &gt;https://odada-o.github.io//dp21/&lt;/a
                                            &gt;
                                        &lt;/dd&gt;
                                        &lt;dt&gt;키워드&lt;/dt&gt;
                                        &lt;dd&gt;#design #html5 #sass #css3 #javascript #slick&lt;/dd&gt;
                                    &lt;/dl&gt;
                                &lt;/div&gt;
                                &lt;div class=&quot;row text-center box-img&quot;&gt;
                                    &lt;div class=&quot;col-md-10 col-md-offset-1&quot;&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/images/2021/210919-dp21.png&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                    &lt;/div&gt;
                                &lt;/div&gt;
                            &lt;/div&gt;

                            &lt;!-- 환경지킴이 복무관리시스템 --&gt;
                            &lt;div class=&quot;wrap-project&quot;&gt;
                                &lt;img
                                    src=&quot;https://odada-o.github.io//odada/images/2021/210518-5rc-1.png&quot;
                                    alt=&quot;환경지킴이 복무관리시스템 이미지&quot;
                                /&gt;

                                &lt;div class=&quot;box-explain&quot;&gt;
                                    &lt;dl&gt;
                                        &lt;dt&gt;작업명&lt;/dt&gt;
                                        &lt;dd&gt;&lt;strong&gt;[PC] 환경지킴이 복무관리시스템&lt;/strong&gt;&lt;/dd&gt;
                                        &lt;!-- &lt;dt&gt;작업영역&lt;/dt&gt;
            &lt;dd&gt;디자인 + 퍼블리싱 + jQuery&lt;/dd&gt;
            &lt;dt&gt;작업기간&lt;/dt&gt;
            &lt;dd&gt;1달&lt;/dd&gt; --&gt;
                                    &lt;/dl&gt;
                                    &lt;dl&gt;
                                        &lt;dt&gt;작업URL&lt;/dt&gt;
                                        &lt;dd&gt;
                                            &lt;a
                                                href=&quot;/work/5rc/s101.html&quot;
                                                target=&quot;_blank&quot;
                                                &gt;https://odada-o.github.io//5rc/s101.html&lt;/a
                                            &gt;
                                        &lt;/dd&gt;
                                        &lt;dt&gt;키워드&lt;/dt&gt;
                                        &lt;dd&gt;#XD #html5 #css3 #jQuery #slick&lt;/dd&gt;
                                    &lt;/dl&gt;
                                &lt;/div&gt;
                                &lt;div class=&quot;row text-center box-img&quot;&gt;
                                    &lt;div class=&quot;col-md-6&quot;&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/images/2021/210518-5rc-1.png&quot;
                                            alt=&quot;환경지킴이 복무관리시스템 이미지&quot;
                                        /&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/images/2021/210518-5rc-2.png&quot;
                                            alt=&quot;환경지킴이 복무관리시스템 이미지&quot;
                                        /&gt;
                                    &lt;/div&gt;
                                    &lt;div class=&quot;col-md-6&quot;&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/images/2021/210518-5rc-3.png&quot;
                                            alt=&quot;환경지킴이 복무관리시스템 이미지&quot;
                                        /&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/images/2021/210518-5rc-4.png&quot;
                                            alt=&quot;환경지킴이 복무관리시스템 이미지&quot;
                                        /&gt;
                                    &lt;/div&gt;
                                &lt;/div&gt;
                            &lt;/div&gt;

                            &lt;!-- 2000n --&gt;
                            &lt;div class=&quot;wrap-project&quot;&gt;
                                &lt;img
                                    src=&quot;https://odada-o.github.io//odada/images/2019/191023-2000n-1.png&quot;
                                    alt=&quot;&quot;
                                /&gt;
                                &lt;div class=&quot;box-explain&quot;&gt;
                                    &lt;dl&gt;
                                        &lt;dt&gt;작업명&lt;/dt&gt;
                                        &lt;dd&gt;&lt;strong&gt;[PC] 이천동행누리센터&lt;/strong&gt;&lt;/dd&gt;
                                        &lt;!-- &lt;dt&gt;작업영역&lt;/dt&gt;
            &lt;dd&gt;디자인 + 퍼블리싱 + jQuery&lt;/dd&gt;
            &lt;dt&gt;작업기간&lt;/dt&gt;
            &lt;dd&gt;1달&lt;/dd&gt; --&gt;
                                    &lt;/dl&gt;
                                    &lt;dl&gt;
                                        &lt;dt&gt;작업URL&lt;/dt&gt;
                                        &lt;dd&gt;
                                            &lt;a
                                                href=&quot;https://www.2000happydream.or.kr/&quot;
                                                target=&quot;_blank&quot;
                                                &gt;https://www.2000happydream.or.kr/&lt;/a
                                            &gt;
                                        &lt;/dd&gt;
                                        &lt;dt&gt;키워드&lt;/dt&gt;
                                        &lt;dd&gt;#웹접근성 #html5 #css3 #jQuery #slick&lt;/dd&gt;
                                    &lt;/dl&gt;
                                &lt;/div&gt;
                                &lt;div class=&quot;row text-center box-img&quot;&gt;
                                    &lt;div class=&quot;col-md-6&quot;&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/images/2019/191023-2000n-1.png&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/images/2019/191023-2000n-3.png&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                    &lt;/div&gt;
                                    &lt;div class=&quot;col-md-6&quot;&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/images/2019/191023-2000n-2.png&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/images/2019/191023-2000n-4.png&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                    &lt;/div&gt;
                                &lt;/div&gt;
                            &lt;/div&gt;

                            &lt;!-- oci --&gt;
                            &lt;div class=&quot;wrap-project&quot;&gt;
                                &lt;img
                                    src=&quot;https://odada-o.github.io//odada/imgs/flato/oci-pc.jpg&quot;
                                    alt=&quot;&quot;
                                /&gt;
                                &lt;div class=&quot;box-explain&quot;&gt;
                                    &lt;dl&gt;
                                        &lt;dt&gt;작업명&lt;/dt&gt;
                                        &lt;dd&gt;&lt;strong&gt;[반응형] OCI&lt;/strong&gt;&lt;/dd&gt;
                                        &lt;!-- &lt;dt&gt;작업영역&lt;/dt&gt;
            &lt;dd&gt;퍼블리싱 + jQuery&lt;/dd&gt;
            &lt;dt&gt;작업기간&lt;/dt&gt;
            &lt;dd&gt;2일&lt;/dd&gt; --&gt;
                                    &lt;/dl&gt;
                                    &lt;dl&gt;
                                        &lt;dt&gt;작업URL&lt;/dt&gt;
                                        &lt;dd&gt;
                                            &lt;a
                                                href=&quot;https://odada-o.github.io//oci/&quot;
                                                target=&quot;_blank&quot;
                                                &gt;https://odada-o.github.io//oci/&lt;/a
                                            &gt;
                                        &lt;/dd&gt;
                                        &lt;dt&gt;키워드&lt;/dt&gt;
                                        &lt;dd&gt;#html5, #css3, #jQuery, #swiper, #css3amimate&lt;/dd&gt;
                                    &lt;/dl&gt;
                                &lt;/div&gt;
                                &lt;div class=&quot;row text-center box-img&quot;&gt;
                                    &lt;div class=&quot;col-sm-8&quot;&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/imgs/flato/oci-pc.jpg&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                    &lt;/div&gt;
                                    &lt;div class=&quot;col-sm-4&quot;&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/imgs/flato/oci-m.jpg&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                    &lt;/div&gt;
                                &lt;/div&gt;
                            &lt;/div&gt;

                            &lt;!-- oci --&gt;
                            &lt;div class=&quot;wrap-project&quot;&gt;
                                &lt;img
                                    src=&quot;https://odada-o.github.io//odada/imgs/flato/kjmbc-pc1.PNG&quot;
                                    alt=&quot;&quot;
                                /&gt;
                                &lt;div class=&quot;box-explain&quot;&gt;
                                    &lt;dl&gt;
                                        &lt;dt&gt;작업명&lt;/dt&gt;
                                        &lt;dd&gt;&lt;strong&gt;[반응형] 광주MBC&lt;/strong&gt;&lt;/dd&gt;
                                        &lt;!-- &lt;dt&gt;작업영역&lt;/dt&gt;
            &lt;dd&gt;퍼블리싱 + jQuery&lt;/dd&gt;
            &lt;dt&gt;작업기간&lt;/dt&gt;
            &lt;dd&gt;8일&lt;/dd&gt; --&gt;
                                    &lt;/dl&gt;
                                    &lt;dl&gt;
                                        &lt;dt&gt;작업URL&lt;/dt&gt;
                                        &lt;dd&gt;
                                            &lt;a
                                                href=&quot;https://odada-o.github.io//kjmbc/&quot;
                                                target=&quot;_blank&quot;
                                                &gt;https://odada-o.github.io//kjmbc/&lt;/a
                                            &gt;
                                        &lt;/dd&gt;
                                        &lt;dt&gt;키워드&lt;/dt&gt;
                                        &lt;dd&gt;#html5, #css3, #jQuery, #swiper, #css3amimate&lt;/dd&gt;
                                    &lt;/dl&gt;
                                &lt;/div&gt;
                                &lt;div class=&quot;row text-center box-img&quot;&gt;
                                    &lt;div class=&quot;col-sm-8&quot;&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/imgs/flato/kjmbc-pc1.PNG&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/imgs/flato/kjmbc-pc2.PNG&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                    &lt;/div&gt;
                                    &lt;div class=&quot;col-sm-4&quot;&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/imgs/flato/kjmbc-m1.PNG&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/imgs/flato/kjmbc-m2.PNG&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                    &lt;/div&gt;
                                &lt;/div&gt;
                            &lt;/div&gt;

                            &lt;!-- 벽초지수목원 --&gt;
                            &lt;div class=&quot;wrap-project&quot;&gt;
                                &lt;img
                                    src=&quot;https://odada-o.github.io//odada/imgs/flato/bcj-pc1.jpg&quot;
                                    alt=&quot;&quot;
                                /&gt;
                                &lt;div class=&quot;box-explain&quot;&gt;
                                    &lt;dl&gt;
                                        &lt;dt&gt;작업명&lt;/dt&gt;
                                        &lt;dd&gt;&lt;strong&gt;[반응형] 벽초지수목원&lt;/strong&gt;&lt;/dd&gt;
                                        &lt;!-- &lt;dt&gt;작업영역&lt;/dt&gt;
            &lt;dd&gt;퍼블리싱 + jQuery&lt;/dd&gt;
            &lt;dt&gt;작업기간&lt;/dt&gt;
            &lt;dd&gt;2주&lt;/dd&gt; --&gt;
                                    &lt;/dl&gt;
                                    &lt;dl&gt;
                                        &lt;dt&gt;작업URL&lt;/dt&gt;
                                        &lt;dd&gt;
                                            &lt;a
                                                href=&quot;http://www.bcj.co.kr/&quot;
                                                target=&quot;_blank&quot;
                                                &gt;http://www.bcj.co.kr/&lt;/a
                                            &gt;
                                        &lt;/dd&gt;
                                        &lt;dt&gt;키워드&lt;/dt&gt;
                                        &lt;dd&gt;#html5, #css3, #jQuery, #swiper, #css3amimate&lt;/dd&gt;
                                    &lt;/dl&gt;
                                &lt;/div&gt;
                                &lt;div class=&quot;row text-center box-img&quot;&gt;
                                    &lt;div class=&quot;col-sm-6&quot;&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/imgs/flato/bcj-pc1.jpg&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                    &lt;/div&gt;
                                    &lt;div class=&quot;col-sm-6&quot;&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/imgs/flato/bcj-pc2.jpg&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/imgs/flato/bcj-pc3.jpg&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                    &lt;/div&gt;
                                &lt;/div&gt;
                            &lt;/div&gt;

                            &lt;!-- 현재인재개발센터 --&gt;
                            &lt;div class=&quot;wrap-project&quot;&gt;
                                &lt;img
                                    src=&quot;https://odada-o.github.io//odada/imgs/flato/hyunhai.jpg&quot;
                                    alt=&quot;&quot;
                                /&gt;
                                &lt;div class=&quot;box-explain&quot;&gt;
                                    &lt;dl&gt;
                                        &lt;dt&gt;작업명&lt;/dt&gt;
                                        &lt;dd&gt;&lt;strong&gt;[PC] 현대인재개발센터&lt;/strong&gt;&lt;/dd&gt;
                                        &lt;!-- &lt;dt&gt;작업영역&lt;/dt&gt;
            &lt;dd&gt;퍼블리싱 + jQuery&lt;/dd&gt;
            &lt;dt&gt;작업기간&lt;/dt&gt;
            &lt;dd&gt;&lt;/dd&gt; --&gt;
                                    &lt;/dl&gt;
                                    &lt;dl&gt;
                                        &lt;dt&gt;작업URL&lt;/dt&gt;
                                        &lt;dd&gt;
                                            &lt;a
                                                href=&quot;https://odada-o.github.io//hyundaihrd/&quot;
                                                target=&quot;_blank&quot;
                                                &gt;https://odada-o.github.io//hyundaihrd/&lt;/a
                                            &gt;
                                        &lt;/dd&gt;
                                        &lt;dt&gt;키워드&lt;/dt&gt;
                                        &lt;dd&gt;#html5, #css3, #jQuery, #swiper, #css3amimate&lt;/dd&gt;
                                    &lt;/dl&gt;
                                &lt;/div&gt;
                                &lt;div class=&quot;row text-center box-img&quot;&gt;
                                    &lt;div class=&quot;col-sm-12&quot;&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/imgs/flato/hyunhai.jpg&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                    &lt;/div&gt;
                                &lt;/div&gt;
                            &lt;/div&gt;

                            &lt;!-- 수원화성 --&gt;
                            &lt;div class=&quot;wrap-project&quot;&gt;
                                &lt;img
                                    src=&quot;https://odada-o.github.io//odada/imgs/flato/suwon1.jpg&quot;
                                    alt=&quot;&quot;
                                /&gt;
                                &lt;div class=&quot;box-explain&quot;&gt;
                                    &lt;dl&gt;
                                        &lt;dt&gt;작업명&lt;/dt&gt;
                                        &lt;dd&gt;&lt;strong&gt;[반응형] 수원화성 군공항이전사업&lt;/strong&gt;&lt;/dd&gt;
                                        &lt;!-- &lt;dt&gt;작업영역&lt;/dt&gt;
            &lt;dd&gt;웹접근성 + 퍼블리싱 + jQuery&lt;/dd&gt;
            &lt;dt&gt;작업기간&lt;/dt&gt;
            &lt;dd&gt;&lt;/dd&gt; --&gt;
                                    &lt;/dl&gt;
                                    &lt;dl&gt;
                                        &lt;dt&gt;작업URL&lt;/dt&gt;
                                        &lt;dd&gt;
                                            &lt;a
                                                href=&quot;https://odada-o.github.io//suwon/&quot;
                                                target=&quot;_blank&quot;
                                                &gt;https://odada-o.github.io//suwon/&lt;/a
                                            &gt;
                                        &lt;/dd&gt;
                                        &lt;dt&gt;키워드&lt;/dt&gt;
                                        &lt;dd&gt;#html5, #css3, #jQuery, #swiper, #fullpage&lt;/dd&gt;
                                    &lt;/dl&gt;
                                &lt;/div&gt;
                                &lt;div class=&quot;row text-center box-img&quot;&gt;
                                    &lt;div class=&quot;col-sm-6&quot;&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/imgs/flato/suwon1.jpg&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                    &lt;/div&gt;
                                    &lt;div class=&quot;col-sm-6&quot;&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/imgs/flato/suwon2.jpg&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                    &lt;/div&gt;
                                    &lt;div class=&quot;col-sm-6&quot;&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/imgs/flato/suwon3.jpg&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                    &lt;/div&gt;
                                    &lt;div class=&quot;col-sm-6&quot;&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/imgs/flato/suwon4.jpg&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                    &lt;/div&gt;
                                &lt;/div&gt;
                            &lt;/div&gt;

                            &lt;!-- POA 국제식물검역인증원 --&gt;
                            &lt;div class=&quot;wrap-project&quot;&gt;
                                &lt;img
                                    src=&quot;https://odada-o.github.io//odada/imgs/flato/agm-pc.png&quot;
                                    alt=&quot;&quot;
                                /&gt;
                                &lt;div class=&quot;box-explain&quot;&gt;
                                    &lt;dl&gt;
                                        &lt;dt&gt;작업명&lt;/dt&gt;
                                        &lt;dd&gt;&lt;strong&gt;[반응형] POA 국제식물검역인증원 인터랙션&lt;/strong&gt;&lt;/dd&gt;
                                        &lt;!-- &lt;dt&gt;작업영역&lt;/dt&gt;
            &lt;dd&gt;GNB, SNB, MAP 퍼블리싱 + jQuery&lt;/dd&gt;
            &lt;dt&gt;작업기간&lt;/dt&gt;
            &lt;dd&gt;&lt;/dd&gt; --&gt;
                                    &lt;/dl&gt;
                                    &lt;dl&gt;
                                        &lt;dt&gt;작업URL&lt;/dt&gt;
                                        &lt;dd&gt;
                                            &lt;a
                                                href=&quot;https://www.ipab.or.kr/&quot;
                                                target=&quot;_blank&quot;
                                                &gt;https://www.ipab.or.kr/&lt;/a
                                            &gt;
                                        &lt;/dd&gt;
                                        &lt;dt&gt;키워드&lt;/dt&gt;
                                        &lt;dd&gt;#html, #css, #반응형, #jQuery, #웹표준&lt;/dd&gt;
                                    &lt;/dl&gt;
                                &lt;/div&gt;
                                &lt;div class=&quot;row text-center box-img&quot;&gt;
                                    &lt;div class=&quot;col-sm-6&quot;&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/imgs/flato/agm-pc.png&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                    &lt;/div&gt;
                                    &lt;div class=&quot;col-sm-6&quot;&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/imgs/flato/agm-pc-nav.png&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                    &lt;/div&gt;
                                    &lt;div class=&quot;col-sm-4&quot;&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/imgs/flato/agm-m.png&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                    &lt;/div&gt;
                                    &lt;div class=&quot;col-sm-4&quot;&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/imgs/flato/agm-m-nav.png&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                    &lt;/div&gt;
                                    &lt;div class=&quot;col-sm-12&quot;&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/imgs/flato/poa2.png&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                    &lt;/div&gt;
                                &lt;/div&gt;
                            &lt;/div&gt;

                            &lt;!-- Bullbull --&gt;
                            &lt;div class=&quot;wrap-project&quot;&gt;
                                &lt;img
                                    src=&quot;https://odada-o.github.io//odada/imgs/flato/bullbull-pc.png&quot;
                                    alt=&quot;&quot;
                                /&gt;
                                &lt;div class=&quot;box-explain&quot;&gt;
                                    &lt;dl&gt;
                                        &lt;dt&gt;작업명&lt;/dt&gt;
                                        &lt;dd&gt;&lt;strong&gt;[반응형] Bullbull&lt;/strong&gt;&lt;/dd&gt;
                                        &lt;!-- &lt;dt&gt;작업영역&lt;/dt&gt;
            &lt;dd&gt;디자인 + 퍼블리싱&lt;/dd&gt;
            &lt;dt&gt;작업기간&lt;/dt&gt;
            &lt;dd&gt;1주&lt;/dd&gt; --&gt;
                                    &lt;/dl&gt;
                                    &lt;dl&gt;
                                        &lt;dt&gt;작업URL&lt;/dt&gt;
                                        &lt;dd&gt;
                                            &lt;a
                                                href=&quot;https://odada-o.github.io//bullbull&quot;
                                                target=&quot;_blank&quot;
                                                &gt;https://odada-o.github.io//bullbull/&lt;/a
                                            &gt;
                                        &lt;/dd&gt;
                                        &lt;dt&gt;키워드&lt;/dt&gt;
                                        &lt;dd&gt;#Photoshop, #html, #css, #반응형, #jQuery, #bootstrap&lt;/dd&gt;
                                    &lt;/dl&gt;
                                &lt;/div&gt;
                                &lt;div class=&quot;row text-center box-img&quot;&gt;
                                    &lt;div class=&quot;col-sm-4&quot;&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/imgs/flato/bullbull-pc.png&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                    &lt;/div&gt;
                                    &lt;div class=&quot;col-sm-4&quot;&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/imgs/flato/bullbull-m1.png&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                    &lt;/div&gt;
                                    &lt;div class=&quot;col-sm-4&quot;&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/imgs/flato/bullbull-m2.png&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                    &lt;/div&gt;
                                &lt;/div&gt;
                            &lt;/div&gt;

                            &lt;!-- 온라인MICE연수원 --&gt;
                            &lt;div class=&quot;wrap-project&quot;&gt;
                                &lt;img
                                    src=&quot;https://odada-o.github.io//odada/imgs/flato/mice_pc.jpg&quot;
                                    alt=&quot;&quot;
                                /&gt;
                                &lt;div class=&quot;box-explain&quot;&gt;
                                    &lt;dl&gt;
                                        &lt;dt&gt;작업명&lt;/dt&gt;
                                        &lt;dd&gt;&lt;strong&gt;[반응형] 온라인MICE연수원&lt;/strong&gt;&lt;/dd&gt;
                                        &lt;!-- &lt;dt&gt;작업영역&lt;/dt&gt;
            &lt;dd&gt;디자인 + 퍼블리싱&lt;/dd&gt;
            &lt;dt&gt;작업기간&lt;/dt&gt;
            &lt;dd&gt;2주&lt;/dd&gt; --&gt;
                                    &lt;/dl&gt;
                                    &lt;dl&gt;
                                        &lt;dt&gt;작업URL&lt;/dt&gt;
                                        &lt;dd&gt;
                                            &lt;a
                                                href=&quot;http://5gamm.co.kr/!list.html&quot;
                                                target=&quot;_blank&quot;
                                                &gt;http://5gamm.co.kr/!list.html&lt;/a
                                            &gt;
                                        &lt;/dd&gt;
                                        &lt;dt&gt;키워드&lt;/dt&gt;
                                        &lt;dd&gt;#Photoshop, #Illustrator, #html, #css, #반응형, #jQuery&lt;/dd&gt;
                                    &lt;/dl&gt;
                                &lt;/div&gt;
                                &lt;div class=&quot;row text-center box-img&quot;&gt;
                                    &lt;div class=&quot;col-sm-4&quot;&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/imgs/flato/mice_pc.jpg&quot;
                                            alt=&quot;&quot;
                                        /&gt;&lt;img
                                            src=&quot;https://odada-o.github.io//odada/imgs/flato/mice_m.jpg&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                    &lt;/div&gt;
                                    &lt;div class=&quot;col-sm-4&quot;&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/imgs/flato/mice_pc_sub2.png&quot;
                                            alt=&quot;&quot;
                                        /&gt;&lt;img
                                            src=&quot;https://odada-o.github.io//odada/imgs/flato/mice_m_sub2.png&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                    &lt;/div&gt;
                                    &lt;div class=&quot;col-sm-4&quot;&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/imgs/flato/mice_pc_sub.png&quot;
                                            alt=&quot;&quot;
                                        /&gt;&lt;img
                                            src=&quot;https://odada-o.github.io//odada/imgs/flato/mice_m_sub.png&quot;
                                            alt=&quot;&quot;
                                        /&gt;&lt;img
                                            src=&quot;https://odada-o.github.io//odada/imgs/flato/mice_m_sub3.png&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                    &lt;/div&gt;
                                &lt;/div&gt;
                            &lt;/div&gt;
                        &lt;/div&gt;
                    &lt;/div&gt;

                    &lt;!-- daum --&gt;
                    &lt;div
                        class=&quot;tab-pane fade&quot;
                        id=&quot;daum&quot;
                        role=&quot;tabpanel&quot;
                        aria-labelledby=&quot;daum-tab&quot;
                    &gt;
                        &lt;h2&gt;dk techin&lt;/h2&gt;
                        &lt;div class=&quot;wrap-explain&quot;&gt;
                            &lt;div class=&quot;wrap-project&quot;&gt;
                                &lt;img
                                    src=&quot;https://odada-o.github.io//odada/imgs/daumkakao/m_kakao_gift0.png&quot;
                                    class=&quot;img_thumb&quot;
                                    alt=&quot;&quot;
                                /&gt;
                                &lt;div class=&quot;box-explain&quot;&gt;
                                    &lt;dl&gt;
                                        &lt;dt&gt;작업명&lt;/dt&gt;
                                        &lt;dd&gt;&lt;strong&gt;M) kakao 선물하기&lt;/strong&gt;&lt;/dd&gt;
                                        &lt;dt&gt;작업영역&lt;/dt&gt;
                                        &lt;dd&gt;퍼블리싱&lt;/dd&gt;
                                    &lt;/dl&gt;
                                    &lt;dl&gt;
                                        &lt;dt&gt;작업 URL&lt;/dt&gt;
                                        &lt;dd&gt;
                                            &lt;strong
                                                &gt;&lt;a
                                                    href=&quot;https://www.kakaocorp.com/service/KakaoGift&quot;
                                                    target=&quot;_blank&quot;
                                                    &gt;https://www.kakaocorp.com/service/KakaoGift&lt;/a
                                                &gt;&lt;/strong
                                            &gt;
                                        &lt;/dd&gt;
                                    &lt;/dl&gt;
                                &lt;/div&gt;
                                &lt;div class=&quot;row text-center box-img&quot;&gt;
                                    &lt;div class=&quot;col-sm-4&quot;&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/imgs/daumkakao/m_kakao_gift0.png&quot;
                                            class=&quot;img_thumb&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                    &lt;/div&gt;
                                    &lt;div class=&quot;col-sm-4&quot;&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/imgs/daumkakao/m_kakao_gift1.png&quot;
                                            class=&quot;img_thumb&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                    &lt;/div&gt;
                                    &lt;div class=&quot;col-sm-4&quot;&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/imgs/daumkakao/m_kakao_gift2.png&quot;
                                            class=&quot;img_thumb&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                    &lt;/div&gt;
                                &lt;/div&gt;
                            &lt;/div&gt;

                            &lt;div class=&quot;wrap-project&quot;&gt;
                                &lt;img
                                    src=&quot;https://odada-o.github.io//odada/imgs/daumkakao/pc_1001.png&quot;
                                    class=&quot;img_thumb&quot;
                                    alt=&quot;&quot;
                                /&gt;

                                &lt;div class=&quot;box-explain&quot;&gt;
                                    &lt;dl&gt;
                                        &lt;dt&gt;작업명&lt;/dt&gt;
                                        &lt;dd&gt;&lt;strong&gt;PC) Daum 백과&lt;/strong&gt;&lt;/dd&gt;
                                        &lt;dt&gt;작업영역&lt;/dt&gt;
                                        &lt;dd&gt;퍼블리싱&lt;/dd&gt;
                                    &lt;/dl&gt;
                                    &lt;dl&gt;
                                        &lt;dt&gt;작업URL&lt;/dt&gt;
                                        &lt;dd&gt;
                                            &lt;a
                                                href=&quot;http://100.daum.net/&quot;
                                                target=&quot;_blank&quot;
                                                &gt;http://100.daum.net/&lt;/a
                                            &gt;
                                        &lt;/dd&gt;
                                    &lt;/dl&gt;
                                &lt;/div&gt;
                                &lt;div class=&quot;row text-center box-img&quot;&gt;
                                    &lt;div class=&quot;col-sm-6&quot;&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/imgs/daumkakao/pc_1001.png&quot;
                                            class=&quot;img_thumb&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                    &lt;/div&gt;
                                    &lt;div class=&quot;col-sm-6&quot;&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/imgs/daumkakao/pc_1002.png&quot;
                                            class=&quot;img_thumb&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                    &lt;/div&gt;
                                &lt;/div&gt;
                            &lt;/div&gt;

                            &lt;div class=&quot;wrap-project&quot;&gt;
                                &lt;img
                                    src=&quot;https://odada-o.github.io//odada/imgs/daumkakao/m_webtoon1_1.png&quot;
                                    class=&quot;img_thumb&quot;
                                    alt=&quot;&quot;
                                /&gt;
                                &lt;div class=&quot;box-explain&quot;&gt;
                                    &lt;dl&gt;
                                        &lt;dt&gt;작업명&lt;/dt&gt;
                                        &lt;dd&gt;&lt;strong&gt;M) Daum 웹툰&lt;/strong&gt;&lt;/dd&gt;
                                        &lt;dt&gt;작업영역&lt;/dt&gt;
                                        &lt;dd&gt;퍼블리싱&lt;/dd&gt;
                                    &lt;/dl&gt;
                                    &lt;dl&gt;
                                        &lt;dt&gt;작업URL&lt;/dt&gt;
                                        &lt;dd&gt;
                                            &lt;a
                                                href=&quot;http://m.webtoon.daum.net/m/&quot;
                                                target=&quot;_blank&quot;
                                                &gt;http://m.webtoon.daum.net/m/&lt;/a
                                            &gt;
                                        &lt;/dd&gt;
                                    &lt;/dl&gt;
                                &lt;/div&gt;
                                &lt;div class=&quot;row text-center box-img&quot;&gt;
                                    &lt;div class=&quot;col-sm-4&quot;&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/imgs/daumkakao/m_webtoon1_1.png&quot;
                                            class=&quot;img_thumb&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                    &lt;/div&gt;
                                    &lt;div class=&quot;col-sm-4&quot;&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/imgs/daumkakao/m_webtoon1_2.png&quot;
                                            class=&quot;img_thumb&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                    &lt;/div&gt;
                                    &lt;div class=&quot;col-sm-4&quot;&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/imgs/daumkakao/m_webtoon1_3.png&quot;
                                            class=&quot;img_thumb&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                    &lt;/div&gt;
                                &lt;/div&gt;
                            &lt;/div&gt;

                            &lt;div class=&quot;wrap-project&quot;&gt;
                                &lt;img
                                    src=&quot;https://odada-o.github.io//odada/imgs/daumkakao/m_music1.png&quot;
                                    class=&quot;img_thumb&quot;
                                    alt=&quot;&quot;
                                /&gt;
                                &lt;div class=&quot;box-explain&quot;&gt;
                                    &lt;dl&gt;
                                        &lt;dt&gt;작업명&lt;/dt&gt;
                                        &lt;dd&gt;&lt;strong&gt;App) Daum 뮤직&lt;/strong&gt;&lt;/dd&gt;
                                        &lt;dt&gt;작업영역&lt;/dt&gt;
                                        &lt;dd&gt;퍼블리싱&lt;/dd&gt;
                                    &lt;/dl&gt;
                                    &lt;dl&gt;
                                        &lt;dt&gt;작업URL&lt;/dt&gt;
                                        &lt;dd&gt;
                                            &lt;a
                                                href=&quot;http://m.music.daum.net/&quot;
                                                target=&quot;_blank&quot;
                                                &gt;http://m.music.daum.net/&lt;/a
                                            &gt;
                                        &lt;/dd&gt;
                                    &lt;/dl&gt;
                                &lt;/div&gt;
                                &lt;div class=&quot;row text-center box-img&quot;&gt;
                                    &lt;div class=&quot;col-sm-2&quot;&gt;&lt;/div&gt;
                                    &lt;div class=&quot;col-sm-4&quot;&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/imgs/daumkakao/m_music1.png&quot;
                                            class=&quot;img_thumb&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                    &lt;/div&gt;
                                    &lt;div class=&quot;col-sm-4&quot;&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/imgs/daumkakao/m_music2.png&quot;
                                            class=&quot;img_thumb&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                    &lt;/div&gt;
                                    &lt;div class=&quot;col-sm-2&quot;&gt;&lt;/div&gt;
                                &lt;/div&gt;
                            &lt;/div&gt;

                            &lt;div class=&quot;wrap-project&quot;&gt;
                                &lt;img
                                    src=&quot;https://odada-o.github.io//odada/imgs/daumkakao/m_fortune1_1.png&quot;
                                    class=&quot;img_thumb&quot;
                                    alt=&quot;&quot;
                                /&gt;
                                &lt;div class=&quot;box-explain&quot;&gt;
                                    &lt;dl&gt;
                                        &lt;dt&gt;작업명&lt;/dt&gt;
                                        &lt;dd&gt;&lt;strong&gt;M) Daum 운세&lt;/strong&gt;&lt;/dd&gt;
                                        &lt;dt&gt;작업영역&lt;/dt&gt;
                                        &lt;dd&gt;퍼블리싱&lt;/dd&gt;
                                    &lt;/dl&gt;
                                    &lt;dl&gt;
                                        &lt;dt&gt;작업URL&lt;/dt&gt;
                                        &lt;dd&gt;
                                            &lt;a
                                                href=&quot;http://m.fortune.daum.net/&quot;
                                                target=&quot;_blank&quot;
                                                &gt;http://m.fortune.daum.net/&lt;/a
                                            &gt;
                                        &lt;/dd&gt;
                                    &lt;/dl&gt;
                                &lt;/div&gt;
                                &lt;div class=&quot;row text-center box-img&quot;&gt;
                                    &lt;div class=&quot;col-sm-2&quot;&gt;&lt;/div&gt;
                                    &lt;div class=&quot;col-sm-4&quot;&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/imgs/daumkakao/m_fortune1_1.png&quot;
                                            class=&quot;img_thumb&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                    &lt;/div&gt;
                                    &lt;div class=&quot;col-sm-4&quot;&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/imgs/daumkakao/m_fortune1_2.jpg&quot;
                                            class=&quot;img_thumb&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                    &lt;/div&gt;
                                    &lt;div class=&quot;col-sm-2&quot;&gt;&lt;/div&gt;
                                &lt;/div&gt;
                            &lt;/div&gt;

                            &lt;div class=&quot;wrap-project&quot;&gt;
                                &lt;img
                                    src=&quot;https://odada-o.github.io//odada/imgs/daumkakao/pc_kakao_mobilead0.png&quot;
                                    class=&quot;img_thumb&quot;
                                    alt=&quot;&quot;
                                /&gt;
                                &lt;div class=&quot;box-explain&quot;&gt;
                                    &lt;dl&gt;
                                        &lt;dt&gt;작업명&lt;/dt&gt;
                                        &lt;dd&gt;&lt;strong&gt;PC) kakao Mobile AD&lt;/strong&gt;&lt;/dd&gt;
                                        &lt;dt&gt;작업영역&lt;/dt&gt;
                                        &lt;dd&gt;퍼블리싱&lt;/dd&gt;
                                    &lt;/dl&gt;
                                    &lt;dl&gt;
                                        &lt;dt&gt;작업URL&lt;/dt&gt;
                                        &lt;dd&gt;
                                            &lt;a
                                                href=&quot;http://ads.kakao.com/&quot;
                                                target=&quot;_blank&quot;
                                                &gt;http://ads.kakao.com/&lt;/a
                                            &gt;
                                        &lt;/dd&gt;
                                    &lt;/dl&gt;
                                &lt;/div&gt;
                                &lt;div class=&quot;row text-center box-img&quot;&gt;
                                    &lt;div class=&quot;col-sm-6&quot;&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/imgs/daumkakao/pc_kakao_mobilead0.png&quot;
                                            class=&quot;img_thumb&quot;
                                            alt=&quot;&quot;
                                        /&gt;&lt;img
                                            src=&quot;imgs/daumkakao/pc_kakao_mobilead1.png&quot;
                                            class=&quot;img_thumb&quot;
                                            alt=&quot;&quot;
                                        /&gt;&lt;img
                                            src=&quot;https://odada-o.github.io//odada/imgs/daumkakao/pc_kakao_mobilead2.png&quot;
                                            class=&quot;img_thumb&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                    &lt;/div&gt;
                                    &lt;div class=&quot;col-sm-6&quot;&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/imgs/daumkakao/pc_kakao_mobilead3.png&quot;
                                            class=&quot;img_thumb&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                    &lt;/div&gt;
                                &lt;/div&gt;
                            &lt;/div&gt;

                            &lt;div class=&quot;wrap-project&quot;&gt;
                                &lt;img
                                    src=&quot;https://odada-o.github.io//odada/imgs/daumkakao/pc_tistory.png&quot;
                                    class=&quot;img_thumb&quot;
                                    alt=&quot;&quot;
                                /&gt;
                                &lt;div class=&quot;box-explain&quot;&gt;
                                    &lt;dl&gt;
                                        &lt;dt&gt;작업명&lt;/dt&gt;
                                        &lt;dd&gt;&lt;strong&gt;PC) TISTORY 관리&lt;/strong&gt;&lt;/dd&gt;
                                        &lt;dt&gt;작업영역&lt;/dt&gt;
                                        &lt;dd&gt;퍼블리싱&lt;/dd&gt;
                                    &lt;/dl&gt;
                                    &lt;dl&gt;
                                        &lt;dt&gt;작업URL&lt;/dt&gt;
                                        &lt;dd&gt;
                                            &lt;a
                                                href=&quot;https://www.tistory.com/&quot;
                                                target=&quot;_blank&quot;
                                                &gt;https://www.tistory.com/&lt;/a
                                            &gt;
                                        &lt;/dd&gt;
                                    &lt;/dl&gt;
                                &lt;/div&gt;
                                &lt;div class=&quot;row text-center box-img&quot;&gt;
                                    &lt;div class=&quot;col-sm-2&quot;&gt;&lt;/div&gt;
                                    &lt;div class=&quot;col-sm-8&quot;&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/imgs/daumkakao/pc_tistory.png&quot;
                                            class=&quot;img_thumb&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                    &lt;/div&gt;
                                    &lt;div class=&quot;col-sm-2&quot;&gt;&lt;/div&gt;
                                &lt;/div&gt;
                            &lt;/div&gt;

                            &lt;div class=&quot;wrap-project&quot;&gt;
                                &lt;img
                                    src=&quot;https://odada-o.github.io//odada/imgs/daumkakao/pc_disney1.png&quot;
                                    class=&quot;img_thumb&quot;
                                    alt=&quot;&quot;
                                /&gt;

                                &lt;div class=&quot;box-explain&quot;&gt;
                                    &lt;dl&gt;
                                        &lt;dt&gt;작업명&lt;/dt&gt;
                                        &lt;dd&gt;&lt;strong&gt;PC) 키즈짱 디즈니존&lt;/strong&gt;&lt;/dd&gt;
                                        &lt;dt&gt;작업영역&lt;/dt&gt;
                                        &lt;dd&gt;퍼블리싱&lt;/dd&gt;
                                    &lt;/dl&gt;
                                    &lt;dl&gt;
                                        &lt;dt&gt;작업URL&lt;/dt&gt;
                                        &lt;dd&gt;
                                            &lt;!--&lt;a href=&quot;http://ads.kakao.com/&quot; target=&quot;_blank&quot;&gt;http://ads.kakao.com/&lt;/a&gt;--&gt;
                                        &lt;/dd&gt;
                                    &lt;/dl&gt;
                                &lt;/div&gt;
                                &lt;div class=&quot;row text-center box-img&quot;&gt;
                                    &lt;div class=&quot;col-sm-6&quot;&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/imgs/daumkakao/pc_disney1.png&quot;
                                            class=&quot;img_thumb&quot;
                                            alt=&quot;&quot;
                                        /&gt;&lt;img
                                            src=&quot;imgs/daumkakao/pc_disney3.png&quot;
                                            class=&quot;img_thumb&quot;
                                            alt=&quot;&quot;
                                        /&gt;&lt;img
                                            src=&quot;https://odada-o.github.io//odada/imgs/daumkakao/pc_disney5.png&quot;
                                            class=&quot;img_thumb&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                    &lt;/div&gt;
                                    &lt;div class=&quot;col-sm-6&quot;&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/imgs/daumkakao/pc_disney2.png&quot;
                                            class=&quot;img_thumb&quot;
                                            alt=&quot;&quot;
                                        /&gt;&lt;img
                                            src=&quot;imgs/daumkakao/pc_disney4.png&quot;
                                            class=&quot;img_thumb&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                    &lt;/div&gt;
                                &lt;/div&gt;
                            &lt;/div&gt;

                            &lt;div class=&quot;wrap-project&quot;&gt;
                                &lt;img
                                    src=&quot;https://odada-o.github.io//odada/imgs/daumkakao/pc_event_tour.jpg&quot;
                                    class=&quot;img_thumb&quot;
                                    alt=&quot;&quot;
                                /&gt;
                                &lt;div class=&quot;box-explain&quot;&gt;
                                    &lt;dl&gt;
                                        &lt;dt&gt;작업명&lt;/dt&gt;
                                        &lt;dd&gt;&lt;strong&gt;PC) 라이프 솔루션&lt;/strong&gt;&lt;/dd&gt;
                                        &lt;dt&gt;작업영역&lt;/dt&gt;
                                        &lt;dd&gt;퍼블리싱&lt;/dd&gt;
                                    &lt;/dl&gt;
                                    &lt;dl&gt;
                                        &lt;dt&gt;작업URL&lt;/dt&gt;
                                        &lt;dd&gt;
                                            &lt;!--&lt;a href=&quot;http://ads.kakao.com/&quot; target=&quot;_blank&quot;&gt;http://ads.kakao.com/&lt;/a&gt;--&gt;
                                        &lt;/dd&gt;
                                    &lt;/dl&gt;
                                &lt;/div&gt;
                                &lt;div class=&quot;row text-center box-img&quot;&gt;
                                    &lt;div class=&quot;col-sm-3&quot;&gt;&lt;/div&gt;
                                    &lt;div class=&quot;col-sm-6&quot;&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/imgs/daumkakao/pc_event_tour.jpg&quot;
                                            class=&quot;img_thumb&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                    &lt;/div&gt;
                                    &lt;div class=&quot;col-sm-3&quot;&gt;&lt;/div&gt;
                                &lt;/div&gt;
                            &lt;/div&gt;

                            &lt;div class=&quot;wrap-project&quot;&gt;
                                &lt;img
                                    src=&quot;https://odada-o.github.io//odada/imgs/daumkakao/pc_kakaopay1.png&quot;
                                    class=&quot;img_thumb&quot;
                                    alt=&quot;&quot;
                                /&gt;
                                &lt;div class=&quot;box-explain&quot;&gt;
                                    &lt;dl&gt;
                                        &lt;dt&gt;작업명&lt;/dt&gt;
                                        &lt;dd&gt;&lt;strong&gt;M,PC) kakaopay event&lt;/strong&gt;&lt;/dd&gt;
                                        &lt;dt&gt;작업영역&lt;/dt&gt;
                                        &lt;dd&gt;퍼블리싱&lt;/dd&gt;
                                    &lt;/dl&gt;
                                    &lt;dl&gt;
                                        &lt;dt&gt;작업URL&lt;/dt&gt;
                                        &lt;dd&gt;
                                            &lt;a
                                                href=&quot;daumkakao/pc_kakaopay.html&quot;
                                                class=&quot;link_url&quot;
                                                target=&quot;_blank&quot;
                                                &gt;daumkakao/pc_kakaopay.html&lt;/a
                                            &gt;
                                        &lt;/dd&gt;
                                    &lt;/dl&gt;
                                &lt;/div&gt;
                                &lt;div class=&quot;row text-center box-img mb0&quot;&gt;
                                    &lt;div class=&quot;col-sm-6&quot;&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/imgs/daumkakao/pc_kakaopay1.png&quot;
                                            class=&quot;img_thumb&quot;
                                            alt=&quot;&quot;
                                        /&gt;&lt;img
                                            src=&quot;imgs/daumkakao/pc_kakaopay3.png&quot;
                                            class=&quot;img_thumb&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                    &lt;/div&gt;
                                    &lt;div class=&quot;col-sm-6&quot;&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/imgs/daumkakao/pc_kakaopay2.png&quot;
                                            class=&quot;img_thumb&quot;
                                            alt=&quot;&quot;
                                        /&gt;&lt;img
                                            src=&quot;imgs/daumkakao/pc_kakaopay4.png&quot;
                                            class=&quot;img_thumb&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                    &lt;/div&gt;
                                &lt;/div&gt;
                                &lt;div class=&quot;row text-center box-img&quot;&gt;
                                    &lt;div class=&quot;col-sm-4&quot;&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/imgs/daumkakao/pay_evt4.png&quot;
                                            class=&quot;img_thumb&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                    &lt;/div&gt;
                                    &lt;div class=&quot;col-sm-4&quot;&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/imgs/daumkakao/pay_evt5.png&quot;
                                            class=&quot;img_thumb&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                    &lt;/div&gt;
                                    &lt;div class=&quot;col-sm-4&quot;&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/imgs/daumkakao/pay_evt6.png&quot;
                                            class=&quot;img_thumb&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                    &lt;/div&gt;
                                &lt;/div&gt;
                            &lt;/div&gt;

                            &lt;div class=&quot;wrap-project&quot;&gt;
                                &lt;img
                                    src=&quot;https://odada-o.github.io//odada/imgs/daumkakao/m_event_music.png&quot;
                                    class=&quot;img_thumb&quot;
                                    alt=&quot;&quot;
                                /&gt;
                                &lt;div class=&quot;box-explain&quot;&gt;
                                    &lt;dl&gt;
                                        &lt;dt&gt;작업명&lt;/dt&gt;
                                        &lt;dd&gt;&lt;strong&gt;M) EVENT&lt;/strong&gt;&lt;/dd&gt;
                                        &lt;dt&gt;작업영역&lt;/dt&gt;
                                        &lt;dd&gt;퍼블리싱&lt;/dd&gt;
                                    &lt;/dl&gt;
                                    &lt;dl&gt;
                                        &lt;dt&gt;작업URL&lt;/dt&gt;
                                        &lt;dd&gt;
                                            &lt;!--&lt;a href=&quot;http://ads.kakao.com/&quot; target=&quot;_blank&quot;&gt;http://ads.kakao.com/&lt;/a&gt;--&gt;
                                        &lt;/dd&gt;
                                    &lt;/dl&gt;
                                &lt;/div&gt;
                                &lt;div class=&quot;row text-center box-img&quot;&gt;
                                    &lt;div class=&quot;col-sm-2&quot;&gt;&lt;/div&gt;
                                    &lt;div class=&quot;col-sm-4&quot;&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/imgs/daumkakao/m_event_music.png&quot;
                                            class=&quot;img_thumb&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                    &lt;/div&gt;
                                    &lt;div class=&quot;col-sm-4&quot;&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/imgs/daumkakao/m_event_webtoon.png&quot;
                                            class=&quot;img_thumb&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                    &lt;/div&gt;
                                    &lt;div class=&quot;col-sm-2&quot;&gt;&lt;/div&gt;
                                &lt;/div&gt;
                            &lt;/div&gt;

                            &lt;div class=&quot;wrap-project&quot;&gt;
                                &lt;img
                                    src=&quot;https://odada-o.github.io//odada/imgs/daumkakao/pc_event_game1.jpg&quot;
                                    class=&quot;img_thumb&quot;
                                    alt=&quot;&quot;
                                /&gt;
                                &lt;div class=&quot;box-explain&quot;&gt;
                                    &lt;dl&gt;
                                        &lt;dt&gt;작업명&lt;/dt&gt;
                                        &lt;dd&gt;&lt;strong&gt;PC) 이벤트&lt;/strong&gt;&lt;/dd&gt;
                                        &lt;dt&gt;작업영역&lt;/dt&gt;
                                        &lt;dd&gt;퍼블리싱&lt;/dd&gt;
                                    &lt;/dl&gt;
                                    &lt;dl&gt;
                                        &lt;dt&gt;작업URL&lt;/dt&gt;
                                        &lt;dd&gt;
                                            &lt;!--&lt;a href=&quot;http://ads.kakao.com/&quot; target=&quot;_blank&quot;&gt;http://ads.kakao.com/&lt;/a&gt;--&gt;
                                        &lt;/dd&gt;
                                    &lt;/dl&gt;
                                &lt;/div&gt;
                                &lt;div class=&quot;row text-center box-img&quot;&gt;
                                    &lt;div class=&quot;col-sm-6&quot;&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/imgs/daumkakao/pc_event_game1.jpg&quot;
                                            class=&quot;img_thumb&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                    &lt;/div&gt;
                                    &lt;div class=&quot;col-sm-6&quot;&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/imgs/daumkakao/pc_event_game1.png&quot;
                                            class=&quot;img_thumb&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                    &lt;/div&gt;
                                &lt;/div&gt;
                            &lt;/div&gt;
                        &lt;/div&gt;
                    &lt;/div&gt;

                    &lt;!-- 웅진패스원 --&gt;
                    &lt;div
                        class=&quot;tab-pane fade&quot;
                        id=&quot;woongjin&quot;
                        role=&quot;tabpanel&quot;
                        aria-labelledby=&quot;woongjin-tab&quot;
                    &gt;
                        &lt;h2&gt;woongjin passone&lt;/h2&gt;
                        &lt;div class=&quot;wrap-explain&quot;&gt;
                            &lt;div class=&quot;wrap-project&quot;&gt;
                                &lt;img
                                    src=&quot;https://odada-o.github.io//odada/imgs/woongjin/hrdb2b_1.jpg&quot;
                                    class=&quot;img_thumb&quot;
                                    alt=&quot;&quot;
                                /&gt;
                                &lt;div class=&quot;box-explain&quot;&gt;
                                    &lt;dl&gt;
                                        &lt;dt&gt;작업명&lt;/dt&gt;
                                        &lt;dd&gt;&lt;strong&gt;KG패스원 기업교육&lt;/strong&gt;&lt;/dd&gt;
                                        &lt;dt&gt;작업영역&lt;/dt&gt;
                                        &lt;dd&gt;디자인 + 퍼블리싱&lt;/dd&gt;
                                    &lt;/dl&gt;
                                    &lt;dl&gt;
                                        &lt;dt&gt;작업 URL&lt;/dt&gt;
                                        &lt;dd&gt;
                                            &lt;!--&lt;a href=&quot;./_past/dk/html/main2.html&quot; target=&quot;_blank&quot;&gt;./_past/dk/html/main2.html&lt;/a&gt;--&gt;
                                        &lt;/dd&gt;
                                    &lt;/dl&gt;
                                &lt;/div&gt;
                                &lt;div class=&quot;row text-center box-img&quot;&gt;
                                    &lt;div class=&quot;col-sm-6&quot;&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/imgs/woongjin/hrdb2b_1.jpg&quot;
                                            class=&quot;img_thumb&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/imgs/woongjin/hrdb2b_3.jpg&quot;
                                            class=&quot;img_thumb&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/imgs/woongjin/hrdb2b_5.jpg&quot;
                                            class=&quot;img_thumb&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/imgs/woongjin/hrdb2b_7.jpg&quot;
                                            class=&quot;img_thumb&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/imgs/woongjin/hrdb2b_9.jpg&quot;
                                            class=&quot;img_thumb&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/imgs/woongjin/hrdb2b_11.jpg&quot;
                                            class=&quot;img_thumb&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                    &lt;/div&gt;
                                    &lt;div class=&quot;col-sm-6&quot;&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/imgs/woongjin/hrdb2b_2.jpg&quot;
                                            class=&quot;img_thumb&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/imgs/woongjin/hrdb2b_4.jpg&quot;
                                            class=&quot;img_thumb&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/imgs/woongjin/hrdb2b_6.jpg&quot;
                                            class=&quot;img_thumb&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/imgs/woongjin/hrdb2b_8.jpg&quot;
                                            class=&quot;img_thumb&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/imgs/woongjin/hrdb2b_10.jpg&quot;
                                            class=&quot;img_thumb&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                    &lt;/div&gt;
                                &lt;/div&gt;
                            &lt;/div&gt;

                            &lt;div class=&quot;wrap-project&quot;&gt;
                                &lt;img
                                    src=&quot;https://odada-o.github.io//odada/imgs/woongjin/nh_1.jpg&quot;
                                    class=&quot;img_thumb&quot;
                                    alt=&quot;&quot;
                                /&gt;
                                &lt;div class=&quot;box-explain&quot;&gt;
                                    &lt;dl&gt;
                                        &lt;dt&gt;작업명&lt;/dt&gt;
                                        &lt;dd&gt;&lt;strong&gt;NH농협 직무교육&lt;/strong&gt;&lt;/dd&gt;
                                        &lt;dt&gt;작업영역&lt;/dt&gt;
                                        &lt;dd&gt;디자인 + 퍼블리싱&lt;/dd&gt;
                                    &lt;/dl&gt;
                                    &lt;dl&gt;
                                        &lt;dt&gt;작업 URL&lt;/dt&gt;
                                        &lt;dd&gt;
                                            &lt;!--&lt;a href=&quot;./_past/dk/html/main2.html&quot; target=&quot;_blank&quot;&gt;./_past/dk/html/main2.html&lt;/a&gt;--&gt;
                                        &lt;/dd&gt;
                                    &lt;/dl&gt;
                                &lt;/div&gt;
                                &lt;div class=&quot;row text-center box-img&quot;&gt;
                                    &lt;div class=&quot;col-sm-6&quot;&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/imgs/woongjin/nh_1.jpg&quot;
                                            class=&quot;img_thumb&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/imgs/woongjin/nh_3.jpg&quot;
                                            class=&quot;img_thumb&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                    &lt;/div&gt;
                                    &lt;div class=&quot;col-sm-6&quot;&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/imgs/woongjin/nh_2.jpg&quot;
                                            class=&quot;img_thumb&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/imgs/woongjin/nh_4.jpg&quot;
                                            class=&quot;img_thumb&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                    &lt;/div&gt;
                                &lt;/div&gt;
                            &lt;/div&gt;

                            &lt;div class=&quot;wrap-project&quot;&gt;
                                &lt;img
                                    src=&quot;https://odada-o.github.io//odada/imgs/woongjin/dk_1.jpg&quot;
                                    class=&quot;img_thumb&quot;
                                    alt=&quot;&quot;
                                /&gt;
                                &lt;div class=&quot;box-explain&quot;&gt;
                                    &lt;dl&gt;
                                        &lt;dt&gt;작업명&lt;/dt&gt;
                                        &lt;dd&gt;&lt;strong&gt;동국제강그룹 직무교육 반응형웹&lt;/strong&gt;&lt;/dd&gt;
                                        &lt;dt&gt;작업영역&lt;/dt&gt;
                                        &lt;dd&gt;디자인 + 퍼블리싱&lt;/dd&gt;
                                    &lt;/dl&gt;
                                    &lt;dl&gt;
                                        &lt;dt&gt;작업 URL&lt;/dt&gt;
                                        &lt;dd&gt;
                                            &lt;!--&lt;a href=&quot;./_past/dk/html/main2.html&quot; target=&quot;_blank&quot;&gt;./_past/dk/html/main2.html&lt;/a&gt;--&gt;
                                        &lt;/dd&gt;
                                    &lt;/dl&gt;
                                &lt;/div&gt;
                                &lt;div class=&quot;row text-center box-img&quot;&gt;
                                    &lt;div class=&quot;col-sm-6&quot;&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/imgs/woongjin/dk_1.jpg&quot;
                                            class=&quot;img_thumb&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/imgs/woongjin/dk_3.jpg&quot;
                                            class=&quot;img_thumb&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                    &lt;/div&gt;
                                    &lt;div class=&quot;col-sm-6&quot;&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/imgs/woongjin/dk_2.jpg&quot;
                                            class=&quot;img_thumb&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/imgs/woongjin/dk_4.jpg&quot;
                                            class=&quot;img_thumb&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                    &lt;/div&gt;
                                &lt;/div&gt;
                            &lt;/div&gt;

                            &lt;div class=&quot;wrap-project&quot;&gt;
                                &lt;img
                                    src=&quot;https://odada-o.github.io//odada/imgs/woongjin/intra_0.jpg&quot;
                                    class=&quot;img_thumb&quot;
                                    alt=&quot;&quot;
                                /&gt;
                                &lt;div class=&quot;box-explain&quot;&gt;
                                    &lt;dl&gt;
                                        &lt;dt&gt;작업명&lt;/dt&gt;
                                        &lt;dd&gt;&lt;strong&gt;KG패스원 손익관리시스템&lt;/strong&gt;&lt;/dd&gt;
                                        &lt;dt&gt;작업영역&lt;/dt&gt;
                                        &lt;dd&gt;디자인 + 퍼블리싱&lt;/dd&gt;
                                    &lt;/dl&gt;
                                    &lt;dl&gt;
                                        &lt;dt&gt;작업 URL&lt;/dt&gt;
                                        &lt;dd&gt;
                                            &lt;!--&lt;a href=&quot;./_past/dk/html/main2.html&quot; target=&quot;_blank&quot;&gt;./_past/dk/html/main2.html&lt;/a&gt;--&gt;
                                        &lt;/dd&gt;
                                    &lt;/dl&gt;
                                &lt;/div&gt;
                                &lt;div class=&quot;row text-center box-img&quot;&gt;
                                    &lt;div class=&quot;col-sm-6&quot;&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/imgs/woongjin/intra_0.jpg&quot;
                                            class=&quot;img_thumb&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/imgs/woongjin/intra_2.jpg&quot;
                                            class=&quot;img_thumb&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                    &lt;/div&gt;
                                    &lt;div class=&quot;col-sm-6&quot;&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/imgs/woongjin/intra_1.jpg&quot;
                                            class=&quot;img_thumb&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/imgs/woongjin/intra_3.jpg&quot;
                                            class=&quot;img_thumb&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                    &lt;/div&gt;
                                &lt;/div&gt;
                            &lt;/div&gt;

                            &lt;div class=&quot;wrap-project&quot;&gt;
                                &lt;img
                                    src=&quot;https://odada-o.github.io//odada/imgs/woongjin/hrd_1.jpg&quot;
                                    class=&quot;img_thumb&quot;
                                    alt=&quot;&quot;
                                /&gt;
                                &lt;div class=&quot;box-explain&quot;&gt;
                                    &lt;dl&gt;
                                        &lt;dt&gt;작업명&lt;/dt&gt;
                                        &lt;dd&gt;&lt;strong&gt;KG패스원 직무교육 사이트 개편&lt;/strong&gt;&lt;/dd&gt;
                                        &lt;dt&gt;작업영역&lt;/dt&gt;
                                        &lt;dd&gt;디자인 + 퍼블리싱&lt;/dd&gt;
                                    &lt;/dl&gt;
                                    &lt;dl&gt;
                                        &lt;dt&gt;작업 URL&lt;/dt&gt;
                                        &lt;dd&gt;
                                            &lt;a
                                                href=&quot;http://hrd.passone.net/main/&quot;
                                                target=&quot;_blank&quot;
                                                &gt;http://hrd.passone.net/main/&lt;/a
                                            &gt;
                                        &lt;/dd&gt;
                                    &lt;/dl&gt;
                                &lt;/div&gt;
                                &lt;div class=&quot;row text-center box-img&quot;&gt;
                                    &lt;div class=&quot;col-sm-6&quot;&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/imgs/woongjin/hrd_1.jpg&quot;
                                            class=&quot;img_thumb&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/imgs/woongjin/hrd_3.jpg&quot;
                                            class=&quot;img_thumb&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/imgs/woongjin/hrd_5.jpg&quot;
                                            class=&quot;img_thumb&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/imgs/woongjin/hrd_7.jpg&quot;
                                            class=&quot;img_thumb&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/imgs/woongjin/hrd_9.jpg&quot;
                                            class=&quot;img_thumb&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/imgs/woongjin/hrd_11.jpg&quot;
                                            class=&quot;img_thumb&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                    &lt;/div&gt;
                                    &lt;div class=&quot;col-sm-6&quot;&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/imgs/woongjin/hrd_2.jpg&quot;
                                            class=&quot;img_thumb&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/imgs/woongjin/hrd_4.jpg&quot;
                                            class=&quot;img_thumb&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/imgs/woongjin/hrd_6.jpg&quot;
                                            class=&quot;img_thumb&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/imgs/woongjin/hrd_8.jpg&quot;
                                            class=&quot;img_thumb&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/imgs/woongjin/hrd_10.jpg&quot;
                                            class=&quot;img_thumb&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/imgs/woongjin/hrd_12.jpg&quot;
                                            class=&quot;img_thumb&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                    &lt;/div&gt;
                                &lt;/div&gt;
                            &lt;/div&gt;

                            &lt;div class=&quot;wrap-project&quot;&gt;
                                &lt;img
                                    src=&quot;https://odada-o.github.io//odada/imgs/woongjin/skn_1.jpg&quot;
                                    class=&quot;img_thumb&quot;
                                    alt=&quot;&quot;
                                /&gt;
                                &lt;div class=&quot;box-explain&quot;&gt;
                                    &lt;dl&gt;
                                        &lt;dt&gt;작업명&lt;/dt&gt;
                                        &lt;dd&gt;&lt;strong&gt;SK네트웍스 직무교육 사이트&lt;/strong&gt;&lt;/dd&gt;
                                        &lt;dt&gt;작업영역&lt;/dt&gt;
                                        &lt;dd&gt;디자인 + 퍼블리싱&lt;/dd&gt;
                                    &lt;/dl&gt;
                                    &lt;dl&gt;
                                        &lt;dt&gt;작업 URL&lt;/dt&gt;
                                        &lt;dd&gt;
                                            &lt;!--&lt;a href=&quot;http://hrd.passone.net/main/&quot; target=&quot;_blank&quot;&gt;http://hrd.passone.net/main/&lt;/a&gt;--&gt;
                                        &lt;/dd&gt;
                                    &lt;/dl&gt;
                                &lt;/div&gt;
                                &lt;div class=&quot;row text-center box-img&quot;&gt;
                                    &lt;div class=&quot;col-sm-6&quot;&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/imgs/woongjin/skn_1.jpg&quot;
                                            class=&quot;img_thumb&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/imgs/woongjin/skn_3.jpg&quot;
                                            class=&quot;img_thumb&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/imgs/woongjin/skn_5.jpg&quot;
                                            class=&quot;img_thumb&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/imgs/woongjin/skn_7.jpg&quot;
                                            class=&quot;img_thumb&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/imgs/woongjin/skn_9.jpg&quot;
                                            class=&quot;img_thumb&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/imgs/woongjin/skn_11.jpg&quot;
                                            class=&quot;img_thumb&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                    &lt;/div&gt;
                                    &lt;div class=&quot;col-sm-6&quot;&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/imgs/woongjin/skn_2.jpg&quot;
                                            class=&quot;img_thumb&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/imgs/woongjin/skn_4.jpg&quot;
                                            class=&quot;img_thumb&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/imgs/woongjin/skn_6.jpg&quot;
                                            class=&quot;img_thumb&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/imgs/woongjin/skn_8.jpg&quot;
                                            class=&quot;img_thumb&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/imgs/woongjin/skn_10.jpg&quot;
                                            class=&quot;img_thumb&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                    &lt;/div&gt;
                                &lt;/div&gt;
                            &lt;/div&gt;

                            &lt;div class=&quot;wrap-project&quot;&gt;
                                &lt;img
                                    src=&quot;https://odada-o.github.io//odada/imgs/woongjin/kha_1.jpg&quot;
                                    class=&quot;img_thumb&quot;
                                    alt=&quot;&quot;
                                /&gt;
                                &lt;div class=&quot;box-explain&quot;&gt;
                                    &lt;dl&gt;
                                        &lt;dt&gt;작업명&lt;/dt&gt;
                                        &lt;dd&gt;&lt;strong&gt;대한병원협회 직무교육&lt;/strong&gt;&lt;/dd&gt;
                                        &lt;dt&gt;작업영역&lt;/dt&gt;
                                        &lt;dd&gt;디자인 + 퍼블리싱&lt;/dd&gt;
                                    &lt;/dl&gt;
                                    &lt;dl&gt;
                                        &lt;dt&gt;작업 URL&lt;/dt&gt;
                                        &lt;dd&gt;
                                            &lt;!--&lt;a href=&quot;http://hrd.passone.net/main/&quot; target=&quot;_blank&quot;&gt;http://hrd.passone.net/main/&lt;/a&gt;--&gt;
                                        &lt;/dd&gt;
                                    &lt;/dl&gt;
                                &lt;/div&gt;
                                &lt;div class=&quot;row text-center box-img&quot;&gt;
                                    &lt;div class=&quot;col-sm-6&quot;&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/imgs/woongjin/kha_1.jpg&quot;
                                            class=&quot;img_thumb&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/imgs/woongjin/kha_2.jpg&quot;
                                            class=&quot;img_thumb&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                    &lt;/div&gt;
                                    &lt;div class=&quot;col-sm-6&quot;&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/imgs/woongjin/kha_3.jpg&quot;
                                            class=&quot;img_thumb&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                    &lt;/div&gt;
                                &lt;/div&gt;
                            &lt;/div&gt;

                            &lt;div class=&quot;wrap-project&quot;&gt;
                                &lt;img
                                    src=&quot;https://odada-o.github.io//odada/imgs/woongjin/aju_1.jpg&quot;
                                    class=&quot;img_thumb&quot;
                                    alt=&quot;&quot;
                                /&gt;
                                &lt;div class=&quot;box-explain&quot;&gt;
                                    &lt;dl&gt;
                                        &lt;dt&gt;작업명&lt;/dt&gt;
                                        &lt;dd&gt;&lt;strong&gt;아주그룹 직무교육 인트로&lt;/strong&gt;&lt;/dd&gt;
                                        &lt;dt&gt;작업영역&lt;/dt&gt;
                                        &lt;dd&gt;디자인 + 퍼블리싱&lt;/dd&gt;
                                    &lt;/dl&gt;
                                    &lt;dl&gt;
                                        &lt;dt&gt;작업 URL&lt;/dt&gt;
                                        &lt;dd&gt;
                                            &lt;!--&lt;a href=&quot;http://hrd.passone.net/main/&quot; target=&quot;_blank&quot;&gt;http://hrd.passone.net/main/&lt;/a&gt;--&gt;
                                        &lt;/dd&gt;
                                    &lt;/dl&gt;
                                &lt;/div&gt;
                                &lt;div class=&quot;row text-center box-img&quot;&gt;
                                    &lt;div class=&quot;col-sm-6&quot;&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/imgs/woongjin/aju_1.jpg&quot;
                                            class=&quot;img_thumb&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/imgs/woongjin/aju_3.jpg&quot;
                                            class=&quot;img_thumb&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                    &lt;/div&gt;
                                    &lt;div class=&quot;col-sm-6&quot;&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/imgs/woongjin/aju_2.jpg&quot;
                                            class=&quot;img_thumb&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/imgs/woongjin/aju_4.jpg&quot;
                                            class=&quot;img_thumb&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                    &lt;/div&gt;
                                &lt;/div&gt;
                            &lt;/div&gt;
                        &lt;/div&gt;
                    &lt;/div&gt;

                    &lt;!-- 팍스넷 --&gt;
                    &lt;div
                        class=&quot;tab-pane fade&quot;
                        id=&quot;paxnet&quot;
                        role=&quot;tabpanel&quot;
                        aria-labelledby=&quot;paxnet-tab&quot;
                    &gt;
                        &lt;h2&gt;paxnet&lt;/h2&gt;
                        &lt;div class=&quot;wrap-explain&quot;&gt;
                            &lt;div class=&quot;wrap-project&quot;&gt;
                                &lt;img
                                    src=&quot;https://odada-o.github.io//odada/imgs/paxnet/ui_1.jpg&quot;
                                    class=&quot;img_thumb&quot;
                                    alt=&quot;&quot;
                                /&gt;
                                &lt;div class=&quot;box-explain&quot;&gt;
                                    &lt;dl&gt;
                                        &lt;dt&gt;작업명&lt;/dt&gt;
                                        &lt;dd&gt;&lt;strong&gt;PC) 팍스넷&lt;/strong&gt;&lt;/dd&gt;
                                        &lt;dt&gt;작업영역&lt;/dt&gt;
                                        &lt;dd&gt;디자인 + 퍼블리싱&lt;/dd&gt;
                                    &lt;/dl&gt;
                                    &lt;dl&gt;
                                        &lt;dt&gt;작업 URL&lt;/dt&gt;
                                        &lt;dd&gt;
                                            &lt;strong
                                                &gt;&lt;a
                                                    href=&quot;http://paxnet.moneta.co.kr/&quot;
                                                    target=&quot;_blank&quot;
                                                    &gt;http://paxnet.moneta.co.kr/&lt;/a
                                                &gt;&lt;/strong
                                            &gt;
                                        &lt;/dd&gt;
                                    &lt;/dl&gt;
                                &lt;/div&gt;
                                &lt;div class=&quot;row text-center box-img&quot;&gt;
                                    &lt;div class=&quot;row&quot;&gt;
                                        &lt;div class=&quot;col-sm-4&quot;&gt;
                                            &lt;img
                                                src=&quot;https://odada-o.github.io//odada/imgs/paxnet/ui_1.jpg&quot;
                                                class=&quot;img_thumb&quot;
                                                alt=&quot;&quot;
                                            /&gt;
                                        &lt;/div&gt;
                                        &lt;div class=&quot;col-sm-4&quot;&gt;
                                            &lt;img
                                                src=&quot;https://odada-o.github.io//odada/imgs/paxnet/ui_1_1.jpg&quot;
                                                class=&quot;img_thumb&quot;
                                                alt=&quot;&quot;
                                            /&gt;
                                        &lt;/div&gt;
                                        &lt;div class=&quot;col-sm-4&quot;&gt;
                                            &lt;img
                                                src=&quot;https://odada-o.github.io//odada/imgs/paxnet/ui_1_2.jpg&quot;
                                                class=&quot;img_thumb&quot;
                                                alt=&quot;&quot;
                                            /&gt;
                                        &lt;/div&gt;
                                    &lt;/div&gt;
                                &lt;/div&gt;
                            &lt;/div&gt;

                            &lt;div class=&quot;wrap-project&quot;&gt;
                                &lt;img
                                    src=&quot;https://odada-o.github.io//odada/imgs/paxnet/ui_2.jpg&quot;
                                    class=&quot;img_thumb&quot;
                                    alt=&quot;&quot;
                                /&gt;
                                &lt;div class=&quot;box-explain&quot;&gt;
                                    &lt;dl&gt;
                                        &lt;dt&gt;작업명&lt;/dt&gt;
                                        &lt;dd&gt;&lt;strong&gt;PC) 모네타 부동산&lt;/strong&gt;&lt;/dd&gt;
                                        &lt;dt&gt;작업영역&lt;/dt&gt;
                                        &lt;dd&gt;디자인 + 퍼블리싱&lt;/dd&gt;
                                    &lt;/dl&gt;
                                    &lt;dl&gt;
                                        &lt;dt&gt;작업 URL&lt;/dt&gt;
                                        &lt;dd&gt;
                                            &lt;a
                                                href=&quot;http://www.moneta.co.kr/&quot;
                                                target=&quot;_blank&quot;
                                                &gt;http://www.moneta.co.kr/&lt;/a
                                            &gt;
                                        &lt;/dd&gt;
                                    &lt;/dl&gt;
                                &lt;/div&gt;
                                &lt;div class=&quot;text-center box-img&quot;&gt;
                                    &lt;div class=&quot;row&quot;&gt;
                                        &lt;div class=&quot;col-sm-4&quot;&gt;
                                            &lt;img
                                                src=&quot;https://odada-o.github.io//odada/imgs/paxnet/ui_2.jpg&quot;
                                                class=&quot;img_thumb&quot;
                                                alt=&quot;&quot;
                                            /&gt;
                                        &lt;/div&gt;
                                        &lt;div class=&quot;col-sm-4&quot;&gt;
                                            &lt;img
                                                src=&quot;https://odada-o.github.io//odada/imgs/paxnet/ui_2_1.jpg&quot;
                                                class=&quot;img_thumb&quot;
                                                alt=&quot;&quot;
                                            /&gt;
                                        &lt;/div&gt;
                                        &lt;div class=&quot;col-sm-4&quot;&gt;
                                            &lt;img
                                                src=&quot;https://odada-o.github.io//odada/imgs/paxnet/ui_2_2.jpg&quot;
                                                class=&quot;img_thumb&quot;
                                                alt=&quot;&quot;
                                            /&gt;
                                        &lt;/div&gt;
                                    &lt;/div&gt;
                                &lt;/div&gt;
                            &lt;/div&gt;

                            &lt;div class=&quot;wrap-project&quot;&gt;
                                &lt;img
                                    src=&quot;https://odada-o.github.io//odada/imgs/paxnet/ui_3.jpg&quot;
                                    class=&quot;img_thumb&quot;
                                    alt=&quot;&quot;
                                /&gt;
                                &lt;div class=&quot;box-explain&quot;&gt;
                                    &lt;dl&gt;
                                        &lt;dt&gt;작업명&lt;/dt&gt;
                                        &lt;dd&gt;&lt;strong&gt;PC) 퇴직연금&lt;/strong&gt;&lt;/dd&gt;
                                        &lt;dt&gt;작업영역&lt;/dt&gt;
                                        &lt;dd&gt;디자인 + 퍼블리싱&lt;/dd&gt;
                                    &lt;/dl&gt;
                                    &lt;dl&gt;
                                        &lt;dt&gt;작업 URL&lt;/dt&gt;
                                        &lt;dd&gt;&lt;/dd&gt;
                                    &lt;/dl&gt;
                                &lt;/div&gt;
                                &lt;div class=&quot;text-center box-img&quot;&gt;
                                    &lt;div class=&quot;row&quot;&gt;
                                        &lt;div class=&quot;col-sm-6&quot;&gt;
                                            &lt;img
                                                src=&quot;https://odada-o.github.io//odada/imgs/paxnet/ui_3.jpg&quot;
                                                class=&quot;img_thumb&quot;
                                                alt=&quot;&quot;
                                            /&gt;&lt;img
                                                src=&quot;imgs/paxnet/ui_3_2.jpg&quot;
                                                class=&quot;img_thumb&quot;
                                                alt=&quot;&quot;
                                            /&gt;&lt;img
                                                src=&quot;https://odada-o.github.io//odada/imgs/paxnet/ui_3_4.jpg&quot;
                                                class=&quot;img_thumb&quot;
                                                alt=&quot;&quot;
                                            /&gt;
                                        &lt;/div&gt;
                                        &lt;div class=&quot;col-sm-6&quot;&gt;
                                            &lt;img
                                                src=&quot;https://odada-o.github.io//odada/imgs/paxnet/ui_3_1.jpg&quot;
                                                class=&quot;img_thumb&quot;
                                                alt=&quot;&quot;
                                            /&gt;&lt;img
                                                src=&quot;imgs/paxnet/ui_3_9.jpg&quot;
                                                class=&quot;img_thumb&quot;
                                                alt=&quot;&quot;
                                            /&gt;&lt;img
                                                src=&quot;https://odada-o.github.io//odada/imgs/paxnet/ui_3_15.jpg&quot;
                                                class=&quot;img_thumb&quot;
                                                alt=&quot;&quot;
                                            /&gt;
                                        &lt;/div&gt;
                                    &lt;/div&gt;
                                &lt;/div&gt;
                            &lt;/div&gt;

                            &lt;div class=&quot;wrap-project&quot;&gt;
                                &lt;img
                                    src=&quot;https://odada-o.github.io//odada/imgs/paxnet/game_1.jpg&quot;
                                    class=&quot;img_thumb&quot;
                                    alt=&quot;&quot;
                                /&gt;
                                &lt;div class=&quot;box-explain&quot;&gt;
                                    &lt;dl&gt;
                                        &lt;dt&gt;작업명&lt;/dt&gt;
                                        &lt;dd&gt;&lt;strong&gt;PC) 한화증권 실전투자대회&lt;/strong&gt;&lt;/dd&gt;
                                        &lt;dt&gt;작업영역&lt;/dt&gt;
                                        &lt;dd&gt;디자인 + 퍼블리싱&lt;/dd&gt;
                                    &lt;/dl&gt;
                                    &lt;dl&gt;
                                        &lt;dt&gt;작업 URL&lt;/dt&gt;
                                        &lt;dd&gt;
                                            &lt;a
                                                href=&quot;./_past/game/html/!list.html&quot;
                                                target=&quot;_blank&quot;
                                                &gt;./_past/game/html/!list.html&lt;/a
                                            &gt;
                                            &lt;a
                                                href=&quot;./_past/game/html/main.html&quot;
                                                target=&quot;_blank&quot;
                                                &gt;./_past/game/html/main.html&lt;/a
                                            &gt;
                                        &lt;/dd&gt;
                                    &lt;/dl&gt;
                                &lt;/div&gt;
                                &lt;div class=&quot;text-center box-img&quot;&gt;
                                    &lt;div class=&quot;row&quot;&gt;
                                        &lt;div class=&quot;col-sm-4&quot;&gt;
                                            &lt;img
                                                src=&quot;https://odada-o.github.io//odada/imgs/paxnet/game_1.jpg&quot;
                                                class=&quot;img_thumb&quot;
                                                alt=&quot;&quot;
                                            /&gt;&lt;img
                                                src=&quot;imgs/paxnet/game_5.jpg&quot;
                                                class=&quot;img_thumb&quot;
                                                alt=&quot;&quot;
                                            /&gt;
                                        &lt;/div&gt;
                                        &lt;div class=&quot;col-sm-4&quot;&gt;
                                            &lt;img
                                                src=&quot;https://odada-o.github.io//odada/imgs/paxnet/game_2.jpg&quot;
                                                class=&quot;img_thumb&quot;
                                                alt=&quot;&quot;
                                            /&gt;&lt;img
                                                src=&quot;imgs/paxnet/game_6.jpg&quot;
                                                class=&quot;img_thumb&quot;
                                                alt=&quot;&quot;
                                            /&gt;
                                        &lt;/div&gt;
                                        &lt;div class=&quot;col-sm-4&quot;&gt;
                                            &lt;img
                                                src=&quot;https://odada-o.github.io//odada/imgs/paxnet/game_3.jpg&quot;
                                                class=&quot;img_thumb&quot;
                                                alt=&quot;&quot;
                                            /&gt;&lt;img
                                                src=&quot;imgs/paxnet/game_4.jpg&quot;
                                                class=&quot;img_thumb&quot;
                                                alt=&quot;&quot;
                                            /&gt;
                                        &lt;/div&gt;
                                    &lt;/div&gt;
                                &lt;/div&gt;
                            &lt;/div&gt;
                        &lt;/div&gt;
                    &lt;/div&gt;

                    &lt;div
                        class=&quot;tab-pane fade&quot;
                        id=&quot;flash&quot;
                        role=&quot;tabpanel&quot;
                        aria-labelledby=&quot;flash-tab&quot;
                    &gt;
                        &lt;h2&gt;도서&lt;/h2&gt;
                        &lt;div class=&quot;wrap-explain&quot;&gt;
                            &lt;div class=&quot;wrap-project&quot;&gt;
                                &lt;img
                                    src=&quot;https://odada-o.github.io//odada/imgs/flash/flash_3.jpg&quot;
                                    alt=&quot;&quot;
                                /&gt;
                                &lt;div class=&quot;box-explain&quot;&gt;
                                    &lt;dl&gt;
                                        &lt;dt&gt;작업명&lt;/dt&gt;
                                        &lt;dd&gt;
                                            &lt;strong&gt;플래시 8 Motion Action 저&lt;/strong&gt;
                                        &lt;/dd&gt;
                                    &lt;/dl&gt;
                                    &lt;dl&gt;
                                        &lt;dt&gt;작업URL&lt;/dt&gt;
                                        &lt;dd&gt;
                                            &lt;a
                                                href=&quot;https://book.naver.com/bookdb/book_detail.naver?bid=2496994&quot;
                                                target=&quot;_blank&quot;
                                                &gt;네이버책 플래시 8 Motion Action&lt;/a
                                            &gt;
                                        &lt;/dd&gt;
                                        &lt;dt&gt;키워드&lt;/dt&gt;
                                        &lt;dd&gt;#flash #제우미디어&lt;/dd&gt;
                                    &lt;/dl&gt;
                                &lt;/div&gt;
                                &lt;div class=&quot;row text-center box-img&quot;&gt;
                                    &lt;div class=&quot;col-md-6&quot;&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/imgs/flash/flash_3.jpg&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                    &lt;/div&gt;
                                &lt;/div&gt;
                            &lt;/div&gt;
                            &lt;div class=&quot;wrap-project&quot;&gt;
                                &lt;img
                                    src=&quot;https://odada-o.github.io//odada/imgs/flash/flash_4.jpg&quot;
                                    alt=&quot;&quot;
                                /&gt;
                                &lt;div class=&quot;box-explain&quot;&gt;
                                    &lt;dl&gt;
                                        &lt;dt&gt;작업명&lt;/dt&gt;
                                        &lt;dd&gt;
                                            &lt;strong&gt;플래시 MX 모션 &amp; 액션 저&lt;/strong&gt;
                                        &lt;/dd&gt;
                                    &lt;/dl&gt;
                                    &lt;dl&gt;
                                        &lt;dt&gt;작업URL&lt;/dt&gt;
                                        &lt;dd&gt;
                                            &lt;a
                                                href=&quot;https://book.naver.com/bookdb/book_detail.naver?bid=124048&quot;
                                                target=&quot;_blank&quot;
                                                &gt;네이버책 플래시 MX 모션 &amp; 액션&lt;/a
                                            &gt;
                                        &lt;/dd&gt;
                                        &lt;dt&gt;키워드&lt;/dt&gt;
                                        &lt;dd&gt;#flash #제우미디어&lt;/dd&gt;
                                    &lt;/dl&gt;
                                &lt;/div&gt;
                                &lt;div class=&quot;row text-center box-img&quot;&gt;
                                    &lt;div class=&quot;col-md-6&quot;&gt;
                                        &lt;img
                                            src=&quot;https://odada-o.github.io//odada/imgs/flash/flash_4.jpg&quot;
                                            alt=&quot;&quot;
                                        /&gt;
                                    &lt;/div&gt;
                                &lt;/div&gt;
                            &lt;/div&gt;
                        &lt;/div&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
            &lt;/div&gt;

            &lt;!-- &lt;footer class=&quot;container-fluid text-center&quot;&gt;
                &lt;a href=&quot;#myPage&quot; title=&quot;To Top&quot;&gt;
                    &lt;span class=&quot;glyphicon glyphicon-chevron-up&quot;&gt;&lt;/span&gt;
                &lt;/a&gt;
            &lt;/footer&gt; --&gt;
        &lt;/div&gt;

        &lt;script&gt;
            $(document).ready(function () {
                // Add smooth scrolling to all links in navbar + footer link
                $(&quot;.navbar a, footer a[href='#myPage'], .btn-down&quot;).on('click', function (event) {
                    // Make sure this.hash has a value before overriding default behavior
                    if (this.hash !== '') {
                        // Prevent default anchor click behavior
                        event.preventDefault();

                        // Store hash
                        var hash = this.hash;

                        // Using jQuery's animate() method to add smooth page scroll
                        // The optional number (900) specifies the number of milliseconds it takes to scroll to the specified area
                        $('html, body').animate(
                            {
                                scrollTop: $(hash).offset().top,
                            },
                            900,
                            function () {
                                // Add hash (#) to URL when done scrolling (default click behavior)
                                window.location.hash = hash;
                            },
                        );
                    } // End if
                });

                $(window).scroll(function () {
                    $('.slideanim').each(function () {
                        var pos = $(this).offset().top;

                        var winTop = $(window).scrollTop();
                        if (pos &lt; winTop + 600) {
                            $(this).addClass('slide');
                        }
                    });
                });

                $('.btn-down').on('click', function () {
                    var position = $('#portfolio').offset().top();
                    $('HTML, BODY').animate(
                        {
                            scrollTop: position,
                        },
                        1000,
                    );
                });

                //        var $window = $(window),
                //                btnDown = $('.btn-down')
                //
                //        $window.on('scroll', function(){
                //            if($window.scrollTop() &gt; 1000){
                //                btnDown.hide();
                //            }else{
                //                btnDown.visible();
                //            }
                //        });
                //        if($window.scrollTop() &gt; 1000){
                //            btnDown.hide();
                //        }else{
                //            btnDown.visible();
                //        }
            });
        &lt;/script&gt;
    &lt;/body&gt;
&lt;/html&gt;</description>
      <author>oodada</author>
      <guid isPermaLink="true">https://oddcode.tistory.com/pages/about</guid>
      <pubDate>Mon, 12 Aug 2024 16:47:27 +0900</pubDate>
    </item>
    <item>
      <title>타입 가져오기, 내보내기</title>
      <link>https://oddcode.tistory.com/348</link>
      <description>&lt;h1&gt;ts 가져오기, 내보내기&lt;/h1&gt;
&lt;p&gt;타입스크립트에서는 &lt;code&gt;import&lt;/code&gt;와 &lt;code&gt;export&lt;/code&gt; 키워드를 사용하여 모듈을 가져오고 내보낼 수 있습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;// export.ts
interface User {
    name: string;
    age: number;
    role: boolean;
}

export function introduce(user: User) {
    return `${user.name}는 ${user.age}살이며, 관리자인가요? ${user.role ? &amp;#39;네&amp;#39; : &amp;#39;아니요&amp;#39;}`;
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;// import.ts
import { introduce } from &amp;#39;./export&amp;#39;;

const userA = {
    name: &amp;#39;김가을&amp;#39;,
    age: 2,
    role: false,
};

const userB = {
    name: &amp;#39;김겨울&amp;#39;,
    age: 3,
    role: true,
};

const message1 = introduce(userA);
const message2 = introduce(userB);

console.log(message1);
console.log(message2);&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Front/TypeScript</category>
      <author>oodada</author>
      <guid isPermaLink="true">https://oddcode.tistory.com/348</guid>
      <comments>https://oddcode.tistory.com/348#entry348comment</comments>
      <pubDate>Thu, 18 Jul 2024 22:03:39 +0900</pubDate>
    </item>
    <item>
      <title>타입 제너릭</title>
      <link>https://oddcode.tistory.com/347</link>
      <description>&lt;h1&gt;제너릭 (Generics)&lt;/h1&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;제너릭은 타입을 파라미터로 전달하여 타입 안정성을 확보하는 방법입니다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;h2&gt;함수&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;interface Obj {
    x: number;
}
type Arr = [number, number];

function toArray(a: string, b: string): string[];
function toArray(a: number, b: number): number[];
function toArray(a: boolean, b: boolean): boolean[];
function toArray(a: Obj, b: Obj): Obj[];
function toArray(a: Arr, b: Arr): Arr[];
function toArray(a: any, b: any) {
    return [a, b];
}

console.log(
    toArray(&amp;#39;a&amp;#39;, &amp;#39;b&amp;#39;),
    toArray(1, 2),
    toArray(true, false),
    toArray({ x: 1 }, { x: 2 }),
    toArray([1, 2], [3, 4]),
);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위의 오버로딩 함수를 제너릭으로 변경하면 다음과 같습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;interface Obj {
    x: number;
}
type Arr = [number, number];

// 제너릭 문법을 사용하여 타입을 파라미터로 전달한다.
function toArray&amp;lt;T, U&amp;gt;(a: T, b: U): [T, U] {
    return [a, b];
}

console.log(
    toArray&amp;lt;string, string&amp;gt;(&amp;#39;a&amp;#39;, &amp;#39;b&amp;#39;), // 꺽쇠 괄호 안에 타입을 명시적으로 지정 // 추론 가능하여 생략 가능하다.
    toArray&amp;lt;number, number&amp;gt;(1, 2),
    toArray&amp;lt;boolean, boolean&amp;gt;(true, false),
    toArray&amp;lt;Obj, Obj&amp;gt;({ x: 1 }, { x: 2 }),
    toArray&amp;lt;Arr, Arr&amp;gt;([1, 2], [3, 4, 5]), // 오류 발생 Arr 타입에서 요소의 개수가 다르기 때문
);&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;클래스&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;class User {
    public name;
    constructor(name: string) {
        this.name = name;
    }
    getName(): string {
        return this.name;
    }
}

const user1 = new User(&amp;#39;김가을&amp;#39;);
console.log(user1.getName());&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위 코드를 축약하면...&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;class User {
    constructor(public name: string) {}
    getName(): string {
        return this.name;
    }
}

(...)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위 코드를 제너릭으로 변경하면 다음과 같습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;// 제너릭 문법을 사용하여 타입을 파라미터로 전달한다.
class User&amp;lt;T&amp;gt; {
    constructor(public name: T) {}
    getName(): T {
        return this.name;
    }
}

// 꺽쇠 괄호 안에 타입을 명시적으로 지정
const user1 = new User&amp;lt;string&amp;gt;(&amp;#39;김가을&amp;#39;);
console.log(user1.getName());&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;인터페이스&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;interface User&amp;lt;T&amp;gt; {
    name: string;
    value: T;
}

const user1: User&amp;lt;string&amp;gt; = {
    name: &amp;#39;김가을&amp;#39;,
    value: &amp;#39;귀여움&amp;#39;,
};
const user2: User&amp;lt;number&amp;gt; = {
    name: &amp;#39;김겨울&amp;#39;,
    value: 22,
};
const user3: User&amp;lt;boolean&amp;gt; = {
    name: &amp;#39;김봄&amp;#39;,
    value: true,
};
const user4: User&amp;lt;number[]&amp;gt; = {
    name: &amp;#39;김여름&amp;#39;,
    value: [1, 2, 3],
};&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;제너릭 타입 제약&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;interface User&amp;lt;T extends string | number&amp;gt; {
    name: string;
    value: T;
}

const user1: User&amp;lt;string&amp;gt; = {
    name: &amp;#39;김가을&amp;#39;,
    value: &amp;#39;귀여움&amp;#39;,
};
const user2: User&amp;lt;number&amp;gt; = {
    name: &amp;#39;김겨울&amp;#39;,
    value: 22,
};

// 제약 조건을 만족하지 않으므로 아래 코드는 오류 발생
const user3: User&amp;lt;boolean&amp;gt; = {
    name: &amp;#39;김봄&amp;#39;,
    value: true,
};
const user4: User&amp;lt;number[]&amp;gt; = {
    name: &amp;#39;김여름&amp;#39;,
    value: [1, 2, 3],
};&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Front/TypeScript</category>
      <author>oodada</author>
      <guid isPermaLink="true">https://oddcode.tistory.com/347</guid>
      <comments>https://oddcode.tistory.com/347#entry347comment</comments>
      <pubDate>Thu, 18 Jul 2024 22:02:56 +0900</pubDate>
    </item>
    <item>
      <title>타입 별칭, this, 오버로딩, 클래스</title>
      <link>https://oddcode.tistory.com/346</link>
      <description>&lt;h1&gt;타입 별칭 (Type Aliases)&lt;/h1&gt;
&lt;p&gt;타입 별칭은 새로운 타입을 정의하는 용도로 사용됩니다. 타입 별칭은 기존 타입을 참조하여 새로운 타입을 정의할 수 있습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;type User =
    | {
          name: string;
          age: number;
          role: boolean;
      }
    | [string, number, boolean];

const user1: User = {
    name: &amp;#39;김가을&amp;#39;,
    age: 2,
    role: false,
};

const user2: User = [&amp;#39;김겨울&amp;#39;, 22, true];&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;명시적 this&lt;/h1&gt;
&lt;p&gt;인터페이스를 사용하여 클래스의 메서드 체이닝을 구현할 수 있습니다. 이때 인터페이스에 명시적 this를 사용하여 메서드 체이닝을 구현할 수 있습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;interface Cat {
    name: string;
    age: number;
}

const cat: Cat = {
    name: &amp;#39;김가을&amp;#39;,
    age: 2,
};

// 문법: this: Type
function setName(this: Cat, name: string) {
    console.log(`이름을 변경합니다: ${this.name} -&amp;gt; ${name}`);
    // 일반 함수에서의 this는 호출 시점에 결정된다.
    // setName 함수를 호출할 때 this는 cat 객체를 가리킨다.
    // setName 함수 내부에서 this는 cat 객체를 가리키므로 cat 객체의 name 프로퍼티를 변경할 수 있다.
    // any 타입을 사용하지 않고 명시적으로 this를 지정하여 타입 안정성을 확보할 수 있다.
    // Cat interface를 사용하여 this가 Cat 타입임을 명시적으로 지정한다.
}

setName.call(cat, &amp;#39;김겨울&amp;#39;);
// call 메서드를 사용하여 setName 함수를 호출할 때 this를 cat 객체로 지정한다.
// setName 함수를 cat 객체의 메서드로 호출할 수 있다.&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;오버로딩 (Overloading)&lt;/h1&gt;
&lt;p&gt;오버로딩은 함수의 이름은 같지만 매개변수의 타입 또는 개수가 다른 여러 함수를 정의하는 방법입니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;function add1(a: number, b: number) {
    return a + b;
}

function add2(a: string, b: string) {
    return a + b;
}

add1(1, 2); // 3
add2(&amp;#39;겨울이&amp;#39;, &amp;#39; 바보&amp;#39;); // &amp;#39;helloworld&amp;#39;
add1(1, &amp;#39;겨울이&amp;#39;); // 오류 발생
add2(1, 2); // 오류 발생&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;function add(a: number, b: number): number; // 타입 선언
function add(a: string, b: string): string; // 타입 선언
// 함수 구현
function add(a: any, b: any) {
    return a + b;
}

add(1, 2); // 3
add(&amp;#39;가을이&amp;#39;, &amp;#39; 바보&amp;#39;);
add(1, &amp;#39; 바보&amp;#39;); // 2가지 중 하나의 타입만 사용할 수 있다.&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;클래스&lt;/h1&gt;
&lt;h2&gt;접근 제한자 (Access Modifiers)&lt;/h2&gt;
&lt;p&gt;접근 제한자는 클래스의 멤버에 접근할 수 있는 범위를 지정하는 용도로 사용됩니다. 접근 제한자는 &lt;code&gt;public&lt;/code&gt;, &lt;code&gt;protected&lt;/code&gt;, &lt;code&gt;private&lt;/code&gt; 키워드로 지정할 수 있습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;public&lt;/code&gt;: 외부에서 접근 가능, 클래스 바디에서 생략 가능&lt;/li&gt;
&lt;li&gt;&lt;code&gt;protected&lt;/code&gt;: 상속받은 클래스에서 접근 가능&lt;/li&gt;
&lt;li&gt;&lt;code&gt;private&lt;/code&gt;: 클래스 내부에서만 접근 가능 (비공개)&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;class Animal {
    // 속성 타입 지정
    public name: string = &amp;#39;&amp;#39;; // 외부에서 접근 가능
    protected age: number = 0; // 상속받은 클래스에서 접근 가능
    private weight: number = 0; // 클래스 내부에서만 접근 가능

    constructor(name: string, age: number, weight: number) {
        this.name = name;
        this.age = age;
        this.weight = weight;
    }
    // 메서드에 public을 붙이면 외부에서 접근 가능
    public print() {
        console.log(`이름: ${this.name}, 나이: ${this.age}, 몸무게: ${this.weight}`);
    }
}

class Cat extends Animal {
    print() {
        console.log(`${this.name}은 ${this.age}살이고, 몸무게는 ${this.weight}kg입니다.`);
    }
}

const cat = new Animal(&amp;#39;김겨울&amp;#39;, 2, 3);
cat.print(); // &amp;#39;이름: 김겨울, 나이: 2, 몸무게: 3&amp;#39;

const cat2 = new Cat(&amp;#39;김가을&amp;#39;, 3, 6);
cat2.print(); // &amp;#39;김가을은 3살입니다.&amp;#39;

console.log(cat.name); // &amp;#39;김겨울&amp;#39;
console.log(cat.age); // 상속받은 클래스에서 접근 가능
console.log(cat.weight); // 클래스 내부에서만 접근 가능&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;각각의 속성과 constructor의 매개변수가 동일한 이름을 가지고 있을 때는 생성자의 매개변수에 접근 제한자를 붙여서 속성을 선언할 수 있습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;class Animal {
    constructor(
        public name: string = &amp;#39;&amp;#39;,
        protected age: number = 0,
        private weight: number = 0,
    ) {}

    public print() {
        console.log(`이름: ${this.name}, 나이: ${this.age}, 몸무게: ${this.weight}`);
    }
}

class Cat extends Animal {
    print() {
        console.log(`${this.name}은 ${this.age}살이고, 몸무게는 ${this.weight}kg입니다.`);
    }
}

const cat = new Animal(&amp;#39;김겨울&amp;#39;, 2, 3);
cat.print(); // &amp;#39;이름: 김겨울, 나이: 2, 몸무게: 3&amp;#39;

const cat2 = new Cat(&amp;#39;김가을&amp;#39;, 3, 6);
cat2.print(); // &amp;#39;김가을은 3살입니다.&amp;#39;

console.log(cat.name); // &amp;#39;김겨울&amp;#39;
console.log(cat.age); // 상속받은 클래스에서 접근 가능
console.log(cat.weight); // 클래스 내부에서만 접근 가능&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Front/TypeScript</category>
      <author>oodada</author>
      <guid isPermaLink="true">https://oddcode.tistory.com/346</guid>
      <comments>https://oddcode.tistory.com/346#entry346comment</comments>
      <pubDate>Thu, 18 Jul 2024 22:02:18 +0900</pubDate>
    </item>
    <item>
      <title>타입 인터페이스 (Interface)</title>
      <link>https://oddcode.tistory.com/345</link>
      <description>&lt;h1&gt;인터페이스 (Interface)&lt;/h1&gt;
&lt;p&gt;인터페이스란 객체 데이터의 타입을 지정하는 용도로 사용되는 문법입니다. 인터페이스는 객체의 구조를 정의하는 형태로 사용되며, 객체의 타입을 지정하는 역할을 합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;// 인터페이스 정의 (대문자로 시작)
interface User {
    name: string;
    age: number;
    isAdult: boolean;
}

const user1: User = {
    name: &amp;#39;김겨울&amp;#39;,
    age: 22,
    isAdult: true,
};

const user2: User = {
    name: &amp;#39;김가을&amp;#39;,
    age: 2,
    // `isAdult` 프로퍼티가 누락되어 타입 오류가 발생합니다.
    // isAdult: false,
};&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위 코드에서 &lt;code&gt;user2&lt;/code&gt; 객체는 &lt;code&gt;isAdult&lt;/code&gt; 프로퍼티가 누락되어 타입 오류가 발생합니다.&lt;/p&gt;
&lt;h2&gt;선택적 프로퍼티 (Optional Properties)&lt;/h2&gt;
&lt;p&gt;선택적으로 사용할 수 있는 프로퍼티를 정의할 때는 프로퍼티 이름 뒤에 &lt;code&gt;?&lt;/code&gt; 기호를 붙입니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;interface User {
    name: string;
    age: number;
    isAdult?: boolean; // ? 기호를 붙여 선택적 프로퍼티로 지정
}

const user1: User = {
    name: &amp;#39;김겨울&amp;#39;,
    age: 22,
    isAdult: true,
};

const user2: User = {
    name: &amp;#39;김가을&amp;#39;,
    age: 2,
    // `isAdult` 프로퍼티가 누락되어도 타입 오류가 발생하지 않습니다.
};&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;읽기 전용 프로퍼티 (Readonly Properties)&lt;/h2&gt;
&lt;p&gt;읽기 전용 프로퍼티를 정의할 때는 프로퍼티 이름 앞에 &lt;code&gt;readonly&lt;/code&gt; 키워드를 붙입니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;interface User {
    readonly name: string; // readonly 키워드를 붙여 읽기 전용 프로퍼티로 지정
    age: number;
    isAdult?: boolean;
}

const user1: User = {
    name: &amp;#39;김겨울&amp;#39;,
    age: 22,
    isAdult: true,
};

user1.age = 30; // age 프로퍼티는 읽기/쓰기 모두 가능
user1.name = &amp;#39;홍길동&amp;#39;; // name 프로퍼티는 읽기 전용이므로 오류 발생

const user2: User = {
    name: &amp;#39;김가을&amp;#39;,
    age: 2,
};&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;함수 인터페이스 (Function Interface) - 호출 시그니처 (Call Signature)&lt;/h2&gt;
&lt;p&gt;함수 인터페이스는 함수의 타입을 지정하는 용도로 사용됩니다. 함수 인터페이스는 함수의 매개변수와 반환 타입을 정의하는 형태로 사용됩니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;interface GetName {
    (param: string): string; // 매개변수와 반환 타입을 정의
    // 매개변수의 이름은 상관없으며, 반환 타입과 갯수만 일치하면 됩니다.
}

interface User {
    name: string;
    age: number;
    getName: GetName;
    // getName: (param: string) =&amp;gt; string;
}

const user1: User = {
    name: &amp;#39;김가을&amp;#39;,
    age: 2,
    getName(name: string) {
        return `내 이름은 ${name}입니다.`;
    },
};

console.log(user1.getName);&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;인덱스 가능 타입 (Indexable Types) - 인덱스 시그니처 (Index Signature)&lt;/h2&gt;
&lt;p&gt;인덱스 가능 타입은 객체의 프로퍼티 이름이 동적으로 변하는 경우 사용됩니다. 인덱스 시그니처는 객체의 프로퍼티 이름과 값의 타입을 정의하는 형태로 사용됩니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;interface animals {
    [item: number]: string; // 인덱스 시그니처
    // 위와 같이 number로 지정하면 배열처럼 사용할 수 있다.
}

const arr: animals = [&amp;#39;dog&amp;#39;, &amp;#39;cat&amp;#39;, &amp;#39;fish&amp;#39;];
console.log(arr[0]); // &amp;#39;dog&amp;#39;
console.log(arr[1]); // &amp;#39;cat&amp;#39;
console.log(arr[2]); // &amp;#39;fish&amp;#39;&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;interface User {
    [index: string]: unknown; // 인덱스 시그니처
    // unknown은 모든 타입을 의미한다.
}

const obj: User = {
    name: &amp;#39;김가을&amp;#39;,
    age: &amp;#39;2&amp;#39;,
};

console.log(obj[&amp;#39;name&amp;#39;]); // &amp;#39;김가을&amp;#39;
console.log(obj[&amp;#39;age&amp;#39;]); // &amp;#39;2&amp;#39;

obj[&amp;#39;name&amp;#39;] = &amp;#39;김봄&amp;#39;; // &amp;#39;김가을&amp;#39; -&amp;gt; &amp;#39;김봄&amp;#39;
obj[&amp;#39;email&amp;#39;] = &amp;#39;eehd@mail.com&amp;#39;; // &amp;#39;undefined&amp;#39; -&amp;gt; &amp;#39;eehd@mail.com&amp;#39;
console.log(obj);&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;확장 인터페이스 (Extending Interfaces)&lt;/h2&gt;
&lt;p&gt;인터페이스는 다른 인터페이스를 확장할 수 있습니다. 인터페이스 확장은 기존 인터페이스의 프로퍼티를 상속받아 사용할 수 있습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;interface User {
    name: string;
    age: number;
}

interface Admin extends User {
    role: string;
}

const user: User = {
    name: &amp;#39;김가을&amp;#39;,
    age: 2,
    // role: &amp;#39;사용자&amp;#39;, // User 인터페이스에는 role 프로퍼티가 없어 타입 오류 발생
};

const admin: Admin = {
    name: &amp;#39;김겨울&amp;#39;,
    age: 22,
    role: &amp;#39;관리자&amp;#39;,
};&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;상속 인터페이스 (Interface Inheritance)&lt;/h2&gt;
&lt;p&gt;인터페이스는 클래스를 상속할 수 있습니다. 인터페이스 상속은 클래스의 프로퍼티를 상속받아 사용할 수 있습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;interface User {
    name: string;
    age: number;
}

// 위 인터페이스를 상속받아 새로운 인터페이스를 만들 수 있다.
interface User {
    role: string;
    // 후속 인터페이스에서 동일한 프로퍼티를 재정의해야 합니다.
}

const user1: User = {
    name: &amp;#39;김가을&amp;#39;,
    age: 2,
    role: &amp;#39;사용자&amp;#39;,
};&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Front/TypeScript</category>
      <author>oodada</author>
      <guid isPermaLink="true">https://oddcode.tistory.com/345</guid>
      <comments>https://oddcode.tistory.com/345#entry345comment</comments>
      <pubDate>Thu, 18 Jul 2024 22:01:33 +0900</pubDate>
    </item>
    <item>
      <title>타입 추론, 할당, 단언, 가드</title>
      <link>https://oddcode.tistory.com/344</link>
      <description>&lt;h1&gt;타입 추론 (Type Inference)&lt;/h1&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;최대한 타입을 적게 명시하여 코드를 간결하게 작성하는 것이 좋음&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;ul&gt;
&lt;li&gt;타입 추론이란 TypeScript가 자동으로 타입을 추론하는 것&lt;/li&gt;
&lt;li&gt;변수나 함수의 타입을 명시하지 않아도 타입을 추론하여 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;let str = &amp;#39;문자열&amp;#39;; // 타입스크립트가 str을 문자열 타입으로 추론
str = 123; // Error: Type &amp;#39;123&amp;#39; is not assignable to type &amp;#39;string&amp;#39;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;01. 변수 추론&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;초기화된 변수&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;let str = &amp;#39;문자열&amp;#39;; // 타입스크립트가 str을 문자열 타입으로 추론
let num = 123;
let bool = true;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;기본값이 설정된 매개변수&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;// a와 b의 타입을 명시하지 않아도 타입스크립트가 number 타입으로 추론
function add(a: number, b = 2) {
    return a + b;
}

console.log(add()); // 3&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;반환값이 있는 함수&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;// 반환값이 있는 함수는 타입스크립트가 반환값의 타입을 추론
function hello(name: string) {
    return `Hello, ${name}!`;
}

console.log(hello(&amp;#39;TypeScript&amp;#39;)); // Hello, TypeScript!&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;타입 단언 (Assertion)&lt;/h1&gt;
&lt;p&gt;타입 단언(Type Assertion)은 &lt;strong&gt;타입스크립트 컴파일러에게 &amp;quot;내가 더 잘 알고 있어&amp;quot; 라고 말해주는 방법&lt;/strong&gt;입니다. 타입 단언은 다른 언어에서 비슷한 개념으로 &amp;quot;타입 캐스팅(Type Casting)&amp;quot; 이라고도 불립니다.&lt;/p&gt;
&lt;h2&gt;01. &lt;code&gt;as&lt;/code&gt; 키워드&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;as&lt;/code&gt; 키워드를 사용하는 방법은 JSX를 사용할 때 &lt;code&gt;&amp;lt;타입&amp;gt;&lt;/code&gt; 문법과 혼동을 피하기 위해 도입된 방법입니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;const el = document.querySelector(&amp;#39;body&amp;#39;); // HTMLElement | null
el.textContent = &amp;#39;주말이 끝났다&amp;#39;; // `el`는 `null`일 수 있습니다.&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;document.querySelector&lt;/code&gt;는 선택자 문자열을 인수로 받아 해당 선택자와 일치하는 문서 내 첫 번째 요소를 반환합니다. 만약 문서 내에 해당 요소가 존재하지 않는다면, 반환 값은 &lt;code&gt;null&lt;/code&gt;이 됩니다.&lt;/p&gt;
&lt;p&gt;이런 상황에서 타입스크립트는 &lt;code&gt;null&lt;/code&gt;일 가능성을 고려하여 &lt;code&gt;el&lt;/code&gt; 변수의 타입을 &lt;code&gt;HTMLElement | null&lt;/code&gt;로 간주합니다. 따라서 &lt;code&gt;el&lt;/code&gt; 변수를 개발자가 명확하게 &lt;code&gt;HTMLElement&lt;/code&gt; 타입으로 단언해주어야 합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;const el = document.querySelector(&amp;#39;body&amp;#39;) as HTMLElement;
el.textContent = &amp;#39;주말이 끝났다&amp;#39;; // `el`은 `null`일 수 없습니다.&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 경우, &lt;code&gt;el&lt;/code&gt; 변수는 &lt;code&gt;HTMLElement&lt;/code&gt; 타입으로 단언되었기 때문에 &lt;code&gt;null&lt;/code&gt;일 가능성이 없어졌습니다.&lt;/p&gt;
&lt;h2&gt;02. non-null 단언 연산자 (&lt;code&gt;!&lt;/code&gt;)&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;!&lt;/code&gt; 연산자는 &lt;code&gt;null&lt;/code&gt; 또는 &lt;code&gt;undefined&lt;/code&gt;가 아님을 단언하는 연산자입니다. 이 연산자는 변수 뒤에 붙여 사용합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;const el = document.querySelector(&amp;#39;body&amp;#39;)!;
el.textContent = &amp;#39;주말이 끝났다&amp;#39;; // `el`은 `null`일 수 없습니다.&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;!&lt;/code&gt; 연산자를 사용하면 &lt;code&gt;null&lt;/code&gt; 또는 &lt;code&gt;undefined&lt;/code&gt;가 아님을 단언할 수 있습니다. 하지만, &lt;code&gt;!&lt;/code&gt; 연산자를 남발하면 코드의 가독성이 떨어지고, &lt;code&gt;null&lt;/code&gt; 또는 &lt;code&gt;undefined&lt;/code&gt;로 인한 오류가 발생할 수 있습니다. 따라서, &lt;code&gt;!&lt;/code&gt; 연산자 사용은 신중하게 사용해야 합니다.&lt;/p&gt;
&lt;h2&gt;03. &lt;code&gt;as&lt;/code&gt; 키워드 vs &lt;code&gt;&amp;lt;타입&amp;gt;&lt;/code&gt; 문법&lt;/h2&gt;
&lt;p&gt;아래 예제에서 &lt;code&gt;getNumber&lt;/code&gt; 함수는 &lt;code&gt;number&lt;/code&gt;, &lt;code&gt;null&lt;/code&gt;, 또는 &lt;code&gt;undefined&lt;/code&gt; 값을 받을 수 있습니다. 그러나 &lt;code&gt;null&lt;/code&gt; 또는 &lt;code&gt;undefined&lt;/code&gt; 값을 받았을 때 &lt;code&gt;toFixed&lt;/code&gt; 메서드를 호출하려 하면 오류가 발생합니다. 이 문제를 해결하려면 함수 내부에서 &lt;code&gt;타입 단언&lt;/code&gt; 또는 &lt;code&gt;null&lt;/code&gt; 및 &lt;code&gt;undefined&lt;/code&gt; 체크를 해줘야 합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;function getNumber(x: number | null | undefined) {
    return Number(x.toFixed(2)); // 오류 발생
}

getNumber(3.14);
getNumber(null);&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- &lt;code&gt;as&lt;/code&gt; 키워드 사용&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;function getNumber(x: number | null | undefined) {
    return Number((x as number).toFixed(2)); // 타입 단언
}

getNumber(3.14);
getNumber(null);&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- &lt;code&gt;non-null&lt;/code&gt; 단언 연산자 사용&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;function getNumber(x: number | null | undefined) {
    return Number(x!.toFixed(2)); // non-null 단언 연산자
}

getNumber(3.14);
getNumber(null);&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- 조건문 사용&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;function getNumber(x: number | null | undefined) {
    if (x === null || x === undefined) {
        return 0;
    }
    return Number(x.toFixed(2));
}

getNumber(3.14);
getNumber(null);&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;할당 단언 (Non-null Assertion)&lt;/h1&gt;
&lt;p&gt;타입스크립트에서는 타입을 명시적으로 지정하지 않은 변수에 값을 할당할 때 오류가 발생합니다. 이때, 변수 뒤에 &lt;code&gt;!&lt;/code&gt; 연산자를 붙여 할당 단언을 사용하면 오류를 해결할 수 있습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;let num!: number;
// num = 10;
console.log(num); // 변수가 할당되기 전에 사용하면 오류 발생

num = 10;&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;타입 가드 (Type Guard)&lt;/h1&gt;
&lt;p&gt;타입 가드(Type Guard)는 런타임에 발생하는 오류를 줄이기 위해 사용하는 타입스크립트의 기능입니다. 타입 가드는 &lt;strong&gt;작성된 코드가 실제로 잘 동작할 수 있도록 코드 상에서 방어 역할&lt;/strong&gt;을 합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;// Element | null 타입을 Element 타입으로 단언
function logText(el: Element) {
    console.log(el.textContent);
}

const el = document.querySelector(&amp;#39;div&amp;#39;);
logText(el); // el이 null일 가능성이 있어 오류 발생&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위 코드에서 &lt;code&gt;logText&lt;/code&gt; 함수는 &lt;code&gt;Element&lt;/code&gt; 타입을 인수로 받습니다. 그러나 &lt;code&gt;document.querySelector&lt;/code&gt; 메서드는 &lt;code&gt;Element&lt;/code&gt; 또는 &lt;code&gt;null&lt;/code&gt;을 반환할 수 있습니다. 따라서 &lt;code&gt;el&lt;/code&gt; 변수를 &lt;code&gt;Element&lt;/code&gt; 타입으로 단언해주어야 합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;function logText(el: Element) {
    console.log(el.textContent);
}

const el = document.querySelector(&amp;#39;div&amp;#39;) as Element;
logText(el);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;as 키워드를 사용하여 &lt;code&gt;el&lt;/code&gt; 변수를 &lt;code&gt;Element&lt;/code&gt; 타입으로 단언하면 빨간 줄은 사라지지만 콘솔창에서는 오류가 발생합니다. 이는 &lt;code&gt;el&lt;/code&gt; 변수가 &lt;code&gt;null&lt;/code&gt;일 가능성이 있기 때문입니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;// Element 타입을 반환하는 함수
function logText(el: Element | null) {
    if (el === null) {
        return;
    }
    console.log(el.textContent);
}

const el = document.querySelector(&amp;#39;div&amp;#39;);
logText(el);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;if 문을 사용하여 &lt;code&gt;el&lt;/code&gt; 변수가 &lt;code&gt;null&lt;/code&gt;인지 확인한 후, &lt;code&gt;el&lt;/code&gt; 변수를 &lt;code&gt;Element&lt;/code&gt; 타입으로 사용하면 오류가 발생하지 않습니다.&lt;/p&gt;</description>
      <category>Front/TypeScript</category>
      <author>oodada</author>
      <guid isPermaLink="true">https://oddcode.tistory.com/344</guid>
      <comments>https://oddcode.tistory.com/344#entry344comment</comments>
      <pubDate>Thu, 18 Jul 2024 22:01:04 +0900</pubDate>
    </item>
    <item>
      <title>타입 종류 (TypeScript Types)</title>
      <link>https://oddcode.tistory.com/342</link>
      <description>&lt;h1&gt;타입 종류 (TypeScript Types)&lt;/h1&gt;
&lt;h2&gt;01. 타입 종류&lt;/h2&gt;
&lt;h3&gt;- 문자&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;code&gt;let str: string = &amp;#39;문자열&amp;#39;&lt;/code&gt;&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;let str: string;
let name: string = &amp;#39;김가을&amp;#39;;
let hello: string = `안녕하세요. ${blue}님`;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- 숫자&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;code&gt;let num: number = 123&lt;/code&gt;&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;let num: number; // undefined
num = 123; // 123
console.log(num);

let integer: number = 6;
let float: number = 3.14;
let hex: number = 0xf00d;
let infinity: number = Infinity;
let nan: number = NaN;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- 불리언&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;code&gt;let bool: boolean = true&lt;/code&gt;&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;let bool: boolean;
let isTrue: boolean = true;
let isFalse: boolean = false;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- 배열&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;code&gt;let arr: 배열타입[] = [값, 값, 값]&lt;/code&gt;&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;let arr: number[] = [1, 2, 3];
let animals: string[] = [&amp;#39;사자&amp;#39;, &amp;#39;호랑이&amp;#39;, &amp;#39;코끼리&amp;#39;];
let empty: never[] = []; // 빈 배열
let union: (string | number)[] = [&amp;#39;one&amp;#39;, 2]; // 유니언 타입
let any: any[] = [1, &amp;#39;two&amp;#39;, true]; // any 타입&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- 튜플 : 고정된 요소 수 만큼의 타입을 미리 선언 후 배열을 표현&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;code&gt;let tuple: [타입, 타입, ...] = [값, 값, ...]&lt;/code&gt;&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;let tuple: [string, number] = [&amp;#39;one&amp;#39;, 1];
let person: [string, number] = [&amp;#39;김겨울&amp;#39;, 30];&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- 객체&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;code&gt;let obj: { key: 타입, key: 타입 } = { key: 값, key: 값 }&lt;/code&gt;&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;let obj: { name: string; age: number } = { name: &amp;#39;김겨울&amp;#39;, age: 30 };
let person: { name: string; age: number; etc?: boolean } = { name: &amp;#39;김가을&amp;#39;, age: 30, etc: true };
// ? : 선택적 프로퍼티
// etc?: boolean : etc 키는 있을 수도 있고 없을 수도 있음&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- 객체 interface&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;객체의 타입을 미리 정의하여 사용&lt;/li&gt;
&lt;li&gt;재사용성이 가능하고 가독성이 좋음&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;code&gt;interface 인터페이스 { key: 타입, key: 타입 }&lt;/code&gt;&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;// interface 명명규칙 : 대문자로 시작
interface IPerson {
    name: string;
    age: number;
    etc?: boolean;
}

let userA: IPerson = { name: &amp;#39;김겨울&amp;#39;, age: 3 };
let userB: IPerson = { name: &amp;#39;김가을&amp;#39;, age: 2, etc: true };&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- 함수 타입&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;code&gt;let 함수명: (매개변수: 타입) =&amp;gt; 반환타입 = (매개변수) =&amp;gt; { ... }&lt;/code&gt;&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;const sum: (a: number, b: number) =&amp;gt; number = (a, b) =&amp;gt; {
    return a + b;
};

sum(1, 2); // 3&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;// void : 반환값이 없을 때 사용
const hello: (name: string) =&amp;gt; void = name =&amp;gt; {
    console.log(`Hello, ${name}!`);
};

hello(&amp;#39;TypeScript&amp;#39;); // Hello, TypeScript!&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;06. React에서 ts 사용하기&lt;/h2&gt;
&lt;p&gt;타입스크립트를 사용하면 컴포넌트의 타입을 지정하여 개발할 수 있음&lt;/p&gt;
&lt;h3&gt;- 컴포넌트 생성&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;컴포넌트를 생성할 때 타입을 지정하기 위해 &lt;code&gt;React.FC&amp;lt;Props&amp;gt;&lt;/code&gt;를 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;code&gt;const 컴포넌트명: React.FC&amp;lt;Props&amp;gt; = ({ props }) =&amp;gt; { ... }&lt;/code&gt;&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;h4&gt;문법&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;import React from &amp;#39;react&amp;#39;;

// Props 타입 정의
type Props = {
    name: string;
};

// 컴포넌트 정의
const 컴포넌트이름: React.FC&amp;lt;Props&amp;gt; = ({ name }) =&amp;gt; {
    return &amp;lt;div&amp;gt;Hello, {name}!&amp;lt;/div&amp;gt;;
};

export default Hello;&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;예시&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;// src/components/Hello.tsx
import React from &amp;#39;react&amp;#39;;

type HelloProps = {
    name: string;
};

const Hello: React.FC&amp;lt;HelloProps&amp;gt; = ({ name }) =&amp;gt; {
    return &amp;lt;div&amp;gt;Hello, {name}!&amp;lt;/div&amp;gt;;
};

export default Hello;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- useState 사용하기&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;code&gt;const [state, setState] = useState&amp;lt;타입&amp;gt;(초기값)&lt;/code&gt;&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;// src/components/Counter.tsx
import React, { useState } from &amp;#39;react&amp;#39;;

const Counter: React.FC = () =&amp;gt; {
    const [count, setCount] = useState&amp;lt;number&amp;gt;(0);

    return (
        &amp;lt;div&amp;gt;
            &amp;lt;p&amp;gt;{count}&amp;lt;/p&amp;gt;
            &amp;lt;button onClick={() =&amp;gt; setCount(count + 1)}&amp;gt;+1&amp;lt;/button&amp;gt;
            &amp;lt;button onClick={() =&amp;gt; setCount(count - 1)}&amp;gt;-1&amp;lt;/button&amp;gt;
        &amp;lt;/div&amp;gt;
    );
};

export default Counter;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- useReducer 사용하기&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;code&gt;const [state, dispatch] = useReducer&amp;lt;Reducer&amp;lt;상태타입, 액션타입&amp;gt;&amp;gt;(reducer, 초기값)&lt;/code&gt;&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;// src/components/Counter.tsx
import React, { useReducer } from &amp;#39;react&amp;#39;;

type Action = { type: &amp;#39;INCREASE&amp;#39; } | { type: &amp;#39;DECREASE&amp;#39; };

function reducer(state: number, action: Action): number {
    switch (action.type) {
        case &amp;#39;INCREASE&amp;#39;:
            return state + 1;
        case &amp;#39;DECREASE&amp;#39;:
            return state - 1;
        default:
            throw new Error(&amp;#39;Unhandled action&amp;#39;);
    }
}

const Counter: React.FC = () =&amp;gt; {
    const [count, dispatch] = useReducer(reducer, 0);

    return (
        &amp;lt;div&amp;gt;
            &amp;lt;p&amp;gt;{count}&amp;lt;/p&amp;gt;
            &amp;lt;button onClick={() =&amp;gt; dispatch({ type: &amp;#39;INCREASE&amp;#39; })}&amp;gt;+1&amp;lt;/button&amp;gt;
            &amp;lt;button onClick={() =&amp;gt; dispatch({ type: &amp;#39;DECREASE&amp;#39; })}&amp;gt;-1&amp;lt;/button&amp;gt;
        &amp;lt;/div&amp;gt;
    );
};

export default Counter;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- useContext 사용하기&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;code&gt;const value = useContext&amp;lt;타입&amp;gt;(Context)&lt;/code&gt;&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;// src/components/Counter.tsx
import React, { useContext } from &amp;#39;react&amp;#39;;
import { CounterContext } from &amp;#39;../contexts/CounterContext&amp;#39;;

const Counter: React.FC = () =&amp;gt; {
    const { count, dispatch } = useContext(CounterContext);

    return (
        &amp;lt;div&amp;gt;
            &amp;lt;p&amp;gt;{count}&amp;lt;/p&amp;gt;
            &amp;lt;button onClick={() =&amp;gt; dispatch({ type: &amp;#39;INCREASE&amp;#39; })}&amp;gt;+1&amp;lt;/button&amp;gt;
            &amp;lt;button onClick={() =&amp;gt; dispatch({ type: &amp;#39;DECREASE&amp;#39; })}&amp;gt;-1&amp;lt;/button&amp;gt;
        &amp;lt;/div&amp;gt;
    );
};

export default Counter;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- useRef 사용하기&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;code&gt;const ref = useRef&amp;lt;타입&amp;gt;(null)&lt;/code&gt;&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;// src/components/Counter.tsx
import React, { useRef } from &amp;#39;react&amp;#39;;

const Counter: React.FC = () =&amp;gt; {
    const inputRef = useRef&amp;lt;HTMLInputElement&amp;gt;(null);

    return (
        &amp;lt;div&amp;gt;
            &amp;lt;input ref={inputRef} /&amp;gt;
            &amp;lt;button onClick={() =&amp;gt; inputRef.current?.focus()}&amp;gt;Focus&amp;lt;/button&amp;gt;
        &amp;lt;/div&amp;gt;
    );
};

export default Counter;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- interface 사용하기&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;code&gt;import { 인터페이스 } from &amp;#39;파일경로&amp;#39;&lt;/code&gt;&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;// src/interfaces/index.ts
export interface HelloProps {
    name: string;
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;// src/components/Hello.tsx
import React from &amp;#39;react&amp;#39;;
import { HelloProps } from &amp;#39;../interfaces&amp;#39;;

const Hello: React.FC&amp;lt;HelloProps&amp;gt; = ({ name }) =&amp;gt; {
    return &amp;lt;div&amp;gt;Hello, {name}!&amp;lt;/div&amp;gt;;
};

export default Hello;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- 타입스크립트로 리액트 컴포넌트 작성 시 주의사항&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;컴포넌트의 props나 state를 정의할 때 &lt;code&gt;interface&lt;/code&gt;나 &lt;code&gt;type&lt;/code&gt;으로 정의&lt;/li&gt;
&lt;li&gt;컴포넌트의 props나 state를 정의할 때 &lt;code&gt;React.FC&lt;/code&gt;를 사용하여 정의&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Front/TypeScript</category>
      <author>oodada</author>
      <guid isPermaLink="true">https://oddcode.tistory.com/342</guid>
      <comments>https://oddcode.tistory.com/342#entry342comment</comments>
      <pubDate>Thu, 18 Jul 2024 21:58:56 +0900</pubDate>
    </item>
    <item>
      <title>javascript 커리큘럼</title>
      <link>https://oddcode.tistory.com/341</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;- JS 목차&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 275px;&quot; border=&quot;1&quot; data-ke-style=&quot;style4&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 64.7674%; height: 19px;&quot;&gt;js 핵심 개념&lt;/td&gt;
&lt;td style=&quot;width: 35.2326%; height: 19px;&quot;&gt;&lt;a href=&quot;https://odada.me/334&quot;&gt;https://odada.me/334&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 64.7674%;&quot;&gt;react 핵심 개념&lt;/td&gt;
&lt;td style=&quot;width: 35.2326%;&quot;&gt;&lt;a href=&quot;https://odada.me/353&quot;&gt;https://odada.me/353&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 64.7674%;&quot;&gt;node.js, npm 개념 잡기&lt;/td&gt;
&lt;td style=&quot;width: 35.2326%;&quot;&gt;&lt;a href=&quot;https://odada.me/246&quot;&gt;https://odada.me/246&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 64.7674%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 35.2326%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 64.7674%; height: 19px;&quot;&gt;변수 (Variable)&lt;/td&gt;
&lt;td style=&quot;width: 35.2326%; height: 19px;&quot;&gt;&lt;a href=&quot;https://odada.me/335&quot;&gt;https://odada.me/335&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 64.7674%; height: 19px;&quot;&gt;데이터 (Data)&lt;/td&gt;
&lt;td style=&quot;width: 35.2326%; height: 19px;&quot;&gt;&lt;a href=&quot;https://odada.me/232&quot;&gt;https://odada.me/232&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 64.7674%;&quot;&gt;연산자 (Operator)&lt;/td&gt;
&lt;td style=&quot;width: 35.2326%;&quot;&gt;&lt;a href=&quot;https://odada.me/233&quot;&gt;https://odada.me/233&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 64.7674%; height: 19px;&quot;&gt;구문 (Statement)&lt;/td&gt;
&lt;td style=&quot;width: 35.2326%; height: 19px;&quot;&gt;&lt;a href=&quot;https://odada.me/235&quot;&gt;https://odada.me/235&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 64.7674%;&quot;&gt;함수 (Function)&lt;/td&gt;
&lt;td style=&quot;width: 35.2326%;&quot;&gt;&lt;a href=&quot;https://odada.me/236&quot;&gt;https://odada.me/236&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 64.7674%; height: 19px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 35.2326%; height: 19px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 64.7674%;&quot;&gt;&lt;span style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot;&gt;node.js, npm&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 35.2326%;&quot;&gt;&lt;a href=&quot;https://odada.me/246&quot;&gt;https://odada.me/246&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 64.7674%;&quot;&gt;모듈 (Module)&lt;/td&gt;
&lt;td style=&quot;width: 35.2326%;&quot;&gt;&lt;a href=&quot;https://odada.me/266&quot;&gt;https://odada.me/266&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 64.7674%;&quot;&gt;&lt;span style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 35.2326%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 64.7674%;&quot;&gt;&lt;span style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot;&gt;클래스 (Class)&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 35.2326%;&quot;&gt;&lt;a href=&quot;https://odada.me/247&quot;&gt;https://odada.me/247&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 64.7674%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 35.2326%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 64.7674%; height: 19px;&quot;&gt;표준내장객체 객체 - 문자열(String)&lt;/td&gt;
&lt;td style=&quot;width: 35.2326%; height: 19px;&quot;&gt;&lt;a href=&quot;https://odada.me/261&quot;&gt;https://odada.me/261&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 64.7674%; height: 19px;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;표준내장객체 객체 -&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;숫자(Number), 수학(Math)&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 35.2326%; height: 19px;&quot;&gt;&lt;a href=&quot;https://odada.me/262&quot;&gt;https://odada.me/262&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 64.7674%; height: 19px;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;표준내장객체 객체 -&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;날짜(Date)&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 35.2326%; height: 19px;&quot;&gt;&lt;a href=&quot;https://odada.me/263&quot;&gt;https://odada.me/263&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 64.7674%; height: 17px;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;표준내장객체 객체 -&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;배열(Array)&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 35.2326%; height: 17px;&quot;&gt;&lt;a href=&quot;https://odada.me/264&quot;&gt;https://odada.me/264&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 64.7674%; height: 17px;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;표준내장객체 객체 -&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;객체(Object)&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 35.2326%; height: 17px;&quot;&gt;&lt;a href=&quot;https://odada.me/265&quot;&gt;https://odada.me/265&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 64.7674%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 35.2326%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 64.7674%;&quot;&gt;비동기 (Async)&lt;/td&gt;
&lt;td style=&quot;width: 35.2326%;&quot;&gt;&lt;a href=&quot;https://odada.me/337&quot;&gt;https://odada.me/337&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 64.7674%;&quot;&gt;&lt;span style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot;&gt;비동기2 (Async)&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 35.2326%;&quot;&gt;&lt;a href=&quot;https://odada.me/309&quot;&gt;https://odada.me/309&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 64.7674%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 35.2326%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 64.7674%; height: 17px;&quot;&gt;DOM&lt;/td&gt;
&lt;td style=&quot;width: 35.2326%; height: 17px;&quot;&gt;&lt;a href=&quot;https://odada.me/338&quot;&gt;https://odada.me/338&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 64.7674%;&quot;&gt;이벤트 (Events)&lt;/td&gt;
&lt;td style=&quot;width: 35.2326%;&quot;&gt;&lt;a href=&quot;https://odada.me/339&quot;&gt;https://odada.me/339&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Front/JavaScript</category>
      <author>oodada</author>
      <guid isPermaLink="true">https://oddcode.tistory.com/341</guid>
      <comments>https://oddcode.tistory.com/341#entry341comment</comments>
      <pubDate>Sun, 7 Jul 2024 20:34:45 +0900</pubDate>
    </item>
    <item>
      <title>이벤트(Events) - javascript 기본</title>
      <link>https://oddcode.tistory.com/339</link>
      <description>&lt;h1&gt;Javascript Event&lt;/h1&gt;
&lt;h2&gt;Event란?&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;Event는 웹 페이지에서 발생하는 모든 일을 의미한다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;ul&gt;
&lt;li&gt;사용자가 버튼을 클릭하거나, 텍스트를 입력하거나, 마우스를 움직이는 등의 모든 행위를 의미한다.&lt;/li&gt;
&lt;li&gt;이벤트는 브라우저에서 발생하는 것이기 때문에 브라우저에서만 사용할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;간단한 예제&lt;/h2&gt;
&lt;p&gt;버튼을 클릭하면 target의 글자색이 빨간색으로 바뀌는 예제를 만들어보겠습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;button&amp;gt;change color&amp;lt;/button&amp;gt;
&amp;lt;div id=&amp;quot;target&amp;quot;&amp;gt;Hello&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const button = document.querySelector(&amp;#39;button&amp;#39;);
const target = document.querySelector(&amp;#39;#target&amp;#39;);

button.addEventListener(&amp;#39;click&amp;#39;, () =&amp;gt; {
    target.style.color = &amp;#39;red&amp;#39;;
});&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Event 종류&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://www.w3schools.com/jsref/dom_obj_event.asp&quot;&gt;HTML DOM Events&lt;/a&gt; 에서 확인할 수 있고 가장 많이 사용되는 이벤트는 다음과 같다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;click&lt;/code&gt; : 클릭&lt;/li&gt;
&lt;li&gt;&lt;code&gt;mouseover&lt;/code&gt; : 마우스가 요소 위에 올라갈 때&lt;/li&gt;
&lt;li&gt;&lt;code&gt;mouseout&lt;/code&gt; : 마우스가 요소에서 벗어날 때&lt;/li&gt;
&lt;li&gt;&lt;code&gt;keydown&lt;/code&gt; : 키보드를 누를 때&lt;/li&gt;
&lt;li&gt;&lt;code&gt;keyup&lt;/code&gt; : 키보드를 뗄 때&lt;/li&gt;
&lt;li&gt;&lt;code&gt;submit&lt;/code&gt; : 폼을 제출할 때&lt;/li&gt;
&lt;li&gt;&lt;code&gt;change&lt;/code&gt; : input 요소의 값이 변경될 때&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Event 객체&lt;/h2&gt;
&lt;p&gt;이벤트가 발생하면 이벤트 객체가 생성되고 이벤트에 대한 정보를 가지고 있다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;document.querySelector(&amp;#39;button&amp;#39;).addEventListener(&amp;#39;click&amp;#39;, event =&amp;gt; {
    console.log(event);
});&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Event 객체의 속성&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;type&lt;/code&gt; : 이벤트의 종류&lt;/li&gt;
&lt;li&gt;&lt;code&gt;target&lt;/code&gt; : 이벤트가 발생한 요소&lt;/li&gt;
&lt;li&gt;&lt;code&gt;currentTarget&lt;/code&gt; : 이벤트가 발생한 요소&lt;/li&gt;
&lt;li&gt;&lt;code&gt;preventDefault()&lt;/code&gt; : 이벤트의 기본 동작을 막는다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;stopPropagation()&lt;/code&gt; : 이벤트의 전파를 막는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Event 객체의 메소드&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;addEventListener()&lt;/code&gt; : 이벤트 리스너를 등록한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;removeEventListener()&lt;/code&gt; : 이벤트 리스너를 제거한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Event 전파&lt;/h2&gt;
&lt;p&gt;이벤트는 전파되는데, 이벤트가 발생한 요소에서 시작해서 상위 요소로 전파된다.&lt;/p&gt;
&lt;p&gt;이벤트 전파는 &lt;code&gt;캡처링&lt;/code&gt;과 &lt;code&gt;버블링&lt;/code&gt; 두 가지 방식이 있다.&lt;/p&gt;
&lt;h3&gt;- 이벤트 전파의 두 가지 방식&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;캡처링(Capturing)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;이벤트가 상위 요소에서 시작하여 하위 요소로 전파됩니다.&lt;/li&gt;
&lt;li&gt;이벤트 리스너를 등록할 때 세 번째 인수로 true를 전달하여 캡처링 모드를 활성화합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;버블링(Bubbling)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;이벤트가 하위 요소에서 시작하여 상위 요소로 전파됩니다.&lt;/li&gt;
&lt;li&gt;기본 모드이며, 이벤트 리스너를 등록할 때 세 번째 인수를 생략하거나 false로 설정합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;- 캡처링&lt;/h3&gt;
&lt;p&gt;버튼을 클릭하면 캡처링 모드에서는 body -&amp;gt; div -&amp;gt; button 순으로 콘솔에 출력됩니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;div style=&amp;quot;padding: 50px; background-color: lightgray;&amp;quot;&amp;gt;
    &amp;lt;button style=&amp;quot;padding: 20px;&amp;quot;&amp;gt;Click Me&amp;lt;/button&amp;gt;
&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;document.querySelector(&amp;#39;button&amp;#39;).addEventListener(
    &amp;#39;click&amp;#39;,
    () =&amp;gt; {
        console.log(&amp;#39;button&amp;#39;);
    },
    true,
);

document.querySelector(&amp;#39;div&amp;#39;).addEventListener(
    &amp;#39;click&amp;#39;,
    () =&amp;gt; {
        console.log(&amp;#39;div&amp;#39;);
    },
    true,
);

document.querySelector(&amp;#39;body&amp;#39;).addEventListener(
    &amp;#39;click&amp;#39;,
    () =&amp;gt; {
        console.log(&amp;#39;body&amp;#39;);
    },
    true,
);&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- 버블링&lt;/h3&gt;
&lt;p&gt;버튼을 클릭하면 버블링 모드에서는 button -&amp;gt; div -&amp;gt; body 순으로 콘솔에 출력됩니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;document.querySelector(&amp;#39;button&amp;#39;).addEventListener(&amp;#39;click&amp;#39;, () =&amp;gt; {
    console.log(&amp;#39;button&amp;#39;);
});

document.querySelector(&amp;#39;div&amp;#39;).addEventListener(&amp;#39;click&amp;#39;, () =&amp;gt; {
    console.log(&amp;#39;div&amp;#39;);
});

document.querySelector(&amp;#39;body&amp;#39;).addEventListener(&amp;#39;click&amp;#39;, () =&amp;gt; {
    console.log(&amp;#39;body&amp;#39;);
});&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Event 객체의 전파 방지&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;stopPropagation()&lt;/code&gt; 메소드를 사용하여 이벤트 전파를 막을 수 있다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;document.querySelector(&amp;#39;button&amp;#39;).addEventListener(&amp;#39;click&amp;#39;, event =&amp;gt; {
    event.stopPropagation();
    console.log(&amp;#39;button&amp;#39;);
});

document.querySelector(&amp;#39;div&amp;#39;).addEventListener(&amp;#39;click&amp;#39;, () =&amp;gt; {
    console.log(&amp;#39;div&amp;#39;);
});

document.querySelector(&amp;#39;body&amp;#39;).addEventListener(&amp;#39;click&amp;#39;, () =&amp;gt; {
    console.log(&amp;#39;body&amp;#39;);
});&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Event 객체의 기본 동작 막기&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;preventDefault()&lt;/code&gt; 메소드를 사용하여 이벤트의 기본 동작을 막을 수 있다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;document.querySelector(&amp;#39;a&amp;#39;).addEventListener(&amp;#39;click&amp;#39;, event =&amp;gt; {
    event.preventDefault();
    console.log(&amp;#39;a&amp;#39;);
});&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;버튼을 클릭하면 button만 출력됩니다. stopPropagation() 메서드가 이벤트 전파를 막기 때문입니다.&lt;/p&gt;
&lt;h2&gt;Event 객체의 활용&lt;/h2&gt;
&lt;p&gt;이벤트 객체를 활용하여 버튼을 클릭하면 target의 글자색을 빨간색으로 바꾸고, 이벤트가 발생한 요소의 글자색을 파란색으로 바꾸는 예제를 만들어보겠습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;button&amp;gt;change color&amp;lt;/button&amp;gt;
&amp;lt;div id=&amp;quot;target&amp;quot;&amp;gt;Hello&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;document.querySelector(&amp;#39;button&amp;#39;).addEventListener(&amp;#39;click&amp;#39;, event =&amp;gt; {
    const target = document.querySelector(&amp;#39;#target&amp;#39;);
    target.style.color = &amp;#39;red&amp;#39;;
    event.target.style.color = &amp;#39;blue&amp;#39;;
});&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Front/JavaScript</category>
      <author>oodada</author>
      <guid isPermaLink="true">https://oddcode.tistory.com/339</guid>
      <comments>https://oddcode.tistory.com/339#entry339comment</comments>
      <pubDate>Sat, 6 Jul 2024 22:26:47 +0900</pubDate>
    </item>
    <item>
      <title>DOM - javascript 기본</title>
      <link>https://oddcode.tistory.com/338</link>
      <description>&lt;h1&gt;DOM(Document Object Model)&lt;/h1&gt;
&lt;p&gt;JS HTML DOM : &lt;a href=&quot;https://www.w3schools.com/js/js_htmldom.asp&quot;&gt;https://www.w3schools.com/js/js_htmldom.asp&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.w3schools.com/js/pic_htmltree.gif&quot; alt=&quot;The HTML DOM Tree of Objects&quot;&gt;&lt;/p&gt;
&lt;h2&gt;1. DOM이란?&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;DOM은 HTML 문서를 객체로 표현한 것으로,&lt;/li&gt;
&lt;li&gt;자바스크립트를 이용해 HTML 문서를 제어할 수 있게 해준다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const h1El = document.querySelector(&amp;#39;h1&amp;#39;);
console.log(h1El.textContent);&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;2. Node vs Element(요소)&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Node(노드) : HTML 요소, 텍스트, 주석 등 모든 것을 의미한다.&lt;/li&gt;
&lt;li&gt;Element(요소) : HTML 요소를 의미한다.&lt;/li&gt;
&lt;li&gt;Node는 Element의 &lt;strong&gt;상위 개념&lt;/strong&gt;이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;div class=&amp;quot;container&amp;quot;&amp;gt;
    &amp;lt;!-- 주석 --&amp;gt;
    &amp;lt;div class=&amp;quot;item&amp;quot;&amp;gt;1&amp;lt;/div&amp;gt;
    text1
    &amp;lt;div
        id=&amp;quot;item1&amp;quot;
        class=&amp;quot;item&amp;quot;
    &amp;gt;
        2
    &amp;lt;/div&amp;gt;
    text2
&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const container = document.querySelector(&amp;#39;.container&amp;#39;);

// container의 자식 노드들을 출력한다.
console.log(container.childNodes); // NodeList(5) [text, div.item, text, div.item, text]

// container의 자식 요소들을 출력한다.
console.log(container.children); // HTMLCollection(2) [div.item, div.item]

console.dir(container); // container의 속성들을 출력한다.&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;3. 검색과 탐색&lt;/h2&gt;
&lt;h3&gt;- &lt;code&gt;document.getElementById()&lt;/code&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;id 속성을 이용해 요소를 찾는다.&lt;/li&gt;
&lt;li&gt;id는 문서 내에서 &lt;strong&gt;유일&lt;/strong&gt;해야 한다.&lt;/li&gt;
&lt;li&gt;id가 없으면 &lt;code&gt;null&lt;/code&gt;을 반환한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const item = document.getElementById(&amp;#39;item1&amp;#39;);
console.log(item); // &amp;lt;div id=&amp;quot;item1&amp;quot; class=&amp;quot;item&amp;quot;&amp;gt;2&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- &lt;code&gt;document.querySelector()&lt;/code&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;CSS 선택자를 이용해 요소를 찾는다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;첫 번째&lt;/strong&gt;로 일치하는 요소를 반환한다.&lt;/li&gt;
&lt;li&gt;일치하는 요소가 없으면 &lt;code&gt;null&lt;/code&gt;을 반환한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const item = document.querySelector(&amp;#39;.item:nth-child(2)&amp;#39;);
console.log(item); // &amp;lt;div id=&amp;quot;item1&amp;quot; class=&amp;quot;item&amp;quot;&amp;gt;2&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- &lt;code&gt;document.querySelectorAll()&lt;/code&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;CSS 선택자를 이용해 요소를 찾는다.&lt;/li&gt;
&lt;li&gt;일치하는 &lt;strong&gt;모든&lt;/strong&gt; 요소를 반환한다.&lt;/li&gt;
&lt;li&gt;일치하는 요소가 없으면 빈 NodeList를 반환한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;NodeList&lt;/code&gt; 객체는 &lt;code&gt;forEach()&lt;/code&gt; 메소드를 사용할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const items = document.querySelectorAll(&amp;#39;.item&amp;#39;);
console.log(items); // NodeList(2) [div.item, div.item]

items.forEach(item =&amp;gt; {
    console.log(item); // 각 item 요소를 출력한다.
});&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- &lt;code&gt;Node.parentElement&lt;/code&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;부모 요소를 반환한다.&lt;/li&gt;
&lt;li&gt;부모 요소가 없으면 &lt;code&gt;null&lt;/code&gt;을 반환한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const item = document.querySelector(&amp;#39;.item&amp;#39;);
console.log(item.parentElement); // &amp;lt;div class=&amp;quot;container&amp;quot;&amp;gt;...&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- &lt;code&gt;Element.closest()&lt;/code&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;가장 가까운 조상 요소를 반환한다.&lt;/li&gt;
&lt;li&gt;일치하는 요소가 없으면 &lt;code&gt;null&lt;/code&gt;을 반환한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const item = document.querySelector(&amp;#39;.item&amp;#39;);
console.log(item.closest(&amp;#39;div&amp;#39;)); // &amp;lt;div class=&amp;quot;item&amp;quot;&amp;gt;...&amp;lt;/div&amp;gt; : item 요소 자신
console.log(item.closest(&amp;#39;body&amp;#39;)); // &amp;lt;body&amp;gt;...&amp;lt;/body&amp;gt; : item 요소의 부모 요소
console.log(item.closest(.header)); // null : item 요소의 조상 요소 중 header 클래스를 가진 요소가 없다.&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- &lt;code&gt;Node.previousSibling&lt;/code&gt;, &lt;code&gt;Node.nextSibling&lt;/code&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;노드의 이전 형제 요소, 다음 형제 요소를 반환한다.&lt;/li&gt;
&lt;li&gt;형제 요소가 없으면 &lt;code&gt;null&lt;/code&gt;을 반환한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const item = document.querySelector(&amp;#39;.item&amp;#39;);
console.log(item.previousSibling); // #text : 이전 형제 요소
console.log(item.nextSibling); // #text : 다음 형제 요소
// node에서 text는 줄바꿈, 공백 등을 의미한다.

console.log(item.previousSibling.parentElement); // &amp;lt;div class=&amp;quot;container&amp;quot;&amp;gt;...&amp;lt;/div&amp;gt; : 이전 형제 요소의 부모 요소&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- &lt;code&gt;Element.previousElementSibling&lt;/code&gt;, &lt;code&gt;Element.nextElementSibling&lt;/code&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;요소의 이전 형제 요소, 다음 형제 요소를 반환한다.&lt;/li&gt;
&lt;li&gt;형제 요소가 없으면 &lt;code&gt;null&lt;/code&gt;을 반환한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const item = document.querySelector(&amp;#39;.item&amp;#39;);
console.log(item.previousElementSibling); // null : 이전 형제 요소
console.log(item.nextElementSibling); // &amp;lt;div class=&amp;quot;item&amp;quot;&amp;gt;2&amp;lt;/div&amp;gt; : 다음 형제 요소&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- &lt;code&gt;Element.children&lt;/code&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;요소의 자식 요소들을 반환한다.&lt;/li&gt;
&lt;li&gt;자식 요소가 없으면 빈 HTMLCollection을 반환한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const container = document.querySelector(&amp;#39;.container&amp;#39;);
console.log(container.children); // HTMLCollection(2) [div.item, div.item]&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- &lt;code&gt;Element.firstElementChild&lt;/code&gt;, &lt;code&gt;Element.lastElementChild&lt;/code&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;요소의 첫 번째 자식 요소, 마지막 자식 요소를 반환한다.&lt;/li&gt;
&lt;li&gt;자식 요소가 없으면 &lt;code&gt;null&lt;/code&gt;을 반환한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const container = document.querySelector(&amp;#39;.container&amp;#39;);
console.log(container.firstElementChild); // &amp;lt;div class=&amp;quot;item&amp;quot;&amp;gt;1&amp;lt;/div&amp;gt; : 첫 번째 자식 요소
console.log(container.lastElementChild); // &amp;lt;div id=&amp;quot;item1&amp;quot; class=&amp;quot;item&amp;quot;&amp;gt;2&amp;lt;/div&amp;gt; : 마지막 자식 요소&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;DOM&lt;/h1&gt;
&lt;h2&gt;4. 생성, 조회, 수정, 삭제&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;div class=&amp;quot;container&amp;quot;&amp;gt;
    &amp;lt;div class=&amp;quot;item&amp;quot;&amp;gt;1&amp;lt;/div&amp;gt;
    &amp;lt;div class=&amp;quot;item&amp;quot;&amp;gt;2&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- &lt;code&gt;document.createElement()&lt;/code&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;메모리에만 존재하는 새로운 HTML 요소를 생성해 반환한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const newItem = document.createElement(&amp;#39;div&amp;#39;);
newItem.textContent = &amp;#39;3&amp;#39;;
console.log(newItem); // &amp;lt;div&amp;gt;3&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- &lt;code&gt;Element.prepend()&lt;/code&gt;, &lt;code&gt;Element.append()&lt;/code&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;요소의 첫 번째 자식 요소, 마지막 자식 요소로 추가한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const container = document.querySelector(&amp;#39;.container&amp;#39;);
container.prepend(new Comment(&amp;#39;prepend comment&amp;#39;)); // 주석을 추가한다.
container.append(newItem); // newItem을 마지막 자식 요소로 추가한다.
container.append(&amp;#39;text&amp;#39;); // text를 마지막 자식 요소로 추가한다.&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- &lt;code&gt;Element.remove()&lt;/code&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;요소를 삭제한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const item = document.querySelector(&amp;#39;.item&amp;#39;);
item.remove();&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- &lt;code&gt;Element.insertAdjacentElement()&lt;/code&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;지정한 위치에 새로운 요소를 추가한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;대상요소.insertAdjacentElement(&amp;#39;위치&amp;#39;, 추가할요소)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;!-- &amp;#39;beforebegin&amp;#39; --&amp;gt;
&amp;lt;div class=&amp;quot;target&amp;quot;&amp;gt;
    &amp;lt;!-- &amp;#39;afterbegin&amp;#39; --&amp;gt;
    &amp;lt;div class=&amp;quot;item&amp;quot;&amp;gt;1&amp;lt;/div&amp;gt;
    &amp;lt;div class=&amp;quot;item&amp;quot;&amp;gt;2&amp;lt;/div&amp;gt;
    &amp;lt;!-- &amp;#39;beforeend&amp;#39; --&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;!-- &amp;#39;afterend&amp;#39; --&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const target = document.querySelector(&amp;#39;.item&amp;#39;);
const newItem = document.createElement(&amp;#39;span&amp;#39;);
newItem.textContent = &amp;#39;새로운 아이템&amp;#39;;

target.insertAdjacentElement(&amp;#39;beforebegin&amp;#39;, newItem);
target.insertAdjacentElement(&amp;#39;afterbegin&amp;#39;, &amp;#39;새로운 고양이&amp;#39;);&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- &lt;code&gt;Node.insertBefore()&lt;/code&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&amp;#39;부모 노드&amp;#39;의 &amp;#39;자식 노드&amp;#39;로 &amp;#39;기준 노드&amp;#39; 앞에 &amp;#39;추가할 노드&amp;#39;를 추가한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;부모노드.insertBefore(추가할노드, 기준노드)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const container = document.querySelector(&amp;#39;.container&amp;#39;);
container.insertBefore(newItem, container.firstElementChild);&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- &lt;code&gt;Node.contains()&lt;/code&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&amp;#39;해당 노드&amp;#39;가 다른 노드의 자식인지 확인한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;부모노드.contains(자식노드)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const container = document.querySelector(&amp;#39;.container&amp;#39;);

console.log(document.body.contains(container)); // true
console.log(container.contains(newItem)); // true
console.log(container.contains(document.body)); // false&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- &lt;code&gt;Node.textContent&lt;/code&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;노드의 텍스트 내용을 반환한다.&lt;/li&gt;
&lt;li&gt;노드의 텍스트 내용을 수정할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const item = document.querySelector(&amp;#39;.item&amp;#39;);
console.log(item.textContent); // 2

item.textContent = &amp;#39;3&amp;#39;;
console.log(item.textContent); // 3&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- &lt;code&gt;Element.innerHTML&lt;/code&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;요소의 HTML 내용을 반환한다.&lt;/li&gt;
&lt;li&gt;요소의 HTML 내용을 수정할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const container = document.querySelector(&amp;#39;.container&amp;#39;);
console.log(container.innerHTML); // &amp;lt;div class=&amp;quot;item&amp;quot;&amp;gt;1&amp;lt;/div&amp;gt;&amp;lt;div class=&amp;quot;item&amp;quot;&amp;gt;2&amp;lt;/div&amp;gt;

// comment tagged templates 확장 프로그램을 사용하면 html,css의 색상이 달라진다.
container.innerHTML = /* html */ `
    &amp;lt;div class=&amp;quot;item&amp;quot;&amp;gt;3&amp;lt;/div&amp;gt;
`;
console.log(container.innerHTML); // &amp;lt;div class=&amp;quot;item&amp;quot;&amp;gt;3&amp;lt;/div&amp;gt;

container.innerHTML += /* html */ `
    &amp;lt;div class=&amp;quot;item&amp;quot; style=&amp;quot;border: 1px solid;&amp;quot;&amp;gt;4&amp;lt;/div&amp;gt;
`;
console.log(container.innerHTML); // &amp;lt;div class=&amp;quot;item&amp;quot;&amp;gt;3&amp;lt;/div&amp;gt;&amp;lt;div class=&amp;quot;item&amp;quot;&amp;gt;4&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- &lt;code&gt;E.dataset&lt;/code&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;요소의 &lt;code&gt;data-*&lt;/code&gt; 속성을 가져오거나 수정할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;!-- ```html
&lt;div class=&quot;container&quot;&gt;
    &lt;div class=&quot;item&quot;&gt;1&lt;/div&gt;
    &lt;div class=&quot;item&quot;&gt;2&lt;/div&gt;
&lt;/div&gt;
```

```js
const item = document.querySelector('.item');
const str = 'Hello, world!';
const obj = { a: 1, b: 2 };

item.dataset.helloWorld = str; // data-hello-world=&quot;Hello, world!&quot;
item.dataset.object = JSON.stringify(obj); // JSON.stringify() : 객체를 문자열로 변환한다.

console.log(item.dataset.helloWorld); // Hello, world!
console.log(item.dataset.object); // {&quot;a&quot;:1,&quot;b&quot;:2}
console.log(JSON.parse(item.dataset.obj)); // {a: 1, b: 2}
``` --&gt;

&lt;h3&gt;- &lt;code&gt;Element.tagName&lt;/code&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;요소의 태그 이름을 반환한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const item = document.querySelector(&amp;#39;.item&amp;#39;);
console.log(item.tagName); // DIV&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- &lt;code&gt;Element.Id&lt;/code&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;요소의 id 속성 값을 얻거나 반환할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const item = document.querySelector(&amp;#39;.item&amp;#39;);
console.log(item.id); // item1

item.id = &amp;#39;new-item&amp;#39;;
console.log(item.id); // new-item&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- &lt;code&gt;Element.className&lt;/code&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;요소의 class 속성 값을 얻거나 반환할 수 있다.&lt;/li&gt;
&lt;li&gt;하지만 문자열로 반환되기 때문에 classList를 사용하는 것이 좋다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const item = document.querySelector(&amp;#39;.item&amp;#39;);
console.log(item.className); // item

item.className += &amp;#39;new-item active&amp;#39;;
console.log(item.className); // new-item&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- &lt;code&gt;Element.classList&lt;/code&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;요소의 클래스 속성을 다루는 객체이다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;add()&lt;/code&gt; : 클래스 추가&lt;/li&gt;
&lt;li&gt;&lt;code&gt;remove()&lt;/code&gt; : 클래스 삭제&lt;/li&gt;
&lt;li&gt;&lt;code&gt;toggle()&lt;/code&gt; : 클래스 토글&lt;/li&gt;
&lt;li&gt;&lt;code&gt;contains()&lt;/code&gt; : 클래스 포함 여부 확인&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;ul class=&amp;quot;list&amp;quot;&amp;gt;
    &amp;lt;li&amp;gt;1&amp;lt;/li&amp;gt;
    &amp;lt;li&amp;gt;2&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const listEl = document.querySelector(&amp;#39;.list li&amp;#39;);

listEl.classList.add(&amp;#39;active&amp;#39;);
listEl.classList.remove(&amp;#39;active&amp;#39;);
listEl.addEventListener(&amp;#39;click&amp;#39;, () =&amp;gt; {
    listEl.classList.toggle(&amp;#39;active&amp;#39;);
});&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;모든 &lt;code&gt;li&lt;/code&gt; 요소에 대해 클래스를 추가하거나 제거하려면, &lt;code&gt;querySelectorAll&lt;/code&gt; 메서드를 사용하여 모든 &lt;code&gt;li&lt;/code&gt; 요소를 선택하고, 이를 반복하여 각 요소에 대해 이벤트 리스너를 추가해야 합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;// 모든 li 요소를 선택
const listItems = document.querySelectorAll(&amp;#39;.list li&amp;#39;);

// 모든 li 요소에 대해 반복
listItems.forEach(listEl =&amp;gt; {
    // &amp;#39;active&amp;#39; 클래스를 추가했다가 제거
    listEl.classList.add(&amp;#39;active&amp;#39;);
    listEl.classList.remove(&amp;#39;active&amp;#39;);

    // 클릭 이벤트 리스너 추가
    listEl.addEventListener(&amp;#39;click&amp;#39;, () =&amp;gt; {
        listEl.classList.toggle(&amp;#39;active&amp;#39;);
    });
});&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- &lt;code&gt;Element.style&lt;/code&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;요소의 &lt;code&gt;style&lt;/code&gt; 속성(인라인 스타일)을 css 속성 값을 가져오거나 수정할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const item = document.querySelector(&amp;#39;.item&amp;#39;);

// 개별 지정
item.style.width = &amp;#39;100px&amp;#39;;
item.style.color = &amp;#39;red&amp;#39;;
item.style.backgroundColor = &amp;#39;yellow&amp;#39;;
item.style.fontSize = &amp;#39;20px&amp;#39;;

// cssText
item.style.cssText = &amp;#39;width: 100px; color: red; background-color: yellow; font-size: 20px;&amp;#39;;

// 한 번에 지정
Object.assign(item.style, {
    width: &amp;#39;100px&amp;#39;,
    color: &amp;#39;red&amp;#39;,
    backgroundColor: &amp;#39;yellow&amp;#39;,
    fontSize: &amp;#39;20px&amp;#39;,
});

// getPropertyValue
console.log(item.style);
console.log(item.style.width); // 100px
console.log(item.style.fontSize); // 20px&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- &lt;code&gt;window.getComputedStyle()&lt;/code&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;요소의 모든 CSS 속성 값을 가져온다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.item {
    width: 100px;
    font-size: 20px;
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const item = document.querySelector(&amp;#39;.item&amp;#39;);
const style = window.getComputedStyle(item);

console.log(style.width); // 100px
console.log(style.fontSize); // 20px&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- &lt;code&gt;Element.getAttribute()&lt;/code&gt;, &lt;code&gt;Element.setAttribute()&lt;/code&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;요소의 속성을 가져오거나 수정할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const item = document.querySelector(&amp;#39;.item&amp;#39;);

item.setAttribute(&amp;#39;title&amp;#39;, &amp;#39;Hello, world!&amp;#39;); // title 속성을 추가한다.
console.log(item.getAttribute(&amp;#39;title&amp;#39;)); // Hello, world!&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- &lt;code&gt;Element.hasAttr()&lt;/code&gt;, &lt;code&gt;Element.removeAttr()&lt;/code&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;요소가 속성을 가지고 있는지 확인하거나 삭제할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const item = document.querySelector(&amp;#39;.item&amp;#39;);

console.log(item.hasAttribute(&amp;#39;title&amp;#39;)); // true

item.removeAttribute(&amp;#39;title&amp;#39;);
console.log(item.hasAttribute(&amp;#39;title&amp;#39;)); // false&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;5. 크기와 위치&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;div class=&amp;quot;container&amp;quot;&amp;gt;
    &amp;lt;div class=&amp;quot;item&amp;quot;&amp;gt;1&amp;lt;/div&amp;gt;
    &amp;lt;div class=&amp;quot;item&amp;quot;&amp;gt;2&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;

&amp;lt;style&amp;gt;
    body {
        height: 1000px;
        padding: 500px 0;
    }
    .container {
        overflow: auto;
        width: 300px;
        height: 300px;
        padding: 20px;
        border: 10px solid;
    }

    .item {
        height: 100px;
        margin-top: 100px;
        border: 1px solid red;
    }
&amp;lt;/style&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- &lt;code&gt;window.innerWidth&lt;/code&gt;, &lt;code&gt;window.innerHeight&lt;/code&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;브라우저의 너비, 높이를 반환한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;console.log(window.innerWidth);
console.log(window.innerHeight);&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- &lt;code&gt;window.scrollX&lt;/code&gt;, &lt;code&gt;window.scrollY&lt;/code&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;페이지의 최상단 기준으로 스크롤한 양을 반환한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;console.log(window.scrollX);
console.log(window.scrollY);&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- &lt;code&gt;window.scrollTo()&lt;/code&gt;, &lt;code&gt;window.scrollBy()&lt;/code&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;스크롤을 지정한 양만큼 이동시킨다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;window.scrollBy(x, y)&lt;/code&gt; : 현재 스크롤 위치에서 지정한 양만큼 이동한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;window.scrollTo(x, y)&lt;/code&gt; : 페이지의 최상단을 기준으로 지정한 위치로 이동한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;// 페이지의 최상단을 기준으로 100px 아래로 이동한다.
window.scrollTo({
    top: 100,
    behavior: &amp;#39;smooth&amp;#39;,
});

// 현재 스크롤 위치에서 100px 아래로 이동한다.
window.scrollBy(0, 100);&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- &lt;code&gt;Element.clientWidth&lt;/code&gt;, &lt;code&gt;Element.clientHeight&lt;/code&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;테두리(border)와 스크롤바를 제외한 요소의 너비, 높이를 반환한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const container = document.querySelector(&amp;#39;.container&amp;#39;);
const item = document.querySelector(&amp;#39;.item&amp;#39;);

console.log(container.clientWidth, container.clientHeight);
console.log(item.clientWidth, item.clientHeight);&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- &lt;code&gt;Element.offsetWidth&lt;/code&gt;, &lt;code&gt;Element.offsetHeight&lt;/code&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;테두리(border)와 스크롤바를 포함한 요소의 너비, 높이를 반환한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const container = document.querySelector(&amp;#39;.container&amp;#39;);
const item = document.querySelector(&amp;#39;.item&amp;#39;);

console.log(container.offsetWidth, container.offsetHeight);
console.log(item.offsetWidth, item.offsetHeight);&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- &lt;code&gt;Element.scrollWidth&lt;/code&gt;, &lt;code&gt;Element.scrollHeight&lt;/code&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;요소의 콘텐츠가 너비, 높이를 반환한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const container = document.querySelector(&amp;#39;.container&amp;#39;);
const item = document.querySelector(&amp;#39;.item&amp;#39;);

console.log(container.scrollWidth, container.scrollHeight);
console.log(item.scrollWidth, item.scrollHeight);&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- &lt;code&gt;Element.offsetTop&lt;/code&gt;, &lt;code&gt;Element.offsetLeft&lt;/code&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;부모 요소를 기준으로 요소의 상단, 왼쪽 위치를 반환한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const container = document.querySelector(&amp;#39;.container&amp;#39;);
const item = document.querySelector(&amp;#39;.item&amp;#39;);

console.log(container.offsetTop, container.offsetLeft);
console.log(item.offsetTop, item.offsetLeft);&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- &lt;code&gt;Element.getBoundingClientRect()&lt;/code&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;테두리(border)를 포함한 요소의 크기와&lt;/li&gt;
&lt;li&gt;브라우저의 뷰포트를 기준으로 요소의 상대 위치 정보를 반환한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const container = document.querySelector(&amp;#39;.container&amp;#39;);
const item = document.querySelector(&amp;#39;.item&amp;#39;);

console.log(container.getBoundingClientRect());
console.log(item.getBoundingClientRect());&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Front/JavaScript</category>
      <author>oodada</author>
      <guid isPermaLink="true">https://oddcode.tistory.com/338</guid>
      <comments>https://oddcode.tistory.com/338#entry338comment</comments>
      <pubDate>Sat, 6 Jul 2024 22:26:00 +0900</pubDate>
    </item>
    <item>
      <title>비동기(Async) - javascript 기본</title>
      <link>https://oddcode.tistory.com/337</link>
      <description>&lt;h1&gt;동기(Synchronous)와 비동기(Asynchronous)&lt;/h1&gt;
&lt;h2&gt;01. 개요&lt;/h2&gt;
&lt;h3&gt;- 동기 처리&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;동기 처리란, 작업을 순차적으로 처리하는 것을 말한다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;자바스크립트 코드는 기본적으로 동기적으로 처리된다.&lt;br&gt;동기적으로 처리되는 코드는 &lt;strong&gt;위에서부터 아래로 순차적으로 실행&lt;/strong&gt;되며, 어떤 작업이 끝나야 다음 작업을 수행할 수 있다.&lt;/p&gt;
&lt;p&gt;하지만, 동기적으로 처리되는 코드는 &lt;strong&gt;작업이 끝날 때까지 다른 작업을 수행할 수 없다&lt;/strong&gt;는 단점이 있다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;은행에서 번호 순서대로 업무를 처리하는 것, 순차적으로 처리되는 것&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;console.log(&amp;#39;은행 1번 번호표 업무 시작&amp;#39;);
console.log(&amp;#39;은행 1번 번호표 업무 끝&amp;#39;);

console.log(&amp;#39;은행 2번 번호표 업무 시작&amp;#39;);
console.log(&amp;#39;은행 2번 번호표 업무 끝&amp;#39;);&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- 비동기 처리&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;비동기 처리란, 작업을 동시에 처리하는 것을 말한다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;비동기 처리는 이러한 단점을 보완하기 위해 등장했다.&lt;br&gt;비동기 처리는 &lt;strong&gt;작업을 동시에 처리&lt;/strong&gt;할 수 있으며, &lt;strong&gt;작업이 끝나지 않아도 다른 작업을 수행&lt;/strong&gt;할 수 있다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;비동기처리의 예: 커피 주문 시 앞번호가 먼저 나오는 것, 동시에 처리되는 것&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;setTimeout(() =&amp;gt; {
    console.log(&amp;#39;느린 미니언즈 라떼&amp;#39;);
}, 1000);
console.log(&amp;#39;빠른 아메리카노&amp;#39;);&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;fetch(&amp;#39;https://jsonplaceholder.typicode.com/posts/1&amp;#39;)
    .then(res =&amp;gt; res.json())
    .then(data =&amp;gt; console.log(data));

console.log(&amp;#39;데이터 요청 중...&amp;#39;);&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;fetch API를 사용하여 서버에서 데이터를 가져올 수 있습니다.&lt;/li&gt;
&lt;li&gt;fetch 함수는 비동기 방식으로 서버에 데이터를 요청하고,&lt;/li&gt;
&lt;li&gt;요청이 완료되면 then 메서드를 통해 데이터를 처리합니다.&lt;/li&gt;
&lt;li&gt;따라서 &amp;#39;데이터 요청 중...&amp;#39;이 먼저 출력되고, 서버 응답이 완료되면 데이터가 출력됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;- 비동기 처리의 장점&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;효율성&lt;/strong&gt;: 비동기 처리는 결과를 기다리지 않고 다른 작업을 수행할 수 있기 때문에 시스템 자원을 효율적으로 활용할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;응답성&lt;/strong&gt;: 사용자 경험을 향상시키는 데 유용하며, UI가 멈추지 않고 반응을 계속 유지할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;병렬 처리&lt;/strong&gt;: 여러 작업을 병렬로 처리함으로써 전체적인 처리 속도를 높일 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;- 비동기 처리의 실무 예시&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Ajax (Asynchronous JavaScript and XML)&lt;/strong&gt;: 웹 애플리케이션에서 페이지를 전체적으로 새로 고침하지 않고 서버와 통신할 수 있게 합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Promise 및 async/await&lt;/strong&gt;: JavaScript에서 비동기 작업을 다루기 위한 문법으로, 비동기 코드의 가독성을 높입니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Node.js의 비동기 I/O&lt;/strong&gt;: Node.js는 비동기 방식으로 파일 시스템, 데이터베이스, 네트워크 요청 등을 처리하여 높은 성능을 제공합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;비동기 API 호출&lt;/strong&gt;: 클라이언트 애플리케이션에서 서버 API를 호출할 때, 비동기 요청을 통해 다른 작업을 차단하지 않고 처리가 가능합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;실시간 데이터 처리&lt;/strong&gt;: 채팅 애플리케이션, 실시간 알림 시스템 등에서 비동기 처리를 통해 즉각적인 데이터 업데이트가 가능합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이 중 가장 많이 사용되는 방식은 &lt;strong&gt;Promise 및 async/await&lt;/strong&gt;입니다. 이에 대해 자세히 알아보겠습니다.&lt;/p&gt;
&lt;h2&gt;02. 콜백(Callback) 패턴&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;콜백(Callback)&lt;/strong&gt;: 비동기 처리를 위해 다른 함수에 인자로 넘겨주는 함수로, 비동기 작업이 완료되면 콜백 함수가 실행됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;const a = () =&amp;gt; console.log(&amp;quot;a&amp;quot;);
const b = () =&amp;gt; console.log(&amp;quot;b&amp;quot;);

a(); // a
b(); // b&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;위 코드는 동기적으로 실행되기 때문에 a 함수가 먼저 실행되고, b 함수가 실행됩니다.&lt;/li&gt;
&lt;li&gt;숫자 2가 먼저 출력되게 하려면 어떻게 해야 할까요?&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// 비동기 처리를 사용하기 위해 setTimeout 함수를 사용합니다.
// a 함수가 실행되고 1초 후에 b 함수가 실행됩니다.
const a = () =&amp;gt; {
    setTimeout(() =&amp;gt; {
        console.log(&amp;quot;a&amp;quot;);
    }, 1000);
};

const b = () =&amp;gt; console.log(&amp;quot;b&amp;quot;);

a(); // a
b(); // b&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;위 코드는 비동기적으로 실행되기 때문에 a 함수가 실행되고, 1초 후에 1이 출력됩니다.&lt;/li&gt;
&lt;li&gt;이처럼 콜백 함수를 사용하면 비동기 작업을 순차적으로 처리할 수 있습니다.&lt;/li&gt;
&lt;li&gt;만약 출력하는 숫자 1과 2를 순차적으로 출력하려면 어떻게 해야 할까요?&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// 콜백 함수를 사용하여 비동기 작업을 순차적으로 처리합니다.
const a = callback =&amp;gt; {
    setTimeout(() =&amp;gt; {
        console.log(&amp;quot;a&amp;quot;); // a
        callback(); // b
    }, 1000);
};

const b = () =&amp;gt; console.log(&amp;quot;b&amp;quot;);

a(b); // a, b&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;위 코드는 a 함수가 실행되고, 1이 출력된 후에 콜백 함수가 실행되어 b 함수가 실행됩니다.&lt;/li&gt;
&lt;li&gt;이처럼 콜백 함수를 사용하면 비동기 작업을 순차적으로 처리할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;03. Promise 패턴&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;new Promise((resolve, reject) =&amp;gt; { ... });&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Promise&lt;/strong&gt;: 비동기 작업이 완료되었을 때 결과를 반환하거나 에러를 처리할 수 있는 객체로, 비동기 작업을 보다 효율적으로 처리할 수 있습니다. Promise 객체를 사용하면 콜백 함수의 중첩 문제(콜백 지옥)를 해결할 수 있으며, 비동기 코드의 가독성을 높여줍니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;프로미스는 세 가지 상태를 가진다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;대기(pending) : 비동기 처리가 아직 수행되지 않은 상태&lt;/li&gt;
&lt;li&gt;이행(fulfilled) : 비동기 처리가 성공적으로 수행된 상태&lt;/li&gt;
&lt;li&gt;거부(rejected) : 비동기 처리가 실패한 상태&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/odada/post/04475350-f7c3-4253-8aca-f12157c5a1d3/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;프로미스를 사용하여 비동기 처리를 하려면, new Promise()를 사용하여 프로미스 객체를 생성하고, resolve와 reject를 사용하여 프로미스의 상태를 변경한다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// Promise 객체 생성
// new Promise((resolve, reject) =&amp;gt; {...})를 통해 새로운 프로미스 객체 생성
// 이 객체는 비동기 작업을 수행할 콜백 함수를 인자로 받으며,
// 이 콜백 함수는 resolve와 reject라는 두 개의 함수를 매개변수로 가집니다.
// resolve, reject 함수는 프로미스의 상태를 변경하는 역할을 합니다.
const a = () =&amp;gt; {
    return new Promise((resolve, reject) =&amp;gt; {
        setTimeout(() =&amp;gt; {
            // 1초 후에
            console.log(1);
            resolve(); // 성공 시 호출
        }, 1000);
    });
};

const b = () =&amp;gt; console.log(2);

// 아래 코드는 역시나 콜백 지옥에 빠집니다.
// then 메서드를 사용하여 비동기 작업을 순차적으로 처리할 수 있습니다.
// a 함수가 실행되고 완료된 후에 b 함수가 실행되고, c 함수가 실행됩니다.
a().then(() =&amp;gt; {
    b()
});&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위 코드에서는 &lt;code&gt;a&lt;/code&gt;, &lt;code&gt;b&lt;/code&gt;, &lt;code&gt;c&lt;/code&gt;, &lt;code&gt;d&lt;/code&gt; 함수가 각각 1초씩 기다린 후 콘솔에 숫자를 출력하는 비동기 작업을 수행합니다. 각 함수는 &lt;code&gt;Promise&lt;/code&gt; 객체를 반환하며, 이 객체는 작업이 성공했을 때 &lt;code&gt;resolve&lt;/code&gt;를 호출합니다.&lt;/p&gt;
&lt;h3&gt;- 콜백지옥 해결&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// b 함수 호출 시 return 키워드로 반환하게 되면
// promise 객체를 반환하게 되어 then 메서드를 연달아 사용할 수 있습니다.
a()
    .then(() =&amp;gt; {
        return b();
    })&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위 코드와 같이 각 then 메서드 내에서 다음 비동기 함수를 반환하도록 변경하여, then 체인을 형성합니다. 이렇게 하면 콜백 지옥을 피할 수 있습니다.&lt;/p&gt;
&lt;h3&gt;- 화살표 축약&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;a()
    .then(() =&amp;gt; b())&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;화살표 함수의 축약 형태로, 함수 내부가 한 줄로 표현될 때 중괄호를 생략할 수 있습니다.&lt;/p&gt;
&lt;h3&gt;- 단축 표현&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// resolve는 하나의 함수 데이터를 받아서 return으로 처리할 수 있습니다.
// 해서 b, c, d 함수 데이터 자체로 전달할 수 있습니다.
a()
    .then(b)
    .then(() =&amp;gt; {
        console.log(&amp;#39;done&amp;#39;);
    });&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- Promise.all&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// Promise.all 메서드를 사용하여 여러 개의 비동기 작업을 동시에 처리할 수 있습니다.
Promise.all([a(), b()]).then(() =&amp;gt; {
    console.log(&amp;#39;모든 작업이 완료되었습니다.&amp;#39;);
});&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- Promise를 활용한 실무 예시&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href=&quot;https://jsonplaceholder.typicode.com/guide/&quot;&gt;JSONPlaceholder&lt;/a&gt;에서 제공하는 API를 사용하여 사용자 정보를 가져와 출력해보겠습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;fetch()&lt;/strong&gt;: 네트워크 요청을 보내는 API로, Promise 객체를 반환하여 비동기 작업을 처리합니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Resolve&lt;/strong&gt;: Promise 객체가 성공적으로 처리되었을 때 호출되는 콜백 함수로, 비동기 작업이 완료되면 결과를 반환합니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// userId 매개변수를 받아 사용자 정보를 가져오는 함수를 구현합니다.
const getUser = userId =&amp;gt; {
    // Promise 객체를 반환합니다. resolve 함수를 사용하여 비동기 작업이 완료되면 결과를 반환합니다.
    return new Promise(resolve =&amp;gt; {
        // fetch 함수를 사용하여 JSONPlaceholder API를 호출합니다.
        fetch(`https://jsonplaceholder.typicode.com/users/${userId}`)
            // fetch 함수가 성공적으로 호출되면 response 객체를 반환합니다.
            // json 메서드를 사용하여 response 객체를 JSON 형태로 변환합니다.
            .then(res =&amp;gt; res.json())
            // response 객체가 성공적으로 변환되면 data 객체를 반환합니다.
            .then(data =&amp;gt; resolve(data));
    });
};

// 사용자 데이터를 가져오는 함수를 호출합니다.
getUser(1)
    // getUser 함수가 성공적으로 호출되면 data 객체를 반환합니다.
    .then(data =&amp;gt; {
        console.log(`사용자 ID 1의 이름은 ${data.name} 입니다.`);
        return getUser(2);
    })
    .then(data =&amp;gt; {
        console.log(`사용자 ID 2의 이름은 ${data.name} 입니다.`);
        return getUser(3);
    })
    .then(data =&amp;gt; {
        console.log(`사용자 ID 3의 이름은 ${data.name} 입니다.`);
    });&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;위 코드는 사용자 ID 1, 2, 3의 사용자 정보를 차례대로 가져와 출력합니다.&lt;/li&gt;
&lt;li&gt;getUser 함수는 Promise 객체를 반환하며, 사용자 정보를 가져온 후에 다음 사용자 정보를 가져옵니다.&lt;/li&gt;
&lt;li&gt;이처럼 Promise를 사용하면 비동기 작업을 순차적으로 처리할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;04. async/await 패턴&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;async/await&lt;/strong&gt;: Promise를 더욱 간결하게 사용할 수 있는 문법으로, 비동기 작업을 동기적으로 처리할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// async 함수를 사용하여 비동기 작업을 처리합니다.
const a = () =&amp;gt; {
    return new Promise(resolve =&amp;gt; {
        setTimeout(() =&amp;gt; {
            console.log(1);
            resolve();
        }, 1000);
    });
};

// awite 함수를 사용하여 a함수를 실행하고 완료된 후에 b함수를 실행합니다.
// await 키워드는 뒷 부분의 비동기 작업이 완료될 때까지 기다립니다.
// await a();
// b();

// async 함수를 사용하여 비동기 작업을 순차적으로 처리합니다.
// await 키워드를 사용할 때는 async 함수 내부에서만 사용할 수 있기 때문에
// main 함수를 async 함수로 선언합니다.
const main = async () =&amp;gt; {
    await a(); // 1
    b(); // 2
};

main(); // 1, 2&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;위 코드는 async 함수를 사용하여 비동기 작업을 순차적으로 처리합니다.&lt;/li&gt;
&lt;li&gt;main 함수는 async 함수로 선언되어 있으며, await 키워드를 사용하여 비동기 작업을 순차적으로 처리합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;- async/await를 활용한 실무 예시&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;위 사용자 정보 API 예시를 async/await를 사용하여 구현해보겠습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;const getUser = userId =&amp;gt; {
    return new Promise(resolve =&amp;gt; {
        fetch(`https://jsonplaceholder.typicode.com/users/${userId}`)
            .then(res =&amp;gt; res.json())
            .then(data =&amp;gt; resolve(data));
    });
};

// await 키워드를 사용하여 비동기 작업을 순차적으로 처리합니다.
// const user1 = await getUser(1);
// console.log(`사용자 ID 1의 이름은 ${user1.name} 입니다.`);

// const user2 = await getUser(2);
// console.log(`사용자 ID 2의 이름은 ${user2.name} 입니다.`);

// const user3 = await getUser(3);
// console.log(`사용자 ID 3의 이름은 ${user3.name} 입니다.`);

// await 키워드를 사용할 때는 async 함수로 묶어주어야 합니다.
const main = async () =&amp;gt; {
    const user1 = await getUser(1);
    console.log(`사용자 ID 1의 이름은 ${user1.name} 입니다.`);

    const user2 = await getUser(2);
    console.log(`사용자 ID 2의 이름은 ${user2.name} 입니다.`);

    const user3 = await getUser(3);
    console.log(`사용자 ID 3의 이름은 ${user3.name} 입니다.`);
};

main();&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;위 코드는 async/await를 사용하여 사용자 ID 1, 2, 3의 사용자 정보를 차례대로 가져와 출력합니다.&lt;/li&gt;
&lt;li&gt;main 함수는 async 함수로 선언되어 있으며, await 키워드를 사용하여 비동기 작업을 순차적으로 처리합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;05. Resolve, Reject, Error Handling&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Resolve&lt;/strong&gt;: Promise 객체가 성공적으로 처리되었을 때 호출되는 콜백 함수로, 비동기 작업이 완료되면 결과를 반환합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Reject&lt;/strong&gt;: Promise 객체가 실패했을 때 호출되는 콜백 함수로, 비동기 작업이 실패하면 에러를 반환합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Error Handling&lt;/strong&gt;: Promise 객체가 실패했을 때 에러를 처리하는 방법으로, catch 메서드를 사용하여 에러를 처리합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// 사용자 데이터를 가져오는 함수
const getUser = userId =&amp;gt; {
    // resolve, reject 함수를 사용하여 비동기 작업이 성공 또는 실패했을 때 결과를 반환합니다.
    return new Promise((resolve, reject) =&amp;gt; {
        fetch(`https://jsonplaceholder.typicode.com/users/${userId}`)
            .then(response =&amp;gt; {
                // fetch 함수가 성공적으로 호출되면 response 객체를 반환합니다.
                // response 객체가 정상적이지 않을 경우 에러를 반환합니다.
                if (!response.ok) {
                    throw new Error(&amp;#39;네트워크 응답이 정상적이지 않습니다: &amp;#39; + response.statusText);
                }
                return response.json();
            })
            .then(data =&amp;gt; resolve(data))
            // fetch 함수가 실패했을 경우 에러를 반환합니다.
            .catch(error =&amp;gt; reject(error));
    });
};

// 사용자 데이터를 가져오는 함수를 호출합니다.
getUser(1)
    .then(data =&amp;gt; {
        console.log(`사용자 ID 1의 이름은 ${data.name} 입니다.`);
        return getUser(2);
    })
    .then(data =&amp;gt; {
        console.log(`사용자 ID 2의 이름은 ${data.name} 입니다.`);
        return getUser(3);
    })
    .then(data =&amp;gt; {
        console.log(`사용자 ID 3의 이름은 ${data.name} 입니다.`);
    })
    // catch 메서드를 사용하여 에러를 처리합니다.
    .catch(error =&amp;gt; {
        console.error(&amp;#39;사용자 정보를 가져오는 중 오류가 발생했습니다.&amp;#39;);
    })
    // finally 메서드를 사용하여 작업이 완료되었을 때 메시지를 출력합니다.
    .finally(() =&amp;gt; {
        console.log(&amp;#39;사용자 정보를 가져오는 작업이 완료되었습니다.&amp;#39;);
    });

// async/await를 사용하여 에러를 처리합니다.
const main = async () =&amp;gt; {
    try {
        const user1 = await getUser(1);
        console.log(`사용자 ID 1의 이름은 ${user1.name} 입니다.`);

        const user2 = await getUser(2);
        console.log(`사용자 ID 2의 이름은 ${user2.name} 입니다.`);

        const user3 = await getUser(3);
        console.log(`사용자 ID 3의 이름은 ${user3.name} 입니다.`);
    } catch (error) {
        console.error(&amp;#39;사용자 정보를 가져오는 중 오류가 발생했습니다.&amp;#39;);
    } finally {
        console.log(&amp;#39;사용자 정보를 가져오는 작업이 완료되었습니다.&amp;#39;);
    }
};&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;위 코드는 async/await를 사용하여 사용자 ID 1, 2, 3의 사용자 정보를 차례대로 가져와 출력합니다.&lt;/li&gt;
&lt;li&gt;getWeather 함수는 Promise 객체를 반환하며, fetch 함수가 성공적으로 호출되면 response 객체를 반환합니다.&lt;/li&gt;
&lt;li&gt;response 객체가 정상적이지 않을 경우 에러를 반환하며, fetch 함수가 실패했을 경우 에러를 반환합니다.&lt;/li&gt;
&lt;li&gt;catch 메서드를 사용하여 에러를 처리하며, finally 메서드를 사용하여 작업이 완료되었을 때 메시지를 출력합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;06. axios&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;axios&lt;/strong&gt;: HTTP 클라이언트 라이브러리로, 비동기 방식으로 서버와 데이터를 주고받을 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href=&quot;https://axios-http.com/kr/&quot;&gt;axios&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;- 문법&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;const axios = require(&amp;#39;axios&amp;#39;);

// 지정된 ID를 가진 유저에 대한 요청
axios.get(&amp;#39;/user?ID=12345&amp;#39;)
  .then(function (response) {
    // 성공 핸들링
    console.log(response);
  })
  .catch(function (error) {
    // 에러 핸들링
    console.log(error);
  })
  .finally(function () {
    // 항상 실행되는 영역
  });&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- axios를 활용한 실무 예시&lt;/h3&gt;
&lt;p&gt;cdn : &lt;code&gt;&amp;lt;script src=&amp;quot;https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;const getUser = userId =&amp;gt; {
    return new Promise((resolve, reject) =&amp;gt; {
        axios.get(`https://jsonplaceholder.typicode.com/users/${userId}`)
            .then(response =&amp;gt; resolve(response.data))
            .catch(error =&amp;gt; reject(error));
    });
};

// async/await를 사용하여 에러를 처리
const main = async () =&amp;gt; {
    try {
        const user1 = await getUser(1);
        console.log(`사용자 ID 1의 이름은 ${user1.name} 입니다.`);

        const user2 = await getUser(2);
        console.log(`사용자 ID 2의 이름은 ${user2.name} 입니다.`);

        const user3 = await getUser(3);
        console.log(`사용자 ID 3의 이름은 ${user3.name} 입니다.`);
    } catch (error) {
        console.error(&amp;#39;사용자 정보를 가져오는 중 오류가 발생했습니다.&amp;#39;);
    } finally {
        console.log(&amp;#39;사용자 정보를 가져오는 작업이 완료되었습니다.&amp;#39;);
    }
};&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;주요 변경사항:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;fetch → axios.get&lt;/code&gt; 으로 변경&lt;/li&gt;
&lt;li&gt;&lt;code&gt;response.json()&lt;/code&gt; 과정이 필요 없음 (axios는 자동으로 JSON 변환)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;response.ok&lt;/code&gt; 체크가 필요 없음 (axios는 에러 상태코드를 자동으로 catch로 보냄)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;response.data&lt;/code&gt;로 바로 데이터에 접근 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;axios가 더 간단하고 편리하죠!  &lt;/p&gt;
&lt;h2&gt;06. fetch API&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;fetch(주소, 옵션)&lt;/li&gt;
&lt;li&gt;네트워크를 통해 리소스를 요청(Request) 및 응답(Response)하는 API로,&lt;/li&gt;
&lt;li&gt;Promise 객체를 반환하여 비동기 작업을 처리합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;console 창에서 fetch 함수를 사용하여 &lt;a href=&quot;https://jsonplaceholder.typicode.com/&quot;&gt;JSONPlaceholder&lt;/a&gt; API를 호출해보겠습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// fetch 함수를 사용하여 JSONPlaceholder API를 호출합니다.
console.log(fetch(&amp;#39;https://jsonplaceholder.typicode.com/users&amp;#39;));&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;콘솔창의 promise instance를 클릭하면&lt;br&gt;&lt;strong&gt;then, catch, finally 메서드&lt;/strong&gt;를 사용할 수 있도록 지정된 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/odada/post/aae76968-a811-43c2-afef-18040a1459d3/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;fetch 함수를 사용하여 JSONPlaceholder API를 호출하면, Promise 객체를 반환합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;fetch(&amp;#39;https://jsonplaceholder.typicode.com/users&amp;#39;).then(res =&amp;gt; console.log(res));&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/odada/post/da516083-7c85-446d-92ad-41340c53f5d5/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;json 메서드를 사용하여 response 객체를 JSON 형태로 변환할 수 있습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;fetch(&amp;#39;https://jsonplaceholder.typicode.com/users&amp;#39;).then(res =&amp;gt; console.log(res.json()));&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;그러면 json 형태로 변환된 데이터를 확인할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/odada/post/daf4b59f-6b4c-491c-95c9-e13cbfb1d1ef/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;fetch 함수를 사용하여 JSONPlaceholder API를 호출하면, JSON 형태로 변환된 데이터를 반환합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;fetch(&amp;#39;https://jsonplaceholder.typicode.com/users&amp;#39;, {
    // HTTP 요청 메서드를 지정합니다.
    method: &amp;#39;POST&amp;#39;,
    // 서버로 전송되는 데이터의 형식을 지정합니다.
    headers: {
        &amp;#39;Content-Type&amp;#39;: &amp;#39;application/json&amp;#39;,
    },
    // 서버로 전송되는 데이터를 지정합니다.
    // body 옵션에 명시하는 데이터는 문자열 형태로 지정해야 합니다.
    // JSON.stringify 메서드를 사용하여 JSON 형태로 변환합니다.
    body: JSON.stringify({
        title: &amp;#39;foo&amp;#39;,
        body: &amp;#39;bar&amp;#39;,
        userId: 1,
    }),
})
    .then(res =&amp;gt; res.json()) // JSON 형태로 변환된 데이터를 반환합니다.
    .then(json =&amp;gt; console.log(json)); // JSON 형태로 변환된 데이터를 출력합니다.

// .then(res =&amp;gt; {
//     return res.json();
// });&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;fetch 함수가 promise 객체를 반환하므로, async/await를 사용하여 비동기 작업을 처리할 수 있습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;const main = async () =&amp;gt; {
    const res = await fetch(&amp;#39;https://jsonplaceholder.typicode.com/users&amp;#39;);
    const json = await res.json();
    console.log(json);
};

main();&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Front/JavaScript</category>
      <author>oodada</author>
      <guid isPermaLink="true">https://oddcode.tistory.com/337</guid>
      <comments>https://oddcode.tistory.com/337#entry337comment</comments>
      <pubDate>Sat, 6 Jul 2024 22:17:17 +0900</pubDate>
    </item>
    <item>
      <title>변수(Variable) - javascript 기본</title>
      <link>https://oddcode.tistory.com/335</link>
      <description>&lt;h1&gt;JS변수(Variable)&lt;/h1&gt;
&lt;h2&gt;변수 선언&lt;/h2&gt;
&lt;p&gt;변수 선언은 &lt;code&gt;var&lt;/code&gt;, &lt;code&gt;let&lt;/code&gt;, &lt;code&gt;const&lt;/code&gt; 키워드를 사용한다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;var a = 1;
let b = 2;
const c = 3;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;var&lt;/code&gt; : 변수 재선언 가능&lt;/li&gt;
&lt;li&gt;&lt;code&gt;let&lt;/code&gt; : 변수 재선언 불가능&lt;/li&gt;
&lt;li&gt;&lt;code&gt;const&lt;/code&gt; : 변수 재선언 불가능, 상수 선언&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;변수 타입&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;number&lt;/code&gt; : 숫자&lt;/li&gt;
&lt;li&gt;&lt;code&gt;string&lt;/code&gt; : 문자열&lt;/li&gt;
&lt;li&gt;&lt;code&gt;boolean&lt;/code&gt; : 참 &amp;amp; 거짓&lt;/li&gt;
&lt;li&gt;&lt;code&gt;null&lt;/code&gt; &amp;amp; &lt;code&gt;undefined&lt;/code&gt; : 값 없음&lt;/li&gt;
&lt;li&gt;&lt;code&gt;object&lt;/code&gt; : 객체&lt;/li&gt;
&lt;li&gt;&lt;code&gt;array&lt;/code&gt; : 배열&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;변수 이름 규칙&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;변수명은 문자, 숫자, 언더스코어(_), 달러 기호($)로 구성&lt;/li&gt;
&lt;li&gt;변수명은 숫자로 시작할 수 없음&lt;/li&gt;
&lt;li&gt;변수명은 대소문자를 구분&lt;/li&gt;
&lt;li&gt;예약어(this, if 등) 사용 불가능&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;변수 할당&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;=&lt;/code&gt; : 할당 연산자&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;let a = 1;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;변수 재할당&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;변수에 새로운 값을 할당하는 것&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;let a = 1;
a = 2;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;변수 참조&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;변수에 할당된 값을 참조하는 것&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;let a = 1;
let b = a;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;변수 범위&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;global&lt;/code&gt; : 전역 변수&lt;/li&gt;
&lt;li&gt;&lt;code&gt;local&lt;/code&gt; : 지역 변수&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;let a = 1; // global

function test() {
    let b = 2; // local
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;변수 호이스팅&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;변수 호이스팅은 변수를 선언하기 전에 참조할 수 있는 현상&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;console.log(a); // undefined
var a = 1;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;변수 스코프&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;변수 스코프는 변수의 유효 범위가 어디까지인지를 나타냄&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;let a = 1; // global

function test() {
    let b = 2; // local
    console.log(a); // 1
    console.log(b); // 2
}

console.log(a); // 1
console.log(b); // b는 지역 변수이기 때문에 전역에서 참조할 수 없음&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;변수 형 변환&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Number&lt;/code&gt; : 숫자로 변환&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;const a = &amp;#39;1&amp;#39;;
const b = &amp;#39;2&amp;#39;;

console.log(a + b); // 12
console.log(Number(a) + Number(b)); // 3&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;String&lt;/code&gt; : 문자열로 변환&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;const a = 1;
const b = 2;

console.log(a + b); // 3
console.log(String(a) + String(b)); // 12&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Boolean&lt;/code&gt; : 참 &amp;amp; 거짓&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;const a = 1;
const b = 2;

console.log(a === b); // false
console.log(Boolean(a === b)); // false
// a와 b가 같은지 비교한 결과를 boolean으로 변환&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Front/JavaScript</category>
      <author>oodada</author>
      <guid isPermaLink="true">https://oddcode.tistory.com/335</guid>
      <comments>https://oddcode.tistory.com/335#entry335comment</comments>
      <pubDate>Sun, 23 Jun 2024 21:08:25 +0900</pubDate>
    </item>
    <item>
      <title>javascript 핵심 요약</title>
      <link>https://oddcode.tistory.com/334</link>
      <description>&lt;h1&gt;javascript 핵심 요약&lt;/h1&gt;
&lt;p&gt;&lt;img src=&quot;https://i.namu.wiki/i/Ekx2TcTVJLW4mbkiOrcWGSdqAWWRj8OqXHu7XFKdYFVbi8slMrgY5doKsL78Xt_0pnmqscD_YoNhJvMmm-Q1nLcUuDG05GJZlY2q9eDAgbh88JvEHwpmJGTPwjbdbrymAhfyGymbY0tDBxdosFjH7g.svg&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;자바스크립트는 웹페이지를 동적으로 만들어주는 언어로 웹페이지를 꾸미거나 사용자와 상호작용을 할 수 있게 해준다.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.tiobe.com/tiobe-index/&quot;&gt;TIOBE 프로그래밍 언어 순위&lt;/a&gt;에서 2024년 기준 6위에 위치하고 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/odada/post/960f9938-294c-43ec-94f3-8a1528971c1a/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h2&gt;자바스크립트 소개&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.w3schools.com/js/js_intro.asp&quot;&gt;자바스크립트 소개&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;자바스크립트는 HTML 콘텐츠를 변경할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
    &amp;lt;body&amp;gt;
        &amp;lt;h2&amp;gt;What Can JavaScript Do?&amp;lt;/h2&amp;gt;

        &amp;lt;p id=&amp;quot;demo&amp;quot;&amp;gt;JavaScript can change HTML content.&amp;lt;/p&amp;gt;

        &amp;lt;button type=&amp;quot;button&amp;quot; onclick=&amp;#39;document.getElementById(&amp;quot;demo&amp;quot;).innerHTML = &amp;quot;Hello JavaScript!&amp;quot;&amp;#39;&amp;gt;
            Click Me!
        &amp;lt;/button&amp;gt;
    &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;자바스크립트는 HTML 속성을 변경할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
    &amp;lt;body&amp;gt;
        &amp;lt;h2&amp;gt;What Can JavaScript Do?&amp;lt;/h2&amp;gt;

        &amp;lt;button onclick=&amp;quot;document.getElementById(&amp;#39;myImage&amp;#39;).src=&amp;#39;pic_bulbon.gif&amp;#39;&amp;quot;&amp;gt;Turn on the light&amp;lt;/button&amp;gt;
        &amp;lt;img id=&amp;quot;myImage&amp;quot; src=&amp;quot;pic_bulboff.gif&amp;quot; style=&amp;quot;width:100px&amp;quot; /&amp;gt;
        &amp;lt;button onclick=&amp;quot;document.getElementById(&amp;#39;myImage&amp;#39;).src=&amp;#39;pic_bulboff.gif&amp;#39;&amp;quot;&amp;gt;Turn off the light&amp;lt;/button&amp;gt;
    &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;자바스크립트는 HTML 스타일을 변경할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
    &amp;lt;body&amp;gt;
        &amp;lt;h2&amp;gt;What Can JavaScript Do?&amp;lt;/h2&amp;gt;

        &amp;lt;p id=&amp;quot;demo&amp;quot; style=&amp;quot;color: red;&amp;quot;&amp;gt;Hello JavaScript!&amp;lt;/p&amp;gt;

        &amp;lt;button type=&amp;quot;button&amp;quot; onclick=&amp;quot;document.getElementById(&amp;#39;demo&amp;#39;).style.color = &amp;#39;blue&amp;#39;&amp;quot;&amp;gt;Click Me!&amp;lt;/button&amp;gt;
    &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;자바스크립트는 HTML 요소를 숨길 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
    &amp;lt;body&amp;gt;
        &amp;lt;h2&amp;gt;What Can JavaScript Do?&amp;lt;/h2&amp;gt;

        &amp;lt;p id=&amp;quot;demo&amp;quot;&amp;gt;Hello JavaScript!&amp;lt;/p&amp;gt;

        &amp;lt;button type=&amp;quot;button&amp;quot; onclick=&amp;quot;document.getElementById(&amp;#39;demo&amp;#39;).style.display = &amp;#39;none&amp;#39;&amp;quot;&amp;gt;Click Me!&amp;lt;/button&amp;gt;
    &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;자바스크립트는 HTML 요소를 보여줄 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
    &amp;lt;body&amp;gt;
        &amp;lt;h2&amp;gt;What Can JavaScript Do?&amp;lt;/h2&amp;gt;

        &amp;lt;p id=&amp;quot;demo&amp;quot; style=&amp;quot;display: none;&amp;quot;&amp;gt;Hello JavaScript!&amp;lt;/p&amp;gt;

        &amp;lt;button type=&amp;quot;button&amp;quot; onclick=&amp;quot;document.getElementById(&amp;#39;demo&amp;#39;).style.display = &amp;#39;block&amp;#39;&amp;quot;&amp;gt;Click Me!&amp;lt;/button&amp;gt;
    &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;자바스크립트의 사용&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.w3schools.com/js/js_whereto.asp&quot;&gt;자바스크립트 사용하기&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; 태그를 사용하여 자바스크립트 코드를 작성할 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; 태그는 HTML 문서의 &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; 또는 &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt; 태그 안에 작성할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
    &amp;lt;body&amp;gt;
        &amp;lt;h2&amp;gt;JavaScript in Body&amp;lt;/h2&amp;gt;

        &amp;lt;p id=&amp;quot;demo&amp;quot;&amp;gt;A Paragraph.&amp;lt;/p&amp;gt;

        &amp;lt;button type=&amp;quot;button&amp;quot; onclick=&amp;quot;myFunction()&amp;quot;&amp;gt;Try it&amp;lt;/button&amp;gt;

        &amp;lt;script&amp;gt;
            function myFunction() {
                document.getElementById(&amp;#39;demo&amp;#39;).innerHTML = &amp;#39;Paragraph changed.&amp;#39;
            }
        &amp;lt;/script&amp;gt;
    &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; 태그를 사용하여 외부 자바스크립트 파일을 불러올 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; 태그의 &lt;code&gt;src&lt;/code&gt; 속성에 외부 자바스크립트 파일의 경로를 지정하면 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
    &amp;lt;body&amp;gt;
        &amp;lt;h2&amp;gt;JavaScript in Body&amp;lt;/h2&amp;gt;

        &amp;lt;p id=&amp;quot;demo&amp;quot;&amp;gt;A Paragraph.&amp;lt;/p&amp;gt;

        &amp;lt;button type=&amp;quot;button&amp;quot; onclick=&amp;quot;myFunction()&amp;quot;&amp;gt;Try it&amp;lt;/button&amp;gt;

        &amp;lt;script src=&amp;quot;myScript.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;
    &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;자바스크립트의 기본 문법&lt;/h1&gt;
&lt;h2&gt;1. 변수(Variable)&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;변수는 어떠한 값(Value)을 저장하는 공간(Stronge Location)으로써 값을 저장하고 사용하기 위해 사용한다. 변수를 선언할 때는 &lt;code&gt;let&lt;/code&gt;, &lt;code&gt;const&lt;/code&gt;, &lt;code&gt;var&lt;/code&gt; 키워드를 사용한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;변수 이름 규칙&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;변수 이름은 문자, 숫자, 밑줄(_), 달러 기호($)로 구성할 수 있으며, 대소문자를 구분한다.&lt;/li&gt;
&lt;li&gt;변수 이름은 숫자로 시작할 수 없다.&lt;/li&gt;
&lt;li&gt;예약어(if, while, for 등)는 변수 이름으로 사용할 수 없다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;변수의 의미&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;변수란 데이터를 저장하는 메모리 공간을 의미한다. 변수에 값을 저장하면 변수에 저장된 값은 메모리에 저장된다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;ul&gt;
&lt;li&gt;변수란 &lt;code&gt;변할 수 있는 것&lt;/code&gt;으로 어떠한 값을 담는 &lt;code&gt;상자&lt;/code&gt;라고 생각하면 된다.&lt;ul&gt;
&lt;li&gt;예를 들어, &lt;code&gt;let a = 1&lt;/code&gt;이라고 선언하면 &lt;code&gt;a&lt;/code&gt;라는 변수에 &lt;code&gt;1&lt;/code&gt;이라는 값을 담는다라는 의미이다.&lt;/li&gt;
&lt;li&gt;그리고 이것을 &lt;code&gt;변수에 값을 할당한다&lt;/code&gt;라고 표현한다.&lt;/li&gt;
&lt;li&gt;그동안 사용했던 같다라는 의미의 &lt;code&gt;=(등호)&lt;/code&gt;는 프로그래밍에서는 &lt;code&gt;할당한다&lt;/code&gt;라는 의미로 사용된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;변수를 사용하는 이유는 &lt;code&gt;값을 재사용&lt;/code&gt;하기 위해서이다.&lt;ul&gt;
&lt;li&gt;예를 들어, &lt;code&gt;let a = 1&lt;/code&gt;이라고 선언하면 &lt;code&gt;a&lt;/code&gt;라는 변수에 &lt;code&gt;1&lt;/code&gt;이라는 값을 담는다.&lt;/li&gt;
&lt;li&gt;그리고 이 값을 재사용할 때 &lt;code&gt;a&lt;/code&gt;라는 변수를 사용하면 된다.&lt;/li&gt;
&lt;li&gt;이렇게 되면 &lt;code&gt;1&lt;/code&gt;이라는 값을 여러 번 사용할 때 &lt;code&gt;1&lt;/code&gt;이라는 값을 여러 번 쓰지 않아도 되기 때문에 편리하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;변수에 줄 수 있는 값은 숫자, 문자, 불리언, 객체, 배열 등 다양한 데이터 타입이 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// a라는 변수에 1이라는 값을 할당한다.
let a = 1
// b라는 변수에 2이라는 값을 할당한다.
const b = 2
// c라는 변수에 3이라는 값을 할당한다.
var c = 3

console.log(a) // 1
console.log(b) // 2
console.log(c) // 3&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;변수 선언&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;변수를 선언할 때는 &lt;code&gt;let&lt;/code&gt;, &lt;code&gt;const&lt;/code&gt;, &lt;code&gt;var&lt;/code&gt; 키워드를 사용한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;1. &lt;code&gt;let&lt;/code&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;let&lt;/code&gt;은 변수를 선언할 때 사용하는 키워드로 변수를 선언하면 변수를 재할당할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;let a = 1
a = 2

console.log(a) // 2&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;2. &lt;code&gt;const&lt;/code&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;const&lt;/code&gt;는 상수를 선언할 때 사용하는 키워드로 변수를 선언하면 변수를 재할당할 수 없다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;const b = 1

b = 2 // TypeError: Assignment to constant variable.&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;3. &lt;code&gt;var&lt;/code&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;var&lt;/code&gt;는 변수를 선언할 때 사용하는 키워드로 변수를 선언하면 변수를 재할당할 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;var&lt;/code&gt; 재할당 오류가 발생하지 않기 때문에 &lt;code&gt;let&lt;/code&gt;, &lt;code&gt;const&lt;/code&gt;를 사용하는 것이 좋다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;var c = 1
c = 2

console.log(c) // 2&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;2. 데이터 타입(Data Type)&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;자바스크립트의 데이터 타입이란 변수에 저장할 수 있는 값의 종류를 의미한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Number&lt;/strong&gt; : 숫자 타입으로 정수, 실수, NaN, Infinity 등의 값을 저장한다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;String&lt;/strong&gt; : 문자열 타입으로 문자열을 저장한다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Boolean&lt;/strong&gt; : 불리언 타입으로 참(true)과 거짓(false)을 저장한다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Object&lt;/strong&gt; : 객체 타입으로 객체를 저장한다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Array&lt;/strong&gt; : 배열 타입으로 배열을 저장한다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Function&lt;/strong&gt; : 함수 타입으로 함수를 저장한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;- Number&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;숫자 타입으로 정수, 실수, NaN, Infinity 등의 값을 저장한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;let a = 1
let b = 2

console.log(a) // 1
console.log(b) // 2&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- String&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;문자열 타입으로 문자열을 저장한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;let a = &amp;#39;Hello&amp;#39;
let b = &amp;#39;World&amp;#39;

console.log(a) // Hello
console.log(b) // World&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- Boolean&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;불리언 타입으로 참(true)과 거짓(false)을 저장한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;let a = true
let b = false

console.log(a) // true
console.log(b) // false&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- Object&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;객체 타입으로 객체를 저장한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;let a = { name: &amp;#39;John&amp;#39;, age: 20 }
let b = { name: &amp;#39;Jane&amp;#39;, age: 25 }

console.log(a) // { name: &amp;#39;John&amp;#39;, age: 20 }
console.log(b) // { name: &amp;#39;Jane&amp;#39;, age: 25 }&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- Array&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;배열 타입으로 배열을 저장한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;let a = [1, 2, 3]
let b = [4, 5, 6]

console.log(a) // [1, 2, 3]
console.log(b) // [4, 5, 6]&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- Function&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;함수 타입으로 함수를 저장한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;let a = function () {
    console.log(&amp;#39;Hello&amp;#39;)
}

let b = function () {
    console.log(&amp;#39;World&amp;#39;)
}

a() // Hello
b() // World&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;3. 연산자(Operator)&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;연산자는 피연산자(Operand)를 대상으로 연산을 수행하는 기호이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;산술 연산자&lt;/strong&gt; : 더하기(+), 빼기(-), 곱하기(*), 나누기(/), 나머지(%)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;대입 연산자&lt;/strong&gt; : 할당(=), 더하기 후 할당(+=), 빼기 후 할당(-=), 곱하기 후 할당(*=), 나누기 후 할당(/=), 나머지 후 할당(%=)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;비교 연산자&lt;/strong&gt; : 같다(===), 같지 않다(!==), 크다(&amp;gt;), 작다(&amp;lt;), 크거나 같다(&amp;gt;=), 작거나 같다(&amp;lt;=)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;논리 연산자&lt;/strong&gt; : 논리곱(&amp;amp;&amp;amp;), 논리합(||), 논리부정(!)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;삼항 연산자&lt;/strong&gt; : 조건 ? 참 : 거짓&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;타입 연산자&lt;/strong&gt; : typeof&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;- 산술 연산자&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;산술 연산자는 더하기(+), 빼기(-), 곱하기(*), 나누기(/), 나머지(%) 연산을 수행한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;let a = 1
let b = 2

console.log(a + b) // 3
console.log(a - b) // -1
console.log(a * b) // 2
console.log(a / b) // 0.5
console.log(a % b) // 1&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- 대입 연산자&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;대입 연산자는 변수에 값을 할당하는 연산을 수행한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;let a = 1

a += 2
console.log(a) // 3

a -= 2
console.log(a) // 1

a *= 2
console.log(a) // 2

a /= 2
console.log(a) // 1

a %= 2
console.log(a) // 1&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- 비교 연산자&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;비교 연산자는 두 값을 비교하는 연산을 수행한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;let a = 1
let b = 2

console.log(a === b) // false
console.log(a !== b) // true
console.log(a &amp;gt; b) // false
console.log(a &amp;lt; b) // true
console.log(a &amp;gt;= b) // false
console.log(a &amp;lt;= b) // true&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- 논리 연산자&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;논리 연산자는 논리곱(&amp;amp;&amp;amp;), 논리합(||), 논리부정(!) 연산을 수행한다.&lt;/li&gt;
&lt;li&gt;논리곱(&amp;amp;&amp;amp;) : 두 값이 모두 참일 때 참을 반환한다.&lt;/li&gt;
&lt;li&gt;논리합(||) : 두 값 중 하나라도 참일 때 참을 반환한다.&lt;/li&gt;
&lt;li&gt;논리부정(!) : 값이 참이면 거짓을 반환하고 거짓이면 참을 반환한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;let a = true
let b = false

console.log(a &amp;amp;&amp;amp; b) // false
console.log(a || b) // true
console.log(!a) // false
console.log(!b) // true&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- 삼항 연산자&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;삼항 연산자는 조건을 판단하여 참일 때와 거짓일 때의 값을 반환한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;let a = 1
let b = 2

let result = a &amp;gt; b ? &amp;#39;a가 크다&amp;#39; : &amp;#39;b가 크다&amp;#39;

console.log(result) // b가 크다&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;4. 조건문(Conditional Statement)&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;조건문은 조건식을 판단하여 해당하는 코드 블록을 실행하는 제어문이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;if문&lt;/strong&gt; : 조건식이 참일 때 코드 블록을 실행한다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;if-else문&lt;/strong&gt; : 조건식이 참일 때 코드 블록을 실행하고 거짓일 때 다른 코드 블록을 실행한다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;if-else if-else문&lt;/strong&gt; : 여러 조건식을 판단하여 해당하는 코드 블록을 실행한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;- if문&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;if문&lt;/code&gt;은 조건식이 참일 때 코드 블록을 실행한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;let a = 1

if (a === 1) {
    console.log(&amp;#39;a는 1이다.&amp;#39;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- if-else문&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;if-else문&lt;/code&gt;은 조건식이 참일 때 코드 블록을 실행하고 거짓일 때 다른 코드 블록을 실행한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;let a = 1

if (a === 1) {
    console.log(&amp;#39;a는 1이다.&amp;#39;)
} else {
    console.log(&amp;#39;a는 1이 아니다.&amp;#39;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- if-else if-else문&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;if-else if-else문&lt;/code&gt;은 여러 조건식을 판단하여 해당하는 코드 블록을 실행한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;let a = 1

if (a === 1) {
    console.log(&amp;#39;a는 1이다.&amp;#39;)
} else if (a === 2) {
    console.log(&amp;#39;a는 2이다.&amp;#39;)
} else {
    console.log(&amp;#39;a는 1도 2도 아니다.&amp;#39;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;5. 반복문(Loop Statement)&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;반복문은 조건식이 참일 때 코드 블록을 반복 실행하는 제어문이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;for문&lt;/strong&gt; : 조건식이 참일 때 코드 블록을 반복 실행한다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;while문&lt;/strong&gt; : 조건식이 참일 때 코드 블록을 반복 실행한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;- for문&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;for문&lt;/code&gt;은 조건식이 참일 때 코드 블록을 반복 실행한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;for (let i = 0; i &amp;lt; 5; i++) {
    console.log(i)
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- while문&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;while문&lt;/code&gt;은 조건식이 참일 때 코드 블록을 반복 실행한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;let i = 0

while (i &amp;lt; 5) {
    console.log(i)
    i++
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;6. 함수(Function)&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;함수는 코드 블록을 하나로 묶어서 재사용할 수 있는 기능이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;함수 선언&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;함수를 선언할 때는 &lt;code&gt;function&lt;/code&gt; 키워드를 사용한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;function hello() {
    console.log(&amp;#39;Hello&amp;#39;)
}

hello() // Hello&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;함수 표현식&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;함수를 변수에 할당할 때는 &lt;code&gt;함수 표현식&lt;/code&gt;을 사용한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;let hello = function () {
    console.log(&amp;#39;Hello&amp;#39;)
}

hello() // Hello&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;화살표 함수&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;화살표 함수는 &lt;code&gt;function&lt;/code&gt; 키워드를 생략하여 함수를 선언한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;let hello = () =&amp;gt; {
    console.log(&amp;#39;Hello&amp;#39;)
}

hello() // Hello&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;7. 객체(Object)&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;객체는 여러 개의 프로퍼티(Property)와 메소드(Method)를 하나로 묶은 복합 데이터 타입이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;객체 생성&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;객체를 생성할 때는 중괄호({})를 사용한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;let person = {
    name: &amp;#39;John&amp;#39;,
    age: 20,
    hello: function () {
        console.log(&amp;#39;Hello&amp;#39;)
    },
}

console.log(person.name) // John
console.log(person.age) // 20
person.hello() // Hello&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;객체 프로퍼티&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;객체의 프로퍼티는 객체의 속성을 의미한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;let person = {
    name: &amp;#39;John&amp;#39;,
    age: 20,
}

console.log(person) // { name: &amp;#39;John&amp;#39;, age: 20 }
console.log(person.name) // John
console.log(person.age) // 20&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;객체 메소드&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;객체의 메소드는 객체의 동작을 의미한다.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.w3schools.com/js/js_object_method.asp&quot;&gt;w3c Object Methods&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;let person = {
    name: &amp;#39;John&amp;#39;,
    age: 20,
    hello: function () {
        console.log(&amp;#39;Hello&amp;#39;)
    },
}

person.hello() // Hello&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;8. 배열(Array)&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;배열은 여러 개의 요소(Element)를 하나로 묶은 복합 데이터 타입이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;배열 생성 &amp;amp; 요소&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;배열을 생성할 때는 대괄호([])를 사용한다.&lt;/li&gt;
&lt;li&gt;배열의 요소는 배열의 값이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;let arr = [1, 2, 3]

console.log(arr[0]) // 1
console.log(arr[1]) // 2
console.log(arr[2]) // 3&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;배열 메소드&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;배열의 메소드는 배열의 동작을 의미한다.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.w3schools.com/js/js_array_methods.asp&quot;&gt;w3c Array Methods&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;let arr = [1, 2, 3]

arr.push(4)
console.log(arr) // [1, 2, 3, 4]

arr.pop()
console.log(arr) // [1, 2, 3]&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;9. 콜백 함수(Callback Function)&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;콜백 함수는 함수의 인자로 전달되어 특정 시점에 호출되는 함수이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;function hello(callback) {
    console.log(&amp;#39;Hello&amp;#39;)
    callback()
}

hello(function () {
    console.log(&amp;#39;World&amp;#39;)
})&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;10. 이벤트(Event)&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;이벤트는 사용자의 동작에 의해 발생하는 사건을 의미한다.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.w3schools.com/js/js_htmldom_events.asp&quot;&gt;w3c Event&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;let button = document.querySelector(&amp;#39;button&amp;#39;)

button.addEventListener(&amp;#39;click&amp;#39;, function () {
    console.log(&amp;#39;Click&amp;#39;)
})&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;11. DOM(Document Object Model)&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;DOM은 웹 페이지의 요소를 객체로 표현한 모델을 의미한다.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.w3schools.com/js/js_htmldom.asp&quot;&gt;w3c DOM&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;let button = document.querySelector(&amp;#39;button&amp;#39;)

button.addEventListener(&amp;#39;click&amp;#39;, function () {
    console.log(&amp;#39;Click&amp;#39;)
})&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;12. AJAX(Asynchronous JavaScript And XML)&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;AJAX는 비동기적으로 서버와 통신하는 기술을 의미한다.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.w3schools.com/js/js_ajax_intro.asp&quot;&gt;w3c AJAX&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;let xhr = new XMLHttpRequest()

xhr.open(&amp;#39;GET&amp;#39;, &amp;#39;https://jsonplaceholder.typicode.com/posts&amp;#39;, true)
xhr.send()

xhr.onload = function () {
    if (xhr.status === 200) {
        console.log(JSON.parse(xhr.responseText))
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;13. ES6(ECMAScript 6)&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;ES6는 ECMAScript 6의 줄임말로 자바스크립트의 표준을 의미한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;let, const&lt;/strong&gt; : 변수를 선언할 때 사용하는 키워드로 &lt;code&gt;let&lt;/code&gt;은 변수를 선언할 때 사용하고 &lt;code&gt;const&lt;/code&gt;는 상수를 선언할 때 사용한다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;화살표 함수&lt;/strong&gt; : 함수를 선언할 때 사용하는 키워드로 &lt;code&gt;function&lt;/code&gt; 키워드를 생략하여 함수를 선언한다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;템플릿 리터럴&lt;/strong&gt; : 문자열을 표현할 때 사용하는 키워드로 백틱(`)을 사용하여 문자열을 표현한다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;구조 분해 할당&lt;/strong&gt; : 배열이나 객체의 값을 추출하여 변수에 할당하는 기능이다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;클래스&lt;/strong&gt; : 객체를 생성할 때 사용하는 키워드로 &lt;code&gt;class&lt;/code&gt; 키워드를 사용하여 객체를 생성한다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;모듈&lt;/strong&gt; : 코드를 모듈화하여 재사용할 수 있는 기능이다.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// math.js
export function sum(a, b) {
    return a + b
}

// index.js
import { sum } from &amp;#39;./math.js&amp;#39;

console.log(sum(1, 2)) // 3&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;14. Babel&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Babel은 자바스크립트 코드를 ES5로 변환하는 도구이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// ES6
let a = 1

// Babel
;(&amp;#39;use strict&amp;#39;)

var a = 1&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;15. Webpack&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Webpack은 모듈 번들러로 여러 개의 파일을 하나로 묶어주는 기능을 제공한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// webpack.config.js
module.exports = {
    entry: &amp;#39;./src/index.js&amp;#39;,
    output: {
        filename: &amp;#39;bundle.js&amp;#39;,
        path: path.resolve(__dirname, &amp;#39;dist&amp;#39;),
    },
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;16. NPM(Node Package Manager)&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;NPM은 자바스크립트 패키지 매니저로 자바스크립트 라이브러리를 설치하고 관리하는 기능을 제공한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// package.json
{
    &amp;quot;dependencies&amp;quot;: {
        &amp;quot;react&amp;quot;: &amp;quot;^17.0.2&amp;quot;,
        &amp;quot;react-dom&amp;quot;: &amp;quot;^17.0.2&amp;quot;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;17. Node.js&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Node.js는 자바스크립트 런타임 환경으로 자바스크립트를 실행할 수 있는 환경을 제공한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// app.js
console.log(&amp;#39;Hello Node.js&amp;#39;)&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;18. Express&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Express는 Node.js 웹 프레임워크로 웹 서버를 구축할 수 있는 기능을 제공한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// app.js
const express = require(&amp;#39;express&amp;#39;)
const app = express()

app.get(&amp;#39;/&amp;#39;, (req, res) =&amp;gt; {
    res.send(&amp;#39;Hello Express&amp;#39;)
})

app.listen(3000, () =&amp;gt; {
    console.log(&amp;#39;Server is running on http://localhost:3000&amp;#39;)
})&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;19. React&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;React는 페이스북에서 개발한 자바스크립트 라이브러리로 사용자 인터페이스를 만들 수 있는 기능을 제공한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// App.js
import React from &amp;#39;react&amp;#39;

function App() {
    return &amp;lt;h1&amp;gt;Hello React&amp;lt;/h1&amp;gt;
}

export default App&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;20. Vue&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Vue는 에반 유(Evan You)가 개발한 자바스크립트 프레임워크로 사용자 인터페이스를 만들 수 있는 기능을 제공한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// App.vue
&amp;lt;template&amp;gt;
    &amp;lt;h1&amp;gt;Hello Vue&amp;lt;/h1&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script&amp;gt;
export default {
    name: &amp;#39;App&amp;#39;,
}
&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;21. Angular&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Angular는 구글에서 개발한 자바스크립트 프레임워크로 사용자 인터페이스를 만들 수 있는 기능을 제공한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// app.component.ts
import { Component } from &amp;#39;@angular/core&amp;#39;

@Component({
    selector: &amp;#39;app-root&amp;#39;,
    templateUrl: &amp;#39;./app.component.html&amp;#39;,
    styleUrls: [&amp;#39;./app.component.css&amp;#39;],
})
export class AppComponent {
    title = &amp;#39;Hello Angular&amp;#39;
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;22. TypeScript&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;TypeScript는 마이크로소프트에서 개발한 자바스크립트의 상위 집합으로 정적 타입을 지원하는 기능을 제공한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// app.ts
function hello(name: string): string {
    return `Hello ${name}`
}

console.log(hello(&amp;#39;TypeScript&amp;#39;)) // Hello TypeScript&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;23. Next.js&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Next.js는 Vercel에서 개발한 리액트 프레임워크로 서버 사이드 렌더링을 지원하는 기능을 제공한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// pages/index.js
export default function Home() {
    return &amp;lt;h1&amp;gt;Hello Next.js&amp;lt;/h1&amp;gt;
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;24. Nuxt.js&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Nuxt.js는 Nuxt 커뮤니티에서 개발한 뷰 프레임워크로 서버 사이드 렌더링을 지원하는 기능을 제공한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// pages/index.vue
&amp;lt;template&amp;gt;
    &amp;lt;h1&amp;gt;Hello Nuxt.js&amp;lt;/h1&amp;gt;
&amp;lt;/template&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;25. NestJS&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;NestJS는 Kamil Myśliwiec이 개발한 Node.js 프레임워크로 서버 사이드 렌더링을 지원하는 기능을 제공한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// app.controller.ts
import { Controller, Get } from &amp;#39;@nestjs/common&amp;#39;

@Controller()
export class AppController {
    @Get()
    getHello(): string {
        return &amp;#39;Hello NestJS&amp;#39;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;26. GraphQL&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;GraphQL은 페이스북에서 개발한 쿼리 언어로 데이터를 요청할 때 사용하는 기능을 제공한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// schema.graphql
type Query {
    hello: String
}

// resolvers.js
const resolvers = {
    Query: {
        hello: () =&amp;gt; &amp;#39;Hello GraphQL&amp;#39;,
    },
}

// index.js
const { ApolloServer, gql } = require(&amp;#39;apollo-server&amp;#39;)

const typeDefs = gql`
    type Query {
        hello: String
    }
`

const resolvers = {
    Query: {
        hello: () =&amp;gt; &amp;#39;Hello GraphQL&amp;#39;,
    },
}

const server = new ApolloServer({ typeDefs, resolvers })

server.listen().then(({ url }) =&amp;gt; {
    console.log(`Server ready at ${url}`)
})&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;27. MongoDB&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;MongoDB는 NoSQL 데이터베이스로 데이터를 저장하고 관리하는 기능을 제공한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// app.js
const mongoose = require(&amp;#39;mongoose&amp;#39;)

mongoose.connect(&amp;#39;mongodb://localhost:27017/test&amp;#39;, {
    useNewUrlParser: true,
    useUnifiedTopology: true,
})

const Cat = mongoose.model(&amp;#39;Cat&amp;#39;, { name: String })

const kitty = new Cat({ name: &amp;#39;Zildjian&amp;#39; })
kitty.save().then(() =&amp;gt; console.log(&amp;#39;meow&amp;#39;))&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;28. MySQL&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;MySQL은 오픈 소스 관계형 데이터베이스로 데이터를 저장하고 관리하는 기능을 제공한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// app.js
const mysql = require(&amp;#39;mysql&amp;#39;)

const connection = mysql.createConnection({
    host: &amp;#39;localhost&amp;#39;,
    user: &amp;#39;&amp;#39;,
    password: &amp;#39;&amp;#39;,
    database: &amp;#39;&amp;#39;,
})&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;29. airbnb&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;airbnb는 에어비앤비에서 만든 자바스크립트 스타일 가이드로 코드의 품질을 향상시키는 기능을 제공한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// .eslintrc.js
module.exports = {
    extends: [&amp;#39;airbnb-base&amp;#39;],
    rules: {
        &amp;#39;no-console&amp;#39;: &amp;#39;off&amp;#39;,
    },
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;30. Prettier&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Prettier는 코드 포맷터로 코드의 일관성을 유지하는 기능을 제공한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// .prettierrc.js
module.exports = {
    singleQuote: true,
    semi: false,
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Front/JavaScript</category>
      <author>oodada</author>
      <guid isPermaLink="true">https://oddcode.tistory.com/334</guid>
      <comments>https://oddcode.tistory.com/334#entry334comment</comments>
      <pubDate>Thu, 30 May 2024 17:48:27 +0900</pubDate>
    </item>
    <item>
      <title>GSAP을 이용한 Parallax Scrolling</title>
      <link>https://oddcode.tistory.com/333</link>
      <description>&lt;h1&gt;GSAP을 이용한 Parallax Scrolling&lt;/h1&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;패럴랙스 스크롤링은 웹사이트의 요소들이 서로 다른 속도로 움직이는 것&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;ul&gt;
&lt;li&gt;레이어별 스크롤 속도를 다르게 하여 입체감을 주는 디자인 기법&lt;/li&gt;
&lt;li&gt;게임, 애니메이션 등에서 주로 사용되던 기법으로 인터랙티브한 웹사이트를 만들 때 사용&lt;/li&gt;
&lt;li&gt;javascript, css, 라이브러리 등을 이용하여 구현&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://opengameart.org/sites/default/files/Parallax.jpg&quot; alt=&quot;Parallax Scrolling&quot;&gt;&lt;/p&gt;
&lt;h2&gt;GSAP이란?&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;웹 페이지나 웹 앱에서 다양한 애니메이션 효과를 만들 수 있도록 도와주는 강력한 도구로 널리 사용됩니다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://gsap.com/&quot;&gt;GSAP&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;이 라이브러리는 웹 개발자들에게 다양한 기능과 API를 제공하여 스무스하고 반응적인 웹 애니메이션을 쉽게 구현할 수 있게 해줍니다.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://gsap.com/&quot;&gt;GSAP&lt;/a&gt; (GreenSock Animation Platform)은 HTML5 기반의 애니메이션 라이브러리로, CSS, SVG, Canvas, WebGL 등 다양한 요소에 애니메이션을 적용할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://greensock.com/scrolltrigger/&quot;&gt;ScrollTrigger&lt;/a&gt;는 GSAP의 플러그인으로, 스크롤에 따라 요소에 애니메이션을 적용할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://codepen.io/GreenSock&quot;&gt;GSAP 코드펜&lt;/a&gt; : GSAP의 다양한 예제를 확인할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;GSAP, ScrollTrigger 설치하기&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;DOCS : &lt;a href=&quot;https://gsap.com/docs/v3/Installation?tab=cdn&amp;amp;module=esm&amp;amp;method=private+registry&amp;amp;tier=free&amp;amp;club=false&amp;amp;require=false&amp;amp;trial=true&amp;amp;plugins=ScrollTrigger&quot;&gt;GSAP, ScrollTrigger 설치&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;script src=&amp;quot;https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.4/gsap.min.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;script src=&amp;quot;https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.4/ScrollTrigger.min.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;NPM&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm install gsap
npm install gsap@3.6.0
npm install gsap@3.6.0 scrolltrigger&lt;/code&gt;&lt;/pre&gt;
&lt;iframe height=&quot;700&quot; style=&quot;width: 100%;&quot; scrolling=&quot;no&quot; title=&quot;GSAP Basic Tween&quot; src=&quot;https://codepen.io/odada/embed/OJqNQqr?default-tab=html%2Cresult&quot; frameborder=&quot;no&quot; loading=&quot;lazy&quot; allowtransparency=&quot;true&quot; allowfullscreen=&quot;true&quot;&gt;
  See the Pen &lt;a href=&quot;https://codepen.io/odada/pen/OJqNQqr&quot;&gt;
  GSAP Basic Tween&lt;/a&gt; by odada (&lt;a href=&quot;https://codepen.io/odada&quot;&gt;@odada&lt;/a&gt;)
  on &lt;a href=&quot;https://codepen.io&quot;&gt;CodePen&lt;/a&gt;.
&lt;/iframe&gt;

&lt;h2&gt;GSAP 사용법&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;gsap.to(&amp;#39;요소&amp;#39;, { 옵션, 시간 })&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href=&quot;https://gsap.com/docs/v3/GSAP&quot;&gt;GSAP docs&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;요소에 애니메이션을 적용&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;gsap.to()&lt;/code&gt; : 현재 상태에서 시작하여 지정된 상태로 애니메이션을 적용&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;gsap.from()&lt;/code&gt; : 지정된 상태에서 시작하여 현재 상태로 애니메이션을 적용&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;gsap.fromTo()&lt;/code&gt; : 지정된 상태에서 시작하여 지정된 상태로 애니메이션을 적용&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;gsap.to(&amp;quot;.box1&amp;quot;, {
        x: 400,
        y: 400,
        rotation: 45,
        backgroundColor: &amp;quot;blue&amp;quot;,
        duration: 2,
      });&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Timeline 사용법&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;gsap.timeline()&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href=&quot;https://gsap.com/docs/v3/GSAP/Timeline&quot;&gt;Timeline docs&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;여러 개의 애니메이션을 순차적으로 실행&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;add()&lt;/code&gt; : 해당 요소에 애니메이션을 추가&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;addLabel()&lt;/code&gt; : 해당 요소에 레이블을 추가&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;play()&lt;/code&gt; : 애니메이션을 실행&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;pause()&lt;/code&gt; : 애니메이션을 일시정지&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;reverse()&lt;/code&gt; : 애니메이션을 역방향으로 실행&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;time()&lt;/code&gt; : 애니메이션의 시간을 설정&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;seek()&lt;/code&gt; : 애니메이션의 위치를 설정&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;duration()&lt;/code&gt; : 애니메이션의 지속 시간을 설정&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;repeat()&lt;/code&gt; : 애니메이션을 반복&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;yoyo()&lt;/code&gt; : 애니메이션을 왕복&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;progress()&lt;/code&gt; : 애니메이션의 진행 상태를 설정&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;kill()&lt;/code&gt; : 애니메이션을 중지&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const tl = gsap.timeline();

tl.to(&amp;quot;.box2&amp;quot;, { y: 200, duration: 2 })
  .to(&amp;quot;.box2&amp;quot;, { x: 200, duration: 2 })
  .to(&amp;quot;.box2&amp;quot;, { rotation: 45, scale: 2 })
  .to(&amp;quot;.box2&amp;quot;, { backgroundColor: &amp;quot;red&amp;quot;, duration: 2 });&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;ScrollTrigger 사용법&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href=&quot;https://greensock.com/docs/v3/Plugins/ScrollTrigger&quot;&gt;ScrollTrigger docs&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;스크롤에 따라 요소에 애니메이션을 적용&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;trigger&lt;/code&gt; : 트리거가 되는 요소&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;start&lt;/code&gt; : 트리거가 되는 요소의 시작 위치&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;end&lt;/code&gt; : 트리거가 되는 요소의 끝 위치&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;scrub&lt;/code&gt; : 스크롤 속도에 따라 애니메이션 속도가 달라짐&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;pin&lt;/code&gt; : 해당 요소를 고정&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;markers&lt;/code&gt; : 해당 요소의 트리거를 표시&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;gsap.registerPlugin(ScrollTrigger);

gsap.to(&amp;quot;.box1&amp;quot;, {
  x: 400,
  y: 400,
  backgroundColor: &amp;quot;blue&amp;quot;,
  duration: 2,
  scrollTrigger: {
    trigger: &amp;quot;.section1&amp;quot;, // 객체 기준 범위
    start: &amp;quot;0 30%&amp;quot;, // 시작 위치
    end: &amp;quot;100% 60%&amp;quot;, // 끝 위치
    scrub: true, // 스크롤 속도에 따라 애니메이션 속도 조절
    // markers: true, // 개발 가이드선
  },
});

const tl = gsap.timeline({
  scrollTrigger: {
    trigger: &amp;quot;.section2&amp;quot;, // 객체 기준 범위
    start: &amp;quot;0 0&amp;quot;, // 시작 위치
    end: &amp;quot;+=400&amp;quot;, // 끝 위치
    scrub: 0.7, // 스크롤 속도에 따라 애니메이션 속도 조절
    markers: true, // 개발 가이드선
    pin: true,
  },
});

tl.to(&amp;quot;.box2&amp;quot;, {
  y: 200,
  duration: 2,
})
  .to(&amp;quot;.box2&amp;quot;, {
  x: 200,
  duration: 2,
})
  .to(&amp;quot;.box2&amp;quot;, {
  rotation: 45,
  scale: 2,
});&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Parallax Scrolling을 이용한 Header, topButton 애니메이션&lt;/h2&gt;
&lt;iframe width=&quot;100%&quot; height=&quot;700&quot; style=&quot;width: 100%;&quot; scrolling=&quot;no&quot; title=&quot;gsap, scrolltoplugin&quot; src=&quot;https://codepen.io/odada/embed/XWyqLPG?default-tab=html%2Cresult&quot; frameborder=&quot;no&quot; loading=&quot;lazy&quot; allowtransparency=&quot;true&quot; allowfullscreen=&quot;true&quot;&gt;
  See the Pen &lt;a href=&quot;https://codepen.io/odada/pen/XWyqLPG&quot;&gt;
  gsap, scrolltoplugin&lt;/a&gt; by odada (&lt;a href=&quot;https://codepen.io/odada&quot;&gt;@odada&lt;/a&gt;)
  on &lt;a href=&quot;https://codepen.io&quot;&gt;CodePen&lt;/a&gt;.
&lt;/iframe&gt;

&lt;h3&gt;- 200px 이상 스크롤 시, Header 숨기기&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;/**
 * 페이지 스크롤에 따른 요소 제어
 */
// 페이지 스크롤에 영향을 받는 요소들을 검색!
const headerEl = document.querySelector(&amp;#39;#header&amp;#39;);
const toTopEl = document.querySelector(&amp;#39;#to-top&amp;#39;);
// 페이지에 스크롤 이벤트를 추가!
// 스크롤이 지나치게 자주 발생하는 것을 조절(throttle, 일부러 부하를 줌)
window.addEventListener(
    &amp;#39;scroll&amp;#39;,
    _.throttle(function () {
        // 페이지 스크롤 위치가 200px 이상.
        if (window.scrollY &amp;gt; 200) {
            // Badge 요소 숨기기!
            // gsap.to(요소, 시간, 옵션);
            gsap.to(headerEl, {
                opacity: 0,
                display: &amp;#39;none&amp;#39;,
                duration: 0.6,
            });
            // 상단으로 스크롤 버튼 보이기!
            gsap.to(toTopEl, {
                right: &amp;#39;30px&amp;#39;,
                // x: 0,
                duration: 0.2,
            });

            // 페이지 스크롤 위치가 500px이 넘지 않으면.
        } else {
            // 스크롤이 200px 이하일 경우
            // Badge 요소 보이기!
            gsap.to(headerEl, {
                opacity: 1,
                display: &amp;#39;block&amp;#39;,
                duration: 0.6,
            });
            // 상단으로 스크롤 버튼 숨기기!
            gsap.to(toTopEl, {
                right: &amp;#39;-50px&amp;#39;,
                // x: 100,
                duration: 0.2,
            });
        }
    }, 300),
);
// 상단으로 스크롤 버튼을 클릭하면,
toTopEl.addEventListener(&amp;#39;click&amp;#39;, function () {
    // 페이지 위치를 최상단으로 부드럽게(0.7초 동안) 이동.
    gsap.to(window, {
        scrollTo: 0,
        duration: 0.7,
    });
});

/**
 * 순서대로 나타나는 기능
 */
// 나타날 요소들(.fade-in) 찾기.
const fadeEls = document.querySelectorAll(&amp;#39;.visual .fade-in&amp;#39;);
// 나타날 요소들을 하나씩 반복해서 처리!
fadeEls.forEach(function (fadeEl, index) {
    // 각 요소들을 순서대로(delay) 보여지게 함!
    gsap.to(fadeEl, {
        delay: (index + 1) * 0.7,
        opacity: 0.5,
        duration: 1,
    });
});&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- ScrollUp, ScrollDown 애니메이션&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;// 페이지 스크롤에 따른 요소 제어
const headerEl = document.querySelector(&amp;#39;#header&amp;#39;);
const toTopEl = document.querySelector(&amp;#39;#to-top&amp;#39;);

// 이전 스크롤 위치 저장 변수
let lastScrollY = window.scrollY;

window.addEventListener(
    &amp;#39;scroll&amp;#39;,
    _.throttle(function () {
        // 현재 스크롤 위치
        const currentScrollY = window.scrollY;

        // 스크롤 다운
        if (currentScrollY &amp;gt; lastScrollY) {
            gsap.to(headerEl, {
                opacity: 0,
                display: &amp;#39;none&amp;#39;,
                duration: 0.6,
            });
            gsap.to(toTopEl, {
                right: &amp;#39;30px&amp;#39;,
                duration: 0.2,
            });
        }
        // 스크롤 업
        else {
            gsap.to(headerEl, {
                opacity: 1,
                display: &amp;#39;block&amp;#39;,
                duration: 0.6,
            });
            gsap.to(toTopEl, {
                right: &amp;#39;-50px&amp;#39;,
                duration: 0.2,
            });
        }

        // 이전 스크롤 위치 업데이트
        lastScrollY = currentScrollY;
    }, 300),
);

// 상단으로 스크롤 버튼을 클릭하면,
toTopEl.addEventListener(&amp;#39;click&amp;#39;, function () {
    // 페이지 위치를 최상단으로 부드럽게(0.7초 동안) 이동.
    gsap.to(window, {
        scrollTo: 0,
        duration: 0.7,
    });
});

// 순서대로 나타나는 기능
const fadeEls = document.querySelectorAll(&amp;#39;.visual .fade-in&amp;#39;);
fadeEls.forEach(function (fadeEl, index) {
    gsap.to(fadeEl, {
        delay: (index + 1) * 0.7,
        opacity: 1,
        duration: 1,
    });
});&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Front/Library</category>
      <author>oodada</author>
      <guid isPermaLink="true">https://oddcode.tistory.com/333</guid>
      <comments>https://oddcode.tistory.com/333#entry333comment</comments>
      <pubDate>Wed, 29 May 2024 14:52:11 +0900</pubDate>
    </item>
    <item>
      <title>AOS를 이용한 Parallax Scrolling</title>
      <link>https://oddcode.tistory.com/332</link>
      <description>&lt;h1&gt;AOS를 이용한 Parallax Scrolling&lt;/h1&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;패럴랙스 스크롤링은 웹사이트의 요소들이 서로 다른 속도로 움직이는 것&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;ul&gt;
&lt;li&gt;레이어별 스크롤 속도를 다르게 하여 입체감을 주는 디자인 기법&lt;/li&gt;
&lt;li&gt;게임, 애니메이션 등에서 주로 사용되던 기법으로 인터랙티브한 웹사이트를 만들 때 사용&lt;/li&gt;
&lt;li&gt;javascript, css, 라이브러리 등을 이용하여 구현&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://opengameart.org/sites/default/files/Parallax.jpg&quot; alt=&quot;Parallax Scrolling&quot;&gt;&lt;/p&gt;
&lt;h2&gt;AOS(Animate On Scroll) 란?&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;스크롤에 따라 요소에 애니메이션을 적용하는 라이브러리로 패럴랙스 스크롤링을 구현할 수 있음&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;ul&gt;
&lt;li&gt;aos : &lt;a href=&quot;https://michalsnik.github.io/aos/&quot;&gt;https://michalsnik.github.io/aos/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;v1 : &lt;a href=&quot;https://github.com/michalsnik/aos&quot;&gt;https://github.com/michalsnik/aos&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;v2 : &lt;a href=&quot;https://github.com/michalsnik/aos/tree/v2&quot;&gt;https://github.com/michalsnik/aos/tree/v2&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;AOS 설치&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;head&gt; 태그 내에 AOS 라이브러리를 추가

&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;link rel=&amp;quot;stylesheet&amp;quot; href=&amp;quot;https://unpkg.com/aos@next/dist/aos.css&amp;quot; /&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;body&gt; 태그 닫는 태그 바로 위에 AOS 라이브러리를 추가

&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;script src=&amp;quot;https://unpkg.com/aos@next/dist/aos.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;script&amp;gt;
    AOS.init()
&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;AOS 초기화&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;AOS.init()&lt;/code&gt; : AOS를 초기화&lt;/li&gt;
&lt;li&gt;&lt;code&gt;AOS.init({})&lt;/code&gt; : AOS를 초기화하면서 옵션을 설정 (&lt;a href=&quot;https://github.com/michalsnik/aos?tab=readme-ov-file#1-initialize-aos&quot;&gt;https://github.com/michalsnik/aos?tab=readme-ov-file#1-initialize-aos&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;AOS 옵션&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;offset&lt;/code&gt; : 요소가 화면에 보이기 전에 애니메이션이 시작되는 위치&lt;/li&gt;
&lt;li&gt;&lt;code&gt;delay&lt;/code&gt; : 애니메이션이 시작되는 지연 시간&lt;/li&gt;
&lt;li&gt;&lt;code&gt;duration&lt;/code&gt; : 애니메이션의 지속 시간&lt;/li&gt;
&lt;li&gt;&lt;code&gt;easing&lt;/code&gt; : 애니메이션의 가속도&lt;/li&gt;
&lt;li&gt;&lt;code&gt;once&lt;/code&gt; : 애니메이션을 한 번만 실행&lt;/li&gt;
&lt;li&gt;&lt;code&gt;anchor-placement&lt;/code&gt; : 애니메이션이 실행되는 기준점&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;AOS 사용&lt;/h2&gt;
&lt;h3&gt;- AOS 효과를 각 요소에 적용&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;AOS 효과를 적용할 요소에 &lt;code&gt;data-aos&lt;/code&gt; 속성을 추가&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;div data-aos=&amp;quot;fade-up&amp;quot;&amp;gt;Fade Up&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;AOS 효과를 적용할 요소에 추가할 수 있는 속성&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;data-aos&lt;/code&gt; : 애니메이션 효과&lt;/li&gt;
&lt;li&gt;&lt;code&gt;data-aos-offset&lt;/code&gt; : 요소가 화면에 보이기 전에 애니메이션이 시작되는 위치&lt;/li&gt;
&lt;li&gt;&lt;code&gt;data-aos-delay&lt;/code&gt; : 애니메이션이 시작되는 지연 시간&lt;/li&gt;
&lt;li&gt;&lt;code&gt;data-aos-duration&lt;/code&gt; : 애니메이션의 지속 시간&lt;/li&gt;
&lt;li&gt;&lt;code&gt;data-aos-easing&lt;/code&gt; : 애니메이션의 가속도&lt;/li&gt;
&lt;li&gt;&lt;code&gt;data-aos-mirror&lt;/code&gt; : 애니메이션을 반복&lt;/li&gt;
&lt;li&gt;&lt;code&gt;data-aos-once&lt;/code&gt; : 애니메이션을 한 번만 실행&lt;/li&gt;
&lt;li&gt;&lt;code&gt;data-aos-anchor-placement&lt;/code&gt; : 애니메이션이 실행되는 기준점&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;div
    data-aos=&amp;quot;fade-up&amp;quot;
    data-aos-offset=&amp;quot;200&amp;quot;
    data-aos-delay=&amp;quot;50&amp;quot;
    data-aos-duration=&amp;quot;1000&amp;quot;
    data-aos-easing=&amp;quot;ease-in-out&amp;quot;
    data-aos-mirror=&amp;quot;true&amp;quot;
    data-aos-once=&amp;quot;false&amp;quot;
    data-aos-anchor-placement=&amp;quot;top-center&amp;quot;
&amp;gt;&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위와 같이 속성을 추가하면 스크롤에 따라 요소에 애니메이션 효과가 적용됩니다. 하지만 옵션이 너무 많아서 코드가 복잡해질 수 있습니다. 반복되는 코드를 줄이기 위해 자바스크립트로 옵션을 설정할 수 있습니다.&lt;/p&gt;
&lt;h3&gt;- AOS 초기화 시 옵션 설정&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;div data-aos=&amp;quot;fade-up&amp;quot;&amp;gt;Fade Up&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;AOS.init({
    offset: 200,
    delay: 50,
    duration: 1000,
    easing: &amp;#39;ease-in-out&amp;#39;,
    mirror: true,
    once: false,
    anchorPlacement: &amp;#39;top-center&amp;#39;,
})&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;AOS 예제&lt;/h2&gt;
&lt;iframe width=&quot;100%&quot; height=&quot;700&quot; style=&quot;width: 100%;&quot; scrolling=&quot;no&quot; title=&quot;Parallax Scrolling - AOS&quot; src=&quot;https://codepen.io/odada/embed/vYPGpdG?default-tab=html%2Cresult&quot; frameborder=&quot;no&quot; loading=&quot;lazy&quot; allowtransparency=&quot;true&quot; allowfullscreen=&quot;true&quot;&gt;
  See the Pen &lt;a href=&quot;https://codepen.io/odada/pen/vYPGpdG&quot;&gt;
  Parallax Scrolling - AOS&lt;/a&gt; by odada (&lt;a href=&quot;https://codepen.io/odada&quot;&gt;@odada&lt;/a&gt;)
  on &lt;a href=&quot;https://codepen.io&quot;&gt;CodePen&lt;/a&gt;.
&lt;/iframe&gt;</description>
      <category>Front/Library</category>
      <author>oodada</author>
      <guid isPermaLink="true">https://oddcode.tistory.com/332</guid>
      <comments>https://oddcode.tistory.com/332#entry332comment</comments>
      <pubDate>Wed, 29 May 2024 14:47:35 +0900</pubDate>
    </item>
    <item>
      <title>Javascript를 이용한 Parallax Scrolling</title>
      <link>https://oddcode.tistory.com/331</link>
      <description>&lt;h1&gt;Javascript를 이용한 Parallax Scrolling&lt;/h1&gt;
&lt;h2&gt;패럴랙스 스크롤링이란?&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;패럴랙스 스크롤링은 웹사이트의 요소들이 서로 다른 속도로 움직이는 것&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;ul&gt;
&lt;li&gt;레이어별 스크롤 속도를 다르게 하여 입체감을 주는 디자인 기법&lt;/li&gt;
&lt;li&gt;게임, 애니메이션 등에서 주로 사용되던 기법으로 인터랙티브한 웹사이트를 만들 때 사용&lt;/li&gt;
&lt;li&gt;javascript, css, 라이브러리 등을 이용하여 구현&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://opengameart.org/sites/default/files/Parallax.jpg&quot; alt=&quot;Parallax Scrolling&quot;&gt;&lt;/p&gt;
&lt;h2&gt;js를 이용한 패럴랙스 스크롤링&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;window.addEventListener(&amp;#39;scroll&amp;#39;, function() {})&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;window.scrollY&lt;/code&gt; : 현재 스크롤된 y축의 위치&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;- translate, transform, scrollY 등을 이용하여 패럴랙스 스크롤링 구현&lt;/h3&gt;
&lt;p&gt;js를 이용하면 스타일을 변경할 수 있다. 이를 이용하여 스크롤에 따라 요소의 스타일을 변경할 수 있다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;div class=&amp;quot;item&amp;quot;&amp;gt;item&amp;lt;/div&amp;gt;

&amp;lt;script&amp;gt;
    const item = document.querySelector(&amp;#39;.item&amp;#39;)

    item.style.color = &amp;#39;red&amp;#39;
    item.style.fontSize = &amp;#39;20px&amp;#39;
    item.style.transform = &amp;#39;translate(100px, 100px) rotate(45deg) scale(1.5)&amp;#39;
&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- 윈도우의 스크롤 값 가져오기&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;window.addEventListener(&amp;#39;scroll&amp;#39;, function () {
    const scrolled = window.scrollY // 윈도우의 스크롤값
    console.log(&amp;#39;scrollY : &amp;#39; + scrolled) // 스크롤값
    console.log(&amp;#39;scrollY : &amp;#39; + scrolled * 0.5) // 스크롤값의 50%
})&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- 예제&lt;/h3&gt;
&lt;iframe width=&quot;100%&quot; height=&quot;700&quot; style=&quot;width: 100%;&quot; scrolling=&quot;no&quot; title=&quot;Parallax Scrolling - JS&quot; src=&quot;https://codepen.io/odada/embed/LYaNXGQ?default-tab=html%2Cresult&quot; frameborder=&quot;no&quot; loading=&quot;lazy&quot; allowtransparency=&quot;true&quot; allowfullscreen=&quot;true&quot;&gt;
  See the Pen &lt;a href=&quot;https://codepen.io/odada/pen/LYaNXGQ&quot;&gt;
  Parallax Scrolling - JS&lt;/a&gt; by odada (&lt;a href=&quot;https://codepen.io/odada&quot;&gt;@odada&lt;/a&gt;)
  on &lt;a href=&quot;https://codepen.io&quot;&gt;CodePen&lt;/a&gt;.
&lt;/iframe&gt;

&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;div class=&amp;quot;section&amp;quot;&amp;gt;
    &amp;lt;div class=&amp;quot;parallax-element background&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;
    &amp;lt;div class=&amp;quot;parallax-element square&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;
    &amp;lt;div class=&amp;quot;parallax-element circle&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;
    &amp;lt;strong class=&amp;quot;parallax-element title&amp;quot;
        &amp;gt;텍스트는 빠르고 &amp;lt;br /&amp;gt;
        배경은 느림&amp;lt;/strong
    &amp;gt;
&amp;lt;/div&amp;gt;

&amp;lt;div class=&amp;quot;extra-content&amp;quot;&amp;gt;
    &amp;lt;p&amp;gt;Parallax Scrolling&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;window.addEventListener(&amp;#39;scroll&amp;#39;, function () {
    // 스크롤 이벤트 리스너 등록
    const sections = document.querySelector(&amp;#39;.section&amp;#39;) // 모든 섹션을 가져옴

    const background = sections.querySelector(&amp;#39;.background&amp;#39;) // 배경 요소
    const title = sections.querySelector(&amp;#39;.title&amp;#39;) // 제목 요소
    const circle = sections.querySelector(&amp;#39;.circle&amp;#39;) // 원 요소
    const square = sections.querySelector(&amp;#39;.square&amp;#39;) // 사각형 요소

    const scrolled = window.scrollY // 윈도우의 스크롤값

    console.log(&amp;#39;scrollY : &amp;#39; + scrolled)

    background.style.transform = `translateY(${scrolled * 0.8}px)`
    // 제목을 스크롤 속도의 80%(빠르게)로 이동
    title.style.transform = `scale(${scrolled * 0.001})`
    // 배경을 스크롤 속도의 30%(느리게)로 이동
    circle.style.transform = `translate(${scrolled * 0.5}px, ${scrolled * 0.5}px)`
    // circle 왼쪽에서 오른쪽으로 이동
    square.style.transform = `translate(${scrolled * -0.5}px, ${scrolled * 0.5}px)`
    // square 오른쪽에서 왼쪽으로 이동
})&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;구역별 패럴랙스 스크롤링&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;for each문을 이용하여 각 섹션에 대해 애니메이션 적용&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;h3&gt;- for each 란?&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;배열의 각 요소에 대해 주어진 함수를 실행&lt;/li&gt;
&lt;li&gt;배열의 각 요소에 대해 반복문을 실행&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const items = [&amp;#39;item1&amp;#39;, &amp;#39;item2&amp;#39;, &amp;#39;item3&amp;#39;]

items.forEach(function (item) {
    console.log(item)
})&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- for each문을 이용하여 각 섹션에 대해 애니메이션 적용&lt;/h3&gt;
&lt;iframe
    width=&quot;100%&quot;
    height=&quot;700&quot;
    style=&quot;width: 100%;&quot;
    scrolling=&quot;no&quot;
    title=&quot;Parallax Scrolling - JS&quot;
    src=&quot;https://codepen.io/odada/embed/jOJqVzj?default-tab=html%2Cresult&quot;
    frameborder=&quot;no&quot;
    loading=&quot;lazy&quot;
    allowtransparency=&quot;true&quot;
    allowfullscreen=&quot;true&quot;
&gt;
    See the Pen &lt;a href=&quot;https://codepen.io/odada/pen/jOJqVzj&quot;&gt; Parallax Scrolling - JS&lt;/a&gt; by odada (&lt;a
        href=&quot;https://codepen.io/odada&quot;
        &gt;@odada&lt;/a
    &gt;) on &lt;a href=&quot;https://codepen.io&quot;&gt;CodePen&lt;/a&gt;.
&lt;/iframe&gt;

&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;div class=&amp;quot;extra-content&amp;quot;&amp;gt;
    &amp;lt;p&amp;gt;Parallax Scrolling (JS)&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;section&amp;quot;&amp;gt;
    &amp;lt;div class=&amp;quot;parallax-element background&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;
    &amp;lt;div class=&amp;quot;parallax-element square&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;
    &amp;lt;div class=&amp;quot;parallax-element circle&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;
    &amp;lt;strong class=&amp;quot;parallax-element title&amp;quot;&amp;gt;느림의 중요성을 깨달은 달팽이&amp;lt;/strong&amp;gt;
&amp;lt;/div&amp;gt;

&amp;lt;div class=&amp;quot;extra-content&amp;quot;&amp;gt;
    &amp;lt;p&amp;gt;Parallax Scrolling&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&amp;lt;div class=&amp;quot;section&amp;quot;&amp;gt;
    &amp;lt;div class=&amp;quot;parallax-element background&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;
    &amp;lt;div class=&amp;quot;parallax-element square&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;
    &amp;lt;div class=&amp;quot;parallax-element circle&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;
    &amp;lt;strong class=&amp;quot;parallax-element title&amp;quot;&amp;gt;그냥 느린 가을이&amp;lt;/strong&amp;gt;
&amp;lt;/div&amp;gt;

&amp;lt;div class=&amp;quot;extra-content&amp;quot;&amp;gt;
    &amp;lt;p&amp;gt;Parallax Scrolling&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;window.addEventListener(&amp;#39;scroll&amp;#39;, function () {
    // 스크롤 이벤트 리스너 등록
    const sections = document.querySelectorAll(&amp;#39;.section&amp;#39;) // 모든 섹션을 가져옴

    sections.forEach(function (section) {
        // 각 섹션에 대해 반복
        let bounds = section.getBoundingClientRect() // 섹션의 위치와 크기 정보를 가져옴
        const background = section.querySelector(&amp;#39;.background&amp;#39;) // 배경 요소
        const title = section.querySelector(&amp;#39;.title&amp;#39;) // 제목 요소
        const circle = section.querySelector(&amp;#39;.circle&amp;#39;) // 원 요소
        const square = section.querySelector(&amp;#39;.square&amp;#39;) // 사각형 요소

        if (bounds.top &amp;lt; window.innerHeight &amp;amp;&amp;amp; bounds.bottom &amp;gt;= 0) {
            // 섹션이 뷰포트 내에 있을 때
            var scrolled = window.pageYOffset - section.offsetTop // 섹션의 시작점에서 스크롤된 거리를 계산
            background.style.transform = `translateY(${scrolled * 0.8}px)` // 제목을 스크롤 속도의 80%(빠르게)로 이동
            title.style.transform = `translateY(${scrolled * 0.3}px)` // 배경을 스크롤 속도의 30%(느리게)로 이동
            circle.style.transform = `translate(${scrolled * 0.5}px, ${scrolled * 0.5}px)` // circle 왼쪽에서 오른쪽으로 이동
            square.style.transform = `translate(${scrolled * -0.5}px)` // square 오른쪽에서 왼쪽으로 이동
        }
    })
})&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Front/Library</category>
      <author>oodada</author>
      <guid isPermaLink="true">https://oddcode.tistory.com/331</guid>
      <comments>https://oddcode.tistory.com/331#entry331comment</comments>
      <pubDate>Wed, 29 May 2024 14:29:19 +0900</pubDate>
    </item>
    <item>
      <title>CSS를 이용한 Parallax Scrolling</title>
      <link>https://oddcode.tistory.com/330</link>
      <description>&lt;h1&gt;CSS를 이용한 Parallax Scrolling&lt;/h1&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;패럴랙스 스크롤링은 웹사이트의 요소들이 서로 다른 속도로 움직이는 것&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;ul&gt;
&lt;li&gt;레이어별 스크롤 속도를 다르게 하여 입체감을 주는 디자인 기법&lt;/li&gt;
&lt;li&gt;게임, 애니메이션 등에서 주로 사용되던 기법으로 인터랙티브한 웹사이트를 만들 때 사용&lt;/li&gt;
&lt;li&gt;javascript, css, 라이브러리 등을 이용하여 구현&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://opengameart.org/sites/default/files/Parallax.jpg&quot; alt=&quot;Parallax Scrolling&quot;&gt;&lt;/p&gt;
&lt;h2&gt;1. Perspective&lt;/h2&gt;
&lt;h3&gt;- perspective 속성&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;css perspective 속성을 이용한 패럴랙스 스크롤링&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;ul&gt;
&lt;li&gt;해당 요소의 z = 0 평면과 사용자 사이의 거리&lt;/li&gt;
&lt;li&gt;transform 효과를 주고자 하는 부모 요소에 적용&lt;/li&gt;
&lt;li&gt;perspective에 따른 변형 효과&lt;ul&gt;
&lt;li&gt;perspective: 100px; =&amp;gt; 100px만큼 멀어져 보임&lt;/li&gt;
&lt;li&gt;perspective가 클수록 (거리가 멀수록) 변형 효과가 줄어듦&lt;/li&gt;
&lt;li&gt;perspective가 작을수록 (거리가 가까울수록) 변형 효과가 커짐&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.w3schools.com/cssref/css3_pr_perspective.php&quot;&gt;w3c schools perspective&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;iframe width=&quot;100%&quot; height=&quot;700&quot; style=&quot;width: 100%;&quot; scrolling=&quot;no&quot; title=&quot;Untitled&quot; src=&quot;https://codepen.io/odada/embed/zYQoEXJ?default-tab=html%2Cresult&quot; frameborder=&quot;no&quot; loading=&quot;lazy&quot; allowtransparency=&quot;true&quot; allowfullscreen=&quot;true&quot;&gt;
  See the Pen &lt;a href=&quot;https://codepen.io/odada/pen/zYQoEXJ&quot;&gt;
  Untitled&lt;/a&gt; by odada (&lt;a href=&quot;https://codepen.io/odada&quot;&gt;@odada&lt;/a&gt;)
  on &lt;a href=&quot;https://codepen.io&quot;&gt;CodePen&lt;/a&gt;.
&lt;/iframe&gt;

&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;h1&amp;gt;The perspective Property&amp;lt;/h1&amp;gt;

&amp;lt;div class=&amp;quot;container&amp;quot;&amp;gt;
    perspective 0
    &amp;lt;div class=&amp;quot;item&amp;quot;&amp;gt;item2&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;

&amp;lt;div class=&amp;quot;container perspective300&amp;quot;&amp;gt;
    perspective 300
    &amp;lt;div class=&amp;quot;item&amp;quot;&amp;gt;item1&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;

&amp;lt;div class=&amp;quot;container perspective100&amp;quot;&amp;gt;
    perspective 100
    &amp;lt;div class=&amp;quot;item&amp;quot;&amp;gt;item1&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;

&amp;lt;div class=&amp;quot;container perspective50&amp;quot;&amp;gt;
    perspective 50
    &amp;lt;div class=&amp;quot;item&amp;quot;&amp;gt;item1&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.container {
    position: relative;
    height: 150px;
    width: 150px;
    margin: 60px;
    border: 1px solid blue;
}

.perspective300 {
    perspective: 300px;
}

.perspective100 {
    perspective: 100px;
}

.perspective50 {
    perspective: 50px;
}

.item {
    position: absolute;
    padding: 50px;
    border: 1px solid black;
    background: rgba(100, 100, 100, 0.5);
    transform-style: preserve-3d;
    transform: rotateX(45deg);
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- perspective-origin 속성&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;사용자가 3D 요소를 바라보는 시점을 설정&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;ul&gt;
&lt;li&gt;perspective-origin: x y; =&amp;gt; perspective의 원점을 x, y로 설정&lt;/li&gt;
&lt;li&gt;perspective-origin: 50% 50%; =&amp;gt; perspective의 원점을 중앙으로 설정&lt;/li&gt;
&lt;li&gt;perspective-origin: 0% 0%; =&amp;gt; perspective의 원점을 왼쪽 상단으로 설정&lt;/li&gt;
&lt;li&gt;perspective-origin: 100% 100%; =&amp;gt; perspective의 원점을 오른쪽 하단으로 설정&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.w3schools.com/cssref/css3_pr_perspective-origin.asp&quot;&gt;w3c schools perspective-origin&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.container {
    position: relative;
    height: 150px;
    width: 150px;
    margin: 60px;
    border: 1px solid blue;
}

.perspective300 {
    perspective: 300px;
    perspective-origin: 50% 50%; // Default
}

.perspective100 {
    perspective: 100px;
    perspective-origin: 0% 0%; // Top left
}

.perspective50 {
    perspective: 50px;
    perspective-origin: 100% 100%; // Bottom right
}

.item {
    position: absolute;
    padding: 50px;
    border: 1px solid black;
    background: rgba(100, 100, 100, 0.5);
    transform-style: preserve-3d;
    transform: rotateX(45deg);
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;2. Parallax Scrolling&lt;/h2&gt;
&lt;h3&gt;- CSS를 이용한 패럴랙스 스크롤링&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;css perspective 속성을 이용한 패럴랙스 스크롤링&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;ul&gt;
&lt;li&gt;container에 perspective 속성을 이용하여 입체감을 주는 효과&lt;/li&gt;
&lt;li&gt;해당 요소의 z = 0 평면과 사용자 사이의 거리&lt;/li&gt;
&lt;li&gt;item에 transform translateZ()를 이용하여 z축 방향을 이동&lt;/li&gt;
&lt;/ul&gt;
&lt;iframe width=&quot;100%&quot; height=&quot;700&quot; style=&quot;width: 100%;&quot; scrolling=&quot;no&quot; title=&quot;Parallax Scrolling - CSS&quot; src=&quot;https://codepen.io/odada/embed/jOJqYbL?default-tab=html%2Cresult&quot; frameborder=&quot;no&quot; loading=&quot;lazy&quot; allowtransparency=&quot;true&quot; allowfullscreen=&quot;true&quot;&gt;
  See the Pen &lt;a href=&quot;https://codepen.io/odada/pen/jOJqYbL&quot;&gt;
  Parallax Scrolling - CSS&lt;/a&gt; by odada (&lt;a href=&quot;https://codepen.io/odada&quot;&gt;@odada&lt;/a&gt;)
  on &lt;a href=&quot;https://codepen.io&quot;&gt;CodePen&lt;/a&gt;.
&lt;/iframe&gt;

&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;div class=&amp;quot;parallax&amp;quot;&amp;gt;
    &amp;lt;div class=&amp;quot;item1&amp;quot;&amp;gt;item1&amp;lt;/div&amp;gt;
    &amp;lt;div class=&amp;quot;item2&amp;quot;&amp;gt;item2&amp;lt;/div&amp;gt;
    &amp;lt;div class=&amp;quot;item3&amp;quot;&amp;gt;item3&amp;lt;/div&amp;gt;
    &amp;lt;strong class=&amp;quot;title&amp;quot;&amp;gt;Parallax Scolling (CSS)&amp;lt;/strong&amp;gt;
&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;.parallax {
    height: 100vh;
    overflow-x: hidden;
    perspective: 1px;
}

.parallax div {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    font-size: 50px;
    color: #fff;
}

.parallax .item1 {
    background-color: #333;
    height: 150rem;
}

.parallax .item2 {
    top: 800px;
    background-color: aqua;
    height: 500px;
    transform: translateZ(-2px);
    color: red;
}

.parallax .item3 {
    top: 900px;
    background-color: blueviolet;
    height: 500px;
    transform: translateZ(-1px);
    opacity: 0.7;
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Front/Library</category>
      <author>oodada</author>
      <guid isPermaLink="true">https://oddcode.tistory.com/330</guid>
      <comments>https://oddcode.tistory.com/330#entry330comment</comments>
      <pubDate>Tue, 28 May 2024 14:54:52 +0900</pubDate>
    </item>
    <item>
      <title>firebase를 이용한 Next.js 블로그 앱 만들기</title>
      <link>https://oddcode.tistory.com/329</link>
      <description>&lt;h1&gt;Next.js 블로그 앱 만들기&lt;/h1&gt;
&lt;p&gt;Next.js를 사용하여 블로그 애플리케이션을 만들어보겠습니다. 블로그 애플리케이션은 다음과 같은 기능을 구현할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://odada.me/328&quot;&gt;Next.js 기초 및 설정&lt;/a&gt; 포스트를 참고하여 Next.js 프로젝트를 생성합니다.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/oddoddo/-next-ts-blog-app&quot;&gt;next.js 블로그 앱 만들기 github 바로가기&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;1. 프로젝트 개요&lt;/h2&gt;
&lt;h3&gt;1.1. 프로젝트 목표&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Next를 사용하여 블로그 앱을 만들어보자.&lt;/li&gt;
&lt;li&gt;Firebase로 사용자 인증을 구현하고 로그인, 회원가입, 로그아웃 기능을 구현한다.&lt;/li&gt;
&lt;li&gt;Firebase의 기본 개념을 익히고 데이터 조회, 추가, 수정, 삭제를 구현한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;1.2. 프로젝트 환경&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Next.js(create-next-app) 프로젝트 생성&lt;/li&gt;
&lt;li&gt;Firebase (Firestore, Authentication)를 이용한 데이터 관리 및 사용자 인증&lt;/li&gt;
&lt;li&gt;Firebase Firestore를 이용한 게시판 CRUD(Create, Read, Update, Delete) 구현&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;1.3. 프로젝트 구조&lt;/h3&gt;
&lt;p&gt;블로그 애플리케이션은 다음과 같은 구조로 구성됩니다. &lt;code&gt;components&lt;/code&gt; 디렉토리에 레이아웃 컴포넌트와 포스트 관련 컴포넌트를 생성하고, &lt;code&gt;pages&lt;/code&gt; 디렉토리에 메인 페이지와 포스트 페이지를 생성할 수 있습니다.&lt;br&gt;&lt;code&gt;public&lt;/code&gt; 디렉토리에 이미지와 스타일 파일을 저장할 수 있습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;next-blog/
├── public/
│   ├── images/
│   │   └── logo.png
│   ├── styles/
│   │   └── main.css
├── src/
│   ├── components/
│   │   ├── Layout/
│   │   │   └── Layout.tsx
│   │   │   └── Header.tsx
│   │   │   └── Footer.tsx
│   │   ├── Post/
│   │   │   └── PostList.tsx    # 포스트 목록 컴포넌트
│   │   │   └── PostItem.tsx    # 포스트 아이템 컴포넌트
│   │   │   └── PostDetail.tsx  # 포스트 상세 컴포넌트
│   │   │   └── PostEdit.tsx    # 포스트 수정 컴포넌트
│   │   │   └── PostNew.tsx     # 포스트 작성 컴포넌트
│   ├── pages/
│   │   ├── index.tsx
│   │   ├── post
│   │   │   ├── index.tsx    # 포스트 목록 페이지
│   │   │   ├── [id].tsx     # 포스트 상세 페이지
│   │   │   ├── edit.tsx     # 포스트 수정 페이지
│   │   │   └── new.tsx      # 포스트 작성 페이지
│   │   ├── login.tsx    # 로그인 페이지
│   │   ├── signup.tsx   # 회원가입 페이지
│   ├── types/               # 타입 정의
│   │   └── types.ts
│   ├── data/                # 데이터 관리
│   │   └── data.ts
│   ├── firebase/            # Firebase 설정
│   │   ├── firebase.ts     # Firebase 초기화 파일
│   │   ├── auth.ts        # Firebase Authentication 설정
│   │   └── firestore.ts    # Firestore CRUD 기능 구현 파일
│   ├── styles/
│   │   ├── globals.css
│   │   ├── Home.module.css
│   │   └── Post.module.css
├── .gitignore
├── package.json
├── README.md
└── tsconfig.json&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3.2. Firestore 프로젝트 구조&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;src/
│
├── components/   # UI 컴포넌트 폴더
│
├── firebase/     # Firebase 설정과 관련 기능을 포함하는 폴더
│   ├── config.ts # Firebase 초기화 파일
│   └── firestore.ts  # Firestore CRUD 기능 구현 파일
│
├── pages/        # Next.js 페이지
│
└── types/        # TypeScript 타입 정의 폴더&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;2. 프로젝트 준비&lt;/h2&gt;
&lt;h3&gt;2.2. Firebase 프로젝트 생성&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://console.firebase.google.com/&quot;&gt;Firebase Console&lt;/a&gt;에 접속합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/odada/post/23e6c0eb-8fd1-4455-bff0-6722186cd213/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;[프로젝트 만들기] 버튼을 클릭해 새 프로젝트를 만들어줍니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/odada/post/77602bfa-576f-4f38-ac00-f6ffc37b2081/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;프로젝트 이름을 입력하고, [계속] 버튼을 클릭합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/odada/post/4e92e862-4c4f-4f20-aca1-deda2a5f7d19/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;현 프로젝트에서는 Google Analytics를 사용하지 않으므로 선택 해제하고, [프로젝트 만들기] 버튼을 클릭합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/odada/post/fbede621-53e5-44c9-bf42-02e4f15910d0/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;프로젝트가 생성되면, [계속] 버튼을 클릭합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/odada/post/f1adfc80-ead1-4989-b234-ac28b4ad4693/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Firebase 프로젝트가 생성되면, 앱 추가 화면이 나타납니다. 웹 앱을 추가하려면, &lt;code&gt;&amp;lt;/&amp;gt;&lt;/code&gt; 아이콘을 클릭합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/odada/post/fccb9729-69ce-4c43-b662-89dfd4f2bec3/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;앱의 이름을 입력하고, Firebase Hosting을 설정하지 않으므로 Firebase Hosting 설정 해제합니다. [앱 등록] 버튼을 클릭합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/odada/post/28c6f46d-ac04-4826-b9f0-ae390d1ce667/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h3&gt;2.3. Firebase SDK 설정&lt;/h3&gt;
&lt;h4&gt;- 패키지 설치&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Firebase SDK 설정을 위한 구성 요소를 추가하기 위해 npm 패키지를 설치합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm install firebase&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;- Firebase 초기화 관리&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Firebase SDK 설정을 위한 구성 요소를 추가합니다. Firebase SDK 구성을 위한 설정 파일을 생성합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;mkdir -p src/firebase
touch src/firebase/firebase.ts&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Firebase SDK 설정을 위한 구성 요소를 추가합니다. Firebase SDK 구성을 위한 설정 파일을 생성합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;// src/firebase/firebase.ts
// Import the functions you need from the SDKs you need
import { initializeApp } from &amp;#39;firebase/app&amp;#39;
import &amp;#39;firebase/auth&amp;#39; // Firebase Authentication 기능을 사용하기 위한 import
// TODO: Add SDKs for Firebase products that you want to use
// https://firebase.google.com/docs/web/setup#available-libraries

// Your web app&amp;#39;s Firebase configuration
const firebaseConfig = {
    apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY,
    authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN,
    projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID,
    storageBucket: process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET,
    messagingSenderId: process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID,
    appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID,
}

// Initialize Firebase
const app = initializeApp(firebaseConfig)

export default app&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;- Firebase 사용하기&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;pages/_app.tsx 파일에서 Firebase SDK 구성을 위한 설정 파일을 import 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;// pages/_app.tsx
import { AppProps } from &amp;#39;next/app&amp;#39;
import Layout from &amp;#39;@/components/layout/Layout&amp;#39;
import app from &amp;#39;@/firebase/firebase&amp;#39; // Firebase SDK 구성을 위한 설정 파일 import

function MyApp({ Component, pageProps }: AppProps) {
    return (
        &amp;lt;Layout&amp;gt;
            &amp;lt;Component {...pageProps} /&amp;gt;
        &amp;lt;/Layout&amp;gt;
    )
}

export default MyApp&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;- 환경 변수 설정&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Firebase 설정에 필요한 환경 변수들은 .env.local 파일에 저장해야 합니다. Next.js에서는 NEXT&lt;em&gt;PUBLIC&lt;/em&gt; 접두어를 사용하여 클라이언트 사이드에서도 환경 변수를 접근할 수 있게 설정합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# .env.local
NEXT_PUBLIC_FIREBASE_API_KEY=your_api_key
NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN=your_auth_domain
NEXT_PUBLIC_FIREBASE_PROJECT_ID=your_project_id
NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET=your_storage_bucket
NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID=your_messaging_sender_id
NEXT_PUBLIC_FIREBASE_APP_ID=your_app_id&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;.env.local 파일은 .gitignore 파일에 추가하여 깃허브에 올리지 않도록 설정합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# .gitignore
.env.local&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;코드에 환경 변수를 사용할 때는 process.env.NEXT&lt;em&gt;PUBLIC&lt;/em&gt; 접두어를 사용하여 환경 변수를 사용합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;const firebaseConfig = {
    apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY,
    authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN,
    projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID,
    storageBucket: process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET,
    messagingSenderId: process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID,
    appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID,
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2.4. Firebase Authentication 설정&lt;/h3&gt;
&lt;h4&gt;- Firebase Authentication 활성화&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Firebase Console에서 Authentication 메뉴로 이동합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/odada/post/94962912-cace-4062-aef9-e539176057cc/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;[시작하기] 버튼을 클릭하여 Firebase Authentication을 활성화합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/odada/post/6f4fb9bd-9527-4cbd-8c41-b96d413d9419/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Firebase Authentication이 활성화되면, [로그인 방법] 탭으로 이동합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/odada/post/e99a2178-f0dd-4b31-9ef0-444018291c2f/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;[google] 로그인 방법을 활성화합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/odada/post/3a577759-5918-4e55-ab6d-62e51d20b753/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Firebase Authentication 설정이 완료되었습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/odada/post/0b87f8fb-a93e-465c-997d-86e2545811e3/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h4&gt;- Firebase Authentication 패키지 설치&lt;/h4&gt;
&lt;h4&gt;- Firebase 인증 설정&lt;/h4&gt;
&lt;p&gt;이제 인증 기능을 초기화하는 코드를 작성합니다.&lt;/p&gt;
&lt;p&gt;src/firebase/auth.ts 파일은 Firebase와 Next.js 프로젝트의 인증 기능을 통합하기 위해 사용자 정의 설정과 함수를 포함하는 파일입니다. 이 파일을 작성하는 목적은 Firebase Authentication 서비스와의 상호 작용을 관리하고, 이를 프로젝트 전반에 걸쳐 쉽게 사용할 수 있도록 하는 것입니다. 이 파일에서 구현하는 주요 기능은 다음과 같습니다:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Firebase 앱 초기화&lt;/strong&gt;: 프로젝트에 필요한 Firebase 서비스를 사용하기 위해 Firebase 앱을 초기화합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;인증 관련 함수 제공&lt;/strong&gt;: 로그인, 회원가입, 로그아웃과 같은 인증 관련 함수를 제공합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;인증 상태 관리&lt;/strong&gt;: 사용자의 로그인 상태를 추적하고, 이를 앱의 다른 부분에서 쉽게 사용할 수 있게 합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;참조 및 작성 방법&lt;/strong&gt; :&lt;br&gt;Firebase 공식 문서와 Next.js의 인증 패턴을 참조하여 auth.ts 파일을 작성합니다. 아래는 Firebase 공식 문서와 Next.js의 인증 패턴을 참조할 수 있는 링크입니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href=&quot;https://firebase.google.com/docs/web/setup?hl=ko&quot;&gt;Firebase 공식 문서&lt;/a&gt;: Firebase의 다양한 기능을 설정하고 사용하는 방법에 대한 자세한 지침이 포함되어 있습니다. 특히, Firebase 웹 문서에서 제공하는 인증 관련 메서드들의 사용 방법을 참조할 수 있습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Next.js 예제 및 튜토리얼: Next.js 커뮤니티와 공식 문서에서 제공하는 인증 튜토리얼을 통해 Next.js에서 인증 시스템을 구현하는 베스트 프랙티스를 배울 수 있습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;// src/firebase/firebase.ts
import { initializeApp } from &amp;#39;firebase/app&amp;#39;
import { getAuth } from &amp;#39;firebase/auth&amp;#39;

// Firebase 앱 설정
const firebaseConfig = {
    apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY,
    authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN,
    projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID,
    storageBucket: process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET,
    messagingSenderId: process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID,
    appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID,
}

// Firebase 앱 초기화
const app = initializeApp(firebaseConfig)
export const auth = getAuth(app)&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;// src/firebase/auth.tsx
// auth.tsx
import { createContext, useContext, useEffect, useState, ReactNode } from &amp;#39;react&amp;#39;
import { auth } from &amp;#39;./firebase&amp;#39;
import {
    signInWithEmailAndPassword,
    createUserWithEmailAndPassword,
    signOut,
    onAuthStateChanged,
    GoogleAuthProvider,
    signInWithPopup,
    User,
} from &amp;#39;firebase/auth&amp;#39;

// 컨텍스트 타입 정의
interface AuthContextType {
    user: User | null
    login: (email: string, password: string) =&amp;gt; Promise&amp;lt;void&amp;gt;
    register: (email: string, password: string) =&amp;gt; Promise&amp;lt;void&amp;gt;
    logout: () =&amp;gt; Promise&amp;lt;void&amp;gt;
    loginWithGoogle: () =&amp;gt; Promise&amp;lt;void&amp;gt;
}

// AuthContext 컨텍스트 생성
const AuthContext = createContext&amp;lt;AuthContextType&amp;gt;({
    user: null,
    login: async () =&amp;gt; {},
    register: async () =&amp;gt; {},
    logout: async () =&amp;gt; {},
    loginWithGoogle: async () =&amp;gt; {},
})

// AuthProvider 컴포넌트 정의
export const AuthProvider = ({ children }: { children: ReactNode }) =&amp;gt; {
    const [user, setUser] = useState&amp;lt;User | null&amp;gt;(null)

    // 사용자 상태 업데이트
    useEffect(() =&amp;gt; {
        const unsubscribe = onAuthStateChanged(auth, setUser)
        return () =&amp;gt; unsubscribe()
    }, [])

    // 로그인 함수
    const login = async (email: string, password: string) =&amp;gt; {
        await signInWithEmailAndPassword(auth, email, password)
    }

    // 회원가입 함수
    const register = async (email: string, password: string) =&amp;gt; {
        await createUserWithEmailAndPassword(auth, email, password)
    }

    // 로그아웃 함수
    const logout = async () =&amp;gt; {
        await signOut(auth)
    }

    // Google 로그인 함수
    const loginWithGoogle = async () =&amp;gt; {
        const provider = new GoogleAuthProvider()
        try {
            await signInWithPopup(auth, provider)
        } catch (error) {
            console.error(&amp;#39;Google 로그인 실패:&amp;#39;, error)
        }
    }

    return (
        &amp;lt;AuthContext.Provider value={{ user, login, register, logout, loginWithGoogle }}&amp;gt;
            {children}
        &amp;lt;/AuthContext.Provider&amp;gt;
    )
}

// 사용자 정의 훅
export const useAuth = () =&amp;gt; useContext(AuthContext)&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;- 로그인 및 회원가입 기능 포함된 헤더&lt;/h4&gt;
&lt;p&gt;이제 로그인 및 회원가입 기능이 포함된 헤더를 만들어보겠습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;// components/Layout/Header.tsx
import Link from &amp;#39;next/link&amp;#39;
import { useAuth } from &amp;#39;@/firebase/auth&amp;#39;

const Header = () =&amp;gt; {
    const { user, loginWithGoogle, logout } = useAuth()

    return (
        &amp;lt;header&amp;gt;
            &amp;lt;h1&amp;gt;
                &amp;lt;Link href=&amp;quot;/&amp;quot;&amp;gt;logo&amp;lt;/Link&amp;gt;
            &amp;lt;/h1&amp;gt;

            &amp;lt;nav&amp;gt;
                &amp;lt;ul&amp;gt;
                    &amp;lt;li&amp;gt;
                        &amp;lt;Link href=&amp;quot;/about&amp;quot;&amp;gt;About&amp;lt;/Link&amp;gt;
                    &amp;lt;/li&amp;gt;
                    &amp;lt;li&amp;gt;
                        &amp;lt;Link href=&amp;quot;/post&amp;quot;&amp;gt;Post&amp;lt;/Link&amp;gt;
                    &amp;lt;/li&amp;gt;
                &amp;lt;/ul&amp;gt;
            &amp;lt;/nav&amp;gt;

            &amp;lt;div className=&amp;quot;space-x-4&amp;quot;&amp;gt;
                {user ? (
                    &amp;lt;button onClick={logout} className=&amp;quot;p-2 bg-red-500 rounded&amp;quot;&amp;gt;
                        로그아웃
                    &amp;lt;/button&amp;gt;
                ) : (
                    &amp;lt;&amp;gt;
                        &amp;lt;button onClick={loginWithGoogle} className=&amp;quot;p-2 bg-blue-500 rounded&amp;quot;&amp;gt;
                            Google로 로그인
                        &amp;lt;/button&amp;gt;
                        &amp;lt;Link href=&amp;quot;/login&amp;quot;&amp;gt;
                            &amp;lt;button className=&amp;quot;p-2 bg-green-500 rounded&amp;quot;&amp;gt;로그인&amp;lt;/button&amp;gt;
                        &amp;lt;/Link&amp;gt;
                        &amp;lt;Link href=&amp;quot;/signup&amp;quot;&amp;gt;
                            &amp;lt;button className=&amp;quot;p-2 bg-yellow-500 rounded&amp;quot;&amp;gt;회원가입&amp;lt;/button&amp;gt;
                        &amp;lt;/Link&amp;gt;
                    &amp;lt;/&amp;gt;
                )}
            &amp;lt;/div&amp;gt;
        &amp;lt;/header&amp;gt;
    )
}

export default Header&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;- 로그인 페이지&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;// pages/login.tsx
import { useState } from &amp;#39;react&amp;#39;
import { useRouter } from &amp;#39;next/router&amp;#39;
import { useAuth } from &amp;#39;@/firebase/auth&amp;#39;

const Login = () =&amp;gt; {
    const { login, loginWithGoogle } = useAuth()
    const router = useRouter()
    const [email, setEmail] = useState(&amp;#39;&amp;#39;)
    const [password, setPassword] = useState(&amp;#39;&amp;#39;)
    const [error, setError] = useState(&amp;#39;&amp;#39;)

    const handleLogin = async (event: React.FormEvent&amp;lt;HTMLFormElement&amp;gt;) =&amp;gt; {
        event.preventDefault()
        try {
            await login(email, password)
            router.push(&amp;#39;/&amp;#39;)
        } catch (err) {
            setError(&amp;#39;로그인 실패: 잘못된 이메일 또는 비밀번호입니다.&amp;#39;)
        }
    }

    return (
        &amp;lt;div&amp;gt;
            &amp;lt;h2&amp;gt;로그인&amp;lt;/h2&amp;gt;
            {error &amp;amp;&amp;amp; &amp;lt;p&amp;gt;{error}&amp;lt;/p&amp;gt;}
            &amp;lt;form onSubmit={handleLogin}&amp;gt;
                &amp;lt;label&amp;gt;
                    이메일:
                    &amp;lt;input type=&amp;quot;email&amp;quot; value={email} onChange={(e) =&amp;gt; setEmail(e.target.value)} required /&amp;gt;
                &amp;lt;/label&amp;gt;
                &amp;lt;label&amp;gt;
                    비밀번호:
                    &amp;lt;input type=&amp;quot;password&amp;quot; value={password} onChange={(e) =&amp;gt; setPassword(e.target.value)} required /&amp;gt;
                &amp;lt;/label&amp;gt;
                &amp;lt;button type=&amp;quot;submit&amp;quot;&amp;gt;로그인&amp;lt;/button&amp;gt;
            &amp;lt;/form&amp;gt;
            &amp;lt;button onClick={loginWithGoogle}&amp;gt;Google로 로그인&amp;lt;/button&amp;gt;
        &amp;lt;/div&amp;gt;
    )
}

export default Login&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;- 회원가입 페이지&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;// pages/signup.tsx
// pages/register.tsx
import { useState } from &amp;#39;react&amp;#39;
import { useRouter } from &amp;#39;next/router&amp;#39;
import { useAuth } from &amp;#39;@/firebase/auth&amp;#39;

const Signup = () =&amp;gt; {
    const { register } = useAuth()
    const router = useRouter()
    const [email, setEmail] = useState(&amp;#39;&amp;#39;)
    const [password, setPassword] = useState(&amp;#39;&amp;#39;)
    const [error, setError] = useState(&amp;#39;&amp;#39;)

    const handleRegister = async (event: React.FormEvent&amp;lt;HTMLFormElement&amp;gt;) =&amp;gt; {
        event.preventDefault()
        try {
            await register(email, password)
            router.push(&amp;#39;/&amp;#39;)
        } catch (err) {
            setError(&amp;#39;회원가입 실패: 이메일 형식이 올바르지 않거나 비밀번호가 너무 짧습니다.&amp;#39;)
        }
    }

    return (
        &amp;lt;div&amp;gt;
            &amp;lt;h2&amp;gt;회원가입&amp;lt;/h2&amp;gt;
            {error &amp;amp;&amp;amp; &amp;lt;p&amp;gt;{error}&amp;lt;/p&amp;gt;}
            &amp;lt;form onSubmit={handleRegister}&amp;gt;
                &amp;lt;label&amp;gt;
                    이메일:
                    &amp;lt;input type=&amp;quot;email&amp;quot; value={email} onChange={(e) =&amp;gt; setEmail(e.target.value)} required /&amp;gt;
                &amp;lt;/label&amp;gt;
                &amp;lt;label&amp;gt;
                    비밀번호:
                    &amp;lt;input type=&amp;quot;password&amp;quot; value={password} onChange={(e) =&amp;gt; setPassword(e.target.value)} required /&amp;gt;
                &amp;lt;/label&amp;gt;
                &amp;lt;button type=&amp;quot;submit&amp;quot;&amp;gt;회원가입&amp;lt;/button&amp;gt;
            &amp;lt;/form&amp;gt;
        &amp;lt;/div&amp;gt;
    )
}

export default Signup&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;- AuthProvider 감싸기&lt;/h4&gt;
&lt;p&gt;마지막으로 AuthProvider 컴포넌트를 모든 페이지에 적용하기 위해 pages/_app.tsx 파일을 다음과 같이 수정합니다:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;// pages/_app.tsx
import { AppProps } from &amp;#39;next/app&amp;#39;
import Layout from &amp;#39;../components/Layout/Layout&amp;#39;
import { AuthProvider } from &amp;#39;../src/firebase/auth&amp;#39;
import app from &amp;#39;../src/firebase/firebase&amp;#39; // Firebase SDK 구성을 위한 설정 파일 import
import &amp;#39;../styles/globals.css&amp;#39;

function MyApp({ Component, pageProps }: AppProps) {
    return (
        &amp;lt;AuthProvider&amp;gt;
            &amp;lt;Layout&amp;gt;
                &amp;lt;Component {...pageProps} /&amp;gt;
            &amp;lt;/Layout&amp;gt;
        &amp;lt;/AuthProvider&amp;gt;
    )
}

export default MyApp&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;3. Firebase Firestore 설정&lt;/h2&gt;
&lt;p&gt;firebase firestore를 사용하여 데이터베이스를 생성하고, 데이터를 추가, 조회, 수정, 삭제하는 방법을 알아보겠습니다.&lt;/p&gt;
&lt;h3&gt;3.1. Firestore 데이터베이스 생성&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Firebase Console에서 Firestore 메뉴로 이동합니다.&lt;/li&gt;
&lt;li&gt;[데이터베이스 만들기] 버튼을 클릭합니다.&lt;/li&gt;
&lt;li&gt;[시작하기] 버튼을 클릭합니다.&lt;/li&gt;
&lt;li&gt;[보안 규칙] 설정을 &lt;code&gt;테스트 모드&lt;/code&gt;로 설정합니다.&lt;/li&gt;
&lt;li&gt;[다음] 버튼을 클릭합니다.&lt;/li&gt;
&lt;li&gt;[데이터베이스 만들기] 버튼을 클릭합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3.2. Firestore 데이터베이스 설정&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Firestore 데이터베이스를 설정합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;// firebase/firebase.ts
import { initializeApp } from &amp;#39;firebase/app&amp;#39;
import { getAuth } from &amp;#39;firebase/auth&amp;#39;
import { getFirestore } from &amp;#39;firebase/firestore&amp;#39; // Firestore를 위한 import 추가

// Firebase 앱 설정
const firebaseConfig = {
    apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY,
    authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN,
    projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID,
    storageBucket: process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET,
    messagingSenderId: process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID,
    appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID,
}

// Firebase 앱 초기화
const app = initializeApp(firebaseConfig)

// 인증 서비스 인스턴스 생성
export const auth = getAuth(app)

// Firestore 서비스 인스턴스 생성
export const db = getFirestore(app) // Firestore 인스턴스를 추가하고 export합니다.&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;// src/firebase/firestore.ts
import { db } from &amp;#39;./firebase&amp;#39;
import { collection, getDocs, addDoc, doc, updateDoc, deleteDoc } from &amp;#39;firebase/firestore&amp;#39;

// 추가 (Create)
export async function addDocument(collectionName: string, data: object) {
    try {
        const docRef = await addDoc(collection(db, collectionName), data)
        console.log(&amp;#39;Document written with ID:&amp;#39;, docRef.id)
    } catch (e) {
        console.error(&amp;#39;Error adding document:&amp;#39;, e)
    }
}

// 조회 (Read)
export async function getAllDocuments(collectionName: string) {
    const querySnapshot = await getDocs(collection(db, collectionName))
    querySnapshot.forEach((doc) =&amp;gt; {
        console.log(`${doc.id} =&amp;gt;`, doc.data())
    })
}

// 수정 (Update)
export async function updateDocument(collectionName: string, docId: string, newData: object) {
    const docRef = doc(db, collectionName, docId)
    try {
        await updateDoc(docRef, newData)
        console.log(&amp;#39;Document updated&amp;#39;)
    } catch (e) {
        console.error(&amp;#39;Error updating document:&amp;#39;, e)
    }
}

// 삭제 (Delete)
export async function deleteDocument(collectionName: string, docId: string) {
    const docRef = doc(db, collectionName, docId)
    try {
        await deleteDoc(docRef)
        console.log(&amp;#39;Document deleted&amp;#39;)
    } catch (e) {
        console.error(&amp;#39;Error deleting document:&amp;#39;, e)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3.4. Firestore 데이터베이스 추가&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Firestore 데이터베이스를 사용하여 데이터를 추가, 조회, 수정, 삭제하는 방법을 알아보겠습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;// src/pages/new.tsx
import React, { useState } from &amp;#39;react&amp;#39;
import { addDocument } from &amp;#39;@/firebase/firestore&amp;#39;

const AddPostPage: React.FC = () =&amp;gt; {
    const [title, setTitle] = useState(&amp;#39;&amp;#39;)
    const [content, setContent] = useState(&amp;#39;&amp;#39;)

    // 폼 제출 시 Firestore에 데이터를 추가하는 함수 호출
    const handleSubmit = async (event: React.FormEvent&amp;lt;HTMLFormElement&amp;gt;) =&amp;gt; {
        event.preventDefault()
        // 입력된 데이터 유효성 검사
        if (title.trim() === &amp;#39;&amp;#39; || content.trim() === &amp;#39;&amp;#39;) {
            alert(&amp;#39;제목과 내용을 입력하세요!&amp;#39;)
            return
        }

        // Firestore에 문서 추가
        try {
            await addDocument(&amp;#39;posts&amp;#39;, { title, content })
            alert(&amp;#39;새 게시물이 추가되었습니다!&amp;#39;)
            setTitle(&amp;#39;&amp;#39;)
            setContent(&amp;#39;&amp;#39;)
        } catch (error) {
            console.error(&amp;#39;게시물 추가 중 오류 발생:&amp;#39;, error)
            alert(&amp;#39;게시물 추가에 실패했습니다.&amp;#39;)
        }
    }

    return (
        &amp;lt;div&amp;gt;
            &amp;lt;h1&amp;gt;게시물 추가하기&amp;lt;/h1&amp;gt;
            &amp;lt;form onSubmit={handleSubmit}&amp;gt;
                &amp;lt;div&amp;gt;
                    &amp;lt;label htmlFor=&amp;quot;title&amp;quot;&amp;gt;제목:&amp;lt;/label&amp;gt;
                    &amp;lt;input id=&amp;quot;title&amp;quot; type=&amp;quot;text&amp;quot; value={title} onChange={(e) =&amp;gt; setTitle(e.target.value)} /&amp;gt;
                &amp;lt;/div&amp;gt;
                &amp;lt;div&amp;gt;
                    &amp;lt;label htmlFor=&amp;quot;content&amp;quot;&amp;gt;내용:&amp;lt;/label&amp;gt;
                    &amp;lt;textarea id=&amp;quot;content&amp;quot; value={content} onChange={(e) =&amp;gt; setContent(e.target.value)} /&amp;gt;
                &amp;lt;/div&amp;gt;
                &amp;lt;button type=&amp;quot;submit&amp;quot;&amp;gt;추가하기&amp;lt;/button&amp;gt;
            &amp;lt;/form&amp;gt;
        &amp;lt;/div&amp;gt;
    )
}

export default AddPostPage&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3.5. Firestore 데이터 수정&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;// src/components/PostEdit.tsx
import React, { useState, useEffect } from &amp;#39;react&amp;#39;
import { updateDocument } from &amp;#39;@/firebase/firestore&amp;#39;

interface PostEditProps {
    id: string
    initialTitle: string
    initialContent: string
    onCancel: () =&amp;gt; void
    onUpdate: () =&amp;gt; void
}

const PostEdit: React.FC&amp;lt;PostEditProps&amp;gt; = ({ id, initialTitle, initialContent, onCancel, onUpdate }) =&amp;gt; {
    const [title, setTitle] = useState(initialTitle)
    const [content, setContent] = useState(initialContent)

    const handleUpdate = async (event: React.FormEvent&amp;lt;HTMLFormElement&amp;gt;) =&amp;gt; {
        event.preventDefault()
        await updateDocument(&amp;#39;posts&amp;#39;, id, { title, content })
        onUpdate() // 성공 시 부모 컴포넌트에 알리기 위해 사용
    }

    return (
        &amp;lt;div&amp;gt;
            &amp;lt;h2&amp;gt;Edit Post&amp;lt;/h2&amp;gt;
            &amp;lt;form onSubmit={handleUpdate}&amp;gt;
                &amp;lt;div&amp;gt;
                    &amp;lt;label htmlFor=&amp;quot;title&amp;quot;&amp;gt;Title:&amp;lt;/label&amp;gt;
                    &amp;lt;input id=&amp;quot;title&amp;quot; type=&amp;quot;text&amp;quot; value={title} onChange={(e) =&amp;gt; setTitle(e.target.value)} /&amp;gt;
                &amp;lt;/div&amp;gt;
                &amp;lt;div&amp;gt;
                    &amp;lt;label htmlFor=&amp;quot;content&amp;quot;&amp;gt;Content:&amp;lt;/label&amp;gt;
                    &amp;lt;textarea id=&amp;quot;content&amp;quot; value={content} onChange={(e) =&amp;gt; setContent(e.target.value)} /&amp;gt;
                &amp;lt;/div&amp;gt;
                &amp;lt;button type=&amp;quot;submit&amp;quot;&amp;gt;Update&amp;lt;/button&amp;gt;
                &amp;lt;button type=&amp;quot;button&amp;quot; onClick={onCancel}&amp;gt;
                    Cancel
                &amp;lt;/button&amp;gt;
            &amp;lt;/form&amp;gt;
        &amp;lt;/div&amp;gt;
    )
}

export default PostEdit&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3.7. Firestore 데이터 조회&lt;/h3&gt;
&lt;p&gt;데이터를 조회하고 수정, 삭제 버튼을 추가하여 데이터를 수정하거나 삭제할 수 있습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;// src/components/PostsList.tsx
// src/components/PostsList.tsx
import React, { useState, useEffect } from &amp;#39;react&amp;#39;
import { db } from &amp;#39;@/firebase/firebase&amp;#39;
import { collection, getDocs, query, orderBy } from &amp;#39;firebase/firestore&amp;#39;
import { deleteDocument } from &amp;#39;@/firebase/firestore&amp;#39;
import PostEdit from &amp;#39;./PostEdit&amp;#39;

interface Post {
    id: string
    title: string
    content: string
}

const PostsList: React.FC = () =&amp;gt; {
    const [posts, setPosts] = useState&amp;lt;Post[]&amp;gt;([])
    const [editingPostId, setEditingPostId] = useState&amp;lt;string | null&amp;gt;(null)

    // Firestore에서 모든 글을 가져와 상태로 설정
    const fetchPosts = async () =&amp;gt; {
        const querySnapshot = await getDocs(query(collection(db, &amp;#39;posts&amp;#39;), orderBy(&amp;#39;title&amp;#39;)))
        const postsData = querySnapshot.docs.map((doc) =&amp;gt; ({
            id: doc.id,
            title: doc.data().title,
            content: doc.data().content,
        }))
        setPosts(postsData)
    }

    useEffect(() =&amp;gt; {
        fetchPosts()
    }, [])

    // 글 삭제
    const handleDelete = async (id: string) =&amp;gt; {
        await deleteDocument(&amp;#39;posts&amp;#39;, id)
        fetchPosts() // 목록을 다시 불러옵니다.
    }

    // 글 편집 완료
    const handleUpdate = () =&amp;gt; {
        setEditingPostId(null)
        fetchPosts() // 업데이트된 목록을 불러옵니다.
    }

    return (
        &amp;lt;div&amp;gt;
            &amp;lt;h2&amp;gt;Posts List&amp;lt;/h2&amp;gt;
            {editingPostId ? (
                &amp;lt;PostEdit
                    id={editingPostId}
                    initialTitle={posts.find((post) =&amp;gt; post.id === editingPostId)?.title ?? &amp;#39;&amp;#39;}
                    initialContent={posts.find((post) =&amp;gt; post.id === editingPostId)?.content ?? &amp;#39;&amp;#39;}
                    onCancel={() =&amp;gt; setEditingPostId(null)}
                    onUpdate={handleUpdate}
                /&amp;gt;
            ) : (
                &amp;lt;&amp;gt;
                    {posts.length &amp;gt; 0 ? (
                        &amp;lt;ul&amp;gt;
                            {posts.map((post) =&amp;gt; (
                                &amp;lt;li key={post.id}&amp;gt;
                                    &amp;lt;h3&amp;gt;{post.title}&amp;lt;/h3&amp;gt;
                                    &amp;lt;p&amp;gt;{post.content}&amp;lt;/p&amp;gt;
                                    &amp;lt;button onClick={() =&amp;gt; setEditingPostId(post.id)}&amp;gt;Edit&amp;lt;/button&amp;gt;
                                    &amp;lt;button onClick={() =&amp;gt; handleDelete(post.id)}&amp;gt;Delete&amp;lt;/button&amp;gt;
                                &amp;lt;/li&amp;gt;
                            ))}
                        &amp;lt;/ul&amp;gt;
                    ) : (
                        &amp;lt;p&amp;gt;No posts found.&amp;lt;/p&amp;gt;
                    )}
                &amp;lt;/&amp;gt;
            )}
        &amp;lt;/div&amp;gt;
    )
}

export default PostsList&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;// src/pages/post/index.tsx
import PostsList from &amp;#39;@/components/PostsList&amp;#39;

const PostsPage: React.FC = () =&amp;gt; {
    return (
        &amp;lt;div&amp;gt;
            &amp;lt;h1&amp;gt;Posts&amp;lt;/h1&amp;gt;
            &amp;lt;PostsList /&amp;gt;
        &amp;lt;/div&amp;gt;
    )
}

export default PostsPage&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Front/Next.js</category>
      <category>blog만들기</category>
      <category>NextJS</category>
      <category>nextjs_blog</category>
      <author>oodada</author>
      <guid isPermaLink="true">https://oddcode.tistory.com/329</guid>
      <comments>https://oddcode.tistory.com/329#entry329comment</comments>
      <pubDate>Sun, 5 May 2024 01:45:35 +0900</pubDate>
    </item>
    <item>
      <title>Next.js 기초 및 설정</title>
      <link>https://oddcode.tistory.com/328</link>
      <description>&lt;h1&gt;Next.js 기초 및 설정&lt;/h1&gt;
&lt;p&gt;&lt;img src=&quot;https://i.namu.wiki/i/_SbahEOnImJMrE9Ml3Hyp1QcF7dkpM_4Nog8QGmiBLyY4_6gqA03E5cNT06csYg9rWFzwrgQNmAzwGm0oeu15ma9GeAPg1TK9gU7dd-qRQYHrOM4nhxg-WkB_ehP_276nl662hPpEHKmziRSYgq2Jg.svg&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h2&gt;1. Next.js 란?&lt;/h2&gt;
&lt;p&gt;Next.js는 React 기반의 웹 프레임워크로, &lt;strong&gt;서버 사이드 렌더링&lt;/strong&gt;과 &lt;strong&gt;정적 파일 생성&lt;/strong&gt; 등을 지원하여 개발자가 보다 효율적으로 웹 애플리케이션을 구축할 수 있도록 도와줍니다. 이 프레임워크는 &lt;strong&gt;React의 기능을 확장하여 더욱 강력한 기능을 제공&lt;/strong&gt;하며, SSR(Server-Side Rendering), CSR(Client-Side Rendering), Static Site Generation 등의 다양한 렌더링 방식을 지원합니다.&lt;/p&gt;
&lt;h2&gt;2. Next.js 특징&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;서버 사이드 렌더링(SSR)&lt;/strong&gt;: Next.js는 서버 사이드 렌더링을 지원하여 초기 로딩 시에 페이지를 서버에서 렌더링하여 사용자에게 보여줍니다. 이를 통해 SEO(Search Engine Optimization)를 개선하고, 초기 로딩 속도를 향상시킵니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;정적 파일 생성(Static Site Generation)&lt;/strong&gt;: Next.js는 페이지의 정적 HTML 파일을 빌드할 수 있는 정적 사이트 생성 기능을 제공합니다. 이를 통해 서버 부하를 줄이고, 사용자 경험을 개선할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;동적 라우팅(Dynamic Routing)&lt;/strong&gt;: Next.js는 동적 라우팅을 지원하여 페이지 경로를 동적으로 생성할 수 있습니다. 이를 통해 유연한 라우팅 구조를 구축할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;API Routes&lt;/strong&gt;: Next.js는 API Routes를 제공하여 서버리스(serverless) 함수를 생성할 수 있습니다. 이를 통해 서버리스 아키텍처를 쉽게 구현할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;개발 환경 설정 간소화&lt;/strong&gt;: Next.js는 기본적으로 웹팩(Webpack)과 바벨(Babel)을 내장하고 있어 개발 환경 설정을 간소화합니다. 또한, 기본적으로 코드 스플리팅(Code Splitting), CSS 모듈화(CSS Modules) 등의 기능을 지원하여 개발자가 보다 효율적으로 개발할 수 있도록 돕습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;** 코드 스플리팅(Code Splitting)이란? **&lt;/p&gt;
&lt;p&gt;코드 스플리팅(Code Splitting)은 웹 애플리케이션의 자바스크립트 코드를 여러 개의 번들로 분리하는 기술을 말합니다. 이를 통해 사용자가 방문한 페이지에 필요한 코드만 로드하여 초기 로딩 속도를 향상시킬 수 있습니다.&lt;/p&gt;
&lt;p&gt;** CSS 모듈화(CSS Modules)이란? **&lt;/p&gt;
&lt;p&gt;CSS 모듈화(CSS Modules)는 CSS 파일을 모듈화하여 컴포넌트 단위로 스타일을 적용할 수 있는 기술을 말합니다. 이를 통해 컴포넌트 간의 스타일 충돌을 방지하고, 코드의 일관성을 유지할 수 있습니다.&lt;/p&gt;
&lt;h2&gt;3. Next.js(Typesrcipt) 시작하기&lt;/h2&gt;
&lt;p&gt;Next.js 프로젝트를 Typesrcipt 버전으로 시작해보겠습니다. Typesrcipt는 정적 타입을 지원하여 코드의 안정성을 높이고, 개발 생산성을 향상시킬 수 있는 언어입니다. Next.js와 Typesrcipt를 함께 사용하면, 더욱 안정적이고 효율적인 웹 애플리케이션을 구축할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;next-blog-app&lt;/code&gt; 디렉토리에 터미널을 열고 다음과 같이 명령어를 실행합니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;프로젝트 생성&lt;/strong&gt;: &lt;code&gt;npx create-next-app ./&lt;/code&gt; 명령어를 사용하여 Next.js 프로젝트를 생성합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;프로젝트 실행&lt;/strong&gt;: &lt;code&gt;npm run dev&lt;/code&gt; 명령어를 사용하여 Next.js 프로젝트를 실행합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;페이지 생성&lt;/strong&gt;: &lt;code&gt;pages&lt;/code&gt; 디렉토리에 페이지를 생성하여 Next.js 애플리케이션을 구축합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npx create-next-app ./
cd next-blog-app
npm run dev # or yarn dev&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위와 같은 방법을 사용하여 Next.js 프로젝트를 생성 시 질문에 따라 Typesrcipt 를 사용할 것인지, ESLint 와 Tailwind 를 사용할 것인지 등을 선택할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/odada/post/aa7e7e91-e4a7-4354-aba8-4105eac861d7/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Typesrcipt&lt;/code&gt;: Typesrcipt 를 사용할 것인지 선택합니다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ESLint&lt;/code&gt;: ESLint 를 사용하여 코드 스타일을 검사할 것인지 선택합니다. (yes)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Tailwind CSS&lt;/code&gt;: Tailwind CSS 를 사용하여 스타일을 작성할 것인지 선택합니다. (yes)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;src&lt;/code&gt; 디렉토리에 컴포넌트를 생성하여 사용할 것인지 선택합니다. (yes)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;App Router&lt;/code&gt; 를 사용하여 라우팅을 구현할 것인지 선택합니다. (yes)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;import alias&lt;/code&gt; 를 사용하여 모듈을 더 쉽게 불러올 것인지 선택합니다. (yes)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;- src/ 디렉토리 사용 여부&lt;/h3&gt;
&lt;p&gt;src/ 디렉토리를 사용할지 여부는 프로젝트의 구조와 개발 환경에 따라 다를 수 있지만 &lt;strong&gt;사용하는 것을 권장합니다.&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;프로젝트 구조의 명확성&lt;/strong&gt;: src/ 디렉토리를 사용하면 프로젝트의 주요 소스 코드가 담긴 디렉토리를 명확하게 구분할 수 있습니다. 이는 프로젝트의 구조를 더 쉽게 이해하고 유지보수하기 쉽게 만듭니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;코드의 일관성&lt;/strong&gt;: src/ 디렉토리를 사용하면 프로젝트의 모든 소스 코드를 한 곳에 모아둘 수 있습니다. 이를 통해 코드의 일관성을 유지하고 관리하기 쉽게 만들 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;프로젝트 확장성&lt;/strong&gt;: src/ 디렉토리를 사용하면 향후 프로젝트를 확장할 때 더 유연하게 구조를 조정할 수 있습니다. 새로운 기능이나 모듈을 추가할 때 기존의 구조를 유지하면서 쉽게 통합할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;4. Next.js 프로젝트 구조&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;next-blog-app/
├── src/
│   ├── pages/        # 라우팅과 관련된 페이지 컴포넌트
│   ├── components/   # 재사용 가능한 UI 컴포넌트
│   ├── styles/       # 스타일 관련 파일
│   └── utils/        # 유틸리티 함수
├── public/           # 정적 파일들이 저장되는 디렉토리
├── .next/            # Next.js 빌드 시 생성되는 파일들이 저장되는
├── node_modules/     # 프로젝트에 설치된 모듈들이 저장되는 디렉토리
├── package.json      # 프로젝트 정보와 의존성 모듈들이 정의된 파일
└── README.md         # 프로젝트의 설명이 작성된 파일&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Next.js 프로젝트 구조화&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://nextjs.org/docs/pages/building-your-application/routing/pages-and-layouts&quot;&gt;next.js Routing&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Next.js 페이지는 &lt;code&gt;pages&lt;/code&gt; 디렉토리에 생성하여 사용할 수 있습니다. 페이지는 파일 이름에 따라 경로가 자동으로 생성되며, 동적 라우팅을 사용하여 동적 경로를 생성할 수 있습니다.&lt;/p&gt;
&lt;p&gt;pages 디렉토리의 파일명은 &lt;strong&gt;소문자&lt;/strong&gt;로 작성하며, 파일명에 따라 경로가 자동으로 생성됩니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;next-blog-app/
├── src/
│   ├── pages/
│   │   ├── index.tsx
│   │   ├── post/
│   │   │   └── index.tsx
│   │   │   └── [id].tsx
│   │   └── about.tsx
└── ...&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;pages&lt;/code&gt; 디렉토리에 페이지를 생성하고, 파일명에 따라 경로가 자동으로 생성됩니다. &lt;code&gt;index.tsx&lt;/code&gt; 파일은 &lt;code&gt;/&lt;/code&gt; 경로로 접근할 수 있으며, &lt;code&gt;[id].tsx&lt;/code&gt; 파일은 &lt;code&gt;/post/:id&lt;/code&gt; 경로로 접근할 수 있습니다.&lt;/p&gt;
&lt;h3&gt;- 페이지 라우팅&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;// src/pages/index.tsx

export default function Home() {
    return (
        &amp;lt;div&amp;gt;
            &amp;lt;h1&amp;gt;Home&amp;lt;/h1&amp;gt;
        &amp;lt;/div&amp;gt;
    )
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;// src/pages/post/index.tsx

const Post = () =&amp;gt; {
    return (
        &amp;lt;div&amp;gt;
            &amp;lt;h1&amp;gt;Post&amp;lt;/h1&amp;gt;
        &amp;lt;/div&amp;gt;
    )
}

export default Post&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;// src/pages/_app.tsx
import { AppProps } from &amp;#39;next/app&amp;#39;

function MyApp({ Component, pageProps }: AppProps) {
    return &amp;lt;Component {...pageProps} /&amp;gt;
}

export default MyApp&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;서버를 실행하고 &lt;code&gt;http://localhost:3000&lt;/code&gt; 경로로 접속하면, &lt;code&gt;Home&lt;/code&gt; 페이지가 렌더링됩니다. &lt;code&gt;http://localhost:3000/post&lt;/code&gt; 경로로 접속하면, &lt;code&gt;Post&lt;/code&gt; 페이지가 렌더링됩니다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;pages&lt;/code&gt; 디렉토리에 페이지를 생성하고, React 컴포넌트로 구성할 수 있습니다. 페이지는 &lt;code&gt;export default&lt;/code&gt; 키워드를 사용하여 내보내며, &lt;code&gt;pages&lt;/code&gt; 디렉토리의 파일명에 따라 경로가 자동으로 생성됩니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;main 페이지가 렌더링되지 않을 경우, &lt;code&gt;app&lt;/code&gt; 폴더의 &lt;code&gt;page.tsx&lt;/code&gt; 을 삭제하면 해결됩니다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;h2&gt;Next.js 레이아웃 구성&lt;/h2&gt;
&lt;p&gt;Next.js 레이아웃은 &lt;code&gt;components&lt;/code&gt; 디렉토리에 컴포넌트를 생성하여 사용할 수 있습니다. 레이아웃은 페이지의 공통 요소를 구성할 때 사용하며, &lt;code&gt;outlet&lt;/code&gt; 컴포넌트를 사용하여 페이지 컨텐츠를 렌더링할 수 있습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;next-blog-app/
├── components/
│   └── layout/
│       └── Layout.tsx
│       └── Header.tsx
│       └── Footer.tsx&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Next.js 레이아웃 적용&lt;/h2&gt;
&lt;p&gt;Next.js 레이아웃은 페이지에서 &lt;code&gt;Layout&lt;/code&gt; 컴포넌트를 사용하여 적용할 수 있습니다. 페이지에서 &lt;code&gt;Layout&lt;/code&gt; 컴포넌트를 사용하면, 페이지의 공통 요소를 효율적으로 구성할 수 있습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;// src/components/layout/Layout.tsx
import Link from &amp;#39;next/link&amp;#39;
import React from &amp;#39;react&amp;#39;

const Header = () =&amp;gt; {
    return (
        &amp;lt;header&amp;gt;
            &amp;lt;h1&amp;gt;
                &amp;lt;Link href=&amp;quot;/&amp;quot;&amp;gt;logo&amp;lt;/Link&amp;gt;
            &amp;lt;/h1&amp;gt;

            &amp;lt;nav&amp;gt;
                &amp;lt;ul&amp;gt;
                    &amp;lt;li&amp;gt;
                        &amp;lt;Link href=&amp;quot;/about&amp;quot;&amp;gt;About&amp;lt;/Link&amp;gt;
                    &amp;lt;/li&amp;gt;
                    &amp;lt;li&amp;gt;
                        &amp;lt;Link href=&amp;quot;/post&amp;quot;&amp;gt;Post&amp;lt;/Link&amp;gt;
                    &amp;lt;/li&amp;gt;
                &amp;lt;/ul&amp;gt;
            &amp;lt;/nav&amp;gt;
        &amp;lt;/header&amp;gt;
    )
}

export default Header&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;// src/components/layout/Header.tsx
export default function Header() {
    return (
        &amp;lt;header&amp;gt;
            &amp;lt;h1&amp;gt;Header&amp;lt;/h1&amp;gt;
        &amp;lt;/header&amp;gt;
    )
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;// src/components/layout/Footer.tsx
export default function Footer() {
    return (
        &amp;lt;footer&amp;gt;
            &amp;lt;h1&amp;gt;Footer&amp;lt;/h1&amp;gt;
        &amp;lt;/footer&amp;gt;
    )
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;Layout&lt;/code&gt; 컴포넌트를 생성하여 레이아웃을 구성하고, &lt;code&gt;Header&lt;/code&gt; 컴포넌트와 &lt;code&gt;Footer&lt;/code&gt; 컴포넌트를 생성하여 레이아웃에 적용할 수 있습니다. 이렇게 구성한 레이아웃은 React Router를 사용하여 페이지 간의 이동 시 공통 요소를 효율적으로 구성할 수 있습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;// src/pages/_app.tsx
import { AppProps } from &amp;#39;next/app&amp;#39; // AppProps 타입을 불러옵니다.
import Layout from &amp;#39;@/components/layout/Layout&amp;#39;

function MyApp({ Component, pageProps }: AppProps) {
    return (
        &amp;lt;Layout&amp;gt;
            &amp;lt;Component {...pageProps} /&amp;gt;
        &amp;lt;/Layout&amp;gt;
    )
}

export default MyApp&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;_app.tsx&lt;/code&gt; 파일을 생성하여 레이아웃을 적용할 수 있습니다. &lt;code&gt;_app.tsx&lt;/code&gt; 파일은 Next.js 애플리케이션의 전역 레이아웃을 설정할 때 사용되며, &lt;code&gt;Layout&lt;/code&gt; 컴포넌트를 사용하여 페이지의 공통 요소를 구성할 수 있습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;// src/pages/_document.tsx
import Document, { Html, Head, Main, Nextsrcipt } from &amp;#39;next/document&amp;#39;

class MyDocument extends Document {
    render() {
        return (
            &amp;lt;Html&amp;gt;
                &amp;lt;Head&amp;gt;
                    &amp;lt;link rel=&amp;quot;icon&amp;quot; href=&amp;quot;/favicon.ico&amp;quot; /&amp;gt;
                &amp;lt;/Head&amp;gt;
                &amp;lt;body&amp;gt;
                    &amp;lt;Main /&amp;gt;
                    &amp;lt;Nextsrcipt /&amp;gt;
                &amp;lt;/body&amp;gt;
            &amp;lt;/Html&amp;gt;
        )
    }
}

export default MyDocument&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;_document.tsx&lt;/code&gt; 파일을 생성하여 HTML 문서의 레이아웃을 설정할 수 있습니다. &lt;code&gt;_document.tsx&lt;/code&gt; 파일은 모든 페이지에 공통으로 적용되는 HTML 문서의 레이아웃을 설정할 때 사용되며, &lt;code&gt;Head&lt;/code&gt; 컴포넌트를 사용하여 메타 태그나 스타일 시트를 적용할 수 있습니다.&lt;/p&gt;
&lt;h2&gt;Next.js 페이지 링크&lt;/h2&gt;
&lt;p&gt;Next.js 페이지 간의 이동은 &lt;code&gt;Link&lt;/code&gt; 컴포넌트를 사용하여 구현할 수 있습니다. &lt;code&gt;Link&lt;/code&gt; 컴포넌트는 페이지 간의 이동을 자동으로 처리하며, 페이지를 미리 로드하여 사용자 경험을 향상시킵니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;// pages/home/index.tsx

import Link from &amp;#39;next/link&amp;#39;

export default const Home = () =&amp;gt; {
    return (
        &amp;lt;div&amp;gt;
            &amp;lt;h1&amp;gt;Home&amp;lt;/h1&amp;gt;
            &amp;lt;Link href=&amp;quot;/post&amp;quot;&amp;gt;
                Go to Post
            &amp;lt;/Link&amp;gt;
        &amp;lt;/div&amp;gt;
    )
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Next.js 정적 파일 제공&lt;/h2&gt;
&lt;p&gt;Next.js는 &lt;code&gt;public&lt;/code&gt; 디렉토리를 사용하여 정적 파일을 제공할 수 있습니다. &lt;code&gt;public&lt;/code&gt; 디렉토리에 저장된 파일은 &lt;code&gt;/&lt;/code&gt; 경로로 접근할 수 있으며, 이미지, CSS 파일, JS 파일 등을 저장할 수 있습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;next-blog-app/
├── public/
│   ├── images/
│   │   └── logo.png
│   ├── styles/
│   │   └── main.css&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;public&lt;/code&gt; 디렉토리에 &lt;code&gt;images&lt;/code&gt; 디렉토리와 &lt;code&gt;styles&lt;/code&gt; 디렉토리를 생성하고, 이미지 파일과 CSS 파일을 저장할 수 있습니다. 이렇게 저장된 파일은 &lt;code&gt;/images/logo.png&lt;/code&gt;, &lt;code&gt;/styles/main.css&lt;/code&gt; 경로로 접근할 수 있습니다.&lt;/p&gt;
&lt;h2&gt;Typesrcipt Type 정의&lt;/h2&gt;
&lt;p&gt;Next.js 프로젝트에서 타입을 정의할 때는 &lt;code&gt;types&lt;/code&gt; 디렉토리를 사용하여 타입을 정의할 수 있습니다. &lt;code&gt;types&lt;/code&gt; 디렉토리에 타입을 정의하고, 필요한 파일에서 타입을 불러와 사용할 수 있습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;next-blog-app/
src/
├── types/
│   └── types.ts       # 타입 정의를 모아둔 디렉토리와 파일
|   └── utils.ts       # 유틸리티 함수를 모아둔 디렉토리와 파일
├── data/
│   └── posts.ts       # 데이터를 모아둔 디렉토리와 파일&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Typesrcipt를 사용하여 타입을 정의&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;// src/types/types.ts
// Post 타입을 정의합니다.
export interface Post {
    id: number
    title: string
    content: string
    author: string
    date: string // 날짜는 ISO 형식 (예: &amp;quot;2024-01-01&amp;quot;)으로 나타냅니다.
    tags?: string[] // 태그는 선택적으로 배열 형태로 나타냅니다.
    // undefined일 수 있기 때문에 옵셔널 체이닝 연산자(?.)를 사용합니다.
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Post 타입을 사용하여 데이터를 정의&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;// src/data/posts.ts
import { Post } from &amp;#39;@/types/types&amp;#39;

// Post 타입을 사용하여 데이터를 정의합니다.
// posts 배열에 Post 타입의 데이터를 저장합니다.
export const posts: Post[] = [
    {
        id: 1,
        title: &amp;#39;Javasrcipt 클로저 이해하기&amp;#39;,
        content: `
        클로저는 자바스크립트에서 중요한 개념입니다. 클로저는 함수가 선언된 시점의 스코프(변수 범위)를 기억하여, 나중에 그 함수가 호출될 때 해당 스코프에 접근할 수 있게 합니다. 
        예를 들어, 내부 함수가 외부 함수의 변수를 기억하고 있는 경우를 생각해 봅시다. 클로저를 잘 이해하면, 고급 자바스크립트 프로그래밍 기법을 쉽게 다룰 수 있습니다. 이 글에서는 클로저의 기초 개념과, 클로저를 활용한 실전 예제를 알아보겠습니다.
        `,
        author: &amp;#39;홍길동&amp;#39;,
        date: &amp;#39;2024-05-10&amp;#39;,
        tags: [&amp;#39;Javasrcipt&amp;#39;, &amp;#39;클로저&amp;#39;, &amp;#39;프로그래밍&amp;#39;],
    },
    {
        id: 2,
        title: &amp;#39;CSS Grid 레이아웃 가이드&amp;#39;,
        content: `
        CSS Grid 레이아웃은 반응형 디자인을 구현하기 위한 강력한 도구입니다. 그리드 시스템을 사용하면 여러 개의 행과 열을 활용하여 복잡한 레이아웃을 손쉽게 구성할 수 있습니다. 
        이 가이드에서는 Grid의 기본 개념을 소개하고, 실제 프로젝트에서 그리드 레이아웃을 활용하는 방법을 단계별로 설명합니다. 그리드 아이템 간의 간격 조절부터 다양한 행과 열의 크기 조절에 이르기까지, 이 글을 통해 여러분은 더욱 유연한 웹 디자인을 만들 수 있을 것입니다.
        `,
        author: &amp;#39;이영희&amp;#39;,
        date: &amp;#39;2024-05-08&amp;#39;,
        tags: [&amp;#39;CSS&amp;#39;, &amp;#39;웹 디자인&amp;#39;, &amp;#39;그리드 레이아웃&amp;#39;],
    },
    {
        id: 3,
        title: &amp;#39;Python 데이터 클래스 활용하기&amp;#39;,
        content: `
        데이터 클래스는 파이썬에서 클래스 구조를 간단하게 정의할 수 있는 방법을 제공합니다. 일반 클래스를 사용할 때보다 코드가 훨씬 간결해지며, 기본적인 기능(예: __init__, __repr__)도 자동으로 제공됩니다.
        이 글에서는 데이터 클래스의 사용법과 주요 기능을 살펴보고, 이를 통해 파이썬 코드의 효율성을 높이는 방법을 알아보겠습니다. 또한, 데이터 클래스의 커스터마이징 방법과 기존 클래스와의 비교를 통해 다양한 활용 사례를 소개합니다.
        `,
        author: &amp;#39;김철수&amp;#39;,
        date: &amp;#39;2024-05-05&amp;#39;,
        tags: [&amp;#39;Python&amp;#39;, &amp;#39;데이터 클래스&amp;#39;, &amp;#39;프로그래밍&amp;#39;],
    },
]&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Components 구성&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;next-blog-app/
├── src/
├── ├── components/
│   ├── ├── post/
│   │   │   ├── PostList.tsx
│   │   │   ├── PostItem.tsx
│   │   │   ├── PostDetail.tsx&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;PostItem 컴포넌트를 생성하여 Post 데이터를 렌더링&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;// components/post/PostItem.tsx
// Type-Only Import 문법을 사용하여 타입만 불러옵니다.
import type { Post } from &amp;#39;@/type/types&amp;#39;
import Link from &amp;#39;next/link&amp;#39; // Link 컴포넌트를 임포트합니다.

// PostProps 타입을 정의합니다.
interface PostProps {
    post: Post
}

const PostItem = ({ post }: PostProps) =&amp;gt; {
    return (
        &amp;lt;div&amp;gt;
            {/* 제목을 Link 컴포넌트로 감싸고 href에 동적 경로를 지정합니다. */}
            &amp;lt;h2&amp;gt;
                &amp;lt;Link href={`/post/${post.id}`}&amp;gt;{post.title}&amp;lt;/Link&amp;gt;
            &amp;lt;/h2&amp;gt;
            &amp;lt;p style={{ whiteSpace: &amp;#39;nowrap&amp;#39;, width: &amp;#39;80%&amp;#39;, overflow: &amp;#39;hidden&amp;#39;, textOverflow: &amp;#39;ellipsis&amp;#39; }}&amp;gt;
                {post.content}
            &amp;lt;/p&amp;gt;
            &amp;lt;p&amp;gt;{post.date}&amp;lt;/p&amp;gt;
        &amp;lt;/div&amp;gt;
    )
}

export default PostItem&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;// components/Post/PostList.tsx
import React from &amp;#39;react&amp;#39;
import { posts } from &amp;#39;@/data/posts&amp;#39;
import PostItem from &amp;#39;@/components/post/PostItem&amp;#39;

const PostList = () =&amp;gt; {
    return (
        &amp;lt;div&amp;gt;
            {posts.map((post) =&amp;gt; (
                &amp;lt;PostItem key={post.id} post={post} /&amp;gt;
            ))}
        &amp;lt;/div&amp;gt;
    )
}

export default PostList&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- 페이지에 데이터 렌더링&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;// pages/post/index.tsx
import PostList from &amp;#39;@/components/post/PostList&amp;#39;

const PostPage = () =&amp;gt; {
    return (
        &amp;lt;div&amp;gt;
            &amp;lt;h1&amp;gt;Post&amp;lt;/h1&amp;gt;
            &amp;lt;PostList /&amp;gt;
        &amp;lt;/div&amp;gt;
    )
}

export default PostPage&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;Post&lt;/code&gt; 컴포넌트를 사용하여 데이터를 렌더링할 수 있습니다. &lt;code&gt;Post&lt;/code&gt; 컴포넌트에 &lt;code&gt;Post&lt;/code&gt; 타입의 데이터를 전달하고, &lt;code&gt;posts&lt;/code&gt; 배열을 사용하여 데이터를 렌더링합니다.&lt;/p&gt;
&lt;h3&gt;- 페이지에 동적 라우팅 적용&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;// components/post/PostDetail.tsx
import type { Post } from &amp;#39;@/type/types&amp;#39;
import Link from &amp;#39;next/link&amp;#39; // Link 컴포넌트를 임포트합니다.

// PostProps 타입을 정의합니다.
interface PostProps {
    post: Post
}

const PostDetail = ({ post }: PostProps) =&amp;gt; {
    return (
        &amp;lt;div&amp;gt;
            &amp;lt;h2&amp;gt;{post.title}&amp;lt;/h2&amp;gt;
            &amp;lt;p&amp;gt;{post.content}&amp;lt;/p&amp;gt;
            &amp;lt;p&amp;gt;{post.author}&amp;lt;/p&amp;gt;
            &amp;lt;p&amp;gt;{post.date}&amp;lt;/p&amp;gt;
            {/* &amp;amp;&amp;amp; 연산자를 사용하여 tags가 존재하는 경우에만 출력합니다. */}
            &amp;lt;ul&amp;gt;{post.tags &amp;amp;&amp;amp; post.tags.map((tag, index) =&amp;gt; &amp;lt;li key={index}&amp;gt;{tag}&amp;lt;/li&amp;gt;)}&amp;lt;/ul&amp;gt;
        &amp;lt;/div&amp;gt;
    )
}

export default PostDetail&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;// pages/post/[id].tsx
import { GetStaticProps, GetStaticPaths } from &amp;#39;next&amp;#39;
import { posts } from &amp;#39;@/data/posts&amp;#39; // 가정한 경로
import PostDetail from &amp;#39;@/components/post/PostDetail&amp;#39;

// PostType 타입을 정의합니다.
interface PostType {
    id: number
    title: string
    content: string
    author: string
    date: string
    tags?: string[]
}

// PostType 타입을 사용하여 PostProps 타입을 정의합니다.
interface PostProps {
    post: PostType
}

// getStaticPaths 함수를 사용하여 동적 경로를 생성합니다.
// async 함수를 사용하여 비동기 처리를 수행합니다.
export const getStaticPaths: GetStaticPaths = async () =&amp;gt; {
    // posts 배열의 id 값을 사용하여 동적 경로를 생성합니다.
    const paths = posts.map((post) =&amp;gt; ({
        params: { id: post.id.toString() },
        // id 값을 문자열로 변환하여 params에 저장합니다.
        // 예: { params: { id: &amp;#39;1&amp;#39; } }
    }))

    return {
        paths, // 생성된 동적 경로를 반환합니다.
        fallback: false, // fallback을 false로 설정하여 없는 페이지는 404 페이지를 반환합니다.
    }
}

// getStaticProps 함수를 사용하여 각 동적 경로에 필요한 데이터를 전달합니다.
// async 함수를 사용하여 비동기 처리를 수행합니다.
export const getStaticProps: GetStaticProps&amp;lt;PostProps, { id: string }&amp;gt; = async ({ params }) =&amp;gt; {
    // params.id 값을 사용하여 해당 포스트를 찾습니다.
    const post = posts.find((post) =&amp;gt; post.id === Number(params?.id))
    // params.id는 문자열이므로 Number로 변환합니다.
    // params가 undefined인 경우를 대비하여 옵셔널 체이닝 연산자(?.)를 사용해 에러를 방지합니다.

    if (!post) {
        return { notFound: true } // 포스트가 없는 경우 404 페이지를 반환
    }

    return {
        props: {
            post,
        },
    }
}

// Post 컴포넌트를 정의합니다.
const Post: React.FC&amp;lt;PostProps&amp;gt; = ({ post }) =&amp;gt; {
    return (
        &amp;lt;div&amp;gt;
            &amp;lt;h1&amp;gt;Post&amp;lt;/h1&amp;gt;
            &amp;lt;PostDetail post={post} /&amp;gt;
        &amp;lt;/div&amp;gt;
    )
}

export default Post&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Tailwind CSS 적용&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;next-blog-app/
src/
├── styles/
│   ├── globals.css
│   ├── Home.module.css
│   ├── Post.module.css&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;/* styles/globals.css */
@tailwind base;
@tailwind components;
@tailwind utilities;&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;/* styles/Home.module.css */
.container {
    max-width: 960px;
    margin: 0 auto;
    padding: 0 1rem;
}

.title {
    font-size: 2rem;
    font-weight: bold;
    color: #333;
}

/* styles/Post.module.css */
.post {
    margin-bottom: 2rem;
    border-bottom: 1px solid #ddd;
}

.postTitle {
    font-size: 1.5rem;
    font-weight: bold;
    color: #333;
}

.postContent {
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    width: 80%;
    margin-top: 1rem;
    color: #666;
}

.postAuthor {
    margin-top: 0.5rem;
    color: #999;
}

.postDate {
    margin-top: 0.5rem;
    color: #999;
}

.postTags {
    margin-top: 0.5rem;
    color: #999;
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;컴포넌트에 스타일 적용&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;// components/Post/Post.tsx
import type { Post } from &amp;#39;@/type/types&amp;#39;
import Link from &amp;#39;next/link&amp;#39;
import styles from &amp;#39;@/styles/Post.module.css&amp;#39;

// PostProps 타입을 정의합니다.
interface PostProps {
    post: Post
}

const PostItem = ({ post }: PostProps) =&amp;gt; {
    return (
        &amp;lt;div className={styles.post}&amp;gt;
            {/* 제목을 Link 컴포넌트로 감싸고 href에 동적 경로를 지정합니다. */}
            &amp;lt;h2&amp;gt;
                &amp;lt;Link href={`/post/${post.id}`}&amp;gt;
                    &amp;lt;span className={styles.postTitle}&amp;gt;{post.title}&amp;lt;/span&amp;gt;
                &amp;lt;/Link&amp;gt;
            &amp;lt;/h2&amp;gt;
            &amp;lt;p className={styles.postContent}&amp;gt;{post.content}&amp;lt;/p&amp;gt;
            &amp;lt;p className={styles.postDate}&amp;gt;{post.date}&amp;lt;/p&amp;gt;
        &amp;lt;/div&amp;gt;
    )
}

export default PostItem&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;Post&lt;/code&gt; 컴포넌트에 &lt;code&gt;Post.module.css&lt;/code&gt; 파일을 적용하여 스타일을 적용할 수 있습니다. &lt;code&gt;Post.module.css&lt;/code&gt; 파일을 사용하여 컴포넌트에 스타일을 적용하고, &lt;code&gt;styles&lt;/code&gt; 객체를 사용하여 클래스 이름을 지정할 수 있습니다.&lt;/p&gt;
&lt;h2&gt;Next.js 빌드 및 실행&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;next-blog-app/
├── .next/
└── ...&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Next.js 프로젝트를 빌드하고 실행하는 방법&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Next.js 프로젝트 빌드
npm run build # or yarn build

# Next.js 프로젝트 실행
npm run start # or yarn start&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;npm run build&lt;/code&gt; 명령어를 사용하여 Next.js 프로젝트를 빌드하고, &lt;code&gt;npm run start&lt;/code&gt; 명령어를 사용하여 Next.js 프로젝트를 실행할 수 있습니다. 빌드된 파일은 &lt;code&gt;.next&lt;/code&gt; 디렉토리에 저장되며, 실행 시 &lt;code&gt;.next&lt;/code&gt; 디렉토리의 파일을 사용하여 Next.js 애플리케이션을 실행합니다.&lt;/p&gt;
&lt;h2&gt;Next.js 프로젝트 배포&lt;/h2&gt;
&lt;p&gt;Next.js 프로젝트를 배포할 때는 Vercel, Netlify, AWS, Heroku 등의 클라우드 서비스를 사용하여 배포할 수 있습니다. 이 중에서 Vercel은 Next.js를 만든 회사이며, Next.js 프로젝트를 쉽게 배포할 수 있는 플랫폼을 제공합니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://vercel.com/home&quot;&gt;Vercel&lt;/a&gt;&lt;/strong&gt;: Next.js를 만든 회사이며, Next.js 프로젝트를 쉽게 배포할 수 있는 플랫폼을 제공합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://www.netlify.com/&quot;&gt;Netlify&lt;/a&gt;&lt;/strong&gt;: 정적 사이트를 무료로 호스팅할 수 있는 플랫폼을 제공합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://aws.amazon.com/&quot;&gt;AWS&lt;/a&gt;&lt;/strong&gt;: 클라우드 서비스를 제공하는 아마존 웹 서비스입니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://www.heroku.com/&quot;&gt;Heroku&lt;/a&gt;&lt;/strong&gt;: 클라우드 플랫폼을 제공하는 회사로, 다양한 언어와 프레임워크를 지원합니다.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Front/Next.js</category>
      <category>NextJS</category>
      <author>oodada</author>
      <guid isPermaLink="true">https://oddcode.tistory.com/328</guid>
      <comments>https://oddcode.tistory.com/328#entry328comment</comments>
      <pubDate>Sun, 5 May 2024 01:31:36 +0900</pubDate>
    </item>
    <item>
      <title>Rudux로 전역 상태 관리하기</title>
      <link>https://oddcode.tistory.com/327</link>
      <description>&lt;h1&gt;Redux로 전역 상태 관리하기&lt;/h1&gt;
&lt;h2&gt;Redux 소개&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://redux.js.org/&quot;&gt;Redux&lt;/a&gt;는 상태 관리 라이브러리로, React와 함께 사용하기 좋습니다. Redux를 사용하면 컴포넌트 간에 상태를 쉽게 공유할 수 있습니다.&lt;/p&gt;
&lt;h3&gt;- Redux 특징&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Single Source of Truth&lt;/strong&gt;: 애플리케이션의 상태는 하나의 스토어에 저장됩니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;State is Read-Only&lt;/strong&gt;: 상태를 직접 변경할 수 없습니다. 상태를 변경하는 유일한 방법은 액션을 디스패치하는 것입니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Changes are Made with Pure Functions&lt;/strong&gt;: 상태를 변경하는 함수인 리듀서는 순수 함수여야 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;- Redux 구조&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Actions&lt;/strong&gt;: 상태 변경을 위한 객체&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Reducers&lt;/strong&gt;: 상태를 변경하는 함수&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Store&lt;/strong&gt;: 애플리케이션의 상태를 저장하는 객체&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;store/
|-- actions/
|   |-- todoAction.js
|   |-- cartAction.js
|
|-- reducers/
|   |-- todoReducer.js
|   |-- cartReducer.js
|
|-- index.js&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Redux를 사용하면 상태를 컴포넌트 간에 쉽게 공유할 수 있습니다. 또한, 상태 변경을 추적하기 쉽고, 상태 변경에 따른 로직을 쉽게 구현할 수 있습니다.&lt;/p&gt;
&lt;h2&gt;Redux 사용하기&lt;/h2&gt;
&lt;p&gt;Redux를 사용하려면 다음과 같은 단계를 따르면 됩니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Redux 설치&lt;/strong&gt;: Redux를 설치합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Actions 정의&lt;/strong&gt;: 상태 변경을 위한 액션을 정의합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Reducers 정의&lt;/strong&gt;: 상태를 변경하는 리듀서를 정의합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Store 생성&lt;/strong&gt;: 애플리케이션의 상태를 저장하는 스토어를 생성합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Store 연결&lt;/strong&gt;: 스토어를 컴포넌트에 연결합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;1. Redux 설치&lt;/h3&gt;
&lt;p&gt;Redux를 설치하려면 다음 명령어를 실행합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm install @reduxjs/toolkit react-redux
yarn add @reduxjs/toolkit react-redux&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. Actions 정의&lt;/h3&gt;
&lt;p&gt;액션은 상태 변경을 위한 객체입니다. 액션은 다음과 같이 정의할 수 있습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;// store/action.js
const action = {
    type: &amp;#39;ADD_TODO&amp;#39;, // 액션 타입
    payload: &amp;#39;Learn Redux&amp;#39;, // 액션 데이터
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3. Reducers 정의&lt;/h3&gt;
&lt;p&gt;리듀서는 상태를 변경하는 함수입니다. 리듀서는 이전 상태와 액션을 받아 새로운 상태를 반환합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;// store/reducer.js
const reducer = (state, action) =&amp;gt; {
    switch (action.type) {
        case &amp;#39;ADD_TODO&amp;#39;:
            return {
                ...state,
                todos: [...state.todos, action.payload],
            }
        default:
            return state
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4. Store 생성&lt;/h3&gt;
&lt;p&gt;스토어는 애플리케이션의 상태를 저장하는 객체입니다. 스토어를 생성하려면 다음과 같이 createStore 함수를 사용합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;// store/index.js
import { createStore } from &amp;#39;redux&amp;#39;

const store = createStore(reducer)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;여러 개의 리듀서를 사용할 때는 &lt;code&gt;combineReducers&lt;/code&gt; 함수를 사용합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;// store/index.js
import { createStore, combineReducers } from &amp;#39;redux&amp;#39;
import todoReducer from &amp;#39;./reducers/todoReducer&amp;#39;
import cartReducer from &amp;#39;./reducers/cartReducer&amp;#39;

const rootReducer = combineReducers({
    todo: todoReducer,
    cart: cartReducer,
})

const store = createStore(rootReducer)&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;5. Store 연결&lt;/h3&gt;
&lt;p&gt;스토어를 컴포넌트에 연결하려면 다음과 같이 Provider 컴포넌트를 사용합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;// App.js
import { Provider } from &amp;#39;react-redux&amp;#39;

ReactDOM.render(
    &amp;lt;Provider store={store}&amp;gt;
        &amp;lt;App /&amp;gt;
    &amp;lt;/Provider&amp;gt;,
    document.getElementById(&amp;#39;root&amp;#39;),
)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Redux를 사용하면 상태를 컴포넌트 간에 쉽게 공유할 수 있습니다. 또한, 상태 변경을 추적하기 쉽고, 상태 변경에 따른 로직을 쉽게 구현할 수 있습니다.&lt;/p&gt;
&lt;h2&gt;[실습] 할 일 관리 앱에 Redux 적용하기&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://odada.me/314&quot;&gt;할 일 관리 앱&lt;/a&gt;에 Redux를 적용해보겠습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;// src/store/actions/todoAction.js
// 액션 타입 정의 (대문자로 작성)
export const ADD_TODO = &amp;#39;ADD_TODO&amp;#39; // 할 일 추가
export const UPDATE_TODO = &amp;#39;UPDATE_TODO&amp;#39;
export const DELETE_TODO = &amp;#39;DELETE_TODO&amp;#39;

// 액션 생성자 함수
export const addTodo = (task) =&amp;gt; ({
    type: ADD_TODO, // 액션 타입
    payload: { id: Date.now(), isDone: false, task, createdDate: new Date().getTime() }, // 액션 페이로드
})

export const updateTodo = (id) =&amp;gt; ({
    type: UPDATE_TODO,
    payload: id,
})

export const deleteTodo = (id) =&amp;gt; ({
    type: DELETE_TODO,
    payload: id,
})&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;// src/store/reducers/todoReducer.js
import { ADD_TODO, UPDATE_TODO, DELETE_TODO } from &amp;#39;../actions/todoAction&amp;#39;

// 초기 상태
const initialState = {
    todos: [
        {
            id: 1,
            isDone: false,
            task: &amp;#39;고양이 밥주기&amp;#39;,
            createdDate: new Date().getTime(),
        },
        {
            id: 2,
            isDone: false,
            task: &amp;#39;감자 캐기&amp;#39;,
            createdDate: new Date().getTime(),
        },
        {
            id: 3,
            isDone: false,
            task: &amp;#39;고양이 놀아주기&amp;#39;,
            createdDate: new Date().getTime(),
        },
    ],
}

// 리듀서 함수
const todoReducer = (state = initialState, action) =&amp;gt; {
    switch (action.type) {
        case ADD_TODO:
            return {
                // 기존 상태를 변경하지 않고 새로운 상태를 반환합니다.
                // redux는 불변성을 유지하고 이전 상태를 변경하지 않아야 합니다.
                ...state, // 기존 상태를 복사합니다.
                todos: [action.payload, ...state.todos], // 새로운 할 일을 추가합니다.
            }
        case UPDATE_TODO:
            return {
                ...state,
                todos: state.todos.map((it) =&amp;gt; (it.id === action.payload ? { ...it, isDone: !it.isDone } : it)),
            }
        case DELETE_TODO:
            return {
                ...state,
                todos: state.todos.filter((it) =&amp;gt; it.id !== action.payload),
            }
        default:
            return state
    }
}

export default todoReducer&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;// src/store/index.js
import todoReducer from &amp;#39;./reducers/todoReducer&amp;#39;
import { combineReducers, createStore } from &amp;#39;redux&amp;#39;

const rootReducer = combineReducers({
    // todoReducer를 todo라는 이름으로 사용합니다.
    todo: todoReducer,
    // cart: cartReducer,
})

const store = createStore(rootReducer)

export default store&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;// src/App.js
// Redux를 사용하기 위해 불필요한 TodoProvider 컴포넌트 삭제
// Redux의 Provider 컴포넌트로 감싸줍니다.

import React from &amp;#39;react&amp;#39;
import TodoHd from &amp;#39;./TodoHd&amp;#39;
import TodoEditor from &amp;#39;./TodoEditor&amp;#39;
import TodoList from &amp;#39;./TodoList&amp;#39;
import { Provider } from &amp;#39;react-redux&amp;#39; // Redux의 Provider 임포트
import store from &amp;#39;./store&amp;#39; // Redux 스토어 임포트

function App() {
    return (
        &amp;lt;Provider store={store}&amp;gt;
            {&amp;#39; &amp;#39;}
            {/* Redux의 Provider로 감싸기 */}
            &amp;lt;div&amp;gt;
                &amp;lt;TodoHd /&amp;gt;
                &amp;lt;TodoEditor /&amp;gt;
                &amp;lt;TodoList /&amp;gt;
            &amp;lt;/div&amp;gt;
        &amp;lt;/Provider&amp;gt;
    )
}

export default App&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;// src/components/TodoEditor.js
// Redux와의 연동을 위해 더 이상 useTodoContext 훅 사용하지 않음
// 액션 함수를 디스패치하기 위해 useDispatch 훅을 사용
// TodoEditor 컴포넌트에서는 더 이상 useState 훅을 사용하여 상태를 관리하지 않음
import React, { useState } from &amp;#39;react&amp;#39;
import { useDispatch } from &amp;#39;react-redux&amp;#39;
import { addTodo } from &amp;#39;../../store/actions/todoAction&amp;#39;

export default function TodoEditor() {
    const [task, setTask] = useState(&amp;#39;&amp;#39;)
    const dispatch = useDispatch() // useDispatch 훅으로 디스패치 함수 가져오기

    const onSubmit = () =&amp;gt; {
        if (!task) return
        dispatch(addTodo(task)) // 액션 함수 디스패치
        setTask(&amp;#39;&amp;#39;)
    }

    const onKeyDown = (e) =&amp;gt; {
        if (e.key === &amp;#39;Enter&amp;#39;) {
            onSubmit()
        }
    }

    return (
        &amp;lt;div&amp;gt;
            &amp;lt;h3&amp;gt;새로운 Todo 작성하기 ✏&amp;lt;/h3&amp;gt;
            &amp;lt;div&amp;gt;
                &amp;lt;input
                    type=&amp;quot;text&amp;quot;
                    ref={(inputRef) =&amp;gt; inputRef &amp;amp;&amp;amp; inputRef.focus()}
                    placeholder=&amp;quot;할 일을 추가로 입력해주세요.&amp;quot;
                    onChange={(e) =&amp;gt; setTask(e.target.value)}
                    onKeyDown={onKeyDown}
                    value={task}
                /&amp;gt;
                &amp;lt;button onClick={onSubmit}&amp;gt;할 일 추가&amp;lt;/button&amp;gt;
            &amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;gt;
    )
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;// src/components/TodoList.js
// Redux와의 연동을 위해 useSelector 훅을 사용하여 상태를 가져오기
// 액션 함수를 디스패치하기 위해 useDispatch 훅을 사용
import React, { useState } from &amp;#39;react&amp;#39;
import TodoItem from &amp;#39;./TodoItem&amp;#39;
import { useSelector } from &amp;#39;react-redux&amp;#39; // useDispatch 대신 useSelector 사용

function TodoList() {
    const [search, setSearch] = useState(&amp;#39;&amp;#39;)
    // useSelector에서 state로 전달되는 객체는 rootReducer에서 정의한 객체입니다.
    // rootReducer에서 todoReducer를 todo라는 이름으로 사용했기 때문에 state.todo로 todo 상태를 가져옵니다.
    // 따라서 state.todo.todo로 todo 상태를 가져올 수 있습니다.
    const todo = useSelector((state) =&amp;gt; state.todo.todos) // todo 상태를 가져옵니다.

    const onChangeSearch = (e) =&amp;gt; {
        setSearch(e.target.value)
    }

    const filteredTodo = () =&amp;gt; {
        // todo 상태가 배열인지 확인하고 배열 메서드를 사용하기
        if (Array.isArray(todo)) {
            return todo.filter((item) =&amp;gt; item.task.toLowerCase().includes(search.toLowerCase()))
        } else {
            return []
        }
    }

    return (
        &amp;lt;div&amp;gt;
            &amp;lt;h3&amp;gt;할 일 목록  &amp;lt;/h3&amp;gt;
            &amp;lt;input type=&amp;quot;text&amp;quot; placeholder=&amp;quot;검색어를 입력하세요&amp;quot; onChange={onChangeSearch} value={search} /&amp;gt;

            &amp;lt;div&amp;gt;
                {filteredTodo().map((item) =&amp;gt; (
                    &amp;lt;TodoItem key={item.id} {...item} /&amp;gt;
                ))}
            &amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;gt;
    )
}

export default TodoList&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;// src/components/TodoItem.js
// Redux와의 연동을 위해 useDispatch 훅을 사용하여 액션 함수 디스패치
import React from &amp;#39;react&amp;#39;
import { format } from &amp;#39;date-fns&amp;#39;
import { useDispatch } from &amp;#39;react-redux&amp;#39;
import { deleteTodo, updateTodo } from &amp;#39;../../store/actions/todoAction&amp;#39;

function TodoItem({ id, isDone, task, createdDate }) {
    const dispatch = useDispatch()

    return (
        &amp;lt;li key={id}&amp;gt;
            &amp;lt;input type=&amp;quot;checkbox&amp;quot; checked={isDone} onChange={() =&amp;gt; dispatch(updateTodo(id))} /&amp;gt;
            &amp;lt;strong style={{ textDecoration: isDone ? &amp;#39;line-through&amp;#39; : &amp;#39;none&amp;#39; }}&amp;gt;{task}&amp;lt;/strong&amp;gt;
            &amp;lt;span&amp;gt;{format(new Date(createdDate), &amp;#39;yyyy.MM.dd&amp;#39;)}&amp;lt;/span&amp;gt;
            &amp;lt;button onClick={() =&amp;gt; dispatch(deleteTodo(id))}&amp;gt;삭제&amp;lt;/button&amp;gt;
        &amp;lt;/li&amp;gt;
    )
}

export default TodoItem&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Redux Toolkit으로 상태 관리하기&lt;/h2&gt;
&lt;h3&gt;- Redux Toolkit이란&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Redux를 사용할 때 더 쉽게 사용할 수 있도록 도와주는 라이브러리입니다.&lt;/li&gt;
&lt;li&gt;Redux Toolkit을 사용하면 Redux를 더 쉽게 사용할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;- createSlice란&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Redux Toolkit에서 제공하는 함수로, 액션 생성자 함수와 리듀서 함수를 한 번에 생성합니다.&lt;/li&gt;
&lt;li&gt;이를 통해 코드의 반복을 줄이고, 코드를 더 간결하게 작성할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;- Redux Toolkit 사용하기&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm install @reduxjs/toolkit
yarn add @reduxjs/toolkit&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;// src/store/slices/todoSlice.js
import { createSlice } from &amp;#39;@reduxjs/toolkit&amp;#39;

// 초기 상태
const initialState = {
    todos: [
        {
            id: 1,
            isDone: false,
            task: &amp;#39;고양이 밥주기&amp;#39;,
            createdDate: new Date().getTime(),
        },
        {
            id: 2,
            isDone: false,
            task: &amp;#39;감자 캐기&amp;#39;,
            createdDate: new Date().getTime(),
        },
        {
            id: 3,
            isDone: false,
            task: &amp;#39;고양이 놀아주기&amp;#39;,
            createdDate: new Date().getTime(),
        },
    ],
}

// todoSlice 함수를 사용하여 `slice`를 생성합니다.
// slice는 액션 생성자 함수와 리듀서 함수를 한 번에 생성합니다.
export const todoSlice = createSlice({
    name: &amp;#39;todo&amp;#39;, // 슬라이스 이름
    initialState, // 초기 상태
    // 객체 형태로 리듀서 함수를 정의합니다.
    reducers: {
        addTodo: (state, action) =&amp;gt; {
            // state.todos 배열에 새로운 할 일을 추가합니다.
            // action.payload에는 새로운 할 일이 들어있습니다.
            state.todos.push(action.payload) // 새로운 할 일을 추가합니다.
        },
        updateTodo: (state, action) =&amp;gt; {
            // state.todos 배열에서 id가 일치하는 할 일을 찾습니다.
            // action.payload에는 할 일의 id가 들어있습니다.
            // 삼항 연산자를 사용하여 isDone 값을 반전시킵니다.
            state.todos = state.todos.map((todo) =&amp;gt;
                todo.id === action.payload ? { ...todo, isDone: !todo.isDone } : todo,
            )
        },
        deleteTodo: (state, action) =&amp;gt; {
            state.todos = state.todos.filter((todo) =&amp;gt; todo.id !== action.payload)
        },
    },
})

// 액션 생성자 함수 내보내기
export const { addTodo, updateTodo, deleteTodo } = todoSlice.actions

// todoSlice.reducer 내보내기
export default todoSlice.reducer&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;// src/App.js
// Redux를 사용하기 위해 불필요한 TodoProvider 컴포넌트 삭제
// Redux의 Provider 컴포넌트로 감싸줍니다.
import React, { useReducer, useState } from &amp;#39;react&amp;#39;
import TodoHd from &amp;#39;./components/TodoHd&amp;#39;
import TodoEditor from &amp;#39;./components/TodoEditor&amp;#39;
import TodoList from &amp;#39;./components/TodoList&amp;#39;
import { Provider } from &amp;#39;react-redux&amp;#39;
import { configureStore } from &amp;#39;@reduxjs/toolkit&amp;#39;
import todoSlice from &amp;#39;../../store/slices/todoSlice&amp;#39;

// Redux store 생성
// configureStore 함수를 사용하여 Redux store를 생성합니다.
const store = configureStore({
    reducer: {
        todo: todoSlice,
    },
})

const Todo = () =&amp;gt; {
    return (
        &amp;lt;Provider store={store}&amp;gt;
            &amp;lt;TodoHd /&amp;gt;
            &amp;lt;TodoEditor /&amp;gt;
            &amp;lt;TodoList /&amp;gt;
        &amp;lt;/Provider&amp;gt;
    )
}

export default Todo&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;// src/components/TodoEditor.js
// Redux와의 연동을 위해 더 이상 useTodoContext 훅 사용하지 않음
// 액션 함수를 디스패치하기 위해 useDispatch 훅을 사용
// TodoEditor 컴포넌트에서는 더 이상 useState 훅을 사용하여 상태를 관리하지 않음

import React, { useState } from &amp;#39;react&amp;#39;
import { useDispatch } from &amp;#39;react-redux&amp;#39;
import { addTodo } from &amp;#39;../../../store/slices/todoSlice&amp;#39; // todoSlice에서 액션 함수 가져오기

export default function TodoEditor() {
    const [task, setTask] = useState(&amp;#39;&amp;#39;)
    const dispatch = useDispatch() // useDispatch 훅으로 디스패치 함수 가져오기

    const onSubmit = () =&amp;gt; {
        if (!task) return
        dispatch(
            addTodo({
                id: Date.now(),
                isDone: false,
                task,
                createdDate: new Date().getTime(),
            }),
        ) // 액션 함수 디스패치
        setTask(&amp;#39;&amp;#39;)
    }

    const onKeyDown = (e) =&amp;gt; {
        if (e.key === &amp;#39;Enter&amp;#39;) {
            onSubmit()
        }
    }

    return (
        &amp;lt;div&amp;gt;
            &amp;lt;h3&amp;gt;새로운 Todo 작성하기 ✏&amp;lt;/h3&amp;gt;
            &amp;lt;div&amp;gt;
                &amp;lt;input
                    type=&amp;quot;text&amp;quot;
                    ref={(inputRef) =&amp;gt; inputRef &amp;amp;&amp;amp; inputRef.focus()}
                    placeholder=&amp;quot;할 일을 추가로 입력해주세요.&amp;quot;
                    onChange={(e) =&amp;gt; setTask(e.target.value)}
                    onKeyDown={onKeyDown}
                    value={task}
                /&amp;gt;
                &amp;lt;button onClick={onSubmit}&amp;gt;할 일 추가&amp;lt;/button&amp;gt;
            &amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;gt;
    )
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;// src/components/TodoList.js
// Redux와의 연동을 위해 useSelector 훅을 사용하여 상태를 가져오기
// 액션 함수를 디스패치하기 위해 useDispatch 훅을 사용
import React, { useState } from &amp;#39;react&amp;#39;
import TodoItem from &amp;#39;./TodoItem&amp;#39;
import { useSelector } from &amp;#39;react-redux&amp;#39; // useDispatch 대신 useSelector 사용

function TodoList() {
    const [search, setSearch] = useState(&amp;#39;&amp;#39;)
    // useSelector에서 state로 전달되는 객체는 rootReducer에서 정의한 객체입니다.
    // rootReducer에서 todoReducer를 todo라는 이름으로 사용했기 때문에 state.todo로 todo 상태를 가져옵니다.
    // 따라서 state.todo.todos로 todo 상태를 가져올 수 있습니다.
    const todo = useSelector((state) =&amp;gt; state.todo.todos) // todo 상태를 가져옵니다.

    const onChangeSearch = (e) =&amp;gt; {
        setSearch(e.target.value)
    }

    const filteredTodo = () =&amp;gt; {
        // todo 상태가 배열인지 확인하고 배열 메서드를 사용하기
        if (Array.isArray(todo)) {
            return todo.filter((item) =&amp;gt; item.task.toLowerCase().includes(search.toLowerCase()))
        } else {
            return []
        }
    }

    return (
        &amp;lt;div&amp;gt;
            &amp;lt;h3&amp;gt;할 일 목록  &amp;lt;/h3&amp;gt;
            &amp;lt;input type=&amp;quot;text&amp;quot; placeholder=&amp;quot;검색어를 입력하세요&amp;quot; onChange={onChangeSearch} value={search} /&amp;gt;

            &amp;lt;div&amp;gt;
                {filteredTodo().map((item) =&amp;gt; (
                    &amp;lt;TodoItem key={item.id} {...item} /&amp;gt;
                ))}
            &amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;gt;
    )
}

export default TodoList&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;// src/components/TodoItem.js
// Redux와의 연동을 위해 useDispatch 훅을 사용하여 액션 함수 디스패치
import React from &amp;#39;react&amp;#39;
import { format } from &amp;#39;date-fns&amp;#39;
import { useDispatch } from &amp;#39;react-redux&amp;#39;
import { updateTodo, deleteTodo } from &amp;#39;../../../store/slices/todoSlice&amp;#39; // todoSlice에서 액션 함수 가져오기

function TodoItem({ id, isDone, task, createdDate }) {
    const dispatch = useDispatch()

    return (
        &amp;lt;li key={id}&amp;gt;
            &amp;lt;input type=&amp;quot;checkbox&amp;quot; checked={isDone} onChange={() =&amp;gt; dispatch(updateTodo(id))} /&amp;gt;
            &amp;lt;strong style={{ textDecoration: isDone ? &amp;#39;line-through&amp;#39; : &amp;#39;none&amp;#39; }}&amp;gt;{task}&amp;lt;/strong&amp;gt;
            &amp;lt;span&amp;gt;{format(new Date(createdDate), &amp;#39;yyyy.MM.dd&amp;#39;)}&amp;lt;/span&amp;gt;
            &amp;lt;button onClick={() =&amp;gt; dispatch(deleteTodo(id))}&amp;gt;삭제&amp;lt;/button&amp;gt;
        &amp;lt;/li&amp;gt;
    )
}

export default TodoItem&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Front/React</category>
      <category>React</category>
      <category>Redux</category>
      <category>상태관리</category>
      <author>oodada</author>
      <guid isPermaLink="true">https://oddcode.tistory.com/327</guid>
      <comments>https://oddcode.tistory.com/327#entry327comment</comments>
      <pubDate>Sat, 4 May 2024 19:16:07 +0900</pubDate>
    </item>
    <item>
      <title>[React style] chakra-ui theme 설정</title>
      <link>https://oddcode.tistory.com/326</link>
      <description>&lt;h1&gt;Chakra-ui로 스타일링하기&lt;/h1&gt;
&lt;h2&gt;0. Tailwind CSS vs Chakra UI&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/odada/post/965dea22-ae47-41f3-b928-ab0d356144e3/image.png&quot; alt=&quot;&quot;&gt;&lt;br&gt;&lt;img src=&quot;https://velog.velcdn.com/images/odada/post/3bbd43b6-1b88-44ee-8068-479ad12e4500/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h3&gt;- Tailwind CSS&lt;/h3&gt;
&lt;p&gt;Tailwind는 유틸리티 클래스를 제공하여, HTML 내에서 직접 스타일을 적용할 수 있게 해줍니다. 이는 빠르게 프로토타이핑을 할 수 있게 해주며, CSS 파일을 별도로 관리할 필요가 없습니다.&lt;/p&gt;
&lt;h3&gt;- Chakra UI&lt;/h3&gt;
&lt;p&gt;Chakra UI는 컴포넌트를 제공하여, 컴포넌트를 사용해 스타일을 적용할 수 있게 해줍니다. 이는 컴포넌트를 사용해 스타일을 적용할 수 있어, 컴포넌트를 재사용하기 용이하며, 테마 설정을 통해 쉽게 스타일을 적용할 수 있습니다.&lt;/p&gt;
&lt;h3&gt;- Tailwind CSS와 Chakra UI를 함께 사용&lt;/h3&gt;
&lt;p&gt;Tailwind CSS와 Chakra UI를 함께 사용하여, Tailwind CSS의 유틸리티 클래스를 사용하고, Chakra UI의 컴포넌트를 사용할 수 있습니다.&lt;/p&gt;
&lt;h3&gt;- 일반적인 회사에서 사용하기 쉬운 라이브러리 선택&lt;/h3&gt;
&lt;p&gt;일반적인 회사에서 사용하기에 적합한 라이브러리를 선택할 때 고려해야 할 요소는 다음과 같습니다:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;프로젝트의 규모와 복잡성&lt;/strong&gt;: 작고 간단한 프로젝트에는 Tailwind CSS와 같은 유틸리티 우선의 접근법이 더 효과적일 수 있습니다. 반면, 대규모 프로젝트나 애플리케이션에는 Chakra UI와 같은 컴포넌트 기반 라이브러리가 더 적합할 수 있습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;팀의 경험과 선호도&lt;/strong&gt;: 팀 구성원들이 컴포넌트 기반의 개발에 익숙하다면 Chakra UI가 더 적합할 수 있으며, 유틸리티 클래스와 직접적인 스타일링에 익숙하다면 Tailwind CSS가 더 나을 수 있습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;맞춤 디자인 시스템의 필요성&lt;/strong&gt;: 프로젝트가 매우 특정한 디자인 시스템이나 광범위한 맞춤 스타일링을 필요로 한다면, Tailwind CSS가 더 유연한 선택이 될 수 있습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;결론적으로, 각 회사의 프로젝트 요구사항, 팀의 스킬셋, 그리고 개발 속도와 유지 보수의 용이성에 따라 적합한 라이브러리를 선택해야 합니다.&lt;/p&gt;
&lt;h2&gt;1. Chakra UI 란?&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/odada/post/038dcbb4-cef8-4030-a2df-f6418f9173ea/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://v2.chakra-ui.com/&quot;&gt;Chakra UI&lt;/a&gt;는 React용 디자인 시스템을 제공하는 라이브러리입니다. Chakra UI는 컴포넌트 기반의 스타일링을 제공하며, 테마 설정을 통해 쉽게 스타일을 적용할 수 있습니다.&lt;/p&gt;
&lt;p&gt;Chakra UI의 주요 특징은 다음과 같습니다:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;컴포넌트 기반 스타일링&lt;/strong&gt;: Chakra UI는 다양한 컴포넌트를 제공하며, 이를 사용해 스타일을 적용할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;테마 설정&lt;/strong&gt;: Chakra UI는 테마 설정을 통해 쉽게 스타일을 적용할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;반응형 디자인&lt;/strong&gt;: Chakra UI는 반응형 디자인을 지원하며, 다양한 디바이스에 대응할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;접근성&lt;/strong&gt;: Chakra UI는 접근성을 고려한 컴포넌트를 제공하며, 웹 접근성을 향상시킬 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;1. Chakra-ui 설치&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Chakra-ui는 React용 디자인 시스템을 제공하는 라이브러리이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm install @chakra-ui/react @emotion/react @emotion/styled framer-motion
yarn add @chakra-ui/react @emotion/react @emotion/styled framer-motion&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;2. Chakra-ui 테마 설정&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Chakra-ui의 테마를 설정할 수 있다.&lt;br&gt;&lt;a href=&quot;https://v2.chakra-ui.com/docs/styled-system/customize-theme&quot;&gt;https://v2.chakra-ui.com/docs/styled-system/customize-theme&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;foundations (디자인 시스템의 기반 요소들을 담당)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;색상(Color): 프로젝트 전체에서 사용되는 색상 팔레트를 정의합니다.&lt;/li&gt;
&lt;li&gt;타이포그래피(Typography): 글꼴, 글자 크기, 줄 높이 등의 기본 텍스트 스타일을 설정합니다.&lt;/li&gt;
&lt;li&gt;공간(Spacing): 마진, 패딩 등의 공간을 일관되게 관리할 수 있는 공간 체계를 정의합니다.&lt;/li&gt;
&lt;li&gt;경계선 스타일(Border Styles): 예를 들어 borders.js에서는 테두리의 두께, 스타일, 색상 등을 정의할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;components (특정 UI 컴포넌트의 스타일을 정의하는데 사용)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Button: button.js 파일에서는 버튼 컴포넌트의 모양, 크기, 색상 변형 등을 정의하고 조정할 수 있습니다.&lt;/li&gt;
&lt;li&gt;Input, Card, Modal 등 다른 컴포넌트들도 각각의 파일에서 커스텀 스타일을 지정할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;- theme 디렉토리 생성&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;src&lt;/code&gt; 디렉토리에 &lt;code&gt;theme&lt;/code&gt; 디렉토리를 생성한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;|-- theme
    |-- index.js
    |-- styles.js
    |-- foundations
        |-- borders.js
    |-- components
        |-- button.js&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- index.js&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;import { extendTheme } from &amp;#39;@chakra-ui/react&amp;#39;;
import { styles } from &amp;#39;./styles&amp;#39;;
import { buttonStyles } from &amp;#39;./components/button&amp;#39;;
import { breakpoints } from &amp;#39;./foundations/breakpoints&amp;#39;;

export default extendTheme(
    styles, 
    buttonStyles, 
    { breakpoints }
);&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- styles.js&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;// theme/chakraStyles.js
import { mode } from &amp;#39;@chakra-ui/theme-tools&amp;#39;;
import { lighten } from &amp;#39;polished&amp;#39;;

export const variablesC = {
  $maxW: &amp;#39;1280px&amp;#39;,
};

export const globalStyles = {
  colors: {
    primary: &amp;#39;#422afb&amp;#39;,
    pirmaryLight: lighten(0.1, &amp;#39;#422AFB&amp;#39;),
    secondary: &amp;#39;#83D9FB&amp;#39;,
    secondaryLight: lighten(0.1, &amp;#39;#83D9FB&amp;#39;),
    tertiary: &amp;#39;#22C55F&amp;#39;,
    tertiaryLight: lighten(0.1, &amp;#39;#22C55F&amp;#39;),

    bgDefault: &amp;#39;#F5F7FF&amp;#39;,

    lineDefault: &amp;#39;#E5E7EB&amp;#39;,

    white: &amp;#39;#FFF&amp;#39;,
    black: &amp;#39;#000&amp;#39;,

    gray: {
      50: &amp;#39;#F9FAFB&amp;#39;,
      100: &amp;#39;#F3F4F6&amp;#39;,
      200: &amp;#39;#E5E7EB&amp;#39;,
      300: &amp;#39;#D1D5DB&amp;#39;,
      400: &amp;#39;#9CA3AF&amp;#39;,
      500: &amp;#39;#6B7280&amp;#39;,
      600: &amp;#39;#4B5563&amp;#39;,
      700: &amp;#39;#374151&amp;#39;,
      800: &amp;#39;#1F2937&amp;#39;,
      900: &amp;#39;#111827&amp;#39;,
    },
  },
  radii: {
    none: &amp;#39;0&amp;#39;,
    sm: &amp;#39;8px&amp;#39;,
    md: &amp;#39;12px&amp;#39;,
    lg: &amp;#39;16px&amp;#39;,
    xl: &amp;#39;20px&amp;#39;,
    full: &amp;#39;9999px&amp;#39;,
  },
  styles: {
    global: (props) =&amp;gt; ({
      &amp;#39;body, html&amp;#39;: {
        minW: &amp;#39;330px&amp;#39;,
        fontFamily: &amp;#39;&amp;quot;DM Sans&amp;quot;, &amp;quot;sans-serif&amp;quot;&amp;#39;,
        letterSpacing: &amp;#39;-0.5px&amp;#39;,
        fontSize: &amp;#39;16px&amp;#39;,
        fontWeight: &amp;#39;400&amp;#39;,
        lineHeight: &amp;#39;1.5&amp;#39;,
        color: mode(&amp;#39;gray.900&amp;#39;, &amp;#39;white&amp;#39;)(props),
        bg: mode(&amp;#39;bgDefault&amp;#39;, &amp;#39;navy.900&amp;#39;)(props),
      },
      input: {
        color: &amp;#39;gray.700&amp;#39;,
      },
      &amp;#39;ul &amp;gt; li&amp;#39;: {
        listStyle: &amp;#39;none&amp;#39;,
      },
      &amp;#39;.blind&amp;#39;: {
        position: &amp;#39;absolute&amp;#39;,
        width: 0,
        height: 0,
        margin: -1,
        padding: 0,
        overflow: &amp;#39;hidden&amp;#39;,
        clip: &amp;#39;rect(0, 0, 0, 0)&amp;#39;,
        border: 0,
        lineHeight: 0,
      },
    }),
  },
};&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;ButtonStyles.js&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;// theme/components/button.js
import { mode } from &amp;#39;@chakra-ui/theme-tools&amp;#39;;
export const buttonStyles = {
  components: {
    Button: {
      baseStyle: {
        borderRadius: &amp;#39;0&amp;#39;,
        boxShadow: &amp;#39;45px 76px 113px 7px rgba(112, 144, 176, 0.08)&amp;#39;,
        transition: &amp;#39;.25s all ease&amp;#39;,
        boxSizing: &amp;#39;border-box&amp;#39;,
        _focus: {
          boxShadow: &amp;#39;none&amp;#39;,
        },
        _active: {
          boxShadow: &amp;#39;none&amp;#39;,
        },
      },
      variants: {
        icon: () =&amp;gt; ({
          w: &amp;#39;45px&amp;#39;,
          h: &amp;#39;45px&amp;#39;,
          minW: &amp;#39;none&amp;#39;,
          bg: &amp;#39;transparent&amp;#39;,
          color: &amp;#39;gray.500&amp;#39;,
          borderRadius: &amp;#39;50%&amp;#39;,
          _focus: {
            bg: &amp;#39;gray.100&amp;#39;,
          },
          _active: {
            bg: &amp;#39;gray.100&amp;#39;,
          },
          _hover: {
            bg: &amp;#39;gray.100&amp;#39;,
          },
        }),
      },
    },
  },
};&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;3. Chakra-ui App 적용&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;src&lt;/code&gt; 디렉토리에 &lt;code&gt;App.jsx&lt;/code&gt; 파일에 &lt;code&gt;ChakraProvider&lt;/code&gt;를 적용하고 위에서 만든 테마를 적용한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;import React from &amp;#39;react&amp;#39;;
import { ChakraProvider } from &amp;#39;@chakra-ui/react&amp;#39;;
import { BrowserRouter as Router } from &amp;#39;react-router-dom&amp;#39;;
import Routers from &amp;#39;./Routers&amp;#39;;
import theme from &amp;#39;./theme/theme&amp;#39;;

const App = () =&amp;gt; {
  return (
    &amp;lt;ChakraProvider theme={theme}&amp;gt;
      &amp;lt;Router&amp;gt;
        &amp;lt;Routers /&amp;gt;
      &amp;lt;/Router&amp;gt;
    &amp;lt;/ChakraProvider&amp;gt;
  );
};

export default App;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;4. Chakra-ui 컴포넌트 적용&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;src/views/home/Home.jsx&lt;/code&gt; 파일을 수정한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;import React from &amp;#39;react&amp;#39;;
import { Box, Button, Heading } from &amp;#39;@chakra-ui/react&amp;#39;;

const Home = () =&amp;gt; {
  return (
    &amp;lt;Box&amp;gt;
      &amp;lt;Heading&amp;gt;Home&amp;lt;/Heading&amp;gt;
      &amp;lt;Button&amp;gt;Button&amp;lt;/Button&amp;gt;
    &amp;lt;/Box&amp;gt;
  );
};&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Front/React</category>
      <author>oodada</author>
      <guid isPermaLink="true">https://oddcode.tistory.com/326</guid>
      <comments>https://oddcode.tistory.com/326#entry326comment</comments>
      <pubDate>Sat, 13 Apr 2024 22:13:33 +0900</pubDate>
    </item>
    <item>
      <title>React로 웹사이트 만들기</title>
      <link>https://oddcode.tistory.com/325</link>
      <description></description>
      <category>Front/React</category>
      <category>React</category>
      <author>oodada</author>
      <guid isPermaLink="true">https://oddcode.tistory.com/325</guid>
      <comments>https://oddcode.tistory.com/325#entry325comment</comments>
      <pubDate>Fri, 5 Apr 2024 21:52:09 +0900</pubDate>
    </item>
    <item>
      <title>React 스타일링 (styled-components, styled-reset, tailwind), 반응형 구현하기 - React 배우기</title>
      <link>https://oddcode.tistory.com/324</link>
      <description>&lt;h2&gt;1. styled-reset&lt;/h2&gt;
&lt;p&gt;styled-reset은 CSS 초기화 라이브러리로, 브라우저별 기본 스타일을 초기화할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/zacanger/styled-reset#readme&quot;&gt;styled-reset 사이트&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;- 설치&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm install styled-reset
yarn add styled-reset&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- 사용법&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;// src/App.js
import React from &amp;#39;react&amp;#39;
import Routers from &amp;#39;./components/Routers&amp;#39;
import { Reset } from &amp;#39;styled-reset&amp;#39;

const App = () =&amp;gt; {
    return (
        &amp;lt;&amp;gt;
            &amp;lt;Reset /&amp;gt; {/* 초기화 */}
            &amp;lt;Routers /&amp;gt;
        &amp;lt;/&amp;gt;
    )
}

export default App&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;2. styled-components&lt;/h2&gt;
&lt;p&gt;styled-components는 CSS in JS 라이브러리로, JavaScript 파일 안에 CSS를 작성할 수 있습니다. 이를 통해 컴포넌트 스타일링을 더욱 편리하게 할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://styled-components.com/&quot;&gt;styled-components 사이트&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;- 설치&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm install styled-components
yarn add styled-components&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- 사용법&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;styled&lt;/code&gt; 함수를 사용해 스타일을 정의하고, 컴포넌트를 생성합니다. 생성한 스타일드 컴포넌트는 일반 컴포넌트처럼 사용할 수 있습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;// src/components/layout/Footer.jsx
import { Link } from &amp;#39;react-router-dom&amp;#39;
import styled from &amp;#39;styled-components&amp;#39;

const Footer = () =&amp;gt; {
    return (
        &amp;lt;FooterWrap&amp;gt;
            &amp;lt;CorpList&amp;gt;
                &amp;lt;li&amp;gt;
                    &amp;lt;Link to=&amp;quot;/&amp;quot;&amp;gt;이용약관&amp;lt;/Link&amp;gt;
                &amp;lt;/li&amp;gt;
                &amp;lt;li&amp;gt;
                    &amp;lt;Link to=&amp;quot;/&amp;quot;&amp;gt;개인정보처리방침&amp;lt;/Link&amp;gt;
                &amp;lt;/li&amp;gt;
                &amp;lt;li&amp;gt;
                    &amp;lt;Link to=&amp;quot;/&amp;quot;&amp;gt;고객센터&amp;lt;/Link&amp;gt;
                &amp;lt;/li&amp;gt;
            &amp;lt;/CorpList&amp;gt;
            &amp;lt;div className=&amp;quot;mt-10 text-gray-600&amp;quot;&amp;gt;Copyright © company. All rights reserved.&amp;lt;/div&amp;gt;
        &amp;lt;/FooterWrap&amp;gt;
    )
}

const FooterWrap = styled.footer`
    display: flex;
    flex-direction: column;
    align-items: center;
    padding: 2rem;
    background-color: lightblue;
`

const CorpList = styled.ul`
    display: flex;
    li {
        &amp;amp;::before {
            content: &amp;#39;|&amp;#39;;
            margin: 0 2px;
        }
        &amp;amp;:first-child::before {
            content: &amp;#39;&amp;#39;;
        }
    }
    a {
        color: red;
        padding: 2rem;
    }
`

export default Footer&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- GlobalStyle 적용&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;createGlobalStyle&lt;/code&gt; 함수를 사용해 전역 스타일을 적용할 수 있습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;google font 적용하여 &lt;code&gt;noto sans kr&lt;/code&gt; 폰트를 사용합니다.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://fonts.google.com/&quot;&gt;구글폰트 사이트&lt;/a&gt;에서 폰트를 찾아 링크를 가져옵니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;link href=&amp;quot;https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@100..900&amp;amp;display=swap&amp;quot; rel=&amp;quot;stylesheet&amp;quot; /&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;//theme/globalStyles.js
import { createGlobalStyle } from &amp;#39;styled-components&amp;#39;

const GlobalStyle = createGlobalStyle`
    * {
        box-sizing: border-box;
    }
    body {
        font-size: 16px;
        line-height: 1.5;
        font-family: &amp;#39;Noto Sans KR&amp;#39;, &amp;#39;Malgun Gothic&amp;#39;, &amp;#39;맑은 고딕&amp;#39;, sans-serif;
        color: $txtDefult;
    }

    /* desktop */
    @media screen and (min-width: 1280px) {
        body {
            font-size: 16px;
        }
    }

    hr {
        display: none;
    }

    input[type=&amp;#39;password&amp;#39;] {
        font-family: &amp;#39;Malgun Gothic&amp;#39;;
    }

    /* 웹폰트 사용시 점이 안보임 - 기본 폰트 사용  */
    /* skipnavigation */

    #u-skip {
        position: relative;
        z-index: 3000;

        a {
            position: absolute;
            top: -35px;
            left: 0;
            right: 0;
            display: inline-block;
            padding: 7px 10px 5px;
            background: #333;
            color: #fff;
            text-decoration: none;
            text-align: center;
            opacity: 0;

            &amp;amp;:focus {
                top: 0;
                opacity: 1;
                z-index: 1000;
            }
        }
    }
    .blind, caption, legend {
        overflow: hidden;
        position: absolute;
        width: 0;
        height: 0;
        line-height: 0;
        text-indent: -9999px;
    }
`

export default GlobalStyle&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;// src/App.js
import React from &amp;#39;react&amp;#39;
import Routers from &amp;#39;./Routers&amp;#39;
import { Reset } from &amp;#39;styled-reset&amp;#39;
import GlobalStyle from &amp;#39;./theme/globalStyles&amp;#39;

const App = () =&amp;gt; {
    return (
        &amp;lt;&amp;gt;
            &amp;lt;Reset /&amp;gt;
            &amp;lt;GlobalStyle /&amp;gt;
            &amp;lt;Routers /&amp;gt;
        &amp;lt;/&amp;gt;
    )
}

export default App&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- ThemeProvider 사용&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;ThemeProvider&lt;/code&gt; 컴포넌트를 사용해 테마를 전역으로 적용할 수 있습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;//theme/theme.js
const theme = {
    colors: {
        primary: &amp;#39;#0041da&amp;#39;,
        secondary: &amp;#39;#ffcd00&amp;#39;,
        red: &amp;#39;#ff0000&amp;#39;,
        white: &amp;#39;#fff&amp;#39;,
        black: &amp;#39;#000&amp;#39;,
        txtDefault: &amp;#39;#222&amp;#39;,
        gray: {
            100: &amp;#39;#f7fafc&amp;#39;,
            200: &amp;#39;#edf2f7&amp;#39;,
            300: &amp;#39;#e2e8f0&amp;#39;,
            400: &amp;#39;#cbd5e0&amp;#39;,
            500: &amp;#39;#a0aec0&amp;#39;,
            600: &amp;#39;#718096&amp;#39;,
            700: &amp;#39;#4a5568&amp;#39;,
            800: &amp;#39;#2d3748&amp;#39;,
            900: &amp;#39;#1a202c&amp;#39;,
        },
    },
    breakpoints: {
        sm: &amp;#39;480px&amp;#39;,
        md: &amp;#39;768px&amp;#39;,
        lg: &amp;#39;992px&amp;#39;,
        xl: &amp;#39;1280px&amp;#39;,
        xxl: &amp;#39;1440px&amp;#39;,
    },
}

export default theme&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;// src/App.js
import React from &amp;#39;react&amp;#39;
import { ThemeProvider } from &amp;#39;styled-components&amp;#39;
import Routers from &amp;#39;./Routers&amp;#39;
import { Reset } from &amp;#39;styled-reset&amp;#39;
import GlobalStyle from &amp;#39;./theme/globalStyles&amp;#39;
import theme from &amp;#39;./theme/theme&amp;#39;

const App = () =&amp;gt; {
    return (
        &amp;lt;ThemeProvider theme={theme}&amp;gt;
            &amp;lt;Reset /&amp;gt;
            &amp;lt;GlobalStyle /&amp;gt;
            &amp;lt;Routers /&amp;gt;
        &amp;lt;/ThemeProvider&amp;gt;
    )
}

export default App&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;// src/components/layout/Footer.jsx
import styled from &amp;#39;styled-components&amp;#39;

(...)

const FooterWrap = styled.footer`
    (...)
    background-color: ${(props) =&amp;gt; props.theme.colors.gray[100]};
`
const CorpList = styled.ul`
    (...)
    a {
        color: ${(props) =&amp;gt; props.theme.colors.secondary};
        padding: 2rem;
    }
`&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- 공용 Button 컴포넌트&lt;/h3&gt;
&lt;p&gt;사이트 전체에서 사용할 Button 컴포넌트를 만들어 사용할 수 있습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;// src/components/common/Button.jsx
import styled from &amp;#39;styled-components&amp;#39;

const Button = styled.button`
    padding: 10px 20px;
    border: none;
    background-color: ${(props) =&amp;gt; (props.primary ? props.theme.colors.black : props.theme.colors.white)};
    color: ${(props) =&amp;gt; (props.primary ? props.theme.colors.white : props.theme.colors.black)};
    cursor: pointer;
    &amp;amp;:hover {
        opacity: 0.8;
    }
`

// Button 컴포넌트를 상속받아서 GhostButton 컴포넌트를 만들 수 있습니다.
export const GhostButton = styled(Button)`
    background-color: transparent;
    color: ${(props) =&amp;gt; (props.black ? &amp;#39;black&amp;#39; : &amp;#39;white&amp;#39;)};
`

export default Button&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;// src/components/layout/Header.jsx
import React from &amp;#39;react&amp;#39;
import styled from &amp;#39;styled-components&amp;#39;
import Button from &amp;#39;../common/Button&amp;#39;

const Header = () =&amp;gt; {
    return (
        &amp;lt;HeaderWrap&amp;gt;
            (...)
            &amp;lt;div className=&amp;quot;util&amp;quot;&amp;gt;
                &amp;lt;Button primary&amp;gt;마이페이지&amp;lt;/Button&amp;gt;
                &amp;lt;Button&amp;gt;장바구니&amp;lt;/Button&amp;gt;
                &amp;lt;GhostButton&amp;gt;로그인&amp;lt;/GhostButton&amp;gt;
            &amp;lt;/div&amp;gt;
        &amp;lt;/HeaderWrap&amp;gt;
    )
}

export default Header&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;3. tailwind CSS&lt;/h2&gt;
&lt;p&gt;tailwind CSS는 CSS 프레임워크로, 클래스 이름을 통해 스타일을 적용할 수 있습니다. 반응형 디자인을 쉽게 구현할 수 있고 빠르게 스타일을 적용할 수 있습니다. 카카오에서 사용하고 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://tailwindcss.com/&quot;&gt;tailwind CSS 사이트 바로가기&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;- 설치&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://tailwindcss.com/docs/guides/create-react-app&quot;&gt;react tailwind 설치하기&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm install -D tailwindcss
npx tailwindcss init

yarn add -D tailwindcss
npx tailwindcss init&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- 설정&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;tailwind.config.js&lt;/code&gt; 파일 생성&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;/** @type {import(&amp;#39;tailwindcss&amp;#39;).Config} */
module.exports = {
    content: [&amp;#39;./src/**/*.{js,jsx,ts,jsx}&amp;#39;],
    theme: {
        extend: {},
    },
    plugins: [],
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;/src/style/&lt;/code&gt; 내에 &lt;code&gt;style.css&lt;/code&gt; 파일 생성&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;/* ./src/style/style.css */
@tailwind base;
@tailwind components;
@tailwind utilities;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;@tailwind&lt;/code&gt;에 오렌지색 지렁이가 나오면&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;vscode의 extension에서&lt;ul&gt;
&lt;li&gt;PostCSS Language Support 플러그인을 설치&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dshFEo/btsFWzN86kH/kPDxCKTdxQfiRWT3fK0h2k/img.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;style.css&lt;/code&gt; 파일을 &lt;code&gt;App.js&lt;/code&gt; 파일에서 import&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;// src/App.js
import React from &amp;#39;react&amp;#39;
import &amp;#39;./style/style.css&amp;#39; // tailwind CSS 적용

const App: React.FC = () =&amp;gt; {
    return &amp;lt;&amp;gt;(...)&amp;lt;/&amp;gt;
}

export default App&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;tailwind.config.js&lt;/code&gt; 파일에서 테마 설정&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://tailwindcss.com/docs/theme&quot;&gt;tailwind 테마 설정 Doc&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;//tailwind.config.js
/** @type {import(&amp;#39;tailwindcss&amp;#39;).Config} */
module.exports = {
    content: [&amp;#39;./src/**/*.{js,jsx,ts,jsx}&amp;#39;],
    plugins: [],
    theme: {
        screens: {
            sm: &amp;#39;480px&amp;#39;,
            md: &amp;#39;768px&amp;#39;,
            lg: &amp;#39;976px&amp;#39;,
            xl: &amp;#39;1440px&amp;#39;,
        },
        colors: {
            primary: &amp;#39;red&amp;#39;,
            secondary: &amp;#39;skyblue&amp;#39;,
            gray: {
                100: &amp;#39;#f7fafc&amp;#39;,
                200: &amp;#39;#edf2f7&amp;#39;,
                300: &amp;#39;#e2e8f0&amp;#39;,
                400: &amp;#39;#cbd5e0&amp;#39;,
                500: &amp;#39;#a0aec0&amp;#39;,
                600: &amp;#39;#718096&amp;#39;,
                700: &amp;#39;#4a5568&amp;#39;,
                800: &amp;#39;#2d3748&amp;#39;,
                900: &amp;#39;#1a202c&amp;#39;,
            },
        },
        extend: {},
    },
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- 사용&lt;/h3&gt;
&lt;p&gt;&amp;#39;Header.jsx&amp;#39; 파일에서 tailwind CSS 적용&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;// src/components/layout/Header.jsx
import React from &amp;#39;react&amp;#39;

const Header = () =&amp;gt; {
    return (
        &amp;lt;header className=&amp;quot;flex items-center justify-between p-4 bg-gray-200&amp;quot;&amp;gt;
            &amp;lt;h1 className=&amp;quot;text-3xl font-bold underline &amp;quot;&amp;gt;Header&amp;lt;/h1&amp;gt;
            &amp;lt;nav id=&amp;quot;gnb&amp;quot;&amp;gt;
                &amp;lt;ul className=&amp;quot;flex gap-4&amp;quot;&amp;gt;
                    &amp;lt;li className=&amp;quot;text-primary&amp;quot;&amp;gt;
                        &amp;lt;Link to=&amp;quot;/&amp;quot;&amp;gt;Home&amp;lt;/Link&amp;gt;
                    &amp;lt;/li&amp;gt;
                    &amp;lt;li className=&amp;quot;text-primary&amp;quot;&amp;gt;
                        &amp;lt;Link to=&amp;quot;/about&amp;quot;&amp;gt;About&amp;lt;/Link&amp;gt;
                    &amp;lt;/li&amp;gt;
                    &amp;lt;li className=&amp;quot;text-primary&amp;quot;&amp;gt;
                        &amp;lt;Link to=&amp;quot;/login&amp;quot;&amp;gt;login&amp;lt;/Link&amp;gt;
                    &amp;lt;/li&amp;gt;
                &amp;lt;/ul&amp;gt;
            &amp;lt;/nav&amp;gt;

            &amp;lt;div className=&amp;quot;util&amp;quot;&amp;gt;
                &amp;lt;Button primary&amp;gt;마이페이지&amp;lt;/Button&amp;gt;
                &amp;lt;Button&amp;gt;장바구니&amp;lt;/Button&amp;gt;
                {/* &amp;lt;GhostButton&amp;gt;로그인&amp;lt;/GhostButton&amp;gt; */}
            &amp;lt;/div&amp;gt;
        &amp;lt;/header&amp;gt;
    )
}

export default Header&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;4. twin.macro&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;twin.macro는 tailwind CSS를 styled-components와 함께 사용할 수 있도록 도와주는 라이브러리입니다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;&lt;a href=&quot;https://github.com/ben-rogerson/twin.macro&quot;&gt;twin.macro 사이트 바로가기&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;- 설치&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;// styled-components
npm install twin.macro styled-components
npm install -D @types/styled-components
npm install -D tailwind-styled-components

// babel-plugin
npm install -D babel-plugin-macross

yarn add twin.macro styled-components
yarn add -D @types/styled-components
yarn add -D tailwind-styled-components
yarn add -D babel-plugin-macross&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- 설정 (안해도 될 수 있으니 우선 적용해보고 안되면 설정)&lt;/h3&gt;
&lt;p&gt;babelMacro를 사용하기 위해 &lt;code&gt;babel-plugin-macros&lt;/code&gt;를 설정해야 합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;// babel-plugin-macros.config.js
module.exports = {
    twin: {
        preset: &amp;#39;styled-components&amp;#39;,
    },
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;package.json&lt;/code&gt; 파일에 &lt;code&gt;babel-plugin-macros&lt;/code&gt; 설정을 추가합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;// package.json
&amp;quot;babelMacros&amp;quot;: {
    &amp;quot;twin&amp;quot;: {
        &amp;quot;preset&amp;quot;: &amp;quot;styled-components&amp;quot;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- 사용&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;tw&lt;/code&gt; 함수를 사용해 tailwind CSS 클래스를 적용할 수 있습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;// src/components/layout/Header.jsx
import { Link } from &amp;#39;react-router-dom&amp;#39;
import tw from &amp;#39;twin.macro&amp;#39;
import styled from &amp;#39;styled-components&amp;#39;

const Header = () =&amp;gt; {
    return (
        &amp;lt;HeaderWrap&amp;gt;
            &amp;lt;Logo&amp;gt;Header&amp;lt;/Logo&amp;gt;
            &amp;lt;nav id=&amp;quot;gnb&amp;quot;&amp;gt;
                &amp;lt;ul&amp;gt;
                    &amp;lt;li&amp;gt;
                        &amp;lt;Link to=&amp;quot;/&amp;quot;&amp;gt;Home&amp;lt;/Link&amp;gt;
                    &amp;lt;/li&amp;gt;
                    &amp;lt;li&amp;gt;
                        &amp;lt;Link to=&amp;quot;/about&amp;quot;&amp;gt;About&amp;lt;/Link&amp;gt;
                    &amp;lt;/li&amp;gt;
                    &amp;lt;li&amp;gt;
                        &amp;lt;Link to=&amp;quot;/login&amp;quot;&amp;gt;login&amp;lt;/Link&amp;gt;
                    &amp;lt;/li&amp;gt;
                &amp;lt;/ul&amp;gt;
            &amp;lt;/nav&amp;gt;

            &amp;lt;div className=&amp;quot;util&amp;quot;&amp;gt;
                &amp;lt;Button primary&amp;gt;마이페이지&amp;lt;/Button&amp;gt;
                &amp;lt;Button&amp;gt;장바구니&amp;lt;/Button&amp;gt;
                {/* &amp;lt;GhostButton&amp;gt;로그인&amp;lt;/GhostButton&amp;gt; */}
            &amp;lt;/div&amp;gt;
        &amp;lt;/HeaderWrap&amp;gt;
    )
}

const HeaderWrap = tw.header`
    flex items-center justify-between p-4 bg-gray-200
`
const Logo = tw.h1`
    text-3xl font-bold underline
`

export default Header&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;styled&lt;/code&gt; 함수를 사용해 &lt;code&gt;Gnb&lt;/code&gt; 스타일을 정의할 수 있습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;// src/components/layout/Header.jsx
import { Link } from &amp;#39;react-router-dom&amp;#39;
import tw from &amp;#39;twin.macro&amp;#39;
import styled from &amp;#39;styled-components&amp;#39;

const Header = () =&amp;gt; {
    return (
        &amp;lt;HeaderWrap&amp;gt;
            &amp;lt;Logo&amp;gt;Header&amp;lt;/Logo&amp;gt;
            &amp;lt;Gnb id=&amp;quot;gnb&amp;quot;&amp;gt;
                &amp;lt;ul&amp;gt;
                    &amp;lt;li&amp;gt;
                        &amp;lt;Link to=&amp;quot;/&amp;quot;&amp;gt;Home&amp;lt;/Link&amp;gt;
                    &amp;lt;/li&amp;gt;
                    &amp;lt;li&amp;gt;
                        &amp;lt;Link to=&amp;quot;/about&amp;quot;&amp;gt;About&amp;lt;/Link&amp;gt;
                    &amp;lt;/li&amp;gt;
                    &amp;lt;li&amp;gt;
                        &amp;lt;Link to=&amp;quot;/login&amp;quot;&amp;gt;login&amp;lt;/Link&amp;gt;
                    &amp;lt;/li&amp;gt;
                &amp;lt;/ul&amp;gt;
            &amp;lt;/Gnb&amp;gt;

            &amp;lt;div className=&amp;quot;util&amp;quot;&amp;gt;
                &amp;lt;Button primary&amp;gt;마이페이지&amp;lt;/Button&amp;gt;
                &amp;lt;Button&amp;gt;장바구니&amp;lt;/Button&amp;gt;
                {/* &amp;lt;GhostButton&amp;gt;로그인&amp;lt;/GhostButton&amp;gt; */}
            &amp;lt;/div&amp;gt;
        &amp;lt;/HeaderWrap&amp;gt;
    )
}

(...)

const Gnb = styled.nav`
    ul {
        ${tw`flex gap-4`}
        li {
            ${tw`text-primary`}
        }
    }
`

export default Header&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;- 반응형 디자인&lt;/h2&gt;
&lt;h3&gt;- tailwind CSS 반응형 클래스&lt;/h3&gt;
&lt;p&gt;tailwind CSS는 반응형 디자인을 쉽게 구현할 수 있도록 클래스를 제공합니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;sm&lt;/code&gt;: 640px 이상&lt;/li&gt;
&lt;li&gt;&lt;code&gt;md&lt;/code&gt;: 768px 이상&lt;/li&gt;
&lt;li&gt;&lt;code&gt;lg&lt;/code&gt;: 1024px 이상&lt;/li&gt;
&lt;li&gt;&lt;code&gt;xl&lt;/code&gt;: 1280px 이상&lt;/li&gt;
&lt;li&gt;&lt;code&gt;2xl&lt;/code&gt;: 1536px 이상&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;// src/components/layout/Header.jsx
import tw from &amp;#39;twin.macro&amp;#39;

const HeaderWrap = tw.header`
    flex flex-col items-center justify-between p-4 bg-gray-200
    md:flex-row
`&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- styled-components 반응형 스타일&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;styled&lt;/code&gt; 함수를 사용해 반응형 스타일을 적용할 수 있습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;// src/components/layout/Header.jsx
import styled from &amp;#39;styled-components&amp;#39;

const HeaderWrap = styled.header`
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: space-between;
    padding: 1rem;
    background-color: lightblue;

    @media screen and (min-width: 768px) {
        flex-direction: row;
    }
`&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- twin.macro 반응형 스타일&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;tw&lt;/code&gt; 함수를 사용해 반응형 스타일을 적용할 수 있습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;// src/components/layout/Header.jsx
import tw from &amp;#39;twin.macro&amp;#39;

const Gnb = styled.nav`
    ul {
        ${tw`flex flex-col gap-4 md:flex-row md:gap-8`}
        li {
            ${tw`text-primary`}
        }
    }
`&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Front/React</category>
      <category>reactstyle</category>
      <category>styled_component</category>
      <category>tailwind</category>
      <category>twin_macro</category>
      <author>oodada</author>
      <guid isPermaLink="true">https://oddcode.tistory.com/324</guid>
      <comments>https://oddcode.tistory.com/324#entry324comment</comments>
      <pubDate>Fri, 5 Apr 2024 21:49:02 +0900</pubDate>
    </item>
    <item>
      <title>쉽게 배우는 리액트(React.js) 앱 제작 실무 - 기본</title>
      <link>https://oddcode.tistory.com/pages/%E1%84%89%E1%85%B1%E1%86%B8%E1%84%80%E1%85%A6-%E1%84%87%E1%85%A2%E1%84%8B%E1%85%AE%E1%84%82%E1%85%B3%E1%86%AB-%E1%84%85%E1%85%B5%E1%84%8B%E1%85%A2%E1%86%A8%E1%84%90%E1%85%B3Reactjs-%E1%84%8B%E1%85%A2%E1%86%B8-%E1%84%8C%E1%85%A6%E1%84%8C%E1%85%A1%E1%86%A8-%E1%84%89%E1%85%B5%E1%86%AF%E1%84%86%E1%85%AE</link>
      <description>&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 350px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 64.6512%; height: 20px;&quot;&gt;리액트란 무엇인가?&lt;/td&gt;
&lt;td style=&quot;width: 35.3488%; height: 20px;&quot;&gt;&lt;a href=&quot;https://odada.me/250&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://odada.me/250&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 64.6512%; height: 20px;&quot;&gt;리액트 패키지 설치, 필수 라이브러리&lt;/td&gt;
&lt;td style=&quot;width: 35.3488%; height: 20px;&quot;&gt;&lt;a href=&quot;https://odada.me/251&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://odada.me/251&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 64.6512%; height: 20px;&quot;&gt;JSX란 무엇인가?&lt;/td&gt;
&lt;td style=&quot;width: 35.3488%; height: 20px;&quot;&gt;&lt;a href=&quot;https://odada.me/252&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://odada.me/252&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 64.6512%; height: 20px;&quot;&gt;[잠깐] React ES6 기본 문법&lt;/td&gt;
&lt;td style=&quot;width: 35.3488%; height: 20px;&quot;&gt;&lt;a href=&quot;https://odada.me/322&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://odada.me/322&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 64.6512%; height: 20px;&quot;&gt;리액트 컴포넌트(Component)&lt;/td&gt;
&lt;td style=&quot;width: 35.3488%; height: 20px;&quot;&gt;&lt;a href=&quot;https://odada.me/254&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://odada.me/254&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 64.6512%; height: 20px;&quot;&gt;리액트 속성(Props, Properties)&lt;/td&gt;
&lt;td style=&quot;width: 35.3488%; height: 20px;&quot;&gt;&lt;a href=&quot;https://odada.me/271&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://odada.me/271&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 64.6512%; height: 20px;&quot;&gt;리액트 이벤트(Event)&lt;/td&gt;
&lt;td style=&quot;width: 35.3488%; height: 20px;&quot;&gt;&lt;a href=&quot;https://odada.me/273&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://odada.me/273&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 64.6512%; height: 20px;&quot;&gt;리액트 상태(State)&lt;/td&gt;
&lt;td style=&quot;width: 35.3488%; height: 20px;&quot;&gt;&lt;a href=&quot;https://odada.me/274&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://odada.me/274&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 64.6512%; height: 20px;&quot;&gt;[실습] React Router Dom을 이용한 레이아웃 구성하기&lt;/td&gt;
&lt;td style=&quot;width: 35.3488%; height: 20px;&quot;&gt;&lt;a href=&quot;https://odada.me/256&quot;&gt;https://odada.me/256&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 64.6512%; height: 20px;&quot;&gt;[실습] styled-component, Chakra-ui, emotion, tailwind 사용하기&lt;/td&gt;
&lt;td style=&quot;width: 35.3488%; height: 20px;&quot;&gt;&lt;a href=&quot;https://odada.me/316&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://odada.me/316&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 64.6512%; height: 20px;&quot;&gt;리액트 클래스(Class)&lt;/td&gt;
&lt;td style=&quot;width: 35.3488%; height: 20px;&quot;&gt;&lt;a href=&quot;https://odada.me/247&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://odada.me/247&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 64.6512%; height: 20px;&quot;&gt;리액트 생명주기(LifeCycle), useEffect&lt;/td&gt;
&lt;td style=&quot;width: 35.3488%; height: 20px;&quot;&gt;&lt;a href=&quot;https://odada.me/313&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://odada.me/313&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 64.6512%; height: 20px;&quot;&gt;[실습] React 할 일 관리 앱 만들기&lt;/td&gt;
&lt;td style=&quot;width: 35.3488%; height: 20px;&quot;&gt;&lt;a href=&quot;https://odada.me/311&quot;&gt;https://odada.me/311&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 64.6512%; height: 18px;&quot;&gt;Context API&lt;/td&gt;
&lt;td style=&quot;width: 35.3488%; height: 18px;&quot;&gt;&lt;a href=&quot;https://odada.me/319&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://odada.me/319&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 64.6512%; height: 18px;&quot;&gt;useReducer&lt;/td&gt;
&lt;td style=&quot;width: 35.3488%; height: 18px;&quot;&gt;&lt;a href=&quot;https://odada.me/317&quot;&gt;https://odada.me/317&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 64.6512%; height: 18px;&quot;&gt;react-redux&lt;/td&gt;
&lt;td style=&quot;width: 35.3488%; height: 18px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 64.6512%;&quot;&gt;리덕스 툴킷&lt;/td&gt;
&lt;td style=&quot;width: 35.3488%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 64.6512%;&quot;&gt;Next.js&lt;/td&gt;
&lt;td style=&quot;width: 35.3488%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 64.6512%;&quot;&gt;[실습] 감정 일기장 앱 만들기&lt;/td&gt;
&lt;td style=&quot;width: 35.3488%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 64.6512%;&quot;&gt;[배포] 감정 일기장 앱 배포&lt;/td&gt;
&lt;td style=&quot;width: 35.3488%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <author>oodada</author>
      <guid isPermaLink="true">https://oddcode.tistory.com/pages/%E1%84%89%E1%85%B1%E1%86%B8%E1%84%80%E1%85%A6-%E1%84%87%E1%85%A2%E1%84%8B%E1%85%AE%E1%84%82%E1%85%B3%E1%86%AB-%E1%84%85%E1%85%B5%E1%84%8B%E1%85%A2%E1%86%A8%E1%84%90%E1%85%B3Reactjs-%E1%84%8B%E1%85%A2%E1%86%B8-%E1%84%8C%E1%85%A6%E1%84%8C%E1%85%A1%E1%86%A8-%E1%84%89%E1%85%B5%E1%86%AF%E1%84%86%E1%85%AE</guid>
      <pubDate>Fri, 29 Mar 2024 12:55:43 +0900</pubDate>
    </item>
    <item>
      <title>리액트 ES6 문법 정리 - 기본편</title>
      <link>https://oddcode.tistory.com/322</link>
      <description>&lt;h1&gt;리액트 ES6 문법 정리&lt;/h1&gt;
&lt;p&gt;리액트는 ES6 문법을 사용하여 개발합니다. ES6는 자바스크립트의 표준 버전으로, 보다 간결하고 효율적인 코드 작성을 가능하게 합니다. 이번 장에서는 ES6 문법을 정리하고, 리액트에서 사용하는 주요 문법을 알아보겠습니다.&lt;/p&gt;
&lt;h2&gt;1. 변수&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;변수는 let, const 키워드를 사용하여 선언한다.&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;변수란 데이터를 담는 공간을 의미합니다. 변수를 선언할 때는 var, let, const 키워드를 사용합니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;let&lt;/strong&gt; : 재할당할 수 있는 변수를 선언할 때 사용한다. 값을 변경할 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;const&lt;/strong&gt; : 상수를 선언할 때 사용한다. 값을 변경할 수 없는 값을 명확하게 표시할 때 사용한다. 의도치 않은 값 변경을 방지한다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;var&lt;/strong&gt; : 변수를 선언할 때 사용한다. ES6 이전에 사용하던 키워드로 가급적 사용을 지양한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;// 변경 가능한
let a = 1
a = 2
console.log(a) // 2

// 변경 불가능한
const b = 1
b = 2 // TypeError: Assignment to constant variable.&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;리액트 애플리케이션에서 콘솔에 로그가 두 번 출력되는 현상&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;리액트 애플리케이션에서 콘솔에 로그가 두 번 출력되는 현상은 `React.StrictMode` 컴포넌트 때문에 발생합니다. `React.StrictMode`는 개발 모드에서만 활성화되며, 애플리케이션 내부의 잠재적 문제를 식별하기 위해 부수 효과를 포함한 일부 생명주기 메서드를 두 번씩 호출합니다. 이는 앱의 로그인, 상태 업데이트, 재렌더링 등을 검사하여 안전하지 않은 생명주기 사용, 레거시 API의 사용, 예측할 수 없는 부수 효과 등을 찾아내기 위함입니다.

코드에서 ReactDOM.createRoot(...).render(...)에 전달된 &amp;lt;React.StrictMode&amp;gt;가 이러한 행동의 원인입니다. 이 모드는 실제 프로덕션 빌드에서는 아무런 영향을 주지 않으며, 개발 중에만 추가 검사와 경고를 제공합니다. 따라서 콘솔 로그가 두 번 호출되는 것은 이중 렌더링을 통해 문제를 조기에 발견하고자 하는 의도적인 동작입니다.&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;2. 데이터 타입&lt;/h2&gt;
&lt;p&gt;리액트에서 데이터를 다루기 위해서는 자바스크립트의 기본 데이터 타입에 대한 이해가 필수적입니다. 이번 장에서는 자바스크립트의 주요 데이터 타입들을 살펴보고, 각 타입이 리액트 개발에서 어떻게 활용되는지 알아보겠습니다.&lt;/p&gt;
&lt;h3&gt;- 문자열(String)&lt;/h3&gt;
&lt;p&gt;문자열은 텍스트 데이터를 표현하는 데 사용됩니다. 리액트에서는 컴포넌트의 속성값이나 내용을 표현할 때 자주 사용됩니다. 문자열은 작은 따옴표(&amp;#39;&amp;#39;)나 큰 따옴표(&amp;quot;&amp;quot;)로 감싸서 표현합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;function Data() {
    const name = &amp;#39;리액트!&amp;#39;
    return &amp;lt;h1&amp;gt;안녕 난 {name}야!&amp;lt;/h1&amp;gt;
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- 숫자(Number)&lt;/h3&gt;
&lt;p&gt;숫자 타입은 정수나 소수 등의 숫자 데이터를 다룰 때 사용됩니다. 컴포넌트의 크기, 간격, 배열의 인덱스 등 다양한 곳에서 활용됩니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;function Data() {
    const age = 10
    return &amp;lt;p&amp;gt;{age}년 전에 출시됐어.&amp;lt;/p&amp;gt;
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- 불리언(Boolean)&lt;/h3&gt;
&lt;p&gt;불리언은 참(true)과 거짓(false)의 두 가지 값만을 가지며, 조건문이나 토글 상태 등을 다룰 때 사용됩니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;function Data() {
    const visible = true
    return {visible ? &amp;lt;p&amp;gt;보인다!&amp;lt;/p&amp;gt; : &amp;lt;p&amp;gt;안보인다!&amp;lt;/p&amp;gt;}
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- 객체(Object)&lt;/h3&gt;
&lt;p&gt;객체는 키와 값의 쌍으로 이루어진 데이터 구조로, 여러 데이터를 하나의 단위로 그룹화하여 관리할 수 있습니다. 컴포넌트의 상태(state)나 속성(props)을 다룰 때 주로 사용됩니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;function Data() {
    const user = {
        name: &amp;#39;리액트&amp;#39;,
        age: 10,
    }

    return (
        &amp;lt;p&amp;gt;
            {user.name}의 나이는 {user.age}살이야.
        &amp;lt;/p&amp;gt;
    )
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- 배열(Array)&lt;/h3&gt;
&lt;p&gt;배열은 여러 개의 데이터를 순서대로 나열한 구조입니다. 리스트나 메뉴 항목 등, 순서가 있는 데이터를 표현할 때 유용합니다.&lt;br&gt;제로 기반 인덱스를 사용하며, 배열의 첫 번째 요소는 0부터 시작합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;function Data() {
    const array = [1, 2, 3]

    return (
        &amp;lt;ul&amp;gt;
            &amp;lt;li&amp;gt;{array[0]}&amp;lt;/li&amp;gt;
            &amp;lt;li&amp;gt;{array[1]}&amp;lt;/li&amp;gt;
            &amp;lt;li&amp;gt;{array[2]}&amp;lt;/li&amp;gt;
        &amp;lt;/ul&amp;gt;

        // map 함수를 사용하여 반복문을 대신할 수 있다.
        &amp;lt;ul&amp;gt;
            {array.map((item) =&amp;gt; (
                &amp;lt;li&amp;gt;{item}&amp;lt;/li&amp;gt;
            ))}
        &amp;lt;/ul&amp;gt;
    )
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- 배열 &amp;amp; 객체&lt;/h3&gt;
&lt;p&gt;배열과 객체는 서로 중첩하여 사용할 수 있습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;function Data() {
    const users = [
        {
            id: 1,
            name: &amp;#39;봄&amp;#39;,
            age: 2,
        },
        {
            id: 2,
            name: &amp;#39;여름&amp;#39;,
            age: 3,
        },
        {
            id: 3,
            name: &amp;#39;가을&amp;#39;,
            age: 1,
        },
        {
            id: 4,
            name: &amp;#39;겨울&amp;#39;,
            age: 5,
        },
    ]

    return (
        &amp;lt;ul&amp;gt;
            &amp;lt;li&amp;gt;
                {users[0].name}이는 {users[0].age}살
            &amp;lt;/li&amp;gt;
            &amp;lt;li&amp;gt;
                {users[1].name}이는 {users[1].age}살
            &amp;lt;/li&amp;gt;
            &amp;lt;li&amp;gt;
                {users[2].name}이는 {users[2].age}살
            &amp;lt;/li&amp;gt;
        &amp;lt;/ul&amp;gt;

        // map 함수를 사용하여 반복문을 대신할 수 있다.
        &amp;lt;ul&amp;gt;
            {users.map((user) =&amp;gt; (
                &amp;lt;li key={user.id}&amp;gt;
                    {user.name}이는 {user.age}살
                &amp;lt;/li&amp;gt;
            ))}
        &amp;lt;/ul&amp;gt;
    )
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- 함수(Function)&lt;/h3&gt;
&lt;p&gt;함수는 일련의 처리를 한 덩어리로 묶어 놓은 코드의 집합입니다. 재사용성이 높고, 유지보수가 쉽다는 장점이 있습니다. 리액트에서는 컴포넌트의 생명주기 함수나 이벤트 처리 함수 등을 함수로 정의하여 사용합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;// 함수 선언식 (기명 함수)
function Func1() {
    return (
        &amp;lt;div&amp;gt;
            &amp;lt;h1&amp;gt;안녕 난 리액트야!&amp;lt;/h1&amp;gt;
            &amp;lt;p&amp;gt;리액트는 재밌어.&amp;lt;/p&amp;gt;
        &amp;lt;/div&amp;gt;
    )
}

// 함수 표현식 (익명 함수)
const Func2 = function () {
    return (
        &amp;lt;div&amp;gt;
            &amp;lt;h1&amp;gt;안녕 난 리액트야!&amp;lt;/h1&amp;gt;
            &amp;lt;p&amp;gt;리액트는 재밌어.&amp;lt;/p&amp;gt;
        &amp;lt;/div&amp;gt;
    )
}

// 화살표 함수
const Func3 = () =&amp;gt; {
    return (
        &amp;lt;div&amp;gt;
            &amp;lt;h1&amp;gt;안녕 난 리액트야!&amp;lt;/h1&amp;gt;
            &amp;lt;p&amp;gt;리액트는 재밌어.&amp;lt;/p&amp;gt;
        &amp;lt;/div&amp;gt;
    )
}

function Data() {
    return (
        &amp;lt;div&amp;gt;
            &amp;lt;Func1 /&amp;gt;
            &amp;lt;Func2 /&amp;gt;
            &amp;lt;Func3 /&amp;gt;
        &amp;lt;/div&amp;gt;
    )
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- undefined &amp;amp; null&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;undefined&lt;/code&gt;는 선언된 변수에 값이 할당되지 않았을 때의 기본값입니다. 반면, &lt;code&gt;null&lt;/code&gt;은 개발자가 명시적으로 &amp;quot;값이 없음&amp;quot;을 나타내고자 할 때 사용합니다. 이 두 타입은 데이터가 &amp;#39;없음&amp;#39;을 나타내지만, 사용 의도에 따라 구분해서 사용합니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;null : 값이 없음을 의미한다.&lt;/li&gt;
&lt;li&gt;undefined : 값이 할당되지 않은 상태를 의미한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;function Data() {
    const info = {
        birth: &amp;#39;2021-01-01&amp;#39;,
        phone: null,
    }

    console.log(info.birth) // 2021-01-01
    console.log(info.phone) // null
    console.log(info.address) // undefined

    return (
        {/* 값이 있으면 출력, 없으면 &amp;#39;주소 정보 없음&amp;#39; 출력 */}
        &amp;lt;ul&amp;gt;
            &amp;lt;li&amp;gt;{info.birth || &amp;#39;주소 정보 없음&amp;#39;}&amp;lt;/li&amp;gt;
            &amp;lt;li&amp;gt;{info.phone || &amp;#39;주소 정보 없음&amp;#39;}&amp;lt;/li&amp;gt;
            &amp;lt;li&amp;gt;{info.address || &amp;#39;주소 정보 없음&amp;#39;}&amp;lt;/li&amp;gt;
        &amp;lt;/ul&amp;gt;
    )
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- 실습 문제 1. 데이터 타입과 조건문 활용하기&lt;/h3&gt;
&lt;p&gt;자바스크립트의 기본 데이터 타입과 조건문을 사용하여 사용자의 로그인 상태를 표시하는 리액트 컴포넌트를 만드세요.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;1. `isLogged`이라는 이름의 `const` 변수를 선언하고, 불리언(`true` 또는 `false`) 값을 할당하세요.
2. `isLogged` 값에 따라 &amp;quot;로그인됨&amp;quot; 또는 &amp;quot;로그인되지 않음&amp;quot;을 표시하는 간단한 리액트 컴포넌트를 작성하세요.
3. `isLogged`이 `true`일 경우 `&amp;lt;p&amp;gt;로그인&amp;lt;/p&amp;gt;`을, `false`일 경우 `&amp;lt;p&amp;gt;로그아웃&amp;lt;/p&amp;gt;`을 반환하도록 조건문을 사용하세요.&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;3. 연산자&lt;/h2&gt;
&lt;p&gt;리액트에서 연산자는 데이터를 처리하거나, 조건부 렌더링을 구현할 때 매우 유용하게 사용됩니다. 여기에는 산술 연산자뿐만 아니라, 비교 연산자, 논리 연산자, 삼항 연산자 등이 포함됩니다. 이제 리액트 컴포넌트에서 연산자를 활용하는 몇 가지 예시를 살펴보겠습니다.&lt;/p&gt;
&lt;h3&gt;- 산술 연산자&lt;/h3&gt;
&lt;p&gt;산술 연산자는 숫자 데이터를 다룰 때 주로 사용됩니다. 예를 들어, 숫자를 증가시키거나, 감소시키는 카운터 컴포넌트를 구현할 때 활용할 수 있습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;+&lt;/code&gt; : 덧셈 연산자&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-&lt;/code&gt; : 뺄셈 연산자&lt;/li&gt;
&lt;li&gt;&lt;code&gt;*&lt;/code&gt; : 곱셈 연산자&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/&lt;/code&gt; : 나눗셈 연산자&lt;/li&gt;
&lt;li&gt;&lt;code&gt;%&lt;/code&gt; : 나머지 연산자&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;function Operator() {
    let a = 1
    let b = 2

    return (
        &amp;lt;div&amp;gt;
            &amp;lt;h2&amp;gt;산술 연산자&amp;lt;/h2&amp;gt;
            &amp;lt;p&amp;gt;
                {a} + {b} = {a + b} {/* 1 + 2 = 3 */}
            &amp;lt;/p&amp;gt;
            &amp;lt;p&amp;gt;
                {a} - {b} = {a - b} {/* 1 - 2 = -1 */}
            &amp;lt;/p&amp;gt;
            &amp;lt;p&amp;gt;
                {a} * {b} = {a * b} {/* 1 * 2 = 2 */}
            &amp;lt;/p&amp;gt;
            &amp;lt;p&amp;gt;
                {a} / {b} = {a / b} {/* 1 / 2 = 0.5 */}
            &amp;lt;/p&amp;gt;
            &amp;lt;p&amp;gt;
                {a} % {b} = {a % b} {/* 1 % 2 = 1 */}
            &amp;lt;/p&amp;gt;
        &amp;lt;/div&amp;gt;
    )
}

export default Operator&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- 증감 연산자&lt;/h3&gt;
&lt;p&gt;증감 연산자는 변수의 값을 1씩 증가시키거나 감소시킬 때 사용됩니다. 전위 연산자와 후위 연산자로 나뉘며, 변수의 값을 증가시킨 후에 반환할지, 반환한 후에 증가시킬지를 결정합니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;++&lt;/code&gt; : 변수의 값을 1 증가시킨다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--&lt;/code&gt; : 변수의 값을 1 감소시킨다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;function Operator() {
    let c = 1
    let d = 1

    return (
        &amp;lt;div&amp;gt;
            &amp;lt;h2&amp;gt;증감 연산자&amp;lt;/h2&amp;gt;
            &amp;lt;p&amp;gt;
                c = {c} {/* 1 */}
            &amp;lt;/p&amp;gt;
            &amp;lt;p&amp;gt;
                ++c = {++c} {/* 2 선연산 후출력 */}
            &amp;lt;/p&amp;gt;
            &amp;lt;p&amp;gt;
                c = {c} {/* 2 */}
            &amp;lt;/p&amp;gt;
            &amp;lt;p&amp;gt;
                d = {d} {/* 1 */}
            &amp;lt;/p&amp;gt;
            &amp;lt;p&amp;gt;
                d++ = {d++} {/* 1 선출력 후연산 */}
            &amp;lt;/p&amp;gt;
            &amp;lt;p&amp;gt;
                d = {d} {/* 2 */}
            &amp;lt;/p&amp;gt;
        &amp;lt;/div&amp;gt;
    )
}

export default Operator&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- 비교 연산자&lt;/h3&gt;
&lt;p&gt;비교 연산자는 두 값을 비교하여 참(true) 또는 거짓(false)을 반환합니다. 주로 조건문에서 사용되며, 두 값이 같은지, 다른지, 큰지, 작은지 등을 비교할 때 활용됩니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;===&lt;/code&gt; : 값과 자료형이 같은지 비교한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;!==&lt;/code&gt; : 값과 자료형이 다른지 비교한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;gt;&lt;/code&gt; : 왼쪽 값이 오른쪽 값보다 큰지 비교한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;&lt;/code&gt; : 왼쪽 값이 오른쪽 값보다 작은지 비교한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;gt;=&lt;/code&gt; : 왼쪽 값이 오른쪽 값보다 크거나 같은지 비교한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;=&lt;/code&gt; : 왼쪽 값이 오른쪽 값보다 작거나 같은지 비교한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;function Operator() {
    let e = 1
    let f = 2

    return (
        &amp;lt;div&amp;gt;
            &amp;lt;h2&amp;gt;비교 연산자&amp;lt;/h2&amp;gt;
            &amp;lt;p&amp;gt;
                {`${e} === ${f} : ${e === f}`} {/* 1 === 2 : false */}
            &amp;lt;/p&amp;gt;
            &amp;lt;p&amp;gt;
                {`${e} !== ${f} : ${e !== f}`} {/* 1 !== 2 : true */}
            &amp;lt;/p&amp;gt;
            &amp;lt;p&amp;gt;
                {`${e} &amp;gt; ${f} : ${e &amp;gt; f}`} {/* 1 &amp;gt; 2 : false */}
            &amp;lt;/p&amp;gt;
            &amp;lt;p&amp;gt;
                {`${e} &amp;lt; ${f} : ${e &amp;lt; f}`} {/* 1 &amp;lt; 2 : true */}
            &amp;lt;/p&amp;gt;
            &amp;lt;p&amp;gt;
                {`${e} &amp;gt;= ${f} : ${e &amp;gt;= f}`} {/* 1 &amp;gt;= 2 : false */}
            &amp;lt;/p&amp;gt;
            &amp;lt;p&amp;gt;
                {`${e} &amp;lt;= ${f} : ${e &amp;lt;= f}`} {/* 1 &amp;lt;= 2 : true */}
            &amp;lt;/p&amp;gt;
        &amp;lt;/div&amp;gt;
    )
}

export default Operator&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- 논리 연산자&lt;/h3&gt;
&lt;p&gt;논리 연산자는 논리적인 연산을 수행할 때 사용됩니다. 주로 조건문에서 사용되며, 두 값이 모두 참일 때 참을 반환하는 AND 연산자(&amp;amp;&amp;amp;), 두 값 중 하나라도 참일 때 참을 반환하는 OR 연산자(||), 값을 부정하는 NOT 연산자(!) 등이 있습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt; : 논리 AND 연산자&lt;/li&gt;
&lt;li&gt;&lt;code&gt;||&lt;/code&gt; : 논리 OR 연산자&lt;/li&gt;
&lt;li&gt;&lt;code&gt;!&lt;/code&gt; : 논리 NOT 연산자&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;function Operator() {
    return (
        &amp;lt;div&amp;gt;
            &amp;lt;h2&amp;gt;논리 연산자&amp;lt;/h2&amp;gt;
            &amp;lt;p&amp;gt;{`true &amp;amp;&amp;amp; true = ${true &amp;amp;&amp;amp; true}`}&amp;lt;/p&amp;gt; {/* 두 조건이 모두 참일 경우 true 반환 */}
            &amp;lt;p&amp;gt;{`true &amp;amp;&amp;amp; false = ${true &amp;amp;&amp;amp; false}`}&amp;lt;/p&amp;gt;{&amp;#39; &amp;#39;}
            {/* 두 조건 중 하나가 거짓이므로 첫 번째 거짓인 값인 false 반환 */}
            &amp;lt;p&amp;gt;{`true || false = ${true || false}`}&amp;lt;/p&amp;gt; {/* 두 조건 중 하나라도 참일 경우 true 반환 */}
            &amp;lt;p&amp;gt;{`false || false = ${false || false}`}&amp;lt;/p&amp;gt; {/* 두 조건이 모두 거짓일 경우 false 반환 */}
            &amp;lt;p&amp;gt;{`!true = ${!true}`}&amp;lt;/p&amp;gt; {/* 부정연산자 : 조건이 참일 경우 false 반환 */}
            &amp;lt;p&amp;gt;{`!false = ${!false}`}&amp;lt;/p&amp;gt; {/* 부정연산자 : 조건이 거짓일 경우 true 반환 */}
        &amp;lt;/div&amp;gt;
    )
}

export default Operator&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- 조건 연산자&lt;/h3&gt;
&lt;p&gt;조건 연산자는 조건문을 간단하게 표현할 때 사용됩니다. 조건식이 참일 때와 거짓일 때의 값을 각각 반환합니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;조건 ? 참 : 거짓&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;function Operator() {
    return (
        &amp;lt;div&amp;gt;
            &amp;lt;h2&amp;gt;조건 연산자&amp;lt;/h2&amp;gt;
            &amp;lt;p&amp;gt;{`true ? &amp;#39;참&amp;#39; : &amp;#39;거짓&amp;#39; = ${true ? &amp;#39;참&amp;#39; : &amp;#39;거짓&amp;#39;}`}&amp;lt;/p&amp;gt; {/* 조건이 참일 경우 &amp;#39;참&amp;#39; 반환 */}
            &amp;lt;p&amp;gt;{`false ? &amp;#39;참&amp;#39; : &amp;#39;거짓&amp;#39; = ${false ? &amp;#39;참&amp;#39; : &amp;#39;거짓&amp;#39;}`}&amp;lt;/p&amp;gt; {/* 조건이 거짓일 경우 &amp;#39;거짓&amp;#39; 반환 */}
        &amp;lt;/div&amp;gt;
    )
}

export default Operator&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- 전개 연산자&lt;/h3&gt;
&lt;p&gt;전개 연산자는 배열이나 객체를 복사하거나 합칠 때 사용됩니다. 배열의 경우 배열 내부의 요소를 추출하거나, 객체의 경우 객체 내부의 속성을 추출할 때 사용합니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;...&lt;/code&gt; : 전개 연산자&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;function Operator() {
    const array1 = [1, 2, 3]
    const array2 = [4, 5, 6]

    const array3 = [...array1, ...array2]

    const object1 = {
        a: 1,
        b: 2,
    }
    const object2 = {
        c: 3,
        d: 4,
    }

    const object3 = { ...object1, ...object2 }

    return (
        &amp;lt;div&amp;gt;
            &amp;lt;h2&amp;gt;전개 연산자&amp;lt;/h2&amp;gt;
            &amp;lt;p&amp;gt;{`[1, 2, 3] + [4, 5, 6] = ${array3}`}&amp;lt;/p&amp;gt;
            &amp;lt;p&amp;gt;{`{a: 1, b: 2} + {c: 3, d: 4} = ${JSON.stringify(object3)}`}&amp;lt;/p&amp;gt;{&amp;#39; &amp;#39;}
            {/* JSON.stringify() : 객체를 문자열로 변환 */}
        &amp;lt;/div&amp;gt;
    )
}

export default Operator&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;4. 함수&lt;/h2&gt;
&lt;p&gt;리액트에서 함수는 컴포넌트의 생명주기 함수, 이벤트 처리 함수, 상태 업데이트 함수 등 다양한 용도로 사용됩니다. 이번 장에서는 리액트에서 사용되는 주요 함수들을 알아보고, 각 함수의 사용법과 활용 방법을 살펴보겠습니다.&lt;/p&gt;
&lt;h3&gt;- 화살표 함수&lt;/h3&gt;
&lt;p&gt;화살표 함수는 ES6에서 도입된 새로운 함수 선언 방식으로, 함수를 간결하게 정의할 수 있습니다. 함수를 선언할 때 function 키워드 대신 =&amp;gt; 기호를 사용하여 함수를 정의합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;// 함수 선언식
function Func1() {
    return (
        &amp;lt;div&amp;gt;
            &amp;lt;h1&amp;gt;안녕 난 리액트야!&amp;lt;/h1&amp;gt;
            &amp;lt;p&amp;gt;리액트는 재밌어.&amp;lt;/p&amp;gt;
        &amp;lt;/div&amp;gt;
    )
}

// 화살표 함수
const Func2 = () =&amp;gt; {
    return (
        &amp;lt;div&amp;gt;
            &amp;lt;h1&amp;gt;안녕 난 리액트야!&amp;lt;/h1&amp;gt;
            &amp;lt;p&amp;gt;리액트는 재밌어.&amp;lt;/p&amp;gt;
        &amp;lt;/div&amp;gt;
    )
}

function Func() {
    return (
        &amp;lt;div&amp;gt;
            &amp;lt;Func1 /&amp;gt;
            &amp;lt;Func2 /&amp;gt;
        &amp;lt;/div&amp;gt;
    )
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- 화살표 함수 &amp;amp; 반환값&lt;/h3&gt;
&lt;p&gt;화살표 함수는 return 키워드를 생략할 수 있습니다. 함수 내부의 코드가 한 줄로 표현 가능할 때는 중괄호를 생략하고, 바로 반환할 수 있습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const Func1 = () =&amp;gt; (
    &amp;lt;div&amp;gt;
        &amp;lt;h1&amp;gt;안녕 난 리액트야!&amp;lt;/h1&amp;gt;
        &amp;lt;p&amp;gt;리액트는 재밌어.&amp;lt;/p&amp;gt;
    &amp;lt;/div&amp;gt;
)

const Func2 = () =&amp;gt; (
    &amp;lt;div&amp;gt;
        &amp;lt;h1&amp;gt;안녕 난 리액트야!&amp;lt;/h1&amp;gt;
        &amp;lt;p&amp;gt;리액트는 재밌어.&amp;lt;/p&amp;gt;
    &amp;lt;/div&amp;gt;
)

function Func() {
    return (
        &amp;lt;div&amp;gt;
            &amp;lt;Func1 /&amp;gt;
            &amp;lt;Func2 /&amp;gt;
        &amp;lt;/div&amp;gt;
    )
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- 화살표 함수 &amp;amp; 매개변수&lt;/h3&gt;
&lt;p&gt;화살표 함수는 매개변수를 받아서 처리할 수 있습니다. 매개변수가 하나일 때는 괄호를 생략할 수 있으며, 매개변수가 없을 때는 괄호를 생략할 수 없습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const Func1 = (name, desc) =&amp;gt; {
    return (
        &amp;lt;div&amp;gt;
            &amp;lt;h1&amp;gt;안녕 난 {name}야!&amp;lt;/h1&amp;gt;
            &amp;lt;p&amp;gt;
                {name}는 {desc}.
            &amp;lt;/p&amp;gt;
        &amp;lt;/div&amp;gt;
    )
}

const Func2 = () =&amp;gt; {
    return (
        &amp;lt;div&amp;gt;
            &amp;lt;h1&amp;gt;안녕 난 리액트야!&amp;lt;/h1&amp;gt;
            &amp;lt;p&amp;gt;리액트는 재밌어.&amp;lt;/p&amp;gt;
        &amp;lt;/div&amp;gt;
    )
}

function Func() {
    return (
        &amp;lt;div&amp;gt;
            &amp;lt;Func1 name=&amp;quot;리액트&amp;quot; desc=&amp;quot;재밌어&amp;quot; /&amp;gt;
            &amp;lt;Func2 /&amp;gt;
        &amp;lt;/div&amp;gt;
    )
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- 비구조화 할당 (Destructuring Assignment)&lt;/h3&gt;
&lt;p&gt;비구조화 할당은 ES6(ECMAScript 2015)에서 도입된 기능으로, 배열이나 객체의 속성을 해체하여 그 값을 개별 변수에 담을 수 있게 해줍니다. 함수의 매개변수로 객체를 전달할 때, 비구조화 할당을 사용하여 속성을 추출할 수 있습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const Func1 = (props) =&amp;gt; {
    // props = { name: &amp;#39;리액트&amp;#39;, desc: &amp;#39;재밌어&amp;#39; }

    return (
        &amp;lt;div&amp;gt;
            &amp;lt;h1&amp;gt;안녕 난 {props.name}야!&amp;lt;/h1&amp;gt;
            &amp;lt;p&amp;gt;
                {props.name}는 {props.desc}.
            &amp;lt;/p&amp;gt;
        &amp;lt;/div&amp;gt;
    )
}

function Func() {
    return (
        &amp;lt;div&amp;gt;
            &amp;lt;Func1 name=&amp;quot;리액트&amp;quot; desc=&amp;quot;재밌어&amp;quot; /&amp;gt;
        &amp;lt;/div&amp;gt;
    )
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;비구조화 할당을 사용하여 속성을 추출할 수 있습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const Func1 = (props) =&amp;gt; {
    const { name, desc } = props

    return (
        &amp;lt;div&amp;gt;
            &amp;lt;h1&amp;gt;안녕 난 {name}야!&amp;lt;/h1&amp;gt;
            &amp;lt;p&amp;gt;
                {name}는 {desc}.
            &amp;lt;/p&amp;gt;
        &amp;lt;/div&amp;gt;
    )
}

function Func() {
    return (
        &amp;lt;div&amp;gt;
            &amp;lt;Func1 name=&amp;quot;리액트&amp;quot; desc=&amp;quot;재밌어&amp;quot; /&amp;gt;
        &amp;lt;/div&amp;gt;
    )
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;비구조화 할당을 사용하여 속성을 추출할 수 있습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const Func1 = ({ name, desc }) =&amp;gt; {
    return (
        &amp;lt;div&amp;gt;
            &amp;lt;h1&amp;gt;안녕 난 {name}야!&amp;lt;/h1&amp;gt;
            &amp;lt;p&amp;gt;
                {name}는 {desc}.
            &amp;lt;/p&amp;gt;
        &amp;lt;/div&amp;gt;
    )
}

function Func() {
    return (
        &amp;lt;div&amp;gt;
            &amp;lt;Func1 name=&amp;quot;리액트&amp;quot; desc=&amp;quot;재밌어&amp;quot; /&amp;gt;
        &amp;lt;/div&amp;gt;
    )
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- 함수의 전개 연산자&lt;/h3&gt;
&lt;p&gt;함수의 전개 연산자를 사용하여 객체나 배열을 전달할 수 있습니다. 함수의 매개변수로 객체나 배열을 전달할 때 전개 연산자를 사용하여 속성을 추출할 수 있습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const Func7 = ({ name, desc }) =&amp;gt; {
    return (
        &amp;lt;div&amp;gt;
            &amp;lt;h1&amp;gt;안녕 난 {name}야!&amp;lt;/h1&amp;gt;
            &amp;lt;p&amp;gt;
                {name}는 {desc}.
            &amp;lt;/p&amp;gt;
        &amp;lt;/div&amp;gt;
    )
}

function Func() {
    const data = {
        name: &amp;#39;리액트&amp;#39;,
        desc: &amp;#39;재밌어&amp;#39;,
    }

    return (
        &amp;lt;div&amp;gt;
            &amp;lt;Func7 {...data} /&amp;gt;
        &amp;lt;/div&amp;gt;
    )
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;기본값 (Default Value)&lt;/h4&gt;
&lt;p&gt;매개변수에 기본값을 설정할 수 있습니다. 매개변수가 전달되지 않았을 때 기본값으로 설정된 값이 사용됩니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const Func1 = ({ name = &amp;#39;리액트&amp;#39;, desc = &amp;#39;재밌어&amp;#39; }) =&amp;gt; {
    return (
        &amp;lt;div&amp;gt;
            &amp;lt;h1&amp;gt;안녕 난 {name}야!&amp;lt;/h1&amp;gt;
            &amp;lt;p&amp;gt;
                {name}는 {desc}.
            &amp;lt;/p&amp;gt;
        &amp;lt;/div&amp;gt;
    )
}

function Func() {
    return (
        &amp;lt;div&amp;gt;
            &amp;lt;Func1 /&amp;gt;
        &amp;lt;/div&amp;gt;
    )
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;정답을 확인해보세요.&lt;/p&gt;
&lt;h3&gt;정답 1.&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;import React from &amp;#39;react&amp;#39;

// 사용자의 로그인 상태를 표시하는 리액트 컴포넌트
function UserStatus() {
    const isLogged = true // 로그인 상태를 나타내는 변수 (true 또는 false)

    return (
        &amp;lt;div&amp;gt;
            {/* isLoggedIn 값에 따라 조건부 렌더링 */}
            {isLogged ? &amp;lt;p&amp;gt;로그인&amp;lt;/p&amp;gt; : &amp;lt;p&amp;gt;로그아웃&amp;lt;/p&amp;gt;}
        &amp;lt;/div&amp;gt;
    )
}

export default UserStatus&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Front/React</category>
      <category>React</category>
      <category>react기본문법</category>
      <category>변수</category>
      <author>oodada</author>
      <guid isPermaLink="true">https://oddcode.tistory.com/322</guid>
      <comments>https://oddcode.tistory.com/322#entry322comment</comments>
      <pubDate>Fri, 29 Mar 2024 12:52:43 +0900</pubDate>
    </item>
    <item>
      <title>리액트(React.js) 커리큘럼</title>
      <link>https://oddcode.tistory.com/321</link>
      <description>&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;React 목차&lt;/p&gt;
&lt;table style=&quot;height: 730px;&quot; width=&quot;800&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;th style=&quot;height: 20px;&quot;&gt;과정&lt;/th&gt;
&lt;th style=&quot;height: 20px;&quot;&gt;주소&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;react 둘러보기&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;&lt;a href=&quot;https://ko.react.dev/&quot;&gt;https://ko.react.dev/&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;react 핵심 개념&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;&lt;a href=&quot;https://odada.me/353&quot;&gt;https://odada.me/353&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;리액트용 ES6 문법&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;&lt;a href=&quot;https://odada.me/322&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://odada.me/322&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;node.js, npm 개념 잡기&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;&lt;a href=&quot;https://odada.me/246&quot;&gt;https://odada.me/246&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;리액트란 무엇인가?&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;&lt;a href=&quot;https://odada.me/250&quot;&gt;https://odada.me/250&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;리액트 컴포넌트(Component)&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;&lt;a href=&quot;https://odada.me/254&quot;&gt;https://odada.me/254&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;리액트 속성(Props, Properties)&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;&lt;a href=&quot;https://odada.me/271&quot;&gt;https://odada.me/271&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;리액트 이벤트(Event)&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;&lt;a href=&quot;https://odada.me/273&quot;&gt;https://odada.me/273&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;[Hooks] useState&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;&lt;a href=&quot;https://odada.me/274&quot;&gt;https://odada.me/274&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;React Router Dom을 이용한 레이아웃 구성하기&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;&lt;a href=&quot;https://odada.me/256&quot;&gt;https://odada.me/256&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;[style] styled-reset, styled-component, tailwind 으로 스타일링하기&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;&lt;a href=&quot;https://odada.me/324&quot;&gt;https://odada.me/324&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;[style] Chakra-UI 로 스타일링 하기&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;&lt;a href=&quot;https://odada.me/326&quot;&gt;https://odada.me/326&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;[실습] React로 반응형 웹사이트 만들기&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;&lt;a href=&quot;https://odada.me/255&quot;&gt;https://odada.me/255&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;[실습] React Github, Netlify 배포&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;&lt;a href=&quot;https://odada.me/279&quot;&gt;https://odada.me/279&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;[실습] React 할 일 관리 앱 만들기&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://odada.me/314&quot;&gt;https://odada.me/314&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;[Hooks] useContext&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://odada.me/319&quot;&gt;https://odada.me/319&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;[Hooks] useReducer&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://odada.me/317&quot;&gt;https://odada.me/317&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;[Hooks] useEffect, &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;LifeCycle&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;&lt;a href=&quot;https://odada.me/313&quot;&gt;https://odada.me/313&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;[Hooks] 최적화&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;&lt;a href=&quot;https://odada.me/315&quot;&gt;https://odada.me/315&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;비동기 기본&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://odada.me/337&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://odada.me/337&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;비동기 예제&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;(뉴스 api)&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://odada.me/309&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://odada.me/309&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;TypeScript&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;&lt;a href=&quot;https://odada.me/&quot;&gt;https://odada.me/&lt;/a&gt;&lt;a href=&quot;https://oddcode.tistory.com/359&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;359&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px;&quot;&gt; React Native&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;[실습] firebase 를 활용한 블로그 앱 만들기&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;&lt;a href=&quot;https://odada.me/329&quot;&gt;https://odada.me/329&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;React 라이브러리&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;- 프레임워크&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;height: 84px;&quot; width=&quot;800&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;th style=&quot;height: 20px; width: 215px;&quot;&gt;라이브러리&lt;/th&gt;
&lt;th style=&quot;height: 20px; width: 319px;&quot;&gt;사이트&lt;/th&gt;
&lt;th style=&quot;height: 20px; width: 259px;&quot;&gt;설명&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;height: 22px; width: 215px;&quot;&gt;React&lt;/td&gt;
&lt;td style=&quot;height: 22px; width: 319px;&quot;&gt;&lt;a href=&quot;https://ko.react.dev/&quot;&gt;https://ko.react.dev/&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;height: 22px; width: 259px;&quot;&gt;UI 구축을 위한 JavaScript 라이브러리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px; width: 215px;&quot;&gt;React Native&lt;/td&gt;
&lt;td style=&quot;height: 21px; width: 319px;&quot;&gt;&lt;a href=&quot;https://reactnative.dev/&quot;&gt;https://reactnative.dev/&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px; width: 259px;&quot;&gt;모바일 앱 개발을 위한 React 프레임워크&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px; width: 215px;&quot;&gt;Next.js&lt;/td&gt;
&lt;td style=&quot;height: 21px; width: 319px;&quot;&gt;&lt;a href=&quot;https://nextjs.org&quot;&gt;https://nextjs.org&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px; width: 259px;&quot;&gt;React 프레임워크 (SSR/SSG)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;-&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;스타일링&lt;/p&gt;
&lt;table style=&quot;height: 180px;&quot; width=&quot;800&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;width: 222px;&quot;&gt;라이브러리&lt;/th&gt;
&lt;th style=&quot;width: 307px;&quot;&gt;사이트&lt;/th&gt;
&lt;th style=&quot;width: 263px;&quot;&gt;설명&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 222px;&quot;&gt;Styled-components&lt;/td&gt;
&lt;td style=&quot;width: 307px;&quot;&gt;&lt;a href=&quot;https://styled-components.com&quot;&gt;https://styled-components.com&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;width: 263px;&quot;&gt;CSS-in-JS의 대표주자&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 222px;&quot;&gt;SASS/SCSS&lt;/td&gt;
&lt;td style=&quot;width: 307px;&quot;&gt;&lt;a href=&quot;https://sass-lang.com&quot;&gt;https://sass-lang.com&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;width: 263px;&quot;&gt;CSS 전처리기&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 222px;&quot;&gt;Emotion&lt;/td&gt;
&lt;td style=&quot;width: 307px;&quot;&gt;&lt;a href=&quot;https://emotion.sh&quot;&gt;https://emotion.sh&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;width: 263px;&quot;&gt;고성능 CSS-in-JS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 222px;&quot;&gt;Tailwind CSS&lt;/td&gt;
&lt;td style=&quot;width: 307px;&quot;&gt;&lt;a href=&quot;https://tailwindcss.com&quot;&gt;https://tailwindcss.com&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;width: 263px;&quot;&gt;유틸리티 우선 CSS&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;-&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;UI 컴포넌트&lt;/p&gt;
&lt;table style=&quot;height: 180px;&quot; width=&quot;800&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;th style=&quot;height: 20px; width: 232px;&quot;&gt;라이브러리&lt;/th&gt;
&lt;th style=&quot;height: 20px; width: 294px;&quot;&gt;사이트&lt;/th&gt;
&lt;th style=&quot;height: 20px; width: 266px;&quot;&gt;설명&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;height: 20px; width: 232px;&quot;&gt;Tailwind UI&lt;/td&gt;
&lt;td style=&quot;height: 20px; width: 294px;&quot;&gt;&lt;a href=&quot;https://tailwindui.com/&quot;&gt;https://tailwindui.com/&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;height: 20px; width: 266px;&quot;&gt;Tailwind 기반 프리미엄 UI 컴포넌트&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;height: 20px; width: 232px;&quot;&gt;Material-UI&lt;/td&gt;
&lt;td style=&quot;height: 20px; width: 294px;&quot;&gt;&lt;a href=&quot;https://mui.com&quot;&gt;https://mui.com&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;height: 20px; width: 266px;&quot;&gt;Material Design 기반 UI 라이브러리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;height: 20px; width: 232px;&quot;&gt;Ant Design&lt;/td&gt;
&lt;td style=&quot;height: 20px; width: 294px;&quot;&gt;&lt;a href=&quot;https://ant.design&quot;&gt;https://ant.design&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;height: 20px; width: 266px;&quot;&gt;기업용 디자인 시스템&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;height: 20px; width: 232px;&quot;&gt;Chakra UI&lt;/td&gt;
&lt;td style=&quot;height: 20px; width: 294px;&quot;&gt;&lt;a href=&quot;https://www.chakra-ui.com/&quot;&gt;https://www.chakra-ui.com/&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;height: 20px; width: 266px;&quot;&gt;접근성 중심 모던 UI 컴포넌트&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;-&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;라우팅/네비게이션&lt;/p&gt;
&lt;table style=&quot;height: 102px;&quot; width=&quot;800&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;width: 245px;&quot;&gt;라이브러리&lt;/th&gt;
&lt;th style=&quot;width: 299px;&quot;&gt;사이트&lt;/th&gt;
&lt;th style=&quot;width: 247px;&quot;&gt;설명&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 245px;&quot;&gt;React Router&lt;/td&gt;
&lt;td style=&quot;width: 299px;&quot;&gt;&lt;a href=&quot;https://reactrouter.com&quot;&gt;https://reactrouter.com&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;width: 247px;&quot;&gt;웹 애플리케이션 라우팅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 245px;&quot;&gt;React Navigation&lt;/td&gt;
&lt;td style=&quot;width: 299px;&quot;&gt;&lt;a href=&quot;https://reactnavigation.org/&quot;&gt;https://reactnavigation.org/&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;width: 247px;&quot;&gt;React Native 앱 네비게이션&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;-&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;상태 관리&lt;/p&gt;
&lt;table style=&quot;height: 120px;&quot; width=&quot;800&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;th style=&quot;height: 20px; width: 248px;&quot;&gt;라이브러리&lt;/th&gt;
&lt;th style=&quot;height: 20px; width: 278px;&quot;&gt;사이트&lt;/th&gt;
&lt;th style=&quot;height: 20px; width: 266px;&quot;&gt;설명&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;height: 20px; width: 248px;&quot;&gt;Redux&lt;/td&gt;
&lt;td style=&quot;height: 20px; width: 278px;&quot;&gt;&lt;a href=&quot;https://redux.js.org&quot;&gt;https://redux.js.org&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;height: 20px; width: 266px;&quot;&gt;가장 널리 사용되는 상태 관리 라이브러리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;height: 20px; width: 248px;&quot;&gt;MobX&lt;/td&gt;
&lt;td style=&quot;height: 20px; width: 278px;&quot;&gt;&lt;a href=&quot;https://mobx.js.org&quot;&gt;https://mobx.js.org&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;height: 20px; width: 266px;&quot;&gt;단순하고 확장 가능한 상태 관리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;height: 20px; width: 248px;&quot;&gt;Recoil&lt;/td&gt;
&lt;td style=&quot;height: 20px; width: 278px;&quot;&gt;&lt;a href=&quot;https://recoiljs.org&quot;&gt;https://recoiljs.org&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;height: 20px; width: 266px;&quot;&gt;Facebook의 실험적 상태 관리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;height: 20px; width: 248px;&quot;&gt;Zustand&lt;/td&gt;
&lt;td style=&quot;height: 20px; width: 278px;&quot;&gt;&lt;a href=&quot;https://zustand-demo.pmnd.rs&quot;&gt;https://zustand-demo.pmnd.rs&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;height: 20px; width: 266px;&quot;&gt;간단하고 가벼운 상태 관리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;height: 20px; width: 248px;&quot;&gt;React Query&lt;/td&gt;
&lt;td style=&quot;height: 20px; width: 278px;&quot;&gt;&lt;a href=&quot;https://tanstack.com/query&quot;&gt;https://tanstack.com/query&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;height: 20px; width: 266px;&quot;&gt;서버 상태 관리 특화&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;-&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;폼 관리&lt;/p&gt;
&lt;table style=&quot;height: 138px;&quot; width=&quot;800&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;width: 249px;&quot;&gt;라이브러리&lt;/th&gt;
&lt;th style=&quot;width: 288px;&quot;&gt;사이트&lt;/th&gt;
&lt;th style=&quot;width: 255px;&quot;&gt;설명&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 249px;&quot;&gt;Formik&lt;/td&gt;
&lt;td style=&quot;width: 288px;&quot;&gt;&lt;a href=&quot;https://formik.org&quot;&gt;https://formik.org&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;width: 255px;&quot;&gt;폼 상태 관리 및 검증&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 249px;&quot;&gt;React Hook Form&lt;/td&gt;
&lt;td style=&quot;width: 288px;&quot;&gt;&lt;a href=&quot;https://react-hook-form.com&quot;&gt;https://react-hook-form.com&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;width: 255px;&quot;&gt;성능 중심 폼 관리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 249px;&quot;&gt;Yup&lt;/td&gt;
&lt;td style=&quot;width: 288px;&quot;&gt;&lt;a href=&quot;https://github.com/jquense/yup&quot;&gt;https://github.com/jquense/yup&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;width: 255px;&quot;&gt;폼 유효성 검증&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;-&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;API/네트워킹&lt;/p&gt;
&lt;table style=&quot;height: 143px;&quot; width=&quot;800&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;width: 253px;&quot;&gt;라이브러리&lt;/th&gt;
&lt;th style=&quot;width: 291px;&quot;&gt;사이트&lt;/th&gt;
&lt;th style=&quot;width: 248px;&quot;&gt;설명&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 253px;&quot;&gt;Axios&lt;/td&gt;
&lt;td style=&quot;width: 291px;&quot;&gt;&lt;a href=&quot;https://axios-http.com&quot;&gt;https://axios-http.com&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;width: 248px;&quot;&gt;HTTP 클라이언트&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 253px;&quot;&gt;SWR&lt;/td&gt;
&lt;td style=&quot;width: 291px;&quot;&gt;&lt;a href=&quot;https://swr.vercel.app&quot;&gt;https://swr.vercel.app&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;width: 248px;&quot;&gt;데이터 페칭&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 253px;&quot;&gt;Apollo (GraphQL)&lt;/td&gt;
&lt;td style=&quot;width: 291px;&quot;&gt;&lt;a href=&quot;https://www.apollographql.com&quot;&gt;https://www.apollographql.com&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;width: 248px;&quot;&gt;GraphQL 클라이언트&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;-&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;애니메이션&lt;/p&gt;
&lt;table style=&quot;height: 128px;&quot; width=&quot;800&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;width: 262px;&quot;&gt;라이브러리&lt;/th&gt;
&lt;th style=&quot;width: 279px;&quot;&gt;사이트&lt;/th&gt;
&lt;th style=&quot;width: 251px;&quot;&gt;설명&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 262px;&quot;&gt;React Spring&lt;/td&gt;
&lt;td style=&quot;width: 279px;&quot;&gt;&lt;a href=&quot;https://react-spring.dev&quot;&gt;https://react-spring.dev&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;width: 251px;&quot;&gt;물리 기반 애니메이션&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 262px;&quot;&gt;Framer Motion&lt;/td&gt;
&lt;td style=&quot;width: 279px;&quot;&gt;&lt;a href=&quot;https://www.framer.com/motion&quot;&gt;https://www.framer.com/motion&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;width: 251px;&quot;&gt;선언적 애니메이션&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 262px;&quot;&gt;GSAP&lt;/td&gt;
&lt;td style=&quot;width: 279px;&quot;&gt;&lt;a href=&quot;https://greensock.com&quot;&gt;https://greensock.com&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;width: 251px;&quot;&gt;전문적 애니메이션&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;-&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;유틸리티&lt;/p&gt;
&lt;table style=&quot;height: 138px;&quot; width=&quot;800&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;width: 268px;&quot;&gt;라이브러리&lt;/th&gt;
&lt;th style=&quot;width: 381px;&quot;&gt;사이트&lt;/th&gt;
&lt;th style=&quot;width: 143px;&quot;&gt;설명&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 268px;&quot;&gt;Day.js&lt;/td&gt;
&lt;td style=&quot;width: 381px;&quot;&gt;&lt;a href=&quot;https://day.js.org&quot;&gt;https://day.js.org&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;width: 143px;&quot;&gt;날짜 처리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 268px;&quot;&gt;Lodash&lt;/td&gt;
&lt;td style=&quot;width: 381px;&quot;&gt;&lt;a href=&quot;https://lodash.com&quot;&gt;https://lodash.com&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;width: 143px;&quot;&gt;데이터 조작&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 268px;&quot;&gt;React Icons&lt;/td&gt;
&lt;td style=&quot;width: 381px;&quot;&gt;&lt;a href=&quot;https://react-icons.github.io/react-icons&quot;&gt;https://react-icons.github.io/react-icons&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;width: 143px;&quot;&gt;아이콘 모음&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;-&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;테스팅&lt;/p&gt;
&lt;table style=&quot;height: 140px;&quot; width=&quot;800&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;width: 273px;&quot;&gt;라이브러리&lt;/th&gt;
&lt;th style=&quot;width: 322px;&quot;&gt;사이트&lt;/th&gt;
&lt;th style=&quot;width: 197px;&quot;&gt;설명&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 273px;&quot;&gt;Jest&lt;/td&gt;
&lt;td style=&quot;width: 322px;&quot;&gt;&lt;a href=&quot;https://jestjs.io&quot;&gt;https://jestjs.io&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;width: 197px;&quot;&gt;JavaScript 테스트&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 273px;&quot;&gt;React Testing Library&lt;/td&gt;
&lt;td style=&quot;width: 322px;&quot;&gt;&lt;a href=&quot;https://testing-library.com/react&quot;&gt;https://testing-library.com/react&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;width: 197px;&quot;&gt;UI 테스트&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 273px;&quot;&gt;Cypress&lt;/td&gt;
&lt;td style=&quot;width: 322px;&quot;&gt;&lt;a href=&quot;https://www.cypress.io&quot;&gt;https://www.cypress.io&lt;/a&gt;&lt;/td&gt;
&lt;td style=&quot;width: 197px;&quot;&gt;E2E 테스트&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;</description>
      <category>Front/React</category>
      <author>oodada</author>
      <guid isPermaLink="true">https://oddcode.tistory.com/321</guid>
      <comments>https://oddcode.tistory.com/321#entry321comment</comments>
      <pubDate>Sun, 24 Mar 2024 09:25:36 +0900</pubDate>
    </item>
    <item>
      <title>[Hooks] useContext - React 배우기</title>
      <link>https://oddcode.tistory.com/319</link>
      <description>&lt;h1&gt;React Context&lt;/h1&gt;
&lt;p&gt;리액트의 하나의 컴포넌트에서 데이터를 생성하거나 업데이트하거나 다른 컴포넌트와 데이터를 공유해서 사용하는 여러 방법이 있습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;state 와 props&lt;/strong&gt;를 사용해서 컴포넌트 간에 데이터를 전달&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/odada/post/585c33a0-8964-4709-afe9-38fa866782a1/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h2&gt;useContext 란?&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;React Context는 Component 트리 전체에 props를 전달하지 않고도 Component 데이터를 제공하는 방법으로&lt;/li&gt;
&lt;li&gt;전역 상태를 관리하기 위한 간단한 방법입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/odada/post/9fa25149-1950-40fb-95fa-1938e41ddfe9/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h2&gt;Context 문법&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;생성&lt;/strong&gt; : &lt;code&gt;createContext&lt;/code&gt; 함수를 사용하여 &lt;code&gt;Context&lt;/code&gt;를 &lt;strong&gt;생성&lt;/strong&gt;합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;제공&lt;/strong&gt; : &lt;code&gt;Context.Provider&lt;/code&gt; 컴포넌트를 만나면, &lt;code&gt;value&lt;/code&gt; prop을 통해 &lt;code&gt;Context&lt;/code&gt;의 값을 &lt;strong&gt;제공&lt;/strong&gt;합니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;사용&lt;/strong&gt; :  &lt;code&gt;useContext&lt;/code&gt; 컴포넌트를 사용하여 &lt;code&gt;Context&lt;/code&gt;의 값을 &lt;strong&gt;사용&lt;/strong&gt;합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Context 사용하기&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;createContext&lt;/code&gt; 함수를 사용하여 Context를 생성합니다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;defaultValue&lt;/code&gt;는 &lt;code&gt;Provider&lt;/code&gt;를 사용하지 않았을 때 사용할 기본값입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;const MyContext = createContext(defaultValue);&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Context.Provider&lt;/code&gt; 컴포넌트를 사용하여 Context를 제공합니다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;value&lt;/code&gt; prop을 통해 Context의 값을 제공합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;// 왜 value를 객체로 감싸서 전달하는가?
// 여러 개의 값을 전달하기 위해 객체로 묶어서 전달
// value={{ key1: value1, key2: value2 }}

&amp;lt;MyContext.Provider value={/* 상태 값 */}&amp;gt;
  {/* 여기에 위치한 컴포넌트들은 이 Context의 값을 사용할 수 있음 */}
&amp;lt;/MyContext.Provider&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Context&lt;/code&gt;의 값을 사용할 때는 &lt;code&gt;useContext&lt;/code&gt; hook을 사용합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;// 왜 useContext로 데이터를 가져오는가?
// 컴포넌트 계층 구조에 상관없이 Context의 값을 사용할 수 있음
// 부모 컴포넌트에서 자식 컴포넌트로 데이터를 전달하지 않아도 됨

const value = useContext(MyContext);&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;왜 주고 받는 구조를 사용하는가?&lt;/h2&gt;
&lt;h3&gt;1. 상태를 전역적으로 공유하기 위해&lt;/h3&gt;
&lt;p&gt;만약 &lt;code&gt;useContext&lt;/code&gt; 없이 컴포넌트 간 데이터를 주고받는다면, 상태를 부모 → 자식 → 손자 순서로 &lt;code&gt;props&lt;/code&gt;를 계속 전달해야 합니다. 하지만, &lt;code&gt;Context&lt;/code&gt;를 사용하면 컴포넌트 계층과 상관없이 원하는 곳에서 상태를 바로 사용할 수 있습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Without Context (props drilling):&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;&amp;lt;Parent isLiked={isLiked} toggleLike={toggleLike}&amp;gt;
  &amp;lt;Child isLiked={isLiked} toggleLike={toggleLike}&amp;gt;
    &amp;lt;Grandchild isLiked={isLiked} toggleLike={toggleLike} /&amp;gt;
  &amp;lt;/Child&amp;gt;
&amp;lt;/Parent&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;With Context:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;&amp;lt;LikeProvider&amp;gt;
  &amp;lt;Parent&amp;gt;
    &amp;lt;Child&amp;gt;
      &amp;lt;Grandchild /&amp;gt; {/* 바로 isLiked와 toggleLike 사용 가능 */}
    &amp;lt;/Child&amp;gt;
  &amp;lt;/Parent&amp;gt;
&amp;lt;/LikeProvider&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;like 버튼 만들기&lt;/h2&gt;
&lt;h3&gt;1. Context 없이 만들기&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;import React, { useState } from &amp;quot;react&amp;quot;;

const LikeButton = ({ isLiked, toggleLike }) =&amp;gt; {
  return (
    &amp;lt;div className=&amp;quot;flex items-center justify-center mt-10&amp;quot;&amp;gt;
      &amp;lt;button onClick={toggleLike} className=&amp;quot;text-3xl&amp;quot;&amp;gt;
        &amp;lt;span className={isLiked ? &amp;quot;text-red-500&amp;quot; : &amp;quot;text-gray-400&amp;quot;}&amp;gt;❤️하트&amp;lt;/span&amp;gt;
      &amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  );
};

const FollowButton = () =&amp;gt; {
  return (
    &amp;lt;div className=&amp;quot;flex items-center justify-center mt-10&amp;quot;&amp;gt;
      &amp;lt;button onClick={toggleLike} className=&amp;quot;text-3xl&amp;quot;&amp;gt;
        &amp;lt;span className={isLiked ? &amp;quot;text-green&amp;quot; : &amp;quot;text-gray-400&amp;quot;}&amp;gt;+팔로우&amp;lt;/span&amp;gt;
      &amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}

const App = () =&amp;gt; {
  const [isLiked, setIsLiked] = useState(false);

  const toggleLike = () =&amp;gt; {
    setIsLiked((prev) =&amp;gt; !prev);
  };

  return (
    &amp;lt;div className=&amp;quot;p-8&amp;quot;&amp;gt;
      &amp;lt;LikeButton isLiked={isLiked} toggleLike={toggleLike} /&amp;gt;
      &amp;lt;FollowButton isLiked={isLiked} toggleLike={toggleLike} /&amp;gt;
    &amp;lt;/div&amp;gt;
  );
};

export default App;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. Context로 만들기&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;import React, { createContext, useContext, useState } from &amp;quot;react&amp;quot;;

// Context 생성
const ButtonContext = createContext();

const App = () =&amp;gt; {
  const [isLiked, setIsLiked] = useState(false);

  const toggleLike = () =&amp;gt; {
    setIsLiked((prev) =&amp;gt; !prev);
  };

  return (
    // Context Provider로 상태 공유
    &amp;lt;ButtonContext.Provider value={{ isLiked, toggleLike }}&amp;gt;
      &amp;lt;div className=&amp;quot;p-8&amp;quot;&amp;gt;
        &amp;lt;LikeButton /&amp;gt;
        &amp;lt;FollowButton /&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/ButtonContext.Provider&amp;gt;
  );
};

const LikeButton = () =&amp;gt; {
  // Context에서 상태 가져오기
  const { isLiked, toggleLike } = useContext(ButtonContext);

  return (
    &amp;lt;div className=&amp;quot;flex items-center justify-center mt-10&amp;quot;&amp;gt;
      &amp;lt;button onClick={toggleLike} className=&amp;quot;text-3xl&amp;quot;&amp;gt;
        &amp;lt;span className={isLiked ? &amp;quot;text-red-500&amp;quot; : &amp;quot;text-gray-400&amp;quot;}&amp;gt;❤️하트&amp;lt;/span&amp;gt;
      &amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  );
};

const FollowButton = () =&amp;gt; {
  // Context에서 상태 가져오기
  const { isLiked, toggleLike } = useContext(ButtonContext);

  return (
    &amp;lt;div className=&amp;quot;flex items-center justify-center mt-10&amp;quot;&amp;gt;
      &amp;lt;button onClick={toggleLike} className=&amp;quot;text-3xl&amp;quot;&amp;gt;
        &amp;lt;span className={isLiked ? &amp;quot;text-green-500&amp;quot; : &amp;quot;text-gray-400&amp;quot;}&amp;gt;+팔로우&amp;lt;/span&amp;gt;
      &amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  );
};

export default App;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3. Custom Hook을 사용하는 경우&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;src/
├── App.jsx                  # 메인 컴포넌트
├── context/
│   └── ButtonContext.jsx    # Context 및 Provider 정의
├── components/
    ├── LikeButton.jsx       # 좋아요 버튼 컴포넌트
    └── FollowButton.jsx     # 팔로우 버튼 컴포넌트&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;// src/contexts/ButtonContext.jsx
import React, { createContext, useContext, useState } from &amp;quot;react&amp;quot;;

// Context 생성
const ButtonContext = createContext();

// 커스텀 Hook
export const useButtonContext = () =&amp;gt; {
  const context = useContext(ButtonContext);
  if (!context) {
    throw new Error(&amp;quot;useButtonContext must be used within a ButtonProvider&amp;quot;);
  }
  return context;
};

// Provider 컴포넌트
export const ButtonProvider = ({ children }) =&amp;gt; {
  const [isLiked, setIsLiked] = useState(false);

  const toggleLike = () =&amp;gt; {
    setIsLiked((prev) =&amp;gt; !prev);
  };

  return (
    &amp;lt;ButtonContext.Provider value={{ isLiked, toggleLike }}&amp;gt;
      {children}
    &amp;lt;/ButtonContext.Provider&amp;gt;
  );
};&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;// src/components/LikeButton.jsx
import React from &amp;quot;react&amp;quot;;
import { useButtonContext } from &amp;quot;../context/ButtonContext&amp;quot;;

const LikeButton = () =&amp;gt; {
  const { isLiked, toggleLike } = useButtonContext();

  return (
    &amp;lt;div className=&amp;quot;flex items-center justify-center mt-10&amp;quot;&amp;gt;
      &amp;lt;button onClick={toggleLike} className=&amp;quot;text-3xl&amp;quot;&amp;gt;
        &amp;lt;span className={isLiked ? &amp;quot;text-red-500&amp;quot; : &amp;quot;text-gray-400&amp;quot;}&amp;gt;❤️하트&amp;lt;/span&amp;gt;
      &amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  );
};

export default LikeButton;&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;// src/components/FollowButton.jsx
import React from &amp;quot;react&amp;quot;;
import { useButtonContext } from &amp;quot;../context/ButtonContext&amp;quot;;

const FollowButton = () =&amp;gt; {
  const { isLiked, toggleLike } = useButtonContext();

  return (
    &amp;lt;div className=&amp;quot;flex items-center justify-center mt-10&amp;quot;&amp;gt;
      &amp;lt;button onClick={toggleLike} className=&amp;quot;text-3xl&amp;quot;&amp;gt;
        &amp;lt;span className={isLiked ? &amp;quot;text-green-500&amp;quot; : &amp;quot;text-gray-400&amp;quot;}&amp;gt;+팔로우&amp;lt;/span&amp;gt;
      &amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  );
};

export default FollowButton;&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;// src/App.jsx
import React from &amp;quot;react&amp;quot;;
import { ButtonProvider } from &amp;quot;./context/ButtonContext&amp;quot;;
import LikeButton from &amp;quot;./components/LikeButton&amp;quot;;
import FollowButton from &amp;quot;./components/FollowButton&amp;quot;;

const App = () =&amp;gt; {
  return (
    &amp;lt;ButtonProvider&amp;gt;
      &amp;lt;div className=&amp;quot;p-8&amp;quot;&amp;gt;
        &amp;lt;LikeButton /&amp;gt;
        &amp;lt;FollowButton /&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/ButtonProvider&amp;gt;
  );
};

export default App;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;데이터 전달 방식 비교&lt;/h2&gt;
&lt;h3&gt;1. props를 사용하는 경우&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;import React from &amp;#39;react&amp;#39;;

const UserProfile = ({ name, email, userData }) =&amp;gt; {
  return (
    &amp;lt;div className=&amp;quot;p-4 border rounded&amp;quot;&amp;gt;
      &amp;lt;h2 className=&amp;quot;text-xl font-bold&amp;quot;&amp;gt;기본 정보&amp;lt;/h2&amp;gt;
      &amp;lt;div className=&amp;quot;mt-2&amp;quot;&amp;gt;
        &amp;lt;p&amp;gt;이름: {name}&amp;lt;/p&amp;gt;
        &amp;lt;p&amp;gt;이메일: {email}&amp;lt;/p&amp;gt;
      &amp;lt;/div&amp;gt;
      &amp;lt;UserInfo age={userData.age} location={userData.location} /&amp;gt;
    &amp;lt;/div&amp;gt;
  );
};

const UserInfo = ({ age, location }) =&amp;gt; {
  return (
    &amp;lt;div className=&amp;quot;mt-4 border-t pt-4&amp;quot;&amp;gt;
      &amp;lt;h3 className=&amp;quot;font-bold&amp;quot;&amp;gt;상세 정보&amp;lt;/h3&amp;gt;
      &amp;lt;div className=&amp;quot;mt-2&amp;quot;&amp;gt;
        &amp;lt;p&amp;gt;나이: {age}&amp;lt;/p&amp;gt;
        &amp;lt;p&amp;gt;지역: {location}&amp;lt;/p&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  );
};

const User = () =&amp;gt; {
  const userData = {
    name: &amp;quot;김철수&amp;quot;,
    email: &amp;quot;cheolsu@example.com&amp;quot;,
    age: 25,
    location: &amp;quot;서울&amp;quot;
  };

  return (
    &amp;lt;div className=&amp;quot;p-8&amp;quot;&amp;gt;
      &amp;lt;UserProfile name={userData.name} email={userData.email} userData={userData} /&amp;gt;
    &amp;lt;/div&amp;gt;
  );
};

export default User;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. Context를 사용하는 경우&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;import React, { createContext, useContext } from &amp;#39;react&amp;#39;;

const UserContext = createContext();

const UserProfile = () =&amp;gt; {
  const { name, email } = useContext(UserContext);

  return (
    &amp;lt;div className=&amp;quot;p-4 border rounded&amp;quot;&amp;gt;
      &amp;lt;h2 className=&amp;quot;text-xl font-bold&amp;quot;&amp;gt;기본 정보&amp;lt;/h2&amp;gt;
      &amp;lt;div className=&amp;quot;mt-2&amp;quot;&amp;gt;
        &amp;lt;p&amp;gt;이름: {name}&amp;lt;/p&amp;gt;
        &amp;lt;p&amp;gt;이메일: {email}&amp;lt;/p&amp;gt;
      &amp;lt;/div&amp;gt;
      &amp;lt;UserInfo /&amp;gt;
    &amp;lt;/div&amp;gt;
  );
};

const UserInfo = () =&amp;gt; {
  const { age, location } = useContext(UserContext);

  return (
    &amp;lt;div className=&amp;quot;mt-4 border-t pt-4&amp;quot;&amp;gt;
      &amp;lt;h3 className=&amp;quot;font-bold&amp;quot;&amp;gt;상세 정보&amp;lt;/h3&amp;gt;
      &amp;lt;div className=&amp;quot;mt-2&amp;quot;&amp;gt;
        &amp;lt;p&amp;gt;나이: {age}&amp;lt;/p&amp;gt;
        &amp;lt;p&amp;gt;지역: {location}&amp;lt;/p&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  );
};

const User = () =&amp;gt; {
  const userData = {
    name: &amp;quot;김철수&amp;quot;,
    email: &amp;quot;cheolsu@example.com&amp;quot;,
    age: 25,
    location: &amp;quot;서울&amp;quot;
  };

  return (
    &amp;lt;div className=&amp;quot;p-8&amp;quot;&amp;gt;
      &amp;lt;UserContext.Provider value={userData}&amp;gt;
        &amp;lt;UserProfile /&amp;gt;
      &amp;lt;/UserContext.Provider&amp;gt;
    &amp;lt;/div&amp;gt;
  );
};

export default User;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3. Custom Hook을 사용하는 경우&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;import React, { createContext, useContext } from &amp;#39;react&amp;#39;;

// Context 생성
const UserContext = createContext();

// Provider 컴포넌트 분리
const UserProvider = ({ children }) =&amp;gt; {
  const userData = {
    name: &amp;quot;김철수&amp;quot;,
    email: &amp;quot;cheolsu@example.com&amp;quot;,
    age: 25,
    location: &amp;quot;서울&amp;quot;
  };

  return (
    &amp;lt;UserContext.Provider value={userData}&amp;gt;
      {children}
    &amp;lt;/UserContext.Provider&amp;gt;
  );
};

// 커스텀 훅 생성
const useUser = () =&amp;gt; {
  const context = useContext(UserContext);
  if (!context) {
    throw new Error(&amp;#39;useUser must be used within a UserProvider&amp;#39;);
  }
  return context;
};

const UserProfile = () =&amp;gt; {
  const { name, email } = useUser();

  return (
    &amp;lt;div className=&amp;quot;p-4 border rounded&amp;quot;&amp;gt;
      &amp;lt;h2 className=&amp;quot;text-xl font-bold&amp;quot;&amp;gt;기본 정보&amp;lt;/h2&amp;gt;
      &amp;lt;div className=&amp;quot;mt-2&amp;quot;&amp;gt;
        &amp;lt;p&amp;gt;이름: {name}&amp;lt;/p&amp;gt;
        &amp;lt;p&amp;gt;이메일: {email}&amp;lt;/p&amp;gt;
      &amp;lt;/div&amp;gt;
      &amp;lt;UserInfo /&amp;gt;
    &amp;lt;/div&amp;gt;
  );
};

const UserInfo = () =&amp;gt; {
  const { age, location } = useUser();

  return (
    &amp;lt;div className=&amp;quot;mt-4 border-t pt-4&amp;quot;&amp;gt;
      &amp;lt;h3 className=&amp;quot;font-bold&amp;quot;&amp;gt;상세 정보&amp;lt;/h3&amp;gt;
      &amp;lt;div className=&amp;quot;mt-2&amp;quot;&amp;gt;
        &amp;lt;p&amp;gt;나이: {age}&amp;lt;/p&amp;gt;
        &amp;lt;p&amp;gt;지역: {location}&amp;lt;/p&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  );
};

const User = () =&amp;gt; {
  return (
    &amp;lt;div className=&amp;quot;p-8&amp;quot;&amp;gt;
      &amp;lt;UserProvider&amp;gt;
        &amp;lt;UserProfile /&amp;gt;
      &amp;lt;/UserProvider&amp;gt;
    &amp;lt;/div&amp;gt;
  );
};

export default User;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Counter useContext로 변경&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;// src/reducers/counterReducer.js
export const ACTION_TYPE = {
    INCREMENT: &amp;#39;INCREMENT&amp;#39;,
    DECREMENT: &amp;#39;DECREMENT&amp;#39;,
    RESET: &amp;#39;RESET&amp;#39;,
}

export function counterReducer(state, action) {
    switch (action.type) {
        case ACTION_TYPE.INCREMENT:
            return { ...state, counter: state.counter + 1 }
        case ACTION_TYPE.DECREMENT:
            return { ...state, counter: state.counter - 1 }
        case ACTION_TYPE.RESET:
            return { ...state, counter: 0 }
        default:
            return state
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;// src/components/Counter.js
import React, { useReducer } from &amp;#39;react&amp;#39;
import { counterReducer, ACTION_TYPE } from &amp;#39;../reducers/counterReducer&amp;#39;

function Counter() {
    const [state, dispatch] = useReducer(counterReducer, {
        counter: 0,
        name: &amp;#39;counter&amp;#39;,
    })

    return (
        &amp;lt;div&amp;gt;
            &amp;lt;h1&amp;gt;
                {state.name}: {state.counter}
            &amp;lt;/h1&amp;gt;
            &amp;lt;button onClick={() =&amp;gt; dispatch({ type: ACTION_TYPE.INCREMENT })}&amp;gt;+1&amp;lt;/button&amp;gt;
            &amp;lt;button onClick={() =&amp;gt; dispatch({ type: ACTION_TYPE.DECREMENT })}&amp;gt;-1&amp;lt;/button&amp;gt;
            &amp;lt;button onClick={() =&amp;gt; dispatch({ type: ACTION_TYPE.RESET })}&amp;gt;Reset&amp;lt;/button&amp;gt;
        &amp;lt;/div&amp;gt;
    )
}

export default Counter&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이전 페이지에서 만든 Counter 코드를 useContext를 사용하여 변경해보겠습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;// src/contexts/CounterContext.js
import React, { createContext, useReducer } from &amp;#39;react&amp;#39;
import { counterReducer, ACTION_TYPE } from &amp;#39;../reducers/counterReducer&amp;#39;

const CounterContext = createContext()

export function CounterProvider({ children }) {
    const [state, dispatch] = useReducer(counterReducer, {
        counter: 0,
        name: &amp;#39;counter&amp;#39;,
    })

    return (
        &amp;lt;CounterContext.Provider value={{ state, dispatch }}&amp;gt;
            {children}
        &amp;lt;/CounterContext.Provider&amp;gt;
    )
}

export function useCounter() {
    const context = React.useContext(CounterContext)
    if (!context) {
        throw new Error(&amp;#39;useCounter must be used within a CounterProvider&amp;#39;)
    }
    return context
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;// src/App.js
import React from &amp;#39;react&amp;#39;
import Counter from &amp;#39;./components/Counter&amp;#39;
import { CounterProvider } from &amp;#39;./contexts/CounterContext&amp;#39;

function App() {
    return (
        &amp;lt;CounterProvider&amp;gt;
            &amp;lt;Counter /&amp;gt;
        &amp;lt;/CounterProvider&amp;gt;
    )
}

export default App&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;// src/components/Counter.js
import React from &amp;#39;react&amp;#39;
import { useCounter, ACTION_TYPE } from &amp;#39;../contexts/CounterContext&amp;#39;

function Counter() {
    const { state, dispatch } = useCounter()

    return (
        &amp;lt;div&amp;gt;
            &amp;lt;h1&amp;gt;
                {state.name}: {state.counter}
            &amp;lt;/h1&amp;gt;
            &amp;lt;button onClick={() =&amp;gt; dispatch({ type: ACTION_TYPE.INCREMENT })}&amp;gt;+1&amp;lt;/button&amp;gt;
            &amp;lt;button onClick={() =&amp;gt; dispatch({ type: ACTION_TYPE.DECREMENT })}&amp;gt;-1&amp;lt;/button&amp;gt;
            &amp;lt;button onClick={() =&amp;gt; dispatch({ type: ACTION_TYPE.RESET })}&amp;gt;Reset&amp;lt;/button&amp;gt;
        &amp;lt;/div&amp;gt;
    )
}

export default Counter&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Counter 컴포넌트에서 useCounter hook을 사용하여 CounterContext의 state와 dispatch를 사용할 수 있습니다.&lt;/p&gt;</description>
      <category>Front/React</category>
      <author>oodada</author>
      <guid isPermaLink="true">https://oddcode.tistory.com/319</guid>
      <comments>https://oddcode.tistory.com/319#entry319comment</comments>
      <pubDate>Wed, 20 Mar 2024 12:56:36 +0900</pubDate>
    </item>
    <item>
      <title>타입 개요 및 개발환경 구성</title>
      <link>https://oddcode.tistory.com/318</link>
      <description>&lt;h1&gt;타입 개요 및 개발환경 구성&lt;/h1&gt;
&lt;h2&gt;01. 타입스크립트란?&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&lt;a href=&quot;https://www.typescriptlang.org/&quot;&gt;TypeScript&lt;/a&gt;&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;ul&gt;
&lt;li&gt;2012년 마이크로소프트에서 발표한 오픈소스 프로그래밍 언어로,&lt;/li&gt;
&lt;li&gt;자바스크립트의 기반의 &lt;strong&gt;정적 타입 문법&lt;/strong&gt;을 추가한 프로그래밍 언어!&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://images.velog.io/images/hanganda23/post/cc68451e-4eed-4df6-8ff0-af19111f699b/ts.png&quot; alt=&quot;TypeScript&quot;&gt;&lt;/p&gt;
&lt;h2&gt;02. 타입스크립트의 특징&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;정적 타입의 컴파일 언어&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;자바스크립트(동적 타입) : 변수의 타입 오류를 런타임(동작) 시점에 확인&lt;/li&gt;
&lt;li&gt;타입스크립트(정적 타입) : 변수의 타입 오류를 컴파일 시점에 확인&lt;/li&gt;
&lt;li&gt;자바스크립트로 변환(컴파일) 후 브라우저에나 Node.js 환경에서 동작&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;03. 개발환경 구성&lt;/h2&gt;
&lt;h3&gt;- 프로젝트 생성&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm init -y&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- 패키지 설치&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;parcel : 웹 애플리케이션 번들러&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm i -D parcel typescript&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
    &amp;quot;name&amp;quot;: &amp;quot;ts-test&amp;quot;,
    &amp;quot;version&amp;quot;: &amp;quot;1.0.0&amp;quot;,
    &amp;quot;description&amp;quot;: &amp;quot;&amp;quot;,
    // &amp;quot;main&amp;quot;: &amp;quot;index.js&amp;quot;, // 오류 발생 제거
    &amp;quot;scripts&amp;quot;: {
        // 추가
        &amp;quot;dev&amp;quot;: &amp;quot;parcel index.html&amp;quot;,
        &amp;quot;build&amp;quot;: &amp;quot;parcel build index.html&amp;quot;
    },
    &amp;quot;keywords&amp;quot;: [],
    &amp;quot;author&amp;quot;: &amp;quot;&amp;quot;,
    &amp;quot;license&amp;quot;: &amp;quot;ISC&amp;quot;,
    &amp;quot;devDependencies&amp;quot;: {
        &amp;quot;parcel&amp;quot;: &amp;quot;^2.12.0&amp;quot;,
        &amp;quot;typescript&amp;quot;: &amp;quot;^5.4.2&amp;quot;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- index, ts 파일 생성&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;ts-test
├── index.html
├── src
    ├── main.ts
├── tsconfig.json
└── package.json&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;ts 확장자를 html 파일에서 불러오기 위해 &lt;code&gt;type=&amp;quot;module&amp;quot;&lt;/code&gt; 속성 추가&lt;/li&gt;
&lt;li&gt;html은 ts 파일을 브라우저에서 실행할 수 없지만 parcel이 번들링하여 실행 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;lt;!-- index.html --&amp;gt;
&amp;lt;!doctype html&amp;gt;
&amp;lt;html lang=&amp;quot;ko&amp;quot;&amp;gt;
    &amp;lt;head&amp;gt;
        &amp;lt;meta charset=&amp;quot;UTF-8&amp;quot; /&amp;gt;
        &amp;lt;meta
            name=&amp;quot;viewport&amp;quot;
            content=&amp;quot;width=device-width, initial-scale=1.0&amp;quot;
        /&amp;gt;
        &amp;lt;title&amp;gt;Document&amp;lt;/title&amp;gt;
        &amp;lt;script
            type=&amp;quot;module&amp;quot;
            src=&amp;quot;./src/main.ts&amp;quot;
        &amp;gt;&amp;lt;/script&amp;gt;
    &amp;lt;/head&amp;gt;
    &amp;lt;body&amp;gt;&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;typescript의 구성옵션을 제공하기 위해 &lt;code&gt;tsconfig.json&lt;/code&gt; 파일 생성&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npx tsc --init&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;// tsconfig.json
{
    &amp;quot;compilerOptions&amp;quot;: {
        &amp;quot;target&amp;quot;: &amp;quot;ES2016&amp;quot;, // 2016년도 버전으로 자바스크립트를 타입스크립트로 변환
        &amp;quot;module&amp;quot;: &amp;quot;ESNext&amp;quot;, // ECMAScript의 가장 최신 버전으로 모듈을 사용
        &amp;quot;moduleResolution&amp;quot;: &amp;quot;Node&amp;quot;, // 모듈 해석 방식을 Node.js의 해석 방식으로 사용
        &amp;quot;esModuleInterop&amp;quot;: true, // CommonJS 모듈을 ES6 모듈로 변환
        &amp;quot;lib&amp;quot;: [&amp;quot;ESNext&amp;quot;, &amp;quot;DOM&amp;quot;], // 사용할 라이브러리를 설정
        &amp;quot;strict&amp;quot;: true // 엄격한 타입 검사를 사용
    },
    &amp;quot;include&amp;quot;: [&amp;quot;**/*.ts&amp;quot;, &amp;quot;**/*.tsx, scr/**/*.ts&amp;quot;], // 타입스크립트 파일을 찾을 위치
    &amp;quot;exclude&amp;quot;: [&amp;quot;node_modules&amp;quot;] // 타입스크립트 파일을 제외할 위치
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;// main.ts
// let hello1: string = 123 // 오류 발생
let hello2: string = &amp;#39;Hello, TypeScript!&amp;#39;;

// hello2 = 123 // 오류 발생
hello2 = &amp;#39;Hello, JavaScript!&amp;#39;;

console.log(hello2);&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- 개발 서버 실행&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;html은 ts 파일을 브라우저에서 실행할 수 없지만 parcel이 번들링하여 실행 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm run dev&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;dist 디렉토리가 생성되고 번들링된 파일이 실행됨&lt;/li&gt;
&lt;li&gt;dist &amp;gt; index.html 파일을 살펴보면 main.js 파일이 생성되어 있음&lt;/li&gt;
&lt;li&gt;dist &amp;gt; main.js 파일을 살펴보면 번들링된 파일이 생성되어 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;// let hello1: string = 123 // 오류 발생
let hello2 = &amp;#39;Hello, TypeScript!&amp;#39;;
// hello2 = 123 // 오류 발생
hello2 = &amp;#39;Hello, JavaScript!&amp;#39;;
console.log(hello2);&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;04. 구문&lt;/h2&gt;
&lt;h3&gt;- 변수 선언&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;타입스크립트는 변수의 타입을 명시적으로 선언하여 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;let 변수명: 타입 = 값;&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;let str: string = &amp;#39;문자열&amp;#39;;
let num: number = 123;
let bool: boolean = true;
let arr: number[] = [1, 2, 3];
let tuple: [string, number] = [&amp;#39;one&amp;#39;, 1];
let obj: { name: string; age: number } = { name: &amp;#39;김겨울&amp;#39;, age: 30 };&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- 함수 선언&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;함수의 매개변수와 반환값에 타입을 명시적으로 선언하여 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;function 함수명(매개변수: 타입): 반환타입 { ... }&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;function hello(name: string): string {
    return `Hello, ${name}!`;
}

console.log(hello(&amp;#39;TypeScript&amp;#39;)); // Hello, TypeScript!&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- 타입 주석&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;변수나 함수의 타입을 주석으로 명시&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;let 변수명: 타입;&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;let str: string;
let num: number;
let bool: boolean;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- 타입 추론&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;변수나 함수의 타입을 명시하지 않아도 타입을 추론하여 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;let str = &amp;#39;문자열&amp;#39;; // 타입스크립트가 str을 문자열 타입으로 추론
let num = 123;
let bool = true;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- 타입 별칭&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;타입을 별칭으로 지정하여 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;type 타입별칭 = 타입;&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;type Point = { x: number; y: number };
let p: Point = { x: 10, y: 20 };&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- interface&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;객체의 타입을 정의하여 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;interface 인터페이스 { key: 타입, key: 타입 }&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;interface IPerson {
    name: string;
    age: number;
}

let userA: IPerson = { name: &amp;#39;김겨울&amp;#39;, age: 30 };
let userB: IPerson = { name: &amp;#39;김가을&amp;#39;, age: 30 };&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Front/TypeScript</category>
      <author>oodada</author>
      <guid isPermaLink="true">https://oddcode.tistory.com/318</guid>
      <comments>https://oddcode.tistory.com/318#entry318comment</comments>
      <pubDate>Tue, 19 Mar 2024 10:36:44 +0900</pubDate>
    </item>
    <item>
      <title>[Hooks] useReducer - React 배우기</title>
      <link>https://oddcode.tistory.com/317</link>
      <description>&lt;h1&gt;useReducer&lt;/h1&gt;
&lt;h2&gt;useReducer 란?&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;useReducer&lt;/code&gt; 는 &lt;code&gt;useState&lt;/code&gt; 보다 더 다양한 컴포넌트 상태를 관리하는 Hook 입니다. &lt;code&gt;useState&lt;/code&gt; 는 컴포넌트 상태를 관리하는 가장 기본적인 Hook 이지만, &lt;code&gt;useReducer&lt;/code&gt; 는 복잡한 상태 로직을 다루기에 더 적합합니다.&lt;/p&gt;
&lt;p&gt;컴포넌트에서 상태 변화 코드를 쉽게 분리할 수 있고, 상태 로직을 컴포넌트 바깥으로 빼내어 재사용할 수 있습니다.&lt;/p&gt;
&lt;h2&gt;useReducer 사용법&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;useReducer&lt;/code&gt; 는 다음과 같이 사용합니다.&lt;/p&gt;
&lt;h3&gt;- 타입 정의&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;const ACTION_TYPE1 = &amp;#39;ACTION_TYPE1&amp;#39;
const ACTION_TYPE2 = &amp;#39;ACTION_TYPE2&amp;#39;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- reducer 함수 정의&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;const [state, dispatch] = useReducer(reducer, initialState)&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;state&lt;/code&gt; : 현재 상태&lt;/li&gt;
&lt;li&gt;&lt;code&gt;dispatch&lt;/code&gt; : 액션을 발생시키는 함수, 액션을 발생시키면 리듀서 함수가 호출되어 상태가 변경됩니다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;reducer&lt;/code&gt; : 상태를 변경하는 함수&lt;/li&gt;
&lt;li&gt;&lt;code&gt;initialState&lt;/code&gt; : 초기 상태&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;reducer&lt;/code&gt; 함수는 다음과 같이 작성합니다.&lt;/p&gt;
&lt;p&gt;reducer 함수는 현재 상태와 액션을 받아서 새로운 상태를 반환합니다.&lt;br&gt;action.type 은 대문자로 작성하여 액션 타입이 상수임을 나타냅니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;function reducer(state, action) {
    switch (action.type) {
        case &amp;#39;ACTION_TYPE1&amp;#39;:
            return { ...state, ...action.payload }
        case &amp;#39;ACTION_TYPE2&amp;#39;:
            return { ...state, ...action.payload }
        default:
            return state
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;useReducer 예제&lt;/h2&gt;
&lt;h3&gt;- useStaet 사용&lt;/h3&gt;
&lt;p&gt;useState 를 사용하여 컴포넌트 상태를 관리하는 예제로 상태관리가 복잡해질수록 &lt;strong&gt;코드가 복잡해 관리가 어려워집니다.&lt;/strong&gt;&lt;br&gt;또한 상태를 변경하는 로직이 컴포넌트 안에 선언되어 있어 &lt;strong&gt;재사용이 어렵습니다.&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;import React, { useState } from &amp;#39;react&amp;#39;

function Counter() {
    const [count, setCount] = useState(0)

    const onIncrease = () =&amp;gt; {
        setCount(count + 1)
    }

    const onDecrease = () =&amp;gt; {
        setCount(count - 1)
    }

    return (
        &amp;lt;div&amp;gt;
            &amp;lt;h1&amp;gt;{count}&amp;lt;/h1&amp;gt;
            &amp;lt;button onClick={onIncrease}&amp;gt;+1&amp;lt;/button&amp;gt;
            &amp;lt;button onClick={onDecrease}&amp;gt;-1&amp;lt;/button&amp;gt;
        &amp;lt;/div&amp;gt;
    )
}

export default Counter&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;- useReducer 사용&lt;/h3&gt;
&lt;p&gt;useReducer 를 사용하여 컴포넌트 상태를 관리하는 예제로 상태관리가 복잡해져도 &lt;strong&gt;코드가 간결해집니다.&lt;/strong&gt;&lt;br&gt;또한 상태를 변경하는 로직이 컴포넌트 바깥에 선언되어 있어 &lt;strong&gt;재사용이 용이합니다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;dispatch 함수를 이용하여 액션을 발생시키면 reducer 함수가 호출되어 상태가 변경됩니다. 상태가 변경되면 컴포넌트가 리렌더링됩니다.&lt;/p&gt;
&lt;p&gt;쉽게 말해, dispatch가 파견 나간 직원이라 생각하고, reducer가 파견 받은 회사라 생각하면 이해하기 쉽습니다. 파견 나간 직원이 정보를 보내면 파견 받은 회사에서 정보를 처리하고, 결과를 돌려줍니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;// src/components/Counter.js
import React, { useReducer } from &amp;#39;react&amp;#39;

// reducer 함수로 상태를 변경
// state : 현재 상태
// action : 상태를 변경할 때 참조하는 값
function reducer(state, action) {
    switch (action.type) {
        case &amp;#39;INCREMENT&amp;#39;:
            return { counter: state.counter + 1 }
        case &amp;#39;DECREMENT&amp;#39;:
            return { counter: state.counter - 1 }
        case &amp;#39;RESET&amp;#39;:
            return { counter: 0 }
        default:
            return state
    }
}

function Counter() {
    // useReducer 로 상태 관리
    // state : 현재 상태
    // dispatch : 액션을 발생시키는 함수
    // reducer : 상태를 변경하는 함수
    // initialState : 초기 상태
    const [state, dispatch] = useReducer(reducer, {
        counter: 0,
    })

    // state : { counter: 0 }
    return (
        &amp;lt;div&amp;gt;
            &amp;lt;h1&amp;gt;Counter&amp;lt;/h1&amp;gt;
            &amp;lt;p&amp;gt;Current count: {state.counter}&amp;lt;/p&amp;gt;
            &amp;lt;button onClick={() =&amp;gt; dispatch({ type: &amp;#39;INCREMENT&amp;#39; })}&amp;gt;+1&amp;lt;/button&amp;gt;
            &amp;lt;button onClick={() =&amp;gt; dispatch({ type: &amp;#39;DECREMENT&amp;#39; })}&amp;gt;-1&amp;lt;/button&amp;gt;
            &amp;lt;button onClick={() =&amp;gt; dispatch({ type: &amp;#39;RESET&amp;#39; })}&amp;gt;Reset&amp;lt;/button&amp;gt;
        &amp;lt;/div&amp;gt;
    )
}

export default Counter&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;useReducer 여러 상태 사용&lt;/h2&gt;
&lt;p&gt;useReducer 를 사용하여 여러 상태를 관리할 수 있습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;import React, { useReducer } from &amp;#39;react&amp;#39;

function reducer(state, action) {
    switch (action.type) {
        case &amp;#39;INCREMENT&amp;#39;:
            return { ...state, counter: state.counter + 1 }
        case &amp;#39;DECREMENT&amp;#39;:
            return { ...state, counter: state.counter - 1 }
        case &amp;#39;RESET&amp;#39;:
            return { ...state, counter: 0 }
        default:
            return state
    }
}

function Counter() {
    const [state, dispatch] = useReducer(reducer, {
        counter: 0,
        name: &amp;#39;counter&amp;#39;,
    })
    // state : { counter: 0, name: &amp;#39;counter&amp;#39; }

    return (
        &amp;lt;div&amp;gt;
            &amp;lt;h1&amp;gt;
                {state.name}: {state.counter}
            &amp;lt;/h1&amp;gt;
            &amp;lt;button onClick={() =&amp;gt; dispatch({ type: &amp;#39;INCREMENT&amp;#39; })}&amp;gt;+1&amp;lt;/button&amp;gt;
            &amp;lt;button onClick={() =&amp;gt; dispatch({ type: &amp;#39;DECREMENT&amp;#39; })}&amp;gt;-1&amp;lt;/button&amp;gt;
            &amp;lt;button onClick={() =&amp;gt; dispatch({ type: &amp;#39;RESET&amp;#39; })}&amp;gt;Reset&amp;lt;/button&amp;gt;
        &amp;lt;/div&amp;gt;
    )
}

export default Counter&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;상태 로직 분리&lt;/h2&gt;
&lt;p&gt;useReducer 를 사용하여 상태 로직을 분리하면 컴포넌트 코드가 간결해지고, 상태 로직을 재사용할 수 있습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;// src/reducers/counterReducer.js
export const ACTION_TYPE = {
    INCREMENT: &amp;#39;INCREMENT&amp;#39;,
    DECREMENT: &amp;#39;DECREMENT&amp;#39;,
    RESET: &amp;#39;RESET&amp;#39;,
}

export function counterReducer(state, action) {
    switch (action.type) {
        case ACTION_TYPE.INCREMENT:
            return { ...state, counter: state.counter + 1 }
        case ACTION_TYPE.DECREMENT:
            return { ...state, counter: state.counter - 1 }
        case ACTION_TYPE.RESET:
            return { ...state, counter: 0 }
        default:
            return state
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;// src/components/Counter.js
import React, { useReducer } from &amp;#39;react&amp;#39;
import { counterReducer, ACTION_TYPE } from &amp;#39;../reducers/counterReducer&amp;#39;

function Counter() {
    const [state, dispatch] = useReducer(counterReducer, {
        counter: 0,
        name: &amp;#39;counter&amp;#39;,
    })

    return (
        &amp;lt;div&amp;gt;
            &amp;lt;h1&amp;gt;
                {state.name}: {state.counter}
            &amp;lt;/h1&amp;gt;
            &amp;lt;button onClick={() =&amp;gt; dispatch({ type: ACTION_TYPE.INCREMENT })}&amp;gt;+1&amp;lt;/button&amp;gt;
            &amp;lt;button onClick={() =&amp;gt; dispatch({ type: ACTION_TYPE.DECREMENT })}&amp;gt;-1&amp;lt;/button&amp;gt;
            &amp;lt;button onClick={() =&amp;gt; dispatch({ type: ACTION_TYPE.RESET })}&amp;gt;Reset&amp;lt;/button&amp;gt;
        &amp;lt;/div&amp;gt;
    )
}

export default Counter&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;useReducer vs useState&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;useReducer&lt;/code&gt; 와 &lt;code&gt;useState&lt;/code&gt; 는 상태를 관리하는 두 가지 방법입니다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;useState&lt;/code&gt; 는 간단한 상태 관리에 적합하고, &lt;code&gt;useReducer&lt;/code&gt; 는 복잡한 상태 관리에 적합합니다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;useState&lt;/code&gt; 는 상태를 변경하는 로직이 컴포넌트 안에 선언되어 있어 재사용이 어렵고, 상태 관리가 복잡해질수록 코드가 복잡해 관리가 어려워집니다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;useReducer&lt;/code&gt; 는 상태를 변경하는 로직을 컴포넌트 바깥에 선언하여 재사용이 용이하고, 상태 관리가 복잡해져도 코드가 간결해집니다.&lt;/p&gt;
&lt;h2&gt;todolist를 useReducer로 변경하기&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://odada.me/314&quot;&gt;todo list&lt;/a&gt;&lt;/p&gt;</description>
      <category>Front/React</category>
      <author>oodada</author>
      <guid isPermaLink="true">https://oddcode.tistory.com/317</guid>
      <comments>https://oddcode.tistory.com/317#entry317comment</comments>
      <pubDate>Tue, 19 Mar 2024 09:47:48 +0900</pubDate>
    </item>
  </channel>
</rss>