<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>jaylion_2009.log</title>
        <link>https://velog.io/</link>
        <description>React js 개발자</description>
        <lastBuildDate>Tue, 28 Mar 2023 16:26:43 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>jaylion_2009.log</title>
            <url>https://images.velog.io/images/jaylion_2009/profile/82f9df8c-d51b-4cf1-8e0b-80f543b708f2/social.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. jaylion_2009.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/jaylion_2009" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[React Native 시작하기 ]]></title>
            <link>https://velog.io/@jaylion_2009/React-Native-%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@jaylion_2009/React-Native-%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0</guid>
            <pubDate>Tue, 28 Mar 2023 16:26:43 GMT</pubDate>
            <description><![CDATA[<h1 id="react-native">React Native</h1>
<p>IOS 와 Android app 을 하나의 코드로 앱을 만들어 낼 수 있다(생산성 up). </p>
<p>리엑트 네이티브 앱은 js 로 개발하는데 native 코드 변환 하여 동작 가능하다.
이는 Bridge 라는 녀석 덕분이다.</p>
<p><strong>Bridge</strong> 는 js 코드를 이용해 네이티브 계층과 통신하는 역할을 한다.
그리고 자바스크립트 스레드 정보를 받아 네이트브에 전달한다.
현재, cpu 집중적인 작업은 아직 Flutter(Bridge가 존재 x) 성능이 좋다고 한다.
하지만, Meta(구 Facebook) 은 향후 bridge 를 제거하고 js interface라는것을 구현하여 성능 향상을 위한 작업을 하고 있다고 한다. Meta 홧팅.!</p>
<h3 id="--react-native앱이-실행되는-과정">- React Native앱이 실행되는 과정</h3>
<ol>
<li>앱이 시작하면서 Main Thread(UI Thread)가 실행되며 JavaScript 번들을 로드한다.</li>
<li>JavaScript번들의 로드가 완료되면 Main Thread(UI Thread)는 JavaScript코드들을 JavaScript Thread로 보낸다.<ul>
<li>JavaScript Thread가 무거운 작업을 하더라도 Main Thread(UI Thread)가 문제를 일으키지 않기 때문이다.</li>
</ul>
</li>
<li>React가 렌더링을 시작하면서 조정자(Reconciler)는 diffing을 시작한다. 새로운 virtual DOM(layout)을 생성하고 변경점들을 다른 thread(Shadow Thread)로 전달한다.</li>
<li>Shadow Thread가 레이아웃의 계산을 끝마치면 레이아웃의 parameter혹은 object를 Main Thread(UI Thread)에 보낸다.</li>
<li>Main Thread는 shadow thread가 보낸 레이아웃을 화면에 렌더링한다.</li>
</ol>
<h3 id="-rn-프로젝트-개발엔-두가지-방법이-있다고-한다">-RN 프로젝트 개발엔 두가지 방법이 있다고 한다.</h3>
<ol>
<li><p>EXPO CLI
 *장점 : 환경설정부터 배포까지 간단하고 EXPO GO 어플로 기기 상관 없이 프로젝트 실행 가능</p>
<p> *단점 : 기본 제공되는 API만을 사용해야됨.(IOS OR Android 추가 기능 업데이트시 expo 에 업데이트 될때 까지 기다려야함.)</p>
</li>
<li><p>REACT NATIVE CLI
 *장점 : 고도화 된기능 개발. 다양한 라이브러리 사용 가능, 네이티브 모듈 연결 가능.</p>
</li>
</ol>
<h4 id="단점--라이브러리-적음-android와-xcode-를-설치해야됨-_ipone-개발엔-mac-이-필수_"><em>단점 : 라이브러리 적음, Android와 xcode 를 설치해야됨!.. _*</em>ipone 개발엔 mac 이 필수....**_</h4>
<p>in app 결제 같은 라이브러리는 RN CLI로 해야한다.
그러나, Eject 작업을 하게되면 expo로 개발된것을 RN CLI 전환 가능.!</p>
<p>이후, node js, VSC 가 설치가 완료가 되었다면, vsc terminal 에서 npx create-expo-app 프로젝트명 으로 만들고, 프로젝트 실행시 npx expo start 명령어를 실행하면 qr 코드가 터미널에 뜨게 되는데, 이 qr 코드는 Expo go 어플이 깔린 스마트폰에 인식하여 결과 화면을 볼수있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Carrot market 정복 노트 [8] - "Authentication page"]]></title>
            <link>https://velog.io/@jaylion_2009/Carrot-market-%EC%A0%95%EB%B3%B5-%EB%85%B8%ED%8A%B8-8-Authentication-page</link>
            <guid>https://velog.io/@jaylion_2009/Carrot-market-%EC%A0%95%EB%B3%B5-%EB%85%B8%ED%8A%B8-8-Authentication-page</guid>
            <pubDate>Wed, 16 Mar 2022 14:21:21 GMT</pubDate>
            <description><![CDATA[<p>이 페이지는 유저 Authentication 에 관한 모든 기능들을 구현 하는 내용이며, 로그인의 경우 일회용 비밀번호를 제공하여 로그인 하는 방식이 될것이다. 그래서, <strong>token 활용, Twillo, Sendgrid, 등</strong>에 대한 내용이 포함되어 있다.</p>
<hr>
<h2 id="1-accounts-logic">1. Accounts logic</h2>
<p>phoe number 또는 email adress로 계정을 만드는 logic 을 구현.</p>
<p>pages/api/users/enter.tsx</p>
<pre><code>import { NextApiRequest, NextApiResponse } from &quot;next&quot;;
import withHandler from &quot;@libs/server/withHandler&quot;;
import client from &quot;@libs/server/client&quot;;

async function handler(req: NextApiRequest, res: NextApiResponse) {
  const { phone, email } = req.body; // user 가 입렵후 submit 한 phone 또는 email 정보를 req.body 로 가져옴.
  const inputInfo = phone ? { phone: +phone } : { email };  //es6 삼항 연산 !!
  const user = await client.user.upsert({  
    where: {
      ...inputInfo,
    },
    create: {
      name: &quot;Anonymous&quot;,        // name is required in user table so, init as Anonymous
      ...inputInfo,
    },
    update: {},
  });
  console.log(user);

  return res.status(200).end();
}

export default withHandler(&quot;POST&quot;, handler);</code></pre><h3 id="-point">※ Point</h3>
<ul>
<li>일반적으로는 원하는 data 하나당 findUnique 쿼리로찾고 없다면 create 쿼리로 만들어주는 코드를 짜게되어 긴 코드가 되지만, 여기서 사용된 upsert 쿼리는 MySQL 에서 UNIQUE KEY 값이 중복 되는 값이 없다면 Updata를 , 중복이 된다면 Insert를 할수 있게 해주는 쿼리 인데, 이를 이용해 코드를 간결 하게 할수 있다. 사용 할때에는 3가지 옵션(create, where, update)을 반드시 명시 해주어야 한다. 또한, const payload = phone ? { phone: +phone } : { email }; 이처럼 삼항 연산으로 원하는 다중의 값을 조건적으로 넣어주어 사용할수 있다(코드 간결화를 위한).</li>
</ul>
<hr>
<h2 id="2-token-logic">2. Token Logic</h2>
<p>prisma/schema.prisma</p>
<pre><code>model User {
    // ...
  tokens    Token[]      // 여러개[]의 Token 을 User 에 담음.
}

model Token {
  id        Int      @id @default(autoincrement())
  payload   String   @unique
  user      User     @relation(fields: [userId], references: [id])
  // User 테이블에 Token 테이블에 관계성을 Token의 userId를 필드로 와 User의 id를 참조로 지정.  
  userId    Int
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
}</code></pre><p>pages/api/users/enter.tsx </p>
<pre><code>async function handler(req: NextApiRequest, res: NextApiResponse) {
  const { phone, email } = req.body;
  const user = phone ? { phone: +phone } : { email };
  const payload = Math.floor(100000 + Math.random() * 900000) + &quot;&quot;;
  // payload 는 User 모델 내에서 unique한 값이어야 되어, 6자리의 random 숫자를 생성하고 + &quot;&quot; 의 의미는 string 으로 변환.
  const token = await client.token.create({
    data: {
      payload,
      user: {
        connectOrCreate: { // command 키를 누르고 data 를 클릭해보면, user 필드를 꼭 넣어주야 한다는 것을 알수 있는데 한층 더 가보게되면 3가지 옵션이 나오는데 그중, connectOrCreate 사용 하여 modle 들을 연결하고 생성 하겠다는 의미
          where: {
            ...user,
          },
          create: {
            name: &quot;Anonymous&quot;,
            ...user,
          },
        },
      },
    },

  });

  console.log(token);</code></pre><h3 id="-point-1">※ Point</h3>
<ul>
<li><p>connectOrCreate를 사용 하면 새로운 token을 이미 존재하는 유저와 연결해주고, 유저가 없다면, 새로 user 계정 아이디와 token을 만들어 준다, 그리고 where 과 create 을 무조건 써주어야한다.</p>
</li>
<li><p>connectOrCreate 으로 token 연결 뿐만 아니라, user 계정 아이디 또한 찾고, 없으면 새로 생성 된다 (token과 함께).</p>
</li>
</ul>
<hr>
<h2 id="3-req-res-를-boolean-type화-시키기">3. req, res 를 boolean type화 시키기</h2>
<p>libs/server/withHandler.ts</p>
<pre><code>export interface ResponseType {
  ok: boolean;
  [key: string]: any;
}
</code></pre><p>pages/api/users/enter.tsx </p>
<pre><code>import withHandler, { ResponseType } from &quot;@libs/server/withHandler&quot;;

async function handler(
  req: NextApiRequest,
  res: NextApiResponse&lt;ResponseType&gt;
) {
const { phone, email } = req.body;
  const user = phone ? { phone: +phone } : email ? { email } : null;
  if (!user) return res.status(400).json({ ok: false });
  // 만약 phone 이나 email 정보 없이 user 가 form 을 제출 하게되면 400 에러 코드를 보낸다.
return res.json({
    ok: true,
  });
}</code></pre><h3 id="-point-2">※ Point</h3>
<p> pages/api/users/enter.tsx 에서, return res.status(200).end(); 이처럼 http 상태코드 리턴 대신 req, res 변수에 boolean type으로 지정해 위의 코드처럼 사용 가능하다.</p>
<hr>
<h2 id="4-twilio">4. Twilio</h2>
<p>이 프로젝트에서는, 로그인을 유저가 입력한 Phone 또는 email로 일회용 비밀번호 제공과 함께 로그인 하는 logic이 들어가게 되는데, Twilio 는 SMS를 보내줄수 있게 제공하게 해준다. 이외에도 WebRTC , 영상 전화 등 많은것을 할수 있다(유료).</p>
<h3 id="setup">Setup</h3>
<ol>
<li>twilio 계정을 만들고 Account SID 과 AUTH TOKEN을 .env 파일에 넣어 준다.</li>
<li>Messaging - Service 탭에서 sender pool 단계에서 try it out - get setup 페이지로 이동후 setup 버튼을 누르면 미국 phone # 가 제공 되어진다.</li>
<li>Trial account 인지라 보내는 번호는 바꿀수 없다.. trial fee는 1$ per month.</li>
<li>Messaging - Service 를 다시 들어가 MSG_SID 또한 .env 파일에 저장 한다.</li>
</ol>
<h3 id="4-1-twilio-sms-메세지-보내는-방법">4-1 Twilio SMS 메세지 보내는 방법</h3>
<p>pages/api/users/enter.tsx </p>
<pre><code>import twilio from &quot;twilio&quot;;

const twilioClient = twilio(process.env.TWILIO_SID, process.env.TWILIO_TOKEN);
// .env 파일에있는 Twilio SID 와 TOKEN 을 받아옴.
 if (phone) {
    const message = await twilioClient.messages.create({
      messagingServiceSid: process.env.TWILIO_MSID,
      to: process.env.MY_PHONE!, 
      body: `Your login token is ${payload}.`,
    });
    console.log(message);
  }</code></pre><h3 id="4-2-twilio-email-보내는-방법">4-2 Twilio email 보내는 방법</h3>
<p>이번엔 Sendgrid (by Twilio)를 통해 SMS이 아닌 사용자 Email 주소로 일회용 비밀번호인 token 을 보내는 방법의 내용이다.</p>
<h3 id="setup-1">Setup</h3>
<ol>
<li>회원가입 이후 API KEY 를 만들어 .env 파일에 넣는다</li>
</ol>
<p> pages/api/users/enter.tsx </p>
<pre><code>import mail from &quot;@sendgrid/mail&quot;;

mail.setApiKey(process.env.SENDGRID_KEY!);

 else if (email) {
    const email = await mail.send({
      from: &quot;Email 주소 입력&quot;,
      to: &quot;Email 주소 입력&quot;,
      subject: &quot;Your Carrot Market Verification Email&quot;,
      text: `Your token is ${payload}`,
      html: `&lt;strong&gt;Your token is ${payload}&lt;/strong&gt;`, //만약을 위해 html 도 같이 보내준다.
    });
    console.log(email);
  }
</code></pre><h3 id="-point-3">※ Point</h3>
<blockquote>
<p>SMS 든 email 이든 현재 trial 목적으로 하기에 to: 부분을 나의 폰번호, 이메일 주소로 해놓았지만, 나중에는 유저 정보를 받아 처리할것이다. 꼭 주의 해야될 점은, 이 부분을 잘 핸들링 하여  fee 비용을 꼭 최소화 시켜야 한다 money.... TEST 도중엔 주석 처리를 해놓자.</p>
</blockquote>
<hr>
<h2 id="5-token-ui">5 Token UI</h2>
<h3 id="5-1-typescript-제네릭generic-사용하기">5-1 TypeScript 제네릭(Generic) 사용하기.</h3>
<p>우선, TypeScript에서 Generic이란, 재사용 가능한 컴포넌트를 생성할때 사용되며, 다양한 타입에 동작하는 컴포넌트를 작성할수 있다. 여기서 T는 제내릭을 선언할때 관용적으로 사용되는 식별자로 Type parameter 라고 한다. </p>
<p>libs/client/useMutation.tsx</p>
<pre><code>import { useState } from &quot;react&quot;;

interface UseMutationState&lt;T&gt; {
  loading: boolean;
  data?: T;                  // 이전엔 data의 type 은 object이였다.
  error?: object;
}
type UseMutationResult&lt;T&gt; = [(data: any) =&gt; void, UseMutationState&lt;T&gt;];
// 이 코드 또한 &lt;T&gt;를 선언해준다

export default function useMutation&lt;T = any&gt;(      //첫번째, &lt;T&gt; 선언 해주고 UseMutationState으로 보냄.
  url: string
): UseMutationResult&lt;T&gt; // 두번째, &lt;T&gt;를 다시 UseMutationResult 넣어주고 {
  const [state, setSate] = useState&lt;UseMutationState&lt;T&gt;&gt;({
    loading: false,
    data: undefined,
    error: undefined,
  });
</code></pre><h3 id="-point-5-1">※ Point 5-1</h3>
<p>이 프로젝트에서 UseMutationState에 선언되어 있는 data의 타입을 object로 지정 했었으나, pages/enter.tsx 파일 내에서는 data에 대한 데이터 존재 여부를 boolean type으로도 사용 하고 싶기에 Generic 을 사용하게 된 것이다. 그리고, data가 쓰이는 모든 객체들 또한 T를 선언해 주어야 한다.</p>
<hr>
<h3 id="5-2-token-입력-페이지-만들기">5-2 Token 입력 페이지 만들기.</h3>
<p>Token 이 email 또는 phone#로 전송이 되었다면, UI에서는 one time password 인 token 을 입력후 로그인 하는 화면이 있어야 한다. </p>
<p> pages/enter.tsx</p>
<pre><code>interface TokenForm {  // token을 위한 type 선언
  token: string;
}

interface MutationResult {
  ok: boolean;
}

const Enter: NextPage = () =&gt; {

 const [enter, { loading, data, error }] =
    useMutation&lt;MutationResult&gt;(&quot;/api/users/enter&quot;);

  const [confirmToken, { loading: tokenLoading, data: tokenData }] =
    useMutation&lt;MutationResult&gt;(&quot;/api/users/confirm&quot;);
    // token을 위한 새로운 mutation hook. 이름을 바꿀쑤 있는것은 useMutation 이 배열을 리턴하기 때문이다.
  const { register, handleSubmit, reset } = useForm&lt;EnterForm&gt;();

  const { register: tokenRegister, handleSubmit: tokenHandleSubmit } =
    useForm&lt;TokenForm&gt;();
    // register과 handleSubmit은 이미 사용 되어 지고 있는 이름이기 때문에 새로운 type을 적용해 다른 변수로 취급하게 한다.

  const onTokenValid = (validForm: TokenForm) =&gt; {
    if (tokenLoading) return;
    confirmToken(validForm);
  };

return (
  {data?.ok ? (           // 이 부분에서 .ok? 부분을 boolean 으로 하고 싶기 때문에 boolean type 으로 선언되어 있는 MutationResult객체 타입을 useMutation 에 적용 한 것이다.
          &lt;form
            onSubmit={tokenHandleSubmit(onTokenValid)}
            className=&quot;flex flex-col mt-8 space-y-4&quot;
          &gt;
           &lt;Input
              register={tokenRegister(&quot;token&quot;, {
                required: true,
              })}
               name=&quot;token&quot;
              label=&quot;Confirmation Token&quot;
              type=&quot;number&quot;
              required
            /&gt;
             &lt;Button text={tokenLoading ? &quot;Loading&quot; : &quot;Confirm Token&quot;} /&gt;
          &lt;/form&gt;
        ) : (
        &lt;&gt;
        // 기존 enter 페이지의 UI
        &lt;/&gt;

       )</code></pre><h3 id="-point-5-2">※ Point 5-2</h3>
<p>Token을 위한useMutation hook 의 재사용과, 만약 변수명이 같은것을 또 같은 변수 명으로 쓰고싶다면, type명을 다르게 명시하여 다른 변수로 취급되어 질수있다.</p>
<hr>
<h3 id="token-확인-만들기">Token 확인 만들기</h3>
<p>pages/api/users/confirm.tsx</p>
<pre><code>import { NextApiRequest, NextApiResponse } from &quot;next&quot;;
import withHandler, { ResponseType } from &quot;@libs/server/withHandler&quot;;
import client from &quot;@libs/server/client&quot;;

async function handler(
  req: NextApiRequest,
  res: NextApiResponse&lt;ResponseType&gt;
) {
  const { token } = req.body; //req.body 에서 token 을 받아 온다.
  console.log(token);
  res.status(200).end(); // 전송 ok
}

export default withHandler(&quot;POST&quot;, handler);</code></pre><h2 id="6-serverless-sessions">6. Serverless Sessions</h2>
<p>이번엔, iron session을 이용하여 user를 인증을 할것이다. serverless가 성립 될수 있는 이유는, 유저가 로그인할때 쓰이는 token이 암호화 되어 쿠키로 저장고 불러와지는 방식으로 이루어 지기 때문이다. 동작 되어지는 과정은, payload(토큰 번호)를 암호화 -&gt; 암호화 된 payload를 쿠키로 user에게 전송 -&gt; 받은 쿠키의 암호화 푼다 -&gt; 현재 페이지에 접근하는 user의 id에 맞게 접근성을 부여해주는 방식으로 이루어 진다. </p>
<blockquote>
<p><strong>잠깐!</strong> 
 <strong>JWT(Json Web Token)</strong>는 유저의 id를 가진 객체에 서명하고 이서명과 함께 유저에게 토큰을 보내는 것인데 JWT는 토큰안에 있는 정보 확인이 가능 하여 보안에 조금 취약할수 있다. 또한, 세션을 위한 백엔드 구축도 해야한다. 하지만 iron session을 사용 하면 이 단점들을 보안 하여 인증 로직을 구현할 수 있다.
설치 커맨드는 다음과 같다. npm i iron-session </p>
</blockquote>
<ul>
<li>** Iron Session사용 방법**</li>
</ul>
<ol>
<li>사용 하고싶은 함수를 iron session helper 함수로 감싸준다.</li>
<li>iron session에 암호화 해줄 비밀번호를 설정해준다 (아주 길고 복잡한 암호로).</li>
<li>이전에 받았던 Token을 session에 저장해준다.</li>
<li>cookie에 암호화가 되어있는 token 이 유저 정보와 같다면, 로그인이 완료.</li>
</ol>
<h3 id="token-데이터-session에-담기-post">Token 데이터 session에 담기 (POST)</h3>
<p>pages/api/users/confirm.tsx</p>
<pre><code>import { withIronSessionApiRoute } from &quot;iron-session/next&quot;;

declare module &quot;iron-session&quot; {       // 아래 res.session.user 에서 모듈의 정의를 찾을수 없다는 error가 생긴다. 
이런 경우엔, declare를 선언 하여 해당 변수가 존재 한다는것을 알려주어야 한다.
  interface IronSessionData {
    user?: {
      id: number;
    };
  }
}

async function handler(
  req: NextApiRequest,
  res: NextApiResponse&lt;ResponseType&gt;
) {
  const { token } = req.body;

const exists = await client.token.findUnique({
    where: {
      payload: token,       // token 모듈에 payload 필드 값을 저장 한다.
    },
  });         
  if (!exists) return res.status(404).end(); // Token 이 없다면 404 에러 코드
  req.session.user = {     // 토큰이 존재한다면, userId 를 세션에 id를 넣어 생성.
    id: exists?.userId,
  };
  await req.session.save(); // 암호화가 된 세션을 저장

  await client.token.deleteMany({  // 토큰의 userId를 찾아 deleteMany로 
    where: {
      userId: exists.userId,
    },
  });
  res.json({ ok: true });
}

export default withIronSessionApiRoute(withHandler(&quot;POST&quot;, handler), {
  cookieName: &quot;carrotsession&quot;,
  password:
    &quot;복잡한 구조의 아무런 번호 넣는곳&quot;,
}); </code></pre><h3 id="token-데이터cookie-정보-불러오기-get">Token 데이터(cookie) 정보 불러오기 (GET)</h3>
<p>pages/api/users/me.tsx</p>
<pre><code>import { withIronSessionApiRoute } from &quot;iron-session/next&quot;;
import { NextApiRequest, NextApiResponse } from &quot;next&quot;;
import withHandler, { ResponseType } from &quot;@libs/server/withHandler&quot;;
import client from &quot;@libs/server/client&quot;;

declare module &quot;iron-session&quot; {
  interface IronSessionData {
    user?: {
      id: number;
    };
  }
}

async function handler(
  req: NextApiRequest,
  res: NextApiResponse&lt;ResponseType&gt;
) {
  console.log(req.session.user);
  const profile = await client.user.findUnique({
    where: { id: req.session.user?.id },
  });
  res.json({
    ok: true,
    profile,
  });
}

export default withIronSessionApiRoute(withHandler(&quot;GET&quot;, handler), {
  cookieName: &quot;carrotsession&quot;,
  password:
    &quot;9845904809485098594385093840598df;slkgjfdl;gkfsdjg;ldfksjgdsflgjdfklgjdflgjflkgjdgd&quot;,
});</code></pre><h3 id="-point-4">※ Point</h3>
<ul>
<li><p>withHandler 함수를 iron session인 withIronSessionApiRoute로 감싸주면, res.session 에 접근할수 있게된다. 이 프로젝트에서 모든 api들은 같은 서버에서 실행되는 것이 아닌, 특정 API 의 URL으로 개별적으로 실행 된다. 그래서 iron session 을 api 마다 감싸 주어야 한다.</p>
</li>
<li><p>iron session 설정을 위와 같이 꼭 추가해 주어야 한다. 그리고, withHandler에 promise 를 void또는 any로 return 해주어야 된다.</p>
</li>
<li><p>추가적으로, withIronSessionApiRoute함수를 따로 컴포넌트화 시켜 놓아 가독성을 높일수 있다.</p>
</li>
</ul>
<h2 id="이외-알아-두어야-할것들">이외 알아 두어야 할것들</h2>
<h3 id="prisma-관련-알아-두어야-할것들">Prisma 관련 알아 두어야 할것들</h3>
<ul>
<li>token과 같이 관계성이 있는 model 들을 삭제해야 될때에는 아래의 코드 처럼 Cascade 를 넣어주면 된다. 이는 parent record 가 삭제되면 child record 도 삭제 시킨다는 뜻 이다. Setnull 을 사용한다면 token은 있지만 null 로 바꿔주는 역할을 한다.</li>
</ul>
<p>prisma/schema.prisma</p>
<pre><code>model Token {
 user      User     @relation(fields: [userId], references: [id], onDelete: Cascade)
 }</code></pre><ul>
<li><p>phone # 의 경우 국가 번호도 같이 입력되기에 User model 타입을 BigInt 또는 String 으로 처리 해주어야 된다.</p>
</li>
<li><p>npx prisma db push : 스키마 업데이트시 커맨드</p>
</li>
<li><p>NextAuth : Next에서는 유저 인증을 위한 기능 또한 제공 된다. 하지만, customize 하기엔 좀 제한적 일수 있다.</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Carrot market 정복 노트 [7] - "Refactoring"]]></title>
            <link>https://velog.io/@jaylion_2009/Carrot-market-%EC%A0%95%EB%B3%B5-%EB%85%B8%ED%8A%B8-7-Refactoring</link>
            <guid>https://velog.io/@jaylion_2009/Carrot-market-%EC%A0%95%EB%B3%B5-%EB%85%B8%ED%8A%B8-7-Refactoring</guid>
            <pubDate>Tue, 15 Mar 2022 09:13:31 GMT</pubDate>
            <description><![CDATA[<p>이 페이지에서는, Refactoring 에 관한 내용이다. <strong>react hook form 를 프로젝트에 적용,  api 만들기, custom-hook 만들기, helper funtion 만들기 등</strong> 꼭 알아야 될것들이 담겨져 있다. 그러므로, 많은 연습 또한 필요할 것이다.</p>
<hr>
<h2 id="1-input-컴포넌트로-registerreact-hook-form객체-보내는-방법">1. Input 컴포넌트로 register(react hook form)객체 보내는 방법.</h2>
<p>기존 코드들을 확인해 보면 type선언을 할때에 [key: string]: any; 로 모든 props 를 받아 올수 있게 해둔 것이 있다. 이것을 통해 그냥 사용할수 있지만, 이 페이지에서는 새로 변수를 할당하여 구현 할것이다.
이를 통하여 이전 페이지에서 다뤘던 React hook form을 이전에 만든 Input 컴포넌트로 보내고 Input 컴포넌트 내에서 register 같은 함수를 props으로 사용 할수 있게 된다. 
추가 방법은 아래의 코드와 같다.</p>
<p>components/input.tsx</p>
<pre><code>import type { UseFormRegisterReturn } from &quot;react-hook-form&quot;;
// register 의 리턴 타입을 살펴보면 UseFormRegisterReturn 이라는것을 알수있다. 앞에 type 을 입력하는것을 잊지 말자.
interface InputProps {
  label: string;
  name: string;
  kind?: &quot;text&quot; | &quot;phone&quot; | &quot;price&quot;;
  type: string;   // type 에 대한 type 선언이 없어 추가.
  register: UseFormRegisterReturn;      // 다음과 같이 register를 위한 타입 선언.
  required: boolean;
}
export default function Input({
  label,
  name,
  kind = &quot;text&quot;,
  register, // register 추가.
  type,
  required,
}: InputProps) {
  return (


&lt;input
       id={name}
       required={required}
       {...register} // 다음과 같이 register 함수를 prop 으로 사용하기 위해 불러온다.
       type={type}
/&gt;
    );

  );
}</code></pre><h3 id="-point">※ Point</h3>
<p>Typescript 개발환경 이라면, register 의 리턴 type은 UseFormRegisterReturn 이며, input 태그 안에 {...register} 로 불러 올수 있고 이는 곧 prop 으로써 사용 가능하다.</p>
<hr>
<h2 id="2-registerreact-hook-form객체를-prop-으로-사용-하는-방법">2. register(react hook form)객체를 prop 으로 사용 하는 방법</h2>
<p>pages/enter.tsx</p>
<pre><code>import { useForm } from &quot;react-hook-form&quot;;

interface EnterForm {
  email?: string;
  phone?: string;
} 

const Enter: NextPage = () =&gt; {
  const { register, handleSubmit, reset } = useForm&lt;EnterForm&gt;();

 const onEmailClick = () =&gt; {
    reset(); //email 버튼 클릭시 폼을 reset 시켜주기 위한 react-hook-form 함수
    setMethod(&quot;email&quot;);
  };

 const onValid = (data: EnterForm) =&gt; {
  console.log(data);
  };

   return (
       &lt;&gt;
         &lt;form
          onSubmit={handleSubmit(onValid)}
          className=&quot;flex flex-col mt-8 space-y-4&quot;
        &gt;
             {method === &quot;email&quot; ? (
             &lt;Input
                register={register(&quot;email&quot;, {
                  required: true,
                })} 
                // 이와 같은 방식으로 register을 prop으로 받아 사용할수 있게되는 것.
                name=&quot;email&quot;
                label=&quot;Email address&quot;
                type=&quot;email&quot;
                required
                /&gt;
              ) : null}

        &lt;/form&gt;
    &lt;/&gt;

      )
  }
</code></pre><h3 id="-point-1">※ Point</h3>
<p>Input 컴포넌트 태그 내에서 register={register(&quot;email&quot;, {required: true,})} 이러한 형태로 register을 prop으로 활용 할수 있으며, register prop 안에서의 register의 사용방법은 동일하다.</p>
<hr>
<h2 id="3-api-post-요청-방법">3. API post 요청 방법</h2>
<p>pages/enter.tsx</p>
<pre><code>const [loading, setLoading] = useState(false); // api 요청 기간동안에 loading 상태를 보여주기 위한.

 const onValid = (data: EnterForm) =&gt; {
    setLoading(true);  // loading 상태 시작
    fetch(&quot;/api/users/enter&quot;, { // API 관련 폴더를 생성해 관리하자.
      method: &quot;POST&quot;,
      body: JSON.stringify(data), // data를 json 의 string 형태로 body에 넣음. 
      headers: {
        &quot;Content-Type&quot;: &quot;application/json&quot;, 
      }, // headers를 설정하여 api 요청후 r, 
    }).then(() =&gt; {        // fetch 가 끝나면~
      setLoading(false); // loading 상태 끝
    });
  };
   return (
   &lt;&gt;
    &lt;Button text={loading ? &quot;Loading&quot; : &quot;Get one-time password&quot;} /&gt;
   &lt;/&gt;
   )
 }</code></pre><p>pages/api/users/enter.tsx</p>
<pre><code>import { NextApiRequest, NextApiResponse } from &quot;next&quot;;
import client from &quot;../../../libs/client&quot;;

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  if (req.method !== &quot;POST&quot;) { // 만약 POST 요청이 아닌 다른것이 왔을때의 조건문
    res.status(401).end();     //Unauthorized
  }
  console.log(req.body.email);
  res.status(200).end();         // OK
}</code></pre><h3 id="-point-2">※ Point</h3>
<ul>
<li><p>먼저, 주의 해야될점은 req.body가 아닌 req.body.(이곳)의 데이터를 보려 할경우 데이터가 나오지 않을것이다. 그 이유는, req의 내용이 인코딩 기준으로 parse 되기 때문이다. 이것을 해결하려면, 우선 클라이언트에서 Json 형태의 데이터를 보낸다는 것을 알려주어야 하기에, Json request는 &quot;Content-Type&quot;: &quot;application/json&quot;으로 설정해주게 되면, 이 문제점이 해결된다. 자세한 내용은 <a href="https://developer.mozilla.org/ko/docs/Web/HTTP/Headers/Content-Type">Headers/Content-Type</a> 를 통해 확인할수 있다.</p>
</li>
<li><p>API 호출 fetch(&quot;url&quot;, {method: ,body: ,headers:{}})</p>
</li>
</ul>
<blockquote>
<p>HTTP 상태 코드 Doc -&gt; <a href="https://developer.mozilla.org/ko/docs/Web/HTTP/Status/401">HTTP 상태 코드 링크</a></p>
</blockquote>
<hr>
<h2 id="4-custom-hook-for-api만들기">4. Custom hook (for API)만들기</h2>
<p>libs/client/useMutation.tsx     </p>
<pre><code>import { useState } from &quot;react&quot;;

interface UseMutationState {
  loading: boolean;
  data?: object;
  error?: object;
}
type UseMutationResult = [(data: any) =&gt; void, UseMutationState];
// type 선언을 코드정리 차원에서 따로 해두었는데, useMutation 의 return type은 첫번째는 함수 이기에 void 그리고 두번째는 {loading, data, error} 이다.

export default function useMutation(url: string): UseMutationResult {
   const [state, setState] = useState&lt;UseMutationState&gt;({
    loading: false,  //초기값 false
    data: undefined, // 초기값 undefined
    error: undefined,
  });
  //  useState를 통합하여 쓰는 방법.

  function mutation(data: any) {}  // 어떠한 인자든지 받을수 있게 any type으로 지정해준다.
   setState((prev) =&gt; ({ ...prev, loading: true }));
    fetch(url, {
      method: &quot;POST&quot;,
      headers: {
        &quot;Content-Type&quot;: &quot;application/json&quot;,
      },
      body: JSON.stringify(data), // 여기서 data 는 enter 페이지에서 유저가 입력한 email 또는 phone 정보가 들어가게 된다.
    })
      .then((response) =&gt; response.json().catch(() =&gt; {})) 
      // response.json() 을 하게되면 promise를 받게 되므로 catch를 적용 catch에 빈 화살표 함수는 json 이 없어도 error 가 보이지 않게 하기 위해.
      .then((data) =&gt; setState((prev) =&gt; ({ ...prev, data })))
      //  setState 중에 data 담기
      .catch((error) =&gt; setState((prev) =&gt; ({ ...prev, error })))
      // setState 중에 error 에 대한 catch
      .finally(() =&gt; setState((prev) =&gt; ({ ...prev, loading: false })));
      // 다 마치게 되면, 로딩의 state를 false 로 변경.
  }        
  return [mutation, { ...state }];
  // mutation 함수 그리고 {loading, data, error} 를 리턴 시켜준다.
}</code></pre><h3 id="-point---1">※ Point - 1</h3>
<ul>
<li>여기서, useMutation의 return type은 (함수, (객체))로 할것.</li>
<li>type UseMutationResult = [(data: any) =&gt; void, UseMutationState]; 이 처럼 custome hook 의 리턴 type을 만들어 주어야 한다.</li>
<li>mutation 함수의 매개변수(data)는 enter page에서 mutation 함수가 호출 되어질때, 인자 값을 받아 이것을 JSON.stringify(data) 형태로 body에 넣어 준다.</li>
<li>만약 useState의 선언이 많아질 경우에는 아래와 같은 코드로 활용 가능하다.<pre><code>const [state, setState] = useState&lt;type&gt;({ A : false,  B string, C undefined,}); // 초기값 A: false , B: string, C:undefined
</code></pre></li>
</ul>
<p>setState((prev) =&gt; ({ ...prev, A: true }));
// setState 하는 방법.</p>
<pre><code>- 비동기 함수는 항상 promise를 리턴하기 때문에 response.json(), fetch()와 같은 비동기 함수에 catch를 적용 해주어야 된다. fetch() 의 두번째 인자는 초기값을 위해 사용 되며 메소드 요청시에 사용 된다.


****

pages/enter.tsx </code></pre><p>import useMutation from &quot;../libs/client/useMutation&quot;;</p>
<p>interface EnterForm {
  email?: string;
  phone?: string;
}</p>
<p> const [enter, { loading, data, error }] = useMutation(&quot;/api/users/enter&quot;);
 // 첫번째는 enter 함수로 fetch를 실행하는 mutation 함수를 의미 하고. 두번째로는 loading,data,error가 담긴 객체. useMutation 에서는 custom-hook이 있는 API 주소 url 을 입력 해주어야 된다.</p>
<p> const onValid = (validForm: EnterForm) =&gt; {
 if (loading) return; // API post 요청 시간이 걸리기 때문에 화면단에서는 loading 상태로 변경시켜줌
    enter(validForm); // 기존에 있었던 fetch 함수는 useMutation custom-hook에 넣어 두었고,enter은 fatch 실행을 의미 하며, 인자 자리에 위치한 validForm은 EnterForm 객체에 있는 것들을 의미 한다(email or phone).
};</p>
<p>&lt;Button text={loading ? &quot;Loading&quot; : &quot;Get one-time password&quot;} /&gt;</p>
<pre><code>### ※ Point - 2
- custom-hook인 useMutation 을 사용 할때에는 정해둔 리턴 타입에 맞춰 사용해야 하며, useMutation 인자에는 custom-hook이 존재 하는 API 주소 url 을 적어 주어야한다.
- enter 은 useMutation hook 안에 있는 mutation 이라는 함수를 call 한것인데 이때 인자 값에는 유저가 입력한 email 또는 phone 정보가 들어가게 된다.


****
## 5. Helper function(for API handling) 만들기
**여기서 Helper function의 정확한 명칭은 Higher-order function 이다.
쉽게 말해, 한개의 function 의 return 을 다른 function 으로 함수를 겹으로 리턴 하므로써, 특정 function 을 customizing 할수 있는 것이다.**

### Helper function 사용 방법.
pages/api/users/enter.tsx</code></pre><p>import { NextApiRequest, NextApiResponse } from &quot;next&quot;;
import client from &quot;../../../libs/server/client&quot;;
import withHandler from &quot;../../../libs/server/withHandler&quot;;</p>
<p>async function handler(req: NextApiRequest, res: NextApiResponse) {
  console.log(req.body);
 return res.status(200).end();
}</p>
<p>export default withHandler(&quot;POST&quot;, handler);<br>// withHandler function에서 POST method 와 handler 함수를 export.
또한 POST 로 요청 될때에만 동작되게 할것 이라는 의미.</p>
<pre><code>

libs/server/withHandler.ts</code></pre><p>import { NextApiRequest, NextApiResponse } from &quot;next&quot;;</p>
<p>export default function withHandler(
  method: &quot;GET&quot; | &quot;POST&quot; | &quot;DELETE&quot;,
  fn: (req: NextApiRequest, res: NextApiResponse) =&gt; void 
  // fn 은 인자로 req 와 res 를 void 리턴하는 함수 이며, pages/api/users/enter.tsx 에 있는 handler 함수로 대체 되어진다.
) {
  return async function (req: NextApiRequest, res: NextApiResponse) {
    if (req.method !== method) {<br>      return res.status(405).end(); 
      // req.method 가 없다면 http 405 코드(bad request)를 실행한다
    }
    try {
      await fn(req, res);  // fn 함수는 위쪽의 조건문이 통과 되면 실행되며, 에러가 있다면 catch 를 통해 에러를 return 해준다. 
    } catch (error) {
      console.log(error);
      return res.status(500).json({ error }); //server error(500) 코드를 실행
    }
  };
}</p>
<pre><code>### ※ Point
- 여기서의 key point는 withHandler 함수 안에 있는 fn 함수가 pages/api/users/enter.tsx 에 있는 handler 함수로 대체 되어지며, 이 fn 함수의 실제 역할인 res.status(200)을 return 정상 실행과 오류 등의 조건적 실행을 위해 withHandler 함수의 return에 조건들을 넣을수 있다.  
그리하여, pages/api/users/enter.tsx파일 내에 있는 res.status(200)을 return 하는 handler 함수를 조금은 더 많은 경우의 환경에서 사용할수 있게 되는 것이다. 

- 동작 부분을 살펴 보면, enter page UI 에서 사용자가 email 또는 phone 을 입력 제출 하게 되면, useMutation 함수 안에 있는 fetch 함수를 통해 api 호출과 POST 방식으로 변경되며 export default withHandler(&quot;POST&quot;, handler);  코드가 성립이되어,  백엔드에서 의 console.log(req.body); 출력 확인이 가능할수 있게 된다.



&gt; **잠깐!**
NextJS에서 어떤 함수든 return 값이 항상 있어야 한다. 하지만, withHaandler 와 같은 파일에 return 값이 없이 잘 작동되는 이유는  res.json()이나 res.status(200).end() 와 같은 것들은 자체에서 return이 void 가 포함되어 있기 때문이다. 하지만 IDE의 도움을 받고 싶다면, return을 붙이는것이 좋다. 

****
## 이외 알아두어야 할것들
- NextJS는 function에 export default 입력을 해주지 않으면 이 function 은 url 로 적용이 되지 않아 호출 되지 않는다. 

- typescript 에서 확장명 ts 와 tsx 의 차이점은 크게 jsx 문법이 있다면, tsx 확장명 으로 해주어야 jsx 문법 지원이 된다. 또한 파일의 구분 짓기 위해 사용 되어지기도 한다. 

- import 할때 많은 층의 구조를 가진 파일들의 경로가 조금은 복잡하게 보이기도 한다. ex)import client from &quot;../../../libs/server/client&quot;; 이처럼, 이런 경로를 간단히 해줄수 있게 하는 방법은 아래의 코드와 같이 tsconfig.json 파일에 추가해 주면 된다. 

tsconfig.json</code></pre><pre><code>&quot;baseUrl&quot;: &quot;.&quot;,  // baseUrl의 &quot;.&quot;은 가장 바깥쪽을 의미한다.
&quot;paths&quot;: {
  &quot;@libs/*&quot;: [&quot;libs/*&quot;],  // @libs/* 는libs 파일 안쪽에 어떠한 path 들을 포함하겠다는 의미 
  &quot;@components/*&quot;: [&quot;components/*&quot;]
}</code></pre><pre><code>이렇게 해주게되면 &quot;@libs/server/client&quot; 로 import가 가능해진다. 많은 폴더 구조를 가지게 된다면 유용하게 사용될 것이다.
</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[Carrot market 정복 노트 [6] - "React Hook Form"]]></title>
            <link>https://velog.io/@jaylion_2009/Carrot-market-%EC%A0%95%EB%B3%B5-%EB%85%B8%ED%8A%B8-6-React-Hook-Form</link>
            <guid>https://velog.io/@jaylion_2009/Carrot-market-%EC%A0%95%EB%B3%B5-%EB%85%B8%ED%8A%B8-6-React-Hook-Form</guid>
            <pubDate>Tue, 15 Mar 2022 07:01:42 GMT</pubDate>
            <description><![CDATA[<h1 id="react-hook-form-">React Hook Form ?</h1>
<p>React Hook Form 은 Input validation, form submit, error 등을 쉽게 핸들링 할수 있게 해주는 package 이다. 또한, input validation 같은경우에 inspctor 페이지에서 validation form을 user가 고칠수 있게되는 경우가 있는데 이것 또한 react hook form 을통하여 자바스크립트에서 제한 가능하다.</p>
<hr>
<h2 id="react-hook-form-setup">React Hook Form Setup</h2>
<p><strong>yarn add react-hook-form  또는  npm i react-hook-form --legacy-peer-deps</strong> 으로 설치해준다. legacy-peer-deps 는 리액트 18 rc 버전을 사용하고 있기에 peeDependencies가 자동으로 설치되는 기능을 무시 해주어 react 18 rc 버전과 react Hook form 을 사용할수 있게되는 것이다.</p>
<h2 id="react-hook-form-사용법">React Hook Form 사용법</h2>
<p>먼저 React hook form 을 import 한뒤 register 함수를 console.log로 출력 해보면, name,onBlur,onChange,ref 가 담긴 obj 로 나온다.</p>
<pre><code>import { useForm } from &quot;react-hook-form&quot;;
import { FieldErrors, useForm } from &quot;react-hook-form&quot;;

interface LoginForm {
  username: string;
  password: string;
  email: string;
} //login form 에 들어갈 타입 선언

export default function Forms() {
  const { register, handleSubmit, formState: {errors} } = useForm&lt;LoginForm&gt;();
  // formState 객체 안에서 errors를 사용하였으며 이를 통해 errors에 담겨있는 메세지들을 웹 화면에 출력가능하다.
  const onValid = (data: LoginForm) =&gt; {
    console.log(&quot;im valid&quot;);
  };

  const onInvalid = (errors: FieldErrors) =&gt; {
  //FieldErrors는 typescript에서 error을 위해 제공 되는 객체 이다.
    console.log(errors);
  };


  return (
    &lt;form onSubmit={handleSubmit(onValid, onInvalid)}&gt;
      &lt;input
        {...register(&quot;username&quot;, {
          required: &quot;Username is required&quot;,
          minLength: {
              message: &quot;The username should be longer than 5 chars.&quot;,
              value: 5,
            //User name is required라는 validation 메시지 이외로 만약 minLength가 5보다 작은경우 minLenght 안에 설정해둔 message의 데이터가 담긴다.
        })}
        type=&quot;text&quot;
        placeholder=&quot;Username&quot;
        required
          /&gt;
      &lt;input 
        {...register(&quot;email&quot;, { required: &quot;Email is required&quot; })}
        type=&quot;email&quot;
        placeholder=&quot;Email&quot;
      /&gt;
      {errors.emial?.message}
      // formState에서 받은 errors객체에서 만약 validationRule 에 의해 email 검출되어 있다면, email 내에 설정된 message를 출력.
      &lt;input
        {...register(&quot;password&quot;, { required: &quot;Password is required&quot; })}
        type=&quot;password&quot;
        placeholder=&quot;Password&quot;
        required
      /&gt;
      &lt;input type=&quot;submit&quot; value=&quot;Create Account&quot; /&gt;
    &lt;/form&gt;
  );
}
</code></pre><h3 id="-point-register">※ Point (register)</h3>
<ul>
<li><p>useForm() 에서 register 함수를  {...register(&quot;username&quot;),{validationRules} 이처럼 사용 하기만 하므로써, useState와 events 들에 관한 코드가 생략된다. 여기서 ...(spread operator)는 register 안에있는 모든 porps를 불러오겠다는 의미.</p>
</li>
<li><p>register의 두번째 인자는 브라우저가아닌 자바스크립트 내에서의 validation rule을 추가해 validation 규칙을 설정 할수 있게된다.</p>
</li>
<li><p>validation 을 커스텀 추가 하는 방법 또한 있다. 그 방법은, ex) register 객체 안에서 validate: { notGmail: (value) =&gt; !value.includes(&quot;@gmail.com&quot;) || &quot;Gmail is not allowed&quot;} 이처럼 사용하면 할수 있다. </p>
</li>
</ul>
<h3 id="-point-handlesumit">※ Point (handleSumit)</h3>
<ul>
<li>handleSubmit 이라는 함수에는 두개의 함수가 arg으로 포함되어 있는데, onValid(유효할때) 라는 함수와 onInValid(비유효할때) 라는 함수를 받을수 있다. </li>
<li>From 태그 내에 onSubmit={handleSubmit(onValid, Invalid)} 를 넣어주기만 해도 event.preventDefault 기능이 자동 적용이 되며, 유효와 비유효 시에 대한 처리를 할수 있다.</li>
</ul>
<h3 id="-point-formstate">※ Point (formState)</h3>
<ul>
<li>formState 객체에 많은 것들이 존재하는데 그중 errors 를 예를들어 사용 하게 된다면, error 메세지가 담긴것을 화면에 출력해줄수 있다.</li>
</ul>
<hr>
<blockquote>
<p><strong>중요 Tip</strong> </p>
</blockquote>
<pre><code>const { register, handleSubmit, formState: {errors} } = useForm&lt;LoginForm&gt;(mode: &quot;onChange&quot;);</code></pre><p>위의 코드 처럼 mode 를 추가하고 &quot;onChange&quot; 옵션을 주게 되면 유저가 input 폼 입력과 동시에validation 여부 error 메세지를 바로 유저가 확인 할수 있게 되며, submit을 하지 않고도 유저는 validation 규칙을 확인할수 있게 된다.</p>
<h2 id="이외-알아두면-좋을것들">이외 알아두면 좋을것들</h2>
<ul>
<li><p>watch() :  또한 유용하게 사용 되며, 이것은 하나의 field 나 모든 field의 변화에 대해 확인 하고싶을때 쓰일수 있다.</p>
</li>
<li><p>setValue(&quot;바꾸고 싶은 이름&quot;, &quot;바뀌게 될 이름&quot;) : input에 초기 값을 넣어줄수 있다. </p>
</li>
<li><p>setError(&quot;전역지역의 error 타입&quot;, {message: &quot;There is no connection with database&quot;}) :  이 예시 코드 처럼 api 호출시 error 를 설정 할수 있다. 화면단에 error 메세지를 표기 하고 싶다면 {errors.errors?.message} 와 같이 출력시킬수 있다.</p>
</li>
<li><p>reset(), resetField() : submit 이후 form 들의 초기화 시키고 싶을때 사용된다.</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Carrot market 정복 노트 [5] - "Prisma database setup"]]></title>
            <link>https://velog.io/@jaylion_2009/Carrot-market-%EC%A0%95%EB%B3%B5-%EB%85%B8%ED%8A%B8-5-Prisma-database-setup</link>
            <guid>https://velog.io/@jaylion_2009/Carrot-market-%EC%A0%95%EB%B3%B5-%EB%85%B8%ED%8A%B8-5-Prisma-database-setup</guid>
            <pubDate>Mon, 14 Mar 2022 09:05:05 GMT</pubDate>
            <description><![CDATA[<h2 id="1-prisma-setup">1. Prisma setup</h2>
<h3 id="prisma">Prisma?</h3>
<p>Prisma는 node.js와 ttypescript ORM(Object Relational Mapping) 이다. 하는 역할은 쉽게 이야기해 js 또는 typescript 와 데이터 베이스 사이에 연결해주는 역할을 한다. 또한, prisma studio(database admin pannel) 라는 DB GUI가 제공되며 client 또한 제공 해준다. Prisma는 PostGreSQL, MySQL, SQL server, SQlite 그리고 MongoDB에 적용용 가능하다. carrot market 강의에서는 MySql 을 사용할 것이다 그 이유는 PlanetScale(MySQL과 호환되는) 데이터 베이스(플랫폼)를 이용 할것이기 때문이다.</p>
<h3 id="prisma-setup">Prisma setup</h3>
<ul>
<li><p>Prisma extention (in VSC) : 설치하게되면 syntax highligt, formatting, 자동 완성 기능 등을 지원해준다.</p>
</li>
<li><p>npm i prisma -D : prisma 설치 커맨드.</p>
</li>
<li><p>npx prisma init : 우선 prisma 를 사용할때에는 npx 를 사용하게되며, 이 커맨드는 prisma file 과 .env 파일이 생기며 terminal 창에 내가 해야될것들을 알려준다. 지나치지말고 읽어보고 설정해야 한다!!!. 여기서 적용해 주어야 될것들은, </p>
</li>
</ul>
<ol>
<li>.env 파일에서 DATABASE_URL=&quot;POSTGRESQL: ....&quot; 이라고 되어있는데 planetscale data base url로 바꾸어 주어야 된다.</li>
<li>schema.prisma 파일에서 provider를 mysql로 설정한다.</li>
</ol>
<blockquote>
<h3 id="잠깐">잠깐!!!</h3>
<p>가끔 아주 중요한것을 잊어버리고 넘어가는 경우가 있다, .gitignore 파일에 .env 파일을 꼭 설정하는것을 꼭 잊지 말아야된다!!!</p>
</blockquote>
<h3 id="prisma-몇가지-기본-사용법">Prisma 몇가지 기본 사용법.</h3>
<p>Prisma 공식 개발 문서 링크 -&gt; </p>
<ul>
<li><strong>@unique :</strong> 어떤 필드를 고유값으로 지정</li>
<li><strong>@id @default(autoincrement()) :</strong> 주로 id 필드를 만들때 사용됨(Primary key) autoincrement는 순차적으로 증가하며 필드의 아이디 값이 지정됨.</li>
<li><strong>@default(now()) :</strong> 현재 시간을 적용함 (createdAt으로 주로 사용) </li>
<li><strong>@updatedAt :</strong> 필드값이 업데이트 되었다면 알려주는 것</li>
</ul>
<hr>
<h2 id="2-planetscale-setup">2. PlanetScale setup</h2>
<h3 id="planetscale">PlanetScale?</h3>
<p>PlanetScale은  MySQL과 호환되는 serverless 데이터베이스 플랫폼이다.
여기서 serverless의 의미는 서버 관리는 이곳 플랫폼 사에서 해주는것이기 때문이다. 이것은 AWS 또는 RDS와는 다르게 서버를 만들고, 크기설정 등 모든것을 대신 해준다. PlanetScale에는 Vitess(MySQL compatible database)가 사용되어 지는데 이것은 Youtube와 같은 곳에 사용되는지는 오픈소스 기술이다. 이것은 데이터 베이스의 scaling, online SChema migrations 등을 쉽게 할수있게 하는 기능들을 제공한다. 이것이 planetScale 적용되어 있어 데이터 베이스 구축을 더 손쉽게 할수 있다. CLI(Command Line Interface) 또한 지원한다.</p>
<h3 id="planetscale-setup-window">PlanetScale setup (Window)</h3>
<ol>
<li><p>우선, mysql 호환을 위한 scoop 설치를 해야한다 -&gt; <a href="https://scoop.sh/">scoop 설치 링크</a> 이후 powershell을 실행하여 진행해야 하지만 VSC 터미널에서 진행 가능하다 </p>
</li>
<li><p>iex (new-object net.webclient).downloadstring(&#39;<a href="https://get.scoop.sh&#39;">https://get.scoop.sh&#39;</a>)</p>
</li>
<li><p>Set-ExecutionPolicy RemoteSigned -scope CurrentUser</p>
</li>
<li><p>yes 이후 엔터</p>
</li>
<li><p>scoop install curl</p>
</li>
<li><p>scoop bucket add pscale <a href="https://github.com/planetscale/scoop-bucket.git">https://github.com/planetscale/scoop-bucket.git</a></p>
</li>
<li><p>scoop install pscale mysql</p>
</li>
<li><p>scoop update pscale</p>
</li>
<li><p>pscale : 이 커맨드를 통해 PlanetScale설치가 정상적으로 되어있나 확인과 커맨드 리스트 를 볼수 있음. </p>
</li>
<li><p>pscale auth login : 브라우저가 뜨면서 confirmation 버튼 클릭후 컴펌. (나중에 서버 연결이 안될시 로그인을 다시 해주어야됨.)</p>
</li>
<li><p>pscale region list : 지역 check. 한국의 경우 japan으로 지정 그리고 Slug 컬럼에서 ap-northeast (Tokyo) 를 기억해 놓자.</p>
</li>
<li><p>pscale database create carrot-market --region ap-northeast  : 데이터베이스 생성 하는 커맨드</p>
</li>
<li><p>pscale connect carrot-market : pscale을 연결 하는 커맨드. 그리고,  .env 에 mysql://127.0.0.1:3306/carrot-market  을 넣어주어야한다. 이 커맨드를 통해 많은 부분을 생략하고 서버와 연결이 가능하다!.</p>
</li>
<li><p>npx prisma db push : 데이터베이스에 새로운 필드나 모델이 수정,추가한 뒤 push 하는 커맨드 </p>
</li>
<li><p>npx prisma studio : 데이터베이스 브라우저 접속 (데이터베이스 admin pannel)</p>
</li>
<li><p>npm i @prisma/client : (***** 안되면 yarn add 로 하기!) 이곳에서 -D(development dependency) 옵션을 사용하지 않은 이유는, 백엔드에서 client를 직접 사용하기 때문이다. client 를 설치하는 커맨드</p>
</li>
<li><p>npx prisma generate : PrismaClient를 생성하는 커맨드 이것은 prismaClient를
node_module/@prisma/client 이 생성되며, client를 백엔드에서 핸들링이 가능하게 된다.</p>
</li>
</ol>
<blockquote>
<p><strong>잠깐!</strong> previewFeatures = [&quot;referentialIntegrity&quot;]  를 schema.prisma 파일내 generator clint 안에 추가 해주어야 된다 이걸 추가해주는 이유는 어떠한 객체(model)가 다른 객체(model)에 연결될때 그 객체(model)가 존재여부 확인을 위해 지정해 주는것이며 referentialIntegrity = &quot;prisma&quot; 는 datasource db 안에 추가해주어 이작업은 prisma 가 하게 할것을 설정한다고 설정 해주어야 된다. 이를 통해 prisma가 referentialintegrity 확인이 가능하게 되어, forign key 에대한 작업을 제약 없이 가능해진다.</p>
</blockquote>
<blockquote>
<p><strong>잠깐!!!</strong> PrismaClient는 브라우저에서 실행 할수 있으면 안된다.(데이터 보안!)</p>
</blockquote>
<h2 id="3-nextjs-api-만들기중요">3. NextJS API 만들기(!!!중요!!!)</h2>
<blockquote>
<p><strong>Awesome NextJS(API route) :</strong> 가장 기본적으로, NextJS 프레임 워크에서는 폴더명이 즉 url로 적용된다. 우선, NextJS 멋진 기능이 너무 많지만 특히나 API 만드는 방법 또한 너무 너무 편리하다...  NextJS를 사용하기 이전에는, 서로 다른 서버에서, frontend는 리액트로, 백엔드는 nodeJS로 만들었었다고 한다. 그러나 NextJS Framework는 API까지도 만들수 있는 기능이 탑재 되어있다. 방법은 간단하다. </p>
</blockquote>
<ul>
<li>pages 폴더 안에 api라는 이름으로 폴더를 만들면된다. 이름은 반드시 api 이여야 된다. API 라우트를 위한 규칙이 있는데 그 규칙은 export default function handler() 을 해주면 된다.  </li>
<li>이 handler()함수 안에서는 NextApiRequest (안에는 query cookie body env,preview, previewData 가 들어있다) 와 NextApiResponse (안에는 send,json,status,redirect 등이 들어있다)  사용하여 client를 백엔드에서 핸들링이 가능해 진다.  </li>
<li>create 과 같은 함수로 데이터 처리를 해야될 경우에는 비동기(async)로 처리해야 한다. </li>
<li>api 데이터 같은경우엔 .json() 함수를 사용하여 json 형태로 전달 할수있다. </li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Carrot market 정복 노트 [4] - "컴포넌트 화 시키기"]]></title>
            <link>https://velog.io/@jaylion_2009/Carrot-market-%EC%A0%95%EB%B3%B5-%EB%85%B8%ED%8A%B8-3-Code-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@jaylion_2009/Carrot-market-%EC%A0%95%EB%B3%B5-%EB%85%B8%ED%8A%B8-3-Code-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Mon, 14 Mar 2022 05:16:03 GMT</pubDate>
            <description><![CDATA[<p>이 페이지에서는 각 웹 페이지에 자주 쓰이는 요소들을 어떤 식으로 컴포넌트화 하는지, 어떠한 팁들이 있는지 적어낼 것이다.</p>
<p>먼저, 버튼의 경우 많은 페이지에 쓰이기 때문에 여러 형태의 버튼들을 컴포넌트화 시켜야 한다.</p>
<h3 id="1-submit-버튼-컴포넌트화-시키기">1. submit 버튼 컴포넌트화 시키기</h3>
<p>components/button.tsx</p>
<pre><code>import { cls } from &quot;../libs/utils&quot;;

interface ButtonProps {
  large?: boolean;
  // 조금은 작은 버튼을 위해 크기 옵션을 위해 선언.
  text: string;
  //버튼내 text 를 위한것.
  [key: string]: any;
  // 어떠한 타입의 형태든지 prop을 보낼수 있게 선언한 이유는 나중에 placeholder , required 등을 설정하게 될경우를 위한것.
}

export default function Button({
  large = false,
  onClick,
  text,
  ...rest
  // ...rest는 지금은 일시적으로 적어둔 이후에 사용 되어질 변수에 대한 선언.
}: ButtonProps) {
  return (
    &lt;button
      {...rest}
      className={cls(
        &quot;w-full bg-orange-500 hover:bg-orange-600 text-white  px-4 border border-transparent rounded-md shadow-sm font-medium focus:ring-2 focus:ring-offset-2 focus:ring-orange-500 focus:outline-none&quot;,
        large ? &quot;py-3 text-base&quot; : &quot;py-2 text-sm &quot;
      )}
    &gt;
      {text}
    &lt;/button&gt;
  );
}</code></pre><blockquote>
<p><strong>잠깐!</strong> :  typescript type 설정중 any라고 설정해 둔 것의 의미는, 어떠한 타입의 형태든지 prop을 보낼수 있게 해주기 위한것. 예를 들어 placeholder , required 등을 설정하게 될경우를 위한것.
적용 방법은 ...(Spread Operator)를 사용하여 이용.</p>
</blockquote>
<ul>
<li><strong>point :</strong> 크기를 정해주는 large 옵션을 주어 스타일이 조금 다르게 해야한다면 이렇게 옵션으로 주면 된다.</li>
</ul>
<h3 id="2-floatingbutton-컴포넌트화-시키기">2. FloatingButton 컴포넌트화 시키기</h3>
<p>components/floating-button.tsx</p>
<pre><code>import Link from &quot;next/link&quot;;
import React from &quot;react&quot;;

interface FloatingButton {
  children: React.ReactNode;
  // 각 페이지 내용에 맞는 아이콘을 넣어주기 위해 react 컴포넌트를 타입스크립트로 선언하였다.
  href: string;
  //href 타입 선언은 페이지 이동의 url 설정을 위한 것.
}

export default function FloatingButton({ children, href }: FloatingButton) {
  return (
    &lt;Link href={href}&gt;
      &lt;a className=&quot;fixed hover:bg-orange-500 border-0 aspect-square border-transparent transition-colors cursor-pointer  bottom-24 right-5 shadow-xl bg-orange-400 rounded-full w-14 flex items-center justify-center text-white&quot;&gt;
        {children}
      &lt;/a&gt;
    &lt;/Link&gt;
  );
}</code></pre><ul>
<li><strong>point :</strong> children에는 각 페이지 내용에 맞는 아이콘이 들어가기 위해 선언된 type.</li>
</ul>
<h3 id="3-input-컴포넌트화-시키기">3. input 컴포넌트화 시키기</h3>
<p>components/input.tsx</p>
<pre><code>interface InputProps {
  label: string;
  name: string;
  kind?: &quot;text&quot; | &quot;phone&quot; | &quot;price&quot;;
  // 기본 text 입력폼, phone# 입력폼, price 입력폼을 위한 옵션
  [key: string]: any;
}

export default function Input({
  label,
  name,
  kind = &quot;text&quot;,
  ...rest 
}: InputProps) {
  return (
    &lt;div&gt;
      &lt;label
        className=&quot;mb-1 block text-sm font-medium text-gray-700&quot;
        htmlFor={name}
      &gt;
        {label}
      &lt;/label&gt;
      {kind === &quot;text&quot; ? (
        &lt;div className=&quot;rounded-md relative flex items-center shadow-sm&quot;&gt;
          &lt;input
            id={name}
            {...rest}
            className=&quot;appearance-none w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-orange-500 focus:border-orange-500&quot;
          /&gt;
        &lt;/div&gt;
      ) : null}
      {kind === &quot;price&quot; ? (
        &lt;div className=&quot;rounded-md relative flex  items-center shadow-sm&quot;&gt;
          &lt;div className=&quot;absolute left-0 pointer-events-none pl-3 flex items-center justify-center&quot;&gt;
            &lt;span className=&quot;text-gray-500 text-sm&quot;&gt;$&lt;/span&gt;
          &lt;/div&gt;
          &lt;input
            id={name}
            {...rest}
            className=&quot;appearance-none pl-7 w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-orange-500 focus:border-orange-500&quot;
          /&gt;
          &lt;div className=&quot;absolute right-0 pointer-events-none pr-3 flex items-center&quot;&gt;
            &lt;span className=&quot;text-gray-500&quot;&gt;KRW&lt;/span&gt;
          &lt;/div&gt;
        &lt;/div&gt;
      ) : null}
      {kind === &quot;phone&quot; ? (
        &lt;div className=&quot;flex rounded-md shadow-sm&quot;&gt;
          &lt;span className=&quot;flex items-center justify-center px-3 rounded-l-md border border-r-0 border-gray-300 bg-gray-50 text-gray-500 select-none text-sm&quot;&gt;
            +82
          &lt;/span&gt;
          &lt;input
            id={name}
            {...rest}
            className=&quot;appearance-none w-full px-3 py-2 border border-gray-300 rounded-md rounded-l-none shadow-sm placeholder-gray-400 focus:outline-none focus:ring-orange-500 focus:border-orange-500&quot;
          /&gt;
        &lt;/div&gt;
      ) : null}
    &lt;/div&gt;
  );
}</code></pre><ul>
<li><strong>point :</strong> type에 따라 원하는 input 스타일을 지정할수 있다.</li>
</ul>
<h3 id="4-item-컴포넌트화-시키기">4. item 컴포넌트화 시키기.</h3>
<pre><code>import Link from &quot;next/link&quot;;

interface ItemProps {
  title: string;
  id: number;
  price: number;
  comments: number;
  hearts: number;
  // 아이템 단위의 컴포넌트화 시키기 위힘이기에, title 부터 hearts 아이콘까지 type 설정을 해주었다.
}

export default function Item({
  title,
  price,
  comments,
  hearts,
  id,
}: ItemProps) {
  return (
    &lt;Link href={`/items/${id}`}&gt;
    // ${id} 나중 게시된 판매 목록들에 대한 넘버를 지정해주어 key 로 사용되어 질것.
      &lt;a className=&quot;flex px-4 pt-5 cursor-pointer justify-between&quot;&gt;
        &lt;div className=&quot;flex space-x-4&quot;&gt;
          &lt;div className=&quot;w-20 h-20 bg-gray-400 rounded-md&quot; /&gt;
          &lt;div className=&quot;pt-2 flex flex-col&quot;&gt;
            &lt;h3 className=&quot;text-sm font-medium text-gray-900&quot;&gt;{title}&lt;/h3&gt;
            &lt;span className=&quot;font-medium mt-1 text-gray-900&quot;&gt;${price}&lt;/span&gt;
          &lt;/div&gt;
        &lt;/div&gt;
        &lt;div className=&quot;flex space-x-2 items-end justify-end&quot;&gt;
          &lt;div className=&quot;flex space-x-0.5 items-center text-sm  text-gray-600&quot;&gt;
            &lt;svg
              className=&quot;w-4 h-4&quot;
              fill=&quot;none&quot;
              stroke=&quot;currentColor&quot;
              viewBox=&quot;0 0 24 24&quot;
              xmlns=&quot;http://www.w3.org/2000/svg&quot;
            &gt;
              &lt;path
                strokeLinecap=&quot;round&quot;
                strokeLinejoin=&quot;round&quot;
                strokeWidth=&quot;2&quot;
                d=&quot;M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z&quot;
              &gt;&lt;/path&gt;
            &lt;/svg&gt;
            &lt;span&gt;{hearts}&lt;/span&gt;
          &lt;/div&gt;
          &lt;div className=&quot;flex space-x-0.5 items-center text-sm  text-gray-600&quot;&gt;
            &lt;svg
              className=&quot;w-4 h-4&quot;
              fill=&quot;none&quot;
              stroke=&quot;currentColor&quot;
              viewBox=&quot;0 0 24 24&quot;
              xmlns=&quot;http://www.w3.org/2000/svg&quot;
            &gt;
              &lt;path
                strokeLinecap=&quot;round&quot;
                strokeLinejoin=&quot;round&quot;
                strokeWidth=&quot;2&quot;
                d=&quot;M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z&quot;
              &gt;&lt;/path&gt;
            &lt;/svg&gt;
            &lt;span&gt;{comments}&lt;/span&gt;
          &lt;/div&gt;
        &lt;/div&gt;
      &lt;/a&gt;
    &lt;/Link&gt;
  );
}</code></pre><ul>
<li><strong>point :</strong> 아이템 단위의 컴포넌트 구성 페이지. heart 와 comment type은 나중을 위한 count 용도로 number type으로 지정.</li>
</ul>
<h3 id="5-item-컴포넌트화-시키기">5. item 컴포넌트화 시키기.</h3>
<pre><code>import { cls } from &quot;../libs/utils&quot;;

interface MessageProps {
  message: string;
  reversed?: boolean;
  // 대화창에서의 대화 상대와 자신의 메세지를 구분시키기 위한 type 설정.
  avatarUrl?: string;
}

export default function Message({
  message,
  avatarUrl,
  reversed,
}: MessageProps) {
  return (
    &lt;div
      className={cls(
        &quot;flex  items-start&quot;,
        reversed ? &quot;flex-row-reverse space-x-reverse&quot; : &quot;space-x-2&quot;
      )}
    &gt;
      &lt;div className=&quot;w-8 h-8 rounded-full bg-slate-400&quot; /&gt;
      &lt;div className=&quot;w-1/2 text-sm text-gray-700 p-2 border border-gray-300 rounded-md&quot;&gt;
        &lt;p&gt;{message}&lt;/p&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  );
}</code></pre><ul>
<li><strong>point :</strong> reversed prop에 따라 ui 스타일링을 다르게 할수 있음.</li>
</ul>
<h3 id="6-layouttsx에서의-추가-코드">6. layout.tsx에서의 추가 코드</h3>
<pre><code> const router = useRouter();
 // useRouter을 통하여 현재 user 가 어떤 페이지에 있는지 확인후 하단 tab bar의 색을 바꿔 주기 위해 선언되었다.
  const onClick = () =&gt; {
    router.back();
  }; // 뒤로 가기 버튼을 위한 화살표 함수.</code></pre><pre><code>router.pathname === &quot;/pageName&quot; ? &quot;참 조건&quot; : &quot;아닐시 조건&quot;</code></pre><ul>
<li><strong>point :</strong> useRouter통하여 특정 페이지에 대한 조건적 스타일링 적용 예시. </li>
</ul>
<blockquote>
<p>이외에도 컴포넌트들을 더욱 세분화 할수 있을것 같다. 이와 같이 컴포넌트화 작업은 큰 규모의 프로젝트 할때에 아주 중요한 포인트가 될것 같다. 그러기 위해 더 생산성 있는 코드를 만들기 위한 많은 고민과 이에 관한 서적들을 읽어보면 좋을것 같다.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[Carrot market 정복 노트 [3] - "Carrot market UI Cloning"]]></title>
            <link>https://velog.io/@jaylion_2009/Carrot-market-%EC%A0%95%EB%B3%B5-%EB%85%B8%ED%8A%B8-3-Carrot-market-UI-Cloning</link>
            <guid>https://velog.io/@jaylion_2009/Carrot-market-%EC%A0%95%EB%B3%B5-%EB%85%B8%ED%8A%B8-3-Carrot-market-UI-Cloning</guid>
            <pubDate>Wed, 09 Mar 2022 11:51:45 GMT</pubDate>
            <description><![CDATA[<p>이 페이지에는 Carrot market UI Cloning에 대한 노트 이다.
무엇보다 Tailwind 사용으로 인하여 코드의 가독성이 떨어짐으로 가독성 관리에 대한 내용, layout(어떠한 페이지에도 항상 나오게될 ex) tab bar, 뒤로가기 버튼 등) page 만드는 방법 그리고 css 스타일링에 있어 알아두면 좋을 점들에 대한 내용들을 적어 놓았다.</p>
<h2 id="주요-노트">주요 노트</h2>
<hr>
<h3 id="1--usestate-2개-이상의-초기값-설정-방법">1. ** useState 2개 이상의 초기값 설정 방법.**</h3>
<p>pages/enter.tsx</p>
<pre><code>const [method, setMethod] = useState&lt;&quot;email&quot; | &quot;phone&quot;&gt;(&quot;email&quot;);
// email 또는 phone 이라는 string 변수를 버튼을 눌렀을때 toggle되도록 넣기 위한 코드이며, 초기 값은 email로 정함.</code></pre><h3 id="2--email과-phone-요소에-이벤트성-스타일-주는-방법">2. ** email과 phone 요소에 이벤트성 스타일 주는 방법**</h3>
<p>pages/enter.tsx </p>
<pre><code>function cls(...classnames: string[]) {
// 무한 으로 인자를 받기위해 ...function을 사용 하였으며(이 코드에선 2개의 인자), 타입은 문자열 배열로 지정.
  return classnames.join(&quot; &quot;);
} // return시 모든 String 배열들 사이에 한칸의 공백을 추가해 리턴.

className={cls(
                &quot;pb-4 font-medium text-sm border-b-2&quot;,
                method === &quot;email&quot;
                  ? &quot; border-orange-500 text-orange-400&quot;
                  : &quot;border-transparent hover:text-gray-400 text-gray-500&quot;
              )}
              //스타일 조건을 위한 삼항연사자와 일반 스타일 속성들이 함께 적용되야 하기 때문에, 이 코드의 가독성이 줄어 들게 된다. 
              그러므로 cls 라는 function 을 만들어 위의 코드와 같이 조금 더 깔끔한 코드로 바꿀수 있다. 
              하드 코딩 시에는 ${} 과 `` 같은 EL들의 중복하게 되어 가독성이 떨어진다.</code></pre><h3 id="3-title--tab-bar--go-back-ui-적용-방법">3. <strong>Title , Tab bar , go back UI 적용 방법</strong></h3>
<p>components/layout.tsx</p>
<pre><code>import React from &quot;react&quot;;
import { cls } from &quot;../libs/utils&quot;;
// 자주 쓰이기에 libs/utils 에 넣어 두었다.

interface LayoutProps {
  title?: string;
  canGoBack?: boolean;
  hasTabBar?: boolean;
  children: React.ReactNode;
}
//여기서 React.ReactNode는  jsx 내에서 사용할수 있는 모든 요소의 타입을 의미 한다.


export default function Layout({
  title,
  canGoBack,
  hasTabBar,
  children,
}: LayoutProps) {
// typescript에서 interface에 지정한 변수의 타입들을 선언할때 이와 같은 방식으로 선언한다.

  return (
    &lt;div&gt;
      &lt;div className=&quot;bg-white w-full text-lg font-medium py-3 fixed text-gray-800 border-b top-0 justify-center flex items-center&quot;&gt;
        {title ? &lt;span&gt;{title}&lt;/span&gt; : null}
      &lt;/div&gt;
      &lt;div className={cls(&quot;pt-16&quot;, hasTabBar ? &quot;pb-16&quot; : &quot;&quot;)}&gt;{children}&lt;/div&gt;
      {hasTabBar ? (
        &lt;nav className=&quot;bg-white text-gray-800 border-t fixed bottom-0 pb-10 pt-3 flex justify-between items-center&quot;&gt;&lt;/nav&gt;
      ) : null}
    &lt;/div&gt;
  );
}</code></pre><p>위의 코드에서, children을 중심으로 위쪽엔 title 아래쪽엔 tab bar 가 화면에 뿌려지게 된다.</p>
<h3 id="4-layout-component-사용-방법">4. <strong>Layout component 사용 방법</strong></h3>
<pre><code>import Layout from &quot;../components/layout&quot;;

&lt;Layout title=&quot;홈&quot; hasTabBar&gt;
... //요소들
&lt;/Layout&gt;</code></pre><p>위의 코드는, 다른 파일에서 Layout 컴포넌트로 감싸 title, hasTabBar, canGoBack 과 같은 props 를 사용 하여 title, 하단 Tab bar, goback UI를 어느 페이지에서나 선택적으로 보여줄수 있다.</p>
<blockquote>
<p>Tailwind plugin : 강의에서는 input layer를 변경 할수있는 plugin을 사용하였다.
설치 커맨드 : npm i @tailwindcss/forms  </p>
</blockquote>
<p>설치후 configration 적용
tailwind.config.js </p>
<pre><code>plugins: [require(&quot;@tailwindcss/forms&quot;)],</code></pre><blockquote>
<p>주요 Tailwind 또는 CSS 스타일 속성들.</p>
</blockquote>
<ul>
<li><strong>rounded-(위치)-(크기)</strong> : 특정 모서리만 둥굴게 변경을 원할때.</li>
<li><strong>select-none</strong> : 유저가 특정 요소를 선택하지 못하게 하고싶을때.</li>
<li><strong>flex items-end</strong> :일반적인 댓글이나 좋아요의 위치 같이 아래쪽에 위치 하고싶을때.</li>
<li><strong>divide-(y or x)-(s)</strong> : 형제 요소가 있는 곳에만, boder를 넣어줌.</li>
<li><strong>flex-row-reverse  space-x-reverse</strong> : 아이템을 반대로 돌려 정렬해줌(ex-채팅창에서의 대화 UI).</li>
<li><strong>aspect-(특정 도형)</strong> : 비율에 맞춰 스타일링 해줌. ex) 정사각형, 원, 비디오 등..</li>
<li><strong>w-full max-w-xl mx-auto</strong> : 최대의 크기 이지만. max width는 xl 크기까지이며 x축의 길이는 자동으로 맞춘다는 의미.</li>
<li><strong>htmlFor=&quot;이름&quot;</strong> : htmlFor은 label이 포함한 태그에 관계성을 주어 하나의 태그 처럼 이벤트가 가능하게 하는것. 그러므로, label의 속한 태그에 같은 &quot;이름&quot;으로 주어야 한다. JSX 에서는 for이 아닌 htmlFor을 사용한다.</li>
</ul>
<blockquote>
<p>이외 알아두어야 할것들.</p>
</blockquote>
<ul>
<li>tailwind에서 특정 value 값을 지정할수 없을때  divide-y-[1px]와같이 arbitrary value를 []로 감싸 tailwind에 지정되어 있지 않은 값도 넣을수 있다.</li>
<li>heroicons : svg 아이콘들을 무료로 사용할수 있는 웹사이트.</li>
<li>노마드 코더 같은 경우 css의 모든글자의 색을 완전 검은색으로 하지않는 특징이 있으며, 한 컴포넌트 단위의 div 구조를 지향하는것 같다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Carrot market 정복 노트 [2] - "Tour of Tailwind "]]></title>
            <link>https://velog.io/@jaylion_2009/Carrot-market-%EC%A0%95%EB%B3%B5-%EB%85%B8%ED%8A%B8-2-Tour-of-Tailwind</link>
            <guid>https://velog.io/@jaylion_2009/Carrot-market-%EC%A0%95%EB%B3%B5-%EB%85%B8%ED%8A%B8-2-Tour-of-Tailwind</guid>
            <pubDate>Tue, 08 Mar 2022 09:50:46 GMT</pubDate>
            <description><![CDATA[<p>사람마다 CSS 스타일링 방법이 다르겠지만, 개발 경험이 있는 개발자들에게 공통 되어지는 스타일링 방법은 있는것 같다. 예를들어, 어떠한 elements들의 구조의 계층을 어떻게 할것인지에 대한 고민 이라던지, 어떤 스타일을 어떻게 적용하는지, 어떤 계열의 컬러 또는 명암 정도를 선택 하는지에 대한 것들을 유심히 관찰 하고 기록하기 위한 페이지가 될것이다. 물론, 이 페이지는 Tailwind styling에 관한 내용이다.</p>
<p>이전 페이지에 언급하였듯이, Tailwind는 지정되어 있는 클래스 이름을 className 속성에 가져다 쓰면 되는 CSS framework 이다.</p>
<hr>
<h4 id="아래-사진에서-빨간-동그라미는-div-태그-또는-각-역할에-대한-태그를-의미-하며-nico-선생님의-div-구조를-파악할수-있다-이구조를-자세히-보면-위에서-아래부터-y축을-기준으로-층을-나눈것을-볼수-있다">아래 사진에서 빨간 동그라미는 div 태그 또는 각 역할에 대한 태그를 의미 하며, Nico 선생님의 div 구조를 파악할수 있다. 이구조를 자세히 보면 위에서 아래부터 y축을 기준으로 층을 나눈것을 볼수 있다.</h4>
<p><img src="https://images.velog.io/images/jaylion_2009/post/7403502a-168a-47d1-8c59-8fbafba7e2e0/css.png" alt=""></p>
<h3 id="주요-tailwind-또는-css-스타일-속성들">주요 Tailwind 또는 CSS 스타일 속성들.</h3>
<p><a href="https://tailwindcss.com/docs/aspect-ratio">Tailwind Doc link</a></p>
<ul>
<li><p><strong>space-x-(수치) 또는 gap-(수치)</strong> : 강의 내용에선 위 사진에 있는 4.9점의 별점 요소와 하트 요소의 간격을 띄우기 위해 사용 되었다. 이제 따로 margin left 또는 right 으로 번거로운 작업을 안해도 된다 </p>
</li>
<li><p><strong>aspect-square</strong> : 실제로 <em>aspect-ratio:1/1;</em> 이며, 정사각형을 만들때 유용하며 주의할점으로는 추가적으로 w(width)를 꼭 주어야 한다. </p>
</li>
<li><p><strong>각종 modifier</strong> : 기존 CSS 스타일링에서는 hover 스타일 속성을 주기위해 조금은 긴 코드를 작성해야 했지만, Tailwind 에서는 className에 바로 적용할수있는 hover,active,focus 등과 같은 긴코드를 아주 간결하게 만들어주는 modifier 가 제공된다. 
modifier 을 중복으로도 사용이 가능하다(<em>The power of just in time compiler</em>) ex) file:hover:<del>~</del> </p>
</li>
</ul>
<ul>
<li><p><strong>group</strong> : 특정 자식 element 의 스타일 속성이 부모 element의 이벤트가 적용 되고 싶을때 쓰면 유용하다. ex) 부모 element에서는 group 속성 추가 자식 element에서는 group-(modifier): 추가</p>
</li>
<li><p><strong>peer</strong> : peer 은 group 과 같은 개념이지만 부모와 자식이 아닌 서로 다른 element의 관계성을 주어 특정 이벤트에 반응하게 해준다. 주의해야될 점은 peer은 peer selector 보다 앞에 위치 해야만 적용 가능하다.</p>
</li>
</ul>
<h3 id="이외-알아두어야-할것들">이외 알아두어야 할것들.</h3>
<ul>
<li>강의 내용에서 JSX에서의 리스트 만들기 내용이 있는데, 주요 point는 아래와 같다.</li>
</ul>
<p>map function 은 arr.map(callbackFunction(currentValue, index, array), thisArg); 으로 이루워 져있으며, index 인자값을 key 값으로 사용 되어 졌다.
자세한 내용은 아래의 링크 (React 공식 홈페이지)를 참고하면 좋을것 같다.
<a href="https://ko.reactjs.org/docs/lists-and-keys.html">React (리스트와 Key) Link</a></p>
<ul>
<li><p>화면 크기를 디자인할때에는 아주작은 모바일 사이즈가 default 로 되어있으니 작은 사이즈부터 큰사이즈 순으로 생각하고 개발해야한다.</p>
</li>
<li><p>반응형 modifier 의 경우 아래의 링크를 통해 grid 컬럼을 다루는 강의를 다시 다시보기를 추천한다.
<a href="https://nomadcoders.co/carrot-market/lectures/3472">https://nomadcoders.co/carrot-market/lectures/3472</a></p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Carrot market 정복 노트 [1] - "Setup"]]></title>
            <link>https://velog.io/@jaylion_2009/Carrot-market-note-1-Setup</link>
            <guid>https://velog.io/@jaylion_2009/Carrot-market-note-1-Setup</guid>
            <pubDate>Mon, 07 Mar 2022 08:01:59 GMT</pubDate>
            <description><![CDATA[<h3 id="velog-의-시작">Velog 의 시작..</h3>
<p>먼저, Carrot market 수업(by Nomad coders)을 한번 다 듣고, 모르는것, 알아야 될것 등 모든걸 내 것으로  만들겠다는 마음에 시작 하게 되었다. 이 Carrot market 정복 노트 게시글들은 내가 잊지 말아야할 내용들을 나중에 개인 프로젝트를 하게 될때 Referance 로 사용 할수 있게 정리 해 놓은 곳이다. </p>
<h3 id="setup">Setup</h3>
<blockquote>
<h3 id="nextjs-"><strong>NextJs ?</strong></h3>
<p> SSR, SEO, TypeScript, API, automatic routing등 개발에 있어 편리성과 생산성을 높일수 있는 많은 장점들과 기능들을 제공하고 있는 React Framework 이다.</p>
</blockquote>
<ul>
<li><p>** npx create-next-app@latest --typescript**
nextjs 와 typescript를 install.</p>
</li>
<li><p>** npm i next@latest react@rc react-dom@rc**
react의 rc(release candidate) 버전을 사용하고 싶다면 이 커맨드를 이용해 install 한다.</p>
</li>
</ul>
<blockquote>
<h3 id="tailwindcss-"><strong>TailwindCSS ?</strong></h3>
<p>utility-first CSS framework 이며, className 속성에 스타일을 지정 할수 있다.
 _VSC 에서 Tailwind CSS Intellisense 이라는 extension 을 설치하여 자동 완성 기능을 추가할수있다 _</p>
</blockquote>
<ul>
<li><p><strong>npm install -D tailwindcss postcss autoprefixer</strong>
tailwindcss, postcss, autoprefixer를 install.</p>
</li>
<li><p><strong>npx tailwindcss init -p</strong></p>
</li>
</ul>
<p>postcss.config.js 와 tailwind.config.js 파일이 생성 된다.
그리고, tailwind.config.js 파일내에 tailwind가 적용 되어지게 설정하는 코드를 넣어 주어야 된다. </p>
<pre><code>// tailwind.config.js

module.exports = {
  content: [
    &quot;./pages/**/*.{js,jsx,ts,tsx}&quot;,
    &quot;./components/**/*.{js,jsx,ts,tsx}&quot;,
    // pages 또는 components폴더 안에 어떠한 폴더든 그 속에 있는 파일의 extension 이 js,jsx,ts,tsx인 
    곳에 tailwind를 적용 할것 이라고 지정 해주는 코드
   ],
</code></pre><p>아래의 코드는 tailwind의 global style을 지정 하기 위한 코드</p>
<pre><code>// styles/globals.css

@tailwind base;
@tailwind components;
@tailwind utilities;
</code></pre>]]></description>
        </item>
    </channel>
</rss>