<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>2_hyeonju.log</title>
        <link>https://velog.io/</link>
        <description>프론드엔드 개발자 이현주 입니다.</description>
        <lastBuildDate>Tue, 14 Apr 2026 08:22:00 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>2_hyeonju.log</title>
            <url>https://velog.velcdn.com/images/2_hyeonju/profile/8152e3ba-e5fd-4f85-a288-0777ea5ee3bc/image.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. 2_hyeonju.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/2_hyeonju" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[프론드엔드가 Express, Prisma, PostgreSQL 써본 후기]]></title>
            <link>https://velog.io/@2_hyeonju/%EB%B0%B1%EC%97%94%EB%93%9C-%EA%B3%B5%EB%B6%80</link>
            <guid>https://velog.io/@2_hyeonju/%EB%B0%B1%EC%97%94%EB%93%9C-%EA%B3%B5%EB%B6%80</guid>
            <pubDate>Tue, 14 Apr 2026 08:22:00 GMT</pubDate>
            <description><![CDATA[<p>MOTI 백엔드를 구현하면서 처음으로 Express.js, Prisma, PostgreSQL를 써봤다. 처음 사용한 기술이다보니 한번 개념을 정리해보려고 한다.</p>
<hr>
<h2 id="expressjs">Express.js</h2>
<p><strong>Express.js</strong>는 Node.js 위에서 동작하는 웹 프레임워크다. Node.js만으로도 서버를 만들 수 있지만, HTTP 요청/응답 처리, 라우팅, 미들웨어 구성 등을 직접 다 구현해야 해서 번거롭다. Express는 이런 것들을 간편하게 처리할 수 있도록 도와준다.</p>
<h3 id="미들웨어">미들웨어</h3>
<p>요청이 들어오면 응답이 나가기 전까지 거치는 함수들의 체인이라고 보면 된다. 미들웨어 함수는 요청 객체(<code>req</code>), 응답 객체(<code>res</code>), 그리고 다음 미들웨어로 넘기는 <code>next</code> 함수에 접근할 수 있다.</p>
<pre><code class="language-typescript">app.use((req, res, next) =&gt; {
  console.log(&#39;요청 시각:&#39;, Date.now());
  next();
});</code></pre>
<p>위에서 아래로 순서대로 실행되고, <code>next()</code>를 호출해야 다음 미들웨어로 넘어간다. <strong><code>next()</code>를 호출하지 않으면 요청이 거기서 멈춰버린다.</strong> 인증, 에러 처리, 로깅 같은 공통 로직을 미들웨어로 만들어두면 각 라우트에서 중복 코드 없이 재사용할 수 있다.</p>
<pre><code class="language-typescript">app.use(helmet());       // 보안 헤더 설정
app.use(cors());         // CORS 허용
app.use(express.json()); // 요청 바디를 JSON으로 파싱
app.use(&#39;/api&#39;, routes); // 라우팅</code></pre>
<h3 id="라우팅">라우팅</h3>
<p>URL 경로와 HTTP 메서드를 조합해서 어떤 요청이 어떤 함수로 연결될지 정의하는 것이다. <code>express.Router()</code>를 사용하면 관련된 라우트끼리 모듈로 묶어서 관리할 수 있다.</p>
<pre><code class="language-typescript">const requireAuth = (req, res, next) =&gt; {
  const token = req.headers.authorization?.replace(&#39;Bearer &#39;, &#39;&#39;);
  if (!token) return res.status(401).json({ error: &#39;인증 필요&#39; });
  next();
};</code></pre>
<p>이렇게 만든 미들웨어를 라우트에 끼워넣으면 해당 라우트에 접근하려면 반드시 인증을 거쳐야 한다.</p>
<pre><code class="language-typescript">const router = express.Router();

router.get(&#39;/&#39;, requireAuth, asyncHandler(getMaterials));
router.post(&#39;/&#39;, requireAuth, asyncHandler(createMaterial));
router.put(&#39;/:id&#39;, requireAuth, asyncHandler(updateMaterial));
router.delete(&#39;/:id&#39;, requireAuth, asyncHandler(deleteMaterial));

module.exports = router;</code></pre>
<p>라우터를 파일별로 나눠두면 코드 관리가 훨씬 편해진다.</p>
<hr>
<h2 id="postgresql">PostgreSQL</h2>
<p><strong>PostgreSQL</strong>은 관계형 데이터베이스다. 데이터를 테이블 형태로 저장하고, 테이블 간의 관계를 정의할 수 있다.</p>
<p>MOTI에서는 사용자, 재료, 작품 데이터가 서로 연결되어 있어서 관계형 DB가 적합했다. 예를 들어 하나의 작품에 여러 재료가 쓰이고, 하나의 재료가 여러 작품에 쓰일 수 있는 <strong>다대다(N:M) 관계</strong>를 표현해야 했다.</p>
<h3 id="외래키-foreign-key">외래키 (Foreign Key)</h3>
<p>다른 테이블의 데이터를 참조할 때 사용하는 키다. 예를 들어 <code>Material</code> 테이블에 <code>userId</code>가 있으면, 이 값이 반드시 <code>User</code> 테이블에 존재하는 id여야 한다는 걸 DB가 강제해준다.</p>
<pre><code class="language-prisma">model Material {
  userId String
  user   User   @relation(fields: [userId], references: [id])
  // userId는 반드시 User 테이블의 id를 참조해야 함
}</code></pre>
<h3 id="참조-무결성-referential-integrity">참조 무결성 (Referential Integrity)</h3>
<p>외래키로 연결된 데이터가 항상 일관성 있게 유지되는 것을 말한다. 존재하지 않는 유저의 재료가 저장된다거나, 삭제된 카테고리가 재료에 남아있는 상황을 방지해준다. 이게 보장되지 않으면 DB에 연결이 끊긴 고아 데이터가 생기게 된다.</p>
<h3 id="정규화-normalization">정규화 (Normalization)</h3>
<p>DB 설계에서 <strong>정규화</strong>는 데이터 중복을 줄이고 무결성을 높이는 과정이다.</p>
<p>처음엔 재료의 카테고리와 특징을 이렇게 배열로 저장했다.</p>
<pre><code class="language-prisma">// 배열로 저장
model Material {
  category  String[]  // [&quot;키링&quot;, &quot;휴대폰고리&quot;]
  features  String[]  // [&quot;유광&quot;, &quot;투명&quot;]
}</code></pre>
<p>이렇게 하면 여러 값이 하나의 필드에 들어가서 <strong>제1정규형(1NF)을 위반</strong>한다. 원자값(더 이상 쪼갤 수 없는 값)만 저장해야 한다는 원칙에 어긋나는 것이다.
효율적인 쿼리가 어렵고, 카테고리 이름을 바꾸려면 모든 레코드를 일일이 수정해야 한다는 문제도 있었다.</p>
<p>또 재료의 타입과 모양은 이렇게 문자열로만 저장했다.</p>
<pre><code class="language-prisma">// 외래키 없이 문자열로
model Material {
  type   String  // &quot;아크릴&quot;
  shape  String  // &quot;원형&quot;
}</code></pre>
<p>별도의 마스터 테이블을 만들어놨지만 실제로 외래키로 연결하지 않아서 존재하지 않는 타입도 저장할 수 있는 상태였다. 이를 <strong>의사 정규화</strong>라고 하는데, 보기엔 정규화된 것 같지만 실제로는 참조 무결성이 전혀 보장되지 않는 구조다.</p>
<p>처음엔 시스템에서 기본으로 제공하는 값들(재질 종류, 모양 종류 등)을 <strong>마스터 테이블</strong>로 관리하려 했다. 근데 마스터 테이블은 외래키로 연결되지 않아서 <strong>폭포수(Cascade) 설계</strong>가 불가능하다. 상위 데이터를 삭제해도 하위 데이터가 자동으로 따라 삭제되지 않아 고아 데이터가 남게 된다. 시스템 고정값이든 사용자 정의값이든 상관없이 <strong>정규화된 참조 테이블 + 외래키 + Cascade</strong>로 관계를 명확히 정의하는 게 맞는 방식이었다.</p>
<h3 id="정션-테이블-junction-table">정션 테이블 (Junction Table)</h3>
<p>다대다 관계를 표현할 때 사용한다. 두 테이블을 직접 연결하는 대신, 중간 테이블을 두는 방식이다.</p>
<pre><code class="language-prisma">// ✅ 정규화된 설계 - 정션 테이블 사용
model MaterialCategoryAssignment {
  id         String           @id @default(cuid())
  materialId String
  categoryId String
  material   Material         @relation(fields: [materialId], references: [id], onDelete: Cascade)
  category   MaterialCategory @relation(fields: [categoryId], references: [id], onDelete: Cascade)
  createdAt  DateTime         @default(now())

  @@unique([materialId, categoryId])  // 중복 방지
}</code></pre>
<p><code>onDelete: Cascade</code>를 설정하면 카테고리를 삭제할 때 연결된 정션 테이블 레코드가 자동으로 삭제된다. Prisma 공식 문서에서도 이렇게 설명하고 있다.</p>
<blockquote>
<p>&quot;onDelete: Cascade means that deleting a User record will also delete all related Post records.&quot;
— Prisma 공식 문서</p>
</blockquote>
<p>이전 방식에서는 배열에 값이 남아 고아 데이터가 생겼는데, 이제는 DB가 알아서 정리해준다.</p>
<p>쿼리도 훨씬 명확해졌다.</p>
<pre><code class="language-prisma">// ❌ 변경 전 - 배열 필터링
const materials = await prisma.material.findMany({
  where: { category: { has: &quot;비즈&quot; } },
});

// ✅ 변경 후 - 정션 테이블 조인
const materials = await prisma.material.findMany({
  where: {
    categories: {
      some: { category: { name: &quot;비즈&quot; } },
    },
  },
});</code></pre>
<p>처음엔 왜 이렇게 복잡하게 설계해야 하나 싶었는데, 직접 문제를 겪고 나서 정규화가 왜 필요한지 체감이 됐다.</p>
<hr>
<h2 id="prisma">Prisma</h2>
<p><strong>Prisma</strong>는 TypeScript 환경에서 PostgreSQL을 편하게 사용할 수 있게 해주는 ORM(Object Relational Mapping)이다. SQL을 직접 작성하는 대신 TypeScript 코드로 DB를 다룰 수 있다.
백엔드를 처음 구현하면서 SQL을 따로 배워야 하나 고민했는데, Prisma 덕분에 TypeScript 문법 그대로 DB를 다룰 수 있어서 진입 장벽이 낮았다.</p>
<h3 id="스키마-정의">스키마 정의</h3>
<p>Prisma는 <code>schema.prisma</code> 파일에서 DB 구조를 정의한다.</p>
<pre><code class="language-prisma">model Material {
  id       String  @id @default(cuid())
  userId   String
  user     User    @relation(fields: [userId], references: [id], onDelete: Cascade)
  name     String
  stock    Int     @default(0)
}</code></pre>
<p>스키마를 수정하고 아래 명령어를 실행하면 DB에 자동으로 반영된다. SQL 마이그레이션 파일도 자동으로 생성되어 변경 이력을 관리할 수 있다.</p>
<pre><code class="language-bash">npx prisma migrate dev</code></pre>
<h3 id="타입-안전한-쿼리">타입 안전한 쿼리</h3>
<p>Prisma의 가장 큰 장점은 쿼리 결과에 타입이 자동으로 붙는다는 점이다.</p>
<pre><code class="language-typescript">// 반환 타입이 자동으로 Material[]로 추론됨
const materials = await prisma.material.findMany({
  where: { userId: req.user.userId },
  orderBy: { createdAt: &#39;desc&#39; },
});</code></pre>
<p>SQL을 직접 쓰면 결과 타입을 수동으로 지정해야 해서 실수가 생기기 쉬운데, Prisma는 스키마 기반으로 타입을 자동 생성해줘서 IDE 자동완성도 잘 되고 엉뚱한 필드명을 쓰면 바로 오류로 잡아줬다. 프론트엔드에서 TypeScript를 쓰던 감각 그대로 백엔드 코드를 작성할 수 있었던 게 생각보다 큰 도움이 됐다.</p>
<h3 id="관계-데이터-가져오기">관계 데이터 가져오기</h3>
<pre><code class="language-typescript">// 작품과 사용한 재료 목록을 한 번에 가져오기
const product = await prisma.product.findUnique({
  where: { id: productId },
  include: {
    productMaterials: {
      include: { material: true },
    },
  },
});</code></pre>
<p><code>include</code>로 연결된 테이블 데이터를 한 번에 가져올 수 있다.  SQL의 JOIN을 직접 작성했다면 꽤 복잡했을 쿼리인데, include 한 줄로 해결되니까 관계형 DB가 처음인 입장에서도 부담이 덜했다.</p>
<hr>
<h2 id="마치며">마치며</h2>
<p>처음 백엔드를 구현하면서 처음 이 세 기술을 사용해 보았는데 괜,,,찮았다. 나름? 기존에 개발하고 있는 프로젝트에서 가끔 백엔드 코드도 보곤 해서 그런지 컨트롤러, 라우트 서비스 파일 등등 그 흐름을 이해하는 데에는 문제없었다! 단지 이해도가 아직 낮아서 좋은 퀄리티의 코드 구현이 잘 안 된 것 같아서 아쉽다. 특히 DB 설계에서 배열로 저장했다가 뒤엎고, 마스터 테이블 만들었다가 또 뒤엎고~ 두 번 갈아엎고 나서야 정규화가 왜 필요한지 머리가 아닌 손으로 이해했다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[백엔드는 처음이라 — MOTI 기술 스택 선택기]]></title>
            <link>https://velog.io/@2_hyeonju/%ED%94%84%EB%A1%A0%ED%8A%B8%EB%A7%8C-%ED%95%98%EB%8D%98-%EB%82%B4%EA%B0%80-%EB%B0%B1%EC%97%94%EB%93%9C%EB%A5%BC-%EC%A7%9C%EB%B4%A4%EB%8B%A4-MOTI-%EA%B8%B0%EC%88%A0-%EC%8A%A4%ED%83%9D</link>
            <guid>https://velog.io/@2_hyeonju/%ED%94%84%EB%A1%A0%ED%8A%B8%EB%A7%8C-%ED%95%98%EB%8D%98-%EB%82%B4%EA%B0%80-%EB%B0%B1%EC%97%94%EB%93%9C%EB%A5%BC-%EC%A7%9C%EB%B4%A4%EB%8B%A4-MOTI-%EA%B8%B0%EC%88%A0-%EC%8A%A4%ED%83%9D</guid>
            <pubDate>Sat, 28 Mar 2026 07:43:20 GMT</pubDate>
            <description><![CDATA[<p>이번엔 처음으로 백엔드를 직접 구현해봤다. 어떤 기술을 선택했고 왜 그런 선택을 했는지 정리해두려 한다.</p>
<hr>
<h2 id="비용은-최소로">비용은 최소로</h2>
<p>앱을 만드려했던 기획 의도 자체가 비즈 키링을 디자인하는 친구의 편의를 위한 것이였다. 그래서 사실 홍보를 한다거나 수익화를 한다거나 그런 생각이 없어서 비용은 최소한으로 들게 하고싶었다. 최대한 무료!!!!를 찾아서 구현했다. 그래서 내가 결정한 서비스는 아래와 같다.</p>
<ul>
<li><strong>서버 호스팅</strong>: Railway (Trial 플랜 이후 $5/월)</li>
<li><strong>데이터베이스</strong>: Railway 내장 PostgreSQL (무료)</li>
<li><strong>이미지 저장</strong>: Cloudinary (무료 플랜)</li>
<li><strong>AI 추천</strong>: Groq (무료 티어)</li>
</ul>
<p>각 서비스의 무료 한계는 이렇다.</p>
<p><strong>Railway</strong>
신규 가입 시 $5 크레딧을 일회성으로 제공하며, <strong>30일 또는 크레딧 소진 중 먼저 도래하는 시점</strong>에 Trial이 종료된다. 크레딧이 남아있어도 30일이 지나면 Hobby 플랜($5/월)으로 전환해야 하고, 남은 크레딧은 이월된다. Hobby 플랜은 $5 구독료에 $5의 리소스 사용 크레딧이 포함되어 있어서, 사용량이 $5 이하면 추가 요금이 없다.</p>
<p><strong>Cloudinary</strong>
무료 플랜은 월 25 크레딧을 제공하며, 1크레딧은 이미지 변환 1,000회, 스토리지 1GB, 또는 대역폭 1GB에 해당한다. 기간 제한 없이 계속 사용할 수 있다.</p>
<p><strong>Groq (Llama 3.3 70B 기준)</strong>
신용카드 없이 사용할 수 있으며, 분당 30 요청(RPM), 분당 약 12,000 토큰(TPM), 하루 최대 1,000 요청까지 사용할 수 있다.</p>
<hr>
<h2 id="supabase를-고민하다-포기한-이유">Supabase를 고민하다 포기한 이유</h2>
<p>처음엔 <strong>Supabase</strong>를 쓸까 고민했다. 현재 일하는 곳에서 Supabase를 쓰는데 테이블 관리나 스토리지도 한번에 되고 되게 편해서 좋은 이미지였기 때문이다.</p>
<p>그런데 알아보니 공식 문서에 <strong>&quot;7일간 서비스 미사용 시 프로젝트가 일시정지된다&quot;</strong> 는 정책이 있었다. 그래서 바로 포기하고 Railway를 선택했다.ㅋㅋ</p>
<h3 id="무료-플랜-비교">무료 플랜 비교</h3>
<table>
<thead>
<tr>
<th></th>
<th>Railway Trial</th>
<th>Supabase Free</th>
</tr>
</thead>
<tbody><tr>
<td>가격</td>
<td>$5 크레딧 (일회성)</td>
<td>무료</td>
</tr>
<tr>
<td>DB</td>
<td>PostgreSQL 포함</td>
<td>PostgreSQL 500MB</td>
</tr>
<tr>
<td>인증</td>
<td>직접 구현</td>
<td>내장 제공</td>
</tr>
<tr>
<td>스토리지</td>
<td>별도 필요</td>
<td>1GB 내장</td>
</tr>
<tr>
<td>비활성 정책</td>
<td>없음</td>
<td><strong>7일 미사용 시 일시정지</strong></td>
</tr>
</tbody></table>
<h3 id="유료-전환-시-최저-플랜-비교">유료 전환 시 최저 플랜 비교</h3>
<table>
<thead>
<tr>
<th></th>
<th>Railway Hobby</th>
<th>Supabase Pro</th>
</tr>
</thead>
<tbody><tr>
<td>가격</td>
<td><strong>$5/월</strong></td>
<td>$25/월</td>
</tr>
<tr>
<td>DB</td>
<td>PostgreSQL 포함</td>
<td>PostgreSQL 8GB</td>
</tr>
<tr>
<td>인증</td>
<td>직접 구현</td>
<td>내장 제공</td>
</tr>
<tr>
<td>스토리지</td>
<td>별도 (Cloudinary 등)</td>
<td>100GB 내장</td>
</tr>
<tr>
<td>비활성 정책</td>
<td>없음</td>
<td>없음</td>
</tr>
<tr>
<td>유연성</td>
<td>높음 (자유로운 커스텀)</td>
<td>낮음 (플랫폼 종속)</td>
</tr>
</tbody></table>
<p>Railway는 저렴하지만 그만큼 인증, 스토리지를 직접 구현해야 하고, Supabase는 비싸지만 그만큼 기능이 많이 포함되어 있다.</p>
<hr>
<h2 id="기술-스택">기술 스택</h2>
<h3 id="typescript">TypeScript</h3>
<p>프론트엔드에서 이미 TypeScript를 쓰고 있었고, 백엔드도 같은 언어를 쓰면 여러 이점이 있었다. 런타임 에러를 컴파일 타임에 잡을 수 있고, API 응답 타입을 프론트와 공유할 수 있다는 점이 특히 좋았다.</p>
<h3 id="expressjs">Express.js</h3>
<p>Node.js 웹 프레임워크 중 가장 검증되고 안정적이며 커뮤니티가 크다. 웹 프론트엔드 경험이 있어서 HTTP 메서드, 미들웨어 개념이 익숙했고, 검색하면 레퍼런스가 잘 나온다는 것도 중요했다.</p>
<pre><code class="language-typescript">app.use(helmet());       // 보안
app.use(cors());         // CORS
app.use(express.json()); // 바디 파싱
app.use(&#39;/api&#39;, routes); // 라우팅</code></pre>
<h3 id="prisma">Prisma</h3>
<p>SQL을 직접 작성하는 대신 <strong>Prisma ORM</strong>을 선택했다. TypeScript와 완벽하게 통합되어 타입 안전한 쿼리를 작성할 수 있고, 스키마만 수정하면 마이그레이션도 자동으로 처리해준다. IDE 자동완성도 잘 돼서 생산성이 높았다.</p>
<pre><code class="language-typescript">// 쿼리 결과 타입이 자동으로 추론됨
const materials = await prisma.material.findMany({
  where: { userId: req.user.userId },
});</code></pre>
<h3 id="postgresql">PostgreSQL</h3>
<p>사용자, 재료, 제품 간의 관계를 표현하기 좋은 관계형 DB다. Railway에서 무료로 제공하고, 검증된 안정성이 있어서 선택했다.</p>
<h3 id="cloudinary">Cloudinary</h3>
<p>이미지 저장 서비스로 Cloudinary를 선택했다. Node.js SDK가 잘 되어 있고, 업로드된 이미지를 자동으로 최적화해주고 CDN도 포함되어 있다.</p>
<hr>
<h2 id="프로젝트-구조">프로젝트 구조</h2>
<p><strong>레이어드 아키텍처</strong>를 적용했다. routes → controllers → services → prisma 흐름으로 역할을 나눴다.</p>
<pre><code>src/
├── config/          # 설정 (DB, 환경변수)
├── controllers/     # HTTP 요청/응답 처리
├── services/        # 비즈니스 로직
├── routes/          # 라우트 정의
├── middleware/      # JWT 인증, 에러 핸들링
├── validators/      # Zod 유효성 검사
├── utils/           # 유틸 함수
└── types/           # 타입 정의</code></pre><p>계층을 나누니까 비즈니스 로직을 바꿔도 컨트롤러는 그대로고, 유지보수가 훨씬 편했다. 프론트엔드에서 <code>app/</code> → <code>src/</code>로 나눴던 것처럼 백엔드도 역할별로 폴더를 나눠두니 구조가 명확해졌다.</p>
<hr>
<h2 id="데이터베이스-설계">데이터베이스 설계</h2>
<p>주요 모델은 다섯 가지다.</p>
<ul>
<li><strong>User</strong> : 네이버 로그인 정보, Refresh Token 저장</li>
<li><strong>Material</strong> : 재료 정보 (이름, 재질, 모양, 특징, 재고 등)</li>
<li><strong>Product</strong> : 작품 정보 (이름, 카테고리 등)</li>
<li><strong>ProductMaterial</strong> : 작품-재료 다대다 관계 (사용 수량 포함)</li>
<li><strong>ColorPalette</strong> : 사용자 색상 팔레트</li>
</ul>
<p>재료와 작품은 N:M 관계라 중간 테이블인 <code>ProductMaterial</code>로 연결했다. 예를 들어 하나의 작품에 여러 재료가 쓰이고, 하나의 재료가 여러 작품에 쓰일 수 있는 구조다.</p>
<pre><code class="language-prisma">model Product {
  materials ProductMaterial[]
}

model Material {
  products ProductMaterial[]
}

model ProductMaterial {
  productId  String
  materialId String
  quantity   Int
}</code></pre>
<hr>
<h2 id="인증-구조">인증 구조</h2>
<p>로그인은 <strong>네이버 OAuth 2.0</strong>으로 처리하고, 이후 API 요청은 <strong>JWT</strong>로 인증한다.</p>
<p>토큰은 두 가지를 사용한다.</p>
<ul>
<li><strong>Access Token</strong> (단기, 15분) : API 요청 시 사용. 짧게 설정해서 탈취당해도 빨리 만료되도록 했다.</li>
<li><strong>Refresh Token</strong> (장기, 7일) : Access Token 만료 시 재발급용. DB에 저장해서 탈취 시 폐기할 수 있다.</li>
</ul>
<p>Access Token이 만료되면 Refresh Token으로 자동 재발급 후 재시도하는 구조라 사용자가 자주 로그인해야 하는 불편함을 줄일 수 있다.</p>
<hr>
<h2 id="마치며">마치며</h2>
<p>처음 백엔드를 구현하는거라 시작 전에 많이 겁먹었는데 막상 해보니 그만큼 어렵지는 않았던 것 같다.</p>
<p>Prisma는 SQL을 직접 안 짜도 돼서 편했고, Express의 미들웨어 시스템도 생각보다 직관적이었다.</p>
<blockquote>
<p>웹 프론트엔드 경험이 있으니까 구조 자체를 이해하는 건 어렵지 않았는데, 막상 직접 설계하려니 고려할 게 많고 너무 복잡했다.</p>
</blockquote>
<p>아직 테스트 코드도 없고 개선할 부분이 많지만, 일단 돌아가는 걸 만드는 게 먼저라고 생각했다. 다음 글에서는 내가 사용한 기술 스택에 대해 좀 더 알아가보려 한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[React Native Expo 안드로이드 네이버 로그인 구현하기]]></title>
            <link>https://velog.io/@2_hyeonju/React-Native-Expo-%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C-%EB%84%A4%EC%9D%B4%EB%B2%84-%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@2_hyeonju/React-Native-Expo-%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C-%EB%84%A4%EC%9D%B4%EB%B2%84-%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0</guid>
            <pubDate>Wed, 18 Mar 2026 18:06:41 GMT</pubDate>
            <description><![CDATA[<p>백엔드 부분 구현 및 연동에 앞서, 먼저 네이버 소셜 로그인 구현 과정을 정리해두려 한다.</p>
<p>React Native + Expo 환경에서 안드로이드 네이버 로그인을 구현하는 글이 생각보다 잘 안 보여서, 직접 겪은 과정을 빠르게 기록해두려 한다. 구현에는 <a href="https://github.com/crossplatformkorea/react-native-naver-login">@react-native-seoul/naver-login</a> 라이브러리를 사용했다.</p>
<p><img src="https://velog.velcdn.com/images/2_hyeonju/post/618ae46f-4ab8-46ef-a5cc-ed78deb30d16/image.gif" alt="실제 로그인 구현 화면"></p>
<hr>
<h2 id="전체-흐름">전체 흐름</h2>
<p>모바일과 백엔드가 역할을 나눠서 처리하는 구조로 설계했다.</p>
<pre><code>모바일 앱
  → 네이버 SDK로 로그인
  → accessToken을 백엔드로 전송
백엔드
  → 받은 토큰으로 네이버 API 호출 (사용자 프로필 조회)
  → DB에 사용자 저장/업데이트
  → JWT 발급 후 앱으로 반환
모바일 앱
  → 로그인 완료</code></pre><p>모바일은 토큰만 받아서 넘기고, 프로필 조회와 DB 저장은 전부 백엔드에서 처리한다. 역할이 명확하게 분리되어 있어서 구조 자체는 깔끔했다.
웹 소셜 로그인만 해보다가 앱을 해보았는데 복잡하지 않고 생각보다 훨씬 편리했다!!!</p>
<hr>
<h2 id="모바일-구현">모바일 구현</h2>
<h3 id="expo-설정">Expo 설정</h3>
<p>먼저 <code>app.json</code>에 네이버 로그인 플러그인을 추가해준다.</p>
<pre><code class="language-json">{
  &quot;expo&quot;: {
    &quot;android&quot;: {
      &quot;package&quot;: &quot;com.moti.app&quot;
    },
    &quot;ios&quot;: {
      &quot;bundleIdentifier&quot;: &quot;com.moti.app&quot;
    },
    &quot;plugins&quot;: [
      &quot;expo-router&quot;,
      [
        &quot;@react-native-seoul/naver-login&quot;,
        {
          &quot;urlScheme&quot;: &quot;com.moti.app&quot;
        }
      ]
    ]
  }
}</code></pre>
<blockquote>
<p><code>urlScheme</code>은 <code>android.package</code>, <code>ios.bundleIdentifier</code>와 동일한 값으로 맞춰줘야 한다.</p>
</blockquote>
<h3 id="네이버-sdk-초기화">네이버 SDK 초기화</h3>
<p>로그인 레이아웃에서 SDK를 초기화해준다.</p>
<pre><code class="language-typescript">// app/auth/_layout.tsx
import NaverLogin from &#39;@react-native-seoul/naver-login&#39;;
import { Stack } from &#39;expo-router&#39;;
import { useEffect } from &#39;react&#39;;

export default function AuthLayout() {
  const NAVER_CLIENT_ID = process.env.EXPO_PUBLIC_NAVER_CLIENT_ID;
  const NAVER_CLIENT_SECRET = process.env.EXPO_PUBLIC_NAVER_CLIENT_SECRET;

  useEffect(() =&gt; {
    NaverLogin.initialize({
      appName: &#39;moti&#39;,
      consumerKey: NAVER_CLIENT_ID,
      consumerSecret: NAVER_CLIENT_SECRET,
      serviceUrlSchemeIOS: &#39;com.moti.app&#39;,
      disableNaverAppAuthIOS: true, // 네이버 앱 없이 웹으로 로그인
    });
  }, []);

  return (
    &lt;Stack
      screenOptions={{
        headerShown: false,
        animation: &#39;fade&#39;,
        contentStyle: { backgroundColor: &#39;#F9F5EB&#39; },
      }}
    &gt;
      &lt;Stack.Screen name=&quot;login&quot; /&gt;
    &lt;/Stack&gt;
  );
}</code></pre>
<h3 id="로그인-버튼-구현">로그인 버튼 구현</h3>
<pre><code class="language-typescript">// app/auth/login.tsx
import NaverLogin from &#39;@react-native-seoul/naver-login&#39;;
import { useAuthStore } from &#39;@store/authStore&#39;;
import { authApi } from &#39;@services/api&#39;;
import { useState } from &#39;react&#39;;
import { Alert, TouchableOpacity, Image } from &#39;react-native&#39;;
import { router } from &#39;expo-router&#39;;

export default function LoginScreen() {
  const { loginWithNaver } = useAuthStore();
  const [isLoading, setIsLoading] = useState(false);

  const handleNaverLogin = async () =&gt; {
    setIsLoading(true);
    try {
      // 1. 네이버 SDK로 로그인
      const naverResponse = await NaverLogin.login();

      if (naverResponse.isSuccess &amp;&amp; naverResponse.successResponse) {
        const { accessToken } = naverResponse.successResponse;

        // 2. 토큰만 백엔드로 전송
        const { user } = await authApi.naverLogin(accessToken);

        // 3. 사용자 정보 저장
        loginWithNaver(user);

        // 4. 메인 화면으로 이동
        router.replace(&#39;/(tabs)&#39;);
      } else if (naverResponse.failureResponse) {
        throw new Error(naverResponse.failureResponse.message || &#39;로그인에 실패했습니다&#39;);
      }
    } catch (e) {
      const errorMessage = e instanceof Error ? e.message : &#39;로그인에 실패했습니다&#39;;
      Alert.alert(&#39;로그인 실패&#39;, errorMessage);
    } finally {
      setIsLoading(false);
    }
  };

  return (
    &lt;TouchableOpacity onPress={handleNaverLogin} disabled={isLoading}&gt;
      &lt;Image
        source={require(&#39;@/assets/images/NAVER_login_button.png&#39;)}
        style={{ width: &#39;100%&#39;, height: 48 }}
        resizeMode=&quot;contain&quot;
      /&gt;
    &lt;/TouchableOpacity&gt;
  );
}</code></pre>
<h3 id="api-클라이언트">API 클라이언트</h3>
<pre><code class="language-typescript">// src/services/api.ts
import { Platform } from &#39;react-native&#39;;

const API_URL = process.env.EXPO_PUBLIC_API_URL || &#39;http://localhost:4000&#39;;

const getApiUrl = (): string =&gt; {
  if (Platform.OS === &#39;android&#39; &amp;&amp; API_URL.includes(&#39;localhost&#39;)) {
    return API_URL.replace(&#39;localhost&#39;, &#39;10.0.2.2&#39;);
  }
  return API_URL;
};

const apiClient = axios.create({
  baseURL: `${getApiUrl()}/api`,
});

export const authApi = {
  async naverLogin(accessToken: string) {
    const response = await apiClient.post(&#39;/auth/naver&#39;, { accessToken });

    if (response.data.success &amp;&amp; response.data.data) {
      const { user, tokens } = response.data.data;

      // 토큰 저장
      await tokenStorage.setTokens(tokens.accessToken, tokens.refreshToken);

      return { user, tokens };
    }

    throw new Error(response.data.error?.message || &#39;Login failed&#39;);
  },
};</code></pre>
<hr>
<h2 id="백엔드-구현">백엔드 구현</h2>
<h3 id="엔드포인트">엔드포인트</h3>
<p><code>POST /api/auth/naver</code>로 accessToken을 받아서 처리한다.</p>
<ol>
<li>받은 토큰으로 네이버 API(<code>GET https://openapi.naver.com/v1/nid/me</code>) 호출</li>
<li>응답으로 받은 사용자 프로필로 DB 저장/업데이트</li>
<li>JWT 발급 후 반환</li>
</ol>
<p><strong>네이버 API 응답 예시:</strong></p>
<pre><code class="language-json">{
  &quot;resultcode&quot;: &quot;00&quot;,
  &quot;message&quot;: &quot;success&quot;,
  &quot;response&quot;: {
    &quot;id&quot;: &quot;12345678&quot;,
    &quot;email&quot;: &quot;user@naver.com&quot;,
    &quot;nickname&quot;: &quot;닉네임&quot;,
    &quot;name&quot;: &quot;홍길동&quot;,
    &quot;profile_image&quot;: &quot;https://...&quot;
  }
}</code></pre>
<p><strong>앱으로 반환하는 응답 예시:</strong></p>
<pre><code class="language-json">{
  &quot;success&quot;: true,
  &quot;data&quot;: {
    &quot;user&quot;: {
      &quot;id&quot;: &quot;clxxx...&quot;,
      &quot;naverId&quot;: &quot;12345678&quot;,
      &quot;naverEmail&quot;: &quot;user@naver.com&quot;,
      &quot;naverName&quot;: &quot;홍길동&quot;,
      &quot;naverProfileImage&quot;: &quot;https://...&quot;
    },
    &quot;tokens&quot;: {
      &quot;accessToken&quot;: &quot;eyJhbGci...&quot;,
      &quot;refreshToken&quot;: &quot;eyJhbGci...&quot;,
      &quot;expiresIn&quot;: 900
    }
  }
}</code></pre>
<hr>
<h2 id="안드로이드-에뮬레이터-실행하기">안드로이드 에뮬레이터 실행하기</h2>
<p>네이티브 모듈을 사용하는 경우 Expo Go에서는 테스트가 안 되기 때문에 프리빌드 후 에뮬레이터로 실행해야 한다.</p>
<pre><code class="language-bash">npx expo prebuild   # iOS / android 폴더 생성
npx expo run:android</code></pre>
<h3 id="마주쳤던-오류들">마주쳤던 오류들</h3>
<p><strong>1. JDK 미설치 오류</strong>
<code>npx expo run:android</code> 실행 시 <code>Unable to locate a Java Runtime</code> 오류가 발생했다. JDK가 설치되어 있지 않아서 생기는 문제로, 아래 명령어로 설치 후 재시도했다.</p>
<pre><code class="language-bash">brew install openjdk@17</code></pre>
<p><strong>2. 테스트 기기 미실행 오류</strong>
안드로이드 스튜디오에서 에뮬레이터(테스트 기기)를 켜두지 않으면 오류가 발생한다. <code>npx expo run:android</code> 전에 에뮬레이터를 먼저 실행해두자.</p>
<p><strong>3. 안드로이드 스튜디오 경로 오류</strong>
안드로이드 스튜디오에서 프로젝트를 열 때 루트 폴더가 아닌 <strong>android 폴더가 있는 경로</strong>로 열어야 한다.</p>
<p>빌드가 성공적으로 진행 중이라면 아래 아래 이미지가 보일것이다.
<img src="https://velog.velcdn.com/images/2_hyeonju/post/f96f569d-3b18-4fab-9f69-623e5851070d/image.png" alt="빌드 중"></p>
<p>빌드가 성공하면 기존 <code>exp://192.168.x.x:8081</code> 경로에서 <code>com.moti.app://expo-development-client/?url=http://192.168.x.x:8081</code>로 바뀌고, <code>Using Expo Go</code> → <code>Using development build</code>로 변경된 것을 확인할 수 있다.</p>
<hr>
<h2 id="막혔던-부분들">막혔던 부분들</h2>
<h3 id="네이버-sdk-설정">네이버 SDK 설정</h3>
<p>처음에 SDK 초기화를 어디서 해야 할지 몰라서 좀 헤맸다. <code>app.json</code> 플러그인 설정을 빠뜨리면 빌드 자체가 제대로 안 되고, <code>initialize()</code>를 너무 늦게 호출하면 로그인 시도 시 오류가 발생한다. 로그인 화면 레이아웃의 <code>useEffect</code>에서 초기화하는 게 가장 자연스러웠다.</p>
<h3 id="안드로이드-에뮬레이터-localhost-문제">안드로이드 에뮬레이터 localhost 문제</h3>
<p>가장 오래 걸렸던 부분이다.</p>
<p>안드로이드 에뮬레이터에서는 <code>localhost</code>가 호스트 머신이 아닌 에뮬레이터 자신을 가리킨다. 그래서 백엔드 서버가 분명히 실행 중인데도 API 요청이 계속 실패했다.</p>
<p>해결책은 안드로이드일 때 <code>localhost</code>를 <code>10.0.2.2</code>로 바꿔주는 것이다. <code>10.0.2.2</code>는 안드로이드 에뮬레이터에서 호스트 머신을 가리키는 특수 IP다.</p>
<pre><code class="language-typescript">const getApiUrl = (): string =&gt; {
  if (Platform.OS === &#39;android&#39; &amp;&amp; API_URL.includes(&#39;localhost&#39;)) {
    return API_URL.replace(&#39;localhost&#39;, &#39;10.0.2.2&#39;);
  }
  return API_URL;
};</code></pre>
<p>iOS 시뮬레이터는 <code>localhost</code> 그대로 써도 되는데 안드로이드만 이렇게 처리해줘야 한다는 게 포인트다.</p>
<blockquote>
<p>서버는 멀쩡히 켜져 있는데 요청이 안 된다면 이것부터 의심해보자.</p>
</blockquote>
<hr>
<h2 id="마치며">마치며</h2>
<p>백엔드를 처음 구현해봤는데, 네이버 로그인 자체보다 에뮬레이터 실행이 안 돼서 환경 차이를 파악하는 데 시간이 더 걸렸다ㅜㅜㅜㅠ 다음 글에서는 아마도? 백엔드 설계에 대해 작성해볼 예정이다. 최대한 빨리 정리하고 싶은데, 제대로 이해한 다음에 쓰고 싶어 미뤄지는 것 같다...</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Groq로 AI 색상 조합 추천 기능 만들기]]></title>
            <link>https://velog.io/@2_hyeonju/Groq%EB%A1%9C-AI-%EC%83%89%EC%83%81-%EC%A1%B0%ED%95%A9-%EC%B6%94%EC%B2%9C-%EA%B8%B0%EB%8A%A5-%EB%A7%8C%EB%93%A4%EA%B8%B0</link>
            <guid>https://velog.io/@2_hyeonju/Groq%EB%A1%9C-AI-%EC%83%89%EC%83%81-%EC%A1%B0%ED%95%A9-%EC%B6%94%EC%B2%9C-%EA%B8%B0%EB%8A%A5-%EB%A7%8C%EB%93%A4%EA%B8%B0</guid>
            <pubDate>Thu, 12 Mar 2026 20:56:42 GMT</pubDate>
            <description><![CDATA[<p>지난 글에서 홈 화면에 AI 색상 조합 추천 기능이 있다고 소개했었는데, 앱에 AI를 직접 연결해본 건 이번이 처음이라 기록을 남겨두려 한다.</p>
<p>솔직히 좀 설레기도 하고 긴장도 됐는데, 생각보다 훨씬 수월하게 연결됐다. 그 과정을 정리해보려 한다.</p>
<hr>
<h2 id="왜-groq인가">왜 Groq인가?</h2>
<p>AI API를 찾아보다가 <strong>Groq</strong>를 선택했다.</p>
<table>
<thead>
<tr>
<th>특징</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>⚡ 속도</td>
<td>자체 LPU 하드웨어로 초고속 응답</td>
</tr>
<tr>
<td>🆓 무료</td>
<td>오픈 소스 LLM 모델 완전 무료</td>
</tr>
<tr>
<td>🌐 한국어</td>
<td>Llama 모델이 한국어를 잘 이해함</td>
</tr>
<tr>
<td>🔓 호환성</td>
<td>OpenAI API와 동일한 형식</td>
</tr>
</tbody></table>
<p>사이드 프로젝트에 유료 API를 붙이는 건 부담이 있었는데, Groq는 오픈 소스 LLM 모델을 완전 무료로 제공한다. 거기다 OpenAI API와 동일한 형식이라 사용법도 익숙했다.</p>
<p>모델은 <strong>Llama 3.3 70B</strong>를 사용했다. Meta가 개발한 700억 파라미터 오픈 소스 모델로, 한국어 생성 능력이 좋고 JSON 출력 모드도 지원한다.</p>
<hr>
<h2 id="api-연결하기">API 연결하기</h2>
<h3 id="1-api-키-발급">1. API 키 발급</h3>
<p><a href="https://console.groq.com">https://console.groq.com</a> 접속 → 좌측 메뉴 <strong>API Keys</strong> → <strong>Create Key</strong> 버튼 클릭</p>
<h3 id="2-환경-변수-설정">2. 환경 변수 설정</h3>
<pre><code class="language-bash"># .env 파일
EXPO_PUBLIC_GROQ_API_KEY=gsk_xxxxxxxxxxxx</code></pre>
<h3 id="3-api-호출">3. API 호출</h3>
<pre><code class="language-typescript">const response = await fetch(&#39;https://api.groq.com/openai/v1/chat/completions&#39;, {
  method: &#39;POST&#39;,
  headers: {
    &#39;Content-Type&#39;: &#39;application/json&#39;,
    &#39;Authorization&#39;: `Bearer ${apiKey}`,
  },
  body: JSON.stringify({
    model: &#39;llama-3.3-70b-versatile&#39;,
    messages: [{ role: &#39;user&#39;, content: &#39;사용자 프롬프트 내용&#39; }],
    temperature: 0.8,
    max_tokens: 1000,
    response_format: { type: &#39;json_object&#39; }, // JSON 형식 강제
  }),
});

const data = await response.json();
const result = JSON.parse(data.choices[0].message.content);</code></pre>
<p>연결 자체는 금방 됐다. 진짜 손이 많이 간 건 다음 단계였다.</p>
<hr>
<h2 id="프롬프트-설계">프롬프트 설계</h2>
<p>원하는 형태의 응답을 안정적으로 받아내는 게 생각보다 까다로웠다. 핵심 전략은 네 가지였다.</p>
<ol>
<li><strong>역할 부여</strong> : &quot;당신은 비즈 공예 색상 전문가입니다.&quot;</li>
<li><strong>JSON 형식 강제</strong> : <code>response_format: { type: &#39;json_object&#39; }</code> + 프롬프트에도 명시</li>
<li><strong>한국어만 강제</strong> : 한자 사용 금지 가이드라인 명시 (이유는 아래에)</li>
<li><strong>수량 정확성</strong> : 필수 색상 제외하고 남은 개수만 정확히 추천하도록 명시</li>
</ol>
<pre><code class="language-typescript">let prompt = `당신은 비즈 공예 색상 전문가입니다. 사용자에게 어울리는 색상 조합을 추천해주세요.`;

// 필수 색상, 테마, 추가 정보 추가
if (mandatoryColors.length &gt; 0) {
  prompt += `\n\n필수로 포함할 색상: ${mandatoryColors.map(c =&gt; `${c.name}(${c.hex})`).join(&#39;, &#39;)}`;
}
if (theme) prompt += `\n\n원하는 테마/분위기: ${theme}`;

// 남은 개수 계산해서 전달
const remainingCount = colorCount - mandatoryColors.length;
prompt += `\n\n정확히 ${remainingCount}개의 새로운 색상만 추천해주세요.`;

// JSON 형식 지정
prompt += `
다음 형식으로 ONLY JSON 응답해주세요:
{
  &quot;colors&quot;: [{&quot;name&quot;: &quot;색상이름(한국어)&quot;, &quot;hex&quot;: &quot;#색상코드&quot;}],
  &quot;description&quot;: &quot;조합의 분위기 (한국어 1-2 문장)&quot;,
  &quot;reasoning&quot;: &quot;색상이 어울리는 이유 (한국어 1-2 문장)&quot;
}`;</code></pre>
<hr>
<h2 id="마주쳤던-문제들">마주쳤던 문제들</h2>
<h3 id="한자가-섞여-나오는-문제">한자가 섞여 나오는 문제</h3>
<p>한국어 모델인데 응답에 중국어 한자가 섞여 나오는 문제가 있었다.</p>
<pre><code>&quot;시각적趣를 눈길 사로잡을 수 있습니다&quot;
&quot;색상들의明度가 조화를 이룹니다&quot;</code></pre><p>프롬프트에 명시적인 한자 → 한글 변환 가이드를 추가해서 해결했다.</p>
<pre><code>🚫🚫🚫🚫🚫 언어 제한 (절대 위반하지 말것!!!):
- 趣 → &quot;흥미&quot; 또는 &quot;재미&quot;
- 明度 → &quot;밝기&quot;
- 調和 → &quot;조화&quot;
- 淡 → &quot;연한&quot; / 濃 → &quot;진한&quot;
- 深 → &quot;진한&quot;
...더 많은 한자 금지 목록

📝 예시를 따르세요:
좋은 예: &quot;부드러운 분홍과 크림색이 봄날의 느낌을 줍니다.&quot;
나쁜 예: &quot;柔한粉色이春天的趣를給합니다.&quot; (한자 사용 ❌)</code></pre><p>강조 다섯 개 + 좋은 예/나쁜 예까지 넣었다. 아직 다른 예외 한자가 안 나온 것일 수도 있지만, 일단 지금은 한자가 보이지 않고 있다.</p>
<h3 id="응답-길이-조절">응답 길이 조절</h3>
<p>처음엔 너어어어어어무 길게 나와서 짧게 조절했더니 너무 짧게 나왔다. &quot;1-2 문장&quot;으로 명확히 지정하는 걸로 해결했다.</p>
<h3 id="색상-개수가-안-맞는-문제">색상 개수가 안 맞는 문제</h3>
<p>사용자가 필수 색상 2개를 선택하고 총 3개를 원하면, AI는 1개만 추천해야 한다. 처음엔 AI가 알아서 계산해줄 거라 생각했는데 그렇지 않았다.... 직접 계산해서 프롬프트에 넘겨주는 걸로 해결했다.</p>
<hr>
<h2 id="실제-구현-화면">실제 구현 화면</h2>
<p>기본 팔레트 색상과 커스텀 색상을 섞어 3가지, 4가지 조합으로 추천받는 화면이다. AI 응답이 완료되면 결과 화면으로 스크롤이 자동으로 내려가도록 구현했다.
<img src="https://velog.velcdn.com/images/2_hyeonju/post/d6be7d95-11ec-4e2d-a323-d1e00869354d/image.gif" alt="ai 색상 추천"></p>
<hr>
<h2 id="마치며">마치며</h2>
<p>처음으로 AI API를 직접 연결해봤는데, 연결 자체보다 <strong>프롬프트를 다듬는 과정</strong>이 훨씬 손이 많이 갔다.</p>
<blockquote>
<p>AI한테 잘 시키는 것도 실력이라는 걸 몸소 체감했다.</p>
</blockquote>
<p>그래도 무료로 이 정도 퀄리티의 AI 기능을 붙일 수 있다는 게 정말 좋았다. 다음 글에서는 백엔드 연동 과정을 담아볼 예정이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[MOTI — 디자인부터 화면 구현까지]]></title>
            <link>https://velog.io/@2_hyeonju/MOTI-%EB%94%94%EC%9E%90%EC%9D%B8%EB%B6%80%ED%84%B0-%ED%99%94%EB%A9%B4-%EA%B5%AC%ED%98%84%EA%B9%8C%EC%A7%80</link>
            <guid>https://velog.io/@2_hyeonju/MOTI-%EB%94%94%EC%9E%90%EC%9D%B8%EB%B6%80%ED%84%B0-%ED%99%94%EB%A9%B4-%EA%B5%AC%ED%98%84%EA%B9%8C%EC%A7%80</guid>
            <pubDate>Thu, 12 Mar 2026 14:35:33 GMT</pubDate>
            <description><![CDATA[<p>Stitch로 잡은 초기 디자인을 토대로 나머지 모든 화면 디자인을 마무리했고, UI 구현까지 한 번에 진행했다.</p>
<hr>
<h2 id="앱-이름-moti">앱 이름: MOTI</h2>
<p>앱 이름은 <strong>MOTI</strong>로 정했다. Motivation의 앞 4글자를 딴 이름으로, <em>당신의 창작 본능을 깨우고 지치지 않게 관리해주는 비즈 관리 파트너</em>라는 의미를 담았다.</p>
<p>로고는 비즈 4개를 실로 연결하는 컨셉으로 만들었다. 메인 컬러를 넣은 버전과 심플한 버전, 두 가지로 제작했다.</p>
<p><img src="https://velog.velcdn.com/images/2_hyeonju/post/9a77f3ea-bbff-4a70-9c99-887b2c1ee4af/image.png" alt="로고"></p>
<hr>
<h2 id="네비게이션별-메인-화면">네비게이션별 메인 화면</h2>
<p>탭 네비게이션은 총 4개 화면으로 구성했다.</p>
<ul>
<li><strong>홈</strong> : 로그인한 사용자 정보, 기능 요약, AI 색상 조합 추천, 빠른 추가 기능</li>
<li><strong>재료</strong> : 타이틀과 추가 버튼, 검색·필터, 재료 목록</li>
<li><strong>제품</strong> : 타이틀과 추가 버튼, 검색·필터, 제품 목록</li>
<li><strong>프로필</strong> : 사용자 정보, 앱 안내, 계정 관련 정보</li>
</ul>
<p><img src="https://velog.velcdn.com/images/2_hyeonju/post/2c5d792f-e918-4725-8291-2ca1e614b6bf/image.png" alt="네비게이션"></p>
<hr>
<h2 id="ai-색상-조합-추천">AI 색상 조합 추천</h2>
<p>개인적으로 가장 공들인 화면이다.</p>
<p>비즈 개수를 선택하고, 조합에 꼭 들어갔으면 하는 색상을 직접 고를 수 있다. 기본 제공 색상 중에서 선택하거나, 색조·채도·명도를 직접 조작해서 커스텀도 가능하다. 추천받은 색상 조합은 이미지로 공유할 수 있다.</p>
<p>이번 프로젝트에서 처음으로 AI를 직접 연결해본 기능이기도 하다. 구현 과정은 따로 글로 자세히 다뤄볼 예정이다. 🙂</p>
<p><img src="https://velog.velcdn.com/images/2_hyeonju/post/1b1993bb-e05f-42ae-a0a4-8b5df38950af/image.png" alt="AI 색상 조합 추천"></p>
<hr>
<h2 id="재료-추가-및-수정">재료 추가 및 수정</h2>
<p>헤더에는 뒤로가기, 타이틀, 즐겨찾기 버튼을 배치했다. 재료 이미지 업로드, 재질·모양 선택, 특징 태그 추가, 카테고리 커스텀까지 상세하게 입력할 수 있는 구조다.</p>
<p><img src="https://velog.velcdn.com/images/2_hyeonju/post/f8095099-21ed-4771-83bb-e8a3432ac747/image.png" alt="재료"></p>
<hr>
<h2 id="제품-정보">제품 정보</h2>
<p>사용한 재료 목록을 한눈에 볼 수 있고, 재료를 탭하면 모달로 상세 정보를 확인할 수 있다. 제품은 이미지로 공유하는 것도 가능하다.</p>
<p><img src="https://velog.velcdn.com/images/2_hyeonju/post/b0dfc5c7-8533-4bb3-b3f9-04c91500d3aa/image.png" alt="제품 정보"></p>
<hr>
<h2 id="제품-추가-및-수정">제품 추가 및 수정</h2>
<p>제작일은 캘린더로 직접 선택할 수 있고, 재료 추가 버튼을 누르면 기존에 등록해둔 재료 목록에서 검색·필터로 골라서 추가할 수 있다. 제품 정보를 상세하게 입력할 수 있는 구조로 잡았다.</p>
<p><img src="https://velog.velcdn.com/images/2_hyeonju/post/5efef819-1a78-4359-a0f6-084e0058f154/image.png" alt="제품"></p>
<hr>
<h2 id="공유-기능">공유 기능</h2>
<p>공유하기를 누르면 전체 화면이 아닌, 이미지와 설명 컨테이너 부분만 따로 이미지로 저장·공유할 수 있다.</p>
<p>제품 공유와 AI 색상 조합 공유, 두 군데 모두 적용했다.</p>
<p><img src="https://velog.velcdn.com/images/2_hyeonju/post/df81afdf-a156-444d-b74b-179937d963ae/image.png" alt="제품 공유"></p>
<p><img src="https://velog.velcdn.com/images/2_hyeonju/post/f3dfd767-acd4-4ba2-9119-481e7029bce7/image.png" alt="AI 색상 조합 공유"></p>
<hr>
<h2 id="마치며">마치며</h2>
<p>혼자 디자인부터 구현까지 해보는 게 이번이 두 번째다.
처음엔 Stitch 같은 도구도 없어서 스스로 디자인했는데 솔직히 결과물이 성에 차지 않았었다.이제는 AI 도구들이 생겨서 퀄리티도 올라가고 만족도도 훨씬 높아진 것 같다. 코드 구현에도 Claude Code를 함께 사용하고 있어서 기능 구현 시간도 줄고 정보 검색도 빠르게 할 수 있어서 개발 속도가 많이 빨라졌다.
물론 AI가 만들어준 결과물이 내가 원하는 방향에 맞게 나왔는지 사람이 직접 확인하는 과정은 여전히 필요한 것 같다.</p>
<blockquote>
<p>AI 도구들 덕분에 1인 개발의 허들이 확실히 낮아졌다는 걸 체감하고 있다.</p>
</blockquote>
<p>프론트엔드는 웹 경력이 있다 보니 React Native가 처음이었음에도 나름 수월하게 진행됐다. 이제 백엔드 구현이 남았는데... 솔직히 좀 많이 걱정된다........ 다음 글에서 그 과정을 담아볼 예정이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[React Native Expo 안드로이드 키보드 설정]]></title>
            <link>https://velog.io/@2_hyeonju/React-Native-Expo-%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C-%ED%82%A4%EB%B3%B4%EB%93%9C-%EC%84%A4%EC%A0%95</link>
            <guid>https://velog.io/@2_hyeonju/React-Native-Expo-%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C-%ED%82%A4%EB%B3%B4%EB%93%9C-%EC%84%A4%EC%A0%95</guid>
            <pubDate>Thu, 05 Mar 2026 15:08:12 GMT</pubDate>
            <description><![CDATA[<p>Stitch를 좀 더 괴롭히다 보니 메인 색상으로 정한 버건디와 서브 색상인 버터 색상은 유지하면서도 훨씬 고급진 디자인이 나왔고, 빠르게 수정을 마쳤다.</p>
<p><img src="https://velog.velcdn.com/images/2_hyeonju/post/20970e0b-6fc3-4450-aceb-0f292eca030b/image.gif" alt="수정한 디자인"></p>
<p>현재 완성된 페이지는 <strong>완성품과 사용한 재료 확인 페이지</strong>, <strong>재료 수정 페이지</strong> 이렇게 두 가지다.</p>
<hr>
<h2 id="또-키보드-문제">또 키보드 문제...</h2>
<p>그런데 재료 수정 페이지에서 문제가 발생했다.</p>
<p>이전 글에서 하이브리드 앱 개발하면서 키보드 처리로 고생했다고 했는데, 이번에도 키보드 문제가 기다리고 있었다... Aㅏ...</p>
<p>상단의 재료 이름과 ID 입력 필드는 괜찮았지만, 맨 아래의 크기와 재고 필드는 키보드가 올라오면 가려져서 뭘 입력하는지 볼 수가 없었다ㅠㅠ</p>
<hr>
<h2 id="react-native-keyboard-controller-적용">react-native-keyboard-controller 적용</h2>
<p>해결 방법을 찾아보니 <code>react-native-keyboard-controller</code> 라이브러리가 있었다. 설치 후 바로 적용해봤다.</p>
<p>먼저 루트 레이아웃에서 <code>KeyboardProvider</code>로 감싸준다.</p>
<pre><code class="language-tsx">import { KeyboardProvider } from &#39;react-native-keyboard-controller&#39;;

return (
  &lt;GestureHandlerRootView style={{ flex: 1 }}&gt;
    &lt;ThemeProvider value={colorScheme === &#39;dark&#39; ? DarkTheme : DefaultTheme}&gt;
      &lt;KeyboardProvider&gt;
        {...}
      &lt;/KeyboardProvider&gt;
    &lt;/ThemeProvider&gt;
  &lt;/GestureHandlerRootView&gt;
);</code></pre>
<p>그 다음, 상단 헤더와 하단 저장 버튼을 제외하고 키보드에 밀려 올라갔으면 하는 중간 영역을 <code>KeyboardAwareScrollView</code>로 감싸주었다.</p>
<p>속성은 두 가지를 줬다.</p>
<ul>
<li><code>showsVerticalScrollIndicator={false}</code> : 스크롤바 숨김</li>
<li><code>keyboardShouldPersistTaps=&quot;handled&quot;</code> : 탭 가능한 요소 외 빈 영역을 탭하면 키보드 내려감</li>
</ul>
<pre><code class="language-tsx">return (
  &lt;View className=&quot;flex-1 bg-ivory&quot;&gt;
    &lt;SafeAreaView className=&quot;flex-1&quot;&gt;
      &lt;View className=&quot;flex-1 px-6&quot;&gt;
        {/* 상단 헤더 */}
        &lt;View className=&quot;flex-row items-center justify-between py-4&quot;&gt;
          {...}
        &lt;/View&gt;

        &lt;KeyboardAwareScrollView showsVerticalScrollIndicator={false} keyboardShouldPersistTaps=&quot;handled&quot;&gt;
          {/* 완성품 이미지 */}
          &lt;View className=&quot;items-center py-6&quot;&gt;
            {...}
          &lt;/View&gt;

          {/* 재료 정보 입력 필드 */}
          &lt;View className=&quot;gap-6 pb-6&quot;&gt;
            {...}
          &lt;/View&gt;
        &lt;/KeyboardAwareScrollView&gt;

        {/* 저장 버튼 */}
        &lt;View className=&quot;pb-10 pt-3&quot;&gt;
          {...}
        &lt;/View&gt;
      &lt;/View&gt;
    &lt;/SafeAreaView&gt;
  &lt;/View&gt;
);</code></pre>
<p><img src="https://velog.velcdn.com/images/2_hyeonju/post/294a91cd-8b72-47d6-a634-4bf3834a7df5/image.jpg" alt="라이브러리만 적용"></p>
<p>키보드에 가려지는 문제는 해결됐는데, 한 가지 더 신경 쓰이는 부분이 생겼다.
저장 버튼 높이 + 하단 상태바 높이만큼 컨텐츠와 키보드 사이에 불필요한 여백이 생기는 것이었다. 키보드가 있을 때와 없을 때를 구분해서 처리할 필요가 있었다.</p>
<hr>
<h2 id="키보드-상태에-따라-조건부-처리">키보드 상태에 따라 조건부 처리</h2>
<p>React Native에 내장된 <code>Keyboard</code>로 키보드 노출 여부를 감지해 상태로 저장하고, 조건부로 처리했다.</p>
<pre><code class="language-tsx">const [isKeyboardVisible, setIsKeyboardVisible] = useState(false);

useEffect(() =&gt; {
  const keyboardDidShowListener = Keyboard.addListener(&#39;keyboardDidShow&#39;, () =&gt; {
    setIsKeyboardVisible(true);
  });
  const keyboardDidHideListener = Keyboard.addListener(&#39;keyboardDidHide&#39;, () =&gt; {
    setIsKeyboardVisible(false);
  });

  return () =&gt; {
    keyboardDidShowListener.remove();
    keyboardDidHideListener.remove();
  };
}, []);</code></pre>
<p>그리고 <code>SafeAreaView</code>의 <code>edges</code>와 저장 버튼 렌더링을 키보드 상태에 따라 다르게 적용했다.</p>
<ul>
<li><strong>키보드 없을 때</strong> → <code>edges={undefined}</code> : 모든 방향(top, bottom, left, right)에 Safe Area 적용</li>
<li><strong>키보드 있을 때</strong> → <code>edges={[&#39;top&#39;, &#39;left&#39;, &#39;right&#39;]}</code> : 하단(bottom) Safe Area 제외, 저장 버튼도 숨김</li>
</ul>
<pre><code class="language-tsx">&lt;SafeAreaView className=&quot;flex-1&quot; edges={isKeyboardVisible ? [&#39;top&#39;, &#39;left&#39;, &#39;right&#39;] : undefined}&gt;
  ...
  {!isKeyboardVisible &amp;&amp; (
    &lt;View className=&quot;pb-10 pt-3&quot;&gt;
      {...}
    &lt;/View&gt;
  )}
&lt;/SafeAreaView&gt;</code></pre>
<p>최종 적용 결과, 여백이 사라지고 컨텐츠 바로 아래 키보드가 딱 붙는 것을 확인할 수 있었다!!!!</p>
<p><img src="https://velog.velcdn.com/images/2_hyeonju/post/5571b6b7-044e-4383-a19c-20a09b3cdd3a/image.jpg" alt="최종 적용"></p>
<blockquote>
<p>이전 하이브리드 앱에서는 직접 훅을 만들어 보정했는데, 이번엔 라이브러리 하나로 훨씬 깔끔하게 해결됐다.</p>
</blockquote>
<hr>
<h2 id="마치며">마치며</h2>
<p>키보드 처리는 앱 개발할 때마다 한 번씩은 꼭 마주치는 것 같다. 다음 글에서는 실제 화면 구현 과정을 이어서 다뤄볼 예정이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[React Native Expo 프로젝트 초기 설정]]></title>
            <link>https://velog.io/@2_hyeonju/React-Native-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%B4%88%EA%B8%B0-%EC%84%A4%EC%A0%95</link>
            <guid>https://velog.io/@2_hyeonju/React-Native-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%B4%88%EA%B8%B0-%EC%84%A4%EC%A0%95</guid>
            <pubDate>Wed, 25 Feb 2026 08:05:34 GMT</pubDate>
            <description><![CDATA[<p>지난 글에서 디자인까지 마쳤으니, 이번엔 본격적으로 개발 환경을 세팅하고 프로젝트 구조를 잡는 과정을 담아보려 한다.</p>
<hr>
<h2 id="프로젝트-생성">프로젝트 생성</h2>
<p>RN에 Expo를 함께 사용할거라 이 명령어로 프로젝트를 만들었다.</p>
<pre><code>npx create-expo-app@latest</code></pre><p>그리고 테일윈드가 너무 좋아서 바<del>~</del>로 테일윈드까지 설치 해버렸습니다.</p>
<pre><code>npm install nativewind tailwindcss</code></pre><p>React Native에서 스타일을 적용하는 방법은 크게 두 가지인데, 기본적으로 제공하는 <strong>StyleSheet</strong>를 사용하거나 NativeWind처럼 <strong>테일윈드 기반 라이브러리</strong>를 사용하는 방법이 있다.</p>
<blockquote>
<p><code>StyleSheet</code>는 애니메이션처럼 동적으로 스타일 값이 바뀌는 경우에 더 유리하지만, 스타일을 확인하려면 정의해둔 곳을 찾아 계속 왔다갔다 해야 한다는 단점이 있다.</p>
</blockquote>
<p>반면 테일윈드는 <strong>태그 바로 옆에 클래스명으로 스타일이 다 보이니까</strong> 훨씬 직관적이었다. 그래서 복잡한 애니메이션에는 <code>StyleSheet</code>를 쓰고 기본적인 UI 스타일 작업에는 테일윈드를 쓰기로 정했다.</p>
<hr>
<h2 id="nativewind-초기-설정">NativeWind 초기 설정</h2>
<p>이후에는 NativeWind 사용을 위해 초기 파일을 설정해주었다.
<a href="https://velog.io/@leeseoleem1014/EXPO-expo-router-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8%EC%97%90%EC%84%9C-babel.config.js-%EC%83%9D%EC%84%B1%ED%95%98%EA%B8%B0">이 블로그</a>를 참고해서 현재 프로젝트에서 숨김 처리 되어있는 babel.config.js + metro.config.js 이 파일을 꺼내야 한다.</p>
<p><strong>tailwind.config.js</strong></p>
<pre><code class="language-js">module.exports = {
  content: [
    &#39;./app/**/*.{js,jsx,ts,tsx}&#39;,
    &#39;./components/**/*.{js,jsx,ts,tsx}&#39;,
  ],
  presets: [require(&#39;nativewind/preset&#39;)],
  theme: {
    extend: {
      colors: {
        primary: &#39;#5B0E14&#39;,
        secondary: &#39;#F1E194&#39;,
        ivory: &#39;#F9F5EB&#39;,
        &#39;primary-light&#39;: &#39;#8B3A3A&#39;,
        &#39;primary-dark&#39;: &#39;#4A1515&#39;,
        white: &#39;#F5F5F5&#39;,
        black: &#39;#333333&#39;
      },
    },
  },
  plugins: [],
};</code></pre>
<p>색상은 지난 글에서 Stitch로 잡아둔 디자인 시스템을 그대로 가져왔다.</p>
<p><strong>global.css</strong></p>
<pre><code class="language-css">@tailwind base;
@tailwind components;
@tailwind utilities;</code></pre>
<p><strong>babel.config.js</strong></p>
<pre><code class="language-js">module.exports = function(api) {
  api.cache(true);
  return {
    presets: [
      [&#39;babel-preset-expo&#39;, { jsxImportSource: &#39;nativewind&#39; }],
      &#39;nativewind/babel&#39;,
    ],
  };
};</code></pre>
<p><strong>metro.config.js</strong></p>
<pre><code class="language-js">const { getDefaultConfig } = require(&#39;expo/metro-config&#39;);
const { withNativeWind } = require(&#39;nativewind/metro&#39;);

const config = getDefaultConfig(__dirname);

module.exports = withNativeWind(config, { input: &#39;./global.css&#39; });</code></pre>
<p><strong>nativewind-env.d</strong></p>
<pre><code class="language-js">/// &lt;reference types=&quot;nativewind/types&quot; /&gt;</code></pre>
<p><strong>tsconfig.json</strong></p>
<pre><code class="language-json">{
  &quot;extends&quot;: &quot;expo/tsconfig.base&quot;,
  &quot;compilerOptions&quot;: {
    &quot;strict&quot;: true,
    &quot;paths&quot;: {
      &quot;@/*&quot;: [&quot;./*&quot;]
    }
  },
  &quot;include&quot;: [
    &quot;**/*.ts&quot;,
    &quot;**/*.tsx&quot;,
    &quot;.expo/types/**/*.ts&quot;,
    &quot;expo-env.d.ts&quot;,

    // 추가한 두 줄
    &quot;**/*.css&quot;,
    &quot;nativewind-env.d.ts&quot;
  ]
}</code></pre>
<p>휴,,, 일단 라이브러리를 위한 초기 설정을 다 해주었다.</p>
<hr>
<h2 id="일단-코드부터-짜봤다">일단 코드부터 짜봤다</h2>
<p>구조를 먼저 잡아야지... 하면서도 디자인이 머릿속에 생생할 때 바로 화면을 만들어보고 싶어서 냅다 기본 구조에 코드 먼저 작성해봤다.ㅋㅋㅋㅋㅋ</p>
<p>그러다 <strong>모달 부분에서 조금 막혔었는데</strong> <a href="https://proteinman.tistory.com/194">이 블로그</a>의 글을 보고 참고해 아주 부드러운 모달을 완성할 수 있었다.</p>
<p><img src="https://velog.velcdn.com/images/2_hyeonju/post/847ff131-94c4-443f-8a7f-8bc14a79ff45/image.gif" alt="모달 완성"></p>
<hr>
<h2 id="디렉토리-구조-잡기">디렉토리 구조 잡기</h2>
<p>파일이 더 많아지기 전에 구조를 잡아야 할 것 같아서 여기저기 서치해서 구조를 정했다.</p>
<p>구조를 짜면서 헷갈렸던 부분이 두 가지 있었다.</p>
<p><strong>1. <code>screens/</code> 폴더가 필요할까?</strong></p>
<p>여러 RN 프로젝트 구조를 보면 <code>screens/</code> 폴더가 있는 경우가 많아서 나도 넣어야 하나 싶었는데, 알고 보니 <strong>React Navigation을 직접 쓸 때 필요한 폴더</strong>였다. Expo Router는 <code>app/</code> 폴더 자체가 라우팅이랑 스크린 역할을 동시에 하기 때문에 따로 만들 필요가 없었다.</p>
<p><strong>2. <code>app/</code>과 <code>src/</code>를 분리해야 할까?</strong></p>
<p>Expo Router를 쓰면 <code>app/</code> 폴더가 라우팅 역할을 고정으로 담당하기 때문에, 여기에 비즈니스 로직까지 섞이면 나중에 관리가 힘들어질 것 같았다. 그래서 <strong>라우팅은 <code>app/</code>에, 나머지 로직은 전부 <code>src/</code> 안에</strong> 넣는 구조로 분리하기로 했다.</p>
<p>그렇게 해서 최종적으로 정한 구조가 이거다!</p>
<pre><code>my-app/
├── app/                        # Expo Router 라우팅
│   ├── (tabs)/                 # 탭 네비게이션
│   ├── (modals)/               # 모달 그룹
│   ├── products/               # 완성품 관련 라우트
│   ├── materials/              # 재료 관련 라우트
│   └── _layout.tsx             # 루트 레이아웃
│
├── src/                        # 비즈니스 로직
│   ├── components/             # 컴포넌트
│   ├── hooks/                  # 커스텀 훅
│   ├── services/               # API 통신
│   ├── store/                  # Zustand store
│   ├── types/                  # 타입 정의
│   ├── utils/                  # 유틸 함수
│   └── constants/              # 상수 (색상, 설정 등)
│
├── assets/                     # 정적 파일
│
├── app.json
├── tailwind.config.js
└── package.json</code></pre><hr>
<h2 id="마치며">마치며</h2>
<p>이제 이 구조대로 본격적으로 개발을 시작해보려 한다! 다음 글에서는 실제 화면 구현 과정을 담아볼 예정이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Stitch로 앱 디자인 뽑기]]></title>
            <link>https://velog.io/@2_hyeonju/Stitch%EB%A1%9C-%EC%95%B1-%EB%94%94%EC%9E%90%EC%9D%B8-%EB%BD%91%EA%B8%B0</link>
            <guid>https://velog.io/@2_hyeonju/Stitch%EB%A1%9C-%EC%95%B1-%EB%94%94%EC%9E%90%EC%9D%B8-%EB%BD%91%EA%B8%B0</guid>
            <pubDate>Wed, 18 Feb 2026 11:37:06 GMT</pubDate>
            <description><![CDATA[<p>지난 글에서 친구에게 필요한 앱을 웹 대신 React Native로 만들기로 했다고 이야기했는데, 이번엔 본격적으로 디자인 단계로 넘어왔다.</p>
<p>내가 개발할 앱은 <strong>비즈 키링 완성품에 어떤 소재와 부자재가 사용됐는지 한눈에 볼 수 있는 앱</strong>이다.</p>
<hr>
<h2 id="stitch로-디자인하기">Stitch로 디자인하기</h2>
<p>디자인 작업에는 <strong>Stitch</strong>를 사용했다. Stitch는 Adobe에서 만든 <strong>AI 기반 UI 디자인 툴</strong>로, 텍스트로 원하는 화면을 설명하면 그에 맞는 UI를 자동으로 생성해준다. 생성된 결과물은 피그마로 바로 내보낼 수 있어서 이후 커스텀 작업도 수월하다.</p>
<p>핀터레스트로 이것저것 상세페이지 등등 구경하다가 마음에 드는 레퍼런스를 찾아서 첨부한 다음 원하는 디자인을 설명했다.</p>
<p><img src="https://velog.velcdn.com/images/2_hyeonju/post/7be8dc92-1b63-49ee-9ce6-aed424113141/image.png" alt="레퍼런스"></p>
<blockquote>
<p>화면 중앙에는 사용자가 업로드한 완성된 비즈 키링의 이미지가 있고, 그 주위에는 완성된 비즈 키링에 사용한 디테일한 비즈와 부자재가 떠있도록 하고 싶어. 완성된 비즈키링에 어떤 소재와 부자재가 사용되었는지 한눈에 볼 수 있는 앱 화면이야.</p>
</blockquote>
<p>이걸 영어로 번역해서 Stitch에 처음 요청했더니</p>
<p><img src="https://velog.velcdn.com/images/2_hyeonju/post/fa6ee491-3f25-4f5c-b110-bd8854174303/image.png" alt="디자인 1"></p>
<p>알아서 임의로 비즈 키링 사진도 넣어주고, 사용한 비즈들이 떠있는 UI랑 목록으로 볼 수 있는 UI를 따로 만들어주었다.</p>
<hr>
<h2 id="색상-지정과-화면-다듬기">색상 지정과 화면 다듬기</h2>
<p>디자인이 마음에 들어서 다음으로는 메인 색상과 서브 색상을 지정해주고, 재료 리스트는 없애달라고 했다.</p>
<p><img src="https://velog.velcdn.com/images/2_hyeonju/post/bc63d7ff-3381-42b7-abd5-fad7187043b0/image.png" alt="디자인 2"></p>
<p>비즈 키링 완성품 확인 화면은 마음에 드는 디자인이 나와서, 이번에는 비즈 키링 재료 목록에서 한 재료를 클릭했을 때의 상세 화면도 디자인해보았다.</p>
<p><img src="https://velog.velcdn.com/images/2_hyeonju/post/47b34967-67e0-4e52-b3d5-9f506f41a438/image.png" alt="디자인 3"></p>
<p>Stitch가 디자인을 무난하게 잘 뽑아주었고, 원하는 방향으로 빠르게 화면을 잡아나갈 수 있었다.</p>
<blockquote>
<p>디자인 툴을 직접 다루는 게 아니라, 결과물을 빠르게 확인하고 다듬는 느낌이었다.</p>
</blockquote>
<hr>
<h2 id="피그마로-내보내기">피그마로 내보내기</h2>
<p>Stitch에서 디자인한 결과물은 <strong>피그마에 바로 붙여넣기</strong>할 수 있다. 디자인 선택 후 Export → Figma 옵션으로 가져오면 된다.</p>
<p><img src="https://velog.velcdn.com/images/2_hyeonju/post/22980ead-c211-48f0-b123-86ab7259abfa/image.png" alt="Stitch export 화면"></p>
<p>피그마에 붙여넣으면 레이아웃 구조도 잘 잡혀 있어서 요소들 하나하나 위치나 색상 등을 커스텀하기가 수월했다.</p>
<p><img src="https://velog.velcdn.com/images/2_hyeonju/post/f2cc19e5-721c-4115-b919-2755449c9771/image.png" alt="피그마 export 화면"></p>
<p>색상과 글씨체도 직접 수정해보았는데, 막상 바꾸고 나니 이전 디자인이 더 나아 보이기도 했다. 개발을 진행하면서 다시 다듬어볼 예정이다.</p>
<p><img src="https://velog.velcdn.com/images/2_hyeonju/post/830bf5ba-85fd-4ddb-9901-5f6914d4fcb4/image.png" alt="피그마에서 수정 화면"></p>
<hr>
<h2 id="마치며">마치며</h2>
<p>AI 툴을 활용해 빠르게 화면을 잡고, 피그마에서 직접 다듬는 흐름이 생각보다 효율적이었다. 다음 글에서는 본격적으로 개발 환경을 세팅하고 화면을 구현하는 과정을 다뤄볼 예정이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Web + Capacitor 기반 하이브리드 앱 대신 진짜 앱을 만들기로 한 이유]]></title>
            <link>https://velog.io/@2_hyeonju/React-Native%EB%A1%9C-%EC%95%B1-%EA%B0%9C%EB%B0%9C%ED%95%98%EA%B8%B0-1.-%EC%9B%B9%EC%9D%84-%EA%B0%90%EC%8B%B8%EB%8A%94-%EB%8C%80%EC%8B%A0-%EC%95%B1%EC%9D%84-%EB%A7%8C%EB%93%A4%EA%B8%B0%EB%A1%9C-%ED%95%9C-%EC%9D%B4%EC%9C%A0</link>
            <guid>https://velog.io/@2_hyeonju/React-Native%EB%A1%9C-%EC%95%B1-%EA%B0%9C%EB%B0%9C%ED%95%98%EA%B8%B0-1.-%EC%9B%B9%EC%9D%84-%EA%B0%90%EC%8B%B8%EB%8A%94-%EB%8C%80%EC%8B%A0-%EC%95%B1%EC%9D%84-%EB%A7%8C%EB%93%A4%EA%B8%B0%EB%A1%9C-%ED%95%9C-%EC%9D%B4%EC%9C%A0</guid>
            <pubDate>Sun, 15 Feb 2026 16:24:14 GMT</pubDate>
            <description><![CDATA[<p>React로 웹 서비스를 개발하다가, 최근에 한 번 <strong>Capacitor</strong>를 사용해 웹 서비스를 앱 형태로 출시해본 경험이 있다. 웹 코드를 그대로 감싸 iOS와 Android에 배포할 수 있다는 점은 분명 매력적이었다.</p>
<blockquote>
<p>웹을 한 번 만들면 앱까지 확장할 수 있다.</p>
</blockquote>
<p>이 구조는 개발 생산성 측면에서 상당히 효율적으로 보였다. 하나의 코드베이스로 웹과 앱을 동시에 운영할 수 있다는 점은 특히 빠르게 서비스를 만들어야 하는 상황에서 충분히 설득력 있는 선택지였다.
하지만 막상 운영을 시작하고 나니, 작은 불편함들이 하나둘씩 쌓이기 시작했다.</p>
<hr>
<h2 id="점점-늘어나는-플랫폼-분기">점점 늘어나는 플랫폼 분기</h2>
<p>가장 먼저 체감한 건 <strong>플랫폼별 조건 분기</strong>였다. 기능에따라 사용자가 접속한 환경이 무엇인지 매번 구분해야 했다.</p>
<ul>
<li>웹 브라우저인지  </li>
<li>iOS인지  </li>
<li>Android인지  </li>
</ul>
<p>“플랫폼에 따라 다를 수 있다”는 전제를 깔고 설계해야 했고, 자연스럽게 구조는 방어적으로 변해갔다. 개발을 확장하는 느낌이라기보다는, 분기를 관리하고 있다는 느낌에 가까웠다.</p>
<hr>
<h2 id="키보드와-하단-ui를-직접-보정해야-했던-순간">키보드와 하단 UI를 직접 보정해야 했던 순간</h2>
<p>특히 기억에 남는 건 <strong>키보드와 하단 UI 처리</strong>였다.</p>
<p>웹에서는 키보드가 레이아웃에 직접적인 영향을 거의 주지 않는다. 하지만 앱에서는 입력창에 포커스를 주는 순간 키보드가 올라오고, 화면 하단 영역을 가려버린다.</p>
<p>결국 직접 키보드 이벤트를 감지하고, 높이를 계산해 보정하는 훅을 만들었다.</p>
<pre><code class="language-ts">import { useEffect, useState } from &#39;react&#39;;
import { Keyboard } from &#39;@capacitor/keyboard&#39;;
import { Capacitor } from &#39;@capacitor/core&#39;;

const IOS_BOTTOM_NAV_HEIGHT = 76;
const ANDROID_BOTTOM_NAV_HEIGHT = 48;

export const useKeyboardOffset = () =&gt; {
  const [bottomOffset, setBottomOffset] = useState(0);

  useEffect(() =&gt; {
    if (!Capacitor.isNativePlatform()) return;

    const platform = Capacitor.getPlatform();
    const BOTTOM_NAV_HEIGHT =
      platform === &#39;ios&#39;
        ? IOS_BOTTOM_NAV_HEIGHT
        : ANDROID_BOTTOM_NAV_HEIGHT;

    let showHandler: any;
    let hideHandler: any;

    const setupListeners = async () =&gt; {
      showHandler = await Keyboard.addListener(&#39;keyboardDidShow&#39;, (info) =&gt; {
        setBottomOffset(info.keyboardHeight - BOTTOM_NAV_HEIGHT);
      });

      hideHandler = await Keyboard.addListener(&#39;keyboardDidHide&#39;, () =&gt; {
        setBottomOffset(0);
      });
    };

    setupListeners();

    return () =&gt; {
      if (showHandler) showHandler.remove();
      if (hideHandler) hideHandler.remove();
    };
  }, []);

  return bottomOffset;
};</code></pre>
<p>그 당시에는 주어진 환경 안에서 할 수 있는 최선의 선택이라고 생각했다. 실제로 문제는 해결됐고, 나름대로 깔끔하게 추상화했다고 느꼈다. 하지만 시간이 지나면서 점점 이런 생각이 들었다.</p>
<blockquote>
<p>나는 앱을 만드는 걸까, 아니면 웹을 억지로 보정하고 있는 걸까?</p>
</blockquote>
<hr>
<h2 id="safe-area와-상태바를-직접-제어해야-했던-순간">Safe Area와 상태바를 직접 제어해야 했던 순간</h2>
<p>키보드뿐만 아니라 상태바와 Safe Area 처리 역시 직접 다뤄야 하는 영역이었다. 웹에서는 브라우저가 상단 시스템 UI를 알아서 처리해주지만, 앱 환경에서는 상태바가 명확한 레이아웃 요소로 존재한다. 특히 iOS에서는 노치 영역이, Android에서는 기기마다 다른 상태바 높이가 화면 상단 구성에 직접적인 영향을 준다.</p>
<p>기본 설정을 그대로 두면 콘텐츠가 상태바 아래로 겹치거나, 배경색이 어색하게 끊기거나, 상태바 아이콘과 배경이 충돌하는 문제가 발생했다.</p>
<p>결국 상태바를 명시적으로 제어하는 Provider를 따로 두게 됐다.</p>
<pre><code class="language-ts">&#39;use client&#39;;

import { useEffect } from &#39;react&#39;;
import { Capacitor } from &#39;@capacitor/core&#39;;
import { StatusBar, Style } from &#39;@capacitor/status-bar&#39;;

export function SystemUIProvider({ children }: { children: React.ReactNode }) {
  useEffect(() =&gt; {
    if (!Capacitor.isNativePlatform()) return;

    const setupSystemBars = async () =&gt; {
      await StatusBar.setOverlaysWebView({ overlay: false });
      await StatusBar.setBackgroundColor({ color: &#39;#f7f7f7&#39; });
      await StatusBar.setStyle({ style: Style.Light });
    };

    setupSystemBars();
  }, []);

  return &lt;&gt;{children}&lt;/&gt;;
}</code></pre>
<p>문제는 여기서 끝나지 않았다. 이 설정이 iOS와 Android에서 동일하게 동작하지 않았고, 특히 iOS에서는 Safe Area 처리를 위해 컴포넌트 단위에서 추가 보정이 필요했다. 결국 또다시 플랫폼 차이를 흡수하는 코드가 늘어났다.</p>
<p>그때부터 점점 명확해졌다. 나는 기능을 구현하고 있는 게 아니라, 플랫폼 간 간극을 계속 메우고 있었다.</p>
<hr>
<h2 id="웹-ux와-앱-ux는-다르다">웹 UX와 앱 UX는 다르다</h2>
<p>웹 인터랙션과 앱 인터랙션의 차이도 분명했다. 웹에서는 hover 스타일이 자연스럽지만 모바일 환경에는 hover라는 개념이 존재하지 않는다. 버튼을 한 번 터치했을 뿐인데 hover 상태가 남아 있는 것처럼 보이거나, 의도하지 않은 시각적 피드백이 유지되는 경우도 있었다.</p>
<p>이런 차이는 작아 보이지만 사용자 경험에는 분명히 영향을 준다. 그때 처음으로 확실히 체감했다.</p>
<blockquote>
<p>웹은 웹의 UX가 있고, 앱은 앱의 UX가 있다.</p>
</blockquote>
<hr>
<h2 id="그래서-결론은-react-native">그래서 결론은? React Native!</h2>
<p>이 경험들이 쌓이면서 생각은 하나로 정리됐다. 앱은 앱 기술로 만들어야 한다는 결론이었다. React를 사용하고 있었기 때문에 자연스럽게 React Native에 관심이 갔다.</p>
<p>React는 DOM 위에서 동작하는 UI 라이브러리이고, React Native는 네이티브 컴포넌트를 렌더링하는 프레임워크다. 문법은 비슷하지만 구조는 전혀 다르다. WebView 위에 웹을 올리는 방식이 아니라, 플랫폼의 실제 UI 요소를 직접 사용한다는 점이 가장 크게 다가왔다.</p>
<p>구조가 다르다는 건, 결국 설계의 출발점이 다르다는 의미였다.</p>
<hr>
<h2 id="expo로-가볍게-시작해보기">Expo로 가볍게 시작해보기</h2>
<p>처음에는 가볍게 경험해보자는 마음으로 React Native와 함께 Expo를 사용했다. 복잡한 네이티브 설정 없이 빠르게 실행할 수 있었고, 실기기 테스트도 비교적 수월했다.</p>
<p><a href="https://docs.expo.dev">공식 문서</a>를 보며 화면 전환과 상태 관리 정도를 구현해보는 것만으로도 차이를 느낄 수 있었다. 상태바 제어가 자연스럽고, Safe Area 처리가 명확하며, iOS에서는 스와이프 제스처로 뒤로 가기가 기본 동작으로 제공된다.</p>
<p>그때 확실히 느꼈다.</p>
<blockquote>
<p>아, 역시 이게 앱이지.</p>
</blockquote>
<hr>
<h2 id="이번에는-제대로-만들어보기">이번에는, 제대로 만들어보기</h2>
<p>그러던 중 친구가 필요한 서비스 아이디어를 이야기했고, 기능은 비교적 단순했다. 로그인, 목록 조회, 생성·수정·삭제(CRUD) 정도의 구조였다.</p>
<p>이 정도 규모라면 구조를 직접 설계해보고, 앱 아키텍처를 경험해보고, 빌드와 배포까지 전 과정을 다뤄보기에 충분하다고 판단했다.</p>
<p>그래서 결심했다.</p>
<blockquote>
<p>이번에는 React Native로, 처음부터 끝까지 직접 만들어보자.</p>
</blockquote>
<hr>
<h2 id="마치며">마치며</h2>
<p>웹 개발자로서 처음으로 <strong>‘제대로’ 앱을 만들어보는 과정</strong>.</p>
<p>이 시리즈는 결과물보다도, 그 과정과 선택의 이유를 남기는 기록이 될 것 같다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[NCP로 정적 웹 배포 어떻게 하는 건데...?]]></title>
            <link>https://velog.io/@2_hyeonju/NCP%EB%A1%9C-%EC%A0%95%EC%A0%81-%EC%9B%B9-%EB%B0%B0%ED%8F%AC-%EC%96%B4%EB%96%BB%EA%B2%8C-%ED%95%98%EB%8A%94-%EA%B1%B4%EB%8D%B0</link>
            <guid>https://velog.io/@2_hyeonju/NCP%EB%A1%9C-%EC%A0%95%EC%A0%81-%EC%9B%B9-%EB%B0%B0%ED%8F%AC-%EC%96%B4%EB%96%BB%EA%B2%8C-%ED%95%98%EB%8A%94-%EA%B1%B4%EB%8D%B0</guid>
            <pubDate>Sat, 07 Dec 2024 21:09:43 GMT</pubDate>
            <description><![CDATA[<h3 id="그런데-누가-aws-말고-ncp-씀">그런데 누가 AWS 말고 NCP 씀?</h3>
<h4 id="안녕하세요-누입니다-크레딧-지원해-준다는데-한-번쯤은-해보려합니다">안녕하세요. &#39;누&#39;입니다. 크레딧 지원해 준다는데 한 번쯤은 해보려합니다.</h4>
<p>항상 AWS에서 배포를 했었는데 프로젝트 지원으로 NCP 크레딧을 받게 되어 어쩌다 보니 NCP로 배포를 해야 하는 상황이 와버렸다. 주변에서 하는 말을 들어보면 AWS랑 비슷하다는데 음... 다들 사용하지 않는 데에는 이유가 있는 게 아닐까?
어찌 됐든 NCP 배포가 처음이라 어떻게 하는 건지 방법을 알아보기 위해 구글 서칭을 해보았다. 그런데 다들 도커, Nginx 등 오픈소스 플랫폼을 함께 사용하네...? 난 그런 거 모르니까 내가 아는 정보들을 찾아 배포한 방법을 남겨보자!
</br></p>
<blockquote>
<h3 id="근데-ncp가-뭔데">근데 NCP가 뭔데?</h3>
</blockquote>
<ol>
<li>NAVER CLOUD PLATFORM의 약자로 네이버(Naver)에서 제공하는 클라우드 기반 인프라 제공 서비스</li>
<li>기업이 IT 인프라를 구축하는 데 필요한 다양한 서비스를 제공하는 클라우드 컴퓨팅 서비스</li>
</ol>
<p></br></br></p>
<h2 id="1-ncp와-친해지길-바라">1. NCP와 친해지길 바라</h2>
<h3 id="1-1-naver-cloud-platform-에서-오른쪽-위-콘솔로-접속">1-1. <a href="https://www.ncloud.com/">NAVER CLOUD PLATFORM</a> <strong>에서 오른쪽 위 콘솔로 접속</strong></h3>
<p><img src="https://velog.velcdn.com/images/2_hyeonju/post/2f858786-b1e7-452f-a5b2-aeb7749a351d/image.png" alt="NAVER CLOUD PLATFORM 홈페이지 이미지"></p>
<p>개인적으로 NCP는 로그인 과정이 너무 귀찮다!!! 총 두번의 인증을 완료해야 로그인 과정이 끝난다.</p>
<h3 id="1-2-로그인">1-2. 로그인</h3>
<p><strong>1차 - 아이디, 비밀번호
2차 - 휴대 전화번호 / 이메일 주소 / OTP / 패스키 중 한 가지</strong>
<img src="https://velog.velcdn.com/images/2_hyeonju/post/017dcd06-7f02-4a0f-a314-94b55e7eb43c/image.png" alt="2차 인증 화면 이미지"></p>
<p>귀찮은 인증을 모두 마치고 나면 드디어 대시보드 화면으로 오게 된다.
<strong>왼쪽에서 서비스를 눌러 NCP에서 사용 가능한 서비스들을 볼 수 있다.</strong>
<em>(참고로 내가 로그인한 계정은 서브 계정이다. AWS처럼 계정 주인이 서브 계정을 만들어 팀원들에게 줄 수 있다)</em>
<img src="https://velog.velcdn.com/images/2_hyeonju/post/08b8b774-3052-46a3-86a8-450380fa9843/image.png" alt="대시보드 화면 이미지"></p>
<p>처음 이 화면을 봤을 때는 뭔가 AWS처럼 꾸며져있다는 생각을 했는데 내가 AWS에서 사용한 서비스가 적어서 그런지 얼마나 비슷한 서비스들이 있는 건지는 잘 모르겠다.
<img src="https://velog.velcdn.com/images/2_hyeonju/post/4b1670f4-969c-4ef3-b739-b02cb1d070cb/image.png" alt="서비스들 화면 이미지"></p>
<p></br></br></p>
<h2 id="2-버킷이-어디-있지-있긴-한가">2. 버킷이... 어디 있지? 있긴 한가....?</h2>
<p>이 정도면 친해진 것 같은데 배포는 어느 서비스를 쓰는 걸까. AWS에서는 S3, CloudFront, Route53 이 세 가지를 사용했는데 비슷한 걸 찾아보면 되지 않을까? 하고 이것저것 눌러봤다.</p>
<h3 id="2-1-storage-카테고리--object-storagestorage-화면-이미지">2-1. Storage 카테고리 &gt; Object Storage<img src="https://velog.velcdn.com/images/2_hyeonju/post/7a4d7f82-4bc3-4c3c-bde3-368699209e5d/image.png" alt="Storage 화면 이미지"></h3>
<p><strong>버킷 매니지먼트</strong>를 찾을 수 있다. <del>(서비스명에 검색은 왜 안되는 거야🥲)</del></p>
<p><img src="https://velog.velcdn.com/images/2_hyeonju/post/41e9beec-7283-43ab-a8d4-b335f220d6f4/image.png" alt="Object Storage 화면 이미지">나는 현재 버킷에 백엔드 버킷 하나, 내가 만든 프론트 버킷 하나가 생성되어 있다.</p>
</br>

<h3 id="2-2-버킷-생성하기">2-2. 버킷 생성하기</h3>
<p>빌드 파일 저장을 위해 <strong>파란색 버킷 생성 버튼을 눌러 버킷 만들기</strong></p>
<ul>
<li><h4 id="기본-정보-단계---버킷-이름짓기">기본 정보 단계 -&gt; 버킷 이름짓기</h4>
<img src="https://velog.velcdn.com/images/2_hyeonju/post/3f765ef0-07d3-44e2-acbc-ad437c5a1bdf/image.png" alt="버킷 생성 1단계 이미지">버킷 이름이 리전 내에서 유일한 이름이어야 해서 정하기 어렵지 않을까 했는데 프로젝트 이름으로 하니까 괜찮았다.</li>
</ul>
</br>

<ul>
<li><h4 id="설정-관리-단계---기본값-그대로-두고-다음-단계버킷-생성-2단계-이미지">설정 관리 단계 -&gt; 기본값 그대로 두고 다음 단계<img src="https://velog.velcdn.com/images/2_hyeonju/post/5fe30fd7-ce1d-43fc-833f-7ff990cdd26e/image.png" alt="버킷 생성 2단계 이미지"></h4>
</li>
</ul>
</br>

<ul>
<li><h4 id="권한-관리-단계---공개로-하고-다음-단계">권한 관리 단계 -&gt; 공개로 하고 다음 단계</h4>
<img src="https://velog.velcdn.com/images/2_hyeonju/post/5ff138f9-93f7-4fe6-b7de-12a66e34939c/image.png" alt="버킷 생성 3단계 이미지"></li>
</ul>
</br>

<ul>
<li><h4 id="최종-확인-단계---최종-확인하고-버킷-생성하기">최종 확인 단계 -&gt; 최종 확인하고 버킷 생성하기</h4>
<img src="https://velog.velcdn.com/images/2_hyeonju/post/d5144018-5f78-4210-a0ae-4029e48ab812/image.png" alt="버킷 생성 마무리 이미지"></li>
</ul>
<p>이렇게 하면 버킷 생성은 끝이다!</p>
</br>

<blockquote>
<h4 id="내가-느낀-ncp-와-aws-버킷-생성-차이">내가 느낀 NCP 와 AWS 버킷 생성 차이</h4>
<p>NCP가 AWS보다 뭔가를 설정하는 부분이 적어서 편했다. 정책 추가도 없다! 그리고 기능에 대한 설명이 짧은 편이고 이해하기 쉬워서 좋았다.</p>
</blockquote>
<p></br></br></p>
<h2 id="3-파일-올리기">3. 파일 올리기</h2>
<p>이제 버킷에 파일을 올리며 되는데 AWS와 다른 점을 또 발견했다. 이것도 좀 귀찮은 건데 NCP 버킷에는 폴더가 업로드가 안된다... 나는 오류가 나서 안되는 건 줄 알았는데 아니었다.</p>
<ul>
<li><h4 id="새-폴더-만들기-버튼으로-새-폴더-생성">새 폴더 만들기 버튼으로 새 폴더 생성</h4>
<img src="https://velog.velcdn.com/images/2_hyeonju/post/2c42f004-c2a3-4429-860b-d14517dec347/image.png" alt="새폴더 생성 모달 이미지"></li>
</ul>
</br>

<ul>
<li><h4 id="자신의-프로젝트-빌드-파일-구조에-맞추어-새-폴더를-만들고-폴더-안에-이미지를-올리기">자신의 프로젝트 빌드 파일 구조에 맞추어 새 폴더를 만들고 폴더 안에 이미지를 올리기</h4>
<img src="https://velog.velcdn.com/images/2_hyeonju/post/0963449e-cace-4056-8c6c-d944df897d31/image.png" alt="프로젝트 파일 업로드 이미지"></li>
</ul>
<p>현재 나는 프로젝트를 빌드 하면 dist 폴더 안에 &#39;assets 폴더, fonts 폴더, index.html 파일&#39;이 생성되어서 사진처럼 만들어놓았다.</p>
</br>

<ul>
<li><h4 id="indexhtml-파일-권한-관리의-전체공개-부분이-공개인지-확인">index.html 파일 권한 관리의 전체공개 부분이 공개인지 확인</h4>
<img src="https://velog.velcdn.com/images/2_hyeonju/post/c7fa252a-9801-4289-94e3-aa2c4411889f/image.png" alt="index.html 상세 이미지"></li>
</ul>
<p>특히 index.html 파일의 경우는 클릭해서 권한 관리의 전체 공개가 공개로 되어 있는지 확인해야 한다.
<em>(전체 공개가 아니라면 권한 관리 옆 버튼을 눌러 공개로 바꾸기)</em></p>
<p></br></br></p>
<h2 id="4-정적-호스팅-설정">4. 정적 호스팅 설정</h2>
<p>이제 새 버킷이 생성된 것을 확인할 수 있을 텐데 이제 만든 버킷에 정적 웹 사이트 호스팅 설정을 해주면 거의 끝난다.</p>
<ul>
<li><h4 id="새로-생성된-버킷-확인새-버킷-생성된-이미지">새로 생성된 버킷 확인<img src="https://velog.velcdn.com/images/2_hyeonju/post/8ad878d9-c0d5-460a-acd1-da9359552162/image.png" alt="새 버킷 생성된 이미지"></h4>
</li>
</ul>
<p>AWS에서 버킷을 만들고 버킷 속성에서 정적 웹 사이트 호스팅 설정하는 것처럼 NCP도 비슷하게 설정할 수 있다.</p>
</br>

<ul>
<li><h4 id="정적-호스팅-원하는-버킷의-설정에서-정적-웹-사이트-호스팅-클릭정적-웹-호스팅-설정-토글-이미지">정적 호스팅 원하는 버킷의 설정에서 정적 웹 사이트 호스팅 클릭<img src="https://velog.velcdn.com/images/2_hyeonju/post/30b32f32-a5c5-400a-a9ee-1a8288865201/image.png" alt="정적 웹 호스팅 설정 토글 이미지"></h4>
</li>
</ul>
</br>

<ul>
<li><h4 id="비활성화-버튼을-활성화로-바꾸기호스팅-토글-모달-이미지">비활성화 버튼을 활성화로 바꾸기<img src="https://velog.velcdn.com/images/2_hyeonju/post/4068f32f-a4a1-4a59-8ed5-126696be11ca/image.png" alt="호스팅 토글 모달 이미지"></h4>
</li>
</ul>
<p>정적 웹 사이트 호스팅을 누르면 모달이 뜨는데 비활성화 버튼을 활성화로 바꾸어주면 된다.</p>
</br>

<ul>
<li><h4 id="호스팅-유형---정적-웹-사이트-호스팅">호스팅 유형 -&gt; 정적 웹 사이트 호스팅</h4>
</li>
<li><h4 id="설정---인덱스-파일과-오류-파일에-indexhtml-작성">설정 -&gt; 인덱스 파일과 오류 파일에 index.html 작성</h4>
<img src="https://velog.velcdn.com/images/2_hyeonju/post/6a43fa24-0fed-41be-b0ae-c9d8416d5e68/image.png" alt="호스팅 모달 이미지"></li>
</ul>
</br>

<ul>
<li><h4 id="엔드포인트-생성-완료">엔드포인트 생성 완료</h4>
<img src="https://velog.velcdn.com/images/2_hyeonju/post/cf9b9e11-a5c1-4ffc-a48a-4a9d512641ea/image.png" alt="엔드포인드 이미지"></li>
</ul>
<p>이렇게 하고 나면 웹 사이트를 사용할 수 있는 엔드 포인트를 받게 된다.</p>
<p></br></br></p>
<h2 id="이렇게-ncp로-정적-웹-배포-끝">이렇게 NCP로 정적 웹 배포 끝!</h2>
<h3 id="마무리하며">마무리하며</h3>
<p>NCP로 정적 웹 사이트 배포하는 방법을 구글에 서칭했었을 때 다른 분들이 더 복잡하고 어려운 방법으로 배포를 하길래 배포를 미루고 있었다. 그래도 해야지 하면서 여러 블로그들을 보면서 나에게만 해당되는 부분들만 흡수해 적용시켜보니 그렇게 복잡하지는 않았던 것 같다. 지금까지 배포를 모두 AWS에서 하다가 이번에 처음으로 NCP에서 해보았는데 아직 다른 서비스는 어떤 게 있는지 몰라서 그런 건지 배포 내내 AWS가 그리웠다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[초보자를 위한 Prettier & ESLint 완전 정복 🌏]]></title>
            <link>https://velog.io/@2_hyeonju/Prettier-ESLint-%EC%99%84%EC%A0%84-%EC%A0%95%EB%B3%B5</link>
            <guid>https://velog.io/@2_hyeonju/Prettier-ESLint-%EC%99%84%EC%A0%84-%EC%A0%95%EB%B3%B5</guid>
            <pubDate>Thu, 04 Jul 2024 14:21:52 GMT</pubDate>
            <description><![CDATA[<h2 id="이-주제로-글을-작성하게-된-계기">이 주제로 글을 작성하게 된 계기</h2>
<p>이전에 <a href="https://velog.io/@2_hyeonju/Prettier-%EC%84%A4%EC%A0%95%ED%95%98%EA%B8%B0">&#39;Prettier 설정하기&#39;</a>라는 이름의 게시글을 작성했습니다. 이 글을 쓸 당시에는 혼자서만 코드를 작성하고 배포해보았고 그 이후에서도 혼자서만 코드를 작성하여 큰 문제 없이 스스로 쓴 글을 보며 사용하고 있었습니다. 하지만 곧 있을 협동 프로젝트로 인해 개인 설정이 아닌 협동을 위한 설정이 필요하게 되었습니다.
특히 Prettier를 단독으로 사용하는 것이 아닌 ESLint설정을 함께 해주고 두 도구의 충돌도 신경을 써주어야 합니다. 그래서 이번 글은 협동 프로젝트를 준비하면서 내가 사용하기 위해 정리한 결과물을 공유하고자 합니다! 😎</p>
<h3 id="본격적으로-들어가기전에">본격적으로 들어가기전에</h3>
<p><em>이 게시글에서는 ESLint에 대한 개념은 정리되어 있으나 Prettier의 개념에 대해서는 생략이 있으니 <a href="https://velog.io/@2_hyeonju/Prettier-%EC%84%A4%EC%A0%95%ED%95%98%EA%B8%B0">&#39;Prettier 설정하기&#39;</a>를 먼저 보고 오는 것을 추천드립니다!</em>
<br><br><br></p>
<h2 id="prettier-사용을-위한-3가지-준비">Prettier 사용을 위한 3가지 준비</h2>
<h3 id="1-vs-code의-extension에서-prettier-확장-프로그램-설치"><strong>1. VS Code의 Extension에서 Prettier 확장 프로그램 설치</strong></h3>
<p><img src="https://velog.velcdn.com/images/2_hyeonju/post/9edc3726-b578-4640-a3de-02623067c8b6/image.png" alt=""></p>
<h3 id="2-vs-code-설정에서-prettier-사용-설정-하기"><strong>2. VS Code 설정에서 Prettier 사용 설정 하기</strong></h3>
<p>맥의 경우 Command + , / 윈도우의 경우 Ctrl + , 를 동시에 눌러 VS Code 설정창을 띄웁니다.
화면에 윗부분에 있는 검색창에 Editor: Default Formatter를 입력한 후 None을 Prettier - Code formatter로 바꾸어 줍니다.
<img src="https://velog.velcdn.com/images/2_hyeonju/post/8c6d3511-3ef8-4489-b1d9-cc248b3a506d/image.png" alt="">다음으로 다시 검색창에 Editor: Format on Save를 입력한 후 빈 박스를 클릭하여 체크합니다.
<img src="https://velog.velcdn.com/images/2_hyeonju/post/f77de60e-0ece-498a-a782-856e9417f49b/image.png" alt=""></p>
<h3 id="3-prettier-패키치-설치"><strong>3. Prettier 패키치 설치</strong></h3>
<p>.prettierrc라는 설정 파일을 생성하여 그 안에 원하는 포맷팅 형식을 저장해야하는데 이 파일을 생성하려면 패키지를 설치해야 합니다.
터미널을 열어 아래 명령어를 입력하여 설치합니다.</p>
<h4 id="패키지-매니저가-npm-일-때">패키지 매니저가 npm 일 때</h4>
<blockquote>
<p>npm install -D prettier<br/>
*<em>테일윈드를 함께 사용할 예정이라면 *</em>
npm install -D prettier prettier-plugin-tailwindcss</p>
</blockquote>
<h4 id="패키지-매니저가-yarn-일-때">패키지 매니저가 yarn 일 때</h4>
<blockquote>
<p>yarn install -D prettier<br/>
*<em>테일윈드를 함께 사용할 예정이라면 *</em>
yarn install -D prettier prettier-plugin-tailwindcss</p>
</blockquote>
<p><br><br></p>
<h2 id="prettier-사용">Prettier 사용</h2>
<h3 id="1-prettierrc">1. <code>.prettierrc</code></h3>
<p>사용 준비를 마쳤으니 이제 사용해보아야겠죠? 가장 상위 폴더(package.json과 같은 선상)에 .prettierrc 파일을 생성합니다. 그리고 원하는 설정 코드를 { }안에 키와 값의 형태로 작성합니다.
<a href="https://prettier.io/docs/en/options.html">Prettier 공식 문서</a>를 확인해보면 더 많은 설정들이 있지만 현재 경험 해본적 없는 파일에 대한 설정은 가져오지 않았습니다.
<br></p>
<p>** 아래 Prettier 적용에 주석 없이 바로 복사 가능한 코드가 있습니다.**</p>
<pre><code>{
  ------------------------
  기본 포맷팅 설정
  ------------------------
  // 한 줄의 최대 길이를 설정 (기본값: 80)
  // 이 길이를 초과하면 자동으로 줄바꿈이 발생
  &quot;printWidth&quot;: 80,

  // 들여쓰기 시 사용할 공백 문자 수 (기본값: 2)
  // useTabs가 false일 때만 적용됨
  &quot;tabWidth&quot;: 2,

  // 들여쓰기에 탭 문자 사용 여부 (기본값: false)
  // true: 탭 문자 사용, false: 공백 문자 사용
  &quot;useTabs&quot;: false,

  // 문장 끝 세미콜론 사용 여부 (기본값: true)
  // true: 모든 문장 끝에 세미콜론 추가
  // false: 필요한 경우에만 세미콜론 추가
  &quot;semi&quot;: true,

  ------------------------
  따옴표 관련 설정
  ------------------------
  // 문자열에 작은따옴표 사용 여부 (기본값: false)
  // true: &#39;string&#39;, false: &quot;string&quot;
  &quot;singleQuote&quot;: true,

  // 객체 속성에 따옴표 추가 방식 (기본값: &quot;as-needed&quot;)
  &quot;quoteProps&quot;: &quot;as-needed&quot;,
  // - &quot;as-needed&quot;: 필요한 경우에만 따옴표 추가
  // - &quot;consistent&quot;: 하나라도 따옴표가 필요하면 모든 속성에 따옴표 추가
  // - &quot;preserve&quot;: 입력된 따옴표 스타일 유지

  // JSX에서 작은따옴표 사용 여부 (기본값: false)
  // singleQuote 설정과 독립적으로 동작
  &quot;jsxSingleQuote&quot;: true,

  ------------------------
  쉼표 및 괄호 설정
  ------------------------
  // 객체, 배열 등의 후행 쉼표 설정 (기본값: &quot;es5&quot;)
  &quot;trailingComma&quot;: &quot;es5&quot;,
  // - &quot;all&quot;: 모든 구문에서 후행 쉼표 사용 (함수 인자 포함)
  // - &quot;es5&quot;: ES5에서 유효한 위치에만 후행 쉼표 추가
  // - &quot;none&quot;: 후행 쉼표 사용 안 함

  // 객체 리터럴의 중괄호 주위 공백 추가 (기본값: true)
  // true: { foo: bar }, false: {foo: bar}
  &quot;bracketSpacing&quot;: true,

  // JSX 요소의 &gt; 위치 설정 (기본값: false)
  // true: 마지막 줄에 &gt; 위치, false: 다음 줄에 &gt; 위치
  &quot;bracketSameLine&quot;: false,

  // 화살표 함수 매개변수 괄호 사용 방식 (기본값: &quot;always&quot;)
  &quot;arrowParens&quot;: &quot;always&quot;,
  // - &quot;always&quot;: (x) =&gt; x
  // - &quot;avoid&quot;: x =&gt; x (매개변수가 하나일 때)

  ------------------------
  특수 포맷팅 설정
  ------------------------
  // 줄 끝 문자 설정 (기본값: &quot;lf&quot;)
  &quot;endOfLine&quot;: &quot;lf&quot;,
  // - &quot;lf&quot;: \n (Unix)
  // - &quot;crlf&quot;: \r\n (Windows)
  // - &quot;cr&quot;: \r (Mac OS)
  // - &quot;auto&quot;: 첫 줄 끝 문자 유지

  // 마크다운 텍스트의 줄바꿈 방식 (기본값: &quot;preserve&quot;)
  &quot;proseWrap&quot;: &quot;always&quot;,
  // - &quot;always&quot;: 항상 printWidth에 따라 줄바꿈
  // - &quot;never&quot;: 줄바꿈 하지 않음
  // - &quot;preserve&quot;: 원본 텍스트 줄바꿈 유지

  // HTML 공백 처리 방식 (기본값: &quot;css&quot;)
  &quot;htmlWhitespaceSensitivity&quot;: &quot;strict&quot;,
  // - &quot;css&quot;: CSS display 속성 기준으로 처리
  // - &quot;strict&quot;: 모든 공백을 유지
  // - &quot;ignore&quot;: 모든 공백을 무시

  ------------------------
  플러그인 설정
  ------------------------
  // Tailwind CSS 클래스 자동 정렬을 위한 플러그인
  &quot;plugins&quot;: [&quot;prettier-plugin-tailwindcss&quot;]
}</code></pre><h3 id="2-prettierignore">2. <code>.prettierignore</code></h3>
<p>.gitignore 파일 처럼 Prettier를 적용하지 않을 파일을 작성하면 됩니다.</p>
<pre><code># 빌드 결과물
dist/
build/

# Node.js 모듈
node_modules/

# 환경 설정 파일
.prettierrc
** eslint.config.js -&gt; 자신이 사용하는 확장자명으로 바꿔주기 **

# 로그 파일
npm-debug.log
yarn-debug.log
yarn-error.log

# 프로젝트 루트에서 제외된 다른 파일들
public/
coverage/

# 기타
.vscode/
.idea/
.DS_Store
*.config.js


# 패키지 관리
package-lock.json</code></pre><p><br><br></p>
<h2 id="prettier-적용">Prettier 적용</h2>
<p>제가 설정한 설정과 동일하게 사용하거나 일부만 수정하실 경우 아래 코드를 사용하시면 됩니다!</p>
<h3 id="1-prettierrc-1">1. <code>.prettierrc</code></h3>
<pre><code>{
  &quot;printWidth&quot;: 80,
  &quot;tabWidth&quot;: 2,
  &quot;useTabs&quot;: false,
  &quot;semi&quot;: true,
  &quot;singleQuote&quot;: true,
  &quot;quoteProps&quot;: &quot;as-needed&quot;,
  &quot;jsxSingleQuote&quot;: true,
  &quot;trailingComma&quot;: &quot;es5&quot;,
  &quot;bracketSpacing&quot;: true,
  &quot;bracketSameLine&quot;: false,
  &quot;arrowParens&quot;: &quot;always&quot;,
  &quot;endOfLine&quot;: &quot;lf&quot;,
  &quot;proseWrap&quot;: &quot;always&quot;,
  &quot;htmlWhitespaceSensitivity&quot;: &quot;strict&quot;,
  &quot;plugins&quot;: [&quot;prettier-plugin-tailwindcss&quot;] // 테일윈드 사용 시
}</code></pre><h3 id="2-prettierignore-1">2. <code>.prettierignore</code></h3>
<pre><code>dist/
build/
node_modules/
.prettierrc
.eslintrc.cjs
npm-debug.log
yarn-debug.log
yarn-error.log
public/
coverage/
.vscode/
.idea/
.DS_Store
*.config.js
package-lock.json</code></pre><p><br><br><br></p>
<blockquote>
<p><strong>잠깐! ESLint를 사용하기 전에 ESLint가 무엇인지 알아봅시다.</strong></p>
</blockquote>
<br>

<h2 id="eslint란">ESLint란?</h2>
<p>쉽게 말해 <strong>사용하는 언어의 문법에 문제가 없는지 코드 실행 전 검사해주는 도구</strong>입니다.</p>
<p>ESLint는 JavaScript 및 TypeScript 코드의 정적 분석 도구로, 코드에서 문제를 식별하고 코드 품질을 유지하는 데 도움을 줍니다. 정적 분석 도구는 코드 실행 없이 소스 코드를 분석하여 잠재적인 오류나 일관성 없는 스타일을 찾아냅니다. 이를 통해 코드의 가독성을 향상시키고, 협업 시 개발자들 사이의 코드 품질을 일치시킬 수 있습니다. </p>
<h3 id="1-eslint의-특징">1. ESLint의 특징</h3>
<ul>
<li><p><strong>코드 품질 검사</strong>
ESLint는 코드 품질을 유지하기 위해 다양한 규칙을 적용하여 코드 내의 잠재적인 문제를 식별합니다. 이는 버그를 예방하고 코드의 안정성을 높이는 데 도움을 줍니다.</p>
</li>
<li><p><strong>구성 가능성</strong>
ESLint는 사용자 정의 규칙을 생성하거나 기본 제공 규칙을 사용자 필요에 맞게 조정할 수 있습니다. 또한, 다양한 규칙 세트를 확장하여 사용할 수 있습니다.</p>
</li>
<li><p><strong>플러그인 지원</strong>
ESLint는 플러그인 아키텍처를 가지고 있어, 추가적인 플러그인을 통해 더 많은 언어 기능 및 스타일 검사 기능을 추가할 수 있습니다. React, Vue.js, TypeScript 등과 같은 다양한 라이브러리 및 프레임워크와 통합할 수 있습니다.</p>
</li>
<li><p><strong>자동 수정 기능</strong>
ESLint는 코드 문제를 식별할 뿐만 아니라, --fix 옵션을 통해 일부 문제를 자동으로 수정할 수 있습니다. 이를 통해 코드 스타일을 일관되게 유지하는 것이 더 쉬워집니다.</p>
</li>
<li><p><strong>통합 가능성</strong>
ESLint는 대부분의 코드 편집기와 통합되어 실시간으로 코드 검사를 수행할 수 있습니다. 또한, 빌드 도구나 CI/CD 파이프라인에 통합하여 코드 품질을 지속적으로 관리할 수 있습니다.</p>
</li>
</ul>
<h3 id="2-eslint의-설정파일-종류">2. ESLint의 설정파일 종류</h3>
<p><code>eslint.config.*</code> 형식의 설정 파일을 사용하며, 확장자에 따라 다음과 같은 특징을 가집니다.</p>
<h4 id="1-eslintconfigjs">1. eslint.config.js</h4>
<ul>
<li>가장 범용적 - 널리 사용되어 문서화가 잘 되어있고 호환성이 좋음</li>
<li>“type”: “module” 주의 - package.json 설정에 따라 문법이 달라질 수 있음</li>
<li>유연성 - 동적 설정이 가능하며 대부분의 상황에 적합<pre><code>export default [
{
  files: [&#39;**/*.{js,jsx}&#39;],
  rules: {
    semi: &#39;error&#39;,
    &#39;prefer-const&#39;: &#39;error&#39;
  }
}
]</code></pre><br>

</li>
</ul>
<h4 id="2-eslintconfigmjs">2. eslint.config.mjs</h4>
<ul>
<li>ESM 전용 - 최신 JavaScript 모듈 시스템을 명시적으로 사용</li>
<li>호환성 제한 - 일부 도구나 이전 Node.js 버전과 호환성 문제 가능</li>
<li>명확한 구문 - import/export가 명확하여 코드 이해가 쉬움<pre><code>import js from &#39;@eslint/js&#39;
</code></pre></li>
</ul>
<p>export default [
  {
    files: [&#39;<em>*/</em>.{js,jsx}&#39;],
    rules: js.configs.recommended.rules
  }
]</p>
<pre><code>&lt;br&gt;

#### 3. eslint.config.cjs

- 안정성 - Node.js 모든 버전에서 안정적으로 동작
- 제한된 기능 - 최신 ESM 기능 사용이 제한됨
- 단순함 - 설정이 단순하고 직관적이나 현대적 기능 부족</code></pre><p>module.exports = [
  {
    files: [&#39;<em>*/</em>.{js,jsx}&#39;],
    rules: {
      semi: &#39;error&#39;
    }
  }
]</p>
<pre><code>&lt;br&gt;

#### 4. eslint.config.ts

- 타입 안정성 - TypeScript의 타입 체크와 자동완성 지원
- 추가 설정 필요 - 빌드 설정과 의존성 추가가 필요함
- 개발 생산성 - 복잡한 설정도 타입 안전하게 작성 가능</code></pre><p>import type { Linter } from &#39;eslint&#39;</p>
<p>const config: Linter.Config[] = [
  {
    files: [&#39;<em>*/</em>.{ts,tsx}&#39;],
    rules: {
      &#39;@typescript-eslint/explicit-function-return-type&#39;: &#39;error&#39;
    }
  }
]</p>
<p>export default config</p>
<pre><code>&lt;br&gt;

#### 5. eslint.config.mts/cts

- 명확한 구조 - TypeScript와 모듈 시스템을 명시적으로 구분
- 높은 진입장벽 - 설정이 복잡하고 도구 지원이 제한적
- 타입 안정성 - 타입스크립트의 장점을 최대한 활용 가능</code></pre><p>import type { Linter } from &#39;eslint&#39;</p>
<p>const config: Linter.Config[] = [
  {
    files: [&#39;<em>*/</em>.{ts,tsx}&#39;],
    languageOptions: {
      parser: &#39;@typescript-eslint/parser&#39;
    }
  }
]</p>
<p>export default config</p>
<pre><code>&lt;br&gt;

### 3. ESLint의 설정파일 우선순위
ESLint는 설정 파일을 찾을 때 특정 우선순위를 따릅니다. 프로젝트 루트 디렉토리에서 설정 파일을 찾으며, 다음 순서로 파일을 검색합니다:

1. `eslint.config.js`
2. `eslint.config.mjs`
3. `eslint.config.cjs`
4. `eslint.config.ts` _(추가 설정 필요)_
5. `eslint.config.mts` _(추가 설정 필요)_
6. `eslint.config.cts` _(추가 설정 필요)_
&lt;br&gt;&lt;br&gt;

## ESLint 사용을 위한 2가지 준비
### **1. VS Code의 Extension에서 ESLint 확장 프로그램 설치**
![](https://velog.velcdn.com/images/2_hyeonju/post/de9518b7-6b2d-4dd2-aaca-cd0d05df3006/image.png)

### **2. ESLint 패키지 설치**
.eslintrc.cjs이라는 설정 파일을 생성하여 그 안에 원하는 포맷팅 형식을 저장해야하는데 이 파일을 생성하려면 패키지를 설치해야 합니다.

저는 타입스크립트를 기반으로 리액트와 테일윈드를 사용하여서 이에 기반한 패키지들을 설치하였습니다. 외에도 [ESLint 공식 문서](https://typescript-eslint.io/getting-started#quickstart)에 다른 패키지도 있으니 필요하신 분들은 추가로 설치하시면 됩니다!

#### 패키지 매니저가 npm 일 때
&gt; npm install -D eslint @eslint/js eslint-config-prettier eslint-import-resolver-node eslint-plugin-import eslint-plugin-jsx-a11y eslint-plugin-prettier eslint-plugin-react eslint-plugin-react-hooks eslint-plugin-react-refresh eslint-plugin-tailwindcss prettier prettier-plugin-tailwindcss typescript-eslint

#### 패키지 매니저가 yarn 일 때
&gt; yarn add -D eslint @eslint/js eslint-config-prettier eslint-import-resolver-node eslint-plugin-import eslint-plugin-jsx-a11y eslint-plugin-prettier eslint-plugin-react eslint-plugin-react-hooks eslint-plugin-react-refresh eslint-plugin-tailwindcss prettier prettier-plugin-tailwindcss typescript-eslint


#### 어떤 패키지를 설치하는 건가요?
각 패키지들의 핵심 기능을 설명하면 다음과 같습니다.

핵심 린팅 도구
    •    `eslint`: JavaScript/TypeScript 코드의 문제점을 검사하는 린터
    •    `@eslint/js`: ESLint의 JavaScript 관련 기본 설정

코드 스타일링
    •    `prettier`: 코드 포맷터
    •    `eslint-config-prettier`: ESLint와 Prettier 충돌 방지
    •    `eslint-plugin-prettier`: Prettier를 ESLint 규칙으로 실행
    •    `prettier-plugin-tailwindcss`: Tailwind CSS 클래스 자동 정렬

React 관련
    •    `eslint-plugin-react`: React 관련 린트 규칙
    •    `eslint-plugin-react-hooks`: React Hooks 규칙 검사
    •    `eslint-plugin-react-refresh`: Fast Refresh 관련 규칙

모듈 및 경로
    •    `eslint-plugin-import`: import/export 구문 검사
    •    `eslint-import-resolver-node`: import 경로 해석

UI 및 접근성
    •    `eslint-plugin-jsx-a11y`: JSX 접근성 규칙 검사
    •    `eslint-plugin-tailwindcss`: Tailwind CSS 관련 린트 규칙

TypeScript 지원
    •    `typescript-eslint`: TypeScript 코드를 위한 ESLint 플러그인

&lt;br&gt;&lt;br&gt;

## ESLint 사용
### 1. `eslint.config.js`
사용 준비를 마쳤으니 이제 사용해보아야겠죠? 이 설정 파일에는 설치한 패키지들에 대한 정보를 작성합니다. 만약 타입스크립트에 관한 패키지를 추가로 설치하셨다면 ESLint 설정파일에도 그 정보를 추가로 작성해주어야 합니다.
Vite를 사용하여 프로젝트를 만들었다면 자동으로 생성되는`eslint.config.js` 파일이 있습니다. 이 설정 파일은 React 프로젝트에서 사용할 ESLint 설정을 일부 정의하고 있습니다. 그래서 이 설정에 우리가 추가로 사용하는 패키지만 추가해 주도록 하겠습니다. 사용을 원하는 설정 코드를 &#39; &#39; : &#39; &#39; 형태로 작성합니다.

&lt;br&gt;

** 아래 ESLint 적용에 주석 없이 바로 복사 가능한 코드가 있습니다.**
</code></pre><p>// TypeScript, React, Tailwind CSS 프로젝트를 위한 통합 린팅 규칙 설정</p>
<hr>
<h2 id="필요한-플러그인-및-설정-import">필요한 플러그인 및 설정 import</h2>
<p>import js from &#39;@eslint/js&#39;                        // 기본 JavaScript 규칙
import reactHooks from &#39;eslint-plugin-react-hooks&#39; // React Hooks 규칙
import reactRefresh from &#39;eslint-plugin-react-refresh&#39; // React Refresh 기능 지원
import globals from &#39;globals&#39;                      // 전역 변수 설정
import tseslint from &#39;typescript-eslint&#39;           // TypeScript 지원
import prettier from &#39;eslint-plugin-prettier&#39;      // Prettier 통합
import react from &#39;eslint-plugin-react&#39;           // React 규칙
import jsxA11y from &#39;eslint-plugin-jsx-a11y&#39;      // 접근성 규칙
import importPlugin from &#39;eslint-plugin-import&#39;    // import/export 규칙
import tailwindcss from &#39;eslint-plugin-tailwindcss&#39; // Tailwind CSS 규칙</p>
<p>export default tseslint.config(
  // 빌드 결과물 폴더 무시
  { ignores: [&#39;dist&#39;] },
  {
    // TypeScript와 TSX 파일만 린팅 대상으로 지정
    files: [&#39;<em>*/</em>.{ts,tsx}&#39;],</p>
<pre><code>------------------------
언어 및 파서 설정
------------------------
languageOptions: {
  // TypeScript 파서 사용
  parser: tseslint.parser,
  // 최신 ECMAScript 문법 지원
  ecmaVersion: &#39;latest&#39;,
  // ES 모듈 시스템 사용
  sourceType: &#39;module&#39;,
  globals: {
    // 브라우저 전역 변수 허용
    ...globals.browser,
    // React를 전역 변수로 설정 (읽기 전용)
    React: &#39;readonly&#39;,
  },
  parserOptions: {
    ecmaVersion: &#39;latest&#39;,
    sourceType: &#39;module&#39;,
    ecmaFeatures: {
      // JSX 문법 활성화
      jsx: true
    }
  }
},

------------------------
프로젝트 설정
------------------------
settings: {
  // React 설정
  react: {
    // React 버전 자동 감지
    version: &#39;18.2&#39;,
    // 새로운 JSX 변환 사용
    runtime: &#39;automatic&#39;
  },
  // import 경로 해석 설정
  &#39;import/resolver&#39;: {
    node: {
      // 자동 확장자 처리
      extensions: [&#39;.js&#39;, &#39;.jsx&#39;, &#39;.ts&#39;, &#39;.tsx&#39;, &#39;.css&#39;]
    }
  }
},

------------------------
사용할 플러그인 설정
------------------------
plugins: {
  &#39;react-hooks&#39;: reactHooks,     // React Hooks 규칙
  &#39;react-refresh&#39;: reactRefresh,  // Fast Refresh 지원
  &#39;prettier&#39;: prettier,           // 코드 포맷팅
  &#39;react&#39;: react,                // React 규칙
  &#39;jsx-a11y&#39;: jsxA11y,           // 접근성 검사
  &#39;import&#39;: importPlugin,         // import 순서 및 규칙
  &#39;tailwindcss&#39;: tailwindcss     // Tailwind CSS 규칙
},

------------------------
상세 린팅 규칙
------------------------
rules: {
  // JavaScript 추천 규칙 적용
  ...js.configs.recommended.rules,
  // React Hooks 추천 규칙 적용
  ...reactHooks.configs.recommended.rules,

  // React 17+ 이상에서는 React import 불필요
  &#39;react/react-in-jsx-scope&#39;: &#39;off&#39;,

  // Prettier 규칙 위반시 에러 표시
  &#39;prettier/prettier&#39;: &#39;error&#39;,

  // 화살표 함수 스타일 제한 해제
  &#39;arrow-body-style&#39;: &#39;off&#39;,
  &#39;prefer-arrow-callback&#39;: &#39;off&#39;,

  // target=&quot;_blank&quot; 사용시 보안 경고 비활성화
  &#39;react/jsx-no-target-blank&#39;: &#39;off&#39;,

  // Fast Refresh 관련 설정
  &#39;react-refresh/only-export-components&#39;: [
    &#39;warn&#39;,
    { allowConstantExport: true }
  ],

  // TypeScript 사용하므로 기본 미사용 변수 규칙 비활성화
  &#39;no-unused-vars&#39;: &#39;off&#39;,

  // TypeScript 미사용 변수 규칙 설정
  &#39;@typescript-eslint/no-unused-vars&#39;: [&#39;warn&#39;, {
    &#39;varsIgnorePattern&#39;: &#39;^_&#39;,      // &#39;_&#39;로 시작하는 변수 무시
    &#39;argsIgnorePattern&#39;: &#39;^_&#39;,      // &#39;_&#39;로 시작하는 매개변수 무시
    &#39;ignoreRestSiblings&#39;: true      // 구조분해할당의 나머지 변수 무시
  }],

  // 일치 연산자 강제 (==, != 대신 ===, !== 사용)
  &#39;eqeqeq&#39;: [&#39;error&#39;, &#39;always&#39;],

  // Tailwind CSS 관련 규칙
  &#39;tailwindcss/classnames-order&#39;: &#39;warn&#39;,    // 클래스명 순서 경고
  &#39;tailwindcss/no-custom-classname&#39;: &#39;off&#39;   // 커스텀 클래스명 허용
}</code></pre><p>  }
)</p>
<pre><code>
### 2. `.eslintignore`
.gitignore 파일 처럼 ESLint를 적용하지 않을 파일을 작성하면 됩니다.</code></pre><h1 id="빌드-결과물">빌드 결과물</h1>
<p>dist/
build/</p>
<h1 id="nodejs-모듈">Node.js 모듈</h1>
<p>node_modules/</p>
<h1 id="환경-설정-파일">환경 설정 파일</h1>
<p>.eslintrc.cjs</p>
<h1 id="로그-파일">로그 파일</h1>
<p>npm-debug.log
yarn-debug.log
yarn-error.log</p>
<h1 id="프로젝트-루트에서-제외된-다른-파일들">프로젝트 루트에서 제외된 다른 파일들</h1>
<p>public/
coverage/</p>
<h1 id="기타">기타</h1>
<p>.vscode/
.idea/
.DS_Store
*.config.js</p>
<h1 id="패키지-관리">패키지 관리</h1>
<p>package-lock.json</p>
<pre><code>&lt;br&gt;&lt;br&gt;

## ESLint 적용
제가 설정한 설정과 동일하게 사용하거나 일부만 수정하실 경우 아래 코드를 사용하시면 됩니다!
### 1. `eslint.config.js`</code></pre><p>import js from &#39;@eslint/js&#39;
import reactHooks from &#39;eslint-plugin-react-hooks&#39;
import reactRefresh from &#39;eslint-plugin-react-refresh&#39;
import globals from &#39;globals&#39;
import tseslint from &#39;typescript-eslint&#39;
import prettier from &#39;eslint-plugin-prettier&#39;
import react from &#39;eslint-plugin-react&#39;
import jsxA11y from &#39;eslint-plugin-jsx-a11y&#39;
import importPlugin from &#39;eslint-plugin-import&#39;
import tailwindcss from &#39;eslint-plugin-tailwindcss&#39;</p>
<p>export default tseslint.config(
  { ignores: [&#39;dist&#39;] },
  {
    files: [&#39;<em>*/</em>.{ts,tsx}&#39;],
    languageOptions: {
      parser: tseslint.parser,
      ecmaVersion: &#39;latest&#39;,
      sourceType: &#39;module&#39;,
      globals: {
        ...globals.browser,
        React: &#39;readonly&#39;,
      },
      parserOptions: {
        ecmaVersion: &#39;latest&#39;,
        sourceType: &#39;module&#39;,
        ecmaFeatures: {
          jsx: true
        }
      }
    },
    settings: {
      react: {
        version: &#39;18.2&#39;,
        runtime: &#39;automatic&#39;
      },
      &#39;import/resolver&#39;: {
        node: {
          extensions: [&#39;.js&#39;, &#39;.jsx&#39;, &#39;.ts&#39;, &#39;.tsx&#39;, &#39;.css&#39;]
        }
      }
    },
    plugins: {
      &#39;react-hooks&#39;: reactHooks,
      &#39;react-refresh&#39;: reactRefresh,
      &#39;prettier&#39;: prettier,
      &#39;react&#39;: react,
      &#39;jsx-a11y&#39;: jsxA11y,
      &#39;import&#39;: importPlugin,
      &#39;tailwindcss&#39;: tailwindcss
    },
    rules: {
      ...js.configs.recommended.rules,
      ...reactHooks.configs.recommended.rules,
      &#39;react/react-in-jsx-scope&#39;: &#39;off&#39;,
      &#39;prettier/prettier&#39;: &#39;error&#39;,
      &#39;arrow-body-style&#39;: &#39;off&#39;,
      &#39;prefer-arrow-callback&#39;: &#39;off&#39;,
      &#39;react/jsx-no-target-blank&#39;: &#39;off&#39;,
      &#39;react-refresh/only-export-components&#39;: [
        &#39;warn&#39;,
        { allowConstantExport: true }
      ],
      &#39;no-unused-vars&#39;: &#39;off&#39;,
      &#39;@typescript-eslint/no-unused-vars&#39;: [&#39;warn&#39;, {
      &#39;varsIgnorePattern&#39;: &#39;^<em>&#39;,
      &#39;argsIgnorePattern&#39;: &#39;^</em>&#39;,
      &#39;ignoreRestSiblings&#39;: true
      }],
      &#39;eqeqeq&#39;: [&#39;error&#39;, &#39;always&#39;],
      &#39;tailwindcss/classnames-order&#39;: &#39;warn&#39;,
      &#39;tailwindcss/no-custom-classname&#39;: &#39;off&#39;
    }
  }
)</p>
<pre><code>
### 2. `.eslintignore`</code></pre><p>dist/
build/
node_modules/
.eslintrc.cjs
npm-debug.log
yarn-debug.log
yarn-error.log
public/
coverage/
.vscode/
.idea/
.DS_Store
*.config.js
package-lock.json</p>
<pre><code>
&lt;br&gt;&lt;br&gt;&lt;br&gt;
## 마무리하며

구글링했을 때 나오는 정보들이 많지만 아직 코딩 초보에게는 어려운 정보들이 너무 많았습니다. 그래서 학습 정도에 맞게 필요한 정보만을 정리하여 코딩 초보도 보고 이해할 수 있는 글을 쓰고싶었습니다.
**다른 코딩 초보분들에게 이 글이 도움이 되었으면 좋겠습니다 **🤘</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[Axios와 Promise의 사용]]></title>
            <link>https://velog.io/@2_hyeonju/Axios%EC%99%80-Promise%EC%9D%98-%EC%82%AC%EC%9A%A9</link>
            <guid>https://velog.io/@2_hyeonju/Axios%EC%99%80-Promise%EC%9D%98-%EC%82%AC%EC%9A%A9</guid>
            <pubDate>Fri, 21 Jun 2024 09:57:58 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h3 id="20240621-8차-스터디">2024.06.21 8차 스터디</h3>
<p>오늘의 스터디 주제는? Axios와 Promise의 사용!</p>
</blockquote>
<p>프로젝트를 할 때는 axios를 사용할 때 async/await을 사용하여 비동기 처리를 하였는데 이번 도커를 배우는 과정에서는 then/catch를 사용하여서 무엇이 다른건지 알아보고자 한다!</p>
<p><br><br><br></p>
<h2 id="1-axios란">1. Axios란?</h2>
<h3 id="1-1-axios의-의미">1-1. Axios의 의미</h3>
<p>Axios는 웹 페이지나 서버에서 HTTP 요청을 쉽게 처리할 수 있는 JavaScript 라이브러리입니다. Promise 기반으로 설계되어 비동기 작업을 간편하게 처리할 수 있으며, 다양한 HTTP 요청 메서드(GET, POST, PUT, DELETE 등)를 지원합니다.</p>
<h3 id="1-2-axios의-주요-기능">1-2. Axios의 주요 기능</h3>
<p>Axios의 주요 기능이 많지만 그 중에서 자주 사용해본 기능 세가지만 알아봅시다.</p>
<ol>
<li><strong>다양한 HTTP 메서드</strong>
Axios는 GET, POST, PUT, DELETE 등 다양한 HTTP 요청 메서드를 지원합니다. 각 메서드는 해당 요청의 특성에 맞게 사용할 수 있습니다.</li>
</ol>
<pre><code>    axios.get(&#39;/data&#39;);     // 데이터 가져오기
    axios.post(&#39;/data&#39;, {   // 데이터 보내기
      key: &#39;value&#39;
    });
    axios.put(&#39;/data/1&#39;, {  // 데이터 업데이트
      key: &#39;newValue&#39;
    });
    axios.delete(&#39;/data/1&#39;); // 데이터 삭제</code></pre><ol start="2">
<li><p><strong>자동 JSON 변환</strong>
Axios는 서버에서 받거나 서버로 보내는 JSON 데이터를 자동으로 JavaScript 객체로 변환합니다. 또한, JavaScript 객체를 서버로 보낼 때 자동으로 JSON 문자열로 변환해줍니다.</p>
<pre><code> axios.get(&#39;https://api.example.com/data&#39;)
    .then(response =&gt; {
     console.log(response.data);  // 자동으로 변환된 JavaScript 객체
    });

 axios.post(&#39;https://api.example.com/data&#39;, {
       key: &#39;value&#39;
 });
</code></pre></li>
</ol>
<pre><code>
3. **응답 데이터 변환**
응답 데이터를 자동으로 변환하여 사용자가 쉽게 처리할 수 있도록 도와줍니다.</code></pre><pre><code>axios.get(&#39;/data&#39;)
  .then(response =&gt; {
    console.log(response.data);  // 자동으로 변환된 응답 데이터
  });</code></pre><pre><code>&lt;br&gt;&lt;br&gt;&lt;br&gt;

## 2. Promise란?
위의 설명에서 Axios가 Promise 기반으로 설계되어있다고 하는데 그럼 Promise는 무엇일까요?

### 2-1. Promise의 세가지 상태
Promise는 JavaScript에서 비동기 작업을 처리하는 객체입니다. 비동기 작업의 완료 또는 실패를 나타내며, 세 가지 상태를 가질 수 있습니다:

- Pending(대기): 작업이 아직 완료되지 않은 상태
- Fulfilled(이행): 작업이 성공적으로 완료된 상태
- Rejected(거부): 작업이 실패한 상태

Promise는 비동기 작업의 결과를 처리하기 위해 then, catch, finally 메서드를 제공합니다.
&lt;br&gt;&lt;br&gt;&lt;br&gt;

## 3. Axios 사용 방법
### 3-1. then/catch
then과 catch는 Promise 객체의 메서드로, 비동기 작업이 완료되거나 실패했을 때 실행할 코드를 지정합니다.
- **then**은 작업이 성공적으로 완료될 때 실행
- **catch**는 작업이 실패했을 때 실행

GET 요청의 예시를 함께 볼까요?</code></pre><p>const axios = require(&#39;axios&#39;);</p>
<p>axios.get(&#39;<a href="https://jsonplaceholder.typicode.com/posts/1&#39;">https://jsonplaceholder.typicode.com/posts/1&#39;</a>)
  .then(response =&gt; {
    console.log(response.data);  // 성공적으로 데이터를 받아온 경우
  })
  .catch(error =&gt; {
    console.error(&#39;Error fetching data:&#39;, error);  // 요청이 실패한 경우
  });</p>
<pre><code>
### 3-2. async/await + try/catch
async와 await 키워드를 사용하면 비동기 코드를 동기 코드처럼 작성할 수 있습니다. await는 Promise가 해결될 때까지 기다리며, async 함수 내에서만 사용할 수 있습니다. 오류 처리는 try/catch 블록을 사용하여 처리합니다.
GET 요청의 예시를 함께 볼까요?</code></pre><p>const axios = require(&#39;axios&#39;);</p>
<p>async function fetchData() {
  try {
    const response = await axios.get(&#39;<a href="https://jsonplaceholder.typicode.com/posts/1&#39;">https://jsonplaceholder.typicode.com/posts/1&#39;</a>);
    console.log(response.data);  // 성공적으로 데이터를 받아온 경우
  } catch (error) {
    console.error(&#39;Error fetching data:&#39;, error);  // 요청이 실패한 경우
  }
}</p>
<p>fetchData();</p>
<pre><code>
### 3-3. async/await + then/catch
async/await와 then/catch를 혼용할 수도 있습니다. 그러나 3-2번 방식으로 코드를 작성하는 것이 더 가독성이 좋습니다.</code></pre><p>const axios = require(&#39;axios&#39;);</p>
<p>async function fetchData() {
  await axios.get(&#39;<a href="https://jsonplaceholder.typicode.com/posts/1&#39;">https://jsonplaceholder.typicode.com/posts/1&#39;</a>)
    .then(response =&gt; {
      console.log(response.data);  // 성공적으로 데이터를 받아온 경우
    })
    .catch(error =&gt; {
      console.error(&#39;Error fetching data:&#39;, error);  // 요청이 실패한 경우
    });
}</p>
<p>fetchData();</p>
<pre><code>&lt;br&gt;&lt;br&gt;&lt;br&gt;

## 4. Axios 사용 방법 차이
then/catch 메서드를 사용하여 비동기 작업을 처리하는 경우와 async/await 키워드와 try/catch 블록을 사용하여 비동기 코드를 동기 코드처럼 작성하는 경우에는 몇 가지 차이점이 있습니다. 주로 코드의 가독성과 작성 방식에서 차이가 나타납니다.
### 4-1. then/catch
then과 catch 메서드는 Promise 객체의 메서드로, 비동기 작업이 완료되거나 실패했을 때 실행할 코드를 지정합니다. 이 방식은 전통적인 Promise 사용 방식입니다.
</code></pre><p>const axios = require(&#39;axios&#39;);</p>
<p>axios.get(&#39;<a href="https://jsonplaceholder.typicode.com/posts/1&#39;">https://jsonplaceholder.typicode.com/posts/1&#39;</a>)
  .then(response =&gt; {
    console.log(response.data);  // 성공적으로 데이터를 받아온 경우
    return axios.get(&#39;<a href="https://jsonplaceholder.typicode.com/posts/2&#39;">https://jsonplaceholder.typicode.com/posts/2&#39;</a>);
  })
  .then(response =&gt; {
    console.log(response.data);  // 두 번째 데이터를 받아온 경우
  })
  .catch(error =&gt; {
    console.error(&#39;Error fetching data:&#39;, error);  // 요청이 실패한 경우
  });</p>
<pre><code>

#### 장점
- **명시적 체이닝**
여러 비동기 작업을 순차적으로 수행할 때 체이닝을 통해 명시적으로 연결할 수 있습니다.

- **직관적인 에러 핸들링**
각 단계별로 에러를 개별적으로 처리할 수 있습니다.

#### 단점
- **콜백 지옥**
여러 비동기 작업을 중첩하여 처리할 경우 코드가 복잡해지고 가독성이 떨어질 수 있습니다.

- **가독성 저하**
코드의 흐름이 함수 체이닝으로 인해 분리되어 가독성이 떨어질 수 있습니다.
&lt;br&gt;
### 4-2. async/await + try/catch
async/await는 비동기 작업을 처리하는 새로운 방식으로, 비동기 코드를 동기 코드처럼 작성할 수 있게 합니다. await는 Promise가 해결될 때까지 기다리며, async 함수 내에서만 사용할 수 있습니다.</code></pre><p>const axios = require(&#39;axios&#39;);</p>
<p>async function fetchData() {
  try {
    const response1 = await axios.get(&#39;<a href="https://jsonplaceholder.typicode.com/posts/1&#39;">https://jsonplaceholder.typicode.com/posts/1&#39;</a>);
    console.log(response1.data);  // 첫 번째 데이터를 받아온 경우</p>
<pre><code>const response2 = await axios.get(&#39;https://jsonplaceholder.typicode.com/posts/2&#39;);
console.log(response2.data);  // 두 번째 데이터를 받아온 경우</code></pre><p>  } catch (error) {
    console.error(&#39;Error fetching data:&#39;, error);  // 요청이 실패한 경우
  }
}</p>
<p>fetchData();</p>
<pre><code>
#### 장점
- **가독성 향상**
동기 코드처럼 작성할 수 있어 코드의 가독성과 유지보수성이 높아집니다.

- **간편한 에러 핸들링**
try/catch 블록을 사용하여 한 곳에서 에러를 처리할 수 있습니다.

- **구조적 작성**
비동기 작업을 순차적으로 작성할 때 코드 구조가 명확해집니다.

#### 단점
- **구형 브라우저 호환성 문제**
오래된 브라우저에서는 async/await를 지원하지 않을 수 있습니다. 이 경우 Babel 같은 트랜스파일러가 필요합니다.

- **에러 전파가 복잡할 수 있음**
여러 단계의 비동기 작업에서 에러를 체이닝할 경우 복잡해질 수 있습니다.
&lt;br&gt;&lt;br&gt;&lt;br&gt;

## 5. Axios 사용 방법 요약
### **5-1. then/catch**
- 여러 비동기 작업을 체인으로 연결할 수 있다.
- 코드의 흐름이 분리되어 가독성이 떨어질 수 있다.
- 각 단계별로 에러를 개별적으로 처리할 수 있다.

### **5-2. async/await + try/catch**
- 동기 코드처럼 작성할 수 있어 가독성이 높다.
- try/catch 블록을 사용하여 간편하게 에러를 처리할 수 있다.
- 코드 구조가 명확하여 유지보수성이 높다.

&gt; 결론적으로, 간단한 비동기 작업에는 then/catch를 사용할 수 있지만, 복잡한 비동기 작업이나 여러 단계의 비동기 작업을 처리할 때는 async/await와 try/catch를 사용하는 것이 더 가독성이 좋고 유지보수가 쉽습니다.</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[Prettier 설정하기]]></title>
            <link>https://velog.io/@2_hyeonju/Prettier-%EC%84%A4%EC%A0%95%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@2_hyeonju/Prettier-%EC%84%A4%EC%A0%95%ED%95%98%EA%B8%B0</guid>
            <pubDate>Fri, 14 Jun 2024 09:58:24 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h3 id="20240614-7차-스터디">2024.06.14 7차 스터디</h3>
<p>오늘의 스터디 주제는? Prettier 설정하기!</p>
</blockquote>
<p>수준별 수업 중에 Prettier 파일을 프로젝트 안에 만들어 놓으면 협업시에 동일한 기준으로 코드를 작성할 수 있어서 만들어 보는 것을 추천해주셔서 방법에 대해 작성하고자 한다!</p>
<p><br><br><br></p>
<h2 id="1-prettier란">1. Prettier란?</h2>
<p>프리티어(Prettier)는 코드 포매터(Code Formatter)로서, 주어진 코드를 일관된 스타일로 자동으로 정리해주는 도구입니다. 코드 포매터는 작성한 코드를 일관된 형식으로 자동으로 정렬하고 들여쓰기를 맞추는 역할을 합니다. 이를 통해 코드의 가독성을 향상시키고, 협업 시 개발자들 사이의 코드 스타일을 일치시킬 수 있습니다.</p>
<h3 id="1-1-prettier의-특징">1-1. Prettier의 특징</h3>
<ul>
<li><p><strong>자동 형식 맞춤</strong>
프리티어는 코드를 자동으로 정리해주어, 개발자가 수동으로 들여쓰기나 공백을 조정할 필요가 없습니다.</p>
</li>
<li><p><strong>다양한 언어 지원</strong>
JavaScript, TypeScript, CSS, HTML, JSON 등 다양한 프로그래밍 언어와 파일 형식을 지원합니다.</p>
</li>
<li><p><strong>구성 가능성</strong>
프리티어는 다양한 설정 옵션을 제공하여 개발자가 코드 스타일을 자신의 기호에 맞게 조정할 수 있습니다. 예를 들어 들여쓰기 너비, 따옴표 스타일, 세미콜론 사용 등을 설정할 수 있습니다.</p>
</li>
<li><p><strong>프로젝트에 통합 가능</strong>
프리티어는 대부분의 개발 환경에서 사용할 수 있으며, 진행 중인 소프트웨어 프로젝트에 쉽게 통합하여 사용할 수 있는 능력을 의미합니다.
즉, 프로젝트에 프리티어를 적용하고, 코드를 정리하거나 포맷팅할 때 프리티어가 원활하게 작동하고 유용하게 사용될 수 있는 상태를 말합니다.</p>
</li>
</ul>
<h3 id="1-2-prettier의-적용-예시">1-2. Prettier의 적용 예시</h3>
<p>아래 예시들로 프리티어가 다양한 프로그래밍 언어와 파일 형식에서 어떻게 코드를 정리하고 포맷팅하는지를 알 수 있습니다. 프리티어는 각 언어의 특정 스타일 가이드에 맞게 코드를 자동으로 정리하여 개발자가 일관된 코드 스타일을 유지할 수 있도록 도와줍니다.</p>
<ul>
<li>CSS 코드의 예시<pre><code>// 사용 전
body{background-color:  #f0f0f0;color: #333;}
</code></pre></li>
</ul>
<p>// 사용 후
body {
  background-color: #f0f0f0;
  color: #333;
}</p>
<pre><code>
- JavaScript 코드의 예시</code></pre><p>// 사용 전
function add(a, b) {return a+b;}
console.log(add(1,2));</p>
<p>// 사용 후
function add(a, b) {
  return a + b;
}
console.log(add(1, 2));</p>
<pre><code>
- JavaScript + JSX 코드의 예시</code></pre><p>// 사용 전
import React from &#39;react&#39;;</p>
<p>function Rendering({ isLoggedIn }) {
if (isLoggedIn) {
return <p>Welcome back!</p>;
} else {
return <p>Please log in.</p>;
}
}</p>
<p>export default \Rendering;</p>
<p>// 사용 후
import React from &#39;react&#39;;</p>
<p>function Rendering({ isLoggedIn }) {
  if (isLoggedIn) {
    return <p>Welcome back!</p>;
  } else {
    return <p>Please log in.</p>;
  }
}</p>
<p>export default Rendering;</p>
<pre><code>&lt;br&gt;&lt;br&gt;&lt;br&gt;

## 2. Prettier 적용 방법
### 2-1. VS Code의 Extension
- **설치하기**
Prettier를 설정하는 첫 번째 방법은 VS Code의 기능중 하나인 Extension에서 Prettier 확장 프로그램을 설치하는 것 입니다.&lt;br&gt;
VS Code를 실행한 뒤 맨 왼쪽에 보이는 메뉴에서 제일 아래 메뉴가 Extension입니다. 눌러보면 Extension 검색창, 내가 설치한 확장 프로그램과 추천 확장 프로그램을 볼 수 있습니다.
검색창에 Prettier를 검색하고 제일 맨 위에 보이는 Prettier - Code formatter를 설치합니다.
![VS Code 이미지](https://velog.velcdn.com/images/2_hyeonju/post/961bbd18-e1d3-4272-8010-f9a18c0af6a2/image.png)

- **설정하기**
맥의 경우 Command + , / 윈도우의 경우 Ctrl + , 를 동시에 눌러 VS Code 설정창을 띄웁니다.
화면에 윗부분에 있는 검색창에 Editor: Default Formatter를 입력한 후 None을 Prettier - Code formatter로 바꾸어 줍니다.
이는 기본적인 Formatter로 Prettier를 사용하는 설정입니다.
![](https://velog.velcdn.com/images/2_hyeonju/post/9d12274b-3d40-4ae3-abaf-a5605f0bf0c1/image.png)다음으로 다시 검색창에 Editor: Format on Save를 입력한 후 빈 박스를 클릭하여 체크합니다.
이는 파일을 저장할 때마다 자동으로 Prettier를 사용하는 설정입니다.
![](https://velog.velcdn.com/images/2_hyeonju/post/5b73e1be-bb2c-4ce3-ac49-b3a52e2e030c/image.png)마지막으로 Prettier를 검색하여 나오는 설정들을 자신의 취향에 따라 설정합니다.
![](https://velog.velcdn.com/images/2_hyeonju/post/f272094d-a345-432d-836e-2f806cc0365f/image.png)

&gt; 만약 모든 프로젝트에 동일한 Prettier 설정을 할 것이라면 위 방법을 따라 설정하면 됩니다. 하지만 프로젝트에 각각 다른 설정이 필요하거나 협업을 진행하게 된다면 Prettier 설정을 다르게하거나 통일할 필요가 있습니다.&lt;br&gt;
이를 해결하기 위해 .prettierrc라는 설정 파일을 프로젝트 내에 생성하여 해당 파일에 원하는 포맷팅 형식을 저장합니다. 

### 2-2. .prettierrc 파일
- **설치하기**
Prettier를 설정하는 두 번째 방법은 prettier를 설치하여 .prettierrc 파일을 생성하는 것 입니다.&lt;br&gt;
prettier는 개발 의존성으로 설치하기 때문에 --save-dev로 설치합니다. 현재 프로젝트 내에서 터미널을 열어 아래 명령어를 입력하여 설치합니다.&lt;br&gt;
```npm install --save-dev prettier```
&gt; _**왜 개발 의존성으로 설치하나요?**_
개발 도구를 분리하고, 애플리케이션의 크기를 줄이고, 의존성 관리를 명확하게 하기 위해서 입니다.
Prettier는 애플리케이션의 실제 동작에는 영향을 미치지 않기 때문에 오로지 개발 도구로서 분리하여 애플리케이션의 크기를 줄일 수 있습니다. 또한 --save-dev 플래그를 사용하면 package.json 파일의 devDependencies에 프리티어가 추가되어 개발 환경에서만 사용된다는 것을 명확히 알 수 있습니다.


- **파일 생성하기**
가장 상위 폴더에 .prettierrc 파일을 생성합니다. 그리고 원하는 Prettier 옵션의 코드를 { }안에 키와 값의 형태로 작성합니다. 작성시에는 CSS파일에서 작성하는 것 처럼 자동완성이 지원됩니다.
![](https://velog.velcdn.com/images/2_hyeonju/post/79fad9d1-4847-4ab7-a1d7-745482cd526a/image.png)여기서 주의할 점이 하나 있는데 CSS 작성 시에 적용의 우선 순위가 있듯이 Prettier 적용 시에도 우선 순위가 있습니다. 바로 VS Code 세팅에서 설정한 값보다 .prettierrc 파일의 적용이 우선적으로 적용됩니다. 따라서 보통 협업시에는 .prettierrc 파일을 생성하게 됩니다.

- **파일 작성 예시**
{ } 안에 필요한 속성과 해당 값을 작성합니다. 일부 옵션을 예시를 통해 알아봅시다.
![](https://velog.velcdn.com/images/2_hyeonju/post/19f5abdd-6a4c-466f-b601-be99c5d072c5/image.png)현재 입력한 옵션은 탭키를 입력하였을 때의 들여쓰기 간격을 2칸으로 설정하고, 코드의 끝부분에 세미콜론(;)을 항상 붙이는 옵션을 true로 설정한 것 입니다.

### 2-3. .prettierignore 파일
- **파일 생성하기**
파일명을 보니 떠오르는 파일이 있나요? 바로 .gitignore 파일입니다. 이름처럼 .prettierignore 파일도 .gitignore와 같은 역할을 합니다. .prettierignore 파일에는 Prettier의 설정을 적용하지 않을 파일명을 작성합니다.
![](https://velog.velcdn.com/images/2_hyeonju/post/9f416a15-8add-441c-81f6-5add275a64bd/image.png)
&gt; 예를 들어, node_modules/ 디렉터리의 경우, 외부 라이브러리의 소스 코드가 위치하고 있기 때문에 포맷팅을 할 필요가 없습니다. 또한 package-lock.json 파일도 npm으로 패키지를 설치할 때 자동으로 업데이트는 되는 파일이므로 굳이 포맷팅을 할 이유가 없겠죠??

&lt;br&gt;&lt;br&gt;

## 3. Prettier 옵션
### 3-1. 개발자들이 자주 사용하는 옵션
- **printWidth**
한 줄의 최대 길이를 설정합니다. 이 값을 초과하면 프리티어는 코드 줄을 적절한 위치에서 자동으로 줄바꿈합니다.
기본값: 80
예시: &quot;printWidth&quot;: 100

- **tabWidth**
탭 하나에 해당하는 공백의 수를 설정합니다.
기본값: 2
예시: &quot;tabWidth&quot;: 4

- **useTabs**
들여쓰기에 탭을 사용할지 여부를 설정합니다. true로 설정하면 탭을 사용하고, false로 설정하면 공백을 사용합니다.
기본값: false
예시: &quot;useTabs&quot;: false

- **semi**
각 코드 라인의 끝에 세미콜론을 추가할지 여부를 설정합니다.
기본값: true
예시: &quot;semi&quot;: true

- **singleQuote**
문자열을 작은따옴표로 감쌀지 여부를 설정합니다. true로 설정하면 작은따옴표를 사용하고, false로 설정하면 큰따옴표를 사용합니다.
기본값: false
예시: &quot;singleQuote&quot;: true

- **trailingComma**
객체, 배열, 함수 매개변수 등에 후행 쉼표를 추가할지 여부를 설정합니다. 가능한 값은 &quot;none&quot;, &quot;es5&quot;, &quot;all&quot; 입니다.
기본값: &quot;es5&quot;
예시: &quot;trailingComma&quot;: &quot;all&quot;

- **bracketSpacing**
객체 리터럴에서 중괄호 사이의 공백을 설정합니다.
기본값: true
예시: &quot;bracketSpacing&quot;: true

- **jsxBracketSameLine**
JSX의 마지막 &gt;를 다음 줄로 내릴지 여부를 설정합니다. true로 설정하면 같은 줄에 유지합니다.
기본값: false
예시: &quot;jsxBracketSameLine&quot;: false

- **arrowParens**
화살표 함수의 매개변수가 하나일 때 괄호를 포함할지 여부를 설정합니다. 가능한 값은 &quot;avoid&quot; (매개변수가 하나일 때 괄호 생략)와 &quot;always&quot; (항상 괄호 사용)입니다.
기본값: &quot;always&quot;
예시: &quot;arrowParens&quot;: &quot;avoid&quot;







</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[자바스크립트 map 메서드]]></title>
            <link>https://velog.io/@2_hyeonju/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-map%EB%A9%94%EC%84%9C%EB%93%9C</link>
            <guid>https://velog.io/@2_hyeonju/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-map%EB%A9%94%EC%84%9C%EB%93%9C</guid>
            <pubDate>Fri, 31 May 2024 10:02:48 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h3 id="20240531-6차-스터디">2024.05.31 6차 스터디</h3>
<p>오늘의 스터디 주제는? 자주 쓰이는 메서드!</p>
</blockquote>
<p>자바스크립트의 라이브러리인 리액트를 배우고 있다 보니 자바스크립트 문법을 사용하고 있다. 자바스크립트를 학습했음에도 기억이 잘 나지 않고 강의에서 자주 사용하는 문법들도 자꾸 까먹고 해서 기록을 해보려고 한다!
<br><br><br></p>
<h2 id="1-map-메서드">1. map 메서드</h2>
<p>map 메서드는 배열의 각 요소를 순회하면서 새로운 배열을 생성하는 함수입니다. 이 과정에서 배열의 각 요소를 변환할 수 있습니다. Todo 리스트 같은 데이터를 화면에 렌더링할 때, 배열을 순회하면서 각 항목을 원하는 형식으로 변환해야 할 때 주로 사용합니다.</p>
<h3 id="1-1-map-메서드의-특징">1-1. map 메서드의 특징</h3>
<ul>
<li><strong>배열을 순회하면서 변환한다.</strong><blockquote>
<p>여러 개의 종이 쪽지가 들어 있는 박스가 있다고 생각해보세요. 각 종이 쪽지에는 해야 할 일이 적혀 있습니다.
이 쪽지들을 하나씩 꺼내서, 각 쪽지를 예쁜 카드에 붙여서 새로운 박스에 넣는다고 생각해보세요.
map은 이 작업을 프로그래밍적으로 도와주는 도구입니다. 각 쪽지를 꺼내고, 변환하고, 새로운 박스에 넣는 일을 자동으로 해줍니다.</p>
</blockquote>
</li>
</ul>
<br>

<ul>
<li><strong>간결하고 읽기 쉬운 코드</strong><blockquote>
<p>만약 종이 쪽지 100개를 하나하나 예쁜 카드에 붙여야 한다면, 시간이 많이 걸리고 실수할 가능성도 있습니다.
map은 이 과정을 간단하게 코드 몇 줄로 처리할 수 있게 해줍니다. 코드가 간결해지면 읽기 쉽고, 유지보수하기도 쉬워집니다.</p>
</blockquote>
</li>
</ul>
<br>

<ul>
<li><strong>불변성 유지</strong><blockquote>
<p>원래 박스에 있는 종이 쪽지들을 변형하지 않고, 새로운 박스에 변형된 쪽지를 넣습니다.
map은 원래 배열을 변경하지 않고, 변형된 새로운 배열을 반환합니다. 이로 인해 데이터의 불변성을 유지할 수 있습니다.</p>
</blockquote>
</li>
</ul>
<p><br><br></p>
<h3 id="1-2-map-메서드의-기본-구조">1-2. map 메서드의 기본 구조</h3>
<p>map 메서드의 기본 구조는 다음과 같습니다.</p>
<pre><code>변수에 반환할 값을 할당 경우

const newArray = array.map((매개변수) =&gt; {
  return {
      // 반환할 값
  }
});</code></pre><p>매개변수로는 3가지가 있습니다.</p>
<ul>
<li><strong>currentValue: 배열의 현재 요소.</strong></li>
<li>index (선택적): 배열의 현재 요소의 인덱스.</li>
<li>array (선택적): map 메서드를 호출한 배열 자체.</li>
</ul>
<p>일반적으로 map 메서드에서는 매개변수 하나만 사용하고 currentValue가 가장 자주 사용되며 필수적으로 사용됩니다. 배열의 인덱스나 배열 자체에 대한 정보를 활용할 때는 매개변수를 여러개 사용하기도 합니다.</p>
<blockquote>
<p><strong>그런데 왜 map메서드의 ()안에 함수를 넣을 것 일까요?</strong>
map 메서드는 배열의 각 요소를 어떻게 변형할지 알아야하기 때문입니다. 이를 위해 &quot;어떻게 변형할지&quot;를 정의한 함수를 매개변수로 받는 것입니다.</p>
</blockquote>
<p><br><br></p>
<h3 id="1-3-map-메서드의-예시">1-3. map 메서드의 예시</h3>
<p>todoList를 반환하는 예시를 함께 볼까요?</p>
<pre><code>import React from &#39;react&#39;;

const TodoList = () =&gt; {
    const todos = [
        { id: 1, text: &#39;밥먹기&#39;, completed: false },
        { id: 2, text: &#39;양치하기&#39;, completed: true },
        { id: 3, text: &#39;공부하기&#39;, completed: false }
    ];

    return (
        &lt;div&gt;
            &lt;h1&gt;Todo List&lt;/h1&gt;
            &lt;ul&gt;
                {todos.map(todo =&gt; &lt;li&gt; {todo.text} &lt;/li&gt;)}
            &lt;/ul&gt;
        &lt;/div&gt;
    );
};

export default TodoList;</code></pre><ul>
<li>전체적으로 코드를 해석해봅시다.</li>
</ul>
<ol>
<li><p>먼저 todos라는 변수에 객체로 이루어진 배열이 할당되어 있습니다.</p>
</li>
<li><p>map 메서드를 사용하여 todos라는 배열을 순회합니다.</p>
</li>
<li><p>text 정보만 담겨있는 새로운 배열을 <code>&lt;li&gt;</code>에 담습니다.</p>
<br>
</li>
</ol>
<ul>
<li>map 메서드 부분만 따로 자세히 해석해볼까요?<pre><code>{todos.map(todo =&gt; &lt;li&gt; {todo.text} &lt;/li&gt;)}</code></pre></li>
</ul>
<ol>
<li><p>todos 배열에 map 메서드를 사용하여 새 배열을 생성하고자 합니다.</p>
</li>
<li><p>매개변수로는 todo라는 이름의 매개변수를 사용하였습니다.</p>
</li>
<li><p>todos의 배열의 각 객체를 매개변수 todo로 접근하게 됩니다.
즉, 객체 하나 하나를 todo란 이름으로 배열의 각 요소를 순회합니다.</p>
</li>
<li><p>todo.text(todo의 text) 정보들만 따로 담은 새 배열을 반환합니다.</p>
</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[삼항 연산자와 논리 연산자]]></title>
            <link>https://velog.io/@2_hyeonju/%EC%82%BC%ED%95%AD-%EC%97%B0%EC%82%B0%EC%9E%90%EC%99%80-%EB%85%BC%EB%A6%AC-%EC%97%B0%EC%82%B0%EC%9E%90</link>
            <guid>https://velog.io/@2_hyeonju/%EC%82%BC%ED%95%AD-%EC%97%B0%EC%82%B0%EC%9E%90%EC%99%80-%EB%85%BC%EB%A6%AC-%EC%97%B0%EC%82%B0%EC%9E%90</guid>
            <pubDate>Fri, 24 May 2024 09:30:35 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h3 id="20240524-5차-스터디">2024.05.24 5차 스터디</h3>
<p>오늘의 스터디 주제는? 삼항 연산자와 논리 연산자!</p>
</blockquote>
<p>지난번 스터디 할 때 화살표 함수의 이해가 어려워서 조금 정리한 뒤 남은 부분을 삼항연산자로 정리하였었다. 이번에 화살표 함수에 대한 기록 작성을 끝내고 나니 내용이 길어져서ㅋㅋ,,, 삼항연산자는 논리 연산자와 함께 기록하려고 한다!
<br><br><br></p>
<h2 id="1-삼항-연산자">1. 삼항 연산자</h2>
<p>삼항 연산자는 자바스크립트에서 조건에 따라 값을 선택할 때 사용하는 간단한 조건문입니다. 삼항 연산자는 세 개의 피연산자를 가지며, 그래서 &#39;삼항&#39;이라고 부릅니다. 일반적으로 if-else 문을 대체할 수 있는 간단한 방법으로 사용됩니다.</p>
<h3 id="1-1-삼항-연산자의-문법">1-1. 삼항 연산자의 문법</h3>
<pre><code>A ? B : C</code></pre><ul>
<li>A : 평가될 조건을 나타내는 표현식입니다. 이 조건이 참(true)이면 B, 거짓(false)이면 C가 평가됩니다.</li>
<li>B : 조건이 참일 때 반환될 표현식입니다.</li>
<li>C : 조건이 거짓일 때 반환될 표현식입니다.</li>
</ul>
<p>삼항 연산자를 사용한 간단한 예시입니다.</p>
<pre><code>const x = 10;

const result = x &gt; 5 ? &#39;크다&#39; : &#39;작거나 같다&#39;;
console.log(result); // 출력 : 크다</code></pre><p>위의 코드에서 x &gt; 5는 참이기 때문에 &#39;크다&#39;가 결과로 반환됩니다.</p>
<h3 id="1-2-삼항연산자의-장점">1-2. 삼항연산자의 장점</h3>
<h4 id="1-간결함--if-else-문보다-짧고-읽기-쉽습니다">1. 간결함 : if-else 문보다 짧고 읽기 쉽습니다.</h4>
<p>삼항 연산자는 if-else 문에 비해 훨씬 짧고 간결하게 조건문을 작성할 수 있습니다. 이는 코드 라인을 줄이고, 코드의 전반적인 길이를 감소시켜 줍니다.</p>
<h4 id="2-가독성--간단한-조건문을-사용할-때-가독성이-좋습니다">2. 가독성 : 간단한 조건문을 사용할 때 가독성이 좋습니다.</h4>
<p>삼항 연산자는 짧고 간결한 조건문을 표현할 때 특히 가독성이 좋습니다. 코드가 더 직관적으로 보이며, 한눈에 조건과 그에 따른 결과를 이해할 수 있습니다.</p>
<h4 id="3-jsx에서의-활용--조건부-렌더링을-간결하게-표현할-수-있습니다">3. JSX에서의 활용 : 조건부 렌더링을 간결하게 표현할 수 있습니다.</h4>
<p>리액트(JSX) 같은 라이브러리에서는 삼항 연산자가 조건부 렌더링에 자주 사용됩니다. 이는 UI를 조건에 따라 다르게 표시할 때 매우 편리합니다.</p>
<pre><code>const user = { isLoggedIn: true };

const greeting = (
  &lt;div&gt;
    {user.isLoggedIn ? &lt;h1&gt;Welcome back!&lt;/h1&gt; : &lt;h1&gt;Please sign in.&lt;/h1&gt;}
  &lt;/div&gt;
);
</code></pre><br>
이처럼 삼항 연산자는 간단한 조건문을 작성할 때 유용한 도구입니다. 하지만 너무 복잡한 논리를 삼항 연산자로 표현하면 오히려 가독성이 떨어질 수 있으므로, 상황에 따라 if-else 문을 사용하는 것이 더 나을 때도 있습니다. 적절한 상황에서 삼항 연산자를 사용하면 코드의 가독성과 간결성을 높일 수 있습니다.
<br><br><br>

<h2 id="2-논리연산자">2. 논리연산자</h2>
<p>자바스크립트의 논리 연산자를 활용한 조건문을 간결하게 작성하는 방법입니다. 논리 연산자는 불린(boolean) 값과 함께 사용되며, 주로 조건을 결합하고 표현식을 평가하는 데 사용됩니다. 주로 &amp;&amp; (AND), || (OR), ! (NOT) 연산자가 있습니다.</p>
<h3 id="2-1-and-연산자-">2-1. AND 연산자 (&amp;&amp;)</h3>
<p>&amp;&amp;의 왼쪽 조건이 참일 때 &amp;&amp;의 오른쪽 부분을 반환합니다. 이를 활용하여 조건이 참일 때만 오른쪽에 있는 표현식이 실행되도록 할 수 있습니다.</p>
<pre><code>&amp;&amp; 연산자를 사용하는 조건부 렌더링

import React from &#39;react&#39;;

function Greeting({ isLoggedIn }) {
  return (
    &lt;div&gt;
      {isLoggedIn &amp;&amp; &lt;h1&gt;Welcome back!&lt;/h1&gt;}
      {!isLoggedIn &amp;&amp; &lt;h1&gt;Please sign in.&lt;/h1&gt;}
    &lt;/div&gt;
  );
}

export default Greeting;</code></pre><p>위 예시는 Greeting 컴포넌트에서 isLoggedIn 객체를 받아서, 로그인 여부를 표시합니다. 
isLoggedIn이 true 일 때는 <code>&lt;h1&gt;Welcome back!&lt;/h1&gt;</code>, false 일 때는 <code>&lt;h1&gt;Please sign in.&lt;/h1&gt;</code>이 렌더링 됩니다.</p>
<h3 id="2-2-or-연산자-">2-2. OR 연산자 (||)</h3>
<p>연산자는 왼쪽 조건이 거짓일 때 오른쪽 부분을 반환합니다. 이를 활용하여 기본값을 설정할 수 있습니다.</p>
<pre><code>|| 연산자를 사용하는 기본값 설정

import React from &#39;react&#39;;

function UserProfile({ user }) {
  const displayName = user.name || &quot;Guest&quot;;

  return (
    &lt;div&gt;
      &lt;h1&gt;Welcome, {displayName}!&lt;/h1&gt;
    &lt;/div&gt;
  );
}

export default UserProfile;</code></pre><p>위 예시는 UserProfile 컴포넌트에서 user 객체를 받아서, 해당 유저의 이름을 표시합니다.
만약 user.name이 존재하지 않는다면 즉, || 의 왼쪽 조건이 거짓일 때 &quot;Guest&quot;라는 기본값이 변수 displayName에 할당되어 <code>&lt;h1&gt;Welcome, Guest!&lt;/h1&gt;</code>이 렌더링 됩니다.</p>
<p>이렇게 함으로써, UserProfile 컴포넌트는 유저 객체에 이름이 포함되어 있지 않더라도 오류 없이 렌더링될 수 있습니다. 이는 리액트에서 매우 유용한 패턴 중 하나입니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[화살표 함수]]></title>
            <link>https://velog.io/@2_hyeonju/%ED%99%94%EC%82%B4%ED%91%9C-%ED%95%A8%EC%88%98</link>
            <guid>https://velog.io/@2_hyeonju/%ED%99%94%EC%82%B4%ED%91%9C-%ED%95%A8%EC%88%98</guid>
            <pubDate>Fri, 17 May 2024 10:00:35 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h3 id="20240517-4차-스터디">2024.05.17 4차 스터디</h3>
<p>오늘의 스터디 주제는? 화살표 함수!</p>
</blockquote>
<p>지난 스터디 기록과 이어지는 기록이다. 지난번엔 함수 선언문과 표현식이였다면 이번에는 표현식의 활용버전인 화살표 함수에 대해 알아보고자 한다! 화살표 함수 사용시 주의점이 복잡하고 어려워서 이틀에 걸쳐 작성한 기록이다!
<br><br><br></p>
<h2 id="1-화살표-함수">1. 화살표 함수</h2>
<p>말그대로 화살표를 쓰는 함수라는 뜻이며, 간단한 덧셈을 하는 함수 표현식을 먼저 살펴보자.</p>
<pre><code>함수 표현식

const add = function(a, b) {
    return a + b;
};

console.log(add(2, 3)); // 5</code></pre><p>add라는 변수에 a와 b라는 매개변수를 더한 값을 반환하는 함수를 할당해보았다.
이제 이 함수를 화살표 함수 표현식으로 바꾸면 아래와 같다.</p>
<pre><code>화살표 함수

const add = (a, b) =&gt; {
    return a + b;
};

console.log(add(2, 3)); // 5</code></pre><p>function이 생략 되고 =&gt; 화살표가 생긴 것을 볼 수 있습니다.</p>
<h3 id="1-1-화살표-함수의-정의">1-1. 화살표 함수의 정의</h3>
<p>이처럼 매개변수 부분이 앞으로 오고 function 단어가 화살표=&gt;로 바뀌어 매개변수 뒤에 위치하는 형태의 함수를 화살표 함수라고 부릅니다. <strong>즉, 매개변수, 화살표 =&gt;, 반환하는 값 순서의 형태를 지니게 됩니다.</strong>
<br><br><br></p>
<h2 id="2-짧은-화살표-함수">2. 짧은 화살표 함수</h2>
<p>화살표 함수를 보다 더 짦게 표현한 것을 짧은 화살표 함수라고 합니다. 위에서 만든 화살표 함수를 짧은 화살표 함수로 만들어보자.</p>
<pre><code>화살표 함수

const add = (a, b) =&gt; {
    return a + b;
};

console.log(add(2, 3)); // 5</code></pre><pre><code>짧은 화살표 함수

const add = (a, b) =&gt; a + b;

console.log(add(2, 3)); // 5</code></pre><p>화살표 뒤의 중괄호와 리턴이 생략되어 더 짧아진 모습을 볼 수 있습니다.</p>
<h3 id="2-1-짧은-화살표-함수는-단일-함수">2-1. 짧은 화살표 함수는 단일 함수</h3>
<p>짧은 화살표 함수는 한 가지 목적을 수행하고 그 목적에 집중하는 단일 함수입니다. 즉, 간단하고 한 가지 일만 수행하는 함수에만 짧은 화살표 함수로 형태를 바꿀 수 있습니다.</p>
<pre><code>const double = x =&gt; x * 2;</code></pre><p>이 함수는 매개변수 x를 받아서 그 값을 두 배로 만드는 일만 수행합니다. 따라서 짧은 화살표 함수 형태로 사용이 가능합니다.</p>
<pre><code>const double = (arr) =&gt; {
    // 변수에 함수 할당
    const result = arr.map(x =&gt; x * 2);
    // 결과를 반환
    return result;
};</code></pre><p>위의 함수는 arr배열을 받아서 각 요소를 두 배로 만드는 작업을 하고, 그 결과를 반환하는 일을 수행합니다. 따라서 이 함수는 여러 작업을 포함하고 있기 때문에 짧은 화살표 함수 형태로 생략이 불가능 합니다.</p>
<h3 id="2-2-매개변수에-따른-짧은-화살표-함수">2-2. 매개변수에 따른 짧은 화살표 함수</h3>
<p>짧은 화살표 함수는 매개변수의 갯수에 따라 형태가 바뀌게 됩니다.</p>
<h4 id="1-매개변수가-2개-일-때">1. 매개변수가 2개 일 때</h4>
<pre><code>const add = (a, b) =&gt; a + b;</code></pre><p>중괄호와 return 키워드를 생략할 수 있습니다.</p>
<h4 id="2-매개변수가-1개-일-때">2. 매개변수가 1개 일 때</h4>
<pre><code>const square = x =&gt; x * x;</code></pre><p>매개변수가 하나인 경우 매개변수를 감싸는 괄호를 생략할 수 있습니다.</p>
<h4 id="3-매개변수가-0개-일-때">3. 매개변수가 0개 일 때</h4>
<pre><code>const sayHello = () =&gt; &#39;Hello&#39;;</code></pre><p>매개변수가 없는 경우 빈 괄호를 사용합니다.
<br><br><br></p>
<h2 id="3-화살표-함수-사용-시-주의점">3. 화살표 함수 사용 시 주의점</h2>
<h3 id="3-1-this-바인딩의-동작">3-1. this 바인딩의 동작</h3>
<h4 id="1-일반-함수의-this">1. 일반 함수의 this</h4>
<p>일반 함수는 호출될 때 this가 결정됩니다. 일반 함수 내에서 this는 함수가 어떻게 호출되었는지에 따라 다릅니다. 전역 컨텍스트에서 정의된 함수는 기본적으로 전역 객체(window 또는 global)를 가리킵니다. 또한 함수가 단순히 호출될 때도 전역 객체를 가리킵니다.</p>
<blockquote>
<p><strong>전역 컨텍스트란?</strong>
전역 컨텍스트(Global Context)는 자바스크립트 코드가 실행될 때 가장 바깥쪽에 있는 기본 환경입니다. 즉, 모든 코드가 실행되기 시작하는 기본적인 공간이라고 생각하면 됩니다.</p>
</blockquote>
<p>또한 객체의 메서드로 정의된 함수에서의 this는 그 메서드를 호출한 객체를 가리킵니다.</p>
<pre><code>    객체의 메서드로 정의된 일반 함수    

    const obj = {
      name: &quot;John&quot;,
      greet: function() {
        console.log(&quot;Hello, &quot; + this.name);
          }
    };

    obj.greet(); // 출력: Hello, John</code></pre><p>greet 메서드는 일반 함수입니다. obj.greet()를 호출하면, this는 메서드를 호출한 객체인 obj를 가리킵니다.
따라서 this.name은 &quot;John&quot;을 참조하여 &quot;Hello, John&quot;이 출력됩니다.</p>
<h4 id="2-화살표-함수의-this">2. 화살표 함수의 this</h4>
<p>화살표 함수는 자신만의 this 바인딩이 없습니다. 대신, 화살표 함수가 정의된 위치의 상위 스코프의 this를 상속받아 사용합니다.
쉽게 말해, <strong>화살표 함수 내부의 this는 화살표 함수가 작성된 위치에서 this가 무엇이었는지를 그대로 따라갑니다.</strong></p>
<blockquote>
<p><strong>상위 스코프란?</strong>
상위 스코프는 함수가 정의된 위치를 둘러싸고 있는 외부 환경을 말합니다.
예를 들어, 함수가 객체의 메서드로 정의되었다면, 그 함수의 상위 스코프는 그 객체입니다.</p>
</blockquote>
<p>아래 코드에서 greetArrow는 화살표 함수입니다.</p>
<pre><code>const obj = {
  name: &quot;John&quot;,
  greetArrow: () =&gt; {
    console.log(&quot;Hello, &quot; + this.name); // this는 상위 스코프의 this를 가리킴
  }
};

obj.greetArrow(); // &quot;Hello, undefined&quot;
</code></pre><p>greetArrow 화살표 함수가 obj 객체의 메서드로 정의되어 있지만 obj 객체안에는 this가 정의되어 있지 않습니다. 따라서 this는 상위 스코프의 전역 컨텍스트의 객체(window 또는 global)를 가리키는 것 입니다.
전역 객체에는 name 속성이 없으므로 this.name은 undefined입니다.</p>
<h4 id="3-일반-함수와-화살표-함수의-this">3. 일반 함수와 화살표 함수의 this</h4>
<p>두 함수를 모두 사용하는 코드를 함께 보겠습니다.</p>
<pre><code>const obj = {
  name: &quot;John&quot;,
  greet: function() {
    const innerGreet = () =&gt; {
      console.log(&quot;Hello, &quot; + this.name);
    };
    innerGreet();
  }
};

obj.greet(); // &quot;Hello, John&quot;</code></pre><p>** 상위 스코프 greet함수의 this 정의**</p>
<p>obj.greet()를 호출하면 일반 함수인 greet가 this 를 정의합니다.
obj.greet()를 호출 시, greet 안에 있는 this는 호출을 한 객체인 obj를 가리킵니다.
화살표 함수 innerGreet는 일반 함수 greet 내부에 정의된 화살표 함수입니다. 따라서 innerGreet의 상위 스코프는 greet 함수입니다.
<br></p>
<p>** 화살표 함수가 상위 스코프 greet함수의 this 사용 **</p>
<p>innerGreet가 호출될 때, this는 상위 스코프인 greet의 this를 그대로 사용합니다.
greet의 this는 obj를 가리키므로, innerGreet의 this도 obj를 가리킵니다.
따라서 this.name은 &quot;John&quot;을 가리키며, &quot;Hello, John&quot;이 출력됩니다.</p>
<p>이처럼 화살표 함수는 자신이 정의된 위치의 상위 스코프에서 this를 상속받아 사용합니다. 이로 인해 화살표 함수는 특히 콜백 함수나 비동기 함수 내에서 유용하게 사용됩니다.</p>
<h3 id="3-2-화살표-함수는-생성자-함수로-사용할-수-없다">3-2. 화살표 함수는 생성자 함수로 사용할 수 없다.</h3>
<blockquote>
<p><strong>생성자 함수란 ?</strong>
객체를 생성할 때 사용되는 특수한 함수입니다. new 키워드를 사용하여 호출되며, 새로운 객체를 반환합니다. 또한 생성자 함수는 일반적으로 첫 글자를 대문자로 시작하는 이름을 가집니다.</p>
</blockquote>
<p>즉, 화살표 함수는 new 키워드와 함께 사용할 수 없다는 의미입니다. 화살표 함수는 prototype 속성을 가지지 않으며, 생성자 함수로 사용할 때 필요한 내부 메커니즘을 지원하지 않습니다.</p>
<pre><code>일반 함수
function RegularFunction() {
  this.value = 42;
}

일반 생성자 함수
const instance = new RegularFunction();
console.log(instance.value); // 42</code></pre><pre><code>화살표 함수
const ArrowFunction = () =&gt; {
  this.value = 42;
};

화살표 생성자 함수
const instance2 = new ArrowFunction(); // TypeError: ArrowFunction is not a constructor
</code></pre><p>화살표 함수는 설계 시에 일반 함수에서 제공하는 일부 특수 기능들을 제공하지 않도록 설계되었습니다.
화살표 함수는 주로 콜백 함수나 간단한 함수 표현에 초점을 맞추고 있으며, 클래스나 객체 지향 프로그래밍에서 사용하는 생성자 함수 역할보다는 더 간단하고 짧은 코드 작성을 목적으로 합니다.</p>
<h3 id="3-3-화살표-함수는-arguments-객체를-가지지-않는다">3-3. 화살표 함수는 arguments 객체를 가지지 않는다.</h3>
<blockquote>
<p><strong>arguments 객체란?</strong>
함수 내부에서 사용할 수 있는 특별한 객체입니다. 이 객체는 함수가 호출될 때 전달된 모든 인수를 포함합니다. 배열처럼 생겼지만 실제 배열은 아닙니다. 그러나 인덱스를 사용해 접근할 수 있습니다.</p>
</blockquote>
<h4 id="1-일반-함수의-arguments-객체">1. 일반 함수의 arguments 객체</h4>
<pre><code>function regularFunction(a, b) {
  console.log(arguments);
  console.log(arguments[0]);
  console.log(arguments[1]);
}

regularFunction(1, 2);

// console.log 결과
// arguments &gt; [Arguments] { &#39;0&#39;: 1, &#39;1&#39;: 2 }
// arguments[0] &gt; 1
// arguments[1] &gt; 2</code></pre><p>regularFunction 함수는 arguments 객체를 통해 전달된 인수 1과 2를 참조합니다.</p>
<h4 id="2-화살표-함수의-arguments-객체">2. 화살표 함수의 arguments 객체</h4>
<p>화살표 함수는 arguments 객체를 가지지 않습니다. 대신, 화살표 함수는 화살표 함수가 정의된 위치의 상위 함수의 arguments 객체를 사용합니다.</p>
<pre><code>function outerFunction() {
  return () =&gt; {
    console.log(arguments);
  };
}

const arrowFunction = outerFunction(1, 2, 3);
arrowFunction();

// console.log(arguments) 결과
// [Arguments] { &#39;0&#39;: 1, &#39;1&#39;: 2, &#39;2&#39;: 3 }</code></pre><p>outerFunction 함수는 호출될 때 1, 2, 3을 받습니다.
outerFunction 안에 있는 화살표 함수는 자신의 arguments 객체가 없기 때문에, 상위 함수인 outerFunction 함수의 arguments 객체(호출 시 전달 된 인수 1, 2, 3)를 참조합니다. 
그래서 arrowFunction을 호출하면 outerFunction의 arguments 객체가 출력됩니다.</p>
<h4 id="3-일반-함수와-화살표-함수-비교">3. 일반 함수와 화살표 함수 비교</h4>
<p>일반 함수는 자기 자신만의 arguments 객체를 가집니다.</p>
<pre><code>function regularFunction() {
  const innerFunction = function() {
    console.log(arguments); // innerFunction의 arguments 객체
  };
  innerFunction(4, 5, 6);
}

regularFunction(1, 2, 3);
// 출력: [Arguments] { &#39;0&#39;: 4, &#39;1&#39;: 5, &#39;2&#39;: 6 }</code></pre><p>innerFunction은 호출될 때 전달된 4, 5, 6을 자신의 arguments로 가집니다.</p>
<p>화살표 함수는 자기 자신만의 arguments 객체가 없습니다.</p>
<pre><code>function regularFunction() {
  const innerFunction = () =&gt; {
    console.log(arguments); // regularFunction의 arguments 객체
  };
  innerFunction(4, 5, 6); // 전달된 인수는 무시됨
}

regularFunction(1, 2, 3);
// 출력: [Arguments] { &#39;0&#39;: 1, &#39;1&#39;: 2, &#39;2&#39;: 3 }</code></pre><p>innerFunction은 화살표 함수라서 자신의 arguments가 없기 때문에 화살표 함수가 정의된 상위 함수의 arguments 객체를 사용합니다.
즉, regularFunction의 arguments 객체를 참조하여 regularFunction의 arguments는 1, 2, 3입니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[2024.04.23 20시 3차 멘토링]]></title>
            <link>https://velog.io/@2_hyeonju/2024.04.23-20%EC%8B%9C-3%EC%B0%A8-%EB%A9%98%ED%86%A0%EB%A7%81</link>
            <guid>https://velog.io/@2_hyeonju/2024.04.23-20%EC%8B%9C-3%EC%B0%A8-%EB%A9%98%ED%86%A0%EB%A7%81</guid>
            <pubDate>Tue, 14 May 2024 07:18:02 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h3 id="오즈-코딩에서의-세-번째-멘토링-활동">오즈 코딩에서의 세 번째 멘토링 활동</h3>
<p>JavaScript &amp; Git, Github 학습 진도 파악하기</p>
</blockquote>
<p>자바스크립트와 깃, 깃허브 강의가 끝나는 날이었기 때문에 전체적으로 어려운 점이나 궁금한 점은 없었는지 이야기 나누었다. 큰 문제 없이 학습하며 이해하고 있어서 멘토님이 추가적으로 필요한 정보들을 알려주셨다.
<br><br><br></p>
<blockquote>
<h3 id="api-테스트-도구">API 테스트 도구</h3>
<p>어떤 요청이 있고 어떤 응답인지 확인하기</p>
</blockquote>
<p>자바스크립트의 마지막 과제가 강아지들의 정보들이 있는 DogAPI를 사용하는 과제였다. API에 관해 추가적으로 나중에 사용하면 좋은 도구를 알려주셨다.</p>
<ul>
<li>Postman &gt; 가장 널리 쓰이는 api test 도구</li>
<li>talend api tester &gt; 가볍게 쓸 수 있는 크롬 확장 api test 도구</li>
</ul>
<p>이 두 가지 도구를 추천해 주셨고 Postman 도구를 함께 구경해 보았다.
<br><br><br></p>
<blockquote>
<h3 id="추가적으로-공부할-것">추가적으로 공부할 것</h3>
<p>어떤 부분은 추가로 더 공부하며 좋을까요?</p>
</blockquote>
<p>멘토님께서 비동기 통신 주제 관련해서 추가로 공부하면 좋은 주제를 추천해 주셨다.</p>
<ul>
<li>REST API 기본 개념 (+ HTTP 또는 네트워크 기초)</li>
<li>인증 처리 방법 (쿠키, 세션, JWT 등)</li>
<li>Swagger : API 스펙을 표현하는 도구</li>
</ul>
<p>위에 두 가지는 강의 영상에도 있어서 복습해 보면 될 것 같고 Swagger는 따로 구글링을 통해 알아보아야겠다!
<br><br><br></p>
<blockquote>
<h3 id="간결한-코드">간결한 코드</h3>
<p>자바스크립트 코드를 작성할 때 사용하면 좋은 프로그램</p>
</blockquote>
<p>다른 멘티분이 나도 항상 고민이었던 자바스크립트 코드 작성 시에 나의 코드가 간결한 것인지, 간결하게 작성하는 방법을 물어보셨다. 멘토님은 VScode에 있는 확장 프로그램인 eslint와 prettier를 소개해 주셨다.</p>
<ul>
<li>eslint &gt; 오류를 잡기 위해 주로 사용한다.</li>
<li>prettier &gt; 코드 작성 스타일을 교정하기 위해 사용한다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[함수 선언문과 함수 표현식]]></title>
            <link>https://velog.io/@2_hyeonju/%ED%95%A8%EC%88%98-%EC%84%A0%EC%96%B8%EB%AC%B8%EA%B3%BC-%ED%95%A8%EC%88%98-%ED%91%9C%ED%98%84%EC%8B%9D</link>
            <guid>https://velog.io/@2_hyeonju/%ED%95%A8%EC%88%98-%EC%84%A0%EC%96%B8%EB%AC%B8%EA%B3%BC-%ED%95%A8%EC%88%98-%ED%91%9C%ED%98%84%EC%8B%9D</guid>
            <pubDate>Fri, 10 May 2024 09:58:12 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h3 id="20240510-3차-스터디">2024.05.10 3차 스터디</h3>
<p>오늘의 스터디 주제는? 함수!</p>
</blockquote>
<p>React 라이브러리 강의를 보면서 강사님과 함께 코드를 작성하는데 버벅였다. Javascript에서는 주로 함수 선언문을 쓰고 호출하는 방식이었는데 지금은 표현식에 화살표 함수 표현식까지 사용한다. 일단 이번에는 함수 차이에 대해서라도 먼저 확실하게 알고자 기록한다!!
<br><br></p>
<h2 id="1-함수-선언문">1. 함수 선언문</h2>
<p>두 수를 더하는 간단한 함수 선언문을 만들어보자</p>
<pre><code>function add(a, b) {
    return a+b;
};</code></pre><p>위 선언문이 의미하는 것은 아래와 같다.</p>
<ul>
<li>function &gt; 함수 선언을 의미</li>
<li>add &gt; 함수의 이름</li>
<li>(a, b) &gt; a와 b라는 매개변수</li>
<li>return a+b &gt; a와 b를 더한 결과 반환을 의미</li>
</ul>
<p>만든 함수 선언문에 매개변수를 넣어 콘솔에 찍어보자</p>
<pre><code>console.log(add(2, 5))  //7</code></pre><p><strong>함수 이름을 사용하여 호출</strong>하고 매개변수로 2와 5를 넣어주면 콘솔에는 두 값을 더한 7이 찍힌다.</p>
<h3 id="1-1-함수-선언문의-구성">1-1. 함수 선언문의 구성</h3>
<p>이렇듯 함수 선언문은 아래와 같은 구성으로 이루어져 있다.</p>
<ul>
<li>함수의 <strong>이름</strong></li>
<li>함수의 <strong>매개변수</strong>(괄호로 묶고 쉼표로 구분)</li>
<li>함수를 정의하는 <strong>{ } 중괄호</strong>로 묶기</li>
</ul>
<h3 id="1-2-함수-선언문의-특징">1-2. 함수 선언문의 특징</h3>
<h4 id="1-문법">1. 문법</h4>
<p><strong>function 키워드를 사용</strong>하여 함수를 선언합니다.</p>
<h4 id="2-함수-이름">2. 함수 이름</h4>
<p>함수 선언문은 <strong>반드시 함수 이름</strong>이 있어야 합니다.</p>
<h4 id="3-호출">3. 호출</h4>
<p>선언된 함수는 스크립트에서 <strong>어디서든 호출</strong>할 수 있습니다. 함수 선언 전에도 호출할 수 있습니다.</p>
<h4 id="4-호이스팅">4. 호이스팅</h4>
<p>스크립트의 시작 시점에서 함수가 메모리에 로드되므로, <strong>함수 선언 전에도 호출</strong>할 수 있습니다.</p>
<h4 id="-선언문은-호이스팅-가능">* 선언문은 호이스팅 가능</h4>
<p>선언문에서는 함수 선언과 호출이 따로 이루어지는데 호출이 함수 선언보다 먼저 이루어져도 작동이 된다는 뜻이다.
<br><br></p>
<h2 id="2-함수-표현식">2. 함수 표현식</h2>
<p>함수 선언문은 표현식으로 바꿀 수 있다.
위에서 만든 선언문을 표현식으로 바꾸어보자</p>
<pre><code>선언문

function add(a, b) {
    return a+b;
};</code></pre><pre><code>표현식

const add = function(a, b) {
    return a + b;
};</code></pre><p>어떤 점이 달라졌나요?
<strong>선언문에서 함수에 붙여준 add란 이름이 없어지고 그게 변수명으로 할당</strong>된 것이 보입니다.
이 표현식 함수는 add라는 <strong>변수에 할당된 익명 함수</strong>입니다.</p>
<p>선언문처럼 이 표현식에도 매개변수를 넣어 콘솔에 찍어보자</p>
<pre><code>console.log(add(2, 5))  //7</code></pre><p><strong>변수명을 사용하여 호출</strong>하고 매개변수로 2와 5를 넣어주면 콘솔에는 두 값을 더한 7이 찍힌다.</p>
<h3 id="2-1-함수-표현식의-구성">2-1. 함수 표현식의 구성</h3>
<p>함수 표현식은 선언문과 비슷하지만 다른 구성으로 이루어져 있다.</p>
<ul>
<li>변수 할당(let, const 또는 var)</li>
<li>함수의 매개변수(괄호로 묶고 쉼표로 구분)</li>
<li>함수를 정의하는 {}중괄호로 묶기</li>
</ul>
<h3 id="2-2-함수-표현식의-특징">2-2. 함수 표현식의 특징</h3>
<h4 id="문법">문법</h4>
<p><strong>함수를 변수에 할당</strong>하여 표현합니다.</p>
<h4 id="함수-이름">함수 이름</h4>
<p>함수 표현식은 <strong>이름이 있을 수도 있고 없을 수도</strong> 있습니다.</p>
<h4 id="호출">호출</h4>
<p>함수 표현식은 <strong>할당된 변수를 통해만 호출</strong>할 수 있습니다. <strong>변수가 선언된 후에만 호출</strong>할 수 있습니다.</p>
<h4 id="호이스팅">호이스팅</h4>
<p>변수의 선언은 호이스팅 되지만, 할당된 함수는 호이스팅 되지 않습니다. 따라서 함수 표현식은 <strong>변수가 선언된 후에만 호출</strong>할 수 있습니다.</p>
<h4 id="-표현식은-호이스팅-불가능">* 표현식은 호이스팅 불가능</h4>
<p>표현식에서는 변수에 함수를 할당하는 방식이기 때문에 호출이 함수 선언보다 먼저 이루어질 수 없다.
<br><br></p>
<h2 id="3-선언문-표현식의-차이점-요약">3. 선언문, 표현식의 차이점 요약</h2>
<h4 id="함수-이름-1">함수 이름</h4>
<p>함수 <strong>선언문은 반드시 함수 이름</strong>이 있어야 하지만, 함수 <strong>표현식은 선택적으로 함수 이름</strong>을 가질 수 있습니다.</p>
<h4 id="호출-가능-여부">호출 가능 여부</h4>
<p>함수 <strong>선언문은 어디서든 호출</strong>할 수 있지만, 함수 표<strong>현식은 변수가 선언된 이후에만 호출</strong>할 수 있습니다.</p>
<h4 id="호이스팅-1">호이스팅</h4>
<p>함수 <strong>선언문은 함수 전체가 호이스팅</strong> 되지만, 함수 <strong>표현식은 변수만이 호이스팅</strong> 됩니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[비동기 데이터 통신 HTTP]]></title>
            <link>https://velog.io/@2_hyeonju/%EB%B9%84%EB%8F%99%EA%B8%B0-%EB%8D%B0%EC%9D%B4%ED%84%B0-%ED%86%B5%EC%8B%A0-HTTP</link>
            <guid>https://velog.io/@2_hyeonju/%EB%B9%84%EB%8F%99%EA%B8%B0-%EB%8D%B0%EC%9D%B4%ED%84%B0-%ED%86%B5%EC%8B%A0-HTTP</guid>
            <pubDate>Fri, 26 Apr 2024 09:54:18 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h3 id="20240426-1차-스터디">2024.04.26 1차 스터디</h3>
<p>오늘의 스터디 주제는? 비동기 데이터 통신 HTTP!</p>
</blockquote>
<p>최근에 JavaScript의 학습 기간이 끝나고 Node.js를 학습하게 되었다. 강의를 보며 이전보다 더욱 복잡한 코드를 이해하는데 어려웠다. 강의를 듣고 로그인 웹 서버를 만들어보는 과제를 하는데 주석으로 써진 방법을 보아도 무슨 코드를 작성해야 하는지 감조차 잡을 수 없었다. 그래서 Velog에 서버를 만드는데 필요한 기본 개념을 정리하여 기록하고자 한다!!
<br><br><br></p>
<blockquote>
<h3 id="웹-서버란-">웹 서버란 ?</h3>
</blockquote>
<p>웹 서버는 텍스트, 이미지, 비디오 및 애플리케이션 데이터와 같은 웹 사이트 콘텐츠를 클라이언트 요청을  받고 전달한다. 가장 일반적인 유형의 <strong>클라이언트( 웹 브라우저)가 링크를 클릭하거나 표시된 페이지에서 문서를 다운로드하고자 할 때 HTTP를 통하여 데이터를 요청받고 데이터를 전달하는 웹 브라우저 프로그램</strong>이다.</p>
<p>일상에서 비유하자면 클라이언트가 웹브라우저로 네이버 주소를 요청을 하면 HTTP를 통하여 요청을 받은 뒤 HTML 문서 등의 필요한 데이터를 반환해 주는 프로그램인 것이다.
<br><br><br></p>
<blockquote>
<h3 id="http란-">HTTP란 ?</h3>
</blockquote>
<p><strong><code>HTTP(HyperText Transfer Protocol</code>는 클라이언트(웹 브라우저)와 서버(웹 서버)가 데이터를 주고받을 수 있게 해주는 프로토콜(규약)</strong>이다. HTTP는 웹에서 이루어지는 <strong>모든 데이터 교환의 기초</strong>이며, 이 규악을 암호화하며 보안을 확보한 규약을 HTTPS라 한다.
HTTP에서는 클라이언트가 서버에 요청 메시지를 보내고 이에 대해 서버가 응답 메시지를 반환한다. 이때 서버는 응답 메시지를 반환한 후에 초기 상태로 돌아가는데 이러한 특징으로 인해 HTTP를 <strong>Stateless Protocol</strong>이라고도 한다.
<br><br><br></p>
<blockquote>
<h3 id="stateless-protocol-이란-">Stateless Protocol 이란 ?</h3>
</blockquote>
<p>HTTP는 상태를 저장하지 않는 프로토콜이기 때문에 서버가 여러 요청을 하는 기간 동안 각 사용자에 대한 정보나 상태를 유지할 필요가 없고, 이는 각 요청에 대한 연결을 재설정하는 데 소요되는 시간 / 대역폭을 최소화하기 위한 것이다.
즉, <strong>클라이언트의 요청을 기억해 두지 않는다</strong>는 특징이 있다.</p>
<pre><code>대역폭이란?
데이터 통신(data communication)에서 어떤 대역의 크기를 표시하는 것으로 최고 주파수와 최저 주파수와의 차</code></pre><p><br><br><br></p>
<blockquote>
<h3 id="http-request와-response-란-">HTTP Request와 Response 란 ?</h3>
</blockquote>
<h3 id="1-request요청-구조">1. Request(요청) 구조</h3>
<p>요청 메시지의 구조는 3가지로 나누어져 있다.</p>
<ul>
<li><strong>Starter line(행)</strong>
HTTP 메서드, URL, HTTP 버전 정보 등을 포함한다. 일반적으로 <code>POST/test.html HTTP/1.1</code> 과 같이 표현된다.<br></li>
<li><strong>Headers(헤더)</strong>
Key:Value 값으로 해당 Request에 대한 추가 정보를 담고 있다. <br></li>
<li><strong>Body(본문)</strong>
서버로 전송하는 데이터가 담겨있는 부분이다. 전송하려는 데이터가 없다면 비어있게 된다.<br>

</li>
</ul>
<h3 id="1-2-request요청-method종류">1-2. Request(요청) Method종류</h3>
<p>HTTP Method는 수행할 작업의 종류를 나타내기 위해 서버에 요청(Request)을 보낼 때 사용한다. 이러한 방법을 사용하면 브라우저와 서버 간의 더 풍부한 통신이 가능하다.</p>
<p>메서드의 종류는 총 9가지가 있으며 자주 사용되는 5가지만 간단히 설명하자면 아래와 같다.</p>
<p><code>GET</code> : 어떠한 데이터를 받아올 때
<code>POST</code> : 데이터를 등록할 때
<code>PUT</code> : 데이터를 수정 또는 대체 할 때
<code>PATCH</code> : 데이터를 일부만 변결 할 때
<code>DELETE</code> : 데이터를 서버에서 삭제할 때
<br><br><br></p>
<h3 id="2-response응답-구조">2. Response(응답) 구조</h3>
<p>응답 메세지의 구조 또한 3가지로 나누어져 있다.</p>
<ul>
<li><strong>Starter line(행)</strong>
Status Code(상태 코드), Status Text(상태 메시지), HTTP 버전 정보 등을 포함한다. 일반적으로 <code>HTTP/1.1 200 OK</code>와 같이 표현된다.<br></li>
<li><strong>Headers(헤더)</strong>
Key:Value 값으로 해당 Request에 대한 추가 정보를 담고 있다. Request와 동일하지만 Response에서만 쓰이는 값이 있다.<br></li>
<li><strong>Body(본문)</strong>
클라이언트 측으로 보내는 데이터를 포함한다.<br>

</li>
</ul>
<h3 id="2-2-response응답-status-code-란">2-2. Response(응답) Status Code 란?</h3>
<p>클라이언트(브라우저) 요청의 처리 상태를 응답을 통해 알려주는 기능으로, 100 ~ 500번 대의 3자리 숫자로 만들어져 있다. 주요 상태 코드들을 알아보자.</p>
<ul>
<li><strong>100 ~ 199</strong>
정보 코드(infomational codes)로 요청에 대한 정보 메세지를 반환하는 코드<br></li>
<li><strong>200 ~ 299</strong>
성공 코드(success codes)로 성공적인 요청을 나타내는 코드
<code>200 (OK) : 요청을 성공했습니다.</code><br></li>
<li><strong>300 ~ 399</strong>
리다이렉션 코드(redirection codes)로 추가적인 작업 조치가 필요한 알림인 코드<br></li>
<li><strong>400 ~ 499</strong>
클라이언트 에러 코드(client error codes)로 클라이언트로 인해 발생한 오류를 나타내는 코드
<code>401 (unauthorized) : 인증에 실패했습니다.</code>
<code>403 (Forbidden) : 액세스가 허용되지 않았습니다.</code>
<code>404 (Not Found) : 요청한 리소스를 찾지 못했습니다.</code><br></li>
<li><strong>500 ~ 599</strong>
서버 에러 코드(server error codes)로 서버에서 오류가 발생했음을 나타내는 코드
<code>500 (Internal Server Error) : 서버 내부에 오류가 발생했습니다.</code>
<code>503 (Serrver Unanailable) : 서비스를 일시적으로 사용할 수 없습니다.</code><br>

</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[2024.04.16 20시 2차 멘토링]]></title>
            <link>https://velog.io/@2_hyeonju/2024.04.16-20%EC%8B%9C-2%EC%B0%A8-%EB%A9%98%ED%86%A0%EB%A7%81</link>
            <guid>https://velog.io/@2_hyeonju/2024.04.16-20%EC%8B%9C-2%EC%B0%A8-%EB%A9%98%ED%86%A0%EB%A7%81</guid>
            <pubDate>Fri, 26 Apr 2024 02:09:42 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h3 id="오즈-코딩에서의-두-번째-멘토링-활동">오즈 코딩에서의 두 번째 멘토링 활동</h3>
<p>Java Script의 학습 진도와 난이도 알아보기</p>
</blockquote>
<p>이번 시간에는 Java Script의 학습 진도 확인과 제출한 과제를 함께 보며 이야기 나누었다. 배열 객체까지 학습한 상태였고 과제들도 대부분 기본 개념에 대한 문제를 풀어보는 것들이었다. 학습한 내용들 중에 이해가 잘 안되거나 해결하는 데 어려운 부분이 있는지 이야기 나누었다. 나와 다른 멘티분 모두 별다른 문제 없이 학습을 진행하면서 이해하고 있었다. 😎
<br><br><br></p>
<blockquote>
<h3 id="git--github-사용-방법">Git &amp; Github 사용 방법</h3>
<p>어떻게 사용하면 더 좋을까? 간단하게 알아보기</p>
</blockquote>
<p>학습 진도에서 별다른 어려움이 없었기에 멘토님이 현재 과제를 제출하고 있는 Git과 Github에 대해 알려주셨다. 🤔 브랜치를 새로 생성한 뒤 적당한 단위로 구분해서 과제를 업로드하면 코드 연습이나 코드 리뷰를 더 편하게 할 수 있다고 알려주셨다. 그리고 풀 리퀘스트라는 기능을 사용하는 연습을 해보는 것도 추천해 주셨다. 🤗</p>
<p>브랜치 생성의 예시 ) 기본 틀을 갖고 있는 &lt;A 브랜치&gt;가 있는 위치에서 &lt;B 브랜치&gt;를 하나 더 만든다. 그럼 A 브랜치의 기본 틀이 그대로 복사가 되어 B 브랜치도 기본 틀을 가지게 된다. 쉽게 말해 멀티버스처럼 다른 공간에 같은 파일을 갖고 있는 상태가 되는 것이다. A 나 B의 브랜치를 수정해도 서로에게 영향이 없다.</p>
]]></description>
        </item>
    </channel>
</rss>