<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>행갬로그</title>
        <link>https://velog.io/</link>
        <description>Front-End Developer</description>
        <lastBuildDate>Thu, 18 Apr 2024 08:45:20 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>행갬로그</title>
            <url>https://velog.velcdn.com/images/hang_kem_0531/profile/cf535859-1711-4d16-8e3a-d7be23d427ef/image.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. 행갬로그. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/hang_kem_0531" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[제가 찾던 React 19 버전 여기 있었네요~	]]></title>
            <link>https://velog.io/@hang_kem_0531/%EC%A0%9C%EA%B0%80-%EC%B0%BE%EB%8D%98-React-19-%EB%B2%84%EC%A0%84-%EC%97%AC%EA%B8%B0-%EC%9E%88%EC%97%88%EB%84%A4%EC%9A%94</link>
            <guid>https://velog.io/@hang_kem_0531/%EC%A0%9C%EA%B0%80-%EC%B0%BE%EB%8D%98-React-19-%EB%B2%84%EC%A0%84-%EC%97%AC%EA%B8%B0-%EC%9E%88%EC%97%88%EB%84%A4%EC%9A%94</guid>
            <pubDate>Thu, 18 Apr 2024 08:45:20 GMT</pubDate>
            <description><![CDATA[<p>항상 생각없이 리액트를 사용하면서
내가 버전 몇을 사용하고 있는지 새삼 깨닫지 못하고 있다가,
며칠 전 리액트 19버전이 릴리즈 될 수 있다는 아티클을 하나 읽었다.</p>
<p>내가 처음 개발을 하겠다고 나대던 시기가 2019년,
이때의 리액트는 16.8.0 버전으로 Hook이 도입되기 시작했던 버전이었다.
근데 지금은 19라고? 물론 아직 릴리즈 되지는 않았지만
미리 알아두어서 도태되지 않고 따라갈 수 있게 준비해두는 건 좋을 것 같아서</p>
<p>그래서 작성하게 된 글이다.</p>
<blockquote>
<p>해당 글은 작성자의 허락을 맡아 <a href="https://www.freecodecamp.org/news/new-react-19-features/">New Features in React 19 – Updates with Code Examples</a> 글을 번역 및 참조하였습니다~!</p>
</blockquote>
<h2 id="react-19에서-변경된-점-19에서-적용-안될수도-잇슴">React 19에서 변경된 점 (19에서 적용 안될수도 잇슴)</h2>
<ol>
<li><p><strong>React 컴파일러</strong></p>
</li>
<li><p><strong>서버 컴포넌트</strong></p>
</li>
<li><p>액션</p>
</li>
<li><p>문서 메타데이터</p>
</li>
<li><p>Assets Loading</p>
</li>
<li><p>웹 컴포넌트</p>
</li>
<li><p>향상된 Hooks</p>
</li>
</ol>
<h3 id="1-react-compiler">1. React Compiler</h3>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/5c8f3fb5-b604-42fe-a991-a6f341558875/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/12206acd-518e-4392-a167-3cf05d72a3ea/image.png" alt=""></p>
<p>우선, 리액트 팀 멤버들의 <del>트위터</del> X를 보면, React 19는 현재 Canary 버전에 적용되어 있지만 React Compiler는 아직 적용되지 않았고 다른 팀 멤버에 따르면 24년 말에 적용될 수 있다고 말했다. 즉 React 19 != React Compiler이니 React 19 버전의 변경점이라고 할 수 없지만 미리 알아두도록 하자.</p>
<p>*<em>그렇다면 React Compiler는 무엇일까? *</em></p>
<p>React는 현재 state 값이 변경될 때, 너무 많은 리렌더링이 발생하거나 자동으로 리렌더링이 발생하지 않는다. 그렇기 때문에 수동으로 렌더링을 조정하는 방법을 사용했는데, 이때 사용했던 것들이 바로 <code>useMemo</code>, <code>useCallback</code> 및 <code>memo</code> API였다. 그러나 이러한 수동적인 조정은 결국 절충안이었으며, 코드가 복잡해지고 오류가 발생하기도 쉽고 최신 상태를 유지하려면 추가 작업이 필요했다.</p>
<p>여기서 적용될 React Compiler는, 바로 모든 것이 자동화 (메모화) 된다는 것이다. 그렇기 때문에 더이상 <code>useCallback</code>이나 <code>useMemo</code>는 사용할 필요가 없어질 것이며, 리렌더링을 줄이기 위해 state를 아래로 이동시키거나 컴포넌트를 자식으로 전달하는 것과 같은 합성 기법들은 사라질 것이다.</p>
<h3 id="2-server-components">2. Server components</h3>
<p>그동안 리액트는 주로 컴포넌트를 클라이언트 사이드에서 실행했기 때문에, 서버 사이드 렌더링과 같은 방법을 사용하려면 Next.js와 같은 프레임워크를 사용해야 했다. Next.js에서 클라이언트 사이드에서 컴포넌트를 실행하기 위해 <code>use client</code> 지시문을 사용하는 것처럼, React 19에서는 <code>use server</code> 지시문을 통해 서버사이드에서 컴포넌트를 실행할 수 있게 되었다.</p>
<pre><code class="language-js">&#39;use server&#39;;

export default async function requestUsername(formData) {
  const username = formData.get(&#39;username&#39;);
  if (canRequest(username)) {
    // ...
    return &#39;successful&#39;;
  }
  return &#39;failed&#39;;
}</code></pre>
<p>서버 컴포넌트가 제공되면서 React 19에서는 다음과 같은 이점을 기대할 수 있다.</p>
<ol>
<li><strong>SEO</strong> : 서버 렌더링 컴포넌트는 웹 크롤러에 더 쉽게 접근할 수 있는 콘텐츠를 제공하여 검색 엔진 최적화를 향상시킬 수 있다.</li>
<li><strong>성능 향상</strong> : 서버 컴포넌트는 특히 콘텐츠가 많은 응용 프로그램의 초기 페이지 로드 속도를 높이고 전반적인 성능을 향상시키는 데 기여할 수 있다.</li>
<li><strong>서버 사이드 실행</strong> : 서버 컴포넌트는 서버에서 코드를 실행할 수 있도록 하여 API 호출과 같은 작업을 원활하고 효율적으로 만든다.</li>
</ol>
<h3 id="3-actions">3. Actions</h3>
<p>리액트 19에서는 액션을 사용하여 HTML 태그 <code>&lt;form/&gt;</code>과 액션을 통합할 수 있다. 즉, <code>onSubmit</code>과 같은 이벤트를 액션으로 대체할 수 있는 것이다. </p>
<pre><code class="language-js">&lt;form onSubmit={search}&gt;
  &lt;input name=&quot;query&quot; /&gt;
  &lt;button type=&quot;submit&quot;&gt;Search&lt;/button&gt;
&lt;/form&gt;</code></pre>
<p>리액트 19 버전 이전에는, onSubmit과 같은 이벤트들로 form 양식 제출을 했었다. 그러나 이 이벤트는 클라이언트 측에서만 사용 가능한 <code>search</code> 메서드를 사용해서, 서버 사이드에서는 실행될 수 없었다. </p>
<pre><code class="language-js">&quot;use server&quot;

const submitData = async (userData) =&gt; {
    const newUser = {
        username: userData.get(&#39;username&#39;),
        email: userData.get(&#39;email&#39;)
    }
    console.log(newUser)
}

const Form = () =&gt; {
    return &lt;form action={submitData}&gt;
        &lt;div&gt;
            &lt;label&gt;Name&lt;/label&gt;
            &lt;input type=&quot;text&quot; name=&#39;username&#39;/&gt;
        &lt;/div&gt;
        &lt;div&gt;
            &lt;label&gt;Name&lt;/label&gt;
            &lt;input type=&quot;text&quot; name=&quot;email&quot; /&gt;
        &lt;/div&gt;
        &lt;button type=&#39;submit&#39;&gt;Submit&lt;/button&gt;
    &lt;/form&gt;
}

export default Form;</code></pre>
<p>리액트 19 이후에는 서버 사이드에서 작업이 가능하므로, 클라이언트 사이드에서만 사용되는 <code>onSubmit</code>과 같은 이벤트 대신에 <code>action</code> 속성을 사용하게  된다. action 속성의 값은 클라이언트 사이드나 서버 사이드에서 데이터를 제출할 수 있다. 이 action을 통해 동기 및 비동기 작업을 모두 실행할 수 있으므로, 데이터 제출 관리 및 state 업데이트는 간소화 된다. </p>
<p>위 코드에서는 <code>submitData</code>는 서버 컴포넌트의 action이 되며, <code>form</code>은 <code>submitData</code>를 action으로 사용하는 클라이언트 사이드의 컴포넌트이다. submitData는 서버에서 실행되며, 클라이언트(<code>form</code>)와 서버(<code>submitData</code>) 컴포넌트간의 통신은 <code>action</code> 때문에 가능해진 것이다.</p>
<h3 id="4-web-components">4. Web Components</h3>
<p>리액트 19 버전에서는 웹 컴포넌트를 사용하여 HTML, CSS, JavaScript를 통해 커스텀 컴포넌트를 생성하여 표준 HTML 태그인 것 처럼 웹 애플리케이션에 원활하게 통합할 수 있도록 할 수 있다. 사실 웹 컴포넌트라는 개념이 기존에 내가 알고 있던 것이 아니라 좀 생소하기는 한데, 기존에는 웹 컴포넌트를 리액트에서 사용하려면 웹 컴포넌트를 리액트 컴포넌트로 변환하거나 추가적인 패키지를 설치, 혹은 추가적인 코드를 작성해야 했다면 이것들을 배제하고 쉽게 사용할 수 있게 된다는 의미같다. 만일 Carousel 웹 컴포넌트를 발견했다면, 별도의 라이브러리 설치를 하거나 리액트 코드로의 변환이 필요 없이 가져와 사용할 수 있다는 의미이다.</p>
<p>현재로써는 별도의 코드 설명과 같은 자세한 업데이트 내용은 없지만, 릴리즈가 된다면 개발은 보다 간소화되고 React 애플리케이션에서 기존 웹 구성 요소의 방대한 생태계를 활용할 수 있게 될 것이다.</p>
<h3 id="5-document-metadata">5. Document Metadata</h3>
<p>HTML 메타 태그와 같은 요소들은 SEO를 최적화하고 접근성을 보장하는 데에 매우 중요한 요소이다. React는 SPA(Single Page Application)이기 때문에 다양한 경로에서 이러한 요소를 관리하는 것이 번거로웠었다.</p>
<pre><code class="language-js">import React, { useEffect } from &#39;react&#39;;

const HeadDocument = ({ title }) =&gt; {
  useEffect(() =&gt; {
    document.title = title;

     const metaDescriptionTag = document.querySelector(&#39;meta[name=&quot;description&quot;]&#39;);
    if (metaDescriptionTag) {
    metaDescriptionTag.setAttribute(&#39;content&#39;, &#39;New description&#39;);
    }
  }, [title]);

  return null;
};

export default HeadDocument;</code></pre>
<p>기존에는 props에 따라 <code>title</code>이나 <code>meta</code> 태그를 useEffect에서 변경하는 위와 같은 로직을 사용하고는 했다. 또한 리액트 내에서도 JavaScript를 사용했었다. 하지만 이와 같은 방법은 clean한 코드를 작성하는데에는 좋지 않았다.</p>
<pre><code class="language-js">Const HomePage = () =&gt; {
  return (
    &lt;&gt;
      &lt;title&gt;Freecodecamp&lt;/title&gt;
      &lt;meta name=&quot;description&quot; content=&quot;Freecode camp blogs&quot; /&gt;
      // Page content
    &lt;/&gt;
  );
}</code></pre>
<p>리액트 19 버전 이후에는, 각각의 컴포넌트 내에 <code>title</code>과 <code>meta</code> 태그가 사용이 가능해져서, 이전과 같은 별도의 로직 없이도 SEO를 최적화하고 접근성을 보장할 수 있게 되었다.</p>
<h3 id="6-asset-loading">6. Asset Loading</h3>
<p>기존에는 브라우저에서 View가 먼저 렌더링되고, stylesheet, font, image와 같은 부수적인 요소들은 view가 렌더링 된 이후에 렌더링 되는 경우들이 종종 있었다. 이로 인해서 사용자가 브라우저를 봤을때 깜빡임 현상이 나타나곤 했었다.</p>
<p>리액트 19에서는 사용자가 페이지를 탐색할 때, 이미지와 기타 파일들이 백그라운드에서 로드되기 때문에 페이지 로드 시간을 줄이고 사용자의 대기 시간을 줄이는데 도움이 된다. 또한, lifeCycle Suspense를 도입하여 언제 컨텐츠들이 표시될 수 있는지에 대한 시간을 결정하여 사용자로 하여금 깜빡임 현상을 제거할 수 있게 하였다.</p>
<h3 id="7-향상된-hooks">7. 향상된 Hooks</h3>
<p>리액트 19에서는 여러가지 향상된 Hooks들이 등장했는데, 이에 대해 알아보자.</p>
<h4 id="usememo">useMemo()</h4>
<p>앞서 설명한 것처럼, React Compiler가 도입될 경우에 전체적으로 메모이제이션이 이루어지기 때문에 <code>useMemo()</code>는 더이상 필요성이 사라졌다.</p>
<h4 id="forwardref">forwardRef()</h4>
<p><code>ref</code> 역시 이제 props로 전달할 수 있기 때문에, <code>forwardRef()</code>는 사용되지 않을 것이다. 이는 코드를 보다 간결하게 작성할 수 있게 한다.</p>
<pre><code class="language-js">
// Before React 19
import React, { forwardRef } from &#39;react&#39;;

const ExampleButton = forwardRef((props, ref) =&gt; (
  &lt;button ref={ref}&gt;
    {props.children}
  &lt;/button&gt;
));

// After React 19  
import React from &#39;react&#39;;

const ExampleButton = ({ ref, children }) =&gt; (
  &lt;button ref={ref}&gt;
    {children}
  &lt;/button&gt;
);</code></pre>
<h4 id="use">use()</h4>
<p>React 19에서는 새로이 <code>use()</code>라는 Hook이 등장하였다.</p>
<pre><code class="language-js">const value = use(resource);</code></pre>
<p>이 Hook은 Promise, async, context와 같은 코드들을 사용화 하는 것을 단순화한다.</p>
<pre><code class="language-js">import { use } from &quot;react&quot;;

const fetchUsers = async () =&gt; {
    const res = await fetch(&#39;https://jsonplaceholder.typicode.com/users&#39;);
    return res.json();
  };

  const UsersItems = () =&gt; {
    const users = use(fetchUsers());

    return (
      &lt;ul&gt;
        {users.map((user) =&gt; (
          &lt;div key={user.id} className=&#39;bg-blue-50 shadow-md p-4 my-6 rounded-lg&#39;&gt;
            &lt;h2 className=&#39;text-xl font-bold&#39;&gt;{user.name}&lt;/h2&gt;
            &lt;p&gt;{user.email}&lt;/p&gt;
          &lt;/div&gt;
        ))}
      &lt;/ul&gt;
    );
  }; 
export default UsersItems;</code></pre>
<p>위 코드를 통해 <code>use</code>의 사용법을 알아보자.</p>
<ol>
<li><code>fetchUsers</code>라는 GET 요청을 보내는 함수를 생성한다.</li>
<li><code>useEffect</code>나 <code>useState</code> Hook 대신에 <code>use</code>를 사용하여 <code>fetchUsers</code>를 실행한다.</li>
<li><code>users</code>에 <code>fetchUsers</code>의 결과값이 저장된다.</li>
<li>return 에서 <code>users</code>를 사용하여 리스트를 매핑한다.</li>
</ol>
<p>기존의 useEffect나 useState를 사용하던 코드보다 훨씬 간결해진 모습을 볼 수 있다!</p>
<p>Context API에서도 <code>use()</code>를 사용할 수 있다.</p>
<pre><code class="language-js">import { createContext, useState, use } from &#39;react&#39;;

const ThemeContext = createContext();

const ThemeProvider = ({ children }) =&gt; {
  const [theme, setTheme] = useState(&#39;light&#39;);

  const toggleTheme = () =&gt; {
    setTheme((prevTheme) =&gt; (prevTheme === &#39;light&#39; ? &#39;dark&#39; : &#39;light&#39;));
  };

  return (
    &lt;ThemeContext.Provider value={{ theme, toggleTheme }}&gt;
      {children}
    &lt;/ThemeContext.Provider&gt;
  );
};

const Card = () =&gt; {
  // use Hook()
  const { theme, toggleTheme } = use(ThemeContext);

  return (
    &lt;div
      className={`p-4 rounded-md ${
        theme === &#39;light&#39; ? &#39;bg-white&#39; : &#39;bg-gray-800&#39;
      }`}
    &gt;
      &lt;h1
        className={`my-4 text-xl ${
          theme === &#39;light&#39; ? &#39;text-gray-800&#39; : &#39;text-white&#39;
        }`}
      &gt;
        Theme Card
      &lt;/h1&gt;
      &lt;p className={theme === &#39;light&#39; ? &#39;text-gray-800&#39; : &#39;text-white&#39;}&gt;
       Hello!! use() hook
      &lt;/p&gt;
      &lt;button
        onClick={toggleTheme}
        className=&#39;bg-blue-500 hover:bg-blue-600 text-white rounded-md mt-4 p-4&#39;
      &gt;
        {theme === &#39;light&#39; ? &#39;Switch to Dark Mode&#39; : &#39;Switch to Light Mode&#39;}
      &lt;/button&gt;
    &lt;/div&gt;
  );
};

const Theme = () =&gt; {
  return (
    &lt;ThemeProvider&gt;
      &lt;Card /&gt;
    &lt;/ThemeProvider&gt;
  );
};

export default Theme</code></pre>
<h4 id="useformstatus">useFormStatus()</h4>
<p><code>useFormStatus()</code>는 React 19에서 나온 새로운 Hook으로, form을 보다 효과적으로 사용할 수 있게 해주는 Hook이다. <code>useFormStatus()</code>는 form 상태의 정보 - 제출이 완료되었는지, 유효성 검사가 끝났는지 - 를 처리하는 데 사용하는 Hook이다.</p>
<pre><code class="language-js">const { pending, data, method, action } = useFormStatus();

or

const { status } = useFormStatus()</code></pre>
<ol>
<li><p>pending
form이 <code>pending</code> 상태일 경우는 <code>true</code>를, 아닐 경우에는 <code>false</code>를 반환한다.</p>
</li>
<li><p>data
form이 제출하는 데이터를 포함하는 FormData 인터페이스를 구현한다.</p>
</li>
<li><p>method
HTTP 메서드 – GET 또는 POST. 기본적으로 GET이다.</p>
</li>
<li><p>action
함수 참조이다.</p>
</li>
</ol>
<pre><code class="language-js">import { useFormStatus } from &quot;react-dom&quot;;

function Submit() {
  const status = useFormStatus();
  return &lt;button disabled={status.pending}&gt;{status.pending ? &#39;Submitting...&#39; : &#39;Submit&#39;}&lt;/button&gt;;
}

const formAction = async () =&gt; {
  // Simulate a delay of 2 seconds
  await new Promise((resolve) =&gt; setTimeout(resolve, 3000));
}

const FormStatus = () =&gt; {
  return (
    &lt;form action={formAction}&gt;
      &lt;Submit /&gt;
    &lt;/form&gt;
  );
};

export default FormStatus;
</code></pre>
<p>위 코드처럼 <code>useFormStatus.pending</code>을 통해 버튼의 disabled 값과 문구를 변경할 수 있다.</p>
<h4 id="useformstate">useFormState()</h4>
<p><code>useFormState()</code> 역시 form에 관련된 새로운 Hook으로, form 제출 결과에 따라 state를 업데이트 할 수 있다.</p>
<pre><code class="language-js">const [state, formAction] = useFormState(fn, initialState, permalink?);</code></pre>
<ol>
<li><p>fn
form을 제출하거나 버튼을 눌렀을 때 호출되는 함수이다.</p>
</li>
<li><p>initialState
state의 초기값이다. </p>
</li>
<li><p>permalink
선택값으로, URL이나 링크가 들어간다. fn이 서버에서 실행될 경우, permalLink로 리다이렉트 된다.</p>
</li>
</ol>
<pre><code class="language-js">import { useFormState } from &#39;react-dom&#39;;

const FormState = () =&gt; {
    const submitForm = (prevState, queryData) =&gt; {
        const name =  queryData.get(&quot;username&quot;);
        console.log(prevState); // previous form state
        if(name === &#39;john&#39;){
            return {
                success: true,
                text: &quot;Welcome&quot;
            }
        }
        else{
            return {
                success: false,
                text: &quot;Error&quot;
            }
        }
    }
    const [ message, formAction ] = useFormState(submitForm, null)
    return &lt;form action={formAction}&gt;
        &lt;label&gt;Name&lt;/label&gt;
        &lt;input type=&quot;text&quot; name=&quot;username&quot; /&gt;
        &lt;button&gt;Submit&lt;/button&gt;
        {message &amp;&amp; &lt;h1&gt;{message.text}&lt;/h1&gt;}
    &lt;/form&gt;
}

export default FormState;</code></pre>
<p>위 코드에서는 이름이 John일 경우에는 Welcome, 아닐 경우에는 Error 메시지를 반환하게 된다.</p>
<h4 id="useoptimistic">useOptimistic()</h4>
<p><code>useOptimistic()</code>은 비동기 작업이 진행되는 동안 다른 state를 표시할 수 있는 Hook이다.
사용자 경험을 향상키는데 도움이 되며 더 빠른 응답을 제공한다. 이는 서버와 상호 작용해야 하는 애플리케이션에 유용하다.</p>
<pre><code class="language-js">const [ optimisticMessage, addOptimisticMessage] = useOptimistic(state, updatefn)</code></pre>
<p>예를 들어, 응답이 진행되는 동안 사용자에게 즉각적인 응답을 제공하기 위해 state를 표시할 수 있는데, 서버에서 실제 응답이 반환되면 &quot;낙관적&quot; 상태가 해당 응답으로 대체되게 된다.</p>
<p>useOptimistic 요청이 성공할 경우 즉시 UI를 업데이트 하고, 사용자는 작업을 수행하면 낙관적인 결과를 볼 수 있기 때문에 &quot;Optimistic&quot;이라는 이름을 갖고 있다.</p>
<pre><code class="language-js">import { useOptimistic, useState } from &quot;react&quot;;

const Optimistic = () =&gt; {
  const [messages, setMessages] = useState([
    { text: &quot;Hey, I am initial!&quot;, sending: false, key: 1 },
  ]);
  const [optimisticMessages, addOptimisticMessage] = useOptimistic(
    messages,
    (state, newMessage) =&gt; [
      ...state,
      {
        text: newMessage,
        sending: true,
      },
    ]
  );

  async function sendFormData(formData) {
    const sentMessage = await fakeDelayAction(formData.get(&quot;message&quot;));
    setMessages((messages) =&gt; [...messages, { text: sentMessage }]);
  }

  async function fakeDelayAction(message) {
    await new Promise((res) =&gt; setTimeout(res, 1000));
    return message;
  }

  const submitData = async (userData) =&gt; {
    addOptimisticMessage(userData.get(&quot;username&quot;));

    await sendFormData(userData);
  };

  return (
    &lt;&gt;
      {optimisticMessages.map((message, index) =&gt; (
        &lt;div key={index}&gt;
          {message.text}
          {!!message.sending &amp;&amp; &lt;small&gt; (Sending...)&lt;/small&gt;}
        &lt;/div&gt;
      ))}
      &lt;form action={submitData}&gt;
        &lt;h1&gt;OptimisticState Hook&lt;/h1&gt;
        &lt;div&gt;
          &lt;label&gt;Username&lt;/label&gt;
          &lt;input type=&quot;text&quot; name=&quot;username&quot; /&gt;
        &lt;/div&gt;
        &lt;button type=&quot;submit&quot;&gt;Submit&lt;/button&gt;
      &lt;/form&gt;
    &lt;/&gt;
  );
};

export default Optimistic;</code></pre>
<p>사용자가 폼에 메시지를 입력하고 전송 버튼을 누르면, useOptimistic Hook은 메시지가 실제로 서버로 전송되기 전에 “Sending..” 라벨이 있는 목록에 메시지가 즉시 나타나도록 한다. 그런 다음 폼은 백그라운드에서 메시지를 실제로 전송하려고 시도하고, 서버가 메시지를 받았음을 확인하면, “Sending…” 라벨이 제거된다. 이러한 접근법은 속도와 반응성의 느낌을 주어 사용자에게 &quot;낙관적인&quot; 효과를 기대할 수 있다.</p>
<p><img src="blob:https://velog.io/5e9e2029-de45-469f-8980-94610b7cb1db" alt="업로드중.."></p>
<hr>
<p>이와 같이, React 19 버전의 변경점들에 대해 알아보았다. 현재 일부 기능들은 react@canary 에서 사용 가능하며, 19 버전의 릴리즈일은 아직 미정이다. 이렇게 새로운 기술들이 나와서 정리할 때마다 느끼는 거지만, 결국 써봐야만 내 기술이 되고 와닿게 되는 것 같다. 그래도 생판 모르는 상태에서 쓰는거보단 정리하고 쓰는게 나으니 이번 블로깅도 만족!</p>
<h3 id="참고-자료">참고 자료</h3>
<blockquote>
</blockquote>
<p><a href="https://www.freecodecamp.org/news/new-react-19-features/">https://www.freecodecamp.org/news/new-react-19-features/</a> (Thanks, Neha😊)
<a href="https://www.frontoverflow.com/magazine/5/%EB%A6%AC%EC%95%A1%ED%8A%B8%20%EC%BB%B4%ED%8C%8C%EC%9D%BC%EB%9F%AC%20(React%20Compiler)">https://www.frontoverflow.com/magazine/5/%EB%A6%AC%EC%95%A1%ED%8A%B8%20%EC%BB%B4%ED%8C%8C%EC%9D%BC%EB%9F%AC%20(React%20Compiler)</a>
<a href="https://ko.react.dev/reference/react/useOptimistic">https://ko.react.dev/reference/react/useOptimistic</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[다시 정리하는 소셜 로그인]]></title>
            <link>https://velog.io/@hang_kem_0531/%EB%8B%A4%EC%8B%9C-%EC%A0%95%EB%A6%AC%ED%95%98%EB%8A%94-%EC%86%8C%EC%85%9C-%EB%A1%9C%EA%B7%B8%EC%9D%B8</link>
            <guid>https://velog.io/@hang_kem_0531/%EB%8B%A4%EC%8B%9C-%EC%A0%95%EB%A6%AC%ED%95%98%EB%8A%94-%EC%86%8C%EC%85%9C-%EB%A1%9C%EA%B7%B8%EC%9D%B8</guid>
            <pubDate>Tue, 16 Apr 2024 04:53:55 GMT</pubDate>
            <description><![CDATA[<p>안녕하세요 오랜만입니다
다들 잘 지내셨나요?
저는 잘 지냈읍니다.. <del>(사실 잘 못지냄)</del>
그동안 개인적으로도 공적으로도 많은 일들이 있었고
이제는 좀 안정이 되어서 다시 블로그를 써볼까 합니다</p>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/0cf3e86a-45d8-421d-a1dd-4b8f6ded405b/image.png" alt=""></p>
<p>회사가 5월부터 유저 대상으로 하는 프로젝트를 기획하고 있고,
원래 기존 내부유저들만 사용하는 일반 로그인에서
소셜 로그인으로 기능을 확장하고자 해서
이번 기회에 소셜 로그인을 프론트에서 어떻게 처리하는지 정리해보려고 한다.</p>
<p>사실 소셜 로그인 기능은 부트캠프 프로젝트 당시에 사용해 본 기억이 있긴한데,
그때는 그냥 동작 방식이나 이런것들은 뭣도 모르고 스윽 붙여넣기만 했기 때문에
이번에는 동작 기능을 확실히 알고 어떻게 사용하는지 잘 정리해두는 것이 중요함!</p>
<h2 id="소셜-로그인">소셜 로그인</h2>
<p>그럼 소셜 로그인이 무엇인지부터 알아보도록 하자.</p>
<blockquote>
<h4 id="소셜-로그인이란">소셜 로그인이란?</h4>
<p>소셜 로그인이란, 국내외 대표 포털 사이트, 소셜 미디어 등의 계정 정보를 이용해 다른 여러 인터넷 서비스를 로그인 또는 회원가입할 수 있는 기능을 말합니다. (출처 - <a href="https://imweb.me/faq?mode=view&amp;category=29&amp;category2=47&amp;idx=49006">https://imweb.me/faq?mode=view&amp;category=29&amp;category2=47&amp;idx=49006</a>)</p>
</blockquote>
<p>소셜 로그인이란, 말 그대로 다른 소셜 미디어의 정보를 통해서 그 소셜 미디어의 사이트가 아닌 다른 인터넷 사이트 및 서비스에 로그인 및 회원가입을 할 수 있는 기능을 뜻한다. </p>
<p>기존의 프론트엔드 및 백엔드에서 자체적으로 구현하는 로그인 기능은,</p>
<ol>
<li><p>[프론트엔드] ID와 비밀번호를 준다.</p>
</li>
<li><p>[백엔드] ID와 비밀번호를 검증하고 <strong>AccessToken과 RefreshToken, AccessToken의 만료시간</strong>을 반환해준다. 이 때 생성한 RefreshToken은 DB에 {ID,RefreshToken}으로 저장한다.</p>
</li>
<li><p>[프론트엔드] 반환받은 AccessToken을 매 api 호출마다 헤더에 붙여서 전송한다.</p>
</li>
<li><p>[백엔드] api호출시 헤더의 AccessToken을 확인하고 유효한지, 만료기간이 지났는지를 체크 후 api를 동작시킨다.</p>
</li>
<li><p>[프론트엔드] AccessToken의 만료 기간이 지나거나, 30초 미만으로 남았다면, 백엔드에 RefreshToken을 붙여 Reissue 요청을 보낸다.</p>
</li>
<li><p>[백엔드] Reissue요청이 들어올 경우, RefreshToken이 DB에 있는 것인지 확인한 후, 맞다면 AccessToken과 새로운 AccessToken 만료 시간을 반환한다.</p>
</li>
<li><p>[프론트엔드] Reissue결과 반환된 AccessToken과 만료기간을 저장하여 다음 api호출에 사용한다.</p>
</li>
</ol>
<p>와 같은 방식으로 동작되는데, 이러한 방식으로 동작할 경우 해당 사이트 내에서만 ID와 비밀번호를 검증하여 자체적으로만 사용 가능한 ID와 비밀번호를 가지게 된다. 또한, 백엔드가 별도의 사용자 데이터베이스를 구축해야 하며, 비밀번호 관리 및 보안 업데이트와 같은 기능들에 대한 리소스도 필요하다.</p>
<p>이러한 불편함들을 줄이고자 등장한 것이 바로 소셜 로그인이다.</p>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/c8f4b005-94f6-4f63-86e0-ad1841087cef/image.png" alt=""></p>
<p>여러분들 다 사이트 사용하시면 한번쯤은 이런 화면 봤죠?
저렇게 구글, 카카오, 네이버와 같은 웹에서 
기존의 소셜 미디어 계정을 통해 로그인을 할 수 있게 제공하는 기능을 소셜 로그인이라고 한답니다.</p>
<h2 id="소셜-로그인-절차">소셜 로그인 절차</h2>
<p>우선, 제일 많은 사람들이 사용하는 카카오 소셜 로그인을 통해 동작 과정을 알아보자.</p>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/7bbe37e2-a1c1-4356-99de-15787d75e956/image.png" alt=""></p>
<p>Kakao Developers에 나와있는 소셜 로그인 절차이다.</p>
<h3 id="1-카카오-developers에서-설정하기">1. 카카오 Developers에서 설정하기</h3>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/8c5924a7-5345-4853-949c-8214c2d8e880/image.png" alt=""></p>
<p>우선 내 애플리케이션을 등록한 뒤,</p>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/c9b02e96-6c3b-40b8-ba36-e4c5ea2e5d91/image.png" alt=""></p>
<p>내 애플리케이션 &gt; 앱 설정 &gt; 플랫폼에 들어가서 사이트 도메인을 등록해준다. </p>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/7460eb32-d904-4e3f-a9f3-27a87fe4ff1c/image.png" alt=""></p>
<p>그런 다음 카카오 로그인에서 Redirect URI도 등록해주어야 하는데, Redirect URI란 OAuth 인증 프로세스에서 사용되는 중요한 요소로 사용자가 로그인을 통해 OAuth 인증이 성공했을 경우 (로그인 성공) 액세스 토큰과 함께 이동하게 될 URI를 뜻한다. 즉, 로그인 성공 후 나타날 페이지 주소이다.</p>
<p>사용자 앱 키 역시 저장을 해두어야 하는데,</p>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/6e72a470-7cb4-4344-97ed-a4264869a6c6/image.png" alt=""></p>
<p>이때 해당 앱 키들은 반드시 .env 파일에 저장해두어야 한다. 왜냐구요? 만일 앱키를 .env 파일에 저장하지 않아서 깃에 올라가기라도 한다면 여러분의 사이트는 해킹을 당하거나, 디도스를 당하거나 <del>(물론 사이트가 흥한다는 가정하에)</del> 여러 가지 보안 이슈들이 생길 수 있기 때문이다.</p>
<p>이와 같은 과정들을 다 하고 나면~</p>
<p><code>https://kauth.kakao.com/oauth/authorize?client_id=${REST_API_KEY}&amp;redirect_uri=${REDIRECT_URI}&amp;response_type=code</code></p>
<p>와 같은 카카오 URL를 얻을 수 있다!</p>
<h3 id="2-코드-작성">2. 코드 작성</h3>
<p>이제 프론트엔드 개발자는</p>
<pre><code class="language-js">const REST_API_KEY = `사용자 REST API KEY`; 
const REDIRECT_URI = `리다이렉트 URI`;
const kakaoURL = `https://kauth.kakao.com/oauth/authorize?client_id=${REST_API_KEY}&amp;redirect_uri=${REDIRECT_URI}&amp;response_type=code`;

const Login = () =&gt; {


   return(
    &lt;div&gt;
     &lt;button onClick={() =&gt; {window.location.href = kakaoURL}} /&gt;
    &lt;/div&gt;

   )


  };</code></pre>
<p>와 같은 방식으로 카카오 URL로 이동할 수 있는 버튼을 만들어주면 된다.</p>
<p>카카오 URL로 이동해 로그인에 성공하면, 앞서 입력한 Redirect URI로 이동하게 되는데, 이때 이 URI 뒤에 인가코드가 <code>/oauth?code=인가코드머시기</code>와 같은 형식으로 붙어있게 된다. 이 코드를 백엔드에 보내주어야만 accessToken과 같은 토큰들을 받아올 수 있기 때문에 프론트엔드 개발자는 다시 이 코드를 떼내서 백엔드 개발자에게 쏴주어야 한다. 방식은 여러분들이 편한대로!</p>
<h3 id="3-토큰-받아오기">3. 토큰 받아오기</h3>
<p>다시 백엔드 개발자가 accessToken을 받아 주면, 또 그 토큰을 저장해두었다가 api 요청을 보낼때마다 헤더에 붙여서 다시 백엔드 개발자에게 넘겨주면 된다. 정말 간단하잖아!</p>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/31c729d8-3bfa-4df0-9515-847003e456d4/image.png" alt=""></p>
<p>이렇게 카카오 로그인을 통해서 소셜 로그인의 동작 과정에 대해 알아보았는데,
다시 한번 요약해서 설명하자면</p>
<blockquote>
<ol>
<li>카카오 로그인 버튼 클릭해서 로그인 창 띄우기</li>
<li>로그인 성공 후 카카오가 보내준 인가코드 받아오기</li>
<li>백엔드한테 다시 인가코드 쏴주기</li>
<li>백엔드가 인가코드 받고 돌려준 JWT 토큰 저장하기</li>
<li>JWT 토큰 저장 후 API 요청할때마다 붙여주기</li>
</ol>
</blockquote>
<p>와 같은 방식이다. 다른 소셜 로그인들은 사용해보지 않았지만, 아마 구글이나 네이버나 메타나.. 이와 똑같은 방식으로 동작할 것이다.</p>
<hr>
<p>막상 정리해보니까 프론트가 소셜 로그인에서 작업해야 할 건 그리 많지 않은 것 같군. 그래도 뭣모르고 썼던 지난 프로젝트들에 비해 이렇게 한번 정리해두니까 이번 작업에서는 확실히 이해하고 잘 사용할 수 있을 것 같다!</p>
<h3 id="참고자료">참고자료</h3>
<p><a href="https://velog.io/@pakxe/React-%EC%A0%95%EB%A7%90-%EC%89%BD%EB%8B%A4-%EC%B9%B4%EC%B9%B4%EC%98%A4-%EC%86%8C%EC%85%9C-%EB%A1%9C%EA%B7%B8%EC%9D%B8-%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%90%EC%84%9C-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B3%A0-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0">https://velog.io/@pakxe/React-%EC%A0%95%EB%A7%90-%EC%89%BD%EB%8B%A4-%EC%B9%B4%EC%B9%B4%EC%98%A4-%EC%86%8C%EC%85%9C-%EB%A1%9C%EA%B7%B8%EC%9D%B8-%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%90%EC%84%9C-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B3%A0-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0</a></p>
<p><a href="https://medium.com/@tellingme/frontend-%EC%B9%B4%EC%B9%B4%EC%98%A4-%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0-%EC%B9%B4%EC%B9%B4%EC%98%A4-%EB%A1%9C%EA%B7%B8%EC%9D%B8%EC%9D%84-%EA%B5%AC%ED%98%84%ED%95%B4%EB%B3%B4%EC%9E%90-by-telling-me-305b3263b374">https://medium.com/@tellingme/frontend-%EC%B9%B4%EC%B9%B4%EC%98%A4-%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0-%EC%B9%B4%EC%B9%B4%EC%98%A4-%EB%A1%9C%EA%B7%B8%EC%9D%B8%EC%9D%84-%EA%B5%AC%ED%98%84%ED%95%B4%EB%B3%B4%EC%9E%90-by-telling-me-305b3263b374</a></p>
<p><a href="https://developers.kakao.com/docs/latest/ko/kakaologin/common">https://developers.kakao.com/docs/latest/ko/kakaologin/common</a></p>
<p>(항상 공부할 때 도움이 되어주는 많은 블로그 작성자분들 사랑합니다..)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[env-cmd]]></title>
            <link>https://velog.io/@hang_kem_0531/env-cmd</link>
            <guid>https://velog.io/@hang_kem_0531/env-cmd</guid>
            <pubDate>Thu, 02 Nov 2023 08:24:34 GMT</pubDate>
            <description><![CDATA[<p><del>(오랜만의 개발 블로그 작성에 대해 반성합니다)</del></p>
<p>우리 회사에서 마참내 리얼서버에 운영 배포를 완료한 뒤, 보안 위험에 따라 개발서버와 운영서버를 별도로 구분지어 작업을 하게 되었다. 그렇기 때문에 작업시에도 사용하는 환경 변수들을 다르게 설정해두어야 하는데, 이때 편리한 작업을 위해 사용한 것이 바로 <code>env-cmd</code>였다.</p>
<h2 id="env-파일이란">.env 파일이란?</h2>
<p>.env 파일이란 <strong>env</strong>ironment, 말 그대로 환경 변수를 저장하는 파일이다. 개발을 하다보면 포트라던지, DB 관련 정보라던지, API_KEY 라던지.. 오픈소스에는 올리면 안되지만 협업하는 동료나 혹은 개인적으로 공유해야 하는 값들이 있다. 이러한 값들을 환경변수 파일로 외부에 만들어 불러와 사용하는 것이 바로 .env 파일이다.</p>
<p>이때 변수명은 반드시 <code>REACT＿APP＿</code>으로 시작되어야 하며, .gitignore에 등록하여 외부에 노출되지 않도록 해야 한다.</p>
<h3 id="env-파일의-종류">env 파일의 종류</h3>
<pre><code>env: 기본.
env.local: 로컬 override. 이 파일은 test를 제외한 모든 환경에 대해 로드
env.development, .env.test, .env.production: 환경별 설정
env.development.local, .env.test.local, .env.production.local: 환경별 설정의 로컬 override.</code></pre><p>해당 파일들은 script 명령어에 따른 우선순위를 갖게 된다.</p>
<pre><code>npm start 

.env.development.local
.env.development
.env.local
.env

npm run build 

.env.production.local
.env.production
.env.local
.env

npm test 

.env.test.local
.env.test
.env
</code></pre><p>으잉? 이러면 .env.development를 build 하고 싶어도 <code>npm run build</code> 를 실행하면 CRA가 정해놓은 우선순위에 의해서 자동적으로 .env.production.local이 build되게 된다.</p>
<h2 id="env-cmd">env-cmd</h2>
<p>이때 사용하는 것이 바로 env-cmd이다 (두둥탁). env-cmd 라이브러리를 사용하면 CRA가 정해놓은 우선순위를 무시해버리고 내가 원하는 환경변수를 바라보게 할 수 있다.</p>
<p>CRA는 내부적으로 <code>dotenv</code>라는 라이브러리를 사용하는데, 만일 스크립트 실행시에 환경변수 이름을 직접 넣어주게 되면 CRA가 설정한 우선순위보다 더 우선적으로 이를 실행하게 된다. env-cmd 라이브러리는 이와 같이 환경변수 이름을 모두 나열하는 과정을 생략하고, 해당 환경변수 파일 자체를 우선순위 맨 위로 올려주게 되는 원리인 것이다.</p>
<h2 id="how-to-use">How to Use</h2>
<p>우선 설치를 해준다.</p>
<pre><code>npm i env-cmd
yarn add env-cmd</code></pre><p>그런 다음 각각의 env 파일을 만들어주고 (회사에서는 현재 local과 development만 사용중)</p>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/9c1f6785-4ed4-49a5-890e-dc806ffd6f53/image.png" alt=""></p>
<p>package.json에서 분기별로 나누어주기만 하면 된다.</p>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/3fccbea6-3f75-4dca-a7fe-c8a9914c846b/image.png" alt=""></p>
<p>정말 간단하군! 우하하!</p>
<hr>
<p>최근 개발 블로그를 엄청나게 안쓰고 쭉 밀렸었는데, 변명아닌 변명을 하자면 실무에 투입되어서 정신없이 일을 하다보니 개발블로그를 쓸 시간도 없었거니와 단순히 내가 배운 내용들을 블로그에 어거지로 쓰는건 의미가 없을 것 같아서 많이 밀렸다. 오늘 이 라이브러리를 사용하면서 아 이건 좀 정리해둘만한데? 싶어서 오랜만에 개발 블로그를 작성했는데, 초심으로 돌아가서 앞으로 배우면서 신기하거나 좀 정리가 필요한 스택들은 꼭 개발블로그를 작성하는 습관을 다시 들여야겠다!</p>
<h2 id="참고-문서">참고 문서</h2>
<p><a href="https://minhanpark.github.io/%EC%8B%9C%EB%A6%AC%EC%A6%88/why-use-env-cmd/">https://minhanpark.github.io/%EC%8B%9C%EB%A6%AC%EC%A6%88/why-use-env-cmd/</a>
<a href="https://velog.io/@leehaeun0/CRA-%ED%99%98%EA%B2%BD%EC%97%90%EC%84%9C-%EB%B0%B0%ED%8F%AC-%EC%8B%9C-.env-%EB%B6%84%EA%B8%B0%ED%95%98%EA%B8%B0-feat.-env-cmd">https://velog.io/@leehaeun0/CRA-%ED%99%98%EA%B2%BD%EC%97%90%EC%84%9C-%EB%B0%B0%ED%8F%AC-%EC%8B%9C-.env-%EB%B6%84%EA%B8%B0%ED%95%98%EA%B8%B0-feat.-env-cmd</a>
<a href="https://medium.com/kangtaehun-io-devtory/cra-env-%EC%A7%80%EC%A0%95%ED%95%B4%EC%84%9C-%EC%93%B0%EB%8A%94%EB%B2%95-env-cmd-27c0dd05a106">https://medium.com/kangtaehun-io-devtory/cra-env-%EC%A7%80%EC%A0%95%ED%95%B4%EC%84%9C-%EC%93%B0%EB%8A%94%EB%B2%95-env-cmd-27c0dd05a106</a>
<a href="https://www.kyulabs.app/b23c2dd5-c4d2-46d2-86ac-7adbf751ea50!%5B%5D(https://velog.velcdn.com/images/hang_kem_0531/post/e788e21f-23a5-4cbc-85b0-506e62c75ad3/image.png)">https://www.kyulabs.app/b23c2dd5-c4d2-46d2-86ac-7adbf751ea50![](https://velog.velcdn.com/images/hang_kem_0531/post/e788e21f-23a5-4cbc-85b0-506e62c75ad3/image.png)</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Redux: 뜻밖의 상태 관리 여정]]></title>
            <link>https://velog.io/@hang_kem_0531/Redux-%EB%9C%BB%EB%B0%96%EC%9D%98-%EC%83%81%ED%83%9C-%EA%B4%80%EB%A6%AC-%EC%97%AC%EC%A0%95</link>
            <guid>https://velog.io/@hang_kem_0531/Redux-%EB%9C%BB%EB%B0%96%EC%9D%98-%EC%83%81%ED%83%9C-%EA%B4%80%EB%A6%AC-%EC%97%AC%EC%A0%95</guid>
            <pubDate>Fri, 03 Feb 2023 08:32:22 GMT</pubDate>
            <description><![CDATA[<p>내가 지금으로부터 약 2년 전, 그러니까 제로초님의 NodeBird 강의를 들을 때 부터 리덕스는 높은 벽과 같은 느낌이었다. 왜 쓰는지도 알고, 어떤 형태인지도 알았지만 실제로 사용할때마다 뇌정지가 씨게 와버렸고, 결국 &#39;Redux를 사용할 줄 안다&#39;라고 말할 수는 없는 상태가 되어버렸다. 이번에는 기필코..! 리덕스를 정복해보자는 취지 아래 내 좌우명인 &#39;기록하는 지식만이 죽은 지식이 되지 않는다&#39;를 토대로 리덕스를 포스팅해서 정리해보고 내 머릿속에 넣도록 하자.</p>
<h2 id="redux">Redux?</h2>
<p>공식문서에서 리덕스는 <code>자바스크립트 앱을 위한 예측 가능한 상태 컨테이너</code>라고 명시해두고 있다. 난 여기서부터 벌써 이해가 되지 않았다. 상태 컨테이너는 뭐 상태 관리를 위한 라이브러리니까 끄덕끄덕하고 넘어갔지만, &#39;예측 가능한&#39;은 대체 어떤 의미일까? 구글링을 통해 알아보던 중, Stack Overflow에 나랑 같은 궁금증을 가진 사람을 발견했다.</p>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/676f50c7-e017-403b-8a17-a27705662e5a/image.png" alt=""></p>
<blockquote>
<p>리덕스 공식문서에서 리덕스는 &#39;예측 가능한&#39; 상태 컨테이너라고 하는데 &#39;예측 가능한&#39;이 먼지 잘 몰?루겠어용</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/0b2c1231-842a-470a-917b-82e34aab2987/image.png" alt=""></p>
<blockquote>
<p>리덕스가 어떻게 동작되는지 먼저 알아야 합니다
<br></p>
</blockquote>
<ol>
<li>state는 불변의 객체이다.</li>
<li>state는 직접 변경되지 않고 새로운, 수정된 state를 반환한다.</li>
<li>모든 state 변경은 action을 통해 일어난다.</li>
<li>리듀서는 현재 state와 action을 통해 새 state를 반환한다.<br>
이 모든 것들은 state -> action -> reducer -> state -> action -> reducer -> state... 와 같은 단방향으로 이루어지고, 순수 함수(side effect가 없고 같은 input에서 나온 output이 항상 같음)를 장려하며 개발자의 명시 아래에서 state 변경이 이루어지기 때문에 '예측이 가능'해 집니다.
고로, 리덕스를 사용하면 애플리케이션의 모든 작업이 어떻게 수행되고 상태가 어떻게 변경되는지 알 수 있기 때문에 예측 가능한 상태 컨테이너라고 명시해 둔 겁니다!

</li>
</ol>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/a4651bd2-2c04-48cd-96f1-564903504f7c/image.png" alt=""></p>
<p>아하! 리덕스에서의 상태 변경은 단방향으로 이루어지고, 개발자의 명시 아래에서만 이루어지기 때문에 상태 변경이 예측 가능한 것이구나! 고마워요 외쿡 개발자맨!</p>
<p>추가적으로 Flux와 MVC 아키텍쳐의 차이에 대해 알아두면 위 질문의 추가적인 해답이 된다.</p>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/11b79f5d-1142-4fbc-b806-934cf99b76ec/image.png" alt=""></p>
<p>Flux가 적용되기 이전에는, 대부분의 애플리케이션은 MVC 아키텍쳐를 채택하였었다. MVC 아키텍쳐에서 <code>Controller</code>는 <code>Model</code>에 정의된 데이터를 <strong>조회</strong>하거나 <strong>업데이트</strong> 하는 역할을 하며, 변경된 모델을 <code>View</code>에 반영해주었다. 또한 사용자는 <code>View</code>를 통해 데이터를 입력하고 <code>Model</code>에 반영되며, <code>View</code>와 <code>Model</code>은 데이터를 양방향으로 주고받는 형태의 아키텍쳐였다. 위와 같이 심플한 구조일 경우에는 아주 알잘딱하고 이쁜 아키텍쳐이다.</p>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/9c5b32a9-d2d2-4757-86ac-cb02ba3315e7/image.png" alt=""></p>
<p>킹치만 이렇게 프로젝트 구조가 커지게 되면? 으악! 거의 스파이더맨 웹스윙이 가능할 것 같은 복잡한 거미줄 구조가 되어버린다. 이러면 데이터의 흐름을 파악하기가 어려워 질 뿐더러, 새 기능을 추가할 때마다 크고 작은 문제가 생기고 사이드 이펙트가 생기게 된다.</p>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/143e5456-d931-4a2b-a6eb-d185e006310f/image.png" alt=""></p>
<p>우리의 킹-갓 페이스북 개발자들은 위와 같은 문제들을 해결하기 위해 기존의 MVC 아키텍쳐를 갖다 버려버리고, Flux라는 새로운 아키텍쳐를 개발하게 된다. Flux 패턴은 <code>Action</code>이 발생하면 <code>dispatcher</code>에 의해 <code>store</code>에 변경된 사항이 저장되고 그 저장된 데이터들에 의해 <code>View</code>가 변경되는 단방향 패턴이다. MVC 아키텍쳐와 다르게 데이터가 단방향으로 흐르기 때문에, 흐름을 훨씬 파악하기 쉽고 <strong>예측이 가능</strong>하게 된다.</p>
<p>이렇게 Flux 패턴이 등장하게 되자, 많은 개발자들이 이 Flux 패턴을 적용한 구현체들을 개발하기 시작했는데, Dan Abramov라는 개발자 형님이 이 패턴을 적용하여 리덕스를 개발하게 된다. 이는 Flux를 더 단순화시켜 사용을 간편하게 했기 때문에 Flux를 개발한 페북 개발자들한테도 기립박수를 받았다는 카더라가 있다.</p>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/7686a984-dc5d-49d8-a941-0b47d7299c80/image.png" alt="">
<em>멋진 남자 Dan Abramov <del>(욱일기 아님)</del></em></p>
<p>무튼 이렇게 리덕스는 상태 관리를 위해 만들어진 라이브러리이다. 그렇다면 왜 상태관리가 필요한 것일까? 근본적인 질문에서부터 들어가보자.</p>
<h3 id="상태-관리-도구state-management-tools의-필요성">상태 관리 도구(State Management Tools)의 필요성</h3>
<blockquote>
<p>서울에 사는 김행갬씨는 어느 날 출근을 하던 중, 버스에 치여 이세카이에 떨어지게 되었다. 이세카이에서는 김행갬이 마왕성에 납치된 공주님을 구할 수 있는 유일한 용사님이었지만 마왕을 잡기 위해서는 고대로부터 내려오는 상태검을 통해서만 잡을 수 있었다. <br>
&quot; 검은 어떻게 구할 수 있죠? &quot;
<br>
&quot; 아, 검은 고대의 용사님들을 통해서 내려오는 거기 때문에 직접 전달을 받아야 하는데 1대 용사님이 2대 용사님에게, 2대 용사님이 3대 용사님에게 ... 결국 지금의 용사님인 4만 8천번째 용사님에게 오려면 100년이 걸립니다.&quot;
<br>
결국 행갬은 127세가 되어서야 검을 받을 수 있었고, 행갬은 검을 들자마자 그 무게때문에 심장에 무리가 와서 마왕의 얼굴을 보지도 못한 채 다시 이세카이에서 현생으로 돌아와 이세카이는 멸망하고 말았다. 끗</p>
</blockquote>
<p>만약 상태검이 용사에서 용사로 직접 전달되는 방식이 아니라, 마을 무기 창고에 보관되어 있었다면 행갬은 마왕의 목을 따버리고 무사히 공주님을 구출할 수 있었을 것이다. 그리고 만약 중간 기수의 용사들이 용사가 되기를 포기하고 농부나 광부의 삶을 살기로 선택했어도 그들은 검을 가지고 있을 필요가 없음에도 후대 용사들에게 상태검을 전해주기 위해 검을 가지고 있었어야 했을 것이다. </p>
<p>이렇듯, 위에서부터 아래로 상태를 내려주는 방식은 자식 컴포넌트의 수가 많아질수록 더 복잡해지고 비효율적이며 시간이 길어지게 된다. 이를 <strong>&#39;Props Drilling&#39;</strong> 이슈라고 한다. </p>
<p>이러한 이슈는 전역 상태 저장소가 있고, 어디서든 해당 저장소에 접근하여 상태를 변경할 수 있으면 해결되는 이슈인데 바로 이 상태 관리를 도와주는 라이브러리가 <strong>Redux</strong>인 것이다.</p>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/e2312020-50cc-4afc-a31a-51a6b49bc9d6/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/67c32436-d336-436d-8f65-45e5a7691eec/image.png" alt=""></p>
<p>상태 관리 라이브러리에는 여러 가지가 있는데, 리덕스는 이들 중에서 당당히 원탑을 차지하고 있다. 근데 중간에 다운로드가 떡락한 기점이 한번 있는데, 그건 바로 작년 크리스마스이다. 개붕이들이 모두 아싸 코스프레를 하지만 결국 그들도 크리스마스에는 나가서 여친과 노는 인싸들이었던 것이다. </p>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/3fe9d709-0510-4e49-a697-b80148b78f9e/image.png" alt=""></p>
<h2 id="redux의-3가지-원칙">Redux의 3가지 원칙</h2>
<p>리덕스에는 3가지 원칙이 있다. 한번 알아보자.</p>
<ol>
<li><strong>Single source of truth : 하나의 어플리케이션은 하나의 store만 가진다.</strong></li>
</ol>
<p>동일한 데이터는 store라는 하나뿐인 데이터 공간에서 관리된다. 이렇게 하면 애플리케이션의 디버깅이 쉬워지고 서버와의 직렬화가 가능하며 클라이언트에서 데이터를 쉽게 받아 들여올 수 있다.</p>
<ol start="2">
<li><strong>State is read-only : 상태는 읽기 전용이다.</strong></li>
</ol>
<p>state를 직접 변경해서는 안되며 state의 변경은 reducer에서만 할 수 있다.  reducer 이외 공간에서의 state는 읽기 전용인 것이다. 이것이 바로 데이터의 단방향 흐름의 이점으로 상태를 변화시키는 의도를 정확히 표현할 수 있으며 상태 변경에 대한 추적이 용이해진다.</p>
<ol start="3">
<li><strong>Changes are made with pure functions : 리듀서는 순수 함수여야 한다.</strong></li>
</ol>
<p>reducer는 순수 함수여야만 한다. reducer 함수는 기존의 state를 직접 변경하지 않고, 새로운 state object를 작성해서 return 해야 한다. 동일한 파라미터로 호출 된 reducer는 순수함수이기 때문에 언제나 같은 결과값만 반환한다.</p>
<p>1번부터 천천히 살펴보자면, 하나의 어플리케이션에서는 state를 관리하는 공간인 store가 하나밖에 없어야 한다는 뜻이다. 아까 Flux 패턴에서 MVC 아키텍쳐와 다르게 단방향으로 데이터의 흐름이 일어난다고 했는데, 만일 store가 여러개이면 단방향이 아니라 양방향이나 여러 갈래로 데이터의 흐름이 꼬이게 되고 이는 곧 사이드 이펙트를 유발하게 된다. 그러므로 하나의 store에서 state를 관리하여 직관적인 데이터 흐름이 일어나도록 해야 한다.</p>
<p>또한, state는 읽기 전용으로 설정되어 state의 직접 변경을 막아야 한다. 즉, state의 변경은 reducer에서 직접 state를 변경하는 것이 아닌 새로운 state object 작성을 통해 return 해야 하며, reducer 이외의 공간에서는 읽기 전용이기 때문에 변경 접근이 불가해야 하는 것이다. 2, 3번과 상통하는 내용이다. 이를 통해 상태 변화의 의도와 상태 변경 추적이 용이해져 <strong>예측 가능한</strong> 상태가 되고, reducer는 순수함수이므로 언제나 같은 결과값만 반환하여 예측 불가능한 결과 값이 나오지 않게 된다.</p>
<hr>
<h2 id="redux-핵심-키워드">Redux 핵심 키워드</h2>
<h3 id="store">Store</h3>
<p>아까부터 계속 나오던 store라는 개념은, 앱의 전체 <strong>상태 트리</strong>를 가지고 있는 저장소이다. 상태 트리란, Redux API에서 저장소에 의해 관리되고 <code>getState()</code>에 의해 반환되는 하나의 상태값을 지칭한다. 컴포넌트에서는 상태 정보가 필요할 때 바로 이 store에 접근하게 된다.</p>
<h3 id="action액션">Action(액션)</h3>
<p>액션은 state를 변화시키려는 의도를 표현하는 객체이다. action은 store에 데이터를 넣는 유일한 방법이며, 모든 데이터는 action으로써 보내지게 된다.</p>
<pre><code class="language-ts">type Action = Object</code></pre>
<p>액션은 어떤 형태의 액션이 행해질지 표시하는 <code>type</code> 필드를 가져야 하며, 다른 모듈에서 import할 수 있다. 그 외의 값들은 개발자 마음대로 넣어줄 수 있다.</p>
<pre><code class="language-ts">{
  type: &quot;ADD_TODO&quot;,
  data: {
    id: 0,
    text: &quot;리덕스 배우기&quot;
  }
}
{
  type: &quot;CHANGE_INPUT&quot;,
  text: &quot;안녕하세요&quot;
}</code></pre>
<h3 id="action-creator액션-생성-함수">Action Creator(액션 생성 함수)</h3>
<p>액션은 단지 동작에 대해 선언된 객체이기 때문에, 컴포넌트에서 이를 더욱 쉽게 발생시키게 하려면 파라미터를 받아와서 액션 객체 형태로 만들어주는 액션 생성 함수를 사용해야 하는 편이 좋다.</p>
<pre><code class="language-ts">export function addTodo(data) {
  return {
    type: &quot;ADD_TODO&quot;,
    data
  };
}

// 화살표 함수로도 만들 수 있습니다.
export const changeInput = text =&gt; ({ 
  type: &quot;CHANGE_INPUT&quot;,
  text
});</code></pre>
<p>리덕스 사용시에 액션 생성함수 사용은 필수적인 것은 아니다. 액션을 발생 시킬때마다 직접 액션 객체를 작성할 수도 있지만, 귀찮으니까 액션 생성 함수를 애용하자!</p>
<h3 id="reducer리듀서">Reducer(리듀서)</h3>
<p>리듀서는 현재 state와 action을 파라미터로 받아 store에 접근하여 action에 맞는 state 변경을 발생시킨다. 앞서 언급했다시피, reducer가 변경하는 state는 직접 변경된 state가 아닌 새로 생성되어 반환된 새로운 state이다. 즉, reducer는 <strong>순수 함수</strong>임을 지켜야 한다.</p>
<pre><code class="language-js">function reducer(state, action) {
  // 상태 업데이트 로직
  return newModifiedState;
}</code></pre>
<p>switch 문을 사용하여 action type에 맞는 case에 따른 state 변경도 가능하다.</p>
<pre><code class="language-js">function counter(state, action) {
  switch (action.type) {
    case &#39;INCREASE&#39;:
      return state + 1;
    case &#39;DECREASE&#39;:
      return state - 1;
    default:
      return state;
  }
}</code></pre>
<p>API 호출은 리듀서 안에 들어가면 안된다. (리듀서는 순수 함수여야 하기 때문에 사이드 이펙트 발생 x)</p>
<h3 id="dispatch디스패치-함수">Dispatch(디스패치 함수)</h3>
<p>디스패치 함수는 액션이나 비동기 액션을 받는 함수로, 액션을 파라미터로 전달하여 호출한 뒤, 스토어가 리듀서 함수를 실행시켜 해당 액션을 처리하게 된다. 즉, 액션을 발생시키는 함수라고 이해하면 된다.</p>
<pre><code class="language-js">&lt;button onClick={()=&gt;{ props.dispatch({ type: &#39;INCREASE&#39;}) }}&gt;+&lt;/button&gt;</code></pre>
<h3 id="subscribe구독">Subscribe(구독)</h3>
<p>Subscribe 함수는, 함수 형태의 값을 파라미터로 받아와 액션이 디스패치 되었을 때 마다 받은 함수가 호출되게 된다. </p>
<h2 id="redux-flow">Redux Flow</h2>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/615999cf-d593-44f2-8ab3-adfbd5b879ad/image.gif" alt=""></p>
<p>이제 구성요소들을 모두 한번씩 훑어봤으니 이들을 조립해서 Redux Flow가 어떤 방식으로 진행되는지 한번 살펴보자!</p>
<p>** 초기 상태 **</p>
<ul>
<li><p>store에서 reducer를 호출하고 return 값을 초기 state에 저장한다.</p>
</li>
<li><p>UI 최초 렌더링 시, store의 state를 렌더링하고, 그 state가 업데이트 되는 것을 subscribe 한다.</p>
</li>
</ul>
<p>** Flow **</p>
<ol>
<li><p>사용자가 Deposit $10 버튼을 클릭한다.</p>
</li>
<li><p>onClick 이벤트에 있는 dispatch 함수를 실행시켜 action을 발생시킨다.</p>
</li>
<li><p>store에서 현재 state와 action을 reducer에 parameter로 전달하고, reducer는 이들을 통해 리턴된 값을 새로운 state로 반환한다. (state 변경)</p>
</li>
<li><p>store에서 subscribe된 UI 컴포넌트들의 업데이트 여부를 확인한다.</p>
</li>
<li><p>store의 데이터가 필요한 컴포넌트들은 state 변경을 확인하고 데이터가 변경된 요소들이 강제 리렌더링되어 화면에 업데이트 된다.</p>
</li>
</ol>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/f671eec2-f4b9-4805-a769-49a37f3cfaf7/image.png" alt=""></p>
<p>이렇게 보니 한눈에 어떻게 Redux가 동작하는지 알기 쉽군!</p>
<hr>
<h2 id="간단한-예제-만들어보기">간단한 예제 만들어보기</h2>
<p>백문이 불여일견, 이제 구성요소도, Flow도 모두 알았으니 직접 간단한 예제를 만들어 보며 머릿속에 완전히 넣어보자구~~</p>
<p>우선 Redux 역시 라이브러리이기 때문에 별도의 설치를 통해 프로젝트에 적용해 주어야 한다.</p>
<pre><code># NPM
npm install redux
npm install react-redux

# Yarn
yarn add redux
yarn add react-redux</code></pre><h3 id="폴더-구조">폴더 구조</h3>
<p>기존의 Redux 구조들은 action 파일들을 따로 action 폴더에, reducer는 또 reducers 폴더에, saga는 saga 폴더에 이렇게 구조 중심적으로 분리해서 작업하고는 했다. 하지만 이렇게 하면, 하나의 기능을 수정하려고 할 때 여러개의 파일들을 수정해야 되기 때문에 번거롭다. 이러한 불편함을 개선하고자 나온 것이 바로 <strong>Ducks 패턴</strong>이다.</p>
<p>Ducks 패턴에서는 기능중심으로 파일을 나누기 때문에, action type, action 생성자, saga, reducer를 같은 기능을 하는 하나의 파일에서 관리한다. 이는 대개 modules라는 폴더에 저장해 놓는다.</p>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/b9fb57ba-267c-4a89-9c26-cb0a43259264/image.png" alt=""></p>
<p>요로코롬! 그러나 Ducks 패턴이 무적권 옳고 정답이다라는 뜻은 아니다. 폴더 구조나 코딩 방식은 항상 개인마다 다르고 회사 컨벤션마다 다르니 이런게 있다더라~ 정도만 알아두고 넘어가도록 하자.</p>
<h3 id="1-action-type-action-creator-생성">1. action type, action creator 생성</h3>
<p>나는 이번 프로젝트에서 나이가 먹으면 철이 들어 얌전해지는 잼민이 친구를 만들어보고자 한다. 우선 액션을 만들어 주도록 하자.</p>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/a2aec8ce-8c68-4e67-a41d-b2363b7946df/image.png" alt=""></p>
<p>각각의 액션과 액션 생성자 함수를 만들어주자. 이때 action 함수는 나중에 컴포넌트에서 사용되기 때문에 export로 내보내야 한다.</p>
<h3 id="2-reducer-생성">2. reducer 생성</h3>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/c9ffe9c0-3703-474d-85c8-95fa1e8fffbd/image.png" alt=""></p>
<p>이제 각 액션들을 실행시킬 리듀서를 작성한다. 우선 initialState라는 변수에 초깃값을 설정해 두고, 리듀서의 parameter에 초기 state와 액션 객체를 넣어준다. 내가 생각하는 잼민이로써 불릴 수 있는 나이의 시작은 7살이니 초깃값을 7로 설정해 주었다. </p>
<p>그리고 switch 문으로 받은 액션 parameter에 따른 값을 리턴하도록 작성하고 어떠한 액션도 발생하지 않았을 때의 기본 default return 값도 반환한다.</p>
<h3 id="3-combinereducer-생성">3. combineReducer 생성</h3>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/029f547b-35e0-433f-8624-2bad3143b978/image.png" alt=""></p>
<p>여러 리듀서들을 하나 하나 연결하는 것은 몹시 비효율적이므로, 이들을 하나로 합쳐주는 리덕스의 내장 함수인 <code>combineReducer</code>를 생성해준다. 지금은 리듀서가 하나 뿐이지만, 나중에 많아질 경우에는 아주 효율적이다.</p>
<h3 id="4-component-생성">4. Component 생성</h3>
<p>이제 잼민이를 우리의 화면에 나타나게 하기 위해서 컴포넌트를 생성해 주자.</p>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/21153856-ee5f-4c65-9b01-4c7239c67a99/image.png" alt=""></p>
<p>useDispatch는 react-redux에서 제공하는 hook으로, dispatch를 함수에서 사용할 수 있게 해주며, useSelector 역시 리덕스 스토어의 state를 조회하는 hook이다.</p>
<h3 id="5-store-생성">5. Store 생성</h3>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/042dec00-5040-4337-9bfb-b3447116f9e8/image.png" alt=""></p>
<p>createStore를 생성하여 앞서 만든 리듀서를 parameter에 넣어준다. 이때 파라미터는 만들어둔 combineReducer를 넣어주면 되는데.. 오잉? createStore에 줄이 그어져 있다. 자세히 보니 리덕스 툴킷에서 제공하는 configureStore를 쓰는것을 권장한다고 나와있다. 왜 그런지는 아직 툴킷 공부를 안해서 모르겠다만.. 까라면 까야지</p>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/aff62bc7-9f95-46eb-a3dc-ec5c403f0665/image.png" alt=""></p>
<p>configureStore로 바꿔주었다. Provider는 리액트 프로젝트에 store 연동을 쉽게 할 수 있도록 도와주는 컴포넌트이므로, props에 store를 넣어준 뒤 App 컴포넌트를 감싸주었다.</p>
<h3 id="6-실행">6. 실행</h3>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/aaf1bd80-9253-4c38-bb3e-4d954109cfc1/image.gif" alt=""></p>
<p>Ta-da! 정상적으로 애플리케이션이 작동하는 모오습이다.</p>
<hr>
<p>이렇게 기초와 이론, 그리고 실습을 통해서 리덕스를 한번 훑어보았다. 이제 조금씩 리덕스의 사용 방법에 대해서 알것만 같은 기분이 솔솔 든다. 하지만 아직 Redux-saga, Redux-thunk, 그리고 이것들을 쉽게 사용할 수 있게 해주는 Redux-toolkit 과 같은 것들이 더 남아있다. 얘네들은 다음 글에서 알아보도록 하면서 이만 마치도록 하겠다.</p>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/a40db9ae-46f2-4faf-9b1c-6b5cb6f63dba/image.png" alt=""></p>
<h3 id="참고-문서">참고 문서</h3>
<p>리덕스 공식 문서
<a href="https://react.vlpt.us/redux/01-keywords.html">https://react.vlpt.us/redux/01-keywords.html</a>
<a href="https://tech.osci.kr/2022/06/29/%EB%B3%B5%EC%9E%A1%ED%95%98%EA%B3%A0-%EC%96%B4%EB%A0%A4%EC%9A%B4-redux-%EC%A0%81%EC%9D%91%EA%B8%B0/">https://tech.osci.kr/2022/06/29/%EB%B3%B5%EC%9E%A1%ED%95%98%EA%B3%A0-%EC%96%B4%EB%A0%A4%EC%9A%B4-redux-%EC%A0%81%EC%9D%91%EA%B8%B0/</a>
<a href="https://ivorycode.tistory.com/entry/Redux%EC%9D%98-%ED%9D%90%EB%A6%84%EA%B3%BC-%EC%98%88%EC%A0%9C">https://ivorycode.tistory.com/entry/Redux%EC%9D%98-%ED%9D%90%EB%A6%84%EA%B3%BC-%EC%98%88%EC%A0%9C</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Axios]]></title>
            <link>https://velog.io/@hang_kem_0531/Axios</link>
            <guid>https://velog.io/@hang_kem_0531/Axios</guid>
            <pubDate>Wed, 01 Feb 2023 06:32:53 GMT</pubDate>
            <description><![CDATA[<p>그동안 백엔드나 서드파티 API에 네트워크 요청을 보낼 때, 나는 <code>fetch</code> 메서드로 HTTP 클라이언트를 사용해서 요청을 전송했었다. 왜? 처음 입문했던게 <code>fetch</code>니깐. 언젠가 Axios를 배우고 이와 관련된 글을 정리하여 내 머릿속에 넣어야겠다는 생각을 하던 찰나에, 이번 회사 웹 리뉴얼 작업에서 Axios를 사용한다고 해서 글을 작성하며 학습해보고자 한다.</p>
<h2 id="axios">Axios?</h2>
<p>Axios는 node.js와 브라우저를 위한 <strong>Promise 기반</strong> HTTP 클라이언트다. HTTP 클라이언트란, HTML 문서와 같은 리소스들을 가져올 수 있도록 해주는 프로토콜인 <strong>HTTP 통신</strong>에서, <strong>요청(Request)</strong>을 보내 서버가 응답(Response)할 수 있도록 하는 단말기(사용자)이다.</p>
<p>HTTP 클라이언트의 다른 예시로는 Fetch API가 있는데, Fetch API는 <code>fetch()</code>라는 메서드를 제공하며 모던 브라우저에 내장되어 있어 따로 설치할 필요가 없다.</p>
<p>반면에 Axios는, 서드파티 라이브러리로 CDN, npm, 혹은 yarn과 같은 패키지 매니저를 통해 설치하여 프로젝트에 추가해야 하며 브라우저 혹은 node.js 환경에서 실행할 수 있다.</p>
<p>Fetch 와 axios는 모두 Promise 기반의 HTTP 클라이언트이므로 이들을 이용해 네트워크 요청을 하면 이행(resolve) 혹은 거부(reject)할 수 있는 promise가 반환된다.</p>
<h3 id="axios의-특징">Axios의 특징</h3>
<ul>
<li><p>브라우저를 위해 <strong>XMLHttpRequests</strong> 생성</p>
</li>
<li><p>node.js를 위해 <strong>http 요청</strong> 생성</p>
</li>
<li><p><strong>Promise API</strong>를 지원</p>
</li>
<li><p>요청 및 응답 인터셉트</p>
</li>
<li><p>요청 및 응답 데이터 변환</p>
</li>
<li><p>요청 취소</p>
</li>
<li><p>JSON 데이터 자동 변환</p>
</li>
<li><p>XSRF를 막기위한 클라이언트 사이드 지원</p>
</li>
</ul>
<blockquote>
<p><strong>XMLHttpRequest</strong>
<code>XMLHttpRequest</code>는 서버와 상호작용(동기 또는 비동기식 요청)을 할 때 사용되며 이를 사용하면 페이지의 새로고침 없이도 URL에서 데이터를 가져올 수 있다. </p>
</blockquote>
<blockquote>
<p><strong>XSRF</strong>
CSRF라고도 하며, 쿠키만으로 인증하는 서비스의 취약점을 이용해, 사용자가 모르게 해당 서비스에 특정 명령을 요청하는 공격이다. Axios는 클라이언트 사이드를 지원해 이 공격을 예방할 수 있다.</p>
</blockquote>
<h3 id="설치">설치</h3>
<p>npm 사용하기
<code>$ npm install axios</code></p>
<p>yarn 사용하기
<code>$ yarn add axios</code></p>
<hr>
<h2 id="기본-예제">기본 예제</h2>
<h3 id="get-요청">GET 요청</h3>
<pre><code class="language-js">const axios = require(&#39;axios&#39;);

// 지정된 ID를 가진 유저에 대한 요청
axios.get(&#39;/user?ID=12345&#39;)
  .then(function (response) {
    // 성공 핸들링
    console.log(response);
  })
  .catch(function (error) {
    // 에러 핸들링
    console.log(error);
  })
  .then(function () {
    // 항상 실행되는 영역
  });

// 선택적으로 위의 요청은 다음과 같이 수행될 수 있다.
axios.get(&#39;/user&#39;, {
    params: {
      ID: 12345
    }
  })
  .then(function (response) {
    console.log(response);
  })
  .catch(function (error) {
    console.log(error);
  })
  .then(function () {
    // 항상 실행되는 영역
  });  

// async/await 사용을 원한다면, 함수 외부에 `async` 키워드를 추가
async function getUser() {
  try {
    const response = await axios.get(&#39;/user?ID=12345&#39;);
    console.log(response);
  } catch (error) {
    console.error(error);
  }
}</code></pre>
<h3 id="post-요청">POST 요청</h3>
<pre><code class="language-js">axios.post(&#39;/user&#39;, {
    firstName: &#39;Fred&#39;,
    lastName: &#39;Flintstone&#39;
  })
  .then(function (response) {
    console.log(response);
  })
  .catch(function (error) {
    console.log(error);
  });

// 여러 동시 POST 요청 생성

function getUserAccount() {
  return axios.get(&#39;/user/12345&#39;);
}

function getUserPermissions() {
  return axios.get(&#39;/user/12345/permissions&#39;);
}

Promise.all([getUserAccount(), getUserPermissions()])
  .then(function (results) {
    const acct = results[0];
    const perm = results[1];
  });</code></pre>
<hr>
<h2 id="axios-api">Axios API</h2>
<p><code>axios</code>에 해당 config를 전송하면 요청이 가능하다.</p>
<p>axios(config)</p>
<pre><code class="language-js">// POST 요청 전송
axios({
  method: &#39;post&#39;,
  url: &#39;/user/12345&#39;,
  data: {
    firstName: &#39;Fred&#39;,
    lastName: &#39;Flintstone&#39;
  }
});

// node.js에서 GET 요청으로 원격 이미지 가져오기
axios({
  method: &#39;get&#39;,
  url: &#39;http://bit.ly/2mTM3nY&#39;,
  responseType: &#39;stream&#39;
})
  .then(function (response) {
    response.data.pipe(fs.createWriteStream(&#39;ada_lovelace.jpg&#39;))
  });</code></pre>
<h3 id="요청-config">요청 Config</h3>
<p>다음은 요청을 만드는 데 사용할 수 있는 config 옵션들이다. 오직 <code>url</code>만 필수이며 method를 지정하지 않으면 <code>GET</code> 방식이 기본값이다.</p>
<pre><code class="language-js">{
  // `url`은 요청에 사용될 서버 URL입니다.
  url: &#39;/user&#39;,

  // `method`는 요청을 생성할때 사용되는 메소드입니다.
  method: &#39;get&#39;, // 기본값

  // `url`이 절대값이 아닌 경우 `baseURL`은 URL 앞에 붙습니다.
  // 상대적인 URL을 인스턴스 메서드에 전달하려면 `baseURL`을 설정하는 것은 편리합니다.
  baseURL: &#39;https://some-domain.com/api&#39;,


  // `transformRequest`는 요청 데이터를 서버로 전송하기 전에 변경할 수 있게 해줍니다.
  // 이것은 &#39;PUT&#39;, &#39;POST&#39;, &#39;PATCH&#39;, &#39;DELETE&#39; 메소드에서만 적용됩니다.
  // 마지막 함수는 Buffer, ArrayBuffer, FormData 또는 Stream의 인스턴스 또는 문자열을 반환해야 합니다.
  // 헤더 객체를 수정할 수 있습니다.
  transformRequest: [function (data, headers) {
    // 데이터를 변환하려는 작업 수행

    return data;
  }],

  // `transformResponse`는 응답 데이터가 then/catch로 전달되기 전에 변경할 수 있게 해줍니다.
  transformResponse: [function (data) {
    // 데이터를 변환하려는 작업 수행

    return data;
  }],

  // `headers`는 사용자 지정 헤더입니다.
  headers: {&#39;X-Requested-With&#39;: &#39;XMLHttpRequest&#39;},

  // `params`은 요청과 함께 전송되는 URL 파라미터입니다.
  // 반드시 일반 객체나 URLSearchParams 객체여야 합니다.
  // 참고: null이나 undefined는 URL에 렌더링되지 않습니다.
  params: {
    ID: 12345
  },

  // `paramsSerializer`는 `params`의 시리얼라이즈를 담당하는 옵션 함수입니다.
  // (예: https://www.npmjs.com/package/qs, http://api.jquery.com/jquery.param/)
  paramsSerializer: function (params) {
    return Qs.stringify(params, {arrayFormat: &#39;brackets&#39;})
  },

  // `data`는 요청 바디로 전송될 데이터입니다.  
  // &#39;PUT&#39;, &#39;POST&#39;, &#39;PATCH&#39;, &#39;DELETE&#39; 메소드에서만 적용 가능합니다.
  // `transformRequest`가 설정되지 않은 경우 다음 타입 중 하나여야 합니다.
  // - string, plain object, ArrayBuffer, ArrayBufferView, URLSearchParams
  // - 브라우저 전용: FormData, File, Blob
  // - Node 전용: Stream, Buffer
  data: {
    firstName: &#39;Fred&#39;
  },

  // 바디로 전송하는 데이터의 대안 문법
  // POST 메소드
  // 키가 아닌 값만 전송됩니다.
  data: &#39;Country=Brasil&amp;City=Belo Horizonte&#39;,

  // `timeout`은 요청이 시간 초과되기 전의 시간(밀리초)을 지정합니다.
  // 요청이 `timeout`보다 오래 걸리면 요청이 중단됩니다.
  timeout: 1000, // 기본값은 `0` (타임아웃 없음)

  // `withCredentials`은 자격 증명을 사용하여 사이트 간 액세스 제어 요청을 해야 하는지 여부를 나타냅니다.
  withCredentials: false, // 기본값

  // `adapter`&#39;은 커스텀 핸들링 요청을 처리할 수 있어 테스트가 쉬워집니다.
  // 유효한 Promise 응답을 반환해야 합니다. (lib/adapters/README.md 참고)
  adapter: function (config) {
    /* ... */
  },

  // `auth`는 HTTP Basic 인증이 사용되며, 자격 증명을 제공합니다.
  // `auth`를 사용하면, `Authorization` 헤더가 설정되어 `headers`를 사용하여 설정한 기존의 `Authorization` 사용자 지정 헤더를 덮어씁니다.
  // 이 파라미터를 통해 HTTP Basic 인증만 구성할 수 있음을 참고하세요.
  // Bearer 토큰 등의 경우 `Authorization` 사용자 지정 헤더를 대신 사용합니다.
  auth: {
    username: &#39;janedoe&#39;,
    password: &#39;s00pers3cret&#39;
  },

  // `responseType`은 서버에서 받는 데이터의 타입입니다.
  // 옵션: &#39;arraybuffer&#39;, &#39;document&#39;, &#39;json&#39;, &#39;text&#39;, &#39;stream&#39;
  // 브라우저 전용: &#39;blob&#39;
  responseType: &#39;json&#39;, // 기본값

  // `responseEncoding`은 응답 디코딩에 사용할 인코딩입니다.
  // Node.js 전용
  // 참고: 클라이언트 사이드 요청 또는 `responseType`이 &#39;stream&#39;이면 무시합니다.
  responseEncoding: &#39;utf8&#39;, // 기본값

  // `xsrfCookieName`은 xsrf 토큰 값으로 사용할 쿠키의 이름입니다.
  xsrfCookieName: &#39;XSRF-TOKEN&#39;, // 기본값

  // `xsrfHeaderName`은 xsrf 토큰 값을 운반하는 HTTP 헤더의 이름입니다.
  xsrfHeaderName: &#39;X-XSRF-TOKEN&#39;, // 기본값

  // `onUploadProgress`는 업로드 진행 이벤트를 처리합니다.
  // 브라우저 전용
  onUploadProgress: function (progressEvent) {
    // 업로드 진행 이벤트 작업 수행
  },

  // `onDownloadProgress`는 다운로드로드 진행 이벤트를 처리합니다.
  // 브라우저 전용
  onDownloadProgress: function (progressEvent) {
    // 다운로드 진행 이벤트 작업 수행
  },

  // `maxContentLength`는 node.js에서 허용되는 http 응답 콘텐츠의 최대 크기를 바이트 단위로 정의합니다.
  maxContentLength: 2000,

  // `maxBodyLength`는 허용되는 http 요청 콘텐츠의 최대 크기를 바이트 단위로 정의합니다.
  // Node.js 전용
  maxBodyLength: 2000,

  // `validateStatus`는 지정된 HTTP 응답 상태 코드에 대한 Promise를 이행할지 또는 거부할지 여부를 정의합니다. 
  // 만약 `validateStatus`가 true를 반환하면(또는 &#39;null&#39; 또는 &#39;undefined&#39;으로 설정) Promise는 이행됩니다.
  // 그렇지 않으면, 그 Promise는 거부될 것이다.
  validateStatus: function (status) {
    return status &gt;= 200 &amp;&amp; status &lt; 300; // 기본값
  },

  // `maxRedirects`는 node.js에서 리디렉션 최대값을 정의합니다.
  // 0으로 설정하면 리디렉션되지 않습니다.
  maxRedirects: 5, // 기본값

  // `socketPath`는 node.js에서 사용될 UNIX 소켓을 정의합니다.
  // 예: &#39;/var/run/docker.sock&#39; 도커 데몬에 요청을 보냅니다.
  // 오직 `socketPath` 또는 `proxy`만 지정할 수 있습니다.
  // 둘 다 지정되면 `socketPath`가 사용됩니다.
  socketPath: null, // 기본값

  // `httpAgent`와 `httpsAgent`는 각각 node.js에서 http 및 https 요청을 수행할 때 사용할 사용자 지정 에이전트를 정의합니다.
  // 이렇게 하면 기본적으로 활성화되지 않은 `keepAlive`와 같은 옵션을 추가할 수 있습니다.
  httpAgent: new http.Agent({ keepAlive: true }),
  httpsAgent: new https.Agent({ keepAlive: true }),

  // `proxy`는 프록시 서버의 호스트이름, 포트, 프로토콜을 정의합니다.
  // 기존의 `http_proxy`와 `https_proxy` 환경 변수를 사용하여
  // 프록시를 정의할 수도 있습니다.
  // 프록시 구성에 환경 변수를 사용하는 경우, &#39;no_proxy&#39; 환경 변수를 
  // 쉼표로 구분된 프록시가 되지 않는 도메인 목록으로 정의할 수도 있습니다.
  // `false`를 사용하면 프록시를 사용하지 않고, 환경 변수를 무시합니다.
  // `auth`는 프록시에 연결하는데 HTTP Basic auth를 사용해야 함을 나타내며, 
  // 자격 증명을 제공합니다. 그러면 `Proxy-Authorization` 헤더가 설정되고,
  // `headers`를 사용하여 기존의 `Proxy-Authorization` 사용자 지정 해더를 덮어씁니다.
  // 만약 프록시 서버가 HTTPS를 사용한다면, 프로토콜을 반드시 `https`로 설정해야 합니다.
  proxy: {
    protocol: &#39;https&#39;,
    host: &#39;127.0.0.1&#39;,
    port: 9000,
    auth: {
      username: &#39;mikeymike&#39;,
      password: &#39;rapunz3l&#39;
    }
  },

  // `cancelToken`은 요청을 취소하는 데 사용할 수 있는 취소 토큰을 지정합니다.
  // (자세한 내용은 요청 취소 섹션 참조)
  cancelToken: new CancelToken(function (cancel) {
  }),

  // `decompress`는 응답 바디의 자동 압축 해제 여부를 나타냅니다.
  //  `true`로 설정하면, 압축 해제된 모든 응답에서 &#39;content-encoding&#39; 헤더도 제거됩니다.
  // Node.js 전용 (XHR은 압축 해제할 수 없습니다)
  decompress: true // 기본값

}</code></pre>
<h3 id="응답-스키마">응답 스키마</h3>
<p>요청에 대한 응답은 아래의 정보를 가지고 있다.</p>
<pre><code class="language-js">{
  // `data`는 서버가 제공하는 응답입니다.
  data: {},

  // `status`는 HTTP 상태 코드입니다.
  status: 200,

  // `statusText`는 HTTP 상태 메시지입니다.
  statusText: &#39;OK&#39;,

  // `headers`는 HTTP 헤더입니다.
  // 모든 헤더 이름은 소문자이며, 괄호 표기법을 사용하여 접근할 수 있습니다.
  // 예시: `response.headers[&#39;content-type&#39;]`
  headers: {},

  // `config`는 요청을 위해 `Axios`가 제공하는 구성입니다.
  config: {},

  // `request`는 이번 응답으로 생성된 요청입니다.
  // 이것은 node.js에서 마지막 ClientRequest 인스턴스 입니다.
  // 브라우저에서는 XMLHttpRequest입니다.
  request: {}
}</code></pre>
<h3 id="config-기본값">Config 기본값</h3>
<p><strong>전역 Axios 기본값</strong></p>
<pre><code class="language-js">axios.defaults.baseURL = &#39;https://api.example.com&#39;;
axios.defaults.headers.common[&#39;Authorization&#39;] = AUTH_TOKEN;
axios.defaults.headers.post[&#39;Content-Type&#39;] = &#39;application/x-www-form-urlencoded&#39;;</code></pre>
<p>*<em>커스텀 인스턴스 기본값 *</em></p>
<pre><code class="language-js">// 인스턴스를 생성할때 config 기본값 설정하기
const instance = axios.create({
  baseURL: &#39;https://api.example.com&#39;
});

// 인스턴스를 만든 후 기본값 변경하기
instance.defaults.headers.common[&#39;Authorization&#39;] = AUTH_TOKEN;</code></pre>
<h3 id="에러-핸들링">에러 핸들링</h3>
<pre><code class="language-js">axios.get(&#39;/user/12345&#39;)
  .catch(function (error) {
    if (error.response) {
      // 요청이 전송되었고, 서버는 2xx 외의 상태 코드로 응답했습니다.
      console.log(error.response.data);
      console.log(error.response.status);
      console.log(error.response.headers);
    } else if (error.request) {
      // 요청이 전송되었지만, 응답이 수신되지 않았습니다. 
      // &#39;error.request&#39;는 브라우저에서 XMLHtpRequest 인스턴스이고,
      // node.js에서는 http.ClientRequest 인스턴스입니다.
      console.log(error.request);
    } else {
      // 오류가 발생한 요청을 설정하는 동안 문제가 발생했습니다.
      console.log(&#39;Error&#39;, error.message);
    }
    console.log(error.config);
  });</code></pre>
<h2 id="url-인코딩-바디">URL-인코딩 바디</h2>
<p>보통 Axios는 JSON을 사용한다. <code>application/x-www-form-urlencoded</code> 포맷으로 데이터를 전송하려면, 다음 옵션 중 하나를 사용할 수 있다.</p>
<h4 id="브라우저">브라우저</h4>
<p>브라우저에서는 <code>URLSearchParams</code> API를 사용할 수 있다.</p>
<pre><code class="language-js">const params = new URLSearchParams();
params.append(&#39;param1&#39;, &#39;value1&#39;);
params.append(&#39;param2&#39;, &#39;value2&#39;);
axios.post(&#39;/foo&#39;, params);</code></pre>
<p>대안으로 qs 라이브러리를 사용하여 데이터를 인코딩할 수 있다.</p>
<pre><code class="language-js">import qs from &#39;qs&#39;;
const data = { &#39;bar&#39;: 123 };
const options = {
  method: &#39;POST&#39;,
  headers: { &#39;content-type&#39;: &#39;application/x-www-form-urlencoded&#39; },
  data: qs.stringify(data),
  url,
};
axios(options);</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[FormData 객체]]></title>
            <link>https://velog.io/@hang_kem_0531/Formdata-%EA%B0%9D%EC%B2%B4</link>
            <guid>https://velog.io/@hang_kem_0531/Formdata-%EA%B0%9D%EC%B2%B4</guid>
            <pubDate>Tue, 31 Jan 2023 01:57:53 GMT</pubDate>
            <description><![CDATA[<p>업무 중, 이미지 파일을 서버에 보내주는 작업이 필요했는데 이전 해커톤과 기업협업 때 사용했던 formdata 객체가 명확히 기억이 나지 않아서 다시 한번 정리 후에 작업을 해보려고 한다.</p>
<h2 id="formdata">FormData</h2>
<p>FormData는 ajax로 폼을 쉽게 보내주도록 도와주는 객체이다. 여기서 FormData 객체는 HTML 폼 데이터를 나타낸다. </p>
<blockquote>
<p><strong>HTML form</strong> 
HTML <code>&lt;form&gt;</code> 요소는 정보를 제출하기 위한 대화형 컨트롤을 포함하는 문서 구획을 나타낸다.</p>
</blockquote>
<pre><code class="language-html">&lt;form action=&quot;&quot; method=&quot;get&quot; class=&quot;form-example&quot;&gt;
  &lt;div class=&quot;form-example&quot;&gt;
    &lt;label for=&quot;name&quot;&gt;Enter your name: &lt;/label&gt;
    &lt;input type=&quot;text&quot; name=&quot;name&quot; id=&quot;name&quot; required&gt;
  &lt;/div&gt;
  &lt;div class=&quot;form-example&quot;&gt;
    &lt;label for=&quot;email&quot;&gt;Enter your email: &lt;/label&gt;
    &lt;input type=&quot;email&quot; name=&quot;email&quot; id=&quot;email&quot; required&gt;
  &lt;/div&gt;
  &lt;div class=&quot;form-example&quot;&gt;
    &lt;input type=&quot;submit&quot; value=&quot;Subscribe!&quot;&gt;
  &lt;/div&gt;
&lt;/form&gt;</code></pre>
<form action="" method="get" class="form-example">
  <div class="form-example">
    <label for="name">Enter your name: </label>
    <input type="text" name="name" id="name" required>
  </div>
  <div class="form-example">
    <label for="email">Enter your email: </label>
    <input type="email" name="email" id="email" required>
  </div>
  <div class="form-example">
    <input type="submit" value="Subscribe!">
  </div>
</form> <br>
위 코드에서는 name과 email에 있는 정보가 form 안에 담겨 제출되게 된다.

<pre><code class="language-js">let formData = new FormData([form]);</code></pre>
<p>HTML에 <code>&lt;form&gt;</code> 요소가 있는 경우, 위와 같은 코드를 작성하면 해당 폼 요소의 필드 전체가 자동으로 반영되게 된다. </p>
<p><code>fetch</code> 등의 네트워크 메서드가 <code>FormData</code> 객체를 바디로 받는다는 것이 <code>FormData</code>의 특징이다. 이때 브라우저가 전송하는 HTTP 메시지는 인코딩되고 Content-Type 속성은 <code>multipart/form-data</code>로 지정된 후 전송되게 된다. </p>
<p>이때 서버 관점에서는 <code>FormData</code>를 사용한 방식과 일반 폼 전송 방식에 차이가 없다.</p>
<pre><code class="language-html">&lt;form id=&quot;formElem&quot;&gt;
  &lt;input type=&quot;text&quot; name=&quot;name&quot; value=&quot;Bora&quot;&gt;
  &lt;input type=&quot;text&quot; name=&quot;surname&quot; value=&quot;Lee&quot;&gt;
  &lt;input type=&quot;submit&quot;&gt;
&lt;/form&gt;

&lt;script&gt;
  formElem.onsubmit = async (e) =&gt; {
    e.preventDefault();

    let response = await fetch(&#39;/article/formdata/post/user&#39;, {
      method: &#39;POST&#39;,
      body: new FormData(formElem)
    });

    let result = await response.json();

    alert(result.message);
  };
&lt;/script&gt;</code></pre>
<p>위와 같이 간단한 폼을 전송한다고 가정해봤을 때, formElem이라는 폼 안에 있는 name과 surname의 value는 submit 되었을 때, FormData를 통해 body에 담겨서 서버측으로 전송되게 된다. 서버는 POST 요청을 받아 FormData를 받아 &#39;저장 성공&#39;이라는 응답을 다시 보내준다. </p>
<h2 id="formdata-메서드">FormData 메서드</h2>
<p><code>FormData</code>에 속하는 필드는 아래와 같은 메서드로 수정할 수 있다.</p>
<ul>
<li><code>formData.append(name, value)</code> - 
<code>name</code>과 <code>value</code>를 가진 폼 필드를 추가</li>
<li><code>formData.append(name, blob, fileName)</code> – <code>&lt;input type=&quot;file&quot;&gt;</code>형태의 필드를 추가. 세 번째 인수 fileName은 사용자가 해당 이름을 가진 파일을 폼에 추가한 것처럼 설정해줌</li>
<li><code>formData.delete(name)</code> – <code>name</code>에 해당하는 필드를 삭제</li>
<li><code>formData.get(name)</code> – <code>name</code>에 해당하는 필드의 값을 가져옴</li>
<li><code>formData.has(name)</code> – <code>name</code>에 해당하는 필드가 있으면 <code>true</code>를, 그렇지 않으면 <code>false</code>를 반환</li>
</ul>
<p>폼은 name이 같은 필드 여러 개를 허용하기 때문에 <code>append</code> 메서드를 여러 번 호출해 이름이 같은 필드를 계속 추가해도 문제가 없다.</p>
<p><code>append</code> 메서드 이외에도 <code>set</code>으로도 필드 추가를 할 수 있지만, <code>set</code>을 사용할 경우에는 동일한 이름을 가진 필드를 모두 제거하고 새로운 필드 하나로만 덮어씌워지기 때문에 주의해야 한다.</p>
<h2 id="blob-데이터가-있는-폼-전송하기">Blob 데이터가 있는 폼 전송하기</h2>
<blockquote>
<p><strong>Blob이란?</strong>
Blob은 Binary Large Object의 줄임말로 텍스트 혹은 이진 데이터를 담고 있는 큰 객체이다. 주로 이미지, 오디오, 비디오 등의 다른 타입에 비해 크기가 큰 타입들을 관리할 때 많이 사용하며, 멀티미디어를 저장하는 목적이 있다.</p>
</blockquote>
<p>위와 같은 <code>Blob</code> 객체 역시 <code>fetch</code> 메서드의 <code>body</code> 매개변수에 바로 넘겨줄 수 있지만, 폼에 필드를 추가하고 이미지 이름등의 메타데이터를 같이 실어 넘겨주는 것이 좀 더 편리하다. </p>
<p>서버의 입장에서도 원시 바이너리 데이터를 받는 것 보다 <code>multipart-encoded</code> 폼을 받는 것이 조금 더 적합하다.</p>
<pre><code>&lt;body style=&quot;margin:0&quot;&gt;
  &lt;canvas id=&quot;canvasElem&quot; width=&quot;100&quot; height=&quot;80&quot; style=&quot;border:1px solid&quot;&gt;&lt;/canvas&gt;

  &lt;input type=&quot;button&quot; value=&quot;이미지 전송&quot; onclick=&quot;submit()&quot;&gt;

  &lt;script&gt;
    canvasElem.onmousemove = function(e) {
      let ctx = canvasElem.getContext(&#39;2d&#39;);
      ctx.lineTo(e.clientX, e.clientY);
      ctx.stroke();
    };

    async function submit() {
      let imageBlob = await new Promise(resolve =&gt; canvasElem.toBlob(resolve, &#39;image/png&#39;));

      let formData = new FormData();
      formData.append(&quot;firstName&quot;, &quot;Bora&quot;);
      formData.append(&quot;image&quot;, imageBlob, &quot;image.png&quot;);

      let response = await fetch(&#39;/article/formdata/post/image-form&#39;, {
        method: &#39;POST&#39;,
        body: formData
      });
      let result = await response.json();
      alert(result.message);
    }

  &lt;/script&gt;
&lt;/body&gt;</code></pre><p>위 코드는 <code>&lt;canvas&gt;</code>를 사용해 만든 이미지를 <code>FormData</code>를 사용해 폼 형태로 다른 추가 필드와 함께 전송하는 예시이다. 위 코드에서 <code>formData.append(&quot;image&quot;, imageBlob, &quot;image.png&quot;);</code> 코드를 통해 <code>&lt;input type=&quot;file&quot; name=&quot;image&quot;&gt;</code>에서 <code>image.png</code>라는 파일을 전송하는 것과 같이 canvas의 이미지 파일을 서버로 전송할 수 있다. 즉, 두 번째 인수의 Blob 객체를 첫번째 인수의 key값으로 세 번째 인수의 value의 이름을 가지게 하여 서버에 전송하는 것이다. </p>
<hr>
<p>현재 고민하고 있던 작업은, 모든 input 값의 value와 이미지를 서버에 어떻게 전송하냐였는데, FormData를 활용하여 각각의 input value를 append하여 필드로 추가해 주고, 이미지 역시 Blob 객체로 전환하여 서버로 전송해주면 해결될 것 같다. 역시 한번 정리하는게 머리에 잘들어오는군..</p>
<h3 id="참고-문서">참고 문서</h3>
<p><a href="https://ko.javascript.info/formdata">https://ko.javascript.info/formdata</a>
<a href="https://developer.mozilla.org/ko/docs/Web/HTML/Element/form">https://developer.mozilla.org/ko/docs/Web/HTML/Element/form</a>
<a href="https://medium.com/hcleedev/web-javascript-blob-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0-9140146e87a8">https://medium.com/hcleedev/web-javascript-blob-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0-9140146e87a8</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[TIL] React Native 오류]]></title>
            <link>https://velog.io/@hang_kem_0531/TIL-React-Native-%EC%98%A4%EB%A5%98</link>
            <guid>https://velog.io/@hang_kem_0531/TIL-React-Native-%EC%98%A4%EB%A5%98</guid>
            <pubDate>Thu, 03 Nov 2022 01:13:44 GMT</pubDate>
            <description><![CDATA[<ol>
<li><p>```</p>
</li>
</ol>
<ul>
<li>What went wrong:
Execution failed for task &#39;:app:installDebug&#39;.<blockquote>
<p>com.android.builder.testing.api.DeviceException: No connected devices!</p>
</blockquote>
<pre><code></code></pre></li>
</ul>
<p>안드로이드 스튜디오 버추얼 디바이스가 실행되지 않아서 생긴 문제. </p>
<p>안드로이드 스튜디오에서
Tools &gt; AVD Manager 메뉴로 들어가면 Andriod Virtual Device Manager 창이 열리고 
여기서 디바이스를 더블클릭하여 버츄어 디바이스 실행 후 명령어를 실행하면 정상 작동된다.
XCode로 실행될줄 알았는데 안드로이드는 AVD에서 열어줘야 되는 듯 하다.</p>
<ol start="2">
<li><p>```</p>
<blockquote>
<p>Failed to apply plugin &#39;com.android.internal.application&#39;.
Android Gradle plugin requires Java 11 to run. You are currently using Java 1.8.
You can try some of the following options:</p>
</blockquote>
</li>
</ol>
<ul>
<li>changing the IDE settings.</li>
<li>changing the JAVA_HOME environment variable.</li>
<li>changing <code>org.gradle.java.home</code> in <code>gradle.properties</code>.<pre><code></code></pre></li>
</ul>
<p>현재 Gradle 플러그인을 실행하려면 Java 11버전이 필요하지만, 1.8 버전을 사용하고 있어서 발생되는 에러이다. Java를 재설치 해주고 터미널에서 <code>./gradlew clean</code> 명령어를 실행하니 오류가 해결되었다.</p>
<ol start="3">
<li><pre><code>SDK location not found. Define location with an ANDROID_SDK_ROOT environment variable or by setting the sdk.dir path in your project&#39;s local properties file at &#39;~/android/local.properties&#39;.</code></pre></li>
</ol>
<p>SDK 경로를 찾을 수 없어서 발생하는 에러이다. 프로젝트 내 android 폴더에 <code>local.properties</code> 파일을 생성하고 <code>sdk.dir=/Users/사용자이름/Library/Android/sdk</code>을 추가해주면 오류가 해결된다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Next.js 13이 나와버렸다]]></title>
            <link>https://velog.io/@hang_kem_0531/Next.js-13%EC%9D%B4-%EB%82%98%EC%99%80%EB%B2%84%EB%A0%B8%EB%8B%A4</link>
            <guid>https://velog.io/@hang_kem_0531/Next.js-13%EC%9D%B4-%EB%82%98%EC%99%80%EB%B2%84%EB%A0%B8%EB%8B%A4</guid>
            <pubDate>Sat, 29 Oct 2022 14:27:20 GMT</pubDate>
            <description><![CDATA[<h2 id="nextjs-conf에서-갑툭튀한-nextjs-13">Next.js Conf에서 갑툭튀한 Next.js 13</h2>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/ab4f8ca8-0fa1-4651-b89d-c1cf325ab4ce/image.png" alt=""></p>
<p>애플 갬성.. 이거슨 혁신입니다..</p>
<p>않이 아직 Next.js 12버전도 공부 못했는데 10월 26일에 진행한 Next.js Conf에서 Next.js 13버전을 발표해 버렸다. 공부를 못했는데 새로운 버전이 나왔다? 오히려 좋아.. 어차피 무슨 직업이 안 그렇겠냐만은 개발자는 유독 평생 공부해야 된다는 말이 많기 때문에 겸허히 받아들이고 이번 포스트를 통해 Next.js 13이 이전과 어떤 것들이 달라졌는지 알아보도록 하자.</p>
<blockquote>
<p>이 포스트는 <a href="https://nextjs.org/blog/next-13">Next.js 공식문서</a>와 <a href="https://www.youtube.com/watch?v=5BRFGMs1v_o&amp;t=46s">코딩애플님 유튜브</a>를 참고하여 작성하였습니다!</p>
</blockquote>
<h2 id="변화점">변화점</h2>
<p>Next.js 공식문서에서 설명하고 있는  대표적인 변화점들은 다음과 같다.</p>
<ul>
<li><strong>app/ Directory (beta)</strong> (app/디렉토리): Easier, faster, less client JS. (쉽고, 빠르고, 적은 클라이언트의 JS)<ul>
<li><strong>Layouts</strong> (레이아웃)</li>
<li><strong>React Server Components</strong> (리액트 서버 컴포넌트)</li>
<li><strong>Streaming</strong> (스트리밍)</li>
</ul>
</li>
<li><strong>Turbopack (alpha)</strong>: Up to 700x faster Rust-based Webpack replacement. (700배 빠른 Rust 기반의 웹팩 대체품)</li>
<li><strong>New next/image (stable)</strong>: Faster with native browser lazy loading. (더 빨라진 레이지 로딩)</li>
<li><strong>New @next/font (beta)</strong>: Automatic self-hosted fonts with zero layout shift. (구글 폰트 내장 (!))</li>
<li><strong>Improved next/link</strong>: Simplified API with automatic <code>&lt;a&gt;</code>. (자식 요소로 <code>&lt;a&gt;</code> 태그 불필요)</li>
</ul>
<p>차근 차근 위에서 부터 살펴보도록 하자.</p>
<blockquote>
<p>최신 Next.js를 설치하려면 다음과 같은 명령어를 입력하면 된다.</p>
</blockquote>
<pre><code>npm i next@latest react@latest react-dom@latest eslint-config-next@latest</code></pre><h3 id="app-directory">app/ Directory</h3>
<p>기존의 Next.js에서는 Automatic Routing을 사용할 때, pages 디렉토리 안에 파일을 생성하여 별도의 라우터 없이도 애플리케이션 내부에서 즉시 경로를 생성할 수 있었다. 하지만, Next.js 13버전부터는 <code>app/</code>이라는 새로운 디렉토리가 등장하여 라우팅 및 레이아웃 기능을 개선하였다.</p>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/5f66ce50-0341-43a1-8f1d-550e98252d45/image.png" alt=""></p>
<p>현재는 pages 디렉토리와 app 디렉토리가 공존할 수 있는 베타 버전으로 제공이 되고 있다.</p>
<h4 id="레이아웃">레이아웃</h4>
<p><code>app/</code> 디렉토리는 레이아웃 기능을 제공하는 데, 여러 페이지 간에 네비게이션 바와 같은 공통적인 UI를 공유하여 불필요한 리렌더링을 방지하고, 컴포넌트간의 상호 작용을 구현할 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/970b1d63-b974-4ba7-9090-a379c738d2c8/image.png" alt=""></p>
<p>위에서 설명한 것처럼, Next.js 13의 Routing은 app 디렉토리 안에서 진행되는데 이때, page.js 라는 단일 파일을 생성해야만 사용할 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/c16408c5-5f90-4c20-8ffe-16f775a92b77/image.png" alt=""></p>
<p>page.js 파일만 생성하고 터미널을 실행시키면, 다음과 같은 문구와 함께 Next.js에서 layout.js 파일을 자동으로 생성해 준다. 이 파일이 바로 레이아웃을 적용할 수 있는 파일이다.</p>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/3429bb7d-8d10-441c-9c43-f9167775e9a8/image.png" alt=""></p>
<p>레이아웃 파일의 초기 구조는 위와 같다.</p>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/75276d9e-a13d-4d46-aa14-28466d37194b/image.png" alt=""></p>
<p>레이아웃을 설정하지 않고 page.js 파일을 위와 같이 작성한 뒤 실행하면,</p>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/bb33785a-fd58-4930-944d-8ea87c340ea9/image.png" alt=""></p>
<p>본문의 내용만 나오게 되지만,</p>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/11ad365d-9a5a-465d-a518-3dc2ed9723d8/image.png" alt=""></p>
<p>이렇게 레이아웃을 만들고 실행하게 되면?</p>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/8ac4cd6b-0ace-44b6-8eb1-b0242f6c2255/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/e21fe050-e551-4d56-90b8-89597d32e81b/image.gif" alt=""></p>
<p>이렇게 타이틀과 네비게이션 바가 레이아웃을 통해 적용된 것을 확인할 수 있다. 별 거 아닌거처럼 보이지만 코린이인 나에게는 엄청나게 편해보이고 컬쳐쇼크 그 잡채였다..</p>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/edfed947-47c1-48ae-a685-f5503730a7ce/image.png" alt=""></p>
<p>app 디렉토리 내부에 page2라는 폴더를 만들고 다른 페이지를 라우팅해도 레이아웃은 똑같이 적용된 것을 확인할 수 있다.</p>
<h4 id="리액트-서버-컴포넌트">리액트 서버 컴포넌트</h4>
<p>이건 Next.js의 app 디렉토리에서 리액트 서버 컴포넌트를 사용하여 서버에서만 컴포넌트를 렌더링하고 클라이언트에 전송되는 자바스크립트의 양을 줄여 초기 페이지 로딩을 더 빠르게 수행할 수 있다는 내용인데... 사실 아직 내가 리액트 서버 컴포넌트를 사용해 본 경험이 없어서 추가적으로 더 공부를 하고 정리해야 할 것 같다.</p>
<h4 id="스트리밍">스트리밍</h4>
<p>스트리밍은 app 디렉토리에서 loading.js라는 예약 파일을 생성하여 사용자가 렌더링이 되기 이전에 로딩 중인 화면을 표시할 수 있는 기능이다. 스피너와 같은 로딩 화면들을 구현해 놓으면 아주 간편할 것 같다.</p>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/e98cdcc2-7e6e-4c37-8c8d-21fbd602c2b4/image.png" alt=""></p>
<h3 id="data-fetching">Data Fetching</h3>
<p>이전 Next.js에서는 데이터를 fetching 해 올때, getServerSideProps나 getStaticProps를 통해 데이터를 가져왔지만, Next.js 13부터는 이 문법들을 사용하지 않는다. 사실 이 두 문법이 Next.js를 공부하면서 뭔가 애증의 존재였는데, 조금 더 간편하게 사용할 수 있을 것 같다.</p>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/fcde6720-4702-43ec-8472-5b691d78aeb8/image.png" alt=""></p>
<p>이전에는 위와 같이 getServerSideProps를 통해 data를 fetching 해왔지만, Next.js 13에서부터는 위 문법을 사용하면 </p>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/802e19fb-9387-4398-8adb-809670097b5d/image.png" alt=""></p>
<p>에러가 발생한다. 그러면 어떻게 사용해야 할까?</p>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/ea8fb1ba-acf5-4e52-9430-2ba8ede72358/image.png" alt=""></p>
<p>위와 같이 함수를 선언한 뒤에, use() 안에 넣어주기만 하면 된다. 추가적으로, fetch 구문의 URL 뒤에 <code>{ cache: &#39;&#39; }</code> 를 넣어줄 수 있는데, 안에 무엇이 들어가냐에 따라 기존의 getServerSideProps, getStaticProps와 유사한 기능을 할 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/410255bd-67ab-4987-95b1-18a7b4eb9e1c/image.png" alt=""></p>
<h3 id="turbopack">Turbopack</h3>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/d93ecda6-81cf-4d95-b547-dbb2390532b1/image.png" alt=""></p>
<p>터보팩은 웹 개발을 하려면 Rust 기반의 JS 번들링 툴이 필요한데, 기존의 웹팩보다 700배, Vite보다 10배 빠르게 JavaScript를 필요한 파일로 컴파일해주는 번들링 툴이다. 이것 역시, 아직 그리 와닿지는 않는다. 그리고 이러한 툴들은 안정화 된 뒤에 사용하는 것이 좋다고 한다.</p>
<h3 id="nextimage">next/Image</h3>
<p>Image 같은 경우에는 큰 변화가 없었다. </p>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/7972104f-906e-41c4-bc18-1caa9a7a8e6d/image.png" alt=""></p>
<p>Image 컴포넌트를 통해서 이미지 파일을 넣을 수 있는데, 이를 통해 사용하면 이미지가 자동으로 최적화가 된다. 자동 최적화가 뭐냐면, 이미지 로드가 느릴 경우에 기존의 레이아웃이 밀려나는 현상인 Layout Shift가 발생하는데, 이를 방지하기 위해 이미지의 width와 height를 설정해야만 한다. Next.js 13 버전에서는 이를 자동으로 처리해주기 때문에, 최적화에 유용하다.</p>
<p>또한, 이제 <code>alt</code> 속성을 필요로 하기 때문에 웹 접근성 향상이 되었다.</p>
<h3 id="nextfont">next/font</h3>
<p>font 역시도 자동으로 Layout Shift가 방지되고, 무엇보다 가장 중요한 핵심적인 기능으로 이제 구글 폰트가 내장되어 브라우저에서 구글 폰트 요청을 별도로 하지 않아도 사용할 수 있다!</p>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/71398c5c-946a-4789-8460-41f4b56cd285/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/2f61e94d-3f3c-484b-a7af-43db89343878/image.png" alt=""></p>
<p>사용 방법은 위와 같다. 커스텀 폰트 역시 같은 방법으로 사용할 수 있다. </p>
<h3 id="nextlink">next/link</h3>
<p><code>&lt;Link&gt;</code> 태그를 사용하려면 <code>&lt;a&gt;</code> 태그를 필요로 했던 이전 버전과는 달리, Next.js 13부터는 <code>&lt;a&gt;</code> 태그를 더이상 자식 요소로 필요하지 않는다. 그렇기 때문에 기본 태그에도 props를 전달할 수 있게 되었다.</p>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/37dd470e-c223-4a2a-92c7-5423be648178/image.png" alt=""></p>
<h3 id="og-image">OG Image</h3>
<p>우리가 카카오톡이나 다른 곳에 링크를 첨부하면 링크와 연관된 이미지가 같이 뜨는 것을 확인할 수 있는데, 이를 OG Image라고 한다. </p>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/e5f9f0cb-e5d4-42e4-925a-126ad2ae81b6/image.png" alt=""></p>
<p>이는 콘텐츠 클릭의 참여를 높여주지만, 시간이 오래 걸리고 오류가 발생하기 쉽고 유지하기가 어려웠기 때문에 건너뛰는 경우가 많았다. 하지만 이번 Next.js 13에서 이러한 문제를 개선하기 위해 동적 소셜카드를 새로 생성하는 라이브러리인 <code>@vercel/og</code>를 도입하였다.</p>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/a8447eb7-45c2-4703-8c64-77131670081d/image.png" alt=""></p>
<p>이 접근 방식은 기존의 솔루션보다 무려 5배나 빠르다고 한다.</p>
<h3 id="미들웨어-api-수정">미들웨어 API 수정</h3>
<p>기존의 도입된 미들웨어를 몇 가지 수정하였다고 한다. 우선 request에 대한 header를 더 쉽게 설정할 수 있고,</p>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/9e373bb6-cb5f-4244-a39c-361b9ade9904/image.png" alt=""></p>
<p><code>rewrite</code>나 <code>redirect</code>를 사용할 필요 없이 직접 미들웨어에서 response를 제공할 수도 있게 되었다.</p>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/ff782742-84a6-4c38-97a9-323de9edf3d9/image.png" alt=""></p>
<p>현재 미들웨어에서 response를 제공하려면, next.config.js에 다음과 같은 코드를 추가해주어야만 한다.</p>
<pre><code class="language-js">// next.config.js
const nextConfig = {
    ...
  experimental: {
    allowMiddlewareResponseBody: true
  },
};</code></pre>
<h2 id="마치며">마치며</h2>
<p>사실 개발을 시작한 기간이 그리 길지 않기 때문에, 이렇게 스택이 업데이트되면서 변화하는 것을 본 경험이 처음이다. (사실 react-router-dom이 업데이트 되긴 했지만 업데이트 된 후에 입문해서..) 이번 기회를 통해서 변화점에 대해 알아보았으니 앞으로 사용할 때 유념하여 사용해야 할 것 같다. 그리고 해당 포스트는 아직 Next.js를 숙련도있게 사용해 보지 못한 코린이가 작성했기 때문에 피드백이나 잘못된 정보가 있으면 마음껏 댓글에 써주시길 바란다. 그럼 오늘도 보람찬 학습 기록!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[테오의 스프린트 12기 회고록 - Time Catcher]]></title>
            <link>https://velog.io/@hang_kem_0531/%ED%85%8C%EC%98%A4%EC%9D%98-%EC%8A%A4%ED%94%84%EB%A6%B0%ED%8A%B8-12%EA%B8%B0-%ED%9A%8C%EA%B3%A0%EB%A1%9D-Time-Catcher</link>
            <guid>https://velog.io/@hang_kem_0531/%ED%85%8C%EC%98%A4%EC%9D%98-%EC%8A%A4%ED%94%84%EB%A6%B0%ED%8A%B8-12%EA%B8%B0-%ED%9A%8C%EA%B3%A0%EB%A1%9D-Time-Catcher</guid>
            <pubDate>Sat, 29 Oct 2022 12:09:56 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/0d5f3141-6a48-4f54-ae24-6a3eabe4adda/image.png" alt=""></p>
<blockquote>
<p>실제 배포 사이트는 <a href="https://time-catcher.netlify.app/">효율적인 시간관리 서비스, 타임캣쳐</a> 에서 확인하실 수 있습니다!</p>
</blockquote>
<h2 id="정말-오랜만의-프로젝트야">정말 오랜만의 프로젝트야!</h2>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/ca9bbd48-d35f-4f70-b571-88ba185266c0/image.png" alt=""></p>
<p>취업 준비 기간이 부트캠프 수료 후 3개월이 지나가면서, 나는 점점 몸도 마음도 조급함에 갉아 먹혀 가고 있었다. 정말 질리도록 자바스크립트 관련 서적과 MDN을 읽고, 면접 질문 리스트들도 달달 외워가며 준비했었지만, 백 몇개의 이력서 지원은 결국 친절한 멘트였지만 맛은 씁쓸한 탈락 메일로 돌아올 뿐이었다. 솔직히, 부트캠프 수료식까지만 해서도 나는 내게 스스로 자신이 있었다. 못해도 한달? 그안에 취업을 할 줄 알았고, 또 내 자신이 그만한 가치가 있음에 확신이 있었다. 그러나 반복되는 무한 탈락에 엄청난 번아웃이 찾아 왔었다.</p>
<p>내가 프론트엔드 개발자가 되기로 한 처음, 모 인터넷 강의를 들으며 내가 작성한 코드들이 화면에 UI가 되어 나타나던 그 순간은 아직도 잊지 못한다. 나는 이 순간을 화가가 처음 붓을 들었던 순간에 비유하고는 한다. 또 내가 어린 시절 꿈꿨던 작가와도 일맥상통하는 부분이라고도 생각된다. 결국 내가 작성하거나 만들어 낸 무언가가 그걸 사용하거나 보는 사람에게 편의성, 혹은 과장하여 감동을 주는 것이 내가 오래전부터 꿈꾸던 직업의 목표였다. 그러나, 결국 그것만으로는 세상은 나를 믿고 사용할 순 없었다.</p>
<p>점차 개발자 취업 시장이 얼어가면서, 신입으로 내던져진 내게는 경쟁자들과는 무언가 다른 차별점이 필요했다. 결과적으로, 부트캠프에서 내가 얼마나 만족을 했고 공부를 했던간에 기업들 입장에서는 그저 개발자 유행에 편승하여 공장의 제품처럼 찍혀 나온 양산형 개발자에 불과했으니까. 그래서 무언가 새로운 스택이나 프로젝트를 해보면 어떨까 하는 생각이 있던 찰나에, 평소에 들어가 있었던 테오님의 프론트엔드 오픈 채팅방에서 스프린트 12기를 진행한다는 소식을 듣게 되었다. 이전부터 후기도 칭찬 일색이고, 모르는 분들과 기획부터 배포까지 새로운 마음으로 프로젝트를 진행해보고 싶다는 마음도 있었어서 모집글이 올라오자마자 바로 신청을 했다.</p>
<h2 id="대망의-첫-날-1012">대망의 첫 날 (10/12)</h2>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/a9454067-c276-4c4c-ad49-230070c3e244/image.png" alt=""></p>
<p>첫 날은 게더타운에서 일단 모이고, 디스코드로 진행을 하려 했으나 12기가 역대급으로 사람이 많이 모이게 되어 디스코드가 터지는 사태가 발생하고 (...) 결국 테오님이 회사에서 사용하시는 카카오 워크에서 진행하게 되었다. 테오님이 이때 회사 홍보가 되는거 같아서 이득이라고 하셔서 재밌었던 기억이 난다.</p>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/53c4a582-9a0a-4fb0-acb3-d1bad6fd3db8/image.png" alt=""></p>
<p>첫 날에는 개발에 당장 들어가지 않고, 모두 각자가 준비해 온 아이디어를 공유하며 최종 아이디어가 될 후보군들을 선정하는 작업을 했다. 나는 당일에 면접이 있었어서 디테일하게 준비하지는 못했지만, 이전부터 생각했었던 내일로 여행 계획을 다른 유저들이 대신 짜주는 사이트를 아이디어로 냈었다. 내일로를 약 5번 정도 다녀오고, 매번 갈때마다 여행 계획 짜는것이 내가 MBTI가 J임에도 여간 귀찮은 일이 아니었어서 평소에 이런 사이트가 있으면 좋지 않을까하고 생각했었다. 반응도 나쁘지 않았고, 테오님도 칭찬해주셨으나 5일, 특히 개발은 약 2일간만 진행하기 때문에 실질적으로 구현하기에는 어려움이 있을 거 같아 보류되었다. </p>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/836183c6-f621-4341-93bc-de190b144dd5/image.png" alt=""></p>
<p>그렇게 다른 분의 아이디어를 찾아 헤메던 중, 잼미가 제안한 뽀모도로 타이머가 아주 매력적으로 다가와 뽀모도로 팀에 참가하기로 했다. 평소에 뽀모도로라는 것이 있는지도 몰랐었고, 공부를 할때마다 집중력이 부족해 방황하던 나에게 필요한 아이템이라는 생각이 들어 바로 지원하게 되었다. 운이 좋게 뜻이 맞는 동료분들이 바로 모여서 팀 조정이 필요없이 바로 원트에 팀이 구성되게 되었다.</p>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/e2c2b08d-5de6-4fc8-9c27-45e1040c7981/image.png" alt=""></p>
<p>팀이 구성되고 나서는, 팀 캔버스를 작성하며 간단하게 자기 소개 및 앞으로 팀에서 해야하거나 하지 말아야 할 일들에 대해 서로 상의하였다. 모든 프로젝트가 그렇듯, 가장 중요한 부분이라고 생각된다. 각자 성격의 장, 단점을 미리 파악하는 것은 갈등이나 분쟁을 최소한으로 유지하려면 필수적인 부분이기에 이 팀 캔버스 작성 시간이 아주 좋았었다. 덕분에, 프로젝트가 종료될 때까지 우리 팀은 단 한명의 탈주자나 갈등이 생기지 않았었다.</p>
<p>특히 첫날부터, 새벽 4시까지 팀 캔버스를 작성하고 서로 얘기를 나누는 모습을 보며 &#39;아, 이 팀은 그래도 파토날 일은 없겠구나.&#39;라는 생각을 했었다. 오히려 다들 열정이 넘치는 모습에 내가 열정이 조금 부족한가? 라는 반성을 하기도 했었다.</p>
<h2 id="둘째-날-1013">둘째 날 (10/13)</h2>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/7bbeed67-2166-4623-ae0c-8cec4d926239/image.png" alt=""></p>
<p>둘째 날은 결정된 아이디어를 토대로 구체적인 방향성을 정해 지도를 만드는 걸 진행하였다. </p>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/1970b4bb-e28e-48b5-87e3-dc17f3f331dc/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/98c22329-de95-4411-80a4-ba9848d3a34e/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/24aee2e4-4121-4899-86d3-359e4c8e7975/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/416aa5ed-9eff-4402-b554-4228af1f3fef/image.png" alt=""></p>
<p>우선적으로 우리가 만들 서비스의 궁극적인 목적, 만들 서비스의 대상, 서비스가 추구해야 할 핵심가치에 대해 이야기하고 어떻게 하면 ~ 할 수 있을까에 대한 질문 리스트를 만들어 작업의 디테일을 정할 수 있었다. 이때 구체적으로 우리가 어떤 스택을 사용할 것인지, 우선적으로 개발해야 할 것은 무엇이며 무엇을 우선순위의 나중으로 둘 것인지를 정해 실질적으로 개발하는데에 차질이 없을 수 있었던 것 같다. 그리고 무엇보다 중요한 서비스의 이름을 짓기 위해 각자 아이디어를 냈는데, 내가 낸 아이디어인 &#39;Time Cat&#39;cher&#39;가 당선되어서 기분이 좋았다. </p>
<p>이 과정에서 우리가 생각한 우리 서비스의 핵심 프로세스는</p>
<ul>
<li>타이머</li>
<li>투두리스트</li>
<li>회고 기능</li>
</ul>
<p>정도로 추려졌었다. 둘째날도 마찬가지로 새벽 4시까지의 열띤 토론을 진행하고, 몹시 피곤하지만 뿌듯한 마음으로 잠자리에 들었던 것 같다.</p>
<h2 id="셋째-날-1014">셋째 날 (10/14)</h2>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/567ba854-0a7e-4ce8-9db4-21f1e1296854/image.png" alt=""></p>
<p>셋째 날은 시작하기에 앞서, 말이 통하지 않는 외계인에게 우리의 직업인 개발자를 어떻게 그림으로 표현할 것인가를 아이스 브레이킹으로 진행하였다. 다들 폐인이 되어 컴퓨터 앞에 앉아 있는 모습을 그리는 걸 보면서, 내 미래에 대한 두려움이 살짝 엄습했지만.. 그래도 내가 정한 길이니 후회는 없도록!</p>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/8c62ba1a-c0e2-4778-b63f-860df51ba9fc/image.png" alt=""></p>
<p>아이스 브레이킹 종료 후, 각자 준비한 레퍼런스와 스케치를 통해 대략적으로 우리의 서비스가 어떻게 나타날 것인지를 그리고 가장 투표수가 많은 아이디어를 기본 베이스로 삼아 개발을 하기로 하였다. 여태 진행한 프로젝트들은 기존 사이트의 UI와 유사하게 진행하였던 클론 코딩에 가까운 느낌이었어서 직접 이러한 기획이나 디자인에 참여하는 점이 아주 흥미로웠다.</p>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/7d2c179f-4363-4dbf-89e3-482e6299fb23/image.png" alt=""></p>
<p>그리고 가장 충격을 받았던, 테오가 알려준 BDD, SDD를 진행하였다. BDD는 해당 컴포넌트나 웹을 사용자의 행동을 중심으로 정리를 하는 것, SDD는 개발을 진행하기 전 미리 공통적인 인터페이스와 같은 데이터 구조를 정하여 그것을 토대로 개발을 진행하는 방법론인데 생전 이런것이 존재하는지도 몰랐던 내게 BDD와 SDD는 신대륙을 발견한 콜럼버스의 충격과 같았다. (BDD는 롤 프로게이머인줄로만 알고 있었다..)</p>
<p>이전에 이 방법론들을 알았더라면 프로젝트를 진행할 때 초기 세팅에 있어서 조금 더 수월하지 않았을까 하는 아쉬움이 있었지만, 이제라도 알아서 정말 다행이라는 생각이 들었다. 실제로 BDD와 SDD를 통해 각 컴포넌트들을 정리하니 정말 명확하게 우리가 작업해야 할 것들이 무엇인지 파악할 수 있었다.</p>
<h2 id="지옥의-개발-마라톤-101516">지옥의 개발 마라톤 (10/15~16)</h2>
<p>그렇게 모든 기초세팅들이 끝나고 주말에는 내내 하루 종일 개발을 진행했다. 나는 페어 프로그래밍 동료로 우리 팀의 MC였던 가갸거겨와 같이 개발을 진행했는데, 워낙 성격도 좋으시고 분위기 메이커셔서 개발하는 내내 지루하지 않았던 것 같다.</p>
<p>사실 Live Share와 페어 프로그래밍의 개념은 스프린트를 진행하기 이전에도 어렴풋이 알았지만, 크게 와닿지는 않았었다. 결국 둘이서 하나의 개발을 진행하는 건 시간적으로도 효율성 측면에서도 조금 별로지 않을까 하는 생각이 있었기 때문이다. 그러나 이번 기회로 페어 프로그래밍에 대해서 다시 한번 시각이 바뀌는 계기가 되었다. 우선 누군가가 날 보고 있다는 압박은, 평소에 개발이 되지 않으면 방황하는 내 단점을 한번에 해결해 주었고 또 무언가가 잘 풀리지 않았을 때 집단 지성으로 해결할 수 있기 때문에 오히려 나 스스로 혼자 코드를 짤 때보다 훨씬 빠른 시간내에 많은 것들을 할 수 있었던 것 같다.</p>
<p>나는 가갸와 함께 서비스의 가장 중요한 기능이었던 메인 페이지를 담당했는데, 초기 UI와 디자인, 그리고 타이머와 같은 부분들의 개발을 담당했었다. 가갸가 피그마로 정말 좋은 초기 디자인을 내주어서 내가 어느정도 수정하며 디자이너도 있던 다른 팀들에 결코 뒤쳐지지 않은 디자인을 만들 수 있었다. </p>
<p>여태 진행했던 프로젝트들은, 내가 부트캠프를 수료하면서 가졌던 라이브러리 사용에 대한 거부감이 있었기 때문에 라이브러리를 거의 활용하지 않고 직접 로직을 구현하였었지만, 이번 스프린트는 시간적 부족함이 있었기 때문에 다수의 리액트 라이브러리를 사용하였다. 그리고 라이브러리 역시, 어떻게 구동되는지 못한 채 복사 붙여넣기 식으로 사용하면 발전도 없고 의미도 없지만 이번 기회를 통해 동작 과정을 잘 알고 사용하기만 하면 순수하게 코드로 구현하는 것 보다 훨씬 효율성 측면이나 완성도 측면에서 좋은 결과물을 낼 수 있다는 걸 깨달았다.</p>
<p>그리고 타입스크립트와 Recoil과 같은 전역 상태관리 라이브러리에 대해서도 항상 사용해보고싶다라는 열망만 가진 채 미뤄왔었는데, 이번 스프린트를 통해 사용해보면서 감을 많이 잡았다. 사실 감을 잡았다기 보단, 내가 왜 에러가 뜨지?라고 고민할 때마다 친절하게 알려주며 도와주었던 가갸의 공이 크다. 해당 에러들은 따로 잘 정리해두어서, 나중에 같은 에러가 발생했을 때 오답노트식으로 확인할 수 있어 트러블 슈팅적인 측면에서도 성장할 수 있는 계기가 되었던 것 같다.</p>
<h2 id="그렇게-마참내-완성된-결과물-1017">그렇게 마참내 완성된 결과물! (10/17)</h2>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/4aef1cf9-433f-4189-a8ab-cbd4b7105de8/image.png" alt=""></p>
<p>그렇게 이틀 밤을 꼬박 새고, 월요일까지 개발을 진행한 뒤 netlify를 통해 우리의 서비스를 최종적으로 배포했다. </p>
<blockquote>
</blockquote>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/ae70c7f9-e815-41fc-bafc-c6cffd49debf/image.gif" alt="">
로그인 페이지
<img src="https://velog.velcdn.com/images/hang_kem_0531/post/4d43e141-1325-4ea7-8ebf-f480d7160fd2/image.gif" alt="">
서비스 알림 모달
<img src="https://velog.velcdn.com/images/hang_kem_0531/post/22b8efdd-8405-4ff6-b244-7c41e5b9e539/image.gif" alt="">
뽀모도로 타이머 기능
<img src="https://velog.velcdn.com/images/hang_kem_0531/post/10ae354b-b155-4393-8e07-de78b69e8ca0/image.gif" alt="">
투두리스트 할일 타이머 연동 기능
<img src="https://velog.velcdn.com/images/hang_kem_0531/post/ed53610e-04a6-4078-a948-f7861ed02004/image.gif" alt="">
휴식 시간 알림 (화면에는 나타나지 않지만 사용자가 다른 스크린을 보고 있을 상황을 고려하여 크롬에 Push 알림으로 소리와 함께 알림 전송 기능을 구현하였다)
<img src="https://velog.velcdn.com/images/hang_kem_0531/post/92768eac-b904-45fd-a972-619fcb93553c/image.gif" alt="">
배경화면 테마 기능
<img src="https://velog.velcdn.com/images/hang_kem_0531/post/13bdfcb9-915a-4093-82a0-6559d893709b/image.gif" alt="">
휴식 &amp; 집중 시간 설정, 엄격 기록모드, 다크모드, 화이트 노이즈 설정
<img src="https://velog.velcdn.com/images/hang_kem_0531/post/95cf06a0-efd3-48b9-a951-360728f3531d/image.gif" alt="">
할일 마무리 후 회고 작성 기능</p>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/f905e7df-7a55-4adf-b9a5-c20e20158351/image.png" alt=""></p>
<p>배포가 끝난 후, 게더타운에서 부스를 운영하여 다른 팀 분들이 오시면 직접 사용해보시면서 설명을 드렸다. 다들 디자인 적인 부분에서 칭찬을 너무 많이 해주셔서 괜히 어깨가 으쓱해졌다. 실제로 배포를 한 후에 사용자에게 피드백을 받고, 좋은 반응들을 얻은 게 처음이라서 정말 이맛에 개발을 하는구나 싶은 순간이었다.</p>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/3e35d12b-73b5-4d44-b0cb-d5a86a7d5165/image.png" alt=""></p>
<p>성공적으로 시연을 마친 후, 팀원들끼리 모여서 그동안의 스프린트에 대한 회고를 진행했다. 초기 피그마잼에 비해 다소 정신없고 난잡해진 느낌이 있지만.. 그만큼 서로가 친해지고 편해졌다는 뜻이어서 좋았다. </p>
<h2 id="스프린트를-마치며">스프린트를 마치며</h2>
<h3 id="좋았던-점">좋았던 점</h3>
<p>정말 이번 스프린트는 안했으면 후회했을 만큼 내게 의미가 컸던 스프린트였다. 일단 이렇게 열정적으로 밤을 새가며 개발했던 게 얼마만이었는지, 초심을 되찾을 수 있게 한 시간들이었다. 개발의 참재미, 그리고 성공적인 협업을 통해 나온 완성도 높은 결과물에 대한 쾌감을 느끼며 개발을 관둬야하나 했던 찰나의 고민들도 모두 사라지게 되었다. </p>
<p>실제로 배포를 해보고, 상용화까지는 아니어도 유저들이 사용해보며 피드백과 좋은 반응들을 받았던 경험이 처음이었기 때문에 진정한 개발자가 되어가는 것에 한 걸음 더 나아가는 기분이었다. 무엇보다 내가 한 디자인과 기능들이 칭찬을 받는 기분은, 마치 내 새끼 다 키워서 수능 만점을 받아온 기분과 비슷할 것만 같았다.</p>
<p>모르는 분들과 협업을 진행하면서도, 한번의 갈등이나 분쟁없이 모두 화목하게 그리고 열정있게 성공적인 협업을 마무리 할 수 있던 것도 아주 중요한 경험이었다. 아무래도 다들 너무 열심히 임해주셨기 때문에, 나도 지치지 않고 따라갈 수 있었고 아마 내가 그동안 진행했던 프로젝트 중에서 가장 애착이 가는 손가락이지 않을 까 싶다.</p>
<h3 id="아쉬운-점">아쉬운 점</h3>
<p>그러나 아쉬웠던 점들도 없지는 않았다. 초기에 코드 컨벤션을 어느 정도 정해두고 개발을 시작하긴 하였지만, 나중에 결과물을 봤을 때 컨벤션을 충실하게 지켰는지에 대해서는 의문점이 들었다. Styled-components를 각 컴포넌트 내부에서 선언하는 지, 분리해서 import 해오는 식으로 적용했는지에 대한 부분들도 지켜지지 않아서, 이 부분은 짚고 넘어가야 할 것 같다.</p>
<p>또한, 우리가 메인 서비스로 기획하였던 회고 전송 기능 역시, 백엔드 개발자가 팀내에 존재하지 않았기 때문에 파이어 베이스를 사용하여 회고를 DB상에 저장하는 것 까지는 구현하였으나, 메일로 직접 전송하는 기능은 미완성으로 그친 부분이 아쉬웠다. </p>
<p>내 스스로 아쉬웠던 부분은, 디자인이나 여러 기능들을 경험하고 완성도를 높이는데에 기여했던 부분은 만족하였으나 주도적으로 로직을 구현하거나 에러 해결에 있어 도움을 주지 못했다는 것이다. 물론 가갸가 스스로 열심히 해결해내긴 했지만, 페어 프로그래밍에 있어서 내가 좋은 Navigator가 되어 주었는지에 대해서는 살짝 아쉬운 부분이 있다.</p>
<h2 id="그래도-개발이-다시-재밌어졌다">그래도 개발이 다시 재밌어졌다.</h2>
<p>번아웃과 자존감 하락에 허덕이던 내게, 이번 스프린트는 인생의 터닝 포인트라고 해도 과언이 아닐 정도로 정말 소중한 경험이었다. 부트캠프를 수료하고, 정체되어 있던 내게 다시금 달려갈 수 있는 원동력을 불어 넣어준 것이 이번 스프린트였으니까.</p>
<p>실제로, 스프린트를 마치고 나서 스프린트 덕분인지는 몰라도 취뽀에 성공하여 11월 1일 첫 출근을 앞두고 있다. 이제 앞으로 실무에서 개발을 하게 될 내게, 이번 경험은 지치고 힘든 순간이 찾아올 때마다 스프린트에서 느꼈던 재미와 열정을 떠올리며 극복해 낼 영양분이 될 것이다.</p>
<p>정말 한순간도 불평 불만없이 모두 재밌고 친절하게 대해 준 엘링, 타카, 잼미, 가갸거겨, 힛동, 기리, 그리고 내가 항상 개발의 최종 목표로 삼고 있는 개발 입문자나 주니어들에게 나아갈 수 있는 선한 영향력을 몸소 실천하고 계신 테오에게도 정말 감사를 표하고 싶다. 모두의 미래와 나의 미래를 위해서 화이팅!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Promise와 async/await]]></title>
            <link>https://velog.io/@hang_kem_0531/Promise%EC%99%80-asyncawait</link>
            <guid>https://velog.io/@hang_kem_0531/Promise%EC%99%80-asyncawait</guid>
            <pubDate>Mon, 10 Oct 2022 15:37:19 GMT</pubDate>
            <description><![CDATA[<h2 id="엥-promise는-약속-아니냐">엥 Promise는 약속 아니냐?</h2>
<p>계속해서 자바스크립트 공부에 매진하던 중, Promise라는 아주 낯설고도 어색한 친구를 만나게 되었다. 그러면서 이 Promise와 async / await 사이에 연관성이 있다는 사실 또한 알게 되었다. async / await은 기업협업 중 레거시 코드나, 기타 강의에서 많이 봤던 친구들이지만.. 사실 내가 써본 적은 한번도 없었다. 왜 안썼을까? 모르는데 어떻게 써요.. 그래서 오늘 이 두 가지 개념에 대해 확실히 짚고 넘어가 보고자 한다.</p>
<h2 id="promise">Promise?</h2>
<p>우선, 우리의 친구 MDN이 정의한 Promise에 대해 살펴보자.</p>
<blockquote>
<p><strong>Promise</strong> 객체는 비동기 작업이 맞이할 미래의 완료 또는 실패와 그 결과 값을 나타냅니다.</p>
</blockquote>
<p>위 설명을 보게 되면, Promise는 객체의 일종이고 비동기 작업, 즉 작업 종료 여부에 관계없이 다음 작업을 수행하는 병렬 수행 작업이 맞이할 미래의 완료, 실패와 결과 값을 나타낸다는 뜻인 것 같다. 이렇게만 봐서는 아직은 Promise에 대해 감을 잡긴 어렵다.</p>
<p>우리는 Promise가 등장하기 이전에, 비동기 작업을 처리할 때 콜백 함수를 사용했었다. 그러면 왜 계속 콜백 함수를 사용하지 않고 Promise가 나타나게 되었을까? 그건 바로 콜백 지옥(Callback Hell) 때문이다.</p>
<blockquote>
<h4 id="콜백-함수-br">콜백 함수 <br/></h4>
<p>콜백 함수란, 다른 함수에 인자로 전달되는 함수이며, 외부 함수 내에서 일종의 루틴 또는 동작을 실행하기 위해 호출되는 함수를 뜻한다. 즉, 코드를 통해 명시적으로 호출하는 함수가 아니라, 어떤 이벤트가 발생했을때, 혹은 특정 시점에 도달했을 때에 시스템에서 호출하는 함수이다. </p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/ec4a8790-05ad-4d66-958f-900fe62b4918/image.png" alt=""></p>
<p>초기의 자바스크립트는 코드의 복잡도가 높지 않아서 콜백 함수를 중첩하는 일이 드물었지만, 현재는 규모가 커졌기 때문에 위 사진처럼 비동기 작업이 많아질 수록 콜백 함수의 nesting이 저렇게 깊고 깊게 들어가게 되는 것이다. 이것을 바로 콜백 지옥이라고 한다.</p>
<p>이를 다른 말로 Pyramid of Doom이라고도 하며, 이러한 프로그래밍은 가독성이 몹시 떨어지고 추후에 코드를 수정할 일이 생길 경우, 이를 몹시 어렵게 한다.</p>
<p>그래서 ES6에서 추가된 개념이 바로 이 Promise인 것이다. Promise는 바로 이 코드의 깊이가 깊어지는 현상을 방지할 수 있다.</p>
<h2 id="promise-생성">Promise 생성</h2>
<pre><code class="language-js">// Promise 객체의 생성
const promise = new Promise((resolve, reject) =&gt; {
  // 비동기 작업을 수행한다.

  if (/* 비동기 작업 수행 성공 */) {
    resolve(&#39;result&#39;);
  }
  else { /* 비동기 작업 수행 실패 */
    reject(&#39;failure reason&#39;);
  }
});</code></pre>
<p>Promise는 Promise 생성자 함수를 new 연산자와 함께 호출하여 생성한다. 이때, 비동기 처리를 수행할 콜백 함수를 인수로 전달받게 되는데, 이 콜백 함수는 resolve와 reject 함수를 인수로 전달받는다. 여기서 비동기 처리가 <strong>성공하게 되면 resolve</strong> 함수를 호출하고, <strong>실패하면 reject</strong> 함수를 호출하게 되는 것이다.</p>
<p>프로미스는 다음과 같이 현재 비동기 처리가 어떻게 진행되고 있는지에 대한 상태(state) 정보를 갖는다.</p>
<table>
<thead>
<tr>
<th>프로미스의 상태 정보</th>
<th>의미</th>
<th>상태 변경 조건</th>
</tr>
</thead>
<tbody><tr>
<td>pending</td>
<td>비동기 처리가 아직 수행되지 않은 상태</td>
<td>프로미스가 생성된 직후 기본 상태</td>
</tr>
<tr>
<td><strong>fulfilled</strong></td>
<td>비동기 처리가 수행된 상태(성공)</td>
<td>resolve 함수 호출</td>
</tr>
<tr>
<td><strong>rejected</strong></td>
<td>비동기 처리가 수행된 상태(실패)</td>
<td>reject 함수 호출</td>
</tr>
</tbody></table>
<p>생성된 직후의 프로미스는 기본적으로 pending 상태이며, 이후 비동기 처리가 수행되면 결과에 따라 다음과 같이 프로미스의 상태가 변경된다.</p>
<ul>
<li><p>비동기 처리 성공: resolve 함수를 호출해 프로미스를 fulfilled 상태로 변경한다.</p>
</li>
<li><p>비동기 처리 실패: reject 함수를 호출해 프로미스를 rejected 상태로 변경한다.</p>
</li>
</ul>
<p>이처럼 <strong>프로미스의 상태는 resolve 또는 reject 함수를 호출하는 것으로 결정된다.</strong></p>
<h2 id="프로미스의-후속-처리-메서드">프로미스의 후속 처리 메서드</h2>
<p>위처럼 프로미스의 비동기 처리 상태가 변화하면, 예를 들어 fulfilled 상태가 되면 프로미스의 처리 결과를 가지고 무언가를 해야 하고, rejected 상태가 되면 에러 처리를 해야 한다. 이를 위해 프로미스는 후속 메서드 then, catch, finally를 제공한다. <strong>프로미스의 비동기 처리 상태가 변화하면 후속 처리 메서드에 인수로 전달한 콜백 함수가 선택적으로 호출된다.</strong></p>
<h3 id="1-promiseprototypethen">1. Promise.prototype.then</h3>
<p>then 메서드는 두 개의 콜백 함수를 인수로 전달받는다.</p>
<ul>
<li><p>첫 번째 콜백 함수는 프로미스가 fulfilled 상태가 되면 호출한다. 이때 콜백 함수는 프로미스의 비동기 처리 결과를 인수로 전달받는다.</p>
</li>
<li><p>두 번째 콜백 함수는 프로미스가 rejected 상태가 되면 호출된다. 이때 콜백 함수는 프로미스의 에러를 인수로 전달받는다.</p>
</li>
</ul>
<pre><code class="language-js">// fulfilled
new Promise(resolve =&gt; resolve(&#39;fulfilled&#39;))
    .then(v =&gt; console.log(v), e =&gt; console.error(e)); // fulfilled

// rejected
new Promise((_, reject) =&gt; reject(new Error(&#39;rejected&#39;)))
    .then(v =&gt; console.log(v), e =&gt; console.error(e)); // Error: rejected</code></pre>
<p>then 메서드는 언제나 프로미스를 반환한다.</p>
<h3 id="2-promiseprototypecatch">2. Promise.prototype.catch</h3>
<p>catch 메서드는 한 개의 콜백 함수를 인수로 전달받고, 프로미스가 rejected 상태인 경우에만 호출된다.</p>
<pre><code class="language-js">// rejected
new Promise((_, reject) =&gt; reject(new Error(&#39;rejected&#39;)))
    .catch(e =&gt; console.log(e)); // Error: rejected</code></pre>
<p>catch 메서드 역시 언제나 프로미스를 반환한다.</p>
<h3 id="3-promiseprototypefinally">3. Promise.prototype.finally</h3>
<p>finally 메서드는 한 개의 콜백 함수를 인수로 전달받고, 프로미스의 성공 또는 실패와 관계없이 무조건 한 번 호출된다. 그렇기 때문에 프로미스의 상태와 상관없이 공통적으로 수행해야 할 처리 내용이 있을 때 유용하게 사용된다. finally 메서드도 언제나 프로미스를 반환한다.</p>
<pre><code class="language-js">new Promise(() =&gt; {})
    .finally(() =&gt; console.log(&#39;finally&#39;)); // finally</code></pre>
<h2 id="그럼-asyncawait은-뭔데">그럼 async/await은 뭔데?</h2>
<p>async/await은 프로미스를 기반으로 동작하지만, 프로미스의 then/catch/finally 후속 처리 메서드에 콜백 함수를 전달해서 비동기 처리 결과를 후속 처리할 필요 없이 마치 동기 처리처럼 프로미스를 사용할 수 있다. 말 그대로 Promise의 상위 호환인 격이다. </p>
<pre><code class="language-js">const fetch = require(&#39;node-fetch&#39;);

async function fetchTodo() {
  const url = &#39;https://jsonplacholder.typicode.com/todos/1&#39;;

  const response = await fetch(url);
  const todo = await response.json();
  console.log(todo);
  // { userId: 1, id: 1, title: &#39;delectus aut autem&#39;, completed: false }
}

fetchTodo();</code></pre>
<h3 id="1-async-함수">1. async 함수</h3>
<p>async 함수는 async 키워드를 사용해 정의하며 언제나 프로미스를 반환한다. await 키워드는 반드시 async 함수 내부에서 사용해야 하며, async 함수가 명시적으로 프로미스를 반환하지 않더라도 async 함수는 암묵적으로 반환값을 resolve 하는 프로미스를 반환한다.</p>
<pre><code class="language-js">// async 함수 선언문 
async function foo(n) { return n; }
foo(1).then(v =&gt; console.log(v)); // 1

// async 함수 표현식
const bar = async function (n) { return n; };
bar(2).then(v =&gt; console.log(v)); // 2

// async 화살표 함수
const baz = async n =&gt; n;
baz(3).then(v =&gt; console.log(v)); // 3

// async 메서드
const obj = {
  async foo(n) { return n; }
};
obj.foo(4).then(v =&gt; console.log(v)); // 4

// async 클래스 메서드
class MyClass {
  async bar(n) { return n; }
}

const myClass = new MyClass();
myClass.bar(5).then(v =&gt; console.log(v)); // 5</code></pre>
<h3 id="2-await-키워드">2. await 키워드</h3>
<p>await 키워드는 프로미스가 settled 상태가 될 때까지 대기하다가 settled 상태가 되면 프로미스가 resolve한 처리 결과를 반환한다. await 키워드는 반드시 프로미스 앞에서 사용해야 한다.</p>
<pre><code class="language-js">const fetch = require(&#39;node-fetch&#39;);

const getGithubUserName = async id =&gt; {
  const res = await fetch(`https://api.github.com/users/${id}`); // (1)
  const { name } = await res.json(); // (2)
  console.log(name); // Hyeong kyeom Kim
};

getGithubUserName(&#39;Kyeom1997&#39;);</code></pre>
<p>위 예제에서는 fetch 함수가 수행한 HTTP 요청에 대한 서버의 응답이 도착해서 fetch 함수가 반환한 프로미스가 settled 상태가 될때까지 (1)은 대기하게 된다. 이후 프로미스가 settled 상태가 되면 프로미스가 resolve한 처리 결과가 res 변수에 할당된다.</p>
<p>이처럼 await 키워드는 다음 실행을 일시 중지 시켰다가 프로미스가 settled 상태가 되면 다시 재개하기 때문에, 모든 프로미스에 await 키워드를 사용하는 것은 주의해야 한다. 만일 여러 비동기 처리가 서로 연관이 없이 개별적으로 수행되는 비동기 처리일때는 각 함수에 일일히 await 키워드를 사용하는 것이 아니라 다음과 같이 처리해야 한다.</p>
<pre><code class="language-js">async function foo() {
  const res = await Promise.all([
    new Promise(resolve =&gt; setTimeout(() =&gt; resolve(1), 3000)),
    new Promise(resolve =&gt; setTimeout(() =&gt; resolve(2), 2000)),
    new Promise(resolve =&gt; setTimeout(() =&gt; resolve(3), 1000)),    
  ]);

  console.log(res); // [1, 2, 3]
}

foo(); // 약 3초 소요</code></pre>
<h3 id="에러-처리">에러 처리</h3>
<p>async / await에서 에러 처리는 try ... catch 문을 사용할 수 있다. 콜백 함수를 인수로 전달받는 비동기 함수와는 달리 프로미스를 반환하는 비동기 함수는 명시적으로 호출할 수 있기 때문에 호출자가 명확하다.</p>
<pre><code class="language-js">const fetch = require(&#39;node-fetch&#39;);

const foo = async () =&gt; {
  try {
    const wrongUrl = &#39;https://wrong.url&#39;;

    const response = await fetch(wrongUrl);
    const data = await response.json();
    console.log(data);
  } catch (err) {
    console.log(err); // TypeError: Failed to fetch
  }
};

foo();</code></pre>
<p><strong>async 함수 내에서 catch 문을 사용해서 에러 처리를 하지 않으면 async 함수는 발생한 에러를 reject하는 프로미스를 반환한다.</strong> 따라서 async 함수를 호출하고 Promise.prototype.catch 후속 처리 메서드를 사용해 에러를 캐치할 수도 있다.</p>
<h2 id="마치며">마치며</h2>
<p>이렇게 Promise와 async/await에 대해 알아보았다. Promise는 비동기 처리를 할때 사용하던 콜백 함수의 콜백 지옥을 방지하기 위해, async/await은 Promise의 후속 처리 메서드를 사용하지 않고도 동기 처리처럼 프로미스가 처리 결과를 반환할 수 있도록 하기 위해 등장한 개념이다. 실무에 가면 정말 방대한 양들의 비동기 처리를 진행할테니 꼭 알아두고 넘어가야 할 스택들인 것 같다. 오늘도 지식 플러스 1!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[리액트 라이프사이클(Life Cycle)]]></title>
            <link>https://velog.io/@hang_kem_0531/%EB%A6%AC%EC%95%A1%ED%8A%B8-%EB%9D%BC%EC%9D%B4%ED%94%84%EC%82%AC%EC%9D%B4%ED%81%B4Life-Cycle</link>
            <guid>https://velog.io/@hang_kem_0531/%EB%A6%AC%EC%95%A1%ED%8A%B8-%EB%9D%BC%EC%9D%B4%ED%94%84%EC%82%AC%EC%9D%B4%ED%81%B4Life-Cycle</guid>
            <pubDate>Sat, 08 Oct 2022 10:29:14 GMT</pubDate>
            <description><![CDATA[<h2 id="리액트의-삶">리액트의 삶</h2>
<p>우리는 오늘날 리액트를 사용하면서, 함수 컴포넌트를 대부분 사용하기 때문에 라이프 사이클에 대해서 잘 알지 못한채 넘어가고는 한다. 오늘은 클래스 컴포넌트의 라이프 사이클에 대해 알아보면서, 컴포넌트가 브라우저 상에 나타나고, 업데이트 되고, 사라지게 될 때 호출되는 메서드들에 알아보고자 한다.</p>
<h2 id="what-is-life-cycle">What is Life Cycle?</h2>
<p>리액트는 컴포넌트 기반의 View를 중심으로 한 자바스크립트 라이브러리이다. 즉, MVC(Model - View - Controller) 패턴이 아닌 오직 사용자에게 보여지는 View만 신경 쓰는 라이브러리라고 할 수 있다. 여기서 사용자에게 특정 부분이 어떻게 View 될 지 정하는 선언체를 컴포넌트(Component)라고 한다. </p>
<p>각각의 컴포넌트에는 컴포넌트의 수명 주기인 라이프사이클이 존재한다. 컴포넌트의 수명은 평균적으로 페이지에서 렌더링 되기 전인 준비 과정에서 시작하여 페이지에서 사라질 때 끝이 난다. 여기서 렌더링이란, 사용자 화면에 View를 보여주는 것을 뜻한다.</p>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/bade4120-9e19-4051-89c1-f871e64eac0e/image.png" alt=""></p>
<h2 id="라이프사이클의-분류">라이프사이클의 분류</h2>
<p>라이프사이클은 위 사진처럼 총 9개가 존재하지만, 크게 생성될 때, 업데이트 될 때, 제거 될 때의 3가지 유형으로 구분지을 수 있다. 이를 다른 말로 마운트(Mount), 업데이트(Update), 언마운트(Unmount)라고 한다. 리액트의 업데이트는 props가 변경될 때, state가 변경될 때, 부모 컴포넌트가 리렌더링 될 때, this.forceUpdate 함수가 사용될 때 발생한다.</p>
<p>그럼 지금부터, 각각의 라이프사이클에 대해 알아보자.</p>
<h3 id="mount">Mount</h3>
<p>마운트 시 발생하는 라이프사이클들은 다음과 같다.</p>
<ul>
<li><p>constructor</p>
</li>
<li><p>getDerivedStateFromProps</p>
</li>
<li><p>render</p>
</li>
<li><p>componentDidMount</p>
</li>
</ul>
<h4 id="1-constructor">1. constructor</h4>
<pre><code class="language-js">constructor(props) {
    super(props);
    console.log(&quot;constructor&quot;);
  }</code></pre>
<p><code>constructor</code> 는 컴포넌트의 생성자 메서드로, 컴포넌트가 생성될 때 가장 먼저 실행되는 메서드이다.</p>
<pre><code class="language-js">// Class
constructor(props) {
  super(props);
  this.state = { isActive : false }
}

// Hooks
const [isActive, setIsActive] = useState(false);</code></pre>
<p>클래스 컴포넌트에서는 state의 초기값을 설정해 주려면 constructor 메서드를 사용해야 한다. 함수 컴포넌트에서는 useState Hook을 활용해 간단하게 초기값을 설정할 수 있다.</p>
<h4 id="2-getderivedstatefromprops">2. getDerivedStateFromProps</h4>
<p><code>getDerivedStateFromProps</code>는 props로 받아온 값을 state에 넣을 때 사용하는 메서드이다. 다른 라이프사이클과는 다르게 static을 우선적으로 작성하고 사용해야 하고, this를 참조할 수 없다.</p>
<pre><code class="language-js">static getDerivedStateFromProps(nextProps, prevState) {
    if (nextProps.value !== prevState.value) {
      return { value: nextProps.value }
    }
    return null;
  }</code></pre>
<p>특정 객체를 반환하게 되면, 해당 값이 컴포넌트의 state로 설정이 되고 null을 반환하게 되면 아무 일도 발생하지 않게 된다.</p>
<pre><code class="language-js">function ScrollView({row}) {
  const [isScrollingDown, setIsScrollingDown] = useState(false);
  const [prevRow, setPrevRow] = useState(null);

  if (row !== prevRow) {
    // 마지막 렌더링 이후 행이 변경되었습니다. isScrollingDown을 업데이트합니다.
    setIsScrollingDown(prevRow !== null &amp;&amp; row &gt; prevRow);
    setPrevRow(row);
  }

  return `Scrolling down: ${isScrollingDown}`;
}</code></pre>
<p>리액트의 공식문서에서는 getDerivedStateFromProps를 Hook에서 구현하는 방식을 위 코드와 같이 설명하고 있다.</p>
<h4 id="3-render">3. render</h4>
<p><code>render</code>는 컴포넌트를 렌더링하는 메서드이다. 클래스 컴포넌트에서는 컴포넌트를 렌더링할 때 필요한 필수 메서드이지만, 함수 컴포넌트에서는 <code>render</code> 메서드 없이도 컴포넌트 렌더링이 가능하다.</p>
<pre><code class="language-js">  render() {
    return &lt;div&gt;Hello, React!&lt;/div&gt;
  }</code></pre>
<h4 id="4-componentdidmount">4. componentDidMount</h4>
<p><code>componentDidMount</code>는 컴포넌트의 첫 렌더링이 종료되면 호출되는 메서드로, 외부 라이브러리를 연동해야 할 때나, axios, fetch 등을 통하여 ajax 요청을 할 때,  DOM 의 속성을 읽거나 직접 변경하는 작업을 진행할 때 사용한다. 함수 컴포넌트에서는 useEffect Hook을 활용하여 같은 기능을 구현할 수 있지만, dependency array를 비워둬야 한다.</p>
<pre><code class="language-js">// Class
componentDidMount() {
        fetch(...)
    }

// Hooks
useEffect(() =&gt; {
        fetch(...)
    }, []);
</code></pre>
<h3 id="update">Update</h3>
<p>컴포넌트가 업데이트 될 시에 발생하는 라이프사이클들은 다음과 같다.</p>
<ul>
<li><p>getDerivedStateFromProps</p>
</li>
<li><p>shouldComponentUpdate</p>
</li>
<li><p>render</p>
</li>
<li><p>getSnapshotBeforeUpdate</p>
</li>
<li><p>componentDidUpdate</p>
</li>
</ul>
<h4 id="1-getderivedstatefromprops">1. getDerivedStateFromProps</h4>
<p><code>getDerivedStateFromProps</code> 메서드는 컴포넌트가 마운트 될 때도 호출되지만, 컴포넌트의 state나 props가 변경되었을 때에도 호출된다.</p>
<h4 id="2-shouldcomponentupdate">2. shouldComponentUpdate</h4>
<p><code>shouldComponentUpdate</code> 메서드는 컴포넌트의 리렌더링 여부를 결정하는 메서드이다. 이 메서드에서는 반드시 true나 false를 반환해야 하며, 성능 최적화를 위해 사용한다.</p>
<pre><code class="language-js">// Class
 shouldComponentUpdate(nextProps) {
    return nextProps.value !== this.props.value
  }

// Hooks 
React.memo(() =&gt; {
      ...
  },
  (prevProps, nextProps) =&gt; {
    return nextProps.value === prevProps.value
  }
)</code></pre>
<p>함수 컴포넌트에서는 <code>React.memo()</code>를 활용하여 같은 기능을 구현할 수 있으며, state의 경우에는 useMemo Hook을 통해 렌더링 성능 최적화가 가능하다.</p>
<h4 id="3-render-1">3. render</h4>
<p><code>render</code> 메서드 역시 컴포넌트의 업데이트가 발생하면 호출된다.</p>
<h4 id="4-getsnapshotbeforeupdate">4. getSnapshotBeforeUpdate</h4>
<p><code>getSnapShotBeforeUpdate</code> 메서드는 렌더링 결과가 실제 DOM에 반영되기 이전에 호출되는 메서드로, 이름 그대로 업데이트 되기 직전에 props나 state에 대한 스냅샷을 확보하는게 목적이다. getSnapshotBeforeUpdate의 반환값은 componentDidUpdate의 세번째 인자로 들어갈 수 있으며, componentDidUpdate에서는 이를 통해 DOM의 상탯값 변화를 알 수 있다.</p>
<pre><code class="language-js">  getSnapshotBeforeUpdate(prevProps, prevState) {
    if (prevProps.color !== this.props.color) {
      return this.myRef.style.color;
    }
    return null;
  }</code></pre>
<h4 id="5-componentdidupdate">5. componentDidUpdate</h4>
<p><code>componentDidUpdate</code> 메서드는 리렌더링이 완료된 후에 실행되는 메서드로, DOM 관련 처리를 할 수 있으며 이전에 설명했던 getSnapshotBeforeUpdate에서 반환한 값을 조회할 수 있다. 함수 컴포넌트에서는 componentDidMount와 마찬가지로 useEffect를 통해 같은 기능을 구현할 수 있다.</p>
<pre><code class="language-js">componentDidUpdate(prevProps, prevState, snapshot) {
    console.log(&quot;componentDidUpdate&quot;, prevProps, prevState);
    if (snapshot) {
      console.log(&quot;업데이트 되기 직전 색상: &quot;, snapshot);
    }
  }</code></pre>
<h3 id="unmount">Unmount</h3>
<p>언마운트에 관련된 라이프사이클 메서드는 componentWillUnmount 하나밖에 존재하지 않는다.</p>
<h4 id="componentwillunmount">componentWillUnmount</h4>
<p><code>componentWillUnmount</code> 메서드는 컴포넌트가 화면에서 사라지기 직전에 호출되며, componentDidMount에서 등록한 이벤트가 있다면 여기서 제거해야 한다. 만약에 setTimeout을 사용했다면 이 부분에서 clearTimeout 을 통하여 제거한다. 함수 컴포넌트에서는 useEffect CleanUp 함수를 통해 같은 기능을 구현할 수 있다.</p>
<pre><code class="language-js">// Class
componentWillUnmount() {
    ...
}

// Hooks
useEffect(() =&gt; {
    return () =&gt; {
         ...
    }
}, []);</code></pre>
<h3 id="componentdidcatch">componentDidCatch?</h3>
<p>위의 라이프사이클 사진에는 존재하지 않지만, <code>componentDidCatch</code>라는 메서드 역시 존재한다. 이 메서드는 렌더링 도중에 발생한 오류에 대해 어플리케이션을 멈추지 않고 오류 UI를 유저에게 보여줄 수 있게 해줄 때 사용한다.</p>
<pre><code class="language-js">componentDidCatch(error, info) {
    console.log(&#39;에러가 발생했습니다.&#39;)
  }</code></pre>
<h2 id="마치며">마치며</h2>
<p>이렇게 모든 라이프사이클에 대해 알아보았다. 사실 나는 리액트를 입문부터 함수 컴포넌트로 입문을 해서 라이프사이클에 대해 잘 알지 못했는데, 이번 기회를 통해서 알고 넘어갈 수 있게 된 것 같다. Hooks로도 라이프사이클을 구현할 수 있으니 개념에 대해 정확히 알고 정확한 Hook을 사용할 수 있도록 연습해야겠다!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[클로저(Closure)]]></title>
            <link>https://velog.io/@hang_kem_0531/%ED%81%B4%EB%A1%9C%EC%A0%80Closure</link>
            <guid>https://velog.io/@hang_kem_0531/%ED%81%B4%EB%A1%9C%EC%A0%80Closure</guid>
            <pubDate>Thu, 06 Oct 2022 14:35:01 GMT</pubDate>
            <description><![CDATA[<h2 id="한-걸음-클로저-내-맘">한 걸음 클로저 내 맘~</h2>
<p>오늘 알아볼 것은 JavaScript의 클로저(Closure)이다. 자바스크립트를 공부하면서 한번 쯤은 들어봤을만한 중요한 개념이지만, 사실 클로저는 자바스크립트의 고유 개념이 아니라 함수형 프로그래밍 언어에서 사용되는 중요한 특성이다.</p>
<blockquote>
<h4 id="함수형-프로그래밍functional-programming">함수형 프로그래밍(Functional Programming)?</h4>
<p>함수형 프로그래밍이란, 프로그램을 메소드와 변수의 묶음인 객체의 집합으로 바라보는 객체 지향형 프로그래밍과 다르게 자료처리를 수학적 함수의 계산으로 취급하고 <strong>상태와 가변 데이터를 멀리</strong>하는 프로그래밍 패러다임이다. 함수형 프로그래밍은 부작용(부산물)이 없고 <strong>순수함수</strong>를 통해 입출력이 순수해야 한다는 특징이 있다. 자바스크립트는 this라는 개념때문에 순수함수를 사용하기 힘들고, 함수형 패러다임을 기반으로 하면서 객체지향의 문법을 쓰는 독특한 언어라는 특징이 있다.</p>
</blockquote>
<p>즉, 자바스크립트는 멀티 패러다임 언어이기 때문에 순수 함수형 프로그래밍 언어라고 보기에는 힘들다. 함수형 프로그래밍 언어에는 얼랭(Erlnag), 스칼라(Scala), 하스켈(Haskell), 리스프(Lisp)등이 있다. 그러나 자바스크립트에서도 클로저는 중요한 개념이기 때문에 한번 짚고 넘어가야 할 필요성이 있다. </p>
<h2 id="클로저">클로저?</h2>
<p>우선, MDN에서 정의한 클로저를 한번 살펴보자.</p>
<blockquote>
<p>“A closure is the combination of a function and the lexical environment within which that function was declared.”
클로저는 함수와 그 함수가 선언됐을 때의 <strong>렉시컬 환경(Lexical environment)</strong>과의 조합이다.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/f9128add-128c-461a-b3cd-317b359f1e2d/image.png" alt=""></p>
<p>MDN은 항상 어려운 말 투성이다. 친절하게 풀어서 설명 좀 해주지.. 하지만 징징거릴 시간에 내가 풀어서 공부해야 한다. 학습에는 끝이 없는 법!</p>
<p>우선 함수와 그 함수가 선언됐을 때의 <strong>렉시컬 환경</strong>, 이 렉시컬 환경이라는 단어에 집중해야 한다. 렉시컬 환경이란, <strong>식별자와 식별자에 바인딩된 값, 그리고 상위 스코프에 대한 참조를 기록</strong>하는 자료구조로 실행 컨텍스트를 구성하는 컴포넌트다. 여기서 실행 컨텍스트란, 식별자를 등록하고 관리하는 스코프와 코드 실행 순서 관리를 구현한 내부 메커니즘을 말한다. 실행 컨텍스트가 식별자와 스코프를 관리할 때 사용하는 컴포넌트가 바로 렉시컬 환경인 것이다.</p>
<pre><code class="language-js">const x = &#39;Kyeom&#39;;

function outerFunc() {
  const x = &#39;Kim&#39;;

  function innerFunc() {
    console.log(x); // &#39;Kim&#39;
  }

  innerFunc();
}

outerFunc();</code></pre>
<p>위 예시를 살펴보면, outerFunc 함수 내부에서 innerFunc가 정의되고 호출되었다. 이때 중첩 함수 innerFunc의 상위 스코프는 외부 함수 outerFunc의 스코프이기 때문에 innerFunc 내부에서 자신을 포함하고 있는 외부 함수 outerFunc의 x 변수에 접근할 수 있다.</p>
<p>이 같은 현상이 발생하는 이유는 자바스크립트가 렉시컬 스코프를 따르는 프로그래밍 언어이기 때문이다.</p>
<h2 id="렉시컬-스코프lexical-scope">렉시컬 스코프(Lexical Scope)</h2>
<p>렉시컬 스코프란, <strong>함수를 어디서 호출했는지가 아니라 함수를 어디에 정의했는지</strong>에 따라 자바스크립트 엔진이 상위 스코프를 결정하는 것을 뜻한다.</p>
<p>즉, 함수를 어디서 호출하는지는 함수의 상위 스코프 결정에 어떠한 영향도 주지 못하며, 함수의 상위 스코프는 함수를 정의한 위치에 의해 정적으로 결정되고 변하지 않는다.</p>
<p>스코프의 실체는 위에서 설명한 것 처럼 실행 컨텍스트의 렉시컬 환경인데, 렉시컬 환경은 자신의 &quot;외부 렉시컬 환경에 대한 참조(Outer Lexical Environment Reference)&quot;를 통해 상위 렉시컬 환경과 연결된다. 이것이 바로 <strong>스코프 체인</strong>이다.</p>
<p>렉시컬 환경의 &quot;외부 렉시컬 환경에 대한 참조&quot;에 저장할 참조값, 즉 상위 스코프에 대한 참조는 함수 정의가 평가되는 시점에 함수가 정의되는 위치에 의해 결정된다. 이것이 바로 렉시컬 스코프이다.</p>
<h2 id="그렇다면-렉시컬-환경이랑-클로저는-대체-무슨-연관일까">그렇다면 렉시컬 환경이랑 클로저는 대체 무슨 연관일까?</h2>
<p>클로저를 알아보자고 했는데 계속 렉시컬 환경만 설명하는 이유는 뭘까? 다음 예제를 살펴보면서 한번 알아보도록 하자.</p>
<pre><code class="language-js">const x = 1;

// (1)
function outer() {
  const x = 10;
  const inner = function () { console.log(x); }; // (2)
  return inner;
}

// outer 함수를 호출하면 중첩 함수 inner 반환
// outer 함수의 실행 컨텍스트는 실행 컨텍스트 스택에서 팝되어 제거
const innerFunc = outer(); // (3)
innerFunc(); // (4) 10</code></pre>
<p>위 예제의 3번에서 outer 함수는 호출되면서 중첩 함수 inner를 반환하고 실행 컨텍스트가 실행 컨텍스트 스택에서 제거되며 라이프 사이클을 마감하게 된다. 이때 outer 함수의 지역 변수 x와 변수 값 10을 저장하고 있던 실행 컨텍스트가 제거되었기 때문에 outer 함수의 지역 변수 x는 더는 유효하지 않게 되어 접근할 수 없어 보인다.</p>
<p>하지만 4번을 보게 되면 실행 결과가 outer 함수의 지역 변수 값인 10이 나오게 된다. 이처럼 <strong>외부 함수보다 중첩 함수가 더 오래 유지되는 경우 중첩 함수는 이미 라이프 사이클이 종료한 외부 함수의 변수를 참조</strong>할 수 있다. 이러한 중첩 함수를 클로저(Closure)라고 부른다.</p>
<p>자바스크립트의 모든 함수는 자신의 상위 스코프를 기억하고, 모든 함수가 기억하는 상위 스코프는 함수를 어디서 호출하든 상관없이 유지된다. 따라서 함수를 어디서 호출하든 상관없이 함수는 언제나 자신이 기억하는 상위 스코프의 식별자를 참조할 수 있으며 식별자에 바인딩 된 값을 변경할 수도 있다.</p>
<p>위 예제에서 outer 함수가 평가되어 함수 객체를 생성할 때(1),실행 컨텍스트는 outer 함수 객체의 내부 슬롯에 전역 렉시컬 환경을 상위 스코프로서 저장한다. 그리고 중첩 함수 inner가 평가될 때(2), inner 역시 outer 함수의 렉시컬 환경을 상위 스코프로서 저장한다.</p>
<p>outer 함수의 실행이 종료되면 inner 함수를 반환하면서 outer 함수의 라이프 사이클이 종료되며 실행 컨텍스트가 실행 컨텍스트 스택에서 제거되지만 (3), 이때 outer 함수의 <strong>렉시컬 환경까지 소멸되는 것은 아니다</strong>. outer 함수의 렉시컬 환경은 inner 함수에 의해 참조되고 있고 inner 함수는 전역 변수 innerFunc에 의해 참조되고 있으므로 가비지 컬렉션의 대상이되지 않기 때문이다.</p>
<p>outer 함수가 반환한 inner 함수를 호출(4)하면 inner 함수의 실행 컨텍스트가 생성되고 실행 컨텍스트 스택에 푸시되게 된다. 그리고 렉시컬 환경의 외부 렉시컬 환경에 대한 참조에는 inner 함수 내부에 저장되어 있는 참조값(outer 함수의 렉시컬 환경)이 할당되게 된다.</p>
<p>이렇듯 자바스크립트의 모든 함수는 상위 스코프를 기억하므로 이론적으로 모든 함수는 클로저라고 할 수 있지만, 일반적으로 모든 함수를 클로저라고 하지는 않는다. <strong>클로저는 중첩 함수가 상위 스코프의 식별자를 참조하고 있고 중첩 함수가 외부 함수보다 더 오래 유지되는 경우에만 한정하는 것이 일반적이다.</strong></p>
<h2 id="클로저의-활용">클로저의 활용</h2>
<p>클로저는 <strong>상태(state)를 안전하게 변경하고 유지하기 위해 사용한다.</strong> 다시 말해, 상태가 의도치 않게 변경되지 않도록 <strong>상태를 안전하게 은닉</strong>하고 <strong>특정 함수에게만 상태 변경을 허용</strong>하기 위해 사용한다.</p>
<pre><code class="language-js">// 카운트 상태 변수
let num = 0;

// 카운트 상태 변경 함수
const increase = function () {
  // 카운트 상태 1만큼 증가
  return ++num;
};

console.log(increase()); // 1
console.log(increase()); // 2
console.log(increase()); // 3</code></pre>
<p>위 예제는 바르게 동작하려면 다음의 두 가지 전제 조건이 지켜져야 한다.</p>
<ol>
<li><p>카운트 상태(num 변수의 값)은 increase 함수가 호출되기 전까지 변경되지 않고 유지되어야 한다.</p>
</li>
<li><p>이를 위해 카운트 상태는 increase 함수만이 변경할 수 있어야 한다.</p>
</li>
</ol>
<p>하지만 카운트 상태는 전역 변수를 통해 관리되고 있는 암묵적 결합 상태이기 때문에, 누구나 접근할 수 있고 언제든지 변경될 수 있다. 따라서 카운트 상태를 안전하게 변경하고 유지하기 위해서는 increase 함수만이 num 변수를 참조하고 변경할 수 있게 해야한다.</p>
<pre><code class="language-js">const increase = function() {
  let num = 0;

  return ++num;
};

console.log(increase()); // 1
console.log(increase()); // 1
console.log(increase()); // 1</code></pre>
<p>위 예제는 전역 변수 num을 increase 함수의 지역 변수로 변경하여 의도치 않은 상태 변경을 방지했지만, increase 함수가 호출될 때마다 지역 변수 num이 다시 선언되고 0으로 초기화되기 때문에 상태가 변경되기 이전 상태를 유지하지 못한다. 이때 사용해야 할 것이 바로 클로저이다.</p>
<pre><code class="language-js">const increase = (function () {
  let num = 0;

  // 클로저
  return function () {
    return ++num;
  };
}());

console.log(increase()); // 1
console.log(increase()); // 2
console.log(increase()); // 3</code></pre>
<p>위 코드가 실행되면 즉시 실행 함수가 호출되고 즉시 실행 함수가 반환한 함수가 increase 변수에 할당된다. </p>
<blockquote>
<h4 id="즉시-실행-함수iife-br">즉시 실행 함수(IIFE) <br></h4>
<p>즉시 실행 함수는 <strong>정의되자마자 즉시 실행되는 함수</strong>를 말한다. <code>(function () {
    console.log(&quot;IIFE&quot;);
})();</code>와 같이  소괄호로 함수를 감싸서 실행하는 문법을 사용한다. 즉시실행함수는 선언과 동시에 호출되어 반환되어 재사용 할 수 없기 때문에 익명 함수로 사용하는 것이 일반적이다. </p>
</blockquote>
<p>increase 변수에 할당된 함수는 자신이 정의된 위치에 의해 결정된 상위 스코프인 즉시 실행 함수의 렉시컬 환경을 기억하는 클로저다. 즉시 실행 함수는 호출된 이후 소멸되지만 즉시 실행 함수가 반환한 클로저는 increase 변수에 할당되어 호출된다. 이때 즉시 실행 함수가 반환한 클로저는 즉시 실행 함수의 렉시컬 환경을 기억하고 있기 때문에, 카운트 상태를 유지하기 위한 자유 변수 num을 언제 어디서 호출하든지 참조하고 변경할 수 있다.</p>
<p>즉시 실행 함수는 한 번만 실행되므로 increase가 호출될 때마다 num 변수가 재차 초기화 될 일도 없고, num 변수는 외부에서 직접 접근할 수 없는 은닉된 private 변수이므로 전역 변수와 같이 의도되지 않는 변경을 걱정할 필요도 없기 때문에 더 안정적인 프로그래밍이 가능하다.</p>
<p>이처럼 클로저는 <strong>상태가 의도치 않게 변경되지 않도록 안전하게 은닉하고 특정 함수에게만 상태 변경을 허용하여 상태를 안전하게 변경하고 유지하기 위해 사용한다.</strong></p>
<h2 id="마치며">마치며</h2>
<p>사실 이 부분은 예전에 모던 자바스크립트 딥 다이브를 통해 공부하면서 velog에 기록했던 부분이다. 하지만 그때는 거의 받아쓰기 수준으로 이해하지 못하고 적어 놓기만 했었는데, 이번에 다시 짚어보며 공부해보니 클로저에 대해 이해할 수 있게 되었다. 이번에도 모던 자바스크립트 딥 다이브를 참고하며 작성하긴 했지만.. 실무에서 데이터 은닉화나 상태변경 방지를 위해 꼭 알아두어야 할 개념이니 이번 기회를 통해 확실히 짚고 넘어가보자!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[What is this? (this는 무엇입니까?)]]></title>
            <link>https://velog.io/@hang_kem_0531/What-is-this-this%EB%8A%94-%EB%AC%B4%EC%97%87%EC%9E%85%EB%8B%88%EA%B9%8C</link>
            <guid>https://velog.io/@hang_kem_0531/What-is-this-this%EB%8A%94-%EB%AC%B4%EC%97%87%EC%9E%85%EB%8B%88%EA%B9%8C</guid>
            <pubDate>Sun, 02 Oct 2022 11:39:15 GMT</pubDate>
            <description><![CDATA[<h2 id="what-is-this">What is this?</h2>
<p>우리는 자바스크립트를 공부하면서 this라는 단어를 정말 지겹게 보고, 면접 준비를 하면서도 this에 관해서 지겹도록 외웠을 것이다. 그래서 this가 뭔데? 라고 물어보면 아마 대다수는 정해진 대답을 할 것이다. </p>
<blockquote>
<p><strong>this</strong>는 자신이 속한 객체 또는 자신이 생성할 인스턴스를 가리키는 자기 참조 변수입니다!</p>
</blockquote>
<p>맞다. this는 자바스크립트에서 자신이 속한 객체 또는 자신이 생성할 인스턴스를 가리키는 자기 참조 변수이다. 그러나, 이렇게만 알아둔다면 this에 대해 정확히 이해를 못하고 넘어갈 가능성이 높다. 왜냐? 내가 지금 그러고 있으니까. 그러니 이번 글을 통해서 this에 대해 한번 deep하게 파고 들어가 보자.</p>
<h2 id="this">this</h2>
<p>그럼 아까 했던 정답을 다시 하나씩 차근차근히 뜯어 보자. 자신이 속한 &quot;객체&quot;에서 객체란, 상태를 나타내는 프로퍼티와 동작을 나타내는 메서드를 하나의 논리적인 단위로 묶은 복합적인 자료구조를 뜻한다. </p>
<pre><code class="language-js">const circle = {
  // 프로퍼티: 객체 고유의 상태 데이터
  radius: 10;
  // 메서드: 상태 데이터를 참조하고 조작하는 동작
  getDiameter() {
    return 2 * circle.radius;
  }
};</code></pre>
<p>위의 코드에서 객체는 &#39;circle&#39;이 된다. 여기서는 객체 리터럴 방식으로 객체를 생성했기 때문에 getDiameter 메서드가 호출되기 전에 객체 리터럴의 평가가 완료되어 circle 객체가 생성되었고 메서드 내부에서 circle을 참조할 수 있다. 객체 리터럴 방식으로 생성한 객체의 경우 메서드 내부에서 메서드 자신이 속한 객체를 가리키는 식별자를 재귀적으로 참조할 수 있다. 하지만 이는 바람직하지 않고 일반적이지도 않다. 그러면 이 코드를 this를 사용해 수정해 보자.</p>
<pre><code class="language-js">// 객체 리터럴
const Circle = {
  radius: 10,
  getDiameter() {
    // this는 메서드를 호출한 객체를 가리킨다.
    return 2 * this.radius;
  }
};

console.log(circle.getDiameter()); // 20</code></pre>
<p>위 코드에서의 this는 메서드를 호출한 객체, 즉 <strong>자신이 속한 객체</strong>인 circle이다. 그렇다면 &#39;자신이 생성할 인스턴스를 가리키는&#39;에 해당하는 예시는 무엇일까? </p>
<p>만일 생성자 함수로 객체를 생성하게 된다면, 생성자 함수를 정의한 이후에 new 연산자와 함께 생성자 함수를 호출하는 단계가 추가적으로 필요하다. 생성자 함수를 정의하는 시점에는 아직 인스턴스를 생성하기 이전이므로 생성자 함수가 생성할 인스턴스를 가리키는 식별자를 알 수 없다.</p>
<pre><code class="language-js">function Circle(radius) {
  // 이 시점에는 생성자 함수 자신이 생성할 인스턴스를 가리키는 식별자를 알 수 없다.
  ????.radius = radius;
}

Circle.prototype.getDiameter = function () {
  // 이 시점에는 생성자 함수 자신이 생성할 인스턴스를 가리키는 식별자를 알 수 없다.
  return 2 * ????.radius;
};

// 생성자 함수로 인스턴스를 생성하려면 먼저 생성자 함수를 정의해야 한다.
const circle = new Circle(5);</code></pre>
<p>바로 이때, 자신이 생성할 인스턴스를 가리키기 위해 this가 사용된다. </p>
<pre><code class="language-js">// 생성자 함수
function Circle(radius) {
  // this는 생성자 함수가 생성할 인스턴스를 가리킨다.
  this.radius = radius;
}

Circle.prototype.getDiameter = function() {
  // this는 생성자 함수가 생성할 인스턴스를 가리킨다.
  return 2 * this.radius;
};

// 인스턴스 생성
const circle1 = new Circle(5);
console.log(circle.getDiameter()); // 10</code></pre>
<p>위의 코드에서의 this는 생성자 함수가 생성할 인스턴스인 circle1을 가리킨다. 즉, this는 객체의 프로퍼티나 메서드를 참조하기 위한 &#39;<strong>자기 참조 변수</strong>&#39; 인 것이다.</p>
<h2 id="this-바인딩">this 바인딩</h2>
<p>위의 코드에서 볼 수 있듯이, this는 상황에 따라 가리키는 대상이 다르다. 자바스크립트의 this는 함수가 호출되는 방식에 따라 this에 바인딩될 값, 즉 this 바인딩이 동적으로 결정된다.</p>
<blockquote>
<h3 id="바인딩-binding">바인딩 (Binding)</h3>
<p>바인딩(Binding) 이란 프로그램의 어떤 기본 단위가 가질 수 있는 구성요소의 구체적인 값, 성격을 확정하는 것을 말한다. 바인딩은 &quot;묶다&quot;라는 사전적 의미로, 코딩에서의 바인딩은 두 데이터 혹은 정보의 소스를 일치시키는 기법을 의미한다.</p>
</blockquote>
<p>함수의 호출 방식에는 다양한 종류가 있다.</p>
<ol>
<li><p>일반 함수 호출</p>
</li>
<li><p>메서드 호출</p>
</li>
<li><p>생성자 함수 호출</p>
</li>
<li><p>Function.prototype.apply/call/bind 메서드에 의한 간접 호출</p>
</li>
</ol>
<p>이 함수 호출 방식에 따라 this 바인딩 역시 동적으로 결정된다. 어떻게 this 바인딩이 되는지 알아보도록 하자.</p>
<h3 id="일반-함수-호출">일반 함수 호출</h3>
<p><strong>기본적으로 일반 함수로 호출된 모든 함수(중첩 함수, 콜백 함수 포함) 내부의 this에는 전역 객체가 바인딩된다.</strong></p>
<pre><code class="language-js">function foo() {
  console.log(&quot;foo&#39;s this: &quot;, this); // window
  function bar() {
    console.log(&quot;bar&#39;s this: &quot;, this); // window
  }
  bar();
}
foo();</code></pre>
<p>하지만 메서드 내에서 정의한 중첩 함수 또는 메서드에게 전달한 콜백 함수(보조 함수)가 일반 함수로 호출될 때 메서드 내의 중첩 함수 또는 콜백 함수의 this가 전역 객체를 바인딩하는 것은 문제가 있다. </p>
<p>중첩 함수 또는 콜백 함수는 외부 함수를 돕는 헬퍼 함수의 역할을 하므로 외부 함수의 일부 로직을 대신하는 경우가 대부분이다. 하지만 외부 함수인 메서드와 중첩 함수 또는 콜백 함수의 this가 일치하지 않는다는 것은 중첩 함수 또는 콜백 함수를 헬퍼 함수로 동작하기 어렵게 만든다.</p>
<p>그렇기 때문에 this 바인딩을 일치시키기 위해서는 화살표 함수를 사용할 수 있다.</p>
<pre><code class="language-js">var value = 1;

const obj = {
  value: 100,
  foo() {
    // 화살표 함수 내부의 this는 상위 스코프의 this를 가리킨다.
    setTimeout(() =&gt; console.log(this.value), 100); // 100
  }
};

obj.foo();</code></pre>
<p>화살표 함수에서는 this가 아예 존재하지 않기 때문에, 그 상위 환경에서의 this를 참조하게 된다. 즉, 화살표 함수는 선언될 시점에서의 상위 스코프가 this로 바인딩된다. 그렇기 때문에 헬퍼 함수로서의 동작을 위해 this 바인딩을 일치시킬 때에 화살표 함수를 사용할 수 있는 것이다.</p>
<h3 id="메서드-호출">메서드 호출</h3>
<p><strong>메서드 내부의 this에는 메서드를 호출한 객체, 즉 메서드를 호출할 때 메서드 이름 앞의 마침표(.) 연산자 앞에 기술한 객체가 바인딩된다.</strong></p>
<pre><code class="language-js">const person = {
  name: &#39;Kyeom&#39;,
  getName() {
    // 메서드 내부의 this는 메서드를 호출한 객체에 바인딩된다.
    return this.name;
  }
};

// 메서드 getName을 호출한 객체는 person이다.
console.log(person.getName()); // Kyeom</code></pre>
<p>주의할 것은 메서드 내부의 this는 메서드를 소유한 객체가 아닌 메서드를 호출한 객체에 바인딩된다는 것이다. 위 예제의 getName 메서드는 person 객체의 메서드로 정의되었다. 즉, getName 프로퍼티가 함수 객체를 가리키고 있을 뿐이지 person 객체의 getName 프로퍼티가 가리키는 함수 객체는 person 객체에 포함된 것이 아니라 독립적으로 존재하는 별도의 객체다. </p>
<p>따라서 getName 프로퍼티가 가리키는 함수 객체, 즉 getName 메서드는 다른 객체의 프로퍼티에 할당하는 것으로 다른 객체의 메서드가 될 수도 있고 일반 변수에 할당하여 일반 함수로 호출될 수도 있다.</p>
<p>따라서 메서드 내부의 this는 프로퍼티로 메서드를 가리키고 있는 객체와는 관계가 없고 메서드를 호출한 객체에 바인딩된다.</p>
<h3 id="생성자-함수-호출">생성자 함수 호출</h3>
<p><strong>생성자 함수 내부의 this에는 생성자 함수가 (미래에) 생성할 인스턴스가 바인딩된다.</strong></p>
<p>생성자 함수는 이름 그대로 인스턴스를 생성하는 함수다. 일반 함수와 동일한 방법으로 생성자 함수를 정의하고 new 연산자와 함께 호출하면 해당 함수는 생성자 함수로 동작한다. 만약 new 연산자와 함께 생성자 함수를 호출하지 않으면 생성자 함수가 아니라 일반 함수로 동작한다.</p>
<h2 id="apply-call-bind">apply(), call(), bind()</h2>
<p>함수 호출 방법으로는 위의 3가지 방법 외에도 apply(), call(), bind()를 사용하는 방법이 있다. apply(), call(), bind()로 함수를 호출하면 this 바인딩이 전역 객체인 window가 아닌 다른 객체에 할당된다. </p>
<h3 id="call">call()</h3>
<pre><code class="language-js">func.call(thisArg[, arg1[, arg2[, ...]]])</code></pre>
<p>Function.prototype.call() 메서드에서 매개변수는 thisArg로, func 호출에 제공되는 this의 값을 뜻한다. 그리고 두 번째부터는 호출할 함수들의 인수가 들어가게 된다. </p>
<pre><code class="language-js">let Human1 = {
    name: &#39;Kyeom&#39;
};

let Human2 = {
    name: &#39;Kim&#39;,
    callName: function() {
        console.log(&#39;이 사람의 이름은&#39; + this.name + &#39; 입니다.&#39;);
    }
};

Human2.callName(); // 이 사람의 이름은 Kim 입니다.

// call()
Human2.callName.call(Human1); // 이 사람의 이름은 Kyeom 입니다.</code></pre>
<p>위 예제를 보게 되면, call() 메서드로 Human2의 function을 호출하고 있지만 매개변수로 Human1을 넣어주었기 때문에 this는 Human1을 가리키게 된다.</p>
<h3 id="apply">apply()</h3>
<pre><code class="language-js">func.apply(thisArg, [argsArray])</code></pre>
<p>Function.prototype.apply() 메서드는 call() 메서드와 유사하지만, call() 은 함수에 전달될 인수 리스트를 받는데 비해, apply() 는 인수들의 단일 배열을 받는다는 차이점이 있다. 즉, 첫 번째 매개변수 thisArg로 this를 지정해주는 점은 동일하지만, apply()는 두 번째 매개변수를 배열 형태로 넣게 된다.</p>
<h3 id="bind">bind()</h3>
<pre><code class="language-js">func.bind(thisArg[, arg1[, arg2[, ...]]])</code></pre>
<p>Function.prototype.bind() 메서드는 호출될 경우에 새로운 함수를 생성하게 된다. 받게되는 첫 인자의 value로는 this 키워드를 설정하고, 이어지는 인자들은 바인드된 함수의 인수에 제공된다.</p>
<pre><code class="language-js">let Human1 = {
    name: &#39;Kyeom&#39;
};

let Human2 = {
    name: &#39;Kim&#39;,
    callName: function() {
        console.log(&#39;이 사람의 이름은&#39; + this.name + &#39; 입니다.&#39;);
    }
};

Human2.callName(); // 이 사람의 이름은 Kim 입니다.

// bind()
let person = Human2.callName.bind(Human1);

person(); // 이 사람의 이름은 Kyeom 입니다.</code></pre>
<p>bind()는 call(), apply()와 같이 함수가 가리키고 있는 this를 바꾸지만 호출되지는 않기 때문에, 변수를 할당하여 호출하는 형태로 사용된다.</p>
<h2 id="마치며">마치며</h2>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/cd76de13-52cc-4b3b-a55b-410482398793/image.png" alt=""></p>
<p>이렇게 우리는 this에 대한 정의와 this 바인딩까지 거의 this에 대한 모든 것을 알아보았다. 박수 짝짝짝! 사실 항상 this를 어렴풋이만 알아오면서 매번 헷갈리기 일쑤였는데 이렇게 글로 정리해보니 이해가 조금 은 더 수월하게 되는 듯 하다. 역시 기록의 힘! 앞으로도 꾸준히 velog를 쓰도록 노력해보자.</p>
<h3 id="참고-자료">참고 자료</h3>
<p><a href="https://www.zerocho.com/category/JavaScript/post/57433645a48729787807c3fd">https://www.zerocho.com/category/JavaScript/post/57433645a48729787807c3fd</a></p>
<p><a href="https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Operators/this">https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Operators/this</a></p>
<p><a href="https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Function/apply">https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Function/apply</a></p>
<p><a href="https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Function/bind">https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Function/bind</a></p>
<p><a href="https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Function/call">https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Function/call</a></p>
<p>모던 자바스크립트 딥 다이브 책</p>
]]></description>
        </item>
        <item>
            <title><![CDATA['www.google.com'을 주소창에 입력하면 일어나는 일]]></title>
            <link>https://velog.io/@hang_kem_0531/www.google.com%EC%9D%84-%EC%A3%BC%EC%86%8C%EC%B0%BD%EC%97%90-%EC%9E%85%EB%A0%A5%ED%95%98%EB%A9%B4-%EC%9D%BC%EC%96%B4%EB%82%98%EB%8A%94-%EC%9D%BC</link>
            <guid>https://velog.io/@hang_kem_0531/www.google.com%EC%9D%84-%EC%A3%BC%EC%86%8C%EC%B0%BD%EC%97%90-%EC%9E%85%EB%A0%A5%ED%95%98%EB%A9%B4-%EC%9D%BC%EC%96%B4%EB%82%98%EB%8A%94-%EC%9D%BC</guid>
            <pubDate>Wed, 28 Sep 2022 10:27:53 GMT</pubDate>
            <description><![CDATA[<h2 id="짠하면-뿅하고-나오는-거-아님">짠하면 뿅하고 나오는 거 아님?</h2>
<p>그동안 우리는 너무 쉽게 웹 페이지에 접근을 해왔다. 그냥 검색창에 우리가 가고 싶은 URL을 타이핑하고 엔터만 치면 뿅하고 사이트가 나왔으니 이 얼마나 쉬운 세상인가. 하지만 우리가 그렇게 쉽게 웹 사이트에 접근할 동안 브라우저는 정말 많은 일들을 처리하고 있었다. 개발자의 길을 걷기로 결정한 만큼, 브라우저의 고충을 이해해 보며 어떤 일들이 일어나는 지 한번 알아보자.</p>
<h2 id="브라우저">브라우저?</h2>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/3f20f953-5362-4445-954e-9883f87a6759/image.png" alt=""></p>
<p>우선 브라우저가 무엇인지에 대해서 부터 알아보자. <strong>웹 브라우저</strong>란, 웹 서버와 통신하여 인터넷의 WWW(World Wide Web) 서비스를 이용할 수 있게 하는 컴퓨터 응용 프로그램이다. Browse라는 뜻은, 원래 둘러보다, 훑어보다 라는 뜻인데 웹에서 여러 문서들을 잠깐식 펼쳐보거나 훑어보는 도구라는 뜻에서 웹 브라우저라는 이름이 지어졌다.</p>
<p>현재 대표적으로 사용되는 웹 브라우저에는 구글의 크롬(Chrome), Mozilla의 파이어폭스(Firefox), 애플의 사파리(Safari), 마이크로소프트의 엣지(Edge) 등이 있다. 우리의 유년기를 함께 했던 마이크로소포트의 Internet Explorer는 현재 서비스가 종료되어 사장된 웹 브라우저이다.</p>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/90c46987-4a75-47d8-b638-b10faed783ae/image.png" alt=""></p>
<p>웹 브라우저의 구조는 브라우저마다 조금 다를수는 있겠지만, 평균적으로 다음과 같이 구성되어 있다.</p>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/79094299-a4b1-40e8-9daf-82c02449cb07/image.png" alt=""></p>
<p>(사진 출처: <a href="https://d2.naver.com/helloworld/59361">https://d2.naver.com/helloworld/59361</a>)</p>
<ol>
<li><p>사용자 인터페이스
주소 표시줄, 이전/다음 버튼, 북마크 메뉴 등. 요청한 페이지를 보여주는 창을 제외한 나머지 모든 부분이다.</p>
</li>
<li><p>브라우저 엔진
사용자 인터페이스와 렌더링 엔진 사이의 동작을 제어한다. </p>
</li>
<li><p>렌더링 엔진
웹 페이지가 표시되는 모든 영역을 제어하며 요청한 콘텐츠를 표시한다. 예를 들어 HTML을 요청하면 HTML과 CSS를 파싱하여 화면에 표시하게 된다.</p>
</li>
<li><p>통신
HTTP 요청과 같은 네트워크 호출에 사용된다. 플랫폼과 독립적인 인터페이스이고 각 플랫폼 하부에서 실행된다.</p>
</li>
<li><p>UI 백엔드
콤보 박스와 창 같은 기본적인 장치를 그리게 된다. 플랫폼에서 명시하지 않은 일반적인 인터페이스로서, OS 사용자 인터페이스 체계를 사용한다.</p>
</li>
<li><p>자바스크립트 해석기
자바스크립트 코드를 해석하고 실행한다. 자바스크립트 엔진이라고도 하며, HTML 파싱 중 script 태그를 만나게 되면 렌더링 엔진에게서 제어 권한을 넘겨받게 된다. 이 작업은 동기적으로 진행된다. </p>
</li>
<li><p>자료 저장소
이 부분은 자료를 저장하는 계층이다. 쿠키를 저장하는 것과 같이 모든 종류의 자원을 하드 디스크에 저장할 필요가 있는데, HTML5 명세에는 브라우저가 지원하는 &#39;웹 데이터 베이스&#39;가 정의되어 있다.</p>
</li>
</ol>
<h2 id="그럼-wwwgooglecom을-입력-해보자">그럼 <a href="http://www.google.com%EC%9D%84">www.google.com을</a> 입력 해보자.</h2>
<p>사용자가 주소창에 <a href="http://www.google.com%EC%9D%84">www.google.com을</a> 입력하게 되면, 다음과 같은 일들이 일어나게 된다. </p>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/1d425dba-8f28-42c4-a331-17cbb16675ac/image.jpeg" alt=""></p>
<h3 id="1-입력한-텍스트-정보-확인">1. 입력한 텍스트 정보 확인</h3>
<p>크롬과 사파리는 구글의 검색창을, 네이버 웨일은 네이버의 검색창을 주소창과 동일하게 사용하고 있다. 그렇기 때문에 브라우저는 우선 사용자의 입력 텍스트가 검색어인지 URL인지 확인하는 과정을 거친다. 이 작업은 브라우저 엔진의 UI 스레드에서 진행하게 된다.</p>
<p>만일 사용자가 입력한 텍스트가 검색어면, 브라우저는 검색 엔진의 URL에 검색어를 포함한 주소로 페이지를 이동시키고, URL일 경우에는 네트워크 호출을 수행하게 된다. 현재는 사용자가 <a href="http://www.google.com">www.google.com</a>, URL을 입력했기 때문에 네트워크 호출을 수행한다.</p>
<h3 id="2-네트워크-호출">2. 네트워크 호출</h3>
<p>브라우저는 구글의 HTML 문서, CSS 문서, 스크립트, 이미지 등의 데이터를 미리 가지고 있지 않기 때문에 구글 서버와의 네트워크 통신을 통해 데이터를 가져와야 한다. 브라우저는 네트워크 통신을 위해 도메인 이름에 해당하는 google.com을 DNS 서버에서 검색을 하게 되는데, DNS 서버 검색 이전에 캐싱된 DNS 기록들을 먼저 확인한다.</p>
<blockquote>
<h3 id="dns">DNS</h3>
<p>DNS(Domain Name System)란 URL들의 이름과 IP주소를 저장하고 있는 데이터베이스다. 인터넷에 있는 모든 URL들에는 고유의 IP 주소가 지정되어있고, 이 IP 주소를 통해서 해당 웹사이트를 호스팅하고 있는 서버 컴퓨터에 접근을 할 수 있다. 따라서 도메인 네임을 IP 주소로 변환해 주는 환경이 필요한데, 이것이 바로 DNS이다.</p>
</blockquote>
<p>만약 해당 도메인 이름에 해당하는 IP 주소가 존재하면, 브라우저는 DNS 서버에 해당 도메인 이름에 해당하는 IP 주소를 요청하지 않고 캐싱된 IP주소를 바로 반환한다. 만약 일치하는 IP 주소가 존재하지 않는다면, 다음 과정인 DNS 서버 요청으로 넘어가게 된다.</p>
<p>브라우저는 ISP(Internet Service Provider)를 통해 DNS 서버가 호스팅하고 있는 서버의 IP 주소를 찾기 위해 DNS query를 전달한다. DNS query는 현재 DNS서버에 원하는 IP주소가 존재하지 않으면 다른 DNS 서버를 방문하는 과정을 원하는 IP주소를 찾을 때까지 반복하게 된다.</p>
<p>IP 주소를 찾게 되면, 브라우저는 구글 서버에 데이터를 요청하는 HTTP Request를 보내게 된다. 해당 HTTP 요청 메세지는 TCP/IP 프로토콜을 통해 서버로 전송되게 된다.</p>
<blockquote>
<h3 id="tcpip-br">TCP/IP <br></h3>
<p>TCP/IP는 현재의 인터넷에서 컴퓨터들이 서로 정보를 주고 받는데 쓰이는 통신규약(프로토콜)의 모음으로 총 4개의 계층으로 이루어져 있다.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/e1ca0409-ce6b-4d78-8cb1-935d1288de24/image.png" alt="">
(사진 출처: <a href="https://intrepidgeeks.com/tutorial/tcpip">https://intrepidgeeks.com/tutorial/tcpip</a>)</p>
<p>HTTP Request를 받은 구글 서버는 클라이언트의 요청 문서를 받아 이를 바이트 형태로 변환 후 다시 클라이언트로 HTTP Response를 보낸다. 이때 전달 과정에서 Status Code를 통해 서버 요청에 따른 결과 및 상태를 전송한다.</p>
<h3 id="3-렌더링">3. 렌더링</h3>
<p>브라우저 엔진은 구글 서버로부터 전달받은 데이터에 바이러스가 있는지 우선적으로 검사한 뒤, 바이트 형태의 텍스트 문서를 브라우저 엔진이 해석할 수 없기 때문에 렌더링 엔진에게 데이터 해석 및 렌더링을 요청한다. </p>
<p>요청을 받은 렌더링 엔진은 데이터를 바탕으로 렌더링 프로세스를 진행하고, 이 과정이 끝나면 브라우저 엔진에게 작업 완료를 알리게 된다.</p>
<p>이 과정들이 모두 끝나면, 마침내 사용자가 보고 있는 화면에 구글 페이지가 출력되는 것이다!</p>
<h2 id="렌더링-프로세스">렌더링 프로세스</h2>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/99a68551-8015-47a0-86ea-01c549f284fc/image.jpeg" alt=""></p>
<p>렌더링 프로세스는 다음과 같은 4가지 단계로 진행된다. </p>
<ol>
<li><p>HTML을 파싱하여 DOM 트리 구축, CSS를 파싱하여 CSSOM 트리 구축 (+ JS 파싱)</p>
</li>
<li><p>DOM 트리와 CSSOM 트리를 통해 렌더 트리 구축 (Attachment / 형상 구축)</p>
</li>
<li><p>레이아웃 배치 (Layout / Reflow)</p>
</li>
<li><p>페인트 (Paint)</p>
</li>
</ol>
<blockquote>
<h3 id="파싱-br">파싱 <br></h3>
<p>파싱은 단순한 텍스트 문서를 해석하지 못하는 브라우저를 위해 문서를 브라우저가 이해할 수 있는 구조로 변환시켜주는 과정을 말한다. 문자열을 의미 있는 작은 단위인 <strong>토큰(token)</strong>으로 분해하는 과정인 <strong>어휘 분석</strong>과 문자열의 문법에 따라 토큰 간의 위계관계를 분석하여 <strong>parsing 트리</strong>를 생성하는 과정인 <strong>구문 분석</strong>의 단계로 나뉜다. parsing 트리는 토큰화된 문자열의 단순한 트리에 불과하기 때문에, 브라우저는 이 parsing 트리를 DOM 트리와 CSSOM 트리로 변환하여 사용한다.</p>
</blockquote>
<h3 id="html-파싱-dom-트리-생성">HTML 파싱, DOM 트리 생성</h3>
<p>렌더링 엔진이 HTML 문서를 수신받으면, HTML 파서는 이를 위에서부터 읽어 내려가며 파싱을 진행하고 DOM 트리를 생성하게 된다. HTML 파싱은 다음과 같이 진행된다.</p>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/498ef9d4-f98e-47e0-815d-9fa2a62b4f25/image.png" alt=""></p>
<ol>
<li><p>서버에서 <strong>바이트</strong> 형태의 HTML 문서를 응답 받는다.</p>
</li>
<li><p>저장된 인코딩 방식에 따라 이를 <strong>문자열</strong>로 변환한다.</p>
</li>
<li><p>변환된 문자열을 <strong>토큰</strong>으로 분해한다.</p>
</li>
<li><p>토큰을 내용에 따라 <strong>객체(노드)</strong>로 변환한다.</p>
</li>
<li><p>노드를 트리 구조로 구성하여 <strong>DOM 트리</strong>를 생성한다.</p>
</li>
</ol>
<blockquote>
<p>바이트 -&gt; 문자열 -&gt; 토큰 -&gt; 노드 -&gt; DOM 트리</p>
</blockquote>
<h3 id="css-파싱-cssom-트리-생성">CSS 파싱, CSSOM 트리 생성</h3>
<p>HTML 파싱 중 CSS 문서를 가져오는 <code>link</code> 태그를 인식하게 되면, DOM 트리 생성을 중단하고 CSS 파싱이 진행된다. CSS 파서는 서버에서 수신받은 CSS 문서를 파싱하여 CSSOM 트리를 생성하는데, CSS 파싱 과정은 기본적으로 HTML 파싱 과정과 동일하다.</p>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/3824f1c8-9b25-414e-a610-7c80b94afcc6/image.png" alt=""></p>
<h3 id="javascript-파싱-ast-생성">JavaScript 파싱, AST 생성</h3>
<p>HTML 파싱 중 <code>script</code> 태그를 인식하게 되면, 렌더링 엔진은 DOM 트리 생성을 중단하고 서버에서 해당 JavaScript 리소스를 브라우저 엔진으로부터 받아오게 된다. 그리고 JavaScript 엔진에게 제어권을 넘겨준다.</p>
<p>JavaScript 엔진은 받아온 JavaScript 리소스를 파싱하여 <strong>AST(추상 구문 트리)</strong>를 생성하고 이를 바이트 코드로 변환하여 실행한다. JavaScript 파싱이 종료되면 렌더링 엔진은 다시 제어권을 돌려받고 DOM 생성을 이어나가게 된다. </p>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/eee87334-284d-4cac-92c0-2bed1c7640b0/image.png" alt=""></p>
<p>만일 JavaScript 코드 내부에 DOM이나 CSSOM을 변경하는 DOM API가 사용된 경우, DOM이나 CSSOM이 변경되게 된다. 이때 변경된 DOM이나 CSSOM은 다시 렌더 트리로 결합되고 변경된 렌더 트리를 기반으로 레이아웃과 페인트 과정을 거쳐 브라우저의 화면에 다시 렌더링된다. 이를 <strong>리플로우, 리페인트</strong>라 한다.</p>
<h3 id="렌더-트리render-tree-생성">렌더 트리(Render Tree) 생성</h3>
<p>HTML과 CSS의 파싱 과정이 모두 끝나면, DOM 트리와 CSSSOM 트리를 서로 결합하여 <strong>렌더 트리 (Render Tree)</strong>가 생성되게 된다. 렌더 트리는 다음과 같은 과정을 통해 생성된다.</p>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/c99325d5-2df3-4621-8432-b5aaf8e5dc2e/image.png" alt=""></p>
<ol>
<li><p>html 태그와 body 태그를 처리하며 렌더 트리 루트를 구성한다.</p>
</li>
<li><p>DOM의 최상위 노드부터 순회하면서 화면에 보여지지 않는 노드를 렌더 트리의 구성에서 제외한다. (이때 <code>display:none</code>과 같이 공간을 차지하지 않는 태그가 적용된 노드는 렌더 트리의 구성에서 제외된다.)</p>
</li>
<li><p>화면에 보여지는 나머지 노드에 CSSOM 규칙을 찾아 일치하는 스타일을 적용한다.
(position이나 float를 사용했을 경우, 실제 그려지는 위치로 렌더 객체가 이동한다.)</p>
</li>
</ol>
<h3 id="레이아웃layout">레이아웃(Layout)</h3>
<p>렌더 트리 생성이 완료되면, 렌더 트리 내 각 노드의 위치, 크기를 계산하고 이를 화면에 배치하는 레이아웃(Layout) 과정이 진행된다. </p>
<p>이때 노드의 위치는 (x, y) 좌표계를 사용하며, 렌더 트리의 루트부터 아래로 내려가면서 계산을 진행한다. 최상위 노드의 위치는 (0, 0)이며, CSS에서 상대적인 모든 값들은 절대값인 px 단위로 변환된다.</p>
<h3 id="페인트paint">페인트(Paint)</h3>
<p>마지막으로, 레이아웃 과정에서 계산된 정보들을 바탕으로 각 노드를 화면에 그려주는 페인트(Paint) 과정이 진행된다. 페인트는 렌더 트리의 각 노드를 화면의 실제 픽셀로 변환해주는 작업이다. 픽셀로 변환하는 이 과정을 <strong>Rasterizing</strong>이라 한다. 페인트까지 마무리 되면 브라우저 화면에 구글 페이지가 나타난다.</p>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/662768ce-2897-4bf5-8384-8988eb5f8956/image.png" alt=""></p>
<h2 id="브라우저에게-감사하다">브라우저에게 감사하다.</h2>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/f372125a-7b5e-42ba-9bbd-da85e1e402e2/image.png" alt=""></p>
<p>이렇듯 우리가 단순히 <a href="http://www.google.com%EC%9D%84">www.google.com을</a> 주소창에 입력해 구글 페이지가 우리 눈앞에 나타나기까지, 정말 수많은 과정들을 브라우저는 진행하게 된다. 우리가 일일히 이런 작업을 안하고 쉽게 페이지를 이동하게 할 수 있는 브라우저? 감사하다.</p>
<h3 id="참고-자료">참고 자료</h3>
<p><a href="https://velog.io/@sylagape1231/%EC%A3%BC%EC%86%8C%EC%B0%BD%EC%97%90-naver.com%EC%9D%84-%EC%B9%98%EB%A9%B4-%EC%9D%BC%EC%96%B4%EB%82%98%EB%8A%94-%EC%9D%BC%EC%9D%84-%EC%89%BD%EA%B2%8C-%EC%9D%B4%ED%95%B4%ED%95%B4%EB%B3%B4%EC%9E%90">https://velog.io/@sylagape1231/%EC%A3%BC%EC%86%8C%EC%B0%BD%EC%97%90-naver.com%EC%9D%84-%EC%B9%98%EB%A9%B4-%EC%9D%BC%EC%96%B4%EB%82%98%EB%8A%94-%EC%9D%BC%EC%9D%84-%EC%89%BD%EA%B2%8C-%EC%9D%B4%ED%95%B4%ED%95%B4%EB%B3%B4%EC%9E%90</a></p>
<p><a href="https://velog.io/@tnehd1998/%EC%A3%BC%EC%86%8C%EC%B0%BD%EC%97%90-www.google.com%EC%9D%84-%EC%9E%85%EB%A0%A5%ED%96%88%EC%9D%84-%EB%95%8C-%EC%9D%BC%EC%96%B4%EB%82%98%EB%8A%94-%EA%B3%BC%EC%A0%95">https://velog.io/@tnehd1998/%EC%A3%BC%EC%86%8C%EC%B0%BD%EC%97%90-www.google.com%EC%9D%84-%EC%9E%85%EB%A0%A5%ED%96%88%EC%9D%84-%EB%95%8C-%EC%9D%BC%EC%96%B4%EB%82%98%EB%8A%94-%EA%B3%BC%EC%A0%95</a></p>
<p><a href="https://intrepidgeeks.com/tutorial/tcpip">https://intrepidgeeks.com/tutorial/tcpip</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[useMemo와 useCallback의 차이]]></title>
            <link>https://velog.io/@hang_kem_0531/useMemo%EC%99%80-useCallback%EC%9D%98-%EC%B0%A8%EC%9D%B4</link>
            <guid>https://velog.io/@hang_kem_0531/useMemo%EC%99%80-useCallback%EC%9D%98-%EC%B0%A8%EC%9D%B4</guid>
            <pubDate>Wed, 28 Sep 2022 06:49:25 GMT</pubDate>
            <description><![CDATA[<p>React Hooks에 대해 공부하면서 useMemo와 useCallback의 차이에 대해 궁금증이 생기게 되었다. 공부한 내용을 포스팅을 통해 정리하면서 다시 한번 짚어보고자 한다.</p>
<h2 id="memoization">Memoization?</h2>
<p>우선, useMemo와 useCallback에 대해 이해하려면 Memoization이라는 개념에 대한 이해가 필요하다. <strong>Memoization</strong>이란, 컴퓨터 프로그램이 동일한 계산을 반복해야 할 때, <strong>이전에 계산한 값을 메모리에 저장</strong>함으로써 <strong>동일한 계산의 반복 수행을 제거하여 프로그램 실행 속도를 빠르게</strong> 하는 기술이다. useCallback과 useMemo는 Memoization 기능을 제공하는 React의 내장 Hook으로, 퍼포먼스 최적화를 위해 사용한다.</p>
<h2 id="re-rendering">Re-rendering</h2>
<p>React는 다음과 같은 조건에서 리렌더링을 진행한다.</p>
<ul>
<li><p>자신의 state가 변경될 때</p>
</li>
<li><p>부모 컴포넌트로부터 전달받은 props가 변경될 때</p>
</li>
<li><p>부모 컴포넌트가 리렌더링 될 때</p>
</li>
<li><p>forceUpdate 함수가 실행될 때</p>
</li>
</ul>
<p>리렌더링이란, 이전에 생성한 컴포넌트 정보와 다시 렌더링한 정보를 비교해 최소한의 연산으로 DOM 트리를 업데이트 하는 것을 말한다. 즉, 이전의 Virtual DOM과 현재의 Virtual DOM을 비교하여 변경된 값에 대해 DOM 트리를 업데이트 해주는 것이다. </p>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/d5db67cd-7185-405b-8430-847e0d147f16/image.png" alt=""></p>
<p>기본적으로 useMemo와 useCallback은 리렌더링을 최적화하는데 도움이 되도록 만들어진 Hook이다. 이 Hook들은 주어진 렌더에서 수행해야 하는 작업의 양을 줄이고, 컴포넌트가 다시 렌더링해야 하는 횟수를 줄이면서 리렌더링을 최적화하게 된다.</p>
<h2 id="usememo">useMemo</h2>
<p>useMemo는 Memoization된 <strong>&#39;값&#39;</strong>을 반환하는 함수이다. </p>
<pre><code class="language-js">useMemo(() =&gt; fn, [])</code></pre>
<p>useMemo는 deps에 변화가 생기면, 내부에 정의된 콜백 함수를 실행하고 그 함수의 반환 <strong>값</strong>을 반환하게 된다. </p>
<pre><code class="language-js">import React, { useState, useCallback, useMemo } from &quot;react&quot;;

export default function App() {
  const [buttonX, setButtonX] = useState(0);
  const [buttonY, setButtonY] = useState(0);

  const handleButtonX = () =&gt; {
    setButtonX((prev) =&gt; prev + 1)
  };

  const handleButtonY = () =&gt; {
    setButtonY((prev) =&gt; prev + 1)
  };

  useMemo(() =&gt; {console.log(buttonX)}, [buttonX]);

  return (
    &lt;&gt;
      &lt;button onClick={handleButtonX}&gt;X&lt;/button&gt;
      &lt;button onClick={handleButtonY}&gt;Y&lt;/button&gt;
    &lt;/&gt;
  );
}</code></pre>
<p>예를 들어 위와 같은 코드를 구현하면, useMemo의 deps가 buttonX이기 때문에 X 버튼을 누를때만 <code>console.log(buttonX)</code>가 실행되게 된다. Y 버튼을 눌러도 함수 컴포넌트는 리렌더링되지만, buttonX 값은 변하지 않기 때문에 useMemo의 변화가 없게 되는 것이다.</p>
<h2 id="usecallback">useCallback</h2>
<p>반면에 useCallback은 Memoization된 &#39;값&#39;이 아닌 <strong>&#39;함수&#39;</strong>를 반환하는 함수이다. useMemo는 함수를 실행해서 그 실행 값을 반환하지만, useCallback은 함수 자체를 반환하는 것이다.</p>
<pre><code class="language-js">useCallback(fn, [])</code></pre>
<p>이때 useCallback은 deps의 변화가 생기게 되면, 새로운 함수를 반환하게 된다.</p>
<pre><code class="language-js">import React, { useState, useCallback, useMemo } from &quot;react&quot;;

function App() {
  const [buttonX, setButtonX] = useState(0);
  const [buttonY, setButtonY] = useState(0);

  const handleButtonX = () =&gt; {
    setButtonX((prev) =&gt; prev + 1)
  };

  const handleButtonY = () =&gt; {
    setButtonY((prev) =&gt; prev + 1)
  };

  const returnUseCallback = useCallback(() =&gt; {console.log(buttonY)}, [buttonX]);

  returnUseCallback();

  return (
    &lt;&gt;
      &lt;button onClick={handleButtonX}&gt;X&lt;/button&gt;
      &lt;button onClick={handleButtonY}&gt;Y&lt;/button&gt;
    &lt;/&gt;
  );
}

export default App;</code></pre>
<p>위 코드에서 useCallback 구문은 <code>() =&gt; {console.log(buttonY)}</code> 라는 함수를 반환하지만, deps인 buttonX의 변화가 생길때만 반환되게 된다. 그렇기 때문에 Y 버튼을 계속 누른다고 해도 X 버튼을 누르지 않으면 <code>() =&gt; {console.log(0)}</code>의 함수가 반환되고, X 버튼을 누를때에 Y값을 가져와 <code>() =&gt; {console.log(새로운값)}</code>이 반환되게 된다. </p>
<p>그렇기 때문에, useCallback은 함수와 상관없는 상태 값이 변할 때, 함수 컴포넌트에서 불필요한 업데이트가 일어나는 것을 방지해 준다. </p>
<h3 id="usecallback은-새로운-함수를-반환한다">useCallback은 새로운 함수를 반환한다.</h3>
<p>useCallback의 deps가 변환될 때 반환되는 함수는, 이전의 함수와 형태가 같지만 새로운 함수이다. 이는 새로운 무기명 함수를 반환한 것이며, 이전의 함수와 값이 같을 뿐 다른 메모리 주소를 가지고 있다.</p>
<h2 id="reactmemo">React.memo</h2>
<p>단순히 useCallback의 사용만으로는 하위 컴포넌트의 리렌더링을 방지할 수 없다. 하위 컴포넌트가 PureComponent 이어야만 비로소 불필요한 리렌더링을 막을 수 있다. 컴포넌트를 React.memo()로 래핑하면 해당 컴포넌트는 PureComponent가 된다.</p>
<blockquote>
<h4 id="reactpurecomponent-br">React.PureComponent <br></h4>
<p>React.PureComponent는 React.Component에 shouldComponentUpdate()가 적용된 버전이다.</p>
</blockquote>
<p>컴포넌트가 React.memo()로 래핑 될 때, React는 컴포넌트를 렌더링하고 결과를 Memoizing 한다. 그리고 다음 렌더링이 일어날 때 props가 같다면, React는 Memoizing 된 내용을 재사용한다.</p>
<pre><code class="language-js">export function Movie({ title, releaseDate }) {
  return (
    &lt;div&gt;
      &lt;div&gt;Movie title: {title}&lt;/div&gt;
      &lt;div&gt;Release date: {releaseDate}&lt;/div&gt;
    &lt;/div&gt;
  );
}

export const MemoizedMovie = React.memo(Movie);</code></pre>
<p>위 컴포넌트에서는 title이나 releaseDate 같은 props가 변경되지 않으면 다음 렌더링 때 메모이징 된 내용을 그대로 사용하게 된다.</p>
<h2 id="usememo와-usecallback을-사용하지-말아야-할-경우">useMemo와 useCallback을 사용하지 말아야 할 경우</h2>
<ol>
<li><p>host 컴포넌트에 (div span a img...) 전달하는 모든 항목에 대해 쓰지 말아야한다. 리액트는 여기에 함수 참조가 변경되었는지 신경쓰지 않는다. (ref, mergeRefs는 여기에서 제외된다.)</p>
</li>
<li><p>useCallback useMemo의 의존성 배열에 완전히 새로운 객체와 배열을 전달해서는 안된다. 이는 항상 의존성이 같지 않다는 결과를 의미하며, 메모이제이션을 하는데 소용이 없다. useEffect useCallback useMemo의 모든 종속성은 참조 동일성을 확인한다.</p>
</li>
<li><p>전달하려는 항목이 새로운 참조여도 상관없다면, 사용하지 말아야 한다. 매번 새로운 참조여도 상관없는데, 새로운 참조라면 메모이제이션하는 것이 의미가 없다.</p>
</li>
</ol>
<h2 id="usememo와-usecallback을-사용해야-할-경우">useMemo와 useCallback을 사용해야 할 경우</h2>
<ol>
<li><p>계산 비용이 많이 들고, 사용자의 입력 값이 map과 filter을 사용했을 때와 같이 이후 렌더링 이후로도 참조적으로 동일할 가능성이 높은 경우, useMemo를 사용하는 것이 좋다.</p>
</li>
<li><p>ref 함수를 부수작용과 함께 전달하거나, mergeRef-style 과 같이 wrapper 함수 ref를 만들 때 useMemo를 쓰자. ref 함수가 변경이 있을 때마다, 리액트는 과거 값을 null로 호출하고 새로운 함수를 호출한다. 이 경우 ref 함수의 이벤트 리스너를 붙이거나 제거하는 등의 불필요한 작업이 일어날 수 있다. 예를 들어, useIntersectionObserver가 반환하는 ref의 경우 ref 콜백 내부에서 observer의 연결이 끊기거나 연결되는 등의 동작이 일어날 수 있다.</p>
</li>
<li><p>자식 컴포넌트에서 useEffect가 반복적으로 트리거되는 것을 막고 싶을 때 사용하자.</p>
</li>
<li><p>매우 큰 리액트 트리 구조 내에서, 부모가 리렌더링 되었을 때 이에 다른 렌더링 전파를 막고 싶을 때 사용하자. 자식 컴포넌트가 React.memo React.PureComponent일 경우, 메모이제이션된 props를 사용하게되면 딱 필요한 부분만 리렌더링 될 것이다.</p>
</li>
</ol>
<h3 id="참조한-글">참조한 글</h3>
<p><a href="https://yceffort.kr/2022/04/best-practice-useCallback-useMemo#usememo%EC%99%80-usecallback%EC%9D%84-%EC%82%AC%EC%9A%A9%ED%95%B4%EC%95%BC-%ED%95%98%EB%8A%94-%EA%B2%BD%EC%9A%B0">https://yceffort.kr/2022/04/best-practice-useCallback-useMemo#usememo%EC%99%80-usecallback%EC%9D%84-%EC%82%AC%EC%9A%A9%ED%95%B4%EC%95%BC-%ED%95%98%EB%8A%94-%EA%B2%BD%EC%9A%B0</a></p>
<p><a href="https://ui.toast.com/weekly-pick/ko_20190731">https://ui.toast.com/weekly-pick/ko_20190731</a></p>
<p><a href="https://basemenks.tistory.com/238">https://basemenks.tistory.com/238</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[타입스크립트 올인원 - 1]]></title>
            <link>https://velog.io/@hang_kem_0531/%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EC%98%AC%EC%9D%B8%EC%9B%90-1</link>
            <guid>https://velog.io/@hang_kem_0531/%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EC%98%AC%EC%9D%B8%EC%9B%90-1</guid>
            <pubDate>Mon, 19 Sep 2022 06:20:07 GMT</pubDate>
            <description><![CDATA[<h2 id="타입스크립트를-왜-사용해야-할까">타입스크립트를 왜 사용해야 할까?</h2>
<p>프로그래밍 언어를 배우는 이유는 실제로 사용하는 프로그램을 만들기 위해서이고, 실제로 사용하는 프로그램에서 에러가 발생하는 것은 문제가 된다. 자바스크립트 프로그램을 타입스크립트로 바꾸면 안정성이 늘어나고 에러가 덜 발생하게 된다. 다만, 타입스크립트가 자바스크립트의 모든 에러를 잡아주는 것은 아니고 실수를 줄여준다고 보면 된다.</p>
<p>타입스크립트는 자바스크립트보다 자유도가 떨어질 수 있다는 단점도 있지만, 실무에서는 자유도보다는 에러를 줄이는 것을 더 중시해야 한다. </p>
<hr>
<h2 id="기본-지식">기본 지식</h2>
<h3 id="메인-룰">메인 룰</h3>
<p><strong>TypeScript는 최종적으로 JavaScript로 변환된다.</strong></p>
<ul>
<li><p>TypeScript는 언어이자 컴파일러(tsc)이다. 컴파일러는 ts 코드를 js로 바꿔준다.</p>
</li>
<li><p>tsc는 tsconfig.json에 따라 ts 코드를 js로 바꿔준다. 인풋인 ts와 아웃풋인 js 모두에게 영향을 미치므로 tsconfig.json 설정을 반드시 확인해야 한다.</p>
</li>
<li><p>ts 코드를 js 코드로 바꾸는 것이 아니라 단순히 타입 검사만 하고 싶다면 tsc --noEmit 하면 된다.</p>
</li>
<li><p>ts 파일을 실행하는 것이 아니라 결과물인 js를 실행해야 한다.</p>
</li>
<li><p>VS Code나 WebStorm 같은 에디터가 필수이다.</p>
</li>
</ul>
<hr>
<h2 id="ts-문법">ts 문법</h2>
<p>기본적으로 자바스크립트의 변수, 속성, 매개변수, 리턴값에 타입이 붙은 것을 타입스크립트라고 생각하면 된다. 타입은 항상 소문자로 시작해야 한다.</p>
<pre><code class="language-ts">const a: string = &#39;5&#39;;
const b: number = 5;
const c: boolean = true;
const d: undefined = undefined;
const e: null = null;
const f: any = true;

const obj: { lat: number, lon: number } = { lat: 37.5, lon: 127.5 };</code></pre>
<p>이때 any 타입은 아무 타입이나 적용이 되기 때문에, 타입스크립트가 아니라 자바스크립트로 인식이 되는것과 마찬가지이다. 그렇기 때문에 any를 최대한 쓰지 않는 것을 목표로 해야 한다.</p>
<pre><code class="language-ts">function add(x: number, y: number): number { return x + y }
//const add: (x: number, y: number) =&gt; number = (x, y) =&gt; x + y;
type Add = (x: number, y: number) =&gt; number;
const add: Add = (x, y) =&gt; x + y;</code></pre>
<p>함수의 경우에는 매개변수 뒤에 리턴값 타입을 선언해주며, 화살표 함수 역시 같은 방법으로 타이핑할 수 있지만, type을 별도로 분리해주어 선언할 경우 더 가독성이 좋은 코드를 작성할 수 있다. 타입스크립트는 타입을 지웠을 때 자바스크립트 코드가 나와야 하는 연습을 해야 한다.</p>
<pre><code class="language-ts">interface Add {
  (x: number, y: number): number
}

const add: Add = (x, y) =&gt; x + y;</code></pre>
<p>interface 역시 타입을 선언해주는 방법 중에 하나이다.</p>
<pre><code class="language-ts">const arr: string[] = [&#39;123&#39;, &#39;456&#39;];
const arr2: Array&lt;number&gt; = [123, 456];
const arr3: [number, number, string] = [123, 456, &#39;789&#39;];</code></pre>
<p>배열은 위와 같은 방법으로 타입을 지정해주며, arr2는 제네릭 타입, arr3은 tuple로 배열의 길이가 고정된 타입이다. </p>
<pre><code class="language-ts">const f: true = true;
const g: 5 = 5;</code></pre>
<p>위와 같은 고정된 원시값을 타입을 아예 지정해줄 수도 있다. </p>
<pre><code class="language-ts">let aa = 123;
aa = &#39;hello&#39; as unknown as number;</code></pre>
<p>타입스크립트에서는 시스템이 추론 및 분석한 타입 내용을 변환해줄 수도 있다. 이때 &quot;타입 표명(type assertion)&quot;이라 불리는 매커니즘이 사용되는데, TypeScript의 타입 표명은 프로그래머가 컴파일러에게 타입에 더 잘 알고 있다는 가정하에 사용된다. </p>
<p>타입스크립트는 자바스크립트보다 자유도가 낮아진다는 단점이 있지만, 실무에서도 타입을 갑자기 변환해주는 일은 거의 없기 때문에 자유도 측면에서 단점이라고 여길 수 없다. </p>
<pre><code class="language-ts">try {
  const array = [];
  array[0];
} catch(error) {
  error;
}</code></pre>
<p>타입스크립트에서 빈 배열은 never라는 타입으로 지정된다. 타입스크립트에서 never 타입은 값의 공집합이다. 집합에 어떤 값도 없기 때문에, never 타입은 any 타입의 값을 포함해 어떤 값도 가질 수 없다. </p>
<p>숫자 체계에 아무것도 없는 양을 나타내는 0처럼 문자 체계에도 불가능을 나타내는 타입이 필요하다.</p>
<p>&quot;불가능&quot;이라는 단어 자체는 모호하다. 타입스크립트에서는 &quot;불가능&quot;을 아래와 같이 다양한 방법으로 나타내고 있다.</p>
<ul>
<li><p>값을 포함할 수 없는 빈 타입</p>
</li>
<li><p>제네릭과 함수에서 허용되지 않는 매개변수</p>
</li>
<li><p>호환되지 않는 타입들의 교차 타입</p>
</li>
<li><p>빈 합집합(무의 합집합)</p>
</li>
<li><p>실행이 끝날 때 호출자에게 제어를 반환하지 않는 함수의 반환 타입</p>
</li>
<li><p>절대로 도달할수 없을 esle 분기의 조건 타입</p>
</li>
<li><p>거부된 프로미스에서 처리된 값의 타입</p>
</li>
</ul>
<pre><code class="language-ts">const head = document.querySelector(&#39;#head&#39;)!;
console.log(head);

const head = document.querySelector(&#39;#head&#39;);
if (head) {
  console.log(head);
}</code></pre>
<p>타입스크립트에서 !는 Definite Assignment Assertions으로 해당 변수의 타입을 확신할 경우에 붙여줄 수 있다. 하지만, 타입이 변할 수도 있고 타입을 확신하는 것은 에러가 발생할 수 있기 때문에 사용을 지양하고 if문을 사용하는 편이 좋다.</p>
<pre><code class="language-ts">type World = &quot;world&quot; | &quot;hell&quot;;
const a: World = &#39;world&#39;;

const b: `hello ${a}`;

//type Greeting = &#39;hello world&#39;
type Greeting = `hello ${World}`;
const c: Greeting = &#39;hell&#39;</code></pre>
<pre><code class="language-ts">const enum EDirection {
  Up,
  Down,
  Left,
  Right,
}

const a = EDirection.Up // 0
const b = EDirection.Left // 2</code></pre>
<p>enum은 열거형 변수로 정수를 하나로 합칠 때 편리한 기능이다. 임의의 숫자나 문자열을 할당할 수 있으며 하나의 유형으로 사용해서 버그를 줄일 수 있다.</p>
<pre><code class="language-ts">type A = { a: string };
const a: A = { a: &#39;hello&#39; };

interface B { a: string };
const b: B = { a: &#39;hello&#39; };</code></pre>
<p>간단한 타입을 지정하고 싶을 때는 type, 객체지향형 프로그래밍을 구현하고 싶을 때는 interface를 사용하는 편이 좋다. </p>
<pre><code class="language-ts">function add(x: string | number, y: string | number): string | number { return x + y }
// union, 여러 속성 중 하나만 있어도 된다.
const result: string | number = add(1, 2);
result.charAt()
add(&#39;1&#39;, &#39;2&#39;)
add(1, &#39;2&#39;)

type A = { hello: &#39;world&#39; } &amp; { name: &#39;kyeom&#39; };
// intersection, 모든 속성이 다 있어야 한다.
const a: A = { hello: &#39;world&#39;, name: &#39;kyeom&#39; };</code></pre>
<pre><code class="language-ts">type Animal = { breath: true };
type Mammal = Animal &amp; { breed: true };
type Human = Mammal &amp; { think: true };

const Kyeom: Human = { breath: true, breed: true, think: true };</code></pre>
<p><code>|</code>는 Union이라 부르며, 또는 관계이기 때문에 타입을 여러개 넣을 수 있다. 그러나 타입스크립트는 모든 경우의 수를 고려하기 때문에, 타입 추론이 잘 되지 않는다는 단점이 있다. 타입스크립트를 사용할 때에는 처음 타입을 제대로 선언하지 않으면 이후의 모든 코드가 에러가 발생할 위험이 있다. </p>
<p><code>&amp;</code>는 여러개의 타입이 모두 만족할때에 사용한다. 보통 객체에 사용하며, 객체의 모든 속성이 타입과 같아야 한다. 상속의 개념으로 생각하면 편하다.</p>
<pre><code class="language-ts">interface A {
  talk: () =&gt; void;
}
interface A {
  eat: () =&gt; void;
}
interface A {
  laugh: () =&gt; void;
}

const a: A = { talk() {}, eat() {}, shit() {} }</code></pre>
<p>interface는 여러번 선언할 수 있으며, 선언할 때마다 합쳐지게 된다. 라이브러리들은 대다수가 interface로 선언되어 있으며, 사용자가 추가적으로 수정할 수 있다. void는 자바스크립트에 없는 키워드로, 함수에 해당 함수가 아무것도 반환하지 않는다는 것을 명시해주는 타입이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[CSR vs SSR vs SSG]]></title>
            <link>https://velog.io/@hang_kem_0531/CSR-vs-SSR-vs-SSG</link>
            <guid>https://velog.io/@hang_kem_0531/CSR-vs-SSR-vs-SSG</guid>
            <pubDate>Thu, 15 Sep 2022 05:59:37 GMT</pubDate>
            <description><![CDATA[<p>이번 글에서는 Next.js의 장점인 SSR에 대해 알아보면서 CSR과 SSG와는 어떤 차이점이 있고 어떠한 장, 단점들이 있는지 알아보고자 한다.</p>
<h2 id="csr">CSR?</h2>
<p>CSR이란, Client Side Rendering의 줄임말로 SPA가 등장하면서 함께 나타나게 된 개념이다. 그렇다면 SPA에 대해서 먼저 짚어보고 넘어가도록 하자.</p>
<h3 id="spa">SPA</h3>
<p>기존의 전통적인 웹 사이트는 현재의 웹 사이트에 비해 문서 하나에 전달되는 파일의 용량이 적었다. 그렇기 때문에 서버에서 요소를 클릭할 때마다 완전히 새로운 페이지를 전송해 주는 것에도 문제가 되지 않았다. 그러나 현재에 이르러 점차 웹 사이트가 고도화됨에 따라서 페이지 용량이 증가했고, 매번 새로운 페이지를 전송해 주는것이 버거워지게 되었다.</p>
<p>이러한 문제를 해결하기 위해 등장한 것이 바로 SPA(Single Page Application)이다. SPA는 최초 한 번 페이지 전체를 로딩한 이후부터는 데이터만 변경하여 사용하는 단일 페이지로 구성된 웹 애플리케이션이다. SPA에서는 화면 구성에 필요한 모든 HTML을 클라이언트가 갖고 있고 서버 측에는 필요한 데이터를 요청하고 JSON으로 받기 때문에 기존의 어플리케이션에 비해 화면을 구성하는 속도가 빠르다.</p>
<p>SPA 프레임워크로는 React, Vue, Angular 등이 있는데 이들은 Virtual DOM이라는 개념을 사용해 SPA를 구현한다. SPA의 문제점은 자바스크립트로 인한 DOM 조작이 빈번하게 일어나 브라우저의 성능을 저하시킨다는 것인데, 이때 실제 DOM 트리를 흉내 낸 가상의 객체 트리로 html 정보를 저장하고 있다가, 이 트리에 변경이 발생하면 모든 변화를 모아 단 한번 브라우저를 호출해 화면을 갱신하는 방법을 사용한다.</p>
<h3 id="client-side-rendering">Client Side Rendering</h3>
<p>이 SPA는 앞서 서술한 것처럼, Client Side Rendering 방식으로 동작한다. CSR은 클라이언트 측에서 최초에 1번 서버에서 전체 페이지를 로딩하여 보여준다. 그 이후에는 사용자의 요청이 올 때마다, 자원(Resource)을 서버에서 제공한 후, 클라이언트가 해석하고 렌더링 하는 방식이다. </p>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/d4ae5565-e924-4778-a8a0-405f7a7cee9d/image.png" alt="">
(사진 출처: <a href="https://rockcontent.com/blog/client-side-rendering-vs-server-side-rendering/">https://rockcontent.com/blog/client-side-rendering-vs-server-side-rendering/</a>)</p>
<ol>
<li><p>유저가 웹사이트에 요청을 보낸다.</p>
</li>
<li><p>브라우저는 HTML파일과 JS파일의 링크를 CDN을 통해 전달받는다.</p>
</li>
<li><p>HTML과 JS파일을 다운받는다. 다운받는 동안 유저는 사이트를 볼 수 없다.</p>
</li>
<li><p>다운로드가 완료되면 JS가 실행되고, 데이터를 위한 API를 불러온다. 이 과정에서 유저는 placeHolder를 보게된다.</p>
</li>
<li><p>서버에서 API요청에 대한 응답을 보내준다.</p>
</li>
<li><p>서버에서 받아온 데이터를 placeHolder자리에 넣는다. 지금부터 클라이언트는 상호작용이 가능하다.</p>
</li>
</ol>
<h3 id="client-side-rendering의-장점">Client Side Rendering의 장점</h3>
<ul>
<li>트래픽 감소와 빠른 인터랙션
CSR은 사용자의 행동에 따라 필요한 부분만 다시 읽어 들이기 때문에 서버 측에서 전체 페이지를 다시 읽어 들이는 것보다 빠른 인터랙션이 가능하다. 서버의 부담 역시 줄어든다.</li>
</ul>
<h3 id="client-side-rendering의-단점">Client Side Rendering의 단점</h3>
<ul>
<li><p>초기 구동 속도가 느림
CSR은 서버에서 View를 렌더링하지 않고 브라우저에서 HTML, JavaScript를 포함한 각 자원들을 다운로드한 후에 렌더링하기 때문에 초기 구동 속도가 느리다.</p>
</li>
<li><p>검색 엔진 최적화 (SEO)가 어려움
CSR방식으로 이루어진 웹 페이지에서는 View를 생성하기 위해선 반드시 JavaScript를 실행시켜야 하는데, 대부분의 웹 크롤러 봇들은 JavaScript파일을 실행시키지 못하기 때문에 HTML에서만 콘텐츠들을 수집하게 되고 CSR페이지를 빈 페이지로 인식하게 된다. 그렇기 때문에 검색 엔진에 노출이 되지 않는다.</p>
</li>
<li><p>보안 문제가 있음
CSR에서는 쿠키 말고는 사용자 정보를 저장할 공간이 마땅치 않기 때문에 보안에 위험이 생길 수 있다. </p>
</li>
</ul>
<hr>
<h2 id="ssr">SSR</h2>
<p>리액트는 CSR 기반 SPA 라이브러리이기 때문에, SSR을 구동하려면 Next.js와 같은 라이브러리를 통한 구동이 필요하다. 그렇다면, SSR이 무엇인지 알아보자.</p>
<h3 id="server-side-rendering">Server Side Rendering</h3>
<p>SSR은 서버에서 사용자에게 보여줄 페이지를 모두 구성하여 사용자에게 페이지를 보여주는 방식으로, 기존의 전통적인 웹 브라우저 렌더링 방식이다. 사용자가 웹 페이지에 접근할 때, 서버에 페이지에 대한 요청을 하는데, 이때 서버에서는 HTML, View와 같은 자원(Resource) 들을 어떻게 보여줄지 해석하고 렌더링 하여 사용자에게 반환한다. 즉, 서버에서 미리 렌더링을 해서 사용자에게 반환하는 방식의 렌더링이다.</p>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/e80e7373-c9b6-419c-8bfb-16e8aac5e600/image.png" alt=""></p>
<ol>
<li><p>유저가 웹사이트에 요청을 보낸다.</p>
</li>
<li><p>서버가 &quot;Ready to Render&quot; 즉시 렌더링 가능한 HTML 파일을 만들어서 클라이언트에 넘긴다.</p>
</li>
<li><p>클라이언트가 HTML파일을 받아 렌더링한다. 하지만 JS파일이 로드되지 않아 상호작용은 불가능하다.</p>
</li>
<li><p>브라우저가 JS파일을 다운로드한다.</p>
</li>
<li><p>JS파일이 받아지는 동안 유저는 컨텐트를 볼 순 있지만 상호작용을 할 순 없다.( 다운받는 동안 일어난 상호작용은 모두 기록해놓는다.)</p>
</li>
<li><p>브라우저가 JS FrameWork를 실행시킨다.</p>
</li>
<li><p>기록되었던 상호작용이 실행된다. 지금부터 클라이언트는 상호작용이 가능하다.</p>
</li>
</ol>
<h3 id="server-side-rendering의-장점">Server Side Rendering의 장점</h3>
<ul>
<li><p>검색 엔진 최적화(SEO) 가능</p>
</li>
<li><p>초기 로딩 속도가 빠름
처음 렌더링된 HTML을 클라이언트에게 전달해주기 때문에 초기 로딩 속도를 줄일 수 있다. 또한 JavaScript파일을 불러오고 렌더링 작업이 완료되지 않아도 사용자가 콘텐츠를 이용할 수 있다.</p>
</li>
</ul>
<h3 id="server-side-rendering의-단점">Server Side Rendering의 단점</h3>
<ul>
<li><p>프로젝트 구조가 복잡해짐
Redux, React Router 등 여러 라이브러리와 함께 연동하여 서버에서 데이터를 가져와 렌더링 하는 상황이 발생한다면, 프로젝트가 많이 복잡해질 수 있다.</p>
</li>
<li><p>매번 새로고침이 발생
SSR은 서버에 요청할 때마다 새로고침이 발생한다. 매번 새로고침이 발생한다는 것은 서버와 잦은 응답을 한다는 의미이며 자연스럽게 서버에 부담을 주게 된다.</p>
</li>
<li><p>TTV와 TTI의 공백이 발생
TTV(Time to View)와 TTI(Time to Interact), 즉 사용자가 웹 브라우저에서 내용을 볼 수 있는 시점과 사용자가 웹 브라우저에서 인터랙션 할 수 있는 시점 사이에 공백이 꽤 길게 발생한다. 그렇기 때문에 SSR을 사용시에 사용자가 브라우저를 보고 인터랙션을 할 수 있는 시간차를 줄이기 위해 고민해야한다.</p>
</li>
</ul>
<hr>
<h2 id="ssg">SSG?</h2>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/a5e5e0a6-0085-4b9c-8a95-5961fb8360e8/image.png" alt=""></p>
<p><del>(신세계몰 아님)</del></p>
<p>SSG는 Static Site Generator의 줄임말로, SSR과 pre-rendering을 동적으로 해서 페이지를 생성하느냐, 정적으로 페이지를 생성하느냐의 차이가 있다.</p>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/20252bbf-192c-48af-a903-ae4b7202fdaf/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/2d687dbf-d87d-42f6-8827-3c82f22ca176/image.png" alt=""></p>
<p>SSG는 빌드를 진행할 때 pages 폴더에서 작성한 각 페이지들에 대해 각각의 문서를 생성해서 static한 파일로 생성해놓은 뒤, 만약 해당 페이지에 대한 요청이 발생하게 되면, 이 페이지들을 재생성하는 것이 아니라 이미 생성이 된 페이지를 반환하는 형태로 동작하게 된다. 따라서 서버와 클라이언트 측 모두 렌더링을 위해서 할 동작이 별로 없기 때문에 SSG로 생성된 웹사이트는 속도가 매우 빠르다는 특징이 있다.</p>
<p>SSG로 생성된 웹사이트는 미리 만들어놓은 수 많은 웹페이지로 이루어졌기 때문에 검색 엔진이 크롤링하기 매우 적합한 구조를 가지게 된다. 따라서 검색 엔진 최적화(SEO)가 중요한 마켓팅 웹사이트를 제작할 때는 SSG가 매우 보편적으로 사용된다.</p>
<h2 id="렌더링-방식-선택-기준">렌더링 방식 선택 기준</h2>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/78bce543-1d4f-4b1f-b40a-d9ebe6f1e20d/image.png" alt="">
(사진 출처: <a href="https://miracleground.tistory.com/165">https://miracleground.tistory.com/165</a>)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Market Kurly Hackerton 회고록 - 2]]></title>
            <link>https://velog.io/@hang_kem_0531/Market-Kurly-Hackerton-%ED%9A%8C%EA%B3%A0%EB%A1%9D-2-v9lrnaye</link>
            <guid>https://velog.io/@hang_kem_0531/Market-Kurly-Hackerton-%ED%9A%8C%EA%B3%A0%EB%A1%9D-2-v9lrnaye</guid>
            <pubDate>Thu, 01 Sep 2022 14:14:13 GMT</pubDate>
            <description><![CDATA[<h2 id="개발-시작">개발 시작!</h2>
<h3 id="프로젝트">프로젝트</h3>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/81343a50-d4b2-4576-ba96-b10ebb1dd87f/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/b0e39ba0-ca36-4e8a-9144-7205fb087b00/image.png" alt=""></p>
<h4 id="프로젝트-명--kurmmunity-마켓컬리-레시피-커뮤니티">프로젝트 명 : Kurmmunity (마켓컬리 레시피 커뮤니티)</h4>
<h4 id="팀-명--보리꼬리-borikkori">팀 명 : 보리꼬리 (Borikkori)</h4>
<h3 id="기간">기간</h3>
<p>2022.08.19 ~ 08.24</p>
<h3 id="인원">인원</h3>
<p>FrontEnd 3명, BackEnd 1명</p>
<h3 id="기술-스택">기술 스택</h3>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/c7b8996a-54ec-47c2-b968-2fb799dc5564/image.png" alt=""></p>
<p>원래는 타입스크립트까지 적용을 해 볼 생각이었지만, 아직 타입스크립트에 대한 숙련도 부족과 시간적 한계로 인해 타입스크립트 적용은 배제하기로 하였다. 또한 초기 계획 단계에 포함되었었던, 리뷰 게시판과 팁 게시판 역시 시간이 부족할 것 같아 &#39;레시피 커뮤니티&#39;에 중점을 두어서 레시피 게시판 위주의 개발이 진행되었다.</p>
<h3 id="협업-도구">협업 도구</h3>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/3cf79927-e897-4526-a7d5-bd811f3bd523/image.png" alt=""></p>
<p>협업 도구로는 가장 많이 사용해본 트렐로를 사용하였다. 이제 Agile    방식으로 프로젝트를 진행하는 것도 익숙해졌고, 트렐로를 사용하여 프로젝트의 진행이 어떻게 되고 있는지, 내가 백엔드 혹은 다른 프론트엔드 개발자와 맞춰봐야 할 부분이 어떠한 것인지에 대해서도 쉽게 인식할 수 있었다. 그래서 이번 프로젝트에서는 각자 담당한 부분의 구현에 있어서는 시간이 부족하거나 서로 커뮤니케이션이 부족하여 생긴 오류는 발생하지 않았었다.</p>
<h3 id="시연-영상">시연 영상</h3>
<p>!youtube[Bk5nmkSkW0U]</p>
<h2 id="내가-구현한-부분">내가 구현한 부분</h2>
<h3 id="best-recipe">Best Recipe</h3>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/094e43b9-643f-4b85-aff7-9d43a083e973/image.gif" alt=""></p>
<p>베스트 레시피는 지난 프로젝트에서 구현해 보고 싶었지만 기회가 없어서 사용해보지 못했던 Infinite Scroll을 통해서 데이터를 호출하는 방식으로 구현하였다. <code>document.documentElement.scrollTop</code>, <code>window.innerHeight</code>, <code>document.documentElement.scrollHeight</code>를 사용하여 스크롤을 최대로 내릴시에 현재 화면의 위치 + 현재 화면의 높이 = 전체 화면의 높이가 될 때 새로운 데이터를 요청하는 방식과, <code>Intersection Observer</code>를 활용하여 Infinite Scroll을 구현하는 방식이 있었지만, 이 두 가지 모두 리플로우나 throttle과 같은 불필요한 메서드를 사용해야 한다는 단점이 있었기 때문에 결국 react-infinite-scroll-component 라이브러리를 활용하여 구현하게 되었다.</p>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/f9b25fc9-247b-4a2a-a305-4374e4689a8f/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/ee4a1ad6-3b86-47fc-b8a4-c4c43f5d3994/image.png" alt=""></p>
<p>사실 개발자라면, 라이브러리를 쓰는 것에 있어서 일종의 경각심을 가져야 한다고 생각하는 편이지만, 기존 로직이나 구현에 있어서 불필요한 메서드 사용이나 메모리 공간의 차지가 발생된다면 라이브러리를 쓰는 것도 고려해봐야 한다는 생각이다. 하지만, 라이브러리를 사용하기 전에 다른 방법들에 대한 장, 단점과 구동 방식에 대한 명확한 이해를 통해 라이브러리를 왜 써야 하는지에 대한 이유를 잘 알고 써야 될 것이다.</p>
<h3 id="kurly-recipe--user-recipe-list">Kurly Recipe &amp; User Recipe List</h3>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/79885ebf-79d1-4e20-9d29-6c874bddbb2c/image.gif" alt=""></p>
<p>다음으로 구현을 담당했던 부분은 커뮤니티의 게시물 리스트인 컬리의 레시피 페이지와 회원 레시피 페이지였다. 사실 백엔드에게 데이터를 fetching 해와서 보여주기만 하면 되는 간단한 기능이었지만, 이번에도 중복 필터링이 말썽을 부렸다. </p>
<p>그냥 리액트의 경우에서는, query parameter를 가져올 때 window.location.search를 활용하여 가져올 수 있었지만, Next.js에서는 서버사이드 렌더링을 할 때 초기에 window와 전역 객체가 선언되지 않기 때문에 해당 객체를 참조할 수 없는 에러가 발생한다.</p>
<p>나는 이 사실을 모른 채, 단순히 router.push안에 query를 선언해 주고, 이를 axios에서 get으로 받아오기만 하면 되겠거니 생각했지만, 생전 처음보는 에러에 이를 해결하기 위해 꽤나 골머리를 썩었었다.</p>
<p>후에 구글링을 통해 해결한 방법은, useEffect 안에서는 DOM 형성 후에 실행되는 API이기 때문에 window.location.search가 가능하다는 것이었다. 이 밖에도, next.js에서 제공하는 dynamic이라는 함수로 SSR 옵션을 종료하거나 <code>typeof window !== &#39;undefined&#39;</code>를 사용하여 window 객체가 있는지 체크하는 방법 역시 존재한다. </p>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/1889cd03-ef56-411a-b8b4-4317ecb75b11/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/facc8b47-d18f-4ffb-95ce-cfe72726fa17/image.png" alt=""></p>
<p>나는 useEffect를 통해 window.location.search 를 사용하여 백엔드에게 보내는 fetch API에 query string을 전달할 수 있었다. 사실 이건 내 무지에서 비롯된 오류였는데, Next.js를 제대로 이해하지 못하고 무작정 새로운 스택을 도입하려는 오만에서 이러한 사단이 발생하지 않았나 싶다.</p>
<h2 id="프로젝트를-진행하면서-잘한-점">프로젝트를 진행하면서 잘한 점</h2>
<h3 id="가장-성공적이었던-협업">가장 성공적이었던 협업</h3>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/85733922-f546-4169-b0b0-a1d025e2d4f3/image.gif" alt=""></p>
<p>여태 두 번의 프로젝트와 한 번의 기업협업을 진행하면서, 물론 동료 개발자들과의 소통을 통한 협업들을 경험을 했었지만 이번 프로젝트에서 가장 의미 그대로의 협업을 경험하지 않았나 싶다. 이전의 프로젝트들은 사실상 기존의 사이트에서 모티브를 얻어 우리가 필요한 기능들만 추가적으로 구현하면 되었기 때문에, 실질적으로 협업을 통한 작업은 크게 필요하지 않았고, 단지 백엔드와 API 통신에 있어서 키 값을 맞춘다거나, 라우팅을 위한 프론트엔드 개발자와의 소통 정도만 필요한 정도였다. 그러나 이번 프로젝트는, 기획 단계에서부터 배포까지 전부 주도적으로 진행했던 프로젝트였기 때문에 기획 단계에서의 아이디어 회의, 디자인 수정, 개발 과정과 배포까지 정말 많은 부분들에서 회의와 의견 조율등이 필요했다.</p>
<p>실무 프로세스가 정확히 어떤 과정으로 이루어지는 지 아직 경험해보지 못했지만, 이번 프로젝트의 경험들을 통해서 실무에 가서도 협업에 큰 어려움을 겪지 않을 것이라는 확신이 생겼다. 무엇보다 디자이너의 중요성, 더 나아가 팀장과 체계의 중요성에 대해 깨달았기 때문에 앞으로의 협업에 있어서도 주도적인 자세와 적극적인 참여로 임해 프로젝트의 완성도를 높이는데 기여해야겠다고 느꼈다.</p>
<h2 id="프로젝트를-진행하면서-아쉬웠던-점">프로젝트를 진행하면서 아쉬웠던 점</h2>
<h3 id="새로운-스택에-대한-이해-부족">새로운 스택에 대한 이해 부족</h3>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/b557c3f4-690e-4f9d-af21-25000f9389ba/image.png" alt=""></p>
<p>매번 프로젝트를 진행할 때마다 빠졌던 딜레마였다. 이번 프로젝트에서는 지난 프로젝트에서 사용해보지 않았던 새로운 스택을 써서 진행해보고 싶다는 생각이 항상 있지만, 정작 사용해보면 미처 숙련되지도 못한채 사용한 코드들이 대다수였다. 개발자라면, 본인의 코드가 왜 이곳에서 사용되었고 어떠한 의미와 효율성을 가졌는지 설명할 수 있어야 할 것이다. 이번 프로젝트에서 내가 그랬었냐고 물어본다면, &#39;그렇다&#39;라고 확답을 내릴수는 없을 것 같다.</p>
<p>이번 프로젝트에서는 Next.js를 도입해서 개발을 진행했지만, 정적 내가 사용했던 Next.js의 기능은 Automatic Routing과 Next/router 뿐이었다. 서버 사이드 렌더링을 통한 SEO가 주된 사용 이유인 Next.js의 장점을 제대로 이용하지 못한 것이다. 더군다나, Next.js를 사용해서 window 객체에 접근하지 못한 채 이유를 알지도 못하고 오류를 수정하는데 오랜 시간이 걸렸었고, <code>getServerSideProps</code>나 <code>getStaticProps</code>를 통해 데이터를 fetching 해왔어야 했는데, 그렇게 구현도 못하고 API 역시 별도의 폴더에 구분해놓지 못한 채 컴포넌트 안에서 정의한 것 역시 아쉬운 기억으로 남는다.</p>
<p>새로운 스택에 대한 욕심은, 결국 본인이 그 스택을 왜 사용하고 어떻게 사용하는지에 대한 숙련도가 없으면 자칫 오만이 되기 십상이다. 새 스택을 도입하고 싶으면 그것에 대한 일정 수준 이상의 이해도와 기존 스택과 비교하여 장단점을 정확히 파악하고 사용하는 습관을 들이도록 해야겠다.</p>
<h2 id="마치며">마치며</h2>
<p>첫 해커톤을 참가하면서, 정말 아쉽기도 했지만 내가 개발을 해오면서 가장 재밌었던 경험이 아니었나 싶다. 고등학교, 대학교 시절에도 안 새본 밤을 새보면서 코딩을 하고, 내 아이디어와 타인의 아이디어를 조율해가며 하나의 결과물이 나오는 과정들이 너무 신기하고 재미있었다. 요즘 들어 강하게 느끼던 개발에 대한 회의감과 체증이 이번 해커톤을 통해 많이 해결되고 성장한 기분이다. 추가적으로 느꼈던 점은, 단순히 이론 공부만 하면 결국 코드를 치는 감을 잃는다는 것이었다. 해커톤 초반에, 단순한 초기 세팅이나 정말 쉬운 로직들도 기억이 나지 않아 구현하지 못하는 내 모습을 보면서, 코드를 안치면 정말 감을 쉽게 잃는구나 하며 반성했던 기억이 있다. </p>
<p>초기에는 참가에만 의의를 두었지만, 점차 발전되어가는 프로젝트의 아이디어와 완성되어가는 사이트의 모습을 보면서 어쩌면 결선에 진출할 수 있을지도..? 하는 생각이 들었었다. 비록 결선에 진출하지는 못했지만, 이번 프로젝트가 내게는 가장 애정이 남는 프로젝트일 것 같다. 요즘 개인적인 사정으로, 몸도 마음도 많이 약해지고 힘들었던 시간들이었는데 정말 이런 좋은 기회를 소개시켜 주어서 잠시나마 온전히 코딩에 집중할 수 있게 해준 팀장님과 팀원들에게 너무 감사를 드리고 싶다. 그러면 이번 프로젝트 회고록도 끝!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Market Kurly Hackerton 회고록 - 1]]></title>
            <link>https://velog.io/@hang_kem_0531/Market-Kurly-Hackerton-%ED%9A%8C%EA%B3%A0%EB%A1%9D</link>
            <guid>https://velog.io/@hang_kem_0531/Market-Kurly-Hackerton-%ED%9A%8C%EA%B3%A0%EB%A1%9D</guid>
            <pubDate>Thu, 01 Sep 2022 06:57:24 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/7d7828c0-8a3b-4dc1-81a1-d668610b0f8a/image.png" alt=""></p>
<h2 id="개발체증">개발체증</h2>
<p>부트캠프를 수료한 후, 단순히 기술면접과 알고리즘 스터디에 집중하면서 개발자로서의 성장에 있어서는 정체되고 있다는 느낌을 받고 있었다. 그래서 사이드 프로젝트라도 진행해보아야하나 고민했었지만, 단순히 취업용으로 이력서를 채우기 위한 프로젝트 진행이라면 의미가 많이 퇴색되지 않을까 싶어서 방황하던 찰나에 같이 스터디를 진행하던 동기분이 아주 좋은 프로그램을 가져오셔서 같이 참가하게 되었다.</p>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/e7241255-13a2-40a7-8ff7-7cc0cab4a552/image.png" alt=""></p>
<blockquote>
<h3 id="해커톤hackerton-br">해커톤(Hackerton)? <br></h3>
<p>해커톤(hackathon)이란 해킹(hacking)과 마라톤(marathon)의 합성어로 기획자, 개발자, 디자이너 등의 직군이 팀을 이루어 제한 시간 내 주제에 맞는 서비스를 개발하는 공모전이다.</p>
</blockquote>
<p>오래전부터 해커톤이라는 프로그램이라는게 있다는 사실은 알고 있었지만, 선뜻 참가하기에는 부족함과 실력에 대한 불확실함이 있었기에 다음에 해야지 하면서 미뤄왔었다. 그러나 이번에는 뜻이 맞는 동기들도 있고, 부족함이 많아도 한번 부딪쳐보는 거 또한 나쁘지 않은 경험이라 생각되었기 때문에 해커톤에 참가해보기로 결정하였다!</p>
<h2 id="대회-일정">대회 일정</h2>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/33971d7b-a17b-4eae-81c9-ed56712ce656/image.png" alt=""></p>
<h2 id="주제-선정">주제 선정</h2>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/69e8b4e8-9e84-48b4-9ad5-d3407707d346/image.png" alt=""></p>
<p>이번 마켓컬리 해커톤의 주제는 위 4가지와 같았다. 기획단계에서 매력적으로 다가왔던 주제는 1번과 2번이었지만, 단순히 프론트엔드 3명, 백엔드 1명으로 구성된 우리 팀이 구현하기에는 한계가 있다는 생각이 들었다. 휴먼에러 해결이나 적정가 모니터링은 결과적으로 머신러닝이나 알고리즘을 통한 데이터 패턴 분석이 필요하다고 판단되었기 때문에, 1번과 2번 주제는 보류하게 되었다. (실제로 팀원을 찾는 해커톤 오픈 채팅방에서도 1번이나 2번 주제 때문에 딥러닝 혹은 머신러닝 엔지니어 팀원을 모집하는 공고가 자주 올라왔었다.)</p>
<p>그래서 우리 팀은 3번 주제를 선택하여 기획하기로 하였다. 단순히 개발적 한계뿐만이 아니라, 회의 과정에서 나온 &#39;레시피 커뮤니티&#39;라는 아이템이 좋은 아이템이라는 생각이 들었기 때문이다. 3번 주제의 핵심은 마켓컬리의 상품 카테고리가 다양화되면서 비목적성 구매가 아닌 고객들이 필요로 하는 적합한 상품을 추천, 검색할 수 있는 방안을 마련하는 것이었다. 우리가 기획한 레시피 커뮤니티는, 단순히 마켓컬리가 상품을 판매하는 사이트가 아닌 회원들이 직접 마켓컬리를 통해 구매한 상품으로 제작한 레시피를 공유해 상품 추천 및 정보 공유가 목적인 사이트였다. 기존의 타 커머스 사이트나 카페 같은 경우에는 업체의 광고 게시글이나 홍보용 리뷰 등이 다수 존재하여 사용자들로 하여금 제품에 대한 정보의 신뢰도와 정확성이 낮았다는 평가가 많았고, 이를 커뮤니티라는 플랫폼으로 구현하여 상품에 대한 정확한 정보와 맞춤형 서비스, 더 나아가 고객 유치의 지속성 역시 기대할 수 있었다.</p>
<p>이렇게 커뮤니티를 기획한 것은, 단순히 우리의 아이디어 뿐만이 아니라 구글 폼을 통한 마켓컬리 이용자들의 의견을 수렴했던 것에도 큰 도움을 받을 수 있었다. 기획 전, 실제 사용자들의 의견이나 불편 사항을 참고해보자는 팀장님의 제안이 있었고, 이를 직접 수렴하면서 더 구체적인 아이디어를 기획하는데에 많은 부분이 도움이 되었었다. 그 동안의 프로젝트는, 사실상 기존 사이트들을 모티브로 삼아 우리가 구현하고 싶은 기능들만 추가하는 형식이었다면, 이번 해커톤은 기획단계에서부터 실제 사용자들의 의견을 수렴하여 처음부터 개발하는 느낌이었기 때문에 사뭇 느낌이 새로웠다.</p>
<p>번외로, 팀 이름을 보리꼬리로 선정했는데 마켓컬리에 어울리는 이름이 뭐가 있을까 생각하다가 예전에 어떤 짤에서 할머니가 브로콜리를 잘못 쓰셔서 보리꼬리로 썼던 귀여운 짤이 생각나서 보리꼬리로 정하게 되었다. 짓고 나니 꽤 마음에 들어서 아마 여태 진행했던 프로젝트 중에서 가장 마음에 드는 이름이 아닐까 싶다.</p>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/5d6eb4ea-0435-4e4b-9475-ad7d101e7773/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/5d7dc913-f3de-4a55-94d5-54d0919631cb/image.png" alt=""></p>
<p>개발 계획서를 제출할 때, 보리꼬리로 작명한 계기에 대해서 그럴듯하게 서사를 만들어 냈었는데, 지금 생각해도 말은 만들어내기 마련이라는 생각이다. 내가 쓰고서도 완전 소설이네.. 했던 기억이 있다.</p>
<h2 id="개발-계획">개발 계획</h2>
<ol>
<li><p>개발형태: Web</p>
</li>
<li><p>개발스택
Frontend : Javascript, Next.js, React-Query, Recoil, Styled-components - Backend : Django, Python, MySQL, AWSS3</p>
</li>
<li><p>페이지 구성(5개) 및 추가기능(1개)
페이지: 컬리의 레시피게시판, 유저레시피 게시판, 후기 게시판, 팁 정보게시판, 베스트 게시판
추가기능 : 유저 레벨제도 </p>
</li>
<li><p>기능 소개</p>
</li>
</ol>
<p>▶ 컬리의 레시피게시판, 유저레시피 게시판, 팁게시판 :</p>
<ul>
<li>컬리의 레시피는 마켓컬리에서 추천하는 레시피 공유, 유저레시피는 유저 상호간 레시피 공유 및 추천 기능 &gt; 팁 게시판은 유저 상호간 살림이나 자취 팁 공유 기능 구현</li>
<li>게시글 작성시 템플릿을 적용하여 디자인의 통일성과 유저 편의성 구현</li>
<li>레시피의 재료를 마켓컬리 상품과 연동할 수 있는 기능 구현</li>
<li>해시태그기능 구현으로 검색 최적화 및 연관 레시피 추천 기능 구현</li>
<li>유저들 끼리 소통할 수 있는 댓글/대댓글 기능구현, 라벨 기능도입으로 댓글별 기능분리(일반/칭찬/질문)</li>
<li>추천 및 조회수가 일정 기준을 넘어서면 베스트 게시판으로 이동 기능 구현</li>
<li>레시피 게시판은 상세페이지에서 작성자의 다른 레시피 추천/연동 기능 구현</li>
<li>팁 게시판은 팁으로 사용한 제품에 대한 제품 연동 기능 구현</li>
</ul>
<p>▶ 후기 게시판</p>
<ul>
<li>레시피를 활용해 만든 후기 또는 기존레시피의 응용 제작 후기 등 기능을 담당</li>
<li>후기로 사용한 레시피 연동 기능 구현</li>
<li>레시피 활용시 변경된 재료 연동 기능 구현</li>
</ul>
<p>▶ 베스트 레시피</p>
<p>일정시간 기준 충족시 회원 레시피에서 베스트 레시피로 실시간 이동 기능</p>
<p>▶ 유저 레벨제도</p>
<ul>
<li>각 레벨별 해택으로 참여도를 높이고 레벨에 따라 게시글에 대한 신뢰도 파악기능</li>
<li>게시글 양, 댓글양 등을 토대로 점수반영 기능, 점수에 따라 레벨상승 기능</li>
<li>레벨별 뱃지 변동 기능</li>
</ul>
<p>보리꼬리 팀의 초기 개발 계획서이다. 이렇게 원대했던 계획이지만, 실제 개발 과정에서 시간적 한계와 기술적 한계로 인해 구현하지 못한 부분들이 많다. 다시 한번 초기 기획 단계와 실제 개발 단계의 갭을 실감했던 순간이었다. 그러나 우리가 핵심적으로 생각했던 기능들은 전부 성공적으로 구현을 마무리할 수 있었던 점은 긍정적으로 생각한다.</p>
<h2 id="유저-플로우-퍼소나-여정-맵">유저 플로우, 퍼소나, 여정 맵</h2>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/667e2054-6d62-42b4-8459-7a23f9e6b882/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/096b856a-0555-4b74-b6be-63f6da6ab553/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/afe7959f-931b-457f-938e-5c4aee94343d/image.png" alt=""></p>
<h2 id="초기-디자인">초기 디자인</h2>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/a4d13b46-15c2-4456-bac4-3bf13420534e/image.png" alt=""></p>
<p>위 부분들은 전부 팀장님인 가영님이 너무 너무 수고해주신 부분들이다. 사실 나는 기획 단계에서 막연하게 그냥 계획서만 쓰고 내면 되겠거니 생각했지만, 가영님을 통해서 배운 부분들이 정말 많았다. 가상 사용자(퍼소나)를 선정해 어떻게 우리가 기획한 커뮤니티에 유입되고 정착되는지, 이를 유저 플로우와 여정맵을 통해 직관적으로 나타낼 수 있다는 점에 대해서도 많은 꺠달음을 얻었다. </p>
<p>또한, 디자이너의 중요성에 대해서도 다시 한번 크게 느꼈는데, 사실 가영님은 프론트엔드 개발자이지만 디자인을 하신 경험이 있어서 초기 디자인을 전부 혼자서 하시느라 고생이 정말 많으셨다. 이렇게 피그마를 통해서 초기 디자인을 디자이너가 해주면, 우리는 그대로 UI를 만들기만 하면 되어서 시간 단축에 큰 도움이 되었다. 사실 그동안의 프로젝트는, 협업이라는 느낌을 크게 받은 경험은 많지 않았지만 이렇게 초기 기획, 디자인, 회의를 진행하면서 협업의 중요성에 대해서 많이 느낀 순간들이었다.</p>
<p>정말 다들 너무 고생해주시고 노력해주신 덕분에, 본선 진출이라는 쾌거를 이룰 수 있었다. 사실 본선 진출에 대한 큰 기대가 없었기 때문에, 본선 진출에 실패하면 막연하게 생각했던 사이드 프로젝트를 이 팀원들과 함께 진행해야지 하는 생각이 있었는데, 해커톤에서 개발을 할 수 있다는 사실이 너무 기쁘고 다행이었다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[잘했고 잘하고 있고 잘될 것이다 - wecode 수료 회고]]></title>
            <link>https://velog.io/@hang_kem_0531/%EC%9E%98%ED%96%88%EA%B3%A0-%EC%9E%98%ED%95%98%EA%B3%A0-%EC%9E%88%EA%B3%A0-%EC%9E%98%EB%90%A0-%EA%B2%83%EC%9D%B4%EB%8B%A4-wecode-%EC%88%98%EB%A3%8C-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@hang_kem_0531/%EC%9E%98%ED%96%88%EA%B3%A0-%EC%9E%98%ED%95%98%EA%B3%A0-%EC%9E%88%EA%B3%A0-%EC%9E%98%EB%90%A0-%EA%B2%83%EC%9D%B4%EB%8B%A4-wecode-%EC%88%98%EB%A3%8C-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Tue, 26 Jul 2022 07:11:53 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/33ebf431-dd89-4172-a17b-1ba65ef9262b/image.jpeg" alt=""></p>
<blockquote>
<p>부트캠프 회고록에 대한 제 생각은 <a href="https://velog.io/@hang_kem_0531/wecode-1%EB%8B%AC%EC%B0%A8-%ED%9A%8C%EA%B3%A0">1달차 회고록</a>에 작성되어 있습니다. 부정적인 시각을 가지신 분들의 생각도 존중하지만, 제 성장의 눈금을 기록하기 위해 작성되는 회고록이니 양해 부탁드립니다.</p>
</blockquote>
<p>선릉에 처음 발을 들였던 날이 기억난다. 선릉역에서 내려서 내가 본 풍경은, 바쁘게 움직이는 직장인들과 높은 건물들이 그득그득 들어찬 도시의 풍경이었다. 나도 서울에 거주하고 있는데도, 도시의 정돈됨과 역설적인 부산함에 압도되어 이게 진짜 서울인가..? 라는 생각이 잠시 들었던 것 같다. 그리고 나도 이런 곳에서 일하고 싶다! 라는 생각이 이어져 들었다. 위코드 첫날의 감정이었다.</p>
<p>어쩌면, 막막함을 조금이라도 떨쳐버리기 위해 부트캠프라는 비교적 직관적인 길을 선택했던 것 같다. 부트캠프를 수료하면 개발자가 되어 있겠지? 혹은 바로 취업을 할 수 있겠지? 하는 막연한 기대 역시 가지고 있었다. 그러나, 이는 정말 어리석은 생각이었다고 느끼게 된 것 역시 첫날의 감정이었다. </p>
<p>나는 사람은 걱정의 동물이라고 생각한다. 중학생은 고등학교 걱정을, 고등학생은 수능 및 대학교 걱정을, 군인은 전역 걱정을, 대학생은 졸업 후 취업 걱정을 한다. 그리고 그 걱정들은, 내가 겪어온 바로는 시간이 해결해 준다. 여태까지 세 번의 회고를 썼고, 회고들을 작성할때마다 아니 벌써 이만큼 했단 말이야? 라는 생각들을 하곤 한다. 그리고 지금은 어느덧 3개월이라는 시간이 훌쩍 지나 수료 회고를 쓰고 있으니, 시간이 참 빠르다는 생각이다. </p>
<p>이 회고록은 위코드에 들어 오게 될, 혹은 수강중인 분들을 대상으로 부트캠프를 홍보하거나 칭찬하고자 작성하는 목적은 아니다. 오히려 내가 느꼈던 단점들이나 아쉬웠던 부분들은 가감없이 작성할 예정이다. 하지만, 나 역시 다른 분들의 회고를 보면서 그래왔듯 이 회고를 통해서 수강생들 혹은 수강 예정인 분들의 선택에 있어서 조금이나마 도움이 되었으면 한다. 아무래도 인생에서 조금은 비중을 차지하게 될 결정이니 만큼, 미리 경험해본 사람의 말들에서 얻어갈 것들이 있었으면 좋겠다. 그리고 나 역시, 이번 회고록을 작성하고 되돌아보면서 얻어갈 것들을 추려 정리하고자 하는 목적도 있다. 그러면 다시 한번 위코드에서의 내 기록들을 되짚어 보자.</p>
<hr>
<h2 id="사전-스터디">사전 스터디</h2>
<p>위코드의 첫 과정은, 한달여의 사전 스터디 기간으로 시작된다. 랜덤으로 팀을 지정해주고, 그 안에서 본인들이 기초적인 개발 지식들을 공부하는 식이다. 돌이켜 보면, 사전 스터디 기간에 조금 더 다양한 것들을 시도했으면 더 괜찮지 않았을까 하는 생각이 들기는 하지만, 후회하고 싶지는 않다. </p>
<p>사전 스터디 1주차에는, HTML과 CSS를 사용하여 자기 소개 페이지를 만드는 Assignment가 있었다. HTML, CSS는 대학교에서도 전공으로 어느 정도 다뤘던 스택이고, 그래도 1년여간 독학을 해온 경험이 있어서 금방 만들겠거니 생각했었다. </p>
<p>실제로도 그리 오랜 기간이 걸리지는 않았지만, 본인 스스로 부족함을 많이 느낀 순간이었다. CSS를 적용하는 데에도 몇번의 구글링이 필요했고, 간단해 보이는 코드들도 내 예상대로 되지 않았던 적이 많았다. 그래서 이때 Flexbox, animation과 같은 CSS 태그들에 대해 더 깊게 공부하게 되었던 것 같다. 이때의 지식은 아직 내 머리에 남아, 더 이상 헷갈리지 않고 자유롭게 사용할 수 있게 되었다.</p>
<blockquote>
<p><a href="https://velog.io/@hang_kem_0531/Wecode-HTML-CSS%EB%A5%BC-%ED%99%9C%EC%9A%A9%ED%95%9C-%EC%9E%90%EA%B8%B0%EC%86%8C%EA%B0%9C-%ED%8E%98%EC%9D%B4%EC%A7%80-%EB%A7%8C%EB%93%A4%EA%B8%B0-1">[Wecode] HTML &amp; CSS를 활용한 자기소개 페이지 만들기 - 1</a>
<a href="https://velog.io/@hang_kem_0531/Wecode-HTML-CSS%EB%A5%BC-%ED%99%9C%EC%9A%A9%ED%95%9C-%EC%9E%90%EA%B8%B0%EC%86%8C%EA%B0%9C-%ED%8E%98%EC%9D%B4%EC%A7%80-%EB%A7%8C%EB%93%A4%EA%B8%B0-2">[Wecode] HTML &amp; CSS를 활용한 자기소개 페이지 만들기 - 2</a></p>
</blockquote>
<p>이때 느끼게 된 것이, 결국 <strong>본인의 코드로 만들려면 본인이 다뤄봐야 한다</strong>는 것이었다. 여태 내가 백날 강의를 보고, 좋다는 책을 읽어도 왜 키보드에 손만 얹으면 뇌정지가 오는지, 이제서야 깨닫게 되었다. 이때부터 내가 입에 달고 살게 된 말이, &#39;<strong>코딩쇼 직관으로는 절대 실력이 늘지 않는다</strong>.&#39; 이다. 학원에서 내게 질문을 하러 오는 동기분들에게도 이 얘기를 하면서, 절대 내가 코드를 쳐주거나 하지 않고 힌트를 주면서 본인이 칠 수 있게끔 했다. 실제로 나중에 본인이 코드를 깨달아서 이제는 잘 사용할 수 있다는 동기분의 말을 듣고 뿌듯하기도 했다.</p>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/d0cdce75-f432-4bc6-85bf-70eeae3b2fe6/image.png" alt=""></p>
<p>2, 3, 4주차에는 우리 팀원분들이 모두 프론트엔드 지망이셔서, JavaScript의 개념들 위주로 발표하는 식으로 스터디를 진행했다. 나같은 경우에는 모던 자바스크립트 딥 다이브를 1회독하고, 거기서 필요하거나 중요하다고 생각하는 개념들을 ppt로 정리해서 발표했는데, 나는 내가 정리하고 설명하면서 개념 확립에 도움이 되었지만 나중에 들어보니 다른 분들에게는 많이 생소하고 어려웠다고 하셨었다. 어떻게 보면, 스터디도 협업의 일종이라고 생각하기 때문에 타인과의 소통에 있어서 나의 기준으로만 생각했던 점이 아쉬운 기억으로 남는다.</p>
<hr>
<h2 id="pre-course--foundation">Pre-Course &amp; Foundation</h2>
<p>아마 대다수의 위코드 수강생들이, 많이 힘들어하고 의문이 들 시기일 것이다. 그래서 나는 누군가가 본인의 머리에 넣어주기를 바라거나, 강의 위주의 학습법에 익숙한 사람들에게는 위코드를 수강하는 것을 <strong>비추</strong>한다. Pre-Course와 Foundation 기간에는 잠깐씩의 세션 시간을 제외하고는, 하루종일 Replit에 있는 JavaScript 문제를 풀고, Vanilla JavaScript로 인스타그램을 클론 코딩하고, 다시 그 인스타그램을 React로 리팩토링한다. 결과적으로 말하자면, 하루 종일 코딩을 하는데 거의 본인의 힘으로만 해야 한다. </p>
<p>나도 그 당시에는 의문이 많이 들었었다. 아니 그래도 낸 돈이 적지 않은데, 이 정도 세션만으로 구현하라고 하는 것이 말이 되나? 너무 무책임한거 아닌가? 실제로 나 말고도 많은 분들이 해당 부분에 대해서 불만을 많이 표했었다. 그러나 지금 되돌아보면, 내가 성장을 가장 많이했던 시기가 pre-course와 foundation 시기인 것 같다. 
스스로 공부하는 방법을 터득하는 것은 어느 학문이든 중요한 부분이다. 결국 아무리 타인의 지식이 빼어나다 한들, 이를 본인의 것으로 만드는 것은 본인의 역량이니까. 그런 면에서 위코드는 스스로 공부를 하는 법을 알려주는 곳인 것 같다. 아까 말했듯, 본인의 코드가 되려면 스스로 이를 다뤄봐야 하는데 그 다룰 기회들을 제공하는 곳인 것이다. 어떻게 보면, 이 부분이 부트캠프와 학원의 차이점인가 싶기도 하다. (물론 세션에서는 큰 도움을 받지 못했다.. 오히려 동기들이나 멘토님들과 코드 리뷰 &amp; 공유를 통해 많은 성장을 했다.)</p>
<p>&#39;리액트를 사용할 줄 안다&#39; 의 수준까지 올라온 것이 가장 큰 수확이었다. State와 Props의 개념조차 접근하지 못해 허우적거리던 내가, 이제 웬만한 로직들은 리액트로 구현하려고 시도를 해볼 수 있게 된 건 그만큼 성장했다는 뜻이리라. 이제는 새로운 스택들이 나와도 두려워하지 않고, 직접 코드를 쳐보면서 내 코드로 만들려는 습관을 기르게 되었다.</p>
<hr>
<h2 id="1st-2nd-projects">1st, 2nd Projects</h2>
<p>나는 대학교에서도 조별과제를 좋아하는 편이었다. 물론, 혼자 하는 과제가 마음이 편하긴 했지만, 여럿이서 같은 주제를 두고 토론하고 작업한 노력들이 응집되어 하나의 결과물로 도출된다는 것이 퍽 매력적이었기 때문이다. </p>
<p>그러나, 그런 조별과제나 팀 프로젝트들은 정말 이상적인, 상상속에서나 존재할 협업이고 현실은 그리 녹록치 않았다. 백엔드, 프론트엔드 개발자간의 소통 부재로 인한 작업 진도 문제, 기획 단계와 달랐던 결과물까지. 어떻게 보면 위코드에서 진행했던 2개의 팀 프로젝트 모두 성공적인 프로젝트였다고 보기에는 조금 어려웠고, 회고록에서 적은 것과는 다르게 정신적인 스트레스도 많이 받았던 시기였다.</p>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/d4d3a1a6-d7ba-427f-8dc8-6b67359c176b/image.gif" alt=""></p>
<p>그래서 나는, <strong>&#39;잘 실패하는 법&#39;, &#39;후회를 잘하는 법&#39;</strong>에 포커스를 맞춰서 프로젝트를 진행했던 것 같다. 물론, 처음부터 성공적으로 프로젝트를 마쳤다면 아주 좋은 스타트였겠지만, 실패할 수 있는 시기도 지금뿐이라는 생각이 들었다. </p>
<p>가령, 1차 프로젝트에서는 리팩토링은 커녕 컴포넌트를 분리하거나 함수의 사이즈를 줄이는 등 최소한의 클린 코딩조차 진행하지 못했었는데, 이 실패를 인식하여 2차 프로젝트에서는 코드를 칠때마다 의식적으로 좀 더 깔끔하고 가독성이 좋은 코드를 작성하고자 하였다. 그리고, 2차 프로젝트에서 내가 했던 실수들은 결국 미래의 내가 다시 반복하지 않을 기록으로 남게 될 것이다. </p>
<hr>
<h2 id="아쉬웠던-기업협업">아쉬웠던 기업협업</h2>
<p>사실 위코드를 선택했던 가장 큰 이유는 기업협업 때문이었다. 현업 개발자들에게 코드 리뷰를 받고, 실무의 애자일 프로젝트와 스크럼 방식을 경험하면 정말 엄청나게 많은 것들을 얻어갈 수 있을 것 같았기 때문이다.</p>
<p>하지만, 내가 협업으로 갔던 기업은 사수는 커녕 프론트엔드 개발자도 없었고, 그렇기 때문에 코드 리뷰 역시 없었다. 레거시 코드도 풀스택 부트캠프인 저스트 코드 4기 분들이 만들어 놓은 코드들 밖에 없었기 때문에, Zoom을 통해 간접적으로 인수인계를 받긴 했지만 코드들을 이해하는 데에 꽤 오랜 시간이 걸렸다.</p>
<p>프로젝트 기간에도 스트레스를 많이 받긴 했지만, 기업협업 기간에는 내가 뭘 하고 있는거지에 대한 회의감이 많이 들었었다. 부랴부랴 Next.js와 TypeScript 강의를 듣고 적용하여 코드를 작성하긴 했지만, 이게 맞는 코드인지 혹은 왜 사용하는 것인지에 대한 확신조차 없는 상태에서 약 2주 정도를 허비했던 것 같다. 그러다가 3주차 쯤에, 멘탈이 정말 크게 박살나서 하루종일 말도 거의 안하고 있었던 적도 있었다.</p>
<p>그래서 돌이켜보면 얻을것이 하나도 없었는가? 음.. 분명히 많지는 않지만 얻어갈 것들이 있었던 것 같다. 일단, Next.js와 TypeScript를 아직 사용할 수 있다라고 단언하기에는 부족하지만, 써본적이 있다라고 말할 수 있게 되었다. 아무래도 실무에서 처음 접해본 스택이기 때문에, 강의로 입문하는 것보다는 차후에 추가 공부를 할 때 아 그때 이래서 이 코드를 썼구나라는 생각으로 이해하기 조금 더 쉽지 않을까 하는 생각이다. </p>
<p>또한, 현업의 백엔드 개발자와 디자이너와 처음으로 같이 작업을 해본 경험이었기 때문에 실무의 개발 시스템을 조금은 알 수 있었던 기회였다. 디자이너분이 Adobe XD로 페이지의 레이아웃이라던지 UI 부분에 대해서 틀을 잡아주시고, 그걸 우리가 그대로 만들기만 하면 되는 구조라서 현업에서는 디자인적인 수고를 조금은 덜 수 있겠구나하는 생각이 들었다. 그리고 이미 배포된 서버에서 작업을 했기 때문에, 통신을 할 때 실시간으로 백엔드 개발자분께서 내게 맞춰주시면서 작업을 할 수 있었던 것도 편리했다. </p>
<p>그래도 대표님과 직원분들께서 우리가 기업협업 대상자로 온 걸 아시고, 많이 배려해주시고 맞춰주셔서 작업하는 데에 있어서 큰 불편은 없었던 점은 좋았던 것 같다. 사실, 기존에는 톡플러스라는 채팅 API를 적용하는 것이 우선 순위였는데, 우리의 작업 속도가 느려서 이를 시도조차 해보지 못한 것이 아쉬움으로 남는다. 그래도 한달의 기간동안, 같이 일했던 직원분들과 대표님께 감사를 표하고 싶다.</p>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/b597bd96-b40d-4260-8648-f87bd527c69c/image.jpg" alt=""></p>
<hr>
<h2 id="그래도-정말-행복한-3개월이었어">그래도 정말 행복한 3개월이었어</h2>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/68fadd97-d4b3-4598-a932-a5515a1af46c/image.jpeg" alt=""></p>
<p>요즘, 정말 행복한 날들을 보내고 있지 않나하는 생각이 자주 들었다. 불과 1년 전만 해도, 나는 밤낮이 바뀐 채 저녁에 일어나서 의미없는 시간들을 보내다가 아침 해가 뜨면 잠드는, 잉여와 같은 삶을 살았었다. 그 당시에도 개발 공부를 한답시고 하는 시늉은 했지만, 말 그대로 받아쓰기에 불과하고 나를 속이는 행위에 불과했다.</p>
<p>그러나 위코드에 오고나서, 3개월 동안 새벽같이 일어나 선릉에 와서 하루 종일 코드를 치고, 막차에 가까운 시간에 집에 들어가 바로 피곤에 지쳐 잠드는 일상을 보내면서도 행복하고 즐겁다라는 생각이 많이 들었다. 어쩌면, 고등학교나 대학교때보다 더 열심히 살았던 것 같다. 내 성격이 모나서, 무언가 하기 싫거나 흥미가 없으면 죽어도 안하는 성격이었는데 자발적으로 공부를 하고, 어떻게 하면 더 좋은 코드를 작성할 수 있는지에 대한 생각을 하루종일 했었다. (심지어, 꿈에서도 코딩을 한 적이 잦았다.)</p>
<p>정말, 과분하게도 평생 보고싶은 좋은 분들을 너무 많이 만났고, 성격 역시 밝아짐을 느꼈다. 원래 극도록 내향적인 성격이던 내가, 먼저 다른 사람에게 다가가고 말을 거는 모습을 보면서 나조차 내 자신이 낯설었다. 위코드 중간에 MBTI 테스트를 다시 헀는데, E가 나왔던 적도 있었다! (지금은 다시 INTJ로 돌아왔다.)</p>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/8dd7d83b-cc82-4f7c-8d55-db5cf8106627/image.jpeg" alt=""></p>
<center> (중간에 생일 축하도 받았다. 정말 감사드려요 해리님 혜수님 하임님..아이시떼루..) </center>

<p>그리고 정말 감사하게도, Slack에서 33기 커뮤니티를 활성화시켰다는 이유로 수료식 날 커뮤니티 상을 수상하기도 했다. 조금 선을 넘거나, 짖궂을 수 있는데도 항상 잘 받아주시고 잘 지내주신 코딩항공 분들한테 정말, 진심으로 감사하다는 말씀 드리고 싶다. </p>
<p>앞으로 개발자로 살고자 결심한 만큼, 힘들때마다 위코드의 기억들을 다시금 떠올리게 될 것만 같다. 지난 3개월 동안, 여러 단점들도 느끼고 관두고 싶다라는 생각도 자주 들었었지만, 한 가지 확실한 것은 위코드가 내게 <strong>&#39;개발하는 즐거움&#39;</strong>을 가르쳐 주었다는 것이다. 이제부터 이력서, 면접, CS 스터디, 알고리즘 공부 등 아직 해야할 것들이 산더미지만 여기서 얻은 소중한 인연들과 이 과정들 역시 함께할 수 있다는 것에 감사하다.</p>
<p><img src="https://velog.velcdn.com/images/hang_kem_0531/post/deb6eb8d-e0d2-4460-9f21-f90a7ff340b7/image.JPG" alt=""></p>
<p>그래서, 누군가가 내게 &#39;다시 과거로 돌아가셔도 위코드 가실건가요?&#39; 라고 묻는다면, 나는 주저없이 &#39;네!&#39;라고 대답할 것 같다. 내게 스스로 공부할 수 있는 방법과 개발의 즐거움, 그래고 평생 갈 개발자 커뮤니티를 선물한 것 만으로도 큰 이득이라고 생각한다. 33기 동기분들 모두가 정말 본인이 원하는 만큼, 좋은 회사에 들어가서 훌륭한 개발자가 되기를 진심으로 기도한다. 그럼 여기서 3개월 회고록 끝!</p>
]]></description>
        </item>
    </channel>
</rss>