<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>edith_0318.log</title>
        <link>https://velog.io/</link>
        <description>코딩으로 쓰는 일기장</description>
        <lastBuildDate>Mon, 06 Feb 2023 03:21:35 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>edith_0318.log</title>
            <url>https://velog.velcdn.com/images/edith_0318/profile/452546a9-acd1-417e-bf23-45b99ef5fa33/social_profile.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. edith_0318.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/edith_0318" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[Proxy를 설정해 HTTP 통신하기]]></title>
            <link>https://velog.io/@edith_0318/Proxy%EB%A5%BC-%EC%84%A4%EC%A0%95%ED%95%B4-HTTP-%ED%86%B5%EC%8B%A0%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@edith_0318/Proxy%EB%A5%BC-%EC%84%A4%EC%A0%95%ED%95%B4-HTTP-%ED%86%B5%EC%8B%A0%ED%95%98%EA%B8%B0</guid>
            <pubDate>Mon, 06 Feb 2023 03:21:35 GMT</pubDate>
            <description><![CDATA[<h1 id="1-과제1">1. 과제1</h1>
<ul>
<li>webpack dev server의 proxy 기능을 사용해 우회하여 응답을 받아오기</li>
</ul>
<h2 id="packagejson">package.json</h2>
<pre><code class="language-js">// api/packge.json
....
},
    &quot;proxy&quot; : &quot;http://localhost:3080&quot;
}</code></pre>
<h2 id="bookservicejs">BookService.js</h2>
<p>기존의 fetch, 혹은 axios를 통해 요청하던 부분에서 도메인 부분을 제거하여주니 CORS 에러를 해결됨</p>
<pre><code class="language-js">// my-app/services/BookService.js
export const getAllBooks = async () =&gt; {

    const response = await fetch(&#39;/api/books&#39;);
    return await response.json();
}

export const createBook = async (data) =&gt; {
    const response = await fetch(&#39;/api/book&#39;, {
        method: &#39;POST&#39;,
        headers: {&#39;Content-Type&#39;: &#39;application/json&#39;},
        body: JSON.stringify({book: data})
      })
    return await response.json();
}</code></pre>
<h1 id="과제2">과제2</h1>
<p>레포지토리로 받아온 파일에 보면 api2 라는 폴더가 존재하고 있다.
실제로 프로젝트 및 실무를 할 때, 하나의 도메인이 아닌 여러 개의 도메인에서 응답을 받아와야 하는 경우가 종종 있는데, 이럴 때는 유연하게 proxy를 설정해주어야 한다.</p>
<ul>
<li>이번 과제는 webpack dev server의 proxy 기능 대신 http-proxy-middleware의 proxy 기능을 사용하여 proxy를 유연히 설정해 2개의 도메인에서 모두 응답을 받아왔고, api2에 관련된 fetch 함수를 만들고, 컴포넌트를 하나 이상 만들어 2개의 도메인에서 모두 응답을 받아오는지 테스트 하여야 한다.</li>
</ul>
<h2 id="packagejson-1">package.json</h2>
<p>먼저 우회할 api 주소 제거</p>
<pre><code class="language-js">....
},

}</code></pre>
<h2 id="setupproxyjs">setupProxy.js</h2>
<p>http-proxy-middleware 라이브러리 설치</p>
<pre><code>//파일 전역에다가 설치
npm install http-proxy-middleware --save</code></pre><p>React App그의 src 파일 안에서 setupProxy.js 파일을 생성 후 아래와 같이 작성을 해주었다.</p>
<pre><code class="language-js">const { createProxyMiddleware } = require(&#39;http-proxy-middleware&#39;);

module.exports = function(app) {
    app.use(
        &quot;/api&quot;,
        createProxyMiddleware({
            target: &quot;http://localhost:3080&quot;,
            changeOrigin: true,
        })
    ),
    app.use(
        &quot;/api2&quot;,
        createProxyMiddleware({
            target: &quot;http://localhost:3070&quot;,
            changeOrigin: true,
        })
    );
};</code></pre>
<h2 id="bookservicejs-1">BookService.js</h2>
<p>api2에 관련된 fetch 함수를 만들어 줌.</p>
<pre><code class="language-js">export const getAllBooks = async () =&gt; {

    const response = await fetch(&#39;/api/books&#39;);
    return await response.json();
}

export const createBook = async (data) =&gt; {
    const response = await fetch(&#39;/api/book&#39;, {
        method: &#39;POST&#39;,
        headers: {&#39;Content-Type&#39;: &#39;application/json&#39;},
        body: JSON.stringify({book: data})
      })
    return await response.json();
}

export const getAllTodos = async () =&gt; {

    const response = await fetch(&#39;/api2/todos&#39;);
    return await response.json();
}

export const createTodos = async (data) =&gt; {
    const response = await fetch(&#39;/api2/todo&#39;, {
        method: &#39;POST&#39;,
        headers: {&#39;Content-Type&#39;: &#39;application/json&#39;},
        body: JSON.stringify({todo: data})
      })
    return await response.json();
}</code></pre>
<h2 id="createtodojs">CreateTodo.js</h2>
<p>CreateBook의 컴포넌트와 복사하여 이름만 바꾸어 주었다.</p>
<pre><code class="language-js">const CreateTodo = ({ onChangeForm, handleSubmit }) =&gt; {
    return(
        &lt;div className=&quot;form-wrapper&quot;&gt;
            &lt;div className=&quot;form&quot;&gt;
                &lt;form&gt;
                    &lt;div className=&quot;input-group&quot;&gt;
                        &lt;label&gt;todo&lt;/label&gt;
                        &lt;input 
                            type=&quot;text&quot; 
                            onChange={(e) =&gt; onChangeForm(e)} 
                            name=&quot;todo&quot; 
                            placeholder=&quot;todo&quot; 
                        /&gt;
                    &lt;/div&gt;
                    &lt;div className=&quot;input-group&quot;&gt;
                        &lt;label&gt;category&lt;/label&gt;
                        &lt;input 
                            type=&quot;text&quot; 
                            onChange={(e) =&gt; onChangeForm(e)} 
                            name=&quot;category&quot; 
                            placeholder=&quot;category&quot; 
                        /&gt;
                    &lt;/div&gt;
                    &lt;div className=&quot;input-group&quot;&gt;
                        &lt;label&gt;isComplete&lt;/label&gt;
                        &lt;input 
                            type=&quot;text&quot; 
                            onChange={(e) =&gt; onChangeForm(e)} 
                            name=&quot;isComplete&quot;
                            placeholder=&quot;isComplete&quot; 
                        /&gt;
                    &lt;/div&gt;
                    &lt;button 
                        className=&quot;submit-button&quot;
                        onClick= {() =&gt; handleSubmit()}
                    &gt;Submit
                    &lt;/button&gt;
                &lt;/form&gt;
            &lt;/div&gt;
        &lt;/div&gt;
    )
}
export default CreateTodo;</code></pre>
<h2 id="displayboardjs">DisplayBoard.js</h2>
<p>DisplayBoard에서는 getAllTodo의 버튼이 클릭 될 때 실행 되도록 만들어 주었다.</p>
<pre><code class="language-js">import React from &#39;react&#39;

const DisplayBoard = ({numberOfBooks, getAllBook, getAllTodo, numberOfTodos}) =&gt; {

    return(
        &lt;div className=&quot;display-wrapper&quot;&gt;
            &lt;div className=&quot;display-box&quot;&gt;
                &lt;div className=&quot;display-board&quot;&gt;
                    &lt;h4&gt;생성된 수&lt;/h4&gt;
                    &lt;div className=&quot;number&quot;&gt;
                    {numberOfBooks}
                    &lt;/div&gt;
                &lt;/div&gt;
                &lt;div className=&quot;display-board&quot;&gt;
                    &lt;h4&gt;생성된 todo 수&lt;/h4&gt;
                    &lt;div className=&quot;number&quot;&gt;
                    {numberOfTodos}
                    &lt;/div&gt;
                &lt;/div&gt;
                &lt;div className=&quot;get-button&quot;&gt;
                    &lt;button onClick={() =&gt; getAllBook()}&gt;Get all Books&lt;/button&gt;
                    &lt;button onClick={() =&gt; getAllTodo()}&gt;Get all todos&lt;/button&gt;
                &lt;/div&gt;
            &lt;/div&gt;
        &lt;/div&gt;
    )
}
export default DisplayBoard;</code></pre>
<h2 id="todotablejs">TodoTable.js</h2>
<p>BookTable과 비슷하게 구성 이름만 바꾸어 줌.</p>
<pre><code class="language-js">import React from &#39;react&#39;

const TodoTable = ({todos}) =&gt; {

    if (todos.length === 0) return null;

    return(
        &lt;div className=&quot;table-wrapper&quot;&gt;
            &lt;div className=&quot;table-box&quot;&gt;
                &lt;h2&gt;My Todos&lt;/h2&gt;
                &lt;div className=&quot;table-scroll&quot;&gt;
                    &lt;table&gt;
                        &lt;thead&gt;
                        &lt;tr&gt;
                            &lt;th&gt;Id&lt;/th&gt;
                            &lt;th&gt;todo&lt;/th&gt;
                            &lt;th&gt;Category&lt;/th&gt;
                            &lt;th&gt;isComplete&lt;/th&gt;
                        &lt;/tr&gt;
                        &lt;/thead&gt;
                        &lt;tbody&gt;
                            {todos.map((todo,index) =&gt; {
                                return (
                                    &lt;tr key = {index} className={index%2 === 0?&#39;odd&#39;:&#39;even&#39;}&gt;
                                        &lt;td&gt;{index + 1}&lt;/td&gt;
                                        &lt;td&gt;{todo.todo}&lt;/td&gt;
                                        &lt;td&gt;{todo.category}&lt;/td&gt;
                                        &lt;td&gt;{todo.isComplete}&lt;/td&gt;
                                    &lt;/tr&gt;
                                )
                            })}
                        &lt;/tbody&gt;
                    &lt;/table&gt;
                &lt;/div&gt;
            &lt;/div&gt;
        &lt;/div&gt;
    )
}

export default TodoTable;</code></pre>
<h2 id="appjs">App.js</h2>
<p>모든 컴포넌트들을 내려 주기만 하면 끝!</p>
<pre><code class="language-js">import React, { useState } from &#39;react&#39;;
import &#39;./App.css&#39;;
import Header from &#39;./components/Header&#39;;
import BookTable from &#39;./components/BookTable&#39;;
import TodoTable from &#39;./components/TodoTable&#39;;
import DisplayBoard from &#39;./components/DisplayBoard&#39;;
import CreateBook from &#39;./components/CreateBook&#39;;
import CreateTodo from &#39;./components/CreateTodo&#39;;
import { getAllBooks, createBook, createTodos, getAllTodos} from &#39;./services/BookService&#39;;
import Footer from &#39;./components/Footer&#39;;


function App () {
  const [bookShelf, setBookShelf] = useState({});
  const [myTodo, setMyTodo] = useState({});
  const [books, setBooks] = useState([]);
  const [numberOfBooks, setNumberBooks] = useState(0);
  const [todos, setTodos] = useState([]);
  const [numberOfTodos, setNumberOfTodos] = useState(0);

  const handleSubmit = () =&gt; {
      createBook(bookShelf)
        .then(() =&gt; {
          setNumberBooks(numberOfBooks+1);
      });
  }

  const handleTodoSubmit = () =&gt; {
    createTodos(myTodo)
      .then(() =&gt; {
        setNumberOfTodos(numberOfTodos+1);
    });
}

  const getAllBook = () =&gt; {
    getAllBooks()
      .then(data =&gt; {
        setBooks(data);
        setNumberBooks(data.length);
      });
  }

  const getAllTodo = () =&gt; {
    getAllTodos()
      .then(data =&gt; {
        console.log(data)
        setTodos(data);
        setNumberOfTodos(data.length);
      });
  }


  const handleOnChangeForm = (e) =&gt; {
      let inputData = bookShelf;
      if (e.target.name === &#39;book&#39;) {
        bookShelf.book = e.target.value;
      } else if (e.target.name === &#39;category&#39;) {
        bookShelf.category = e.target.value;
      } else if (e.target.name === &#39;author&#39;) {
        bookShelf.author = e.target.value;
      }
      setBookShelf(inputData);
  }

  const handleOnChangeTodoForm = (e) =&gt; {
    let inputData = myTodo;
    if (e.target.name === &#39;todo&#39;) {
      myTodo.todo = e.target.value;
    } else if (e.target.name === &#39;category&#39;) {
      myTodo.category = e.target.value;
    } else if (e.target.name === &#39;isComplete&#39;) {
      myTodo.isComplete = e.target.value;
    }
    setMyTodo(inputData);
}




  return (
    &lt;div className=&quot;main-wrapper&quot;&gt;
      &lt;div className=&quot;main&quot;&gt;
        &lt;Header /&gt;
        &lt;div className=&#39;handel_form&#39;&gt;
        &lt;CreateBook 
          bookShelf={bookShelf}
          onChangeForm={handleOnChangeForm}
          handleSubmit={handleSubmit}
        /&gt;
        &lt;CreateTodo 
        myTodo={myTodo}
        onChangeForm={handleOnChangeTodoForm}
        handleSubmit={handleTodoSubmit}
        /&gt;
        &lt;/div&gt;
        &lt;DisplayBoard 
          numberOfBooks={numberOfBooks} 
          getAllBook={getAllBook}
          getAllTodo={getAllTodo} 
          numberOfTodos={numberOfTodos}
        /&gt;
        &lt;BookTable books={books} /&gt;
        &lt;TodoTable todos={todos} /&gt;
        &lt;Footer /&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  );
}

export default App;</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[ Proxy]]></title>
            <link>https://velog.io/@edith_0318/Proxy</link>
            <guid>https://velog.io/@edith_0318/Proxy</guid>
            <pubDate>Mon, 06 Feb 2023 02:10:51 GMT</pubDate>
            <description><![CDATA[<h2 id="cors-정책이-필요한-이유">CORS 정책이 필요한 이유</h2>
<p>브라우저에서 기본적으로 API를 요청 할 때에, 브라우저의 현재 주소와 API 의 주소의 도메인이 일치해야만 데이터를 접근 할 수 있게 되어 있습니다. 만약 다른 도메인에서 API를 요청해서 사용 할 수 있게 해주려면 CORS 설정이 필요하다.</p>
<ul>
<li><p>CORS
교차 출처 리소스 공유(Cross-Origin Resource Sharing, CORS)는 추가 HTTP 헤더를 사용하여, 한 출처에서 실행 중인 웹 애플리케이션이 다른 출처의 선택한 자원에 접근할 수 있는 권한을 부여하도록 브라우저에 알려주는 체제입니다.</p>
</li>
<li><p>출처
웹 콘텐츠의 출처(origin)는 접근할 때 사용하는 URL의 스킴(프로토콜), 호스트(도메인), 포트로 정의됩니다. 두 객체의 스킴, 호스트, 포트가 모두 일치하는 경우 같은 출처를 가졌다고 말합니다.
일부 작업은 동일 출처 콘텐츠로 제한되나, CORS를 통해 제한을 해제할 수 있습니다.</p>
</li>
</ul>
<p>우리가 로컬 환경에서 개발한 앱은 기본적으로 localhost:3000 으로 시작하는 것을 기억하실 것입니다. 그러나 나중에 로컬 환경에서 개발한 실제 서비스 및 프로젝트의 클라이언트에서 서버의 API로 요청하게 되면, 이 포트로 요청하는 것이 차단됩니다. 개발할 당시에는 이 현상이 굉장히 귀찮은 일로 느껴질 수 있겠지만, 실제로 개발한 서비스 및 프로젝트가 모든 출처의 접근을 허락한다면 이는 후에 큰 문제로 야기될 수 있습니다.</p>
<p><img src="https://velog.velcdn.com/images/edith_0318/post/23cddf2b-a042-4d1b-8a12-3cbc0d7eec21/image.png" alt="">
<em>[그림] 데이터를 갈취하는 해커</em></p>
<p>만일 우리가 실제 서비스가 되는 상용 앱을 운영 중이라면, 여러분들이 구축한 클라이언트 뒤의 서버와 연결되어 있는 DB에는 라이브 데이터(live data)가 쌓일 것입니다.</p>
<ul>
<li>라이브 데이터(live data)
실제 서비스되고 있는 앱의 데이터베이스(Data Base, DB)에 적재되고 있는 데이터를 의미합니다. 유저 및 상품, 결제 등 다양한 정보들을 예로 들 수 있습니다.</li>
</ul>
<p>이런 라이브 데이터는 민감성이 높은 데이터들이 위주이기 때문에 보안이 무엇보다 중요합니다. 그러나 우리들의 서비스 및 프로젝트가 모든 출처의 접근을 허락한다면 이러한 보안성이 현저히 낮아지고, 해킹의 위험에 그대로 노출되게 됩니다.</p>
<p><img src="https://velog.velcdn.com/images/edith_0318/post/9ae2c13d-e0b4-480e-ab77-1fc5c8a94890/image.png" alt=""></p>
<p><em>[그림] 백엔드 개발자에게 요청해야 합니다.</em></p>
<p>따라서 우리는 모든 도메인을 허용해서는 안 되고, 특정 도메인을 허용하도록 구현해야 합니다. 프론트엔드 개발자가 백엔드 개발자에게 프론트엔드 개발 서버 도메인을 허용해달라고 요청을 해야하고, 백엔드 개발자는 응답 헤더에 필요한 값들을 담아서 전달을 해줘야 합니다. 서버에서 적절한 응답 헤더를 받지 못하면 브라우저에서 에러가 발생하기 때문입니다.</p>
<h2 id="proxy">Proxy</h2>
<p>그러나 위의 정석적인 과정 없이 React 라이브러리, 혹은 Webpack Dev Server에서 제공하는 proxy 기능을 사용하면 CORS 정책을 우회할 수 있습니다. 이는 별도의 응답 헤더를 받을 필요 없이 브라우저는 React 앱으로 데이터를 요청하고, 해당 요청을 백엔드로 전달하게 됩니다. 여기서 React 앱이 서버로부터 받은 응답 데이터를 다시 브라우저로 전달하는 방법을 쓰기 때문에 브라우저는 CORS 정책을 위반한지 모르게 됩니다. 브라우저를 proxy 기능을 통해 속이는 것입니다.
<img src="https://velog.velcdn.com/images/edith_0318/post/0465ac6d-9ad9-4b5b-8338-bc13cd510823/image.png" alt="">
<em>[그림] proxy 적용 전 흐름</em></p>
<p>위의 그림은 proxy를 적용해 브라우저를 속이기 전 흐름입니다. 프론트엔드, 즉 우리가 개발한 React 앱에서 브라우저 쪽으로 요청을 보냅니다. 그러면 브라우저는 백엔드, 즉 서버 쪽으로 리소스를 요청하게 됩니다. 이때 접근 권한이 있는지, 즉 출처가 같은지 확인하는데 이때 백엔드 서버는 정상적으로 200 OK 응답을 브라우저에게 보냅니다. 마지막으로 브라우저는 받은 리소스 및 응답과 함께 출처가 같은지 아닌지 확인하게 되는데, 이때 출처가 다르다면 응답을 파기(CORS Error) 하고, 출처가 같다면 응답을 파기하지 않고 다시 프론트엔드 쪽으로 응답을 보내주는 것입니다.</p>
<p><img src="https://velog.velcdn.com/images/edith_0318/post/cece2afe-2731-4a85-954e-68090f7194fe/image.png" alt="">
<em>[그림] proxy 적용 후 흐름</em></p>
<p>위의 그림은 proxy를 적용해 브라우저를 속인 후 흐름입니다. React 앱에서 브라우저를 통해 API를 요청할 때, proxy를 통해 백엔드 서버로 요청을 우회하여 보내게 됩니다. 그러면 백엔드 서버는 응답을 React 앱으로 보내고, React 앱은 받은 응답을 백엔드 서버 대신 브라우저에게 전달합니다. 이렇게 되면 출처가 같아지기 때문에 브라우저는 이 사실을 눈치 채지 못하고 허용하게 됩니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[CI/CD 파이프라인]]></title>
            <link>https://velog.io/@edith_0318/CICD-%ED%8C%8C%EC%9D%B4%ED%94%84%EB%9D%BC%EC%9D%B8</link>
            <guid>https://velog.io/@edith_0318/CICD-%ED%8C%8C%EC%9D%B4%ED%94%84%EB%9D%BC%EC%9D%B8</guid>
            <pubDate>Fri, 03 Feb 2023 04:38:53 GMT</pubDate>
            <description><![CDATA[<h2 id="배포-자동화">배포 자동화</h2>
<p>배포 자동화란 한번의 클릭 혹은 명령어 입력을 통해 전체 배포 과정을 자동으로 진행하는 것을 뜻합니다. 배포 자동화가 왜 필요할까요?</p>
<ul>
<li>먼저 수동적이고 반복적인 배포 과정을 자동화함으로써 시간이 절약됩니다.</li>
<li>휴먼 에러(Human Error)를 방지할 수 있습니다.
여기서 휴먼 에러란 사람이 수동적으로 배포 과정을 진행하는 중에 생기는 실수들을 뜻합니다. 그 전에 했던 배포 과정과 비교하여 특정 과정을 생략하거나 다르게 진행하여 오류가 발생하는 것이 휴먼 에러의 예로 볼 수 있습니다.</li>
</ul>
<p>배포 자동화를 통해 전체 배포 과정을 매번 일관되게 진행하는 구조를 설계하여 휴먼 에러 발생 가능성을 낮출 수 있습니다.</p>
<h2 id="cicd-파이프라인">CI/CD 파이프라인</h2>
<p>앞서 우리는 전통적인 개발 프로세스와 모던 개발 프로세스에 대해 배웠습니다. 그리고 SaaS가 모던 개발 프로세스로 개발하기 적합한 소프트웨어임도 확인했습니다.</p>
<p>그렇다면 이번에는 이렇게 생각해볼까요? 사용자 업데이트에 대한 걱정에서도 벗어났고, 하루에 여러 번의 배포도 가능해졌습니다. 그렇다면 어떻게 빠른 배포 속도를 보장 받을 수 있을까요? 개발자가 배포할 때마다 일일히 빌드하고 배포하는 과정을 진행하는 것은 한두 번이면 충분하겠지만, 이러한 과정이 수없이 진행된다면 일일히 이 과정을 수행하는 것이 번잡스럽고 지루할 것입니다.</p>
<p>그래서 이 수없이 진행되는 배포 과정을 자동화시키는 방법을 구축하게 되는데, 그것을 CI/CD 파이프라인이라고 합니다.</p>
<p><img src="https://velog.velcdn.com/images/edith_0318/post/512cd723-9e8b-441a-8bc2-018d708ae0d2/image.png" alt=""></p>
<p>해당 그림은 배포 과정을 도식화한 것입니다. 개발자가 코드를 원격 저장소에 올리면, 그 코드가 빌드 및 테스트와 릴리즈를 거쳐 배포 서버로 전달 됩니다. 배포 서버에 도달한 빌드된 코드는 애플리케이션 서버로 최종 배포가 완료 되고, 그 결과물을 유저가 직접 확인하게 되는 것입니다.</p>
<p>여기서 자동화를 꾀하는 부분은 보통 코드가 빌드되면서 최종적으로 배포가 되는 단계까지입니다. 이 부분을 지속적인 통합 및 배포를 위하여 일련의 자동화 단계로 만드는데, 이것을 파이프라인을 구축한다고 표현합니다.</p>
<h2 id="cicd-파이프라인을-구성하는-기본-단계와-수행-작업">CI/CD 파이프라인을 구성하는 기본 단계와 수행 작업</h2>
<p><img src="https://velog.velcdn.com/images/edith_0318/post/83773400-ebc8-4c53-bfb8-e122c303b926/image.png" alt=""></p>
<p>배포에서 파이프라인(Pipeline)이란 용어는 소스 코드의 관리부터 실제 서비스로의 배포 과정을 연결하는 구조를 뜻합니다. 파이프라인은 전체 배포 과정을 여러 단계(Stages)로 분리합니다. 각 단계는 파이프라인 안에서 순차적으로 실행되며, 각 단계마다 주어진 작업(Actions)들을 수행합니다.</p>
<p>파이프라인을 여러 단계로 분리할 때, 대표적으로 쓰이는 세 가지 단계가 존재합니다. 각 단계의 이름 및 수행하는 작업에 대해서 알아보겠습니다.</p>
<ol>
<li>Source 단계: Source 단계에서는 원격 저장소에 관리되고 있는 소스 코드에 변경 사항이 일어날 경우, 이를 감지하고 다음 단계로 전달하는 작업을 수행합니다.</li>
<li>Build 단계: Build 단계에서는 Source 단계에서 전달받은 코드를 컴파일, 빌드, 테스트하여 가공합니다. 또한 Build 단계를 거쳐 생성된 결과물을 다음 단계로 전달하는 작업을 수행합니다.</li>
<li>Deploy 단계: Deploy 단계에서는 Build 단계로부터 전달받은 결과물을 실제 서비스에 반영하는 작업을 수행합니다.</li>
</ol>
<p>파이프라인의 단계는 필요에 따라 더 세분화되거나 간소화될 수 있습니다. DevOps를 전문으로 학습하는 경우 아래와 같이 파이프라인의 단계를 세분화해서 나누기도 합니다. 또한, 해당 툴을 소개하는 업체에 따라 용어를 미묘하게 다르게 사용하기도 합니다.</p>
<h3 id="cicd-파이프라인-구성-요소-및-장점">CI/CD 파이프라인 구성 요소 및 장점</h3>
<ul>
<li>빌드 (소프트웨어 컴파일)</li>
<li>테스트 (호환성 및 오류 검사)</li>
<li>릴리스 (버전 제어 저장소의 애플리케이션 업데이트)</li>
<li>배포 (개발에서 프로덕션 환경으로의 변환)</li>
<li>규정 준수 및 유효성 검사</li>
</ul>
<p>로 이루어져 있으며, 이 과정이 실무에서는 반복적인 프로세스이기 때문에 이 부분을 일련의 자동화 단계로 만든다고 볼 수 있습니다.</p>
<p>이렇게 구축된 파이프라인은 최신 버전의 소프트웨어 애플리케이션을 업데이트하고 제공하려는 일련의 처리 단계에 걸리는 시간을 수동으로 하는 것보다 더 빠르고 안정적이며 효과적으로 줄여주고 CI/CD 인프라와의 호환성과 효율성을 높여줍니다.</p>
<h3 id="실습">실습</h3>
<p><a href="http://fe-53-shinchangyoung-s3.s3-website.ap-northeast-2.amazonaws.com/">http://fe-53-shinchangyoung-s3.s3-website.ap-northeast-2.amazonaws.com/</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[CI/CD]]></title>
            <link>https://velog.io/@edith_0318/CICD</link>
            <guid>https://velog.io/@edith_0318/CICD</guid>
            <pubDate>Fri, 03 Feb 2023 04:08:46 GMT</pubDate>
            <description><![CDATA[<h2 id="cicd란">CI/CD란</h2>
<p>CI/CD는 약어로, 몇 가지의 다른 의미를 가지고 있습니다. CI/CD의 &quot;CI&quot;는 개발자를 위한 자동화 프로세스인 지속적인 통합(Continuous Integration)을 의미합니다. CI를 성공적으로 구현할 경우 애플리케이션에 대한 새로운 코드 변경 사항이 정기적으로 빌드 및 테스트되어 공유 리포지토리에 통합되므로 여러 명의 개발자가 동시에 애플리케이션 개발과 관련된 코드 작업을 할 경우 서로 충돌할 수 있는 문제를 해결할 수 있습니다.</p>
<p>CI/CD의 &quot;CD&quot;는 지속적인 서비스 제공(Continuous Delivery) 및/또는 지속적인 배포(Continuous Deployment)를 의미하며 이 두 용어는 상호 교환적으로 사용됩니다. 두 가지 의미 모두 파이프라인의 추가 단계에 대한 자동화를 뜻하지만 때로는 얼마나 많은 자동화가 이루어지고 있는지를 설명하기 위해 별도로 사용되기도 합니다.</p>
<h2 id="cicd의-단계">CI/CD의 단계</h2>
<p>일반적인 앱의 개발 및 유지보수 단계는 아래와 같음을 우리는 앞서 배웠습니다. 여기서 지속적 통합 및 지속적 전달을 단계별로 꾀할 수 있습니다.
<img src="https://velog.velcdn.com/images/edith_0318/post/06a159d1-4db1-422b-a7f4-fcf04ff3536c/image.png" alt="">
<em>[그림] 일반적인 앱의 개발 및 유지보수 단계</em></p>
<h3 id="지속적-통합continuous-integration-ci">지속적 통합(Continuous Integration, CI)</h3>
<p>개발자를 위한 자동화 프로세스라고 볼 수 있으며, Code - Build - Test 단계에서 꾀할 수 있습니다.</p>
<ul>
<li>Code : 개발자가 코드를 원격 코드 저장소 (Ex. github repository)에 push하는 단계입니다.</li>
<li>Build : 원격 코드 저장소로부터 코드를 가져와 유닛 테스트 후 빌드하는 단계입니다.</li>
<li>Test : 코드 빌드의 결과물이 다른 컴포넌트와 잘 통합되는 지 확인하는 과정입니다.</li>
</ul>
<p>이 과정에서 개발자는 코드를 잦게 원격 코드 저장소에 push하고, 테스트 및 빌드를 하며 빌드 결과를 통해 빌드가 성공했는지 실패했는지 확인을 하고, 통합 테스트 결과를 통해 개선 방안을 찾습니다. 이 지속적인 통합 과정을 통해 개발자는 버그를 일찍 발견할 수 있고, 테스트가 완료된 코드에 대해 빠른 전달이 가능해지며 지속적인 배포가 가능해집니다.</p>
<p>지속적 통합은 모든 코드 변화를 하나의 리포지토리에서 관리하는 것 부터 시작합니다. 모든 개발팀이 코드의 변화를 확인할 수 있기 때문에, 투명하게 문제점을 파악할 수 있습니다. 그리고 잦은 풀 리퀘스트(pull request)와 머지(merge)로 코드를 자주 통합합니다. 이 때, 기본적인 테스트도 작동시킬 수 있습니다. 이렇게 지속적 통합을 통해 개발팀은 각자 개발한 코드를 이른 시점에 자주 합치고 자주 테스트 해볼 수 있습니다.</p>
<p>지속적 통합으로 보안 이슈, 에러 등을 쉽게 파악할 수 있어 해당 이슈를 빠르게 개선할 수 있습니다. 이전에는 각자 개발자가 작성한 코드를 합치고 난 후, 모두 모여서 빌드를 시작하고 나서야 문제점을 파악할 수 있었습니다. 지속적 통합이 적용된 개발팀은 코드를 머지하기 전, 이미 빌드 오류나 테스트 오류를 확인하여 훨씬 더 효율적인 개발을 할 수 있게 됩니다.</p>
<h3 id="지속적-배포continuous-deliverydeployment-cd">지속적 배포(Continuous Delivery/Deployment, CD)</h3>
<p>지속적인 서비스 제공(Continuous Delivery) 및 지속적인 배포(Continuous Deployment)를 의미하며 이 두 용어는 상호 교환적으로 사용됩니다. 이 부분은 Release - Deploy - Operate 단계에서 꾀할 수 있습니다.</p>
<ul>
<li>Release : 배포 가능한 소프트웨어 패키지를 작성합니다.</li>
<li>Deploy : 프로비저닝을 실행하고 서비스를 사용자에게 노출합니다. 실질적인 배포 부분입니다.</li>
<li>Operate : 서비스 현황을 파악하고 생길 수 있는 문제를 감지합니다.</li>
</ul>
<p>지속적 배포의 경우, 코드 변경 사항의 병합부터 프로덕션에 적합한 빌드 제공에 이르는 모든 단계로, 테스트 자동화와 코드 배포 자동화가 포함됩니다.</p>
<p>이 프로세스를 완료하면 프로덕션 준비가 완료된 빌드를 코드 리포지토리에 자동으로 배포할 수 있기 때문에 운영팀이 보다 빠르고 손쉽게 애플리케이션을 프로덕션으로 배포할 수 있게 됩니다.</p>
<p>최근에는 클라우드 기술 발전과 맞물려 지속적 통합과 지속적 배포가 빠른 속도로 진행되면서 CI/CD를 하나로 묶어서 다루는 경우가 점차 증가하고 있습니다. 예를 들어, 이전에는 배포 자체가 상당히 오래 걸리고 힘든 일이어서 배포 이전 단계에서 많은 고민을 하곤 했습니다. 서버를 전부 재시작해야 한다거나, 일부 기능을 제공하지 못하는 경우도 많았기 때문입니다. 요즘은 고객의 피드백을 빨리 받기 위해서라도, 서비스를 중단하지 않기 위해서라도 릴리즈만 잘 기록해두고 바로바로 배포하는 사례가 증가하고 있습니다.</p>
<ul>
<li>실리콘밸리 테크 기업들은 다양한 모니터링 툴과 장애 대응 프로세스를 마련해 배포에 대한 자신감을 높여 조직의 비즈니스 역량을 키우고 있습니다. 처음에 소개한 듯이 “하루에 1,000번의 배포를 할 수 있는가?” 는 조직의 기술적 확장 역량을 판단할 수 있는 중요한 지표입니다.</li>
</ul>
<h3 id="지속적-배포-사례">지속적 배포 사례</h3>
<p><img src="https://velog.velcdn.com/images/edith_0318/post/3cc409ed-d8c8-4b8d-b95a-b5d37c63fe45/image.png" alt="">
<em>[그림] 지속적 배포 사례</em></p>
<p>지속적 배포의 가장 흔한 사례가 Github Page입니다. 지정해둔 디렉터리에 정해진 방식에 따라 잘 커밋하기만 하면, Github Page가 알아서 해당 index.html 파일과 해당 디렉터리에 있는 파일을 잘 번들링해서 Github Page 서버에 업로드합니다. 이렇게 자동으로 인터넷에 배포가 되었고, 주변 가족이나 친구들에게 쉽게 만든 결과물을 공유할 수 있었습니다. (틀린 부분이 있다면 빠르게 피드백도 받을 수 있었습니다.) 이전 솔로 프로젝트에서 Github Page 배포에 성공했다면, 이미 지속적 배포를 경험해봤다고 볼 수 있습니다.</p>
<h2 id="cicd의-영역">CI/CD의 영역</h2>
<p>CI/CD는 지속적 통합 및 지속적 제공(CD, Continuous Delivery)의 구축 사례만을 지칭할 때도 있고, 지속적 통합, 지속적 제공, 지속적 배포라는 3가지 구축 사례 모두를 의미하는 것일 수도 있습니다.</p>
<p>좀 더 복잡하게 설명하면 &quot;지속적인 서비스 제공&quot;은 때로 지속적인 배포의 과정까지 포함하는 방식으로 사용되기도 합니다.</p>
<p>결과적으로 CI/CD는 파이프라인으로 표현되는 실제 프로세스를 의미하고, 애플리케이션 개발에 지속적인 자동화 및 지속적인 모니터링을 추가하는 것을 의미합니다. 이 용어는 사례별로 CI/CD 파이프라인에 구현된 자동화 수준 정도에 따라 그 의미가 달라집니다.</p>
<p>대부분의 기업에서는 CI를 먼저 추가한 다음 클라우드 네이티브 애플리케이션의 일부로서 배포 및 개발 자동화를 구현해 나갑니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[DevOps]]></title>
            <link>https://velog.io/@edith_0318/DevOps</link>
            <guid>https://velog.io/@edith_0318/DevOps</guid>
            <pubDate>Fri, 03 Feb 2023 04:04:31 GMT</pubDate>
            <description><![CDATA[<p>전통적인 IT 조직 구조로는 개발팀(Dev)과 운영팀(Ops)이 소프트웨어의 개발과 관리 및 유지보수를 담당해왔습니다.</p>
<p><img src="https://velog.velcdn.com/images/edith_0318/post/0bdb2f82-ce2b-4933-a992-835898ce2db7/image.png" alt="">
개발팀이 잦은 업데이트를 통해 제품에 변화를 만들어야 한다면, 운영팀은 이런 서비스의 구성의 변경을 최소화해 안정성을 확보하는데, 이 부분은 꽤 상충이 되는 부분이기 때문에 갈등을 야기하기도 합니다. 이런 갈등이 빚어지는 구조는 현대 IT 시장에는 맞지 않을 뿐더러, 제품의 릴리즈 주기를 길어지게 만들기도 합니다.</p>
<p>그렇기 때문에 DevOps라는 개념이 만들어졌습니다. DevOps는 소프트웨어 개발(Development)과 IT 운영(Operations)의 합성어입니다. 소프트웨어를 자주, 빨리 그리고 안전하게 배포하는 것을 목표로 하며, 그렇기 때문에 애자일 개발 프로세스를 기반으로 한 것이라고 볼 수 있습니다.</p>
<h2 id="devops-문화">DevOps 문화</h2>
<p><img src="https://velog.velcdn.com/images/edith_0318/post/69d05e4c-36f5-414a-862c-f343d38cc922/image.png" alt=""></p>
<p><em>[그림] 기능의 분리가 아닌 교차되어 어느 누구라도 해당 업무를 수행할 수 있어야 합니다.</em></p>
<p>DevOps는 특정한 업무라던지 부서가 아닌 일종의 개발 문화입니다. 만약 서비스가 중단된다면, 누구든지 문제점을 진단하고 시스템을 복구하여 운영할 수 있는 절차를 알고 있어야 합니다. 이를 위한 기술과 지식이 제공되기 위해서 훈련과 효과적인 협업체계를 마련하는 것이 매우 중요합니다.그러나 실제 실무에서는 업무의 분리를 위해 DevOps팀, 혹은 부서를 두고 있을 수 있습니다.</p>
<h2 id="devops-특징">DevOps 특징</h2>
<p>DevOps는 개발에서 운영까지 하나의 통합된 프로세스로 묶어내고 사용하는 툴과 시스템을 표준화하여 의사소통의 효율성을 확보하고 일련의 작업들을 자동화합니다. 즉 코드 통합, 테스트, 배포 과정을 자동화 시키는 것입니다. 이 부분은 지속적으로 유지되어야 할 필요가 있는데, 지속적 통합 및 배포(CI/CD)라고 하며 DevOps의 핵심 원칙이라고 해도 과언이 아닙니다. 잘 구축된 CI/CD는 애플리케이션의 배포 시간을 크게 단축시킵니다.</p>
<p><img src="https://velog.velcdn.com/images/edith_0318/post/faa4c80a-6cc2-41e9-aae2-dd79ca6c1737/image.png" alt="">
<em>[그림] 애플리케이션 배포에 걸리는 시간</em></p>
<h2 id="devops-사례">DevOps 사례</h2>
<p>실제 인터넷 상에서 스트리밍이나 사진과 같은 엄청난 트래픽과 다양한 사용자 환경 및 요구에 빠르게 대응하기 위해 배포가 많은 기업 중에 Devops를 활용하여 성공한 기업 들이 많습니다.</p>
<ul>
<li>미국 최대 온라인 스트리밍 서비스를 제공하고 있는 넷플릭스(Netflex)는 2012년 기준으로 3천만 명이 넘는 가입자를 보유하며 엄청난 스트리밍 트래픽을 아마존 웹 서비스(AWS)에서 10,000여개 이상의 인스턴스를 10여명 정도의 Devops팀으로 관리하고 있습니다.</li>
<li>사진 SNS 서비스로 야후에 합병된 Flickr는 초당 40,000 장의 사진이 업로드 되는데 Devops를 통하여 모든 배포 및 운영을 자동화하여 하루에도 10여 번 씩 이상의 배포를 하곤 합니다.</li>
<li>음악 스트리밍 서비스로 최근 각광을 받고 있는 Spotify도 강력한 Devops 개발문화를 갖추고 오픈소스 도구를 활용하여 배포와 운영을 자동화하여 관리합니다.</li>
</ul>
<p>이렇게 Devops를 통하여 새로운 기능을 시장에 보다 자주 그리고 빨리 출시할 수 있고, 장애발생시 보다 대응과 시스템의 안정성을 향상시킬 수 있음을 알 수 있습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[개발 프로세스의 발전]]></title>
            <link>https://velog.io/@edith_0318/%EA%B0%9C%EB%B0%9C-%ED%94%84%EB%A1%9C%EC%84%B8%EC%8A%A4%EC%9D%98-%EB%B0%9C%EC%A0%84</link>
            <guid>https://velog.io/@edith_0318/%EA%B0%9C%EB%B0%9C-%ED%94%84%EB%A1%9C%EC%84%B8%EC%8A%A4%EC%9D%98-%EB%B0%9C%EC%A0%84</guid>
            <pubDate>Fri, 03 Feb 2023 03:25:12 GMT</pubDate>
            <description><![CDATA[<h2 id="개발-프로세스">개발 프로세스</h2>
<p>개발 프로세스, 즉 소프트웨어 개발 프로세스 모델은 소프트웨어 개발 생명주기(SDLC, Software Develpment Life Cycle)을 기반으로 만들어졌습니다.
<img src="https://velog.velcdn.com/images/edith_0318/post/731d1c3d-b363-4457-b155-7efb395ca815/image.png" alt=""></p>
<ul>
<li><p>요구분석 및 시스템 명세 작성 : 문제분석 단계라고도 하며, 개발할 소프트웨어의 기능과 제약조건, 목표 등을 사용자와 함께 정확히 정의하는 단계입니다. 개발하고자 하는 소프트웨어의 성격을 정확히 이해하여 이를 토대로 개발 방법과 필요한 자원 및 예산 예측 후 요구명세를 작성합니다.</p>
</li>
<li><p>설계 : 설계단계에서는 앞서 정의한 기능을 실제로 수행하기 위한 방법을 논리적으로 결정합니다. 크게 시스템, 프로그램, UI(User Interface) 설계로 나뉘며, 시스템 구조설계는 시스템을 구성하는 내부 프로그램이나 모듈 간의 관계와 구조를 설계하고, 프로그램설계는 프로그램 내의 각 모듈에서의 처리 절차나 알고리즘을 설계합니다.</p>
<ul>
<li>UI(User Interface) 설계는 사용자 인터페이스 설계로, 사용자가 시스템을 사용하기 위해 보이는 부분을 설계합니다.</li>
</ul>
</li>
<li><p>구현 : 설계 단계에서 논리적으로 결정한 문제 해결 방법을 프로그래밍언어를 사용하여 실제 프로그램을 작성합니다. 이때 프로그래밍 기법은 구조화 프로그래밍과 모듈러 프로그래밍 두 개로 나뉩니다.</p>
<ul>
<li>구조화 프로그래밍 : 조건문, 반복문을 사용하여 프로그램을 작성하고, 순차구조, 선택구조, 반복구조의 세 가지 제어구조로 표현하며, 구조가 명확하여 정확성 검증과 테스트 및 유지보수가 쉬운 장점이 있습니다.</li>
<li>모듈러 프로그래밍 : 프로그램을 여러 개의 작은 모듈로 나누어 계층 관계로 구성하는 프로그래밍 기법으로, 모듈별로 개발과 테스트 및 유지보수 가능하며, 모듈의 재사용 가능하다는 장점이 있습니다.</li>
</ul>
</li>
<li><p>테스트 : 테스트 단계에서는 개발한 시스템이 요구사항을 만족하는지, 실행 결과가 예상한 결과와 정확하게 맞는지를 검사하고 평가하는 일련의 과정입니다. 미처 발견하지 못한 오류를 발견할 수 있기 때문에 매우 중요한 과정입니다.</p>
</li>
<li><p>배포 및 유지보수 : 배포와 유지보수 단계는 시스템이 인수되고 설치된 후(배포) 일어나는 모든 활동을 지칭합니다. 이후 일어나는 커스터마이징, 구현, 테스트 등 모두 이 단계에 포함되므로 소프트웨어 생명주기에서 가장 긴 기간을 차지합니다. 유지보수의 유형에는 수정형, 적응형, 완전형, 예방형 총 네 가지가 있습니다.</p>
<ul>
<li>수정형 유지보수 : 사용 중에 발견한 프로그램의 오류 수정 작업을 진행합니다.</li>
<li>적응형 유지보수 : 시스템과 관련한 환경적 변화에 적응하기 위한 재조정 작업을 합니다.</li>
<li>완전형 유지보수 : 시스템의 성능을 향상하기 위한 개선 작업을 합니다.</li>
<li>예방형 유지보수 : 앞으로 발생할지 모를 변경 사항을 수용하기 위한 대비 작업을 수행합니다.</li>
</ul>
</li>
</ul>
<p>무엇을 개발할지 결정하고 요구사항들을 구현 작업에 적합하게 명확하고 조직화된 구조로 바꾸는 과정을 거쳐, 실제 개발에 착수하는 단계는 ‘구현’부터입니다. 현재 그림 상으로는 이 단계들이 계속 꼬리를 물고 도는 순환 구조로 되어 있지만, 실제 개발 환경에서는 환경에 따라 각각의 단계가 또 세밀하게 나뉘어져 있기도 하며, 각 단계가 계속해서 반복되기도 합니다.
<img src="https://velog.velcdn.com/images/edith_0318/post/d6191bbf-7406-4b71-8934-7f4d8dd48a95/image.png" alt=""></p>
<p>[그림] 이런식으로 때때로 해당 단계가 반복될 수 있습니다.</p>
<p>이러한 개발 프로세스는 다양한 모델이 있는데 어느 정도 규모의 앱을 개발하는 지, 혹은 어떤 종류의 앱을 개발하는 지를 고려하여 모델을 선택해 사용합니다.</p>
<h2 id="전통적인-개발-프로세스">전통적인 개발 프로세스</h2>
<p>기존에 존재하고 있던 개발 프로세스는 워터폴(Waterfall) 방식이 있습니다. 영어에서 주는 직관적인 느낌 그대로, 폭포와 같이 한 방향으로만 프로세스가 진행되는 개발 과정을 뜻합니다. 실제로 한국어로도 “폭포수 개발 방식” 이라고도 합니다.
<img src="https://velog.velcdn.com/images/edith_0318/post/cfa910a2-25c5-4d8e-a6e2-a236a5db3632/image.png" alt="">
<em>[그림] 워터폴의 가장 기본적인 모델</em>
<img src="https://velog.velcdn.com/images/edith_0318/post/726e517d-0035-45cf-82f5-b20ea5986a18/image.png" alt=""></p>
<p>보통 이런 사이클을 가지고 있으며, 유지보수까지 끝나면 다시 처음의 단계로 돌아가 시작하는 것이 가장 기본적인 모델이라고 볼 수 있습니다. 여기서 변형된 모델들이 나오나 보통은 프로세스가 기본 모델에서 추가가 되거나 하나의 프로세스가 세부적으로 나뉘거나 하는 수준에서 그칩니다.
<img src="https://velog.velcdn.com/images/edith_0318/post/458016b6-4fbc-4e76-b5c2-06bb3f4c7ddd/image.png" alt="">
_[그림] 개발된 앱이 배포되어 실제 사용하는 유저들에게 도달_하기까지 시간이 걸립니다.</p>
<p>이런 워터폴 개발 방식은 실제 출시 기한을 정해놓고 순차적으로 프로세스가 진행시켜 어플리케이션(소프트웨어)를 완성해 배포하기 때문에 실제로 배포되어 유저에게 전달되는 시간이 오래 걸립니다. 또한 실제 디자인 또는 개발된 화면을 시각적으로 확인할 수 있는 단계는 이미 많은 단계가 지나온 시점이기 때문에 어떤 버그나 수정 사항이 생기면 다시 처음으로 돌아가 수정되기 때문에 일정과 비용 등 많은 부분에서 에로 사항이 생기게 됩니다. 대부분 그래서 출시 시점에 소프트웨어의 신뢰성 및 안정성을 보장 받기가 힘들며, 배포 직후에도 수많은 버그를 마주하게 될 가능성이 있습니다.</p>
<p><img src="https://velog.velcdn.com/images/edith_0318/post/ac7835ec-495f-4f4b-bb17-50aede56506a/image.png" alt=""></p>
<p>[그림] 그래서 테스트 단계에 다양한 테스트를 도입하기도 합니다.</p>
<p>그래서 전통적인 소프트웨어 개발 프로세스에서는 소프트웨어의 안정성 개선을 위해 테스트 단계에 다양한 테스트들을 도입하기도 합니다.</p>
<ul>
<li><p>시스템 테스트 : 모든 모듈을 통합한 후 최종적으로 완성된 시스템이 요구사항을 만족하는지 확인합니다. 요구사항을 만족하지 않는다면 다시 요구분석 단계로 돌아가 새로 개발을 하기도 합니다.</p>
</li>
<li><p>알파 테스트 : 완전히 개발된 시스템을 개발 현장에서 비공개로 테스트하는 것으로, 주문형 제품의 경우 개발진과 클라이언트 사이에서 동의가 이루어질 때까지 수행됩니다. 대기업의 경우 이 업무를 주로 하는 전문 QA팀이 존재합니다.</p>
</li>
<li><p>베타 테스트 : 고객의 실제 사용 환경에서 수행되는 테스트로, 미리 선별된 유저들이 해당 제품을 사용해 봅니다. 이 과정에서 에러나 버그가 발견되면 수정하는 식으로 진행됩니다.</p>
</li>
</ul>
<p>이 외에도 다양한 테스트가 존재합니다.</p>
<h3 id="전통적인-개발-프로세스의-전달이-가지는-한계">전통적인 개발 프로세스의 전달이 가지는 한계</h3>
<p><img src="https://velog.velcdn.com/images/edith_0318/post/65f7a092-0b4a-48ed-bfbb-f95527947935/image.png" alt=""></p>
<p>혹시 애플리케이션을 사용할 때, 접속하는 순간 “새로운 버전이 존재하니 업데이트를 하세요” 라는 문구와 함께 게임 자체가 꺼지는 경험을 해보셨나요? 이러한 점은 대부분의 모바일 애플리케이션이 사용하는 전달 방식의 한계이기도 합니다. 애플리케이션이 접속할 시, 이런 현상을 보이는 이유는 다양한 테스트 방식을 도입했음에도 사용자가 항상 최신 상태로 업데이트를 해줘야 하고, 버그 수정(patch)된 버전을 유저에게 즉각적으로 전달하기가 어렵기 때문입니다.</p>
<h2 id="모던-개발-프로세스">모던 개발 프로세스</h2>
<p>그런 전통적인 개발 프로세스에서 벗어나기 위해 만들어진 프로세스 중 하나가 애자일(Agile) 방식입니다. 이 애자일 방식은 ‘스프린트(sprint)&#39; 라고 불리는 짧은 주기의 개발 사이클을 계속해서 반복합니다.
<img src="https://velog.velcdn.com/images/edith_0318/post/6877f080-2723-48a8-9916-57177c74464f/image.png" alt="">
<em>[그림] 애자일 방식</em></p>
<p>이 방식은 요구사항이 변하는 것을 당연한 전제로 두고 있습니다. 따라서 계획에 의존하여 형식적인 절차를 끝까지 따라야 하고 중간에 뒤로 회귀할 수 없는 전통적인 개발 프로세스보다는 훨씬 효율적으로 개발에 착수할 수 있습니다. 애자일 개발 프로세스를 적절히 사용하면 빠르게 문제를 해결해 하루에도 여러 번의 배포가 가능해집니다. 이러한 방식은 SaaS(Software as a Service, 서비스형 소프트웨어)를 개발하는 데에 적합합니다.</p>
<h3 id="saas란">SaaS란?</h3>
<p><img src="https://velog.velcdn.com/images/edith_0318/post/6993e53e-d98f-45f4-8427-1ba692079903/image.png" alt=""></p>
<p>SaaS는 클라우드 서비스의 한 방식으로, 브라우저에 접속하기만 해도 새 버전을 즉시 사용할 수 있는 서비스 방식입니다. 애플리케이션부터 서버, 가상화, 스토리지, 네트워킹까지 전부 공급자 쪽에서 관리하기 때문에 고객이 제어하거나 관리할 부분이 거의 없게 됩니다. 따라서 사용자 업데이트에 대한 걱정에서 벗어나며, 하루에 여러 번의 배포도 가능합니다. 또한 빠른 배포 속도도 보장 받을 수 있습니다.</p>
<p><img src="https://velog.velcdn.com/images/edith_0318/post/e695bf2d-dc6e-4c39-afc1-dc1d683b155a/image.png" alt=""></p>
<p><em>[그림] 구글의 Gmail 업데이트 로그.</em></p>
<p>해당 그림은 구글의 Gmail 업데이트 로그입니다. 해당 기간 동안 Gmail은 잦은 업데이트를 했지만 Gmail은 SaaS 방식으로 서비스를 제공하기 때문에, 사용자는 업데이트를 했는지도 모르고 해당 애플리케이션을 사용했을 것임을 유추할 수 있습니다.</p>
<h3 id="전통적인-개발-프로세스-vs-모던-개발-프로세스">전통적인 개발 프로세스 VS 모던 개발 프로세스</h3>
<p>그렇다면 전통적인 개발 프로세스는 모던 개발 프로세스에 비해 현저히 떨어지는 프로세스일까요? 그렇지는 않습니다. 모던한 개발 프로세스를 따른다 해도 “제대로&quot; 따르지 않는다면 하느니만 못하기 때문입니다. 아래의 표는 전통적인 개발 프로세스인 워터폴과 모던 개발 프로세스인 애자일의 장점 및 단점을 정리한 표입니다.</p>
<p><img src="https://velog.velcdn.com/images/edith_0318/post/9d4c9bee-6424-4d7e-83da-ae2d9fa1c410/image.png" alt=""></p>
<p>또한 앞서 이야기했듯, 어느 정도 규모의 앱을 개발하는 지, 혹은 어떤 종류의 앱을 개발하는 지를 고려하여 모델을 선택해 사용하기 때문에, 오히려 어떤 상황에서는 체계적인 계획과 문서를 만들고 시작하는 전통적인 개발 프로세스가 더 적합할 수도 있다는 점을 알아둬야 합니다. 밑의 표는 해당 프로세스가 적합한 조직을 나열한 표입니다.</p>
<p><img src="https://velog.velcdn.com/images/edith_0318/post/df7fc656-ff35-4c80-8d58-93cbd9927d73/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Lighthouse 사용 해보기]]></title>
            <link>https://velog.io/@edith_0318/Lighthouse-%EC%82%AC%EC%9A%A9-%ED%95%B4%EB%B3%B4%EA%B8%B0</link>
            <guid>https://velog.io/@edith_0318/Lighthouse-%EC%82%AC%EC%9A%A9-%ED%95%B4%EB%B3%B4%EA%B8%B0</guid>
            <pubDate>Wed, 01 Feb 2023 06:21:07 GMT</pubDate>
            <description><![CDATA[<h2 id="lighthouse란">Lighthouse란?</h2>
<p>Lighthouse는 다양한 지표를 이용하여 웹페이지의 성능 검사를 해줄 뿐만 아니라 그에 대한 개선책도 제공해준다.</p>
<p>나는 네이버에서 성능 검사를 실행시켜 보았다.</p>
<p><img src="https://velog.velcdn.com/images/edith_0318/post/425e81fa-e928-4ae4-bf94-fbd4f7007f0d/image.png" alt=""></p>
<ol>
<li>Performance
Performance 항목에서는 웹 성능을 측정한다.
네이버에서는 88점이 나와서 준수한 점수를 받았다.</li>
</ol>
<p><img src="https://velog.velcdn.com/images/edith_0318/post/355bd8c2-33e5-4b97-b978-48f61a57da6f/image.png" alt=""></p>
<ol start="2">
<li>Accessibility
Accessibility 항목에서는 웹 페이지가 웹 접근성을 잘 갖추고 있는지 확인했다.
Accessibility에는 주의가 필요한 것 같다.</li>
</ol>
<p><img src="https://velog.velcdn.com/images/edith_0318/post/57731df0-c6fb-4dc4-9f40-5fca2945c5d1/image.png" alt=""></p>
<ol start="3">
<li>Best Practices
Best Practices 항목에서는 웹 페이지가 웹 표준 모범 사례를 잘 따르고 있는지 확인한다.
네이버에서는 보안 취약점이 있는 자바스크립트 라이브러리가 포함되 있는것 같다.</li>
</ol>
<p><img src="https://velog.velcdn.com/images/edith_0318/post/bb5d0873-dd9d-4d3a-baee-dcd7baababe2/image.png" alt=""></p>
<ol start="4">
<li>SEO
SEO 항목에서는 웹 페이지가 검색 엔진 최적화가 잘 되어있는지 확인한다.
네이버에는 링크 2곳에 설명 텍스트가 없었다.</li>
</ol>
<p><img src="https://velog.velcdn.com/images/edith_0318/post/575687a1-83db-42f6-8ffe-4c1102b11c1e/image.png" alt=""></p>
<ol start="5">
<li>PWA (Progressive Web App)
PWA 항목에서는 해당 웹 사이트가 모바일 애플리케이션으로서도 잘 작동하는지 확인한다.
몇개 구성되지 않은 부분이 있는 것 같다
<img src="https://velog.velcdn.com/images/edith_0318/post/3088575d-7556-470b-96c9-42802d3cf0c1/image.png" alt=""></li>
</ol>
<h2 id="개선-방향-잡기">개선 방향 잡기</h2>
<p>Lighthouse는 성능을 측정할 뿐 아니라 무엇이 시간을 많이 소모하는지, 어떻게 개선하여 최적화를 할 수 있을지 해결책도 제시해 주었다.
<img src="https://velog.velcdn.com/images/edith_0318/post/86635b56-d16b-4335-8f3e-fdef7e203325/image.png" alt=""></p>
<p>성능 개선 자세한 설명
MDN문서
<a href="https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Map#%EA%B0%99%EC%9D%B4_%EB%B3%B4%EA%B8%B0">https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Map#%EA%B0%99%EC%9D%B4_%EB%B3%B4%EA%B8%B0</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[ Lighthouse]]></title>
            <link>https://velog.io/@edith_0318/Lighthouse</link>
            <guid>https://velog.io/@edith_0318/Lighthouse</guid>
            <pubDate>Wed, 01 Feb 2023 05:58:58 GMT</pubDate>
            <description><![CDATA[<p>사이트를 검사하여 성능 측정을 할 수 있는 도구인 Lighthouse를 소개합니다. Lighthouse는 다양한 지표를 이용하여 웹페이지의 성능 검사를 해줄 뿐만 아니라 그에 대한 개선책도 제공해줍니다.
<img src="https://velog.velcdn.com/images/edith_0318/post/a4ffb119-8cc5-403d-880d-9850097fe9ec/image.png" alt="">
Lighthouse는 구글에서 개발한 오픈소스로서 웹 페이지의 품질을 개선할 수 있는 자동화 툴입니다. Lighthouse는 성능, 접근성, PWA, SEO 등을 검사하며 이를 이용해 사용자는 어떤 웹페이지든 품질 검사를 할 수 있습니다.</p>
<p>Lighthouse는 Chrome DevTools부터 CLI, 노드 모듈 등 다양한 경로를 통해 사용할 수 있습니다. 검사할 페이지의 url을 Lighthouse에 전달하면 Lighthouse는 해당 페이지에 대한 여러 검사를 실행합니다.<img src="https://velog.velcdn.com/images/edith_0318/post/71d4177b-ceed-41c0-be88-ec47cbbe1920/image.png" alt="">
그 후, 위 이미지처럼 검사 결과에 따른 리포트를 생성하고 개발자는 해당 리포트를 통해 점수가 낮은 지표에 대해 개선을 꾀할 수 있습니다. 또한 각각의 지표가 왜 중요한지, 어떻게 개선할 수 있는 지에 대한 레퍼런스도 리포트에서 참고할 수 있습니다.</p>
<h2 id="lighthouse-시작하기">Lighthouse 시작하기</h2>
<p>Lighthouse를 이용할 수 있는 방법은 다양합니다. 여러분의 개발환경에 가장 친숙한 방법을 골라 Lighthouse를 실행할 수 있습니다.</p>
<h3 id="chrome-개발자-도구에서-실행하기">Chrome 개발자 도구에서 실행하기</h3>
<ul>
<li>크롬에서 검사하고 싶은 페이지의 url을 입력합니다.</li>
<li>개발자 도구를 엽니다.</li>
<li>lighthouse 탭을 클릭합니다.
<img src="https://velog.velcdn.com/images/edith_0318/post/d2b18bee-2881-4ed4-ac7e-e19b260f0fc3/image.png" alt=""></li>
<li>Generate report를 클릭합니다. Categories에서 특정한 지표만 선택하여 검사할 수도 있습니다.</li>
<li><img src="https://velog.velcdn.com/images/edith_0318/post/e54b7c0e-8fb1-4994-8a0c-2fe464a49ec8/image.png" alt="">
대략 30-60초간 검사가 실행됩니다. 그 후 아래와 같이 리포트가 해당 페이지의 개발자 도구내에 생성됩니다.</li>
</ul>
<h3 id="node-cli에서-실행하기">Node CLI에서 실행하기</h3>
<ol>
<li><p>Lighthouse를 설치합니다. 이때-g 옵션을 사용하여 Lighthouse를 전역 모듈로 설치하는 것이 좋습니다.</p>
<pre><code class="language-js">npm install -g lighthouse</code></pre>
</li>
<li><p>다음의 명령어로 검사를 실행할 수 있습니다.</p>
<pre><code class="language-js">lighthouse &lt;url&gt;</code></pre>
</li>
<li><p>다음의 명령어로 모든 옵션을 볼 수 있습니다.</p>
<pre><code class="language-js">lighthouse --help </code></pre>
<p>Lighthouse 노드모듈을 이용해 동적으로 프로그래밍하여 페이지 검사 리포트를 생성할 수도 있습니다. 이를 이용해 성능 테스트를 자동화할 수 있습니다.</p>
</li>
</ol>
<h3 id="lighthouse-분석-결과-항목">Lighthouse 분석 결과 항목</h3>
<ol>
<li><p>Performance
Performance 항목에서는 웹 성능을 측정합니다. 화면에 콘텐츠가 표시되는데 시간이 얼마나 걸리는지, 표시된 후 사용자와 상호작용하기 까진 얼마나 걸리는지, 화면에 불안정한 요소는 없는지 등을 확인합니다.</p>
</li>
<li><p>Accessibility
Accessibility 항목에서는 웹 페이지가 웹 접근성을 잘 갖추고 있는지 확인합니다. 대체 텍스트를 잘 작성했는지, 배경색과 콘텐츠 색상의 대비가 충분한지, 적절한 WAI-ARIA 속성을 사용했는지 등을 확인합니다.</p>
</li>
<li><p>Best Practices
Best Practices 항목에서는 웹 페이지가 웹 표준 모범 사례를 잘 따르고 있는지 확인합니다. HTTPS 프로토콜을 사용하는지, 사용자가 확인할 확률은 높지 않지만 콘솔 창에 오류가 표시 되지는 않는지 등을 확인합니다.</p>
</li>
<li><p>SEO
SEO 항목에서는 웹 페이지가 검색 엔진 최적화가 잘 되어있는지 확인합니다. 애플리케이션의 robots.txt가 유효한지, <code>&lt;meta&gt;</code> 요소는 잘 작성되어 있는지, 텍스트 크기가 읽기에 무리가 없는지 등을 확인합니다.</p>
</li>
<li><p>PWA (Progressive Web App)
PWA 항목에서는 해당 웹 사이트가 모바일 애플리케이션으로서도 잘 작동하는지 확인합니다. 앱 아이콘을 제공하는지, 스플래시 화면이 있는지, 화면 크기에 맞게 콘텐츠를 적절하게 배치했는지 등을 점수가 아닌 체크리스트로 확인합니다.</p>
</li>
</ol>
<h3 id="lighthouse의-performance-측정-메트릭">Lighthouse의 Performance 측정 메트릭</h3>
<p><img src="https://velog.velcdn.com/images/edith_0318/post/66e260bb-5650-47fe-913e-ad9d93644fe8/image.png" alt="">
Lighthouse가 생성한 리포트를 보면 위 이미지처럼 하단에 주요 메트릭이 안내되어 있는 것을 볼 수 있습니다. 각각의 메트릭이 어떤 것을 측정하는 지 알아봅시다.</p>
<h4 id="1-first-contentful-paint">1. First Contentful Paint</h4>
<p>First Contentful Paint, 줄여서 FCP는 성능(Performance) 지표를 추적하는 메트릭입니다.</p>
<p>FCP는 사용자가 페이지에 접속했을 때 브라우저가 DOM 컨텐츠의 첫 번째 부분을 렌더링하는 데 걸리는 시간을 측정합니다. 즉 사용자가 감지하는 페이지의 로딩속도를 측정할 수 있습니다. 우수한 사용자 경험을 제공하려면 FCP가 1.8초 이하여야 합니다.</p>
<p>페이지의 이미지와 <code>&lt;canvas&gt;</code> 요소, SVG 등 모두 DOM 콘텐츠로 구분되며 <code>&lt;iframe&gt;</code> 요소의 경우 이에 포함되지 않습니다.
  <img src="https://velog.velcdn.com/images/edith_0318/post/b9a8bbc4-7573-4cf8-9ea9-c7a4aba393db/image.png" alt="">
위 타임라인에서 FCP는 첫 번째 텍스트와 이미지 요소가 화면에 렌더링되는 두 번째 프레임에서 측정됩니다.</p>
<p>이때 FCP처럼 일부 콘텐츠의 첫 번째 렌더링 시점을 측정하는 것이 아닌 주요 콘텐츠 로딩이 완료된 시점을 측정하는 것을 목표로 한다면 Large Contentful Paint, 줄여서 LCP 지표로 확인할 수 있습니다.</p>
<h4 id="2-largest-contentful-paint">2. Largest Contentful Paint</h4>
<p>Largest Contentful Paint, 줄여서 LCP는 뷰포트를 차지하는 가장 큰 콘텐츠(이미지 또는 텍스트 블록)의 렌더 시간을 측정합니다. 이를 이용해 주요 콘텐츠가 유저에게 보이는 시간까지를 가늠할 수 있습니다.</p>
<p>다음의 표를 기준으로 LCP 점수를 해석할 수 있습니다.
<img src="https://velog.velcdn.com/images/edith_0318/post/5c8fbee8-cd64-4a04-97ec-ccdbc555b29c/image.png" alt=""></p>
<h4 id="3-speed-index">3. Speed Index</h4>
<p>Speed Index는 성능(Performance) 지표를 추적하는 메트릭입니다. Speed Index는 페이지를 로드하는 동안 얼마나 빨리 컨텐츠가 시각적으로 표시되는 지를 측정합니다.</p>
<p>Lighthouse는 먼저 브라우저의 페이지 로딩과정을 각 프레임마다 캡쳐합니다. 그리고 프레임 간 화면에 보이는 요소들을 계산합니다. 그 후 Speedline Node.js module을 이용하여 Speed Index 점수를 그래프의 형태로 나타냅니다.</p>
<p>점수에 따라 다음의 기준으로 성능을 분류합니다.
<img src="https://velog.velcdn.com/images/edith_0318/post/474f311b-1762-4329-8420-caba144e628d/image.png" alt=""></p>
<h4 id="4-time-to-interactive">4. Time to interactive</h4>
<p>Time to interactive, 줄여서 TTI는 페이지가 로드되는 시점부터 사용자와의 상호작용이 가능한 시점까지의 시간을 측정합니다.</p>
<p>TTI는 페이지가 완전히 상호 작용 가능하기까지의 시간을 측정합니다. 그 기준은 다음과 같습니다.</p>
<ul>
<li>페이지에 FCP로 측정된 컨텐츠가 표시되어야 합니다.</li>
<li>이벤트 핸들러가 가장 잘 보이는 페이지의 엘리먼트에 등록됩니다.</li>
<li>페이지가 0.05초안에 사용자의 상호작용에 응답합니다.</li>
</ul>
<p>TTI 점수는 아카이브된 HTTP 데이터를 기반으로 백분위 단위로 점수를 측정합니다. 다음의 표를 기준으로 점수를 해석할 수 있습니다
<img src="https://velog.velcdn.com/images/edith_0318/post/c6fe72ad-954a-49d3-9e84-bf81a3297b4b/image.png" alt=""></p>
<h4 id="5-total-blocking-time">5. Total Blocking Time</h4>
<p>대부분의 사용자는 0.05초가 넘는 작업에는 응답이 올때까지 계속 키보드를 두드리거나 마우스를 클릭하기 때문에 페이지가 느리다고 인식합니다. 이를 개선하기 위한 지표가 TBT입니다.</p>
<p>Total Blocking Time, 줄여서 TBT는 페이지가 유저와 상호작용하기까지의 막혀있는 시간을 측정합니다. Lighthouse에서는 FCP와 TTI 사이에 긴 시간이 걸리는 작업들을 모두 기록하여 TBT를 측정합니다.</p>
<p>다음의 예를 통하여 TBT의 측정 기준을 살펴보겠습니다.
<img src="https://velog.velcdn.com/images/edith_0318/post/487cd6c2-9c4c-434c-af21-6c35a9f2a0b7/image.png" alt="">
위 타임라인에는 5개의 작업이 있고 그 중 50ms(0.05초)를 초과하는 3개는 긴 작업으로 간주됩니다.
<img src="https://velog.velcdn.com/images/edith_0318/post/df6ad5eb-2e29-48fd-a135-ae9829b15e20/image.png" alt="">
따라서 메인스레드에서 작업을 실행하는 데 소요된 총 시간은 560ms(0.56)초이지만 TBT로 측정되는 것은 345ms(0.345초)입니다.</p>
<h4 id="6-cumulative-layout-shift">6. Cumulative Layout Shift</h4>
<p>온라인 기사를 읽다가 갑자기 페이지 일부분이 바뀐 경험이 있으신가요? 아무런 경고 없이 텍스트가 움직이며 읽던 부분을 놓치게 되거나, 더 심한 경우 링크나 버튼을 탭하기 직전 갑작스레 링크가 움직이는 바람에 의도하지 않은 것을 클릭할 수도 있습니다.
<img src="https://velog.velcdn.com/images/edith_0318/post/824c3aa6-e8ff-4bb0-a525-95f141b103a2/image.gif" alt="">
대부분의 경우 이러한 경험은 짜증스러운 정도에서 그치지만, 경우에 따라 실제 피해를 겪게 될 수 있습니다. 위 예시처럼 결제와 관련된 경우가 그렇습니다. 이런 상황을 측정하기 위한 지표가 CLS입니다.</p>
<p>Cumulative Layout Shift, 줄여서 CLS는 사용자에게 컨텐츠가 화면에서 얼마나 많이 움직이는지(불안정한 지)를 수치화한 지표입니다. 이 지표를 통해 화면에서 이리저리 움직이는 요소(불안정한 요소)가 있는 지를 측정할 수 있습니다.</p>
<h3 id="개선-방향-잡기">개선 방향 잡기</h3>
<p>Lighthouse는 성능을 측정할 뿐 아니라 무엇이 시간을 많이 소모하는지, 어떻게 개선하여 최적화를 할 수 있을지 해결책도 제시해줍니다. Opportunities 항목을 확인하면 각 메트릭별 문제를 확인할 수 있습니다.
<img src="https://velog.velcdn.com/images/edith_0318/post/a8636df3-590e-4492-996e-cfe8ccb480b2/image.png" alt="">
각 항목을 열어보면 해당 문제에 대한 상세 설명과 함께 어떤 코드, 어떤 화면에서 문제 상황을 발견했는지 확인할 수 있기 때문에 최적화의 방향을 잡기 좋습니다.
<img src="https://velog.velcdn.com/images/edith_0318/post/09ba6890-c380-4ab0-a62f-7ffd82ac523f/image.png" alt="">
Lighthouse는 웹 성능 최적화 뿐만 아니라 웹 접근성, 웹 표준, SEO 관련 항목도 확인하고 해결책을 제시해줍니다. Lighthouse를 이용해서 웹 사이트 성능 최적화 뿐만 아니라, 웹 표준, 웹 접근성, SEO도 개선시켜 보세요.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[트리쉐이킹(Tree Shaking)]]></title>
            <link>https://velog.io/@edith_0318/%ED%8A%B8%EB%A6%AC%EC%89%90%EC%9D%B4%ED%82%B9Tree-Shaking</link>
            <guid>https://velog.io/@edith_0318/%ED%8A%B8%EB%A6%AC%EC%89%90%EC%9D%B4%ED%82%B9Tree-Shaking</guid>
            <pubDate>Wed, 01 Feb 2023 05:43:34 GMT</pubDate>
            <description><![CDATA[<p>트리쉐이킹(Tree Shaking)은 말 그대로 나무를 흔들어 잔가지를 털어내듯 불필요한 코드를 제거하는 것을 의미합니다. 웹 개발을 할 때, 애플리케이션의 규모가 커지면서 코드의 양이 방대해지고, 다양한 라이브러리를 가져다 사용하게 되면 불필요한 코드를 그대로 가져가는 경우가 생각보다 많이 생깁니다. 이런 불필요한 코드들을 찾아내어 제거하면 웹 사이트 성능 최적화에 큰 도움이 됩니다. 특히 JavaScript는 다음과 같은 이유로 가능하면 트리쉐이킹을 해주는 것이 좋습니다.</p>
<h3 id="1-javascript-파일의-크기">1. JavaScript 파일의 크기</h3>
<p>요즘은 과거 HTML 위주의 단순한 웹 페이지와는 비교도 안 될 정도로 규모 있고 화려한 인터랙션을 자랑하는 웹 애플리케이션들이 많습니다. 웹 사이트에서 인터랙션이 많아졌다는 것은 그만큼 JavaScript의 비중이 높아졌다는 뜻이기도 합니다. 실제로 http archive의 자료를 보면, 2011년도에 비해 웹 애플리케이션의 JavaScript 파일 크기의 중윗값이 데스크톱에서는 478.6% 증가했고, 모바일에서는 무려 796.6%나 증가했습니다.
<img src="https://velog.velcdn.com/images/edith_0318/post/e3892bb3-9002-4453-823c-bcc70281a43c/image.png" alt=""></p>
<p>이 자료에서의 파일 크기는 네트워크를 오고 갈 때의 크기로, 압축되어 있는 상태에서의 크기입니다. 실제로 파일을 사용할 땐 압축을 해제한 후에 사용해야 하는 것을 생각하면, 실제 JavaScript 파일의 크기는 훨씬 클 것입니다.</p>
<p>JavaScript 파일의 크기만 커진 것이 아닙니다. JavaScript 파일을 요청하는 HTTP 요청 수 또한 데스크탑에서 155.6%, 모바일에서 425.0% 증가했습니다. 크기가 훨씬 커진 JavaScript 파일이 늘어난 요청 횟수만큼 더 오가는 것이니, 네트워크 리소스 소모가 그만큼 커졌다는 것을 알 수 있습니다. </p>
<p><img src="https://velog.velcdn.com/images/edith_0318/post/b601e154-61c9-4332-95e0-e1733dd15655/image.png" alt=""></p>
<p>JavaScript 파일 크기의 증가, 요청 횟수의 증가는 그만큼 파일이 오고 가는 동안 화면 표시가 늦어진다는 것을 뜻하고, 네트워크 속도가 느린 환경에서는 더 큰 병목현상을 유발합니다. 따라서 트리쉐이킹을 통해 파일 크기를 가능한 줄이는 것이 최적화에 도움이 됩니다.</p>
<h3 id="2-javascript-파일의-실행-시간">2. JavaScript 파일의 실행 시간</h3>
<p>JavaScript 파일이 실행되기 위해서는 여러 과정을 거치게 됩니다. 다운로드부터 필요한 경우에는 우선 요청을 보내어 파일을 다운받아 온 다음 압축을 해제해야 합니다. 그다음에는 JavaScript 코드를 파싱하여 DOM 트리를 생성합니다. 파싱이 끝나면 컴파일하여 컴퓨터가 이해할 수 있는 언어로 바꿔줘야 합니다. 이 컴파일 과정까지 거쳐야지 비로소 코드를 실행할 수 있습니다. 이처럼 코드 실행까지 거쳐야 하는 과정이 많기 때문에 JavaScript는 다른 리소스에 비해서 실행까지 상대적으로 많은 시간을 소모하게 됩니다.</p>
<p>실제로 JavaScript 파일의 크기가 커진 만큼, 파일의 실행 시간 또한 증가한 것을 알 수 있습니다. 데스크톱에서는 측정한 기간이 얼마 되지 않아 확인하기 어렵지만, 모바일에서는 222.2%만큼 실행 시간이 길어져 2.9초의 시간이 소요되는 것을 확인할 수 있습니다.
<img src="https://velog.velcdn.com/images/edith_0318/post/6f941354-13e8-4464-8ade-5a618789b1ad/image.png" alt="">
JavaScript 파일의 실행은 CPU에 크게 영향을 받는데, 그렇다 보니 사양이 천차만별인 모바일 환경에서 그 영향이 더욱 두드러집니다. 실제로 휴대폰의 사양에 따라 소모 시간이 크게 차이 나는 것을 아래 도표를 통해 확인할 수 있습니다.
<img src="https://velog.velcdn.com/images/edith_0318/post/661dd645-39a8-4e28-a3ec-5eb735080e7b/image.png" alt=""></p>
<p>앞서 공부한 최적화의 개념에서, 페이지 로드 시간이 3초를 넘어가면 53%의 사용자가 이탈한다고 했습니다. JavaScript 파일을 요청하고 다운받아 오는 시간을 제외하고서 파일을 실행하는 데만 2.9초가 걸린다면 파일을 실행하는 동안에만 이미 50% 이상의 사용자가 이탈할 것이라고 예상할 수 있습니다. 기기 환경에 따라서 2.9초의 몇 배의 시간을 파일 실행에만 사용할 수도 있는 만큼 이탈률은 그만큼 커질 수 있습니다. 이러한 상황을 최대한 줄이기 위해서라도 트리쉐이킹을 통한 최적화가 필요합니다.</p>
<h2 id="javascript-트리쉐이킹">JavaScript 트리쉐이킹</h2>
<p>웹팩 4버전 이상을 사용하는 경우에는 ES6 모듈(<code>import</code>, <code>export</code>를 사용하는 모듈)을 대상으로는 기본적인 트리쉐이킹을 제공합니다. Create React App을 통해 만든 React 애플리케이션도 웹팩을 사용하고 있기 때문에 트리쉐이킹이 가능합니다. 웹팩을 사용하는 환경에서 효과적으로 트리쉐이킹을 수행하는 방법에 대해서 알아봅시다.</p>
<h3 id="1-필요한-모듈만-import-하기">1. 필요한 모듈만 import 하기</h3>
<p><code>import</code> 구문을 사용해서 라이브러리를 불러와서 사용할 때, 라이브러리 전체를 불러오는 것이 아니라 필요한 모듈만 불러오면 번들링 과정에서 사용하는 부분의 코드만 포함시키기 때문에 트리쉐이킹이 가능해집니다.</p>
<p>예시를 들어보겠습니다. React를 사용하는 애플리케이션에서 React를 통째로 불러온 다음 그 안에 무엇이 있는지 <code>console.log</code>를 사용해 확인해보겠습니다.
<img src="https://velog.velcdn.com/images/edith_0318/post/c8b91fba-09ab-422f-91b5-f4fb726ca494/image.png" alt=""></p>
<p>확인 결과, 아래와 같이 React의 모든 코드가 불려 온 것을 확인할 수 있습니다.</p>
<p><img src="https://velog.velcdn.com/images/edith_0318/post/758e45a6-990c-40f5-97d8-901350a25c09/image.png" alt="">
이렇게 모든 코드를 불러오면, 이 중에서 실제로 사용하는 코드는 얼마 되지 않더라도 번들링할때 이 모든 코드를 같이 빌드하게 됩니다. 불필요한 코드가 포함되는 것이죠. 이를 방지하기 위해서 <code>import</code> 해올 때 아래와 같이 실제로 사용할 코드만 불러와 주면 됩니다.</p>
<pre><code class="language-js">import { useState, useEffect } from &#39;react&#39;</code></pre>
<p>그러면 불러오지 않은 코드는 빌드할 때 제외되므로 코드의 크기를 줄일 수 있게 됩니다.</p>
<h3 id="2-babelrc-파일-설정하기">2. Babelrc 파일 설정하기</h3>
<p>Babel은 자바스크립트 문법이 구형 브라우저에서도 호환이 가능하도록 ES5 문법으로 변환하는 라이브러리입니다. 이 때 ES5문법은 <code>import</code>를 지원하지 않기 때문에 commonJS 문법의 require로 변경시키는데, 이 과정은 트리쉐이킹에 큰 걸림돌이 됩니다. <code>require</code>는 <code>export</code> 되는 모든 모듈을 불러오기 때문입니다. 1번에서 작성한 것처럼 필요한 모듈만 불러오기 위한 코드를 작성해도 소용이 없어지는 것입니다.</p>
<p>이를 방지하기 위해서 Barbelrc 파일에 다음과 같은 코드를 작성해주면 ES5로 변환하는 것을 막을 수 있습니다.</p>
<pre><code class="language-js">{
  “presets”: [ 
    [
      “@babel/preset-env”,
      {
        &quot;modules&quot;: false
      }
    ]
 ]
}</code></pre>
<p>반대로, modules 값을 true로 설정하면 항상 ES5 문법으로 변환하므로 주의해서 작성해야 합니다.</p>
<h3 id="3-sideeffects-설정하기">3. sideEffects 설정하기</h3>
<p>웹팩은 사이드 이펙트를 일으킬 수 있는 코드의 경우, 사용하지 않는 코드라도 트리쉐이킹 대상에서 제외시킵니다.</p>
<pre><code class="language-js">const crews = [&#39;kimcoding&#39;, &#39;parkhacker&#39;]

const addCrew = function (name) {
    crews.push(name)
}</code></pre>
<p>위 코드에서 <code>addCrew</code> 함수는 함수 외부에 있는 배열인 <code>crews</code>를 변경시키는 함수입니다. 해당 함수는 외부에 영향을 주지도 받지도 않는 함수, 순수 함수가 아니기 때문에 트리쉐이킹을 통해 제외하는 경우 문제가 생길 수도 있다고 판단해 웹팩은 이 코드를 제외시키지 않습니다.</p>
<p>이럴 때 package.json 파일에서 <code>sideEffects</code>를 설정하여 사이드 이펙트가 생기지 않을 것이므로 코드를 제외시켜도 됨을 웹팩에게 알려줄 수 있습니다. 다음과 같이 작성하면 애플리케이션 전체에서 사이드 이펙트가 발생하지 않을 것이라고 알려줍니다.</p>
<pre><code class="language-js">{
  &quot;name&quot;: &quot;tree-shaking&quot;,
  &quot;version&quot;: &quot;1.0.0&quot;,
  &quot;sideEffects&quot;: false
}</code></pre>
<p>혹은 아래와 같이 작성하여 특정 파일에서는 발생하지 않을 것임을 알려줄 수 있습니다.</p>
<pre><code class="language-js">{
  &quot;name&quot;: &quot;tree-shaking&quot;,
  &quot;version&quot;: &quot;1.0.0&quot;,
  &quot;sideEffects&quot;: [&quot;./src/components/NoSideEffect.js&quot;]
}</code></pre>
<h3 id="es6-문법을-사용하는-모듈-사용하기">ES6 문법을 사용하는 모듈 사용하기</h3>
<p>보통 3번까지 작성하면 트리쉐이킹이 잘 작동합니다. 그런데 트리쉐이킹이 적용되지 않는 라이브러리가 있다면, 해당 라이브러리가 어떤 문법을 사용하고 있는지 확인해볼 필요가 있습니다. 모듈에 따라서 ES5로 작성된 모듈이 있을 수도 있기 때문입니다. ES5 문법을 사용하는 모듈을 통째로 사용하는 상황이라면 상관없지만, 일부만 사용하는 경우라면 해당 모듈을 대체할 수 있으면서 ES6를 지원하는 다른 모듈을 사용하는 것이 트리쉐이킹에 유리합니다. ES6 문법을 사용하는 모듈을 사용하면 해당 모듈에서도 필요한 부분만 import 해서 사용하지 않는 코드는 빌드할 때 제외되기 때문입니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[캐시 사용하기]]></title>
            <link>https://velog.io/@edith_0318/%EC%BA%90%EC%8B%9C-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@edith_0318/%EC%BA%90%EC%8B%9C-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0</guid>
            <pubDate>Wed, 01 Feb 2023 05:31:58 GMT</pubDate>
            <description><![CDATA[<p>캐시(Cache)는 다운로드 받은 데이터나 값을 미리 복사해 놓는 임시 장소를 뜻하며,  데이터에 접근하는 시간이 오래 걸리는 경우나 값을 다시 계산하는 시간을 절약하고 싶은 경우에 사용한다.
우선 캐시를 사용하지 않을 때의 예시를 들어보자.
<img src="https://velog.velcdn.com/images/edith_0318/post/4a3e31c7-0d5c-4d01-9848-dbf9fa3db84e/image.png" alt="">
서버에서 logo.jpg라는 이미지를 받아오는 요청을 보낸다고 해보자.
첫 번째 요청에서는 이미지를 통째로 받아오게 된다.
해당 이미지를 받아온 적이 없으니까요. 이때 HTTP 헤더의 용량이 0.1M, 이미지의 용량이 1.0M라면 응답의 총 용량은 1.1M이 된다.</p>
<p>문제는 두 번째 요청부터다.
완전히 똑같은 파일을 또 다시 받아오는 일이 발생하기 때문이다.
똑같은 데이터를 굳이 다시 받을 필요가 있을까?
전에 받아두었던 파일을 재사용할 수 있다면, 첫 번째 요청을 보냈을 때처럼 1.1M의 응답을 통째로 받아올 필요없이 HTTP헤더의 용량인 0.1M만 받아도 될 것 같은데 말이다.</p>
<p>한 두번은 그렇다고 쳐도, 100번, 1000번의 요청을 보내는 동안 똑같은 파일을 받아온다고 생각해보자.
똑같은 파일을 받느라 100M, 1000M의 네트워크 리소스를 낭비하게 될 거다.
이럴 때 캐시를 활용하면 이러한 리소스 낭비를 막을 수 있다. 캐시를 사용하게 되면 어떤 일이 일어나는지 확인해보자.
<img src="https://velog.velcdn.com/images/edith_0318/post/7c47785a-a73a-43ae-8942-b734c0886881/image.png" alt=""></p>
<p>이번에는 서버에서 응답을 보내줄 때 이미지 파일과 함께 헤더에 <code>Cache-Control</code> 을 작성해서 보내준 것을 볼 수 있다. 값은 60으로, 해당 이미지 파일이 60초동안 유효하다는 것을 의미한다.
<img src="https://velog.velcdn.com/images/edith_0318/post/3eafc538-2ce6-43f5-b921-39986ccb258c/image.png" alt=""></p>
<p>이제 두 번째 요청부터는 캐시를 우선 조회하게 된다.
캐시에 데이터가 존재하면서 아직 60초가 지나지 않아 유효하다면 캐시에서 해당하는 데이터를 가져와서 사용하게 된다.
<img src="https://velog.velcdn.com/images/edith_0318/post/0a1aa339-d2f4-4223-a2db-d20c304abfc7/image.png" alt=""></p>
<p>만약 유효 시간이 60초가 지났다면? 서버에서 다시 이미지를 받아오게 된다.
캐시 유효 시간 동안은 똑같은 데이터를 다시 받아올 필요가 없어지는 것이다.</p>
<p>이렇게 브라우저 캐시를 활용하면 다음과 같은 효과를 볼 수 있다.</p>
<ul>
<li>캐시가 유효한 시간 동안 네트워크 리소스를 아낄 수 있음</li>
<li>파일을 다시 받아올 필요가 없기 때문에 브라우저 로딩이 빨라짐</li>
<li>로딩이 빨라진 만큼 빠른 사용자 경험 제공 가능</li>
</ul>
<h2 id="캐시-검증-헤더와-조건부-요청">캐시 검증 헤더와 조건부 요청</h2>
<p>캐시를 활용하면 캐시가 유효한 시간 동안은 캐시에 저장해놓은 데이터를 재활용할 수 있다는 것을 알게 되었다. 하지만 다음과 같은 경우는 어떨까?
<img src="https://velog.velcdn.com/images/edith_0318/post/aade0ded-1f30-4701-9b2b-efd873b152f0/image.png" alt=""></p>
<p>캐시 유효 시간은 지났지만, 서버에서 다시 받아와야하는 파일이 캐시에 저장되어 있는 파일과 완전히 동일한 경우를 생각해보자.
이때도 똑같은 파일을 다시 받아와야하는 경우가 발생한다.
이럴 땐 유효 시간이 지났다고해도 굳이 똑같은 파일을 다시 받아올 필요 없이 서버의 파일과 캐시의 파일이 동일한지 확인해서 재사용하면 더 효율적이지 않을까?</p>
<p>다행히도 이런 상황에서 사용할 수 있는 HTTP 헤더들이 존재한다.
바로 캐시 검증 헤더와 조건부 요청 헤더이다.</p>
<h4 id="캐시-검증-헤더">캐시 검증 헤더</h4>
<p>캐시에 지정된 데이터와 서버의 데이터가 동일한지 확인하기 위한 정보를 담은 응답 헤더</p>
<ul>
<li><code>Last-Modified</code> : 데이터가 마지막으로 수정된 시점을 의미하는 응답 헤더로, 조건부 요청 헤더인 <code>If-Modified-Since</code> 와 묶어서 사용한다.</li>
<li><code>Etag</code> : 데이터의 버전을 의미하는 응답 헤더로, 조건부 요청 헤더인 <code>If-None-Match</code> 와 묶어서 사용한다.</li>
</ul>
<h4 id="조건부-요청-헤더">조건부 요청 헤더</h4>
<p>캐시의 데이터와 서버의 데이터가 동일하다면 재사용하게 해달라는 의미의 요청 헤더</p>
<ul>
<li><p><code>If-Modified-Since</code> : 캐시된 리소스의 <code>Last-Modified</code> 값 이후에 서버 리소스가 수정되었는지 확인하고, 수정되지 않았다면 캐시된 리소스를 사용합니다.</p>
</li>
<li><p><code>If-None-Match</code> : 캐시된 리소스의 <code>ETag</code> 값과 현재 서버 리소스의 <code>ETag</code> 값이 같은지 확인하고, 같으면 캐시된 리소스를 사용한다.</p>
</li>
</ul>
<p>캐시 검증 헤더와 조건부 요청 헤더를 어떻게 사용하는지 조금 더 자세히 살펴보자.</p>
<ul>
<li><p><code>Last-Modified</code> 와 <code>If-Modified-Since</code></p>
<ul>
<li><p>첫 번째 요청을 보내고 응답을 받으면서 캐시 유효 시간이 60초인 이미지 파일을 같이 받아옵니다. 이 때, 서버의 파일이 마지막으로 수정된 시간을 의미하는 <code>Last-Modified</code> 헤더에 담긴 내용도 캐시에 함께 저장한다.<img src="https://velog.velcdn.com/images/edith_0318/post/d9b13f7e-165d-4505-b183-c1a028e74c8b/image.png" alt=""></p>
</li>
<li><p>캐시 유효 시간인 60초를 초과한 후에 두 번째 요청을 보낸다고 해보자. 비록 유효 시간이 지났어도 해당 데이터를 재사용해도 되는지 확인하기 위해서 “이 날짜 이후로 데이터 수정이 있었니? 없었다면 캐시에 저장해놓은 데이터를 재사용해도 괜찮을까?”라는 뜻의 요청 헤더 <code>If-Modified-Since</code>를 작성하고 캐시에 함께 저장해놓았던 Last-Modified 값을 담아 요청을 보냅니다. 이 값을 이용해 서버 데이터의 최종 수정일과 캐시에 저장된 데이터의 수정일을 비교한다. 두 데이터가 동일한 데이터라면 최종 수정일이 같아야 한다.</p>
</li>
</ul>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/edith_0318/post/0ca9220a-1e7c-48d6-af82-5c533c41cd9d/image.png" alt="">
    - 서버와 캐시의 데이터가 동일한 데이터임이 검증되었다면 서버는 “데이터가 수정되지 않았음”을 의미하는 <code>304 Not Modified</code> 라는 응답을 보내주고, 캐시 데이터의 유효 시간이 갱신되면서 해당 데이터를 재사용할 수 있게 됩니다.
<img src="https://velog.velcdn.com/images/edith_0318/post/04250170-8ce5-4893-b5db-3f09b71582b8/image.png" alt=""></p>
<ul>
<li><p><code>Etag</code> 와 <code>If-None-Match</code></p>
<ul>
<li><p>첫 번째 요청을 보내고 응답을 받으면서 캐시 유효 시간이 60초인 이미지 파일을 같이 받아옵니다. 이 때, 서버의 파일 버전을 의미하는 <code>Etag</code> 헤더에 담긴 내용도 캐시에 함께 저장합니다.
<img src="https://velog.velcdn.com/images/edith_0318/post/a48cedec-2b56-4e7d-b53c-f76a9181899e/image.png" alt=""></p>
</li>
<li><p>캐시 유효 시간인 60초를 초과한 후에 두 번째 요청을 보낸다고 해봅시다. 비록 유효 시간이 지났어도 해당 데이터를 재사용해도 되는지 확인하기 위해서 “내가 캐시에 저장해놓은 데이터 버전이랑 서버 데이터 버전이랑 일치하니? 일치한다면 캐시에 저장해놓은 데이터를 재사용해도 괜찮을까?”라는 뜻의 요청 헤더 <code>If-None-Match</code> 를 작성하고 캐시에 함께 저장해놓았던 <code>Etag</code> 값을 담아 요청을 보냅니다. 이 값을 이용해 서버 데이터의 <code>Etag</code> 와 캐시에 저장된 데이터의 <code>Etag</code> 를 비교합니다. 두 데이터가 동일한 데이터라면 두 <code>Etag</code> 값이 같아야 합니다.</p>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/edith_0318/post/90b7e381-86f8-424f-81b2-0700b89de9c8/image.png" alt=""></p>
<ul>
<li>서버와 캐시의 데이터가 동일한 데이터임이 검증되었다면 서버는 “데이터가 수정되지 않았음”을 의미하는 <code>304 Not Modified</code> 라는 응답을 보내주고, 캐시 데이터의 유효 시간이 갱신되면서 해당 데이터를 재사용할 수 있게 됩니다.</li>
</ul>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/edith_0318/post/f2696f27-7e58-4b59-8f0d-159cb689fdb3/image.png" alt=""></p>
<p>여기까지 두 쌍의 캐시 검증 헤더와 조건부 요청 헤더를 알아보았습니다. 두 쌍중의 헤더 중 한 쌍만 사용할 수도 있지만, 보통 두 종류를 동시에 사용합니다. 둘 중 하나만 사용했다가 매칭되는 응답 헤더가 없는 경우에는 재사용할 수 있는 경우에도 리소스를 다시 받아와야 하는 경우가 생길 수 있기 때문입니다.</p>
<pre><code>자료참조: 코드스테이츠</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[최적화 기법]]></title>
            <link>https://velog.io/@edith_0318/%EC%B5%9C%EC%A0%81%ED%99%94-%EA%B8%B0%EB%B2%95</link>
            <guid>https://velog.io/@edith_0318/%EC%B5%9C%EC%A0%81%ED%99%94-%EA%B8%B0%EB%B2%95</guid>
            <pubDate>Wed, 01 Feb 2023 03:55:56 GMT</pubDate>
            <description><![CDATA[<h2 id="html-css-코드-최적화하기">HTML, CSS 코드 최적화하기</h2>
<p>화면을 렌더링할 때는 HTML 파일과 CSS 파일이 필요하다.
HTML 파일은 DOM 트리를, CSS 파일은 CSSOM 트리를 만들고 두 트리를 결합하여 렌더링할 때 사용하게 된다.
이 두 트리 중에서 하나라도 변경되면 리렌더링을 유발하는데, 이때 트리의 크기가 크고 복잡할수록 더 많은 계산이 필요하기 때문에 리렌더링에 소모되는 시간도 길어진다.
따라서 HTML, CSS 코드를 최적화함으로써 렌더링 성능을 향상시킬 수 있다.</p>
<h3 id="1-html-최적화-방법">1. HTML 최적화 방법</h3>
<h4 id="1-dom-트리-가볍게-만들기">(1) DOM 트리 가볍게 만들기</h4>
<p>DOM 트리가 깊을수록, 자식 요소가 많을수록 DOM 트리의 복잡도는 커진다.
복잡도가 클 수록 DOM 트리가 변경되었을 때 계산해야 하는 것도 많아진다.
HTML 요소들의 관계를 잘 살펴보고, 불필요하게 깊이를 증가시키는 요소가 있다면 삭제해야 한다.</p>
<pre><code class="language-HTML">// 수정 전
&lt;div&gt;
    &lt;ol&gt;
        &lt;li&gt; 첫 번째 &lt;/li&gt;
        &lt;li&gt; 두 번째 &lt;/li&gt;
        &lt;li&gt; 세 번째 &lt;/li&gt;
    &lt;/ol&gt;
&lt;/div&gt;

// 수정 후 : 불필요한 div 요소 제거
&lt;ol&gt;
    &lt;li&gt; 첫 번째 &lt;/li&gt;
    &lt;li&gt; 두 번째 &lt;/li&gt;
    &lt;li&gt; 세 번째 &lt;/li&gt;
&lt;/ol&gt;</code></pre>
<h4 id="2-인라인-스타일-사용하지-않기">(2) 인라인 스타일 사용하지 않기</h4>
<p>인라인 스타일은 개별 요소에 스타일 속성을 작성해주는 것이기 때문에, 클래스로 묶어서 한 번에 작성해도 될 스타일 속성을 중복으로 작성하게 되는 경우가 생긴다.
이처럼 불필요한 코드 중복은 가독성을 떨어뜨릴 뿐 아니라 파일 크기를 증가시킨다.
또한 CSS 파일을 따로 작성하면 단 한 번의 리플로우만 발생하는 것과 달리, 인라인 스타일은 리플로우를 계속해서 발생시켜 렌더링 완료 시점을 늦춘다.
애초에 인라인 스타일은 웹 표준에 맞지 않으므로 지양해야 한다.</p>
<pre><code class="language-HTML">//수정 전
&lt;div style=&quot;margin: 10px;&quot;&gt; 마진 10px &lt;/div&gt;
&lt;div style=&quot;margin: 10px;&quot;&gt; 이것도 마진 10px &lt;/div&gt;

//수정 후 : class와 CSS로 대체
&lt;div class=&quot;margin10&quot;&gt; 마진 10px &lt;/div&gt;
&lt;div class=&quot;margin10&quot;&gt; 이것도 마진 10px &lt;/div&gt;

.margin10 {
    margin: 10px;
}</code></pre>
<h3 id="2-css-최적화-방법">2. CSS 최적화 방법</h3>
<h4 id="1-사용하지-않는-css-제거하기">(1) 사용하지 않는 CSS 제거하기</h4>
<p>CSS 파일의 모든 코드의 분석이 끝난 후에 CSSOM 트리가 생성된다.
그만큼 불필요한 CSS 코드가 있다면 CSSOM 트리의 완성이 늦어진다.
따라서 사용하지 않는 CSS 코드가 있다면 제거하는 것이 좋다.
보통 해당 CSS 코드를 사용하던 요소를 삭제하면서 CSS 코드만 남게 되는 경우가 많다.
요소를 삭제할 일이 생기면, CSS 코드만 남지는 않는지 확인하고 함께 삭제하면 이런 상황을 방지할 수 있다.</p>
<h4 id="2-간결한-셀렉터-사용하기">(2) 간결한 셀렉터 사용하기</h4>
<p>셀렉터가 복잡할수록 스타일 계산과 레이아웃에 시간을 더 많이 소모하게 된다.
따라서 최대한 간결한 CSS 셀렉터를 사용하는 것이 좋다.</p>
<pre><code class="language-JS">// 복잡한 CSS 셀렉터 예시
.cart_page .cart_item #firstItem { ... }

// 필요한 경우에는 어쩔 수 없지만, 가능한 한 간결하게 작성해줍니다.
.cart_item { ... }</code></pre>
<h2 id="리소스-로딩-최적화하기">리소스 로딩 최적화하기</h2>
<p>HTML 파일에서 JavaScript 파일을 불러올 땐 <script> 요소를, CSS 파일을 불러올 땐 <link> 요소를 사용하게 된다. 이때 파일을 불러오는 위치가 어디인가에 따라서 렌더링 완료 시점이 달라질 수 있다.</p>
<h3 id="1-css-파일-불러오기">1. CSS 파일 불러오기</h3>
<p>화면을 렌더링할 때는 DOM 트리와 CSSOM 트리가 필요하다고 했다.
DOM 트리는 HTML 코드를 한 줄 한 줄 읽으면서 순차적으로 구성할 수 있지만, CSSOM 트리는 CSS 코드를 모두 해석해야 구성할 수 있다.
따라서 CSSOM 트리를 가능한 빠르게 구성할 수 있도록 HTML 문서 최상단에 배치하는 것이 좋다.</p>
<pre><code class="language-js">// CSS 파일은 HTML 파일 상단의 head 요소 안에서 불러오는 것이 좋습니다.
&lt;head&gt;
    &lt;link href=&quot;style.css&quot; rel=&quot;stylesheet&quot; /&gt;
&lt;/head&gt;</code></pre>
<h3 id="2-javascript-파일-불러오기">2. JavaScript 파일 불러오기</h3>
<p>JavaScript는 DOM 트리와 CSSOM 트리를 동적으로 변경할 수 있다.
HTML 코드 파싱 중에 <code>&lt;script&gt;</code> 요소를 만나는 순간 해당 스크립트가 실행되며, <code>&lt;script&gt;</code> 요소 이전까지 생성된 DOM까지만 접근할 수 있다. <code>&lt;script&gt;</code> 요소를 HTML 코드 중간에 넣는다면, 해당 요소 이후에 생성될 DOM을 수정하는 코드가 있는 경우에는 화면이 의도한 대로 표시되지 않게 된다.</p>
<p>또한 스크립트 실행이 완료되기 전까지 DOM 트리 생성이 중단된다.
JavaScript 파일을 다운받아와서 사용하는 경우에는 다운로드 및 스크립트 실행이 완료될 때까지 DOM 트리 생성이 중단된다.
DOM 트리 생성이 중단된 시간만큼 렌더링 완료 시간은 늦춰지게 된다. 
이러한 이유로 JavaScript 파일은 DOM 트리 생성이 완료되는 시점인 HTML 문서 최하단에 배치하는 것이 좋다.</p>
<pre><code class="language-html">&lt;body&gt;
    &lt;div&gt;...&lt;/div&gt;
    ...

    // JavsScript 파일은 body 요소가 닫히기 직전에 작성하는 것이 가장 좋습니다. 
    &lt;script src=&quot;script.js&quot; type=&quot;text/javascript&quot;&gt;&lt;/script&gt;
&lt;/body&gt;</code></pre>
<h2 id="브라우저-이미지-최적화하기">브라우저 이미지 최적화하기</h2>
<p>페이지의 대부분의 용량은 HTML/CSS/JS와 같은 코드 데이터가 아닌 이미지 파일과 같은 미디어 파일이 차지한다. (전체 페이지 용량의 약 51% 차지)그래서 이미지의 용량을 줄이거나 요청의 수를 줄이는 것을 우선적으로 고려할 시, 사용자 경험을 빠르게 개선할 수 있다.</p>
<h3 id="1-이미지-스프라이트">1. 이미지 스프라이트</h3>
<p>클라이언트에서 서버 요청이 증가할수록 로딩 시간은 점점 늘어난다.
따라서 웹 페이지를 로드하는 데 필요한 서버 요청 수를 줄이기 위해 이미지 스프라이트 기법을 사용할 수 있다.<img src="https://velog.velcdn.com/images/edith_0318/post/9d03b86d-fced-49a1-a46f-f2872aba9d0d/image.png" alt=""></p>
<p>다음은 여러분에게 친숙한 네이버의 메인 화면의 일부이다. 박스에 표시한 아이콘 이미지의 경우 각각의 이미지를 서버에 요청할 경우 웹사이트의 로딩 시간이 늘어나게 된다. 이를 해결하기 위해 사용하는 기법이 이미지 스프라이트이다.</p>
<p>이미지 스프라이트 기법은 여러 개의 이미지를 모아 하나의 스프라이트 이미지로 만들고 CSS의 background-position 속성을 사용해 이미지의 일정 부분만 클래스 등으로 구분하여 사용하는 방법이다.
  <img src="https://velog.velcdn.com/images/edith_0318/post/43873765-92c1-4fe0-b4f9-840f9cb2859a/image.png" alt=""></p>
<p>실제로 네이버에 접속한 후 개발자 도구를 이용해 아이콘 컴포넌트를 살펴보면 다음과 같이 스프라이트 이미지가 적용되어 있는 것을 직접 확인할 수 있다.
  <img src="https://velog.velcdn.com/images/edith_0318/post/7355cf20-3e49-496c-858a-6b2893c06b11/image.gif" alt="">
하나의 이미지를 배경 이미지로 사용하되, 표시하고 싶은 부분에 맞춰 width, height, background-position 속성을 주어 아이콘을 만든다.
  위 이미지를 보면서 CSS 속성값에따라 어떤 부분이 보이는지 확인하면서 이해해보자.</p>
<p>해당 기법을 이용하면 한번의 이미지 요청으로 대부분의 개별 이미지를 사용할 수 있기 때문에 네트워크 로딩 시간을 줄일 수 있다.
  또한, 많은 이미지 파일을 개별로 관리할 필요없이 특정 스프라이트 이미지 파일만을 관리하면 되므로 관리가 용이하다는 장점이 있다.
  네이버 예시만 해도 이미지 스프라이트를 통해 120여개의 아이콘을 하나의 이미지로 모두 사용할 수 있게 되었다.</p>
<h3 id="2-아이콘-폰트-사용하기">2. 아이콘 폰트 사용하기</h3>
<p><img src="https://velog.velcdn.com/images/edith_0318/post/a18cf075-accf-45eb-b871-125cee25f879/image.png" alt=""></p>
<p>  아이콘 사용이 많을 때에는, 모든 아이콘을 이미지로 사용하는 것이 아니라 아이콘 폰트를 사용하여 용량을 줄일 수 있다. 대표적인 아이콘 글꼴 서비스로는 Font Awesome이 있다. Font Awesome의 사용 방법에는 두 가지가 있다.</p>
<h4 id="1-cdn으로-사용하기">(1) CDN으로 사용하기</h4>
<p>  Font Awesome에 가입하면 키트를 발급해주는데, 이 키트를 HTML 파일에서 <code>&lt;head&gt;</code> 요소에 넣어주기만 하면 CDN으로 Font Awesome을 사용할 준비가 완료된다.
  <img src="https://velog.velcdn.com/images/edith_0318/post/ee65a4c9-d300-4d66-830c-e79624a01a79/image.png" alt="">
  Font Awesome 사이트에서 사용하고 싶은 아이콘을 찾아서 사용할 환경(HTML, React, Vue)에 맞는 코드를 복사하고 붙여넣기만 하면 사용할 수 있다.</p>
<p>  <img src="https://velog.velcdn.com/images/edith_0318/post/730036bf-77e5-41df-af63-ba0ce75ab3ae/image.png" alt=""></p>
<h4 id="2-font-awesome-모듈-설치하기">(2) Font Awesome 모듈 설치하기</h4>
<p>  Font Awesome을 다른 라이브러리처럼 설치해서 사용하는 방법도 있다. React 환경에서 사용할 경우에는 다음과 같은 패키지들을 설치하면 된다.</p>
<pre><code class="language-jsx">// 핵심 패키지 설치
npm i --save @fortawesome/fontawesome-svg-core

// 아이콘 패키지 설치 (해당 코드는 무료 아이콘들입니다. 유료 아이콘을 사용할 경우 추가로 설치가 필요합니다.)
npm i --save @fortawesome/free-solid-svg-icons
npm i --save @fortawesome/free-regular-svg-icons
npm i --save @fortawesome/free-brands-svg-icons

// Font Awesome React 구성 요소 설치
npm i --save @fortawesome/react-fontawesome@latest</code></pre>
<p>설치 후에는 Font Awesome 사이트에서 사용하고 싶은 아이콘의 정보를 확인한 후에, 알맞게 불러와서 사용하면 된다.
이때 아이콘 이름은 camelCase로 작성해야 한다.
  <img src="https://velog.velcdn.com/images/edith_0318/post/d8df3ab2-371a-4a4b-a0c7-4c0f0f1dcef8/image.png" alt=""></p>
<p>이렇게 불러온 아이콘은 클래스명을 직접 붙이거나 Font Awesome이 정해준 방법을 사용하여 스타일링해줄 수 있다.</p>
<h3 id="3-webp-또는-avif-이미지-포맷-사용하기">3. WebP 또는 AVIF 이미지 포맷 사용하기</h3>
<p>이미지 최적화를 위해 전통적으로 사용하는 JPEG 또는 PNG 형식이 아닌 새롭게 등장한 이미지 포맷인 WebP 또는 AVIF를 사용하여 용량을 더욱 감소시킬 수 있다. WebP는 PNG와 비교해 26% 용량이 감소되며 JPEG와 비교했을 땐 25-35% 더 감소된다. AVIF는 JPEG와 비교했을 때 무려 용량의 50%가 감소되며 WebP와 비교했을 땐 20% 감소됩니다.</p>
<p>하지만 WebP와 AVIF 모두 비교적 최근에 등장한 이미지 포맷이기 때문에 JPEG 포맷처럼 모든 브라우저에서 호환되지 않는다는 단점이 있다. WebP의 경우 비교적 최근에 브라우저 지원이 되었으므로 구버전의 브라우저에서는 지원이 안될 수 있으며 Safari 브라우저에서도 지원하지 않는다. AVIF의 경우에는 Chrome, Opera 등 소수의 브라우저만 지원하고 있다. </p>
<p>그럼 개발자가 각 브라우저의 이미지 호환성을 파악해서 이미지 파일을 분기해야 할까? 다행히도 HTML의 <code>&lt;picture&gt;</code> 태그를 이용하면 각 브라우저의 호환에 맞도록 분기를 대체할 수 있다.</p>
<ul>
<li><code>&lt;picture&gt;</code>: img 요소의 다중 이미지 리소스(multiple image resouces)를 위한 컨테이너를 정의할 때 사용한다.</li>
</ul>
<p>다음과 같이 HTML 태그를 작성할 시, 만약 접속한 브라우저에서 <code>&lt;source&gt;</code>태그 내의 srcset에 정의한 WebP 포맷을 지원하지 않는다면 해당 <code>&lt;source&gt;</code> 태그는 무시된다.
이와 같은 속성을 이용하여 각 브라우저에 따라 이미지 포맷을 최적화할 수 있다.</p>
<pre><code class="language-js">&lt;picture&gt;
  &lt;source srcset=&quot;logo.webp&quot; type=&quot;image/webp&quot;&gt;
  &lt;img src=&quot;logo.png&quot; alt=&quot;logo&quot;&gt;
&lt;/picture&gt;</code></pre>
<h2 id="cdn-사용하기">CDN 사용하기</h2>
<p>CDN은 콘텐츠를 좀 더 빠르고 효율적으로 제공하기 위해 설계되었다.
  네트워크 지연(latency)은 유저와 호스팅 서버간의 물리적 거리의 한계가 존재하기 때문에 발생할 수 밖에 없다.
  유저와 서버의 거리가 멀다면 지연(latency) 또한 늘어난다.
  CDN은 이를 해결하고자 세계 곳곳에 분포한 분산된 서버에 콘텐츠를 저장한다.</p>
<p>간단히 말해, CDN은 유저가 가까운 곳에 위치한 데이터 센터(서버)의 데이터를 가져온다.
  그러므로 데이터가 전달되기 위해 거쳐야하는 서버의 갯수가 크게 줄기 때문에 로딩 속도가 빨라진다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Optimization]]></title>
            <link>https://velog.io/@edith_0318/Optimization</link>
            <guid>https://velog.io/@edith_0318/Optimization</guid>
            <pubDate>Wed, 01 Feb 2023 01:35:58 GMT</pubDate>
            <description><![CDATA[<h2 id="최적화optimization의-개념">최적화(Optimization)의 개념</h2>
<p>최적화란 무엇일까? 한국정보통신기술협회의 정보통신 용어사전에서는 다음과 같이 정의한다.</p>
<ul>
<li>최적화, 最適化, optimization
주어진 상황에서 원하는 가장 알맞은 결과를 얻을 수 있도록 처리하는 과정.
최적화는 허용된 자원의 한계 내에서 주어진 요구사항을 만족시키면서 최선의 결과를 얻는 과정이다. 수익과 관련되는 분야에서는 이익을 최대로 내는 과정을 말하기도 한다. 다양한 분야와 때에 따라 다르게 정의할 수 있고 물류(logistics), 설계(design) 문제 등에 응용된다.</li>
</ul>
<p>분야에 따라서 의미가 조금씩 달라지긴 하지만, 최적화는 보통 주어진 조건으로 최대 효율을 낼 수 있도록 하는 것을 의미한다.</p>
<p>컴퓨터 공학에서의 최적화는 가능한 적은 리소스를 소모하면서 가능한 한 빠르게 원하는 결과를 얻을 수 있도록 하는 것을 의미한다.
알고리즘 문제를 푸는 것을 생각하면 이해하기 쉽다.
원하는 결과가 나온다면, 메모리를 조금이라도 덜 소모하거나 연산 횟수가 한 번이라도 더 적은 코드가 더 효율적이고 최적화된 코드이다.
더 적은 비용, 더 적은 시간을 소모하기 때문이다.
물론 컴퓨터 성능을 업그레이드하면 같은 코드를 사용하더라도 더 빠르게 결과를 얻을 수 있다.
하지만 알고리즘 문제의 결과를 빠르게 확인하기 위해 부품을 업그레이드하는 것은 비용도 많이 들고, 업그레이드한 상태에서도 최적화되지 않은 코드보다 최적화된 코드가 더 빠를 테니, 기본적으로 더 효율적인 코드를 작성하기 위해서 노력하는 것이 좋다.</p>
<p>그렇다면 웹 개발에서의 최적화는 무엇을 의미할까?
바로 주어진 조건 아래에서 최대한 빠르게 화면을 표시하도록 만드는 것이다. 
이번 시리즈에서 블로깅 할 것은 웹 개발 중에서도 프론트엔드 단에서 할 수 있는 최적화 방법이다.
본격적으로 최적화 방법에 대해서 알아보기 전에, 웹 개발에서 최적화가 왜 필요한지, 그 필요성에 대해서 먼저 알아보자.</p>
<h2 id="최적화의-필요성-및-효과">최적화의 필요성 및 효과</h2>
<h3 id="1-이탈률-감소">1. 이탈률 감소</h3>
<p>웹 개발에서의 최적화는 화면을 최대한 빠른 속도로 표시하게 하는 것이라고 했다.
이는 최적화가 잘되지 않은 웹 페이지는 화면 로딩에 시간이 걸린다는 뜻으로도 볼 수 있다.
화면을 불러오는 시간이 길어지면 사용자가 페이지를 이탈할 확률이 높아진다.
여기서 이탈이란 방문자가 웹 사이트의 첫 페이지에서 아무런 상호작용도 하지 않고 종료하는 것을 의미한다.
우리도 한 번쯤은 화면 로딩을 기다리다가 그냥 창을 꺼버린 경험이 있으실 거다.</p>
<h3 id="2-전환율-증가">2. 전환율 증가</h3>
<p>이탈률이 줄어들면, 전환율이 높아질 확률도 커진다.
여기서 전환율이란, 웹 사이트를 방문한 사용자 중 회원가입, 상품 구매, 게시글 조회, 다운로드 등의 행위를 한 방문자의 비율을 의미한다.
너무나 당연한 이야기이다.
화면이 로딩되느라 제대로 뜨지도 않는 상태에서는 회원 가입은 고사하고 버튼 하나 클릭하는 것도 불가능하기 때문이다.
물론 화면이 제대로 표시된다고 해도 방문자를 실제 서비스 이용자로 전환하게 하는 일은 어려운 일이다.
하지만 이탈해버린 사용자의 전환율은 0%이다.
전환율을 늘려 서비스 사용자를 늘리기 위해서는 이탈률을 줄여야 한다.</p>
<h3 id="3-수익-증대">3. 수익 증대</h3>
<p>빠른 웹 사이트 로딩 속도는 수익 증대까지 이어질 수 있다. 이탈률 감소, 전환율 증가는 트래픽 증대 및 회원 수 증가로 이어지고, 이는 곧 수익 증대를 의미한다.
실제로 로딩 속도가 1초 빨라졌을 때 아마존 판매량은 1%, 구글 검색량은 0.2%, 월마트의 전환율은 2% 증가했다고 한다. 퍼센티지로 보면 크지 않아 보이지만, 이 수치를 돈으로 환산하면 각각 68억 달러, 4억 5천만 달러, 2억 4천400만 달러의 매출 증가라고 한다.
1초 차이로 어마어마한 수익이 왔다 갔다 하는 것이다. 꼭 아마존이나 월마트처럼 규모가 크지 않은 서비스라도, 로딩 속도의 차이는 이처럼 유의미한 수익 차이를 낼 수 있다.</p>
<h3 id="4-사용자-경험ux-향상">4. 사용자 경험(UX) 향상</h3>
<p>최적화는 효과적인 UX 개선 수단이다.
페이지 로딩이 빠를수록 UX는 향상되기 때문에 이미 페이지 로드 속도가 빠른 편이라고 해도 최적화를 통해 UX가 더욱 향상할 수 있다.
만약 로딩이 오래 걸릴 경우, 스피너, 프로그레스 바, 스켈레톤과 같이 로딩 중임을 알려주는 UI를 먼저 표시하여 방문자가 조금 더 인내심을 갖고 기다리게 하는 방법도 있다.
하지만 이러한 방법은 최적화를 통해 페이지 로드 속도 자체를 최대한 빠르게 하는 것보다 UX에 좋다고 볼 수는 없다.
또한 방문자의 체류 시간이 좀 더 늘어날 뿐, 페이지 로드 속도가 개선되지 않는다면 이탈률 개선까지 이어지기는 어렵다. 따라서 이탈률 감소와 UX 향상 효과를 동시에 보기 위해서는 웹 사이트 성능 최적화를 진행하는 것이 가장 좋다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[TDD]]></title>
            <link>https://velog.io/@edith_0318/TDD</link>
            <guid>https://velog.io/@edith_0318/TDD</guid>
            <pubDate>Mon, 30 Jan 2023 11:11:51 GMT</pubDate>
            <description><![CDATA[<h2 id="tdd란">TDD란?</h2>
<p>TDD(Test-driven Development)는 코드를 작성하기 전에 테스트를 쓰는 소프트웨어 개발 방법론이다.
다시 말해, 개발자 자신이 바람직하다고 생각하는 코드의 결과를 미리 정의하고, 이것을 바탕으로 코드를 작성하는 법이다.
TDD를 통해 소프트웨어를 개발한다는 것은 작은 단위의 테스트 케이스를 작성하고, 이를 통과하는 코드를 작성하는 과정을 반복하는 것을 의미한다.</p>
<p>TDD의 개발 주기를 그림으로 나타내면 아래와 같이 총 3단계로 이루어진다.</p>
<p><img src="https://velog.velcdn.com/images/edith_0318/post/9fe8b1aa-a87e-4e41-9f8e-c415ccc34a29/image.png" alt="">
<em>[그림] TDD의 개발주기</em></p>
<ol>
<li>Write Failing Test: 실패하는 테스트 코드를 먼저 작성한다.</li>
<li>Make Test Pass: 테스트 코드를 성공시키기 위한 실제 코드를 작성한다.</li>
<li>Refactor: 중복 코드 제거, 일반화 등의 리팩토링을 수행한다.</li>
</ol>
<p>이 과정에서 1의 과정을 마치기 전에 2의 작업을 시작하지 않도록 주의해야 한다.
또한 2를 진행할 때에는, 1의 테스트를 통과할 정도의 최소 코드만 작성해야 한다.
테스트를 먼저 작성하는 것은 필연적으로 코드를 어떻게 구성할지 고민하게 된다는 것을 의미하고, 결과적으로 버그가 더 적은 코드를 짤 수 있게 된다.
또, 불필요한 설계를 피할 수 있고, 테스트 코드의 요구 사항에 집중할 수 있게 된다.
일반적으로 TDD를 따라 소프트웨어를 개발할 경우 그렇지 않은 경우보다 결함을 50 ~ 90% 까지 감소시킬 수 있다.</p>
<p>이처럼 버그가 적은, 보다 효과적인 코드를 짤 수 있는 방법임에도 불구하고, 실제로 완전한 TDD를 따르는 개발자는 의외로 많지 않다.
그 이유는 대부분의 개발자들이 생각하고 일하는 방식과 일치하지 않기 때문이다.
대부분의 개발자는 테스트를 작성하는 것보다, 만들어야 하는 것을 바로 코드로 작성하는 방식이 훨씬 자연스럽고 빠르다고 느낄 것이다.
많은 개발자들에게 왜 TDD를 따르지 않는지 물어보면, 대부분 ‘속도&#39; 때문이라고 대답할 것이다.</p>
<h3 id="tdd를-사용하는-이유">TDD를 사용하는 이유</h3>
<p>그럼에도 불구하고 TDD를 사용하는 이유는 무엇일까?
코드를 작성하기에 앞서 테스트 코드를 먼저 작성해야 하기 때문에 시간이 오래 걸리는 것처럼 느껴지지만, 오히려 예상하지 못했던 버그를 줄여 소요 시간을 줄일 수 있기 때문이다.
개발 과정에서 코드는 다양한 조건에 의해 계속해서 삽입, 수정, 삭제된다.
이 과정에서 코드가 중복되거나 불필요한 코드가 남게 된다.
그리고 그로 인해 여러 가지 버그가 발생하거나, 디버깅 또한 어려워지는 현상이 발생하기도 한다.
결국 그런 코드를 유지보수하기 위해서는 처음 개발할 때 아꼈던 리소스보다 더 많은 리소스를 투입해야 하는 경우가 발생한다.</p>
<p>여러분이 지금 당장 완전한 TDD를 따르는 것은 아주 어려운 일이다.
TDD 방법론에 대해 학습하되, 여러분의 개발 실력을 향상시키는 것을 더 우선순위에 두어야 한다.
그러나 작성하려는 코드에 대해 특정한 규칙(테스트)을 설정하기 위해 고민하면서, 코드가 큰 틀에서 어떤 의미를 갖게 되는지 고민하는 것은 여러분이 스스로를 성장시킬 수 있는 중요한 도구가 될 것이다.</p>
<h3 id="테스트-코드를-작성하는-방법">테스트 코드를 작성하는 방법</h3>
<p>우리는 지금까지 과제를 진행하면서 <code>console.log</code>를 사용하여 현재 작성한 코드가 어떤 결과물을 도출하는지 확인한 경험이 있을 거다.
<code>console.log</code>를 통해 확인하는 것도 일종의 테스트이다.</p>
<p>또, JavaScript Koans 진행하면서 테스트를 작성해 보기도 했다.
그 과정에서 <code>describe</code>, <code>it</code>, <code>assert</code>, <code>expect</code> 등의 다양한 키워드들을 마주쳤을 거다.
이 키워드가 무엇을 의미하는 것인지 궁금했을 거다?
결론부터 이야기하면 이 키워드들은 JavaScript 내장 기능이 아니라 테스트 프레임워크에서 제공하는 테스트 작성을 위한 도구이다.</p>
<p>여러 개발자들이 더 나은 테스트를 작성하기 위해 많은 테스트 오픈소스 프레임워크를 제작했다.
이후 진행할 Test Builder 과제에서는 mocha라는 테스트 프레임워크와 chai라는 라이브러리를 사용한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[GraphQL]]></title>
            <link>https://velog.io/@edith_0318/GraphQL</link>
            <guid>https://velog.io/@edith_0318/GraphQL</guid>
            <pubDate>Mon, 30 Jan 2023 04:06:04 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/edith_0318/post/7d44196c-92e0-4e34-a593-5e052f878dc9/image.png" alt=""></p>
<p>GraphQL은 페이스북에서 만든 쿼리 언어이다.
2016년 처음으로 등장해 현재까지 인지도 및 만족 부분에서 높은 비율을 차지하고 있는 언어이기도 하다.</p>
<p><img src="https://velog.velcdn.com/images/edith_0318/post/eea7a9b8-1ff2-45d6-a729-dbe7f78f6514/image.gif" alt=""></p>
<p><em>[그림] 2020년까지 GraphQL은 만족도, 흥미, 인지도 부분에서 높은 비율을 차지하고 있다.</em></p>
<p>데이터를 다루고 처리하는 데에 있어서 계속해서 비율의 변동이 발생하고는 있지만, 한 가지 분명한 사실은 GraphQL 관련 기술이 계속해서 상위권을 차지하고 있다는 사실이다.</p>
<p><img src="https://velog.velcdn.com/images/edith_0318/post/75daad6b-ca63-46a3-a896-9758cb8d8c0e/image.png" alt=""></p>
<p>GraphQL은 개발자들 사이에서 인식률이 굉장히 높은 언어이고, 배우고 싶은 비율과 다시 사용하겠다는 비율이 굉장히 높은 쿼리 언어이다.</p>
<hr>
<h2 id="graphql">GraphQL</h2>
<p>GraphQL은 Facebook에서 처음으로 개발했고, 오픈 소스로 제공된 쿼리 언어이다.
Graph + Query Language의 줄임말로 Query Language 중에서도 Server API 를 통해 정보를 주고받기 위해 사용하는 Query Language를 뜻한다.
쉽게 말해 API를 위한 쿼리 언어라고 할 수 있다.</p>
<h3 id="왜-graph를-사용할까">왜 Graph를 사용할까?</h3>
<p>GraphQL의 아이디어는 그래프로 생각하기에서부터 출발했다. 그래프라는 자료구조는 인간의 뇌 구조 및 언어적인 설명과 비슷하기 때문에 실제 현실 세계의 많은 현상들을 모델링할 수 있는 강력한 도구다.
따라서 그래프 자료구조를 살펴보면 우리가 특정 개념을 학습하고 이를 다른 개념과 연관시킬 때 자연스럽게 사용하는 마인드맵과 유사한 데이터 구조를 가진다는 것을 알 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/edith_0318/post/75501b7c-13d9-450e-bb1a-e9f657084160/image.png" alt="">
<em>[그림] 그래프 자료구조</em></p>
<p>그래프는 여러 개의 점들이 서로 복잡하게 연결되어 있는 관계를 표현한 자료구조를 뜻한다.
하나의 점을 그래프에서는 Node 또는 정점(vertex)이라고 표현하고, 하나의 선은 간선(edge) 이라고 한다.
직접적인 관계가 있는 경우 두 점 사이를 이어주는 선이 있으며 간접적인 관계라면 몇 개의 점과 선에 걸쳐 이어진다.
또한 각 노드간의 간선을 통해 특정한 순서에 따라 그래프를 재귀적으로 탐색할 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/edith_0318/post/7ffe9a24-21c8-4fc8-ac01-b9c741eb17bf/image.png" alt="">
<em>[그림] 그래프 자료구조와 트리 자료구조의 차이</em></p>
<p>GraphQL에서는 모든 데이터가 그래프 형태로 연결되어 있다고 전제한다.
일대일로 연결된 관계도, 여러 계층으로 이루어진 관계도 모두 그래프이다.
트리나 그래프나 노드와 노드를 연결하는 간선으로 구성된 자료구조이기 때문이다.
단지 그 그래프를 누구의 입장에서 정렬하느냐(클라이언트가 어떤 데이터를 필요로 하느냐)에 따라 트리 구조를 이룰 수 있다.
<img src="https://velog.velcdn.com/images/edith_0318/post/7c9abd03-4fc4-4f70-96c9-9f6644eb1252/image.png" alt=""></p>
<p><em>[그림] 트리 구조로 정렬된 그래프와 GraphQL</em>
이를 통해 GraphQL은 클라이언트 요청에 따라 유연하게 트리 구조의 JSON 데이터를 응답으로 전송할 수 있다.
다시 말해 GraphQL은 REST API 방식의 고정된 자원이 아닌 클라이언트 요청에 따라 유연하게 자원을 가져올 수 있다는 점에서 엄청난 이점을 갖는다.</p>
<h3 id="graphql로-그래프-순회">GraphQL로 그래프 순회</h3>
<p>GraphQL로 그래프를 순회하기 위해 도서관의 도서 목록 시스템을 구축한다고 가정해보자.
하나의 도서 목록에는 많은 책과 저자가 있을 것이며, 각 책에는 최소한 한 명의 저자가 있다.
또한 최소한 한 권의 책을 같이 쓴 공동저자 또한 있을 거다.
<img src="https://velog.velcdn.com/images/edith_0318/post/d1a7eca3-b912-4688-912d-4efa9f63f3da/image.png" alt=""></p>
<p><em>[그림] 도서 목록 시스템 구축을 위한 그래프</em></p>
<p>위의 그래프는 예시에서 설명한 관계를 그래프 형태로 시각화한 것이다.
이런 식으로 그래프로 표현하게 되면 우리가 가지고 있는 데이터의 조각들이나 나타내고자 하는 엔티티(책, 혹은 저자) 간의 관계를 나타낼 수 있다.</p>
<ul>
<li>엔티티는 사물의 구조나 상태, 동작 등을 모델로 표현하는 경우, 그 모델의 구성요소를 말한다.
예를 들어 학생이라는 객체는 “학번”, “이름”, “학과”라는 3개의 속성으로 구성되어 있는 것처럼, 위의 엔티티는 책이라는 객체가 존재한다면 “책이름&quot;이라는 1개의 속성으로 구성되어 있는 셈이다.
정보의 측면에서 볼 때 이 속성은 그 자체만으로는 중요한 의미를 표현하지 못하기 때문에 단독으로 존재하지는 못한다.
앞의 예에서 각 속성들 즉 “학번”, “이름”, “학과”는 개별적으로는 우리에게 어떤 정보를 제공해 주지 못하지만 이것들이 모여 “학생”이라는 객체를 구성해서 표현할 때는 큰 의미를 제공할 수 있게 된다.</li>
</ul>
<p>이렇게 그래프를 그릴 수 있게 된다면, GraphQL을 사용해 트리를 추출할 수 있게 된다.</p>
<p>기본적으로 트리는 방향성은 존재하나 사이클은 존재하지 않는 비순환 그래프이다.
루트와 모서리를 통해 노드를 따라 순회할 수 있으나 동일한 노드로 돌아올 수 없는 속성을 갖고 있는 특별한 그래프임을 뜻한다.</p>
<h3 id="그래프에서-트리-추출하는-방법">그래프에서 트리 추출하는 방법</h3>
<p>GraphQL 쿼리와 위의 그래프에서 트리를 추출해보도록 해보자.
위의 그래프에서 실행할 수 있는 쿼리는 다음과 같다.</p>
<pre><code class="language-js">query {
    책(ISBN이 &quot;9780674430006&quot;) {
        책 이름
        저자 {
            이름
        }
    }
}</code></pre>
<p>위의 그래프는 간단하게 표현했지만 사실 책이란 객체가 가지는 속성은 책 이름 외에도 많을 거다.
따라서 한 권의 책만 검색하기 위해, ISBN이 &quot;9780674430006인 조건을 걸어보자.
이 방식으로 서버에 요청을 보내고, 서버가 해당 요청을 해결한다면, 돌아온 쿼리는 이럴 거다.</p>
<pre><code class="language-js">{
    책 : {
        책 이름 : &quot;GraphQL은 어렵지 않다&quot;,
        저자 : [
            { 이름 : &quot;김코딩&quot;},
            { 이름 : &quot;박해커&quot;},
        ]
    }
}</code></pre>
<p>그럼 이것을 그래프의 관점에서 본다면 어떨까?
<img src="https://velog.velcdn.com/images/edith_0318/post/4707293c-4876-49d1-af01-502b507da196/image.png" alt="">
<em>[그림] GraphQL로 쿼리한 것을 그래프의 관점으로 도식화</em></p>
<p>이 예에서는 ISBN 번호를 사용하여 선택한 “책&quot; 노드에서 시작한다.
그 다음 GraphQL은 중첩된 각 필드로 표시된 간선을 따라 그래프를 탐색하기 시작한다.
즉 쿼리 내 중첩된 “책 이름” 필드를 통해 책의 제목이 있는 노드로 이동한다.
그러면서 “저자”로 레이블이 지정된 “책”의 간선을 따라가 “저자” 노드를 가져오고, 각 저자의 “이름&quot;을 얻어오는 것입니다. 이것을 트리 구조로 표현하면 이렇게 보인다.
<img src="https://velog.velcdn.com/images/edith_0318/post/7ecd3f8f-4711-4f02-8597-62964e74c6c3/image.png" alt="">
<em>[그림] GraphQL로 쿼리한 것을 트리 구조로 도식화</em></p>
<p>이렇게 GraphQL의 중첩된 필드를 그래프의 계층 구조로 표현하면 이렇게 트리 구조로도 표현할 수 있게 된다.
즉, GraphQL은 트리 구조로 쿼리 결과를 받기 위해 그래프를 탐색하는 쿼리 언어라고 볼 수 있다.</p>
<h3 id="graphql의-특징">GraphQL의 특징</h3>
<p>이런 GraphQL의 특징으로는 이런 것들이 있다.</p>
<ul>
<li>GraphQL은 HTTP를 통해 API 서버로 요청을 보내고 응답을 받는다.</li>
<li>응답을 받을 시, 데이터 결과를 JSON 형식으로 받는다.</li>
<li>GraphQL은 서버 개발자가 작성한 각 필드에 대응하는 resolver 함수로 각 필드의 데이터를 조회할 수 있다.</li>
<li>GraphQL은 GraphQL 라이브러리가 조회 대상 schema가 유효한지 검사한다.</li>
</ul>
<hr>
<h2 id="graphql-vs-rest-api">GraphQL VS REST API</h2>
<h3 id="graphql-vs-rest-api-1">GraphQL vs REST API</h3>
<p>GraphQL은 API를 위한 쿼리 언어라고 했다.
그렇다면 이전에 이미 REST API라는 방법론이 존재하고 있음에도 불구하고 왜 GraphQL이 탄생했을까?
예제를 통해 REST API의 한계에 대해 알아보자.</p>
<h4 id="rest-api의-한계">REST API의 한계</h4>
<p>앞서 다룬 REST API라는 방법론이 있음에도 왜 GraphQL이 탄생했을까?
예제를 통해 REST API의 한계에 대해 알아보자.
<img src="https://velog.velcdn.com/images/edith_0318/post/65cb5e89-e82c-4339-8643-7835ff4c05f3/image.png" alt="">
가상의 블로그 앱을 구현한다고 가정해보자.
위와 같은 화면을 구현하기 위해선 다음의 데이터가 필요하자.</p>
<ul>
<li>사용자의 이름</li>
<li>사용자의 포스팅 목록</li>
<li>사용자의 팔로워 목록</li>
</ul>
<h5 id="rest-api로-blog-앱을-구현할-때">REST API로 Blog 앱을 구현할 때</h5>
<p><img src="https://velog.velcdn.com/images/edith_0318/post/47c57e44-ae24-48ae-b9bf-7ccb9e2d9d05/image.png" alt=""></p>
<ul>
<li>Overfetch: 필요 없는 데이터까지 제공함<ul>
<li>블로그 앱 예제처럼 유저의 이름만 필요한 상황에서 REST API를 사용한다면, 응답 데이터에는 유저의 주소, 생일 등과 같이 실제로는 클라이언트에게 필요없는 정보가 포함되어 있을 수도 있다.</li>
</ul>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/edith_0318/post/7984954d-8f08-4872-86ff-48b6ad96e5b3/image.png" alt="">
<img src="https://velog.velcdn.com/images/edith_0318/post/61025f79-8d4e-46d9-972b-84009d9dafeb/image.png" alt=""></p>
<ul>
<li><p>Underfetch: endpoint 가 필요한 정보를 충분히 제공하지 못함</p>
<ul>
<li>Underfetch의 경우 클라이언트는 필요한 정보를 모두 확보하기 위하여 추가적인 요청을 보내야만 한다.
블로그 앱 예제 화면을 구현하기 위해선 유저 정보 뿐만 아니라 유저의 포스팅 목록 및 유저가 보유한 팔로워까지 필요하다.
이때 필요한 정보를 모두 가져오려면 REST API에서는 각각의 자원에 따라 엔드포인트를 구분하기 때문에 3가지 엔드포인트에 요청을 보내야한다.</li>
</ul>
</li>
<li><p>클라이언트 구조 변경 시 엔드포인트 변경 또는 데이터 수정이 필요함</p>
<ul>
<li>REST API에서는 자원의 크기와 형태를 서버에서 결정하기 때문에 클라이언트가 직접 데이터의 형태를 결정할 수 없다.
이로 인해 만약 클라이언트에서 필요한 데이터의 내용이 변할 경우 다른 endpoint를 통해 변경된 데이터를 가져오거나 수정을 해야한다.</li>
</ul>
</li>
</ul>
<h3 id="rest-api와-graphql의-다른점">REST API와 GraphQL의 다른점</h3>
<p>그러므로 REST API와 GraphQL은 이런 부분에서 다르다고 볼 수 있다.
<img src="https://velog.velcdn.com/images/edith_0318/post/04207fee-8471-46a7-83bc-7dd64d10e01f/image.png" alt=""></p>
<ul>
<li>REST API는 Resource에 대한 형태 정의와 데이터 요청 방법이 연결되어 있지만, GraphQL에서는 Resource에 대한 형태 정의와 데이터 요청이 완전히 분리되어 있다.</li>
<li>REST API는 Resource의 크기와 형태를 서버에서 결정하지만, GraphQL에서는 Resource에 대한 정보만 정의하고, 필요한 크기와 형태는 클라이언트 단에서 요청 시 결정한다.</li>
<li>REST API는 URI가 Resource를 나타내고 Method가 작업의 유형을 나타내지만, GraphQL에서는 GraphQL Schema가 Resource를 나타내고 Query, Mutation 타입이 작업의 유형을 나타낸다.</li>
<li>REST API는 여러 Resource에 접근하고자 할 때 여러 번의 요청이 필요하지만, GraphQL에서는 한번의 요청에서 여러 Resource에 접근할 수 있다.</li>
<li>REST API에서 각 요청은 해당 엔드포인트에 정의된 핸들링 함수를 호출하여 작업을 처리하지만, GraphQL에서는 요청 받은 각 필드에 대한 resolver를 호출하여 작업을 처리한다.</li>
</ul>
<h2 id="graphql의-장단점">GraphQL의 장단점</h2>
<p>이렇게 다른 언어와 GraphQL과의 차이를 살펴봤다.
그럼 GraphQL을 사용하면 어떤 점이 좋을지, 그리고 어떤 점이 단점으로 작용할 지 알아보도록 하자.</p>
<p>위와 같은 REST API의 한계때문에 정보를 사용하는 측에서 원하는 대로 정보를 가져올 수 있고, 보다 편하게 정보를 수정할 수 있도록 하는 표준화된 Query language 를 만들게 되었고 이것이 GraphQL이다.
동일한 예제를 통해 GraphQL의 장점을 알아보자.</p>
<h4 id="graphql로-blog-앱을-구현할-때">GraphQL로 Blog 앱을 구현할 때</h4>
<p><img src="https://velog.velcdn.com/images/edith_0318/post/0a7335a5-41dd-40e6-a65d-a88b28c2d980/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/edith_0318/post/b36c0400-c0c6-4f76-a226-9b8c3408e287/image.png" alt=""></p>
<ul>
<li>하나의 endpoint 요청<ul>
<li>/graphql이라는 하나의 endpoint 로 요청을 받고 그 요청에 따라 query , mutation을 resolver 함수로 전달해서 요청에 응답한다. 
모든 클라이언트 요청은 POST 메소드를 사용합니다.</li>
</ul>
</li>
<li>No! under &amp; overfetching<ul>
<li>4여러 개의 endpoint 요청을 할 필요없이 하나의 endpoint에서 쿼리를 이용해 원하는 데이터를 정확하게 API에 요청하고 응답으로 받을 수 있다.</li>
</ul>
</li>
<li>강력한 playground<ul>
<li>graphql 서버를 실행하면 playground라는 GUI를 이용해 resolver 와 schema 를 한 눈에 보고 테스트 해 볼 수 있다. (POSTMAN 과 비슷합니다.)</li>
</ul>
</li>
<li>클라이언트 구조 변경에도 지장이 없음<ul>
<li>클라이언트 구조가 바뀌어도 필요한 데이터를 결정하고 받는 주체가 클라이언트이기 때문에 서버에 지장이 없다.
클라이언트에서는 무슨 데이터가 필요한 지에 대해서만 요구사항을 쿼리로 작성하면 된다.</li>
</ul>
</li>
</ul>
<h3 id="graphql의-단점">GraphQL의 단점</h3>
<p>앞선 예제만 보면 GraphQL이 기존 REST API 방식이 가지고 있는 모든 문제를 해결할 만능키로 보이지만 GraphQL에도 다음과 같은 단점이 존재한다.</p>
<ul>
<li>REST API에 친숙한 개발자의 경우 GraphQL를 학습하는 데 시간이 필요하다.</li>
<li>캐싱이 REST보다 훨씬 복잡합니다.<ul>
<li>HTTP에선 각 메소드에 따라 캐싱이 구현되어 있다. 하지만 GraphQL에선 <code>POST</code> 메소드만을 이용해 요청을 보내기 때문에 각 메소드에 따른 캐싱을 지원받을 수 없습니다. 그래서 이를 보완하기 위해 Apollo 엔진의 캐싱과 영속 쿼리 등이 등장하게 되었다.</li>
</ul>
</li>
<li>고정된 요청과 응답만 필요할 경우에는 Query 로 인해 요청의 크기가 RESTful API 의 경우보다 더 커진다.</li>
</ul>
<hr>
<h3 id="graphql-keywords">GraphQL Keywords</h3>
<p>서버로부터 데이터를 조회(Read)하는 경우, REST API에선 GET 요청이 있었다면 GraphQL에서는 Query를 이용해 원하는 데이터를 요청할 수 있다.
또한 Create, Delete와 같이 저장된 데이터를 수정하는 경우에는 Mutation을 이용해 이를 수행할 수 있다.</p>
<p>더 나아가 GraphQL에서는 구독(Subscription)이라는 개념을 제공하며 이를 이용해 실시간 업데이트를 구현할 수 있다.</p>
<p>Subscription는 전통적인 Client(요청)-Server(응답) 모델을 따르는 Query 또는 Mutation과 달리, 발행/구독(pub/sub) 모델을 따른다.
클라이언트가 어떤 이벤트를 구독하면, 클라이언트는 서버와 WebSocket을 기반으로 지속적인 연결을 형성하고 유지하게 된다.
그 후 특정 이벤트가 발생하면, 서버는 대응하는 데이터를 클라이언트에 푸시해준다.</p>
<ul>
<li>Query: 저장된 데이터 가져오기 (REST의 GET과 비슷합니다.)</li>
<li>Mutation: 저장된 데이터 수정하기<ul>
<li>Create: 새로운 데이터 생성</li>
<li>Update: 기존의 데이터 수정</li>
<li>Delete: 기존의 데이터 삭제</li>
</ul>
</li>
<li>Subscription: 특정 이벤트가 발생 시 서버가 대응하는 데이터를 실시간으로 클라이언트에게 전송</li>
</ul>
<h3 id="쿼리query-데이터-조회">쿼리(query, 데이터 조회)</h3>
<h4 id="필드field">필드(field)</h4>
<p>매우 간단한 query(데이터 조회, 이하 쿼리)와 실행 했을 때 얻은 결과부터 살펴보자.</p>
<pre><code class="language-js">{
  hero {
    name
  }
}</code></pre>
<p><em>[코드] hero의 name을 쿼리</em></p>
<pre><code class="language-js">{
  &quot;data&quot;: {
    &quot;hero&quot;: {
      &quot;name&quot;: &quot;R2-D2&quot;
    }
  }
}</code></pre>
<p><em>[코드] 쿼리를 실행했을 때의 결과</em>
필드의 <code>name</code>은 String 타입을 반환한다.
위의 경우 <code>hero</code>의 <code>name</code>이 “R2-D2”임을 알 수 있다. 보이는 것처럼 쿼리와 결과가 정확하게 같은 모양을 하고 있음을 확인할 수 있는데, 이 부분은 GraphQL에 있어서 필수적이라고 볼 수 있다.
GraphQL은 서버에 요청했을 때 예상했던 대로 돌려받고, 서버는 GraphQL을 통해 클라이언트가 요구하는 필드를 정확히 알기 때문이다.</p>
<p>이번에는 다른 예시를 살펴보자.</p>
<pre><code class="language-js">{
  hero {
    name
    # 이런 식으로 GraphQL 내에서 주석도 작성할 수 있습니다.
    friends {
      name
    }
  }
}</code></pre>
<p>[코드] 히어로의 이름과 히어로의 친구 이름을 같이 쿼리</p>
<pre><code class="language-js">{
  &quot;data&quot;: {
    &quot;hero&quot;: {
      &quot;name&quot;: &quot;R2-D2&quot;,
      &quot;friends&quot;: [
        {
          &quot;name&quot;: &quot;Luke Skywalker&quot;
        },
        {
          &quot;name&quot;: &quot;Han Solo&quot;
        },
        {
          &quot;name&quot;: &quot;Leia Organa&quot;
        }
      ]
    }
  }
}</code></pre>
<p><em>[코드] 히어로의 이름과 히어로의 친구의 이름이 조회되어 나온다.</em></p>
<p>이런 식으로 원하는 필드를 중첩하여 쿼리하는 것도 가능하다.
위의 예에서 freinds 필드는 배열을 반환한다.
GraphQL 쿼리는 관련 객체 및 필드를 순회할 수 있기 때문에 고전적인 REST API에서 그러했듯 다양한 endpoint를 만들어 각기 요청을 보내는 대신 클라이언트가 하나의 요청을 보냄으로써 관련 데이터를 가져올 수 있다.</p>
<h3 id="전달인자arguments">전달인자(Arguments)</h3>
<p>필드에 인수를 전달하는 부분을 추가하게 되면 쿼리의 필드 및 중첩된 객체들에 전달하여 원하는 데이터만 받아올 수 있다.</p>
<pre><code class="language-js">{
  human(id: &quot;1000&quot;) {
    name
    height
  }
}</code></pre>
<p><em>[코드] id가 1000인 human의 name과 height를 쿼리</em></p>
<pre><code class="language-js">{
  &quot;data&quot;: {
    &quot;human&quot;: {
      &quot;name&quot;: &quot;Luke Skywalker&quot;,
      &quot;height&quot;: 1.72
    }
  }
}</code></pre>
<p><em>[코드] 쿼리 결과</em></p>
<p>이런 식으로 id가 1000인 human의 이름과 키를 쿼리해 올 수 있다.
REST와 같은 시스템에서는 단일 인수 집합(요청의 쿼리 매개변수 및 URL 세그먼트)만 전달할 수 있다.</p>
<p>예를 들어 REST API를 이용한다면 <code>?id=1000</code> 이거나 <code>/1000(/:id)</code>일 때와 같은 목적으로 쿼리할 수 있다.</p>
<h3 id="별명aliases">별명(Aliases)</h3>
<p>필드 이름을 중복해서 사용할 수 없으므로, 필드 이름을 중복으로 사용해서 쿼리를 해야 할 때는 별명을 붙여서 쿼리를 한다.</p>
<pre><code class="language-js">{
  hero(episode: EMPIRE) {
    name
  }
  hero(episode: JEDI) {
    name
  }
}</code></pre>
<p><em>[코드] 이런 식으로 중복해 쿼리할 수 없습니다.</em></p>
<pre><code class="language-js">{
  empireHero: hero(episode: EMPIRE) {
    name
  }
  jediHero: hero(episode: JEDI) {
    name
  }
}</code></pre>
<p><em>[코드] 앞에 알아볼 수 있는 별명을 붙여주면 쿼리할 수 있다.</em></p>
<pre><code class="language-js">{
  &quot;data&quot;: {
    &quot;empireHero&quot;: {
      &quot;name&quot;: &quot;Luke Skywalker&quot;
    },
    &quot;jediHero&quot;: {
      &quot;name&quot;: &quot;R2-D2&quot;
    }
  }
}</code></pre>
<p>[코드] 쿼리 결과를 받아볼 수 있다.
위와 같이 다른 이름으로 별명을 지정하면 한 번의 요청으로 두 개의 결과를 모두 얻어낼 수 있다.</p>
<h3 id="오퍼레이션-네임operation-name">오퍼레이션 네임(Operation name)</h3>
<p>여태껏 쿼리와 쿼리 네임을 모두 생략하는 축약형 구문을 사용했습니다만, 실제 앱에서는 코드를 모호하지 않게 작성하는 것이 중요하다.</p>
<pre><code class="language-js">query HeroNameAndFriends {
  hero {
    name
    friends {
      name
    }
  }
}</code></pre>
<p>[코드] 이런 식으로 query keyword와 query name을 작성한다.</p>
<pre><code class="language-js">{
  &quot;data&quot;: {
    &quot;hero&quot;: {
      &quot;name&quot;: &quot;R2-D2&quot;,
      &quot;friends&quot;: [
        {
          &quot;name&quot;: &quot;Luke Skywalker&quot;
        },
        {
          &quot;name&quot;: &quot;Han Solo&quot;
        },
        {
          &quot;name&quot;: &quot;Leia Organa&quot;
        }
      ]
    }
  }
}</code></pre>
<p>앞의 <code>query</code>는 오퍼레이션 타입이다.
오퍼레이션 타입에는 <code>query</code> 뿐만 아니라 <code>mutation</code>, <code>subscription</code>, <code>describes</code> 등이 있다.
쿼리를 약식으로 작성하지 않는 한 이런 오퍼레이션 타입은 반드시 필요하다.
오퍼레이션 네임을 작성할 때는 오퍼레이션 타입에 맞는 이름으로 작성하는 것이 가독성이 좋다.</p>
<h3 id="변수variables">변수(Variables)</h3>
<p>여태껏 고정된 인수를 받아 쿼리했지만, 실제 앱을 사용할 때는 고정된 인수를 받는 것보다는 동적으로 인수를 받아 쿼리하는 경우가 대다수이다.
변수는 그런 인수들을 동적으로 받고 싶을 때 사용한다.</p>
<pre><code class="language-js">query HeroNameAndFriends($episode: Episode) {
  hero(episode: $episode) {
    name
    friends {
      name
    }
  }
}</code></pre>
<p><em>[코드] 변수를 써서 작성된 쿼리</em>
오퍼레이션 네임 옆에 변수를 $변수 이름: 타입 형태 로 정의한다.
위의 예시처럼 <code>$episode: Episode</code> 일 때, 뒤에 !가 붙는다면 episode는 반드시 Episode여야 한다는 뜻이다.
!는 옵셔널한 사항이다.</p>
<h2 id="뮤테이션mutation-데이터-수정">뮤테이션(mutation, 데이터 수정)</h2>
<p>GraphQL은 대개 데이터를 가져오는 데에 중점을 두고 있지만 서버측 데이터를 수정하기도 한다.</p>
<p>REST API에서 <code>GET</code> 요청을 사용하여 데이터를 수정하지 않고, <code>POST</code> 혹은 <code>PUT</code> 요청을 사용하는 것처럼 GraphQL도 유사하다.
GraphQL은 <code>mutation</code>이라는 키워드를 사용하여 서버 측 데이터를 수정한다.</p>
<pre><code class="language-js">mutation CreateReviewForEpisode($ep: Episode!, $review: ReviewInput!) {
  createReview(episode: $ep, review: $review) {
    stars
    commentary
  }
}</code></pre>
<h2 id="스키마타입schematype">스키마/타입(Schema/Type)</h2>
<p>GraphQL 스키마의 가장 기본적인 구성 요소는 서비스에서 가져올 수 있는 객체의 종류, 그리고 포함하는 필드를 나타내는 객체 유형이다.</p>
<pre><code class="language-js">type Character {
  name: String!
  appearsIn: [Episode!]!
}</code></pre>
<ul>
<li>Character는 GraphQL 객체 타입이며, 즉 필드가 있는 타입임을 의미한다.
스키마에 있는 대부분의 타입은 객체 타입이다.</li>
<li><code>name</code> 과 <code>appearIn</code> 은 <code>Character</code> 타입의 필드 다.
즉 <code>name</code> 과 <code>appearIn</code> 은 GraphQL 쿼리의 <code>Character</code> 타입 어디서든 사용할 수 있는 필드이다.</li>
<li><code>String</code>은 내장된 스칼라 타입 중 하나이다.
이는 단일 스칼라 객체로 확인되는 유형이며 쿼리에서 하위 선택을 가질 수 없다.
스칼라 타입에는 ID, Int도 있다.</li>
<li><code>!</code>가 붙는다면 이 필드는 nullable하지 않고 반드시 값이 들어온다는 의미이다.
이것을 붙여 쿼리한다면 반드시 값을 받을 수 있을 것이란 예상을 할 수 있다.</li>
<li><code>[ ]</code>는 배열을 의미한다.
배열에도 <code>!</code>가 붙을 수 있다.
여기서는 <code>!</code> 이 뒤에 붙어 있어 null 값을 허용하지 않으므로 항상 0개 이상의 요소를 포함한 배열을 기대할 수 있게 된다.</li>
</ul>
<h2 id="리졸버resolver">리졸버(Resolver)</h2>
<p>요청에 대한 응답을 결정해주는 함수로써 GraphQL의 여러 가지 타입 중 Query, Mutation, Subscription과 같은 타입의 실제 일하는 방식 즉 로직을 작성한다.
다시 말해 위와 같이 스키마를 정의하면 그 스키마 필드에 사용되는 함수의 실제 행동을 Resolver에서 정의한다.
또한 이러한 함수들이 모여 있기 때문에 보통 Resolvers라 부른다.</p>
<pre><code class="language-js">const db = require(&quot;./../db&quot;)
const resolvers = {
  Query: { // **Query :** 저장된 데이터 가져오기 (REST 에 GET 과 비슷합니다.)
        getUser: async (_, { email, pw }) =&gt; {
            db.findOne({
                where: { email, pw }
            }) ... // 실제 디비에서 데이터를 가져오는 로직을 작성합니다. 
            ...
        }
  },
  Mutation: { // **Mutation :** 저장된 데이터 수정하기 ( Create , Update , Delete )
        createUser: async (_, { email, pw, name }) =&gt; {
            ...
        }
  }
  Subscription: { // **Subscription :** 실시간 업데이트
    newUser: async () =&gt; {
      ...
        }
  }
};</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[코드 분할 (Code Spliting)]]></title>
            <link>https://velog.io/@edith_0318/%EC%BD%94%EB%93%9C-%EB%B6%84%ED%95%A0-Code-Spliting</link>
            <guid>https://velog.io/@edith_0318/%EC%BD%94%EB%93%9C-%EB%B6%84%ED%95%A0-Code-Spliting</guid>
            <pubDate>Wed, 25 Jan 2023 04:43:45 GMT</pubDate>
            <description><![CDATA[<p>대부분 React 앱들은 Webpack, Rollup과 같은 툴을 사용해 번들링(Bundling)한다.
이렇게 하면 HTML 웹 페이지에 JavaScript를 쉽게 추가할 수 있기 때문이다.
번들된 앱은 모든 JavaScript가 한 곳에 있기 때문에 페이지를 설정하는 데 필요한 호출 수가 적은 링크 태그 하나만 필요하게 된다.
과거에는 이렇게 해도 무리가 없없지만. 
모던 웹 이전의 웹 JavaScript 코드는 최소한의 수준으로 작성되었기 때문이다.</p>
<p><img src="https://velog.velcdn.com/images/edith_0318/post/6d207d6b-e125-45a5-b155-cd2f968d203f/image.png" alt="">
<em>[그림] 이제 브라우저 JavaScript 엔진이 해석해야 하는 JavaScript 코드 양이 많아지게 되었습니다.</em></p>
<p>그러나 이제는 번들링하게 되면 특정 지점에서 코드를 해석하고 실행하는 정도가 느려지게 되었다.
모던 웹으로 발전하면서 점점 DOM을 다루는 정도가 정교해지며 JavaScript 코드 자체가 방대해지고 무거워졌기 때문이다.
<img src="https://velog.velcdn.com/images/edith_0318/post/9f21409b-ecd5-4766-9c69-8f68f535e2d1/image.png" alt="">
<em>[그림] 코드 분할 아이디어 : 번들을 나눈 뒤 필요한 코드만 불러오면 어떨까?</em></p>
<p>“그렇다면 어느 페이지에서 코드를 해석하고 실행하는 정도가 느려졌는지 파악해서 번들을 나눈 뒤에 지금 필요한 코드만 불러오고 나중에 필요한 코드는 나중에 불러올 수 있지 않을까??”</p>
<p>이것이 코드 분할의 핵심 아이디어이다.
번들이 거대해지는 것을 방지하기 위한 좋은 해결 방법은 번들을 물리적으로 나누는 것이다.
코드 분할은 런타임 시 여러 번들을 동적으로 만들고 불러오는 것으로, Webpack, Rollup과 같은 번들러가 지원하는 기능이다.</p>
<p>따라서 코드 분할을 하게 되면 지금 당장 필요한 코드가 아니라면 따로 분리를 시키고, 나중에 필요할 때 불러와서 사용할 수 있다. 
이를 통하여 대규모 프로젝트의 앱인 경우에도 페이지의 로딩 속도를 개선할 수 있게 된다.</p>
<h3 id="번들-분할-혹은-줄이는-법">번들 분할 혹은 줄이는 법</h3>
<p>번들링 되는 파일에는 여러분들이 앱을 만들면서 npm을 통해 다운받는 서드파티(Third Party) 라이브러리도 포함이 된다. 서드파티 라이브러리는 개인 개발자나 프로젝트 팀, 혹은 업체등에서 개발하는 라이브러리로, 즉 제 3자 라이브러리 이다. 서드파티 라이브러리는 플러그인이나 라이브러리 또는 프레임워크 등이 존재하며, 이 라이브러리를 잘 사용하면 편하고 효율적인 개발을 할 수 있다.</p>
<p>그러나 서드파티 라이브러리는 사용자에게 다양한 메소드를 제공하기 때문에 코드의 양이 많고, 번들링 시 많은 공간을 차지한다.
따라서 사용 중인 라이브러리의 전부를 불러와서 사용하는 것보다 따로 따로 불러와서 사용할 수 있다면 많은 공간을 차지하지 않을 수 있게 된다.</p>
<pre><code class="language-js">/* 이렇게 lodash 라이브러리를 전체를 불러와서 그 안에 들은 메소드를 꺼내 쓰는 것은 비효율적입니다.*/
import _ from &#39;lodash&#39;;

...

_.find([]);

/* 이렇게 lodash의 메소드 중 하나를 불러와 쓰는 것이 앱의 성능에 더 좋습니다.*/
import find from &#39;lodash/find&#39;;

find([]);</code></pre>
<p>해당 코드는 lodash라는 라이브러리를 예시로 하고 있다.
lodash는 배열, 숫자, 객체, 문자열을 사용할 때 반복적인 작업 같은 것을 할 시 사용하기에 좋은 라이브러리이다.</p>
<p>lodash라는 라이브러리는 하나의 폴더와 같고, 그 폴더 안에는 개발 시 다양한 상황에 쓰기 좋은 메소드들, 즉 함수 코드들이 들어 있다.
이 함수 코드들의 양이 상당하기 때문에 전부 가져올 시, 정말로 필요한 것 한두 개만 쓰인다면 나머지는 그냥 쓰이지 않는 코드 뭉치로 앱 내부에 남게 된다.
이는 앱의 성능을 저하시킬 요지가 있기 때문에, 필요한 것 한두 개만 가져다 쓰는 식으로 개발하는 것이 훨씬 좋다.</p>
<h2 id="react에서의-코드-분할">React에서의 코드 분할</h2>
<p>React는 SPA(Single-Page-Application)인데, 사용하지 않는 모든 컴포넌트까지 한 번에 불러오기 때문에 첫 화면이 렌더링 될때까지의 시간이 오래걸린다.
그래서 사용하지 않는 컴포넌트는 나중에 불러오기 위해 코드 분할 개념을 도입했다.</p>
<p>React에서 코드 분할하는 방법은 dynamic import(동적 불러오기)를 사용하는 것이다.
그 전까지는 코드 파일의 가장 최상위에서 import 지시자를 사용해 사용하고자 하는 라이브러리 및 파일을 불러오는 방법을 사용했었다.
이를 static import(정적 불러오기)라고 한다.</p>
<h4 id="static-import">Static Import</h4>
<pre><code class="language-js">/* 기존에는 파일의 최상위에서 import 지시자를 이용해 라이브러리 및 파일을 불러왔습니다. */
import moduleA from &quot;library&quot;;

form.addEventListener(&quot;submit&quot;, e =&gt; {
  e.preventDefault();
  someFunction();
});

const someFunction = () =&gt; {
  /* 그리고 코드 중간에서 불러온 파일을 사용했습니다. */
}</code></pre>
<p>기존에는 항상 <code>import</code> 구문은 문서의 상위에 위치해야 했고, 블록문 안에서는 위치할 수 없는 제약 사항이 있었다.
왜냐하면 번들링 시 코드 구조를 분석해 모듈을 한 데 모으고 사용하지 않는 모듈은 제거하는 등의 작업을 하는데, 코드 구조가 간단하고 고정이 되어 있을 때에야만 이 작업이 가능해지기 때문이었다.</p>
<p>그러나 이제는 구문 분석 및 컴파일해야 하는 스크립트의 양을 최소화 시키기 위해 dynamic import 구문을 지원한다.</p>
<h4 id="dynamic-import">Dynamic Import</h4>
<pre><code class="language-js">form.addEventListener(&quot;submit&quot;, e =&gt; {
  e.preventDefault();
    /* 동적 불러오기는 이런 식으로 코드의 중간에 불러올 수 있게 됩니다. */
  import(&#39;library.moduleA&#39;)
    .then(module =&gt; module.default)
    .then(someFunction())
    .catch(handleError());
});

const someFunction = () =&gt; {
    /* moduleA를 여기서 사용합니다. */
}</code></pre>
<p>이런 식으로 dynamic import를 사용하게 되면 불러온 <code>moduleA</code> 가 다른 곳에서 사용되지 않는 경우, 사용자가 form을 통해 양식을 제출한 경우에만 가져오도록 할 수 있다.</p>
<p>dynamic import는 <code>then</code> 함수를 사용해 필요한 코드만 가져온다.
가져온 코드에 대한 모든 호출은 해당 함수 내부에 있어야 한다. 
이 방식을 사용하면 번들링 시 분할된 코드(청크)를 지연 로딩시키거나 요청 시에 로딩할 수 있다.</p>
<p>이 dynamic import는 <code>React.lazy</code> 와 함께 사용할 수 있다.</p>
<hr>
<h4 id="reactlazy">React.lazy()</h4>
<p>React.lazy 함수를 사용하면 dynamic import를 사용해 컴포넌트를 렌더링할 수 있다.
React는 SPA(Single-Page-Application)이므로 한 번에 사용하지 않는 컴포넌트까지 불러오는 단점이 있다.
React는 React.lazy를 통해 컴포넌트를 동적으로 import를 할 수 있기 때문에 이를 사용하면 초기 렌더링 지연시간을 어느정도 줄일 수 있게 된다.</p>
<pre><code class="language-js">import Component from &#39;./Component&#39;;

/* React.lazy로 dynamic import를 감쌉니다. */
const Component = React.lazy(() =&gt; import(&#39;./Component&#39;));</code></pre>
<p>이 <code>React.lazy</code>로 감싼 컴포넌트는 단독으로 쓰일 수는 없고, <code>React.suspense</code> 컴포넌트의 하위에서 렌더링을 해야 한다.</p>
<h4 id="reactsuspense">React.Suspense</h4>
<p>Router로 분기가 나누어진 컴포넌트들을 위 코드처럼 lazy를 통해 import하면 해당 path로 이동할때 컴포넌트를 불러오게 되는데 이 과정에서 로딩하는 시간이 생기게 된다. <code>Suspense</code>는 아직 렌더링이 준비되지 않은 컴포넌트가 있을 때 로딩 화면을 보여주고, 로딩이 완료되면 렌더링이 준비된 컴포넌트를 보여주는 기능이다.</p>
<pre><code class="language-js">/* suspense 기능을 사용하기 위해서는 import 해와야 합니다. */
import { Suspense } from &#39;react&#39;;

const OtherComponent = React.lazy(() =&gt; import(&#39;./OtherComponent&#39;));
const AnotherComponent = React.lazy(() =&gt; import(&#39;./AnotherComponent&#39;));

function MyComponent() {
  return (
    &lt;div&gt;
            {/* 이런 식으로 React.lazy로 감싼 컴포넌트를 Suspense 컴포넌트의 하위에 렌더링합니다. */}
      &lt;Suspense fallback={&lt;div&gt;Loading...&lt;/div&gt;}&gt;
                {/* Suspense 컴포넌트 하위에 여러 개의 lazy 컴포넌트를 렌더링시킬 수 있습니다. */}
        &lt;OtherComponent /&gt;
                &lt;AnotherComponent /&gt;
      &lt;/Suspense&gt;
    &lt;/div&gt;
  );
}</code></pre>
<p><code>Supense</code> 컴포넌트의 <code>fallback</code> prop은 컴포넌트가 로드될 때까지 기다리는 동안 로딩 화면으로 보여줄 React 엘리먼트를 받아들여진다.
<code>Suspense</code> 컴포넌트 하나로 여러 개의 lazy 컴포넌트를 보여줄 수도 있다.</p>
<h4 id="reactlazy와-suspense의-적용">React.lazy와 Suspense의 적용</h4>
<p>앱에 코드 분할을 도입할 곳을 결정하는 것은 사실 까다롭기 때문에, 중간에 적용시키는 것보다는 웹 페이지를 불러오고 진입하는 단계인 <code>Route</code>에 이 두 기능을 적용시키는 것이 좋다.</p>
<pre><code class="language-js">import { Suspense, lazy } from &#39;react&#39;;
import { BrowserRouter as Router, Routes, Route } from &#39;react-router-dom&#39;;

const Home = lazy(() =&gt; import(&#39;./routes/Home&#39;));
const About = lazy(() =&gt; import(&#39;./routes/About&#39;));

const App = () =&gt; (
  &lt;Router&gt;
    &lt;Suspense fallback={&lt;div&gt;Loading...&lt;/div&gt;}&gt;
      &lt;Routes&gt;
        &lt;Route path=&quot;/&quot; element={&lt;Home /&gt;} /&gt;
        &lt;Route path=&quot;/about&quot; element={&lt;About /&gt;} /&gt;
      &lt;/Routes&gt;
    &lt;/Suspense&gt;
  &lt;/Router&gt;
);</code></pre>
<p>라우터에 <code>Suspense</code>를 적용하는 것은 간단한 편이다.
라우터가 분기되는 컴포넌트에서 각 컴포넌트에 <code>React.lazy</code>를 사용하여 <code>import</code>한다.
그리고 Route 컴포넌트들을 <code>Suspense</code>로 감싼 후 로딩 화면으로 사용할 컴포넌트를 <code>fallback</code> 속성으로 설정해주면 된다.
초기 렌더링 시간이 줄어드는 분명한 장점이 있으나 페이지를 이동하는 과정마다 로딩 화면이 보여지기 때문에 서비스에 따라서 적용 여부를 결정해야 한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Custom Hooks]]></title>
            <link>https://velog.io/@edith_0318/Custom-Hooks</link>
            <guid>https://velog.io/@edith_0318/Custom-Hooks</guid>
            <pubDate>Wed, 25 Jan 2023 02:15:41 GMT</pubDate>
            <description><![CDATA[<p>Custom Hooks란 개발자가 스스로 커스텀한 훅을 의미하며 이를 이용해 반복되는 로직을 함수로 뽑아내어 재사용할 수 있다.</p>
<p>여러 url을 fetch할 때, 여러 input에 의한 상태 변경 등 반복되는 로직을 동일한 함수에서 작동하게 하고 싶을 때 커스텀 혹을 주로 사용한다. 이를 이용하면</p>
<ol>
<li>상태관리 로직의 재활용이 가능하고</li>
<li>클래스 컴포넌트보다 적은 양의 코드로 동일한 로직을 구현할 수 있으며</li>
<li>함수형으로 작성하기 때문에 보다 명료하다는 장점이 있다.</li>
</ol>
<p>예를 들어 이런 컴포넌트가 있다고 보자. 해당 컴포넌트는 실제 React 공식 문서에 있는 컴포넌트이다.</p>
<pre><code class="language-js">//FriendStatus : 친구가 online인지 offline인지 return하는 컴포넌트
function FriendStatus(props) {
  const [isOnline, setIsOnline] = useState(null);
  useEffect(() =&gt; {
    function handleStatusChange(status) {
      setIsOnline(status.isOnline);
    }
    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
    return () =&gt; {
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
  });

  if (isOnline === null) {
    return &#39;Loading...&#39;;
  }
  return isOnline ? &#39;Online&#39; : &#39;Offline&#39;;
}

//FriendListItem : 친구가 online일 때 초록색으로 표시하는 컴포넌트
function FriendListItem(props) {
  const [isOnline, setIsOnline] = useState(null);
  useEffect(() =&gt; {
    function handleStatusChange(status) {
      setIsOnline(status.isOnline);
    }
    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
    return () =&gt; {
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
  });

  return (
    &lt;li style={{ color: isOnline ? &#39;green&#39; : &#39;black&#39; }}&gt;
      {props.friend.name}
    &lt;/li&gt;
  );
}</code></pre>
<p><code>FriendStatus</code>컴포넌트는 사용자들이 온라인인지 오프라인인지 확인하고, <code>FriendListItem</code> 컴포넌트는 사용자들의 상태에 따라 온라인이라면 초록색으로 표시하는 컴포넌트이다.
이 두 컴포넌트는 정확하게 똑같이 쓰이는 로직이 존재하고 있다.
이 로직을 빼내서 두 컴포넌트에서 공유할 수는 없을까?
Custom Hook을 사용한다면 가능하다.ㅓ</p>
<pre><code class="language-js">function useFriendStatus(friendID) {
  const [isOnline, setIsOnline] = useState(null);

  useEffect(() =&gt; {
    function handleStatusChange(status) {
      setIsOnline(status.isOnline);
    }

    ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);
    return () =&gt; {
      ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);
    };
  });

  return isOnline;</code></pre>
<p>  두 컴포넌트에서 사용하기 위해 동일하게 사용되고 있는 로직을 분리하여 함수 <code>useFriendStatus</code>로 만든다.
  이렇게 Custom Hook을 정의할 때는 일종의 규칙이 필요하다.</p>
<ul>
<li>Custom Hook을 정의할 때는 함수 이름 앞에 <code>use</code>를 붙이는 것이 규칙이다.</li>
<li>대개의 경우 프로젝트 내의 hooks 디렉토리에 Custom Hook을 위치 시킨다.</li>
<li>Custom Hook으로 만들 때 함수는 조건부 함수가 아니어야 한다.
즉 return 하는 값은 조건부여서는 안 된다.
그렇기 때문에 위의 이 <code>useFriendStatus</code> Hook은 온라인 상태의 여부를 boolean탑입으로 반환하고 있다.</li>
</ul>
<p>이렇게 만들어진 Custom Hook은 내부에 useState와 같은 React 내장 Hook을 사용하여 작성할 수 있다.
일반 함수 내부에서는 React 내장 Hook을 불러 사용할 수 없지만 Custom Hook 에서는 가능하다는 것 또한 알아두면 좋다.</p>
<p>이제 이 <code>useFriendStatus</code> Hook을 두 컴포넌트에 적용해보자.</p>
<pre><code class="language-js">function FriendStatus(props) {
  const isOnline = useFriendStatus(props.friend.id);

  if (isOnline === null) {
    return &#39;Loading...&#39;;
  }
  return isOnline ? &#39;Online&#39; : &#39;Offline&#39;;
}

function FriendListItem(props) {
  const isOnline = useFriendStatus(props.friend.id);

  return (
    &lt;li style={{ color: isOnline ? &#39;green&#39; : &#39;black&#39; }}&gt;
      {props.friend.name}
    &lt;/li&gt;
  );
}</code></pre>
<p>로직을 분리해 Custom Hook으로 만들었기 때문에 두 컴포넌트는 더 직관적으로 확인이 가능해진다.</p>
<p>그러나 같은 Custom Hook을 사용했다고 해서 두 개의 컴포넌트가 같은 state를 공유하는 것은 아니다. 그저 로직만 공유할 뿐, state는 컴포넌트 내에서 독립적으로 정의 되어 있다.</p>
<hr>
<h2 id="custom-hook의-예시">Custom Hook의 예시</h2>
<pre><code class="language-js">const useFetch = ( initialUrl:string ) =&gt; {
    const [url, setUrl] = useState(initialUrl);
    const [value, setValue] = useState(&#39;&#39;);

    const fetchData = () =&gt; axios.get(url).then(({data}) =&gt; setValue(data));    

    useEffect(() =&gt; {
        fetchData();
    },[url]);

    return [value];
};

export default useFetch;</code></pre>
<p><em>[코드] 여러 url을 fetch할 때 쓸 수 있는 useFetch Hook</em></p>
<pre><code class="language-js">import { useState, useCallback } from &#39;react&#39;;

function useInputs(initialForm) {
  const [form, setForm] = useState(initialForm);
  // change
  const onChange = useCallback(e =&gt; {
    const { name, value } = e.target;
    setForm(form =&gt; ({ ...form, [name]: value }));
  }, []);
  const reset = useCallback(() =&gt; setForm(initialForm), [initialForm]);
  return [form, onChange, reset];
}

export default useInputs;</code></pre>
<p><em>[코드] 여러 input에 의한 상태 변경을 할 때 쓸 수 있는 useInputs Hooks</em></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[ useCallback]]></title>
            <link>https://velog.io/@edith_0318/useCallback</link>
            <guid>https://velog.io/@edith_0318/useCallback</guid>
            <pubDate>Fri, 20 Jan 2023 06:10:23 GMT</pubDate>
            <description><![CDATA[<p>React Hook은 렌더링 최적화를 위한 Hook도 존재하는데, useCallback과 useMemo가 바로 그 역할을 하는 Hook라고 배웠다.</p>
<p>이번에는 useCallback Hook에 대해 배워보고, useMemo와 useCallback의 차이에 대해 학습하도록 하자.</p>
<h3 id="usecallback이란">useCallback이란?</h3>
<p>useCallback 또한 useMemo와 마찬가지로 메모이제이션 기법을 이용한 Hook이다.
useMemo는 값의 재사용을 위해 사용하는 Hook이라면, useCallback은 함수의 재사용을 위해 사용하는 Hook이다.</p>
<p>아래 코드를 보도록 하자.</p>
<pre><code class="language-jsx">function Calculator({x, y}){

    const add = () =&gt; x + y;

    return &lt;&gt;
      &lt;div&gt;
                    {add()}
      &lt;/div&gt;
  &lt;/&gt;;
}</code></pre>
<p>현재 이 <code>Calculator</code> 컴포넌트 내에는 <code>add</code>라는 함수가 선언이 되어 있는 상태다. 이 <code>add</code> 함수는 <code>props</code>로 넘어온 <code>x</code>와 <code>y</code>값을 더해 <code>&lt;div&gt;</code> 태그에 값을 출력하고 있다. 
이 함수는 해당 컴포넌트가 렌더링 될 때마다 새롭게 만들어질 것이다.</p>
<p><code>useMemo</code>와 마찬가지로, 해당 컴포넌트가 리렌더링 되더라도 그 함수가 의존하고 있는 값인 <code>x</code>와 <code>y</code>가 바뀌지 않는다고 생각해 보자.
그렇다면 함수 또한 메모리 어딘가에 저장해 뒀다가 다시 꺼내서 쓸 수 있을 것이다.</p>
<p>이때 <code>useCallback</code> Hook을 사용하면 그 함수가 의존하는 값들이 바뀌지 않는 한 기존 함수를 계속해서 반환한다. 즉 <code>x</code>와 <code>y</code>값이 동일하다면 다음 렌더링 때 이 함수를 다시 사용한다.</p>
<pre><code class="language-jsx">/* useCallback를 사용하기 전에는 꼭 import해서 불러와야 합니다. */
import React, { useCallback } from &quot;react&quot;;

function Calculator({x, y}){

    const add = useCallback(() =&gt; x + y, [x, y]);

    return &lt;&gt;
      &lt;div&gt;
                    {add()}
      &lt;/div&gt;
  &lt;/&gt;;
}</code></pre>
<p>사실 <code>useCallback</code>만 사용해서는 <code>useMemo</code>에 비해 괄목할 만한 최적화를 느낄 수는 없다. 
왜냐하면 <code>useCallback</code>은 함수를 호출을 하지 않는 Hook이 아니라, 그저 메모리 어딘가에 함수를 꺼내서 호출하는 Hook이기 때문이다.
따라서 단순히 컴포넌트 내에서 함수를 반복해서 생성하지 않기 위해서 <code>useCallback</code>을 사용하는 것은 큰 의미가 없거나 오히려 손해인 경우도 있다.
그러면 언제 사용하는 게 좋을까? 자식 컴포넌트의 props로 함수를 전달해줄 때 이 useCallback을 사용하기가 좋다.</p>
<h3 id="usecallback과-참조-동등성">useCallback과 참조 동등성</h3>
<p><code>useCallback</code>은 참조 동등성에 의존한다.
React는 JavaScript 언어로 만들어진 오픈소스 라이브러리이기 때문에 기본적으로 JavaScript의 문법을 따라간다.
JavaScript에서 함수는 객체이다.
객체는 메모리에 저장할 때 값을 저장하는 게 아니라 값의 주소를 저장하기 때문에, 반환하는 값이 같을 지라도 일치연산자로 비교했을 때 false가 출력된다.</p>
<p>아래 코드를 보자.</p>
<pre><code class="language-jsx">function doubleFactory(){
    return (a) =&gt; 2 * a;
}

const double1 = doubleFactory();
const double2 = doubleFactory();

double1(8); // 16
double2(8); // 16

double1 === double2;  // false
double1 === double1;  // true</code></pre>
<p><code>double1</code>과 <code>double2</code>는 같은 함수를 할당했음에도 메모리 주소 값이 다르기 때문에 같다고 보지 않습니다.
JavaScript에서 함수는 객체이다. 
따라서 두개의 함수는 동일한 코드를 공유하더라도 메모리 주소가 다르기 때문에, 메모리 주소에 의한 참조 비교 시 다른 함수로 본다.</p>
<p>이는 React 또한 같다. React는 리렌더링 시 함수를 새로이 만들어서 호출을 한다.
새로이 만들어 호출된 함수는 기존의 함수와 같은 함수가 아니다. 그러나 <code>useCallback</code>을 이용해 함수 자체를 저장해서 다시 사용하면 함수의 메모리 주소 값을 저장했다가 다시 사용한다는 것과 같다고 볼 수 있다. 따라서 React 컴포넌트 함수 내에서 다른 함수의 인자로 넘기거나 자식 컴포넌트의 prop으로 넘길 때 예상치 못한 성능 문제를 막을 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[useMemo]]></title>
            <link>https://velog.io/@edith_0318/useMemo</link>
            <guid>https://velog.io/@edith_0318/useMemo</guid>
            <pubDate>Fri, 20 Jan 2023 05:54:22 GMT</pubDate>
            <description><![CDATA[<p>컴포넌트는 기본적으로 상태가 변경되거나 부모 컴포넌트가 렌더링이 될 때마다 리렌더링을 하는 구조로 이루어져 있다.
그러나 너무 잦은 리렌더링은 앱에 좋지 않은 성능을 끼친다.
<img src="https://velog.velcdn.com/images/edith_0318/post/3d88372c-753b-45f8-bacb-9977919ae18b/image.png" alt="">
<em>[그림] 너무 많은 렌더링은 앱에 안 좋은 성능을 미치는 극단적인 예</em></p>
<p>React Hook은 함수 컴포넌트가 상태를 조작하고 및 최적화 기능을 사용할 수 있게끔 하는 메소드라고 했다.
그 중 렌더링 최적화를 위한 Hook도 존재하는데, useCallback과 useMemo가 바로 그 역할을 하는 Hook이다.</p>
<h3 id="usememo란">useMemo란?</h3>
<p>useMemo은 특정 값(value)를 재사용하고자 할 때 사용하는 Hook이다.</p>
<p>아래 코드를 보면서 useMemo에 대해 좀 더 알아보자.</p>
<pre><code class="language-jsx">function Calculator({value}){

    const result = calculate(value);

    return &lt;&gt;
      &lt;div&gt;
                    {result}
      &lt;/div&gt;
  &lt;/&gt;;
}</code></pre>
<p>해당 컴포넌트는 props로 넘어온 value값을 calculate라는 함수에 인자로 넘겨서 result 값을 구한 후, <code>&lt;div&gt;</code> 엘리먼트로 출력을 하고 있다.</p>
<p>만약 여기서 calculate가 내부적으로 복잡한 연산을 해야 하는 함수라 계산된 값을 반환하는 데에 시간이 몇 초 이상 걸린다고 가정해 보자.
그렇다면 해당 컴포넌트는 렌더링을 할 때마다 이 함수를 계속해서 호출할 것이고, 그 때마다 시간이 몇 초 이상 소요가 될 것이다.
이 몇 초의 지연은 렌더링에도 영향을 미칠 것이고, 사용자는 “앱의 로딩 속도가 느리네?”라는 생각을 하게 될 것이다.</p>
<pre><code class="language-jsx">/* useMemo를 사용하기 전에는 꼭 import해서 불러와야 합니다. */
import { useMemo } from &quot;react&quot;;

function Calculator({value}){

    const result = useMemo(() =&gt; calculate(value), [value]);

    return &lt;&gt;
      &lt;div&gt;
                    {result}
      &lt;/div&gt;
  &lt;/&gt;;
}</code></pre>
<p>여기 <code>value</code> 를 인자로 받는 Calculator 컴포넌트가 있다.
<code>value</code> 는 일종의 값으로서, 이 값이 계속 바뀌는 경우라면 어쩔 수 없겠지만, 렌더링을 할 때마다 이 <code>value</code>값이 계속 바뀌는 게 아니라고 생각해 보자. 
그럼 이 값을 어딘가에 저장을 해뒀다가 다시 꺼내서 쓸 수만 있다면 굳이 calculate 함수를 호출할 필요도 없을 것이다.
여기서 useMemo Hook을 사용할 수 있다.</p>
<p>이런 식으로 useMemo를 호출하여 calculate를 감싸주면, 이전에 구축된 렌더링과 새로이 구축되는 렌더링을 비교해 value값이 동일할 경우에는 이전 렌더링의 value값을 그대로 재활용할 수 있게 된다.
이는 메모이제이션(Memoization) 개념과 긴밀한 관계가 있다.</p>
<h4 id="memoization">Memoization</h4>
<p>메모이제이션(Memoization)은 알고리즘에서 자주 나오는 개념이다.
기존에 수행한 연산의 결과값을 메모리에 저장을 해두고, 동일한 입력이 들어오면 재활용하는 프로그래밍 기법을 말한다.
이 메모이제이션을 적절히 사용한다면 굳이 중복 연산을 할 필요가 없기 때문에 앱의 성능을 최적화할 수 있다.
useMemo는 바로 이 개념을 이용하여 복잡한 연산의 중복을 피하고 React 앱의 성능을 최적화시킨다.
직접 메모이제이션 개념을 이용하여 로직을 구현할 수도 있겠으나, useMemo Hook을 호출한다면 이런 로직을 직접 구현하는 것을 대신해주기 때문에 훨씬 간편하다고 할 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Component와 Hook]]></title>
            <link>https://velog.io/@edith_0318/Component%EC%99%80-Hook</link>
            <guid>https://velog.io/@edith_0318/Component%EC%99%80-Hook</guid>
            <pubDate>Fri, 20 Jan 2023 05:43:00 GMT</pubDate>
            <description><![CDATA[<p>React에는 다양한 Hook이 존재하고 있다.
그 전에 Hook이 정확히 무엇인지, 가장 기본적인 Hook이 무엇인지, Hook의 사용 규칙 등에 대해 알아보자.</p>
<h3 id="function-component와-class-component">Function Component와 Class Component</h3>
<p>Hook은 함수 컴포넌트에서 사용하는 메소드이다.
함수 컴포넌트 이전에는 클래스(class) 컴포넌트가 있었었다.
많은 React 개발자들이 이 클래스 컴포넌트를 사용하여 React 앱을 개발해왔다. 
React의 클래스 컴포넌트는 조금 생소한 개념일 수 있다.</p>
<h3 id="class-component">Class Component</h3>
<p>여기 <Counter /> 컴포넌트를 클래스로 작성한 간단한 코드를 보자.</p>
<pre><code class="language-JS">class Counter extends Component {
    constructor(props) {
        super(props);
        this.state = {
            counter: 0
        }
        this.handleIncrease = this.handleIncrease.bind(this);
    }

    handleIncrease = () =&gt; {
        this.setState({
            counter: this.state.counter + 1
        })
    }

    render(){
       return (
            &lt;div&gt;
                &lt;p&gt;You clicked {this.state.counter} times&lt;/p&gt;
                &lt;button onClick={this.handleIncrease}&gt;
                    Click me
                &lt;/button&gt;
            &lt;/div&gt;
       ) 
    }
}</code></pre>
<p>우리가 보기에 이게 간단한 코드라고? 라고 생각할 수 있지만 함수 컴포넌트 이전의 클래스 컴포넌트로 작성할 때에는 이 정도는 작성을 해야지 앱이 정상적으로 동작할 수 있었다.</p>
<p><img src="https://velog.velcdn.com/images/edith_0318/post/6a2b1e4e-dd00-43bf-a329-2e2e8bc85fca/image.gif" alt="">
<em>[그림] 단순히 카운팅을 하는 기능만 구현한 것입니다.</em></p>
<p>이런 클래스 컴포넌트는 복잡해질수록 이해하기 어려워졌고, 컴포넌트 사이에서 상태 로직을 재사용하기 어렵다는 단점이 있었다.
또한 React의 클래스 컴포넌트를 사용하기 위해서는 JavaScript의 this 키워드가 어떤 방식으로 동작하는지 알아야 하는데, 이는 문법을 정확히 알지 못하면 동작 방식 자체를 정확히 이해하기 어렵게 만들곤 했다.</p>
<p>그래서 React는 점진적으로 클래스 컴포넌트에서 함수 컴포넌트로 넘어갔다.
다만 이전까지의 함수 컴포넌트는 클래스 컴포넌트와는 다르게 상태 값을 사용하거나 최적화할 수 있는 기능들이 조금 미진했는데, 그 부분들을 보완하기 위해 Hook이라는 개념을 도입하였다.</p>
<h3 id="function-component">Function Component</h3>
<p>이번에는 <Counter /> 컴포넌트를 함수형 컴포넌트로 작성해보자.</p>
<pre><code class="language-JSX">function Counter () {
    const [counter, setCounter] = useState(0);

    const handleIncrease = () =&gt; {
        setCounter(counter + 1)
    }

    return (
        &lt;div&gt;
            &lt;p&gt;You clicked {counter} times&lt;/p&gt;
            &lt;button onClick={handleIncrease}&gt;
                Click me
            &lt;/button&gt;
        &lt;/div&gt;
    )
}</code></pre>
<p>함수형 컴포넌트는 클래스형 컴포넌트에 비해 훨씬 더 직관적이고, 보기 쉽다는 특징이 있다.
이Counter 컴포넌트에서 숫자를 올리기 위해 상태값을 저장하고 사용할 수 있게 해주는 useState() 가 있는데, 여러분도 익히 알고 있는 이 메서드가 바로 Hook이다.</p>
<p>다시 말하자면, Counter 컴포넌트에서 useState() Hook을 호출해 함수 컴포넌트(function component) 안에 state를 추가한 형태이다.
이 state는 컴포넌트가 리렌더링 되어도 그대로 유지될 것이다.
또한 해당 컴포넌트에서 State Hook은 하나만 사용했지만 때에 따라서 여러 개 사용할 수 있다.</p>
<h3 id="hook이란">Hook이란?</h3>
<p>React의 공식문서를 보면 Hook에 대해 이런 문구가 있다.</p>
<ul>
<li>Hook은 React 16.8에 새로 추가된 기능입니다. Hook은 class를 작성하지 않고도 state와 다른 React의 기능들을 사용할 수 있게 해줍니다.</li>
</ul>
<p>Hook은 다르게 말하면 함수형 컴포넌트에서 상태 값 및 다른 여러 기능을 사용하기 편리하게 해주는 메소드를 의미한다. Hook은 class가 아닌 function으로만 React를 사용할 수 있게 해주는 것이기 때문에 클래스형 컴포넌트에서는 동작하지 않는다.</p>
<pre><code class="language-JSX">...
render(){
    /* 클래스 컴포넌트는 render() 안에서 변수를 작성할 수 있습니다. */
    const [counter, setCounter] = useState(0);
...
}</code></pre>
<p><img src="https://velog.velcdn.com/images/edith_0318/post/b71522c6-be17-4aa7-bbff-8cb177f0a57a/image.png" alt="">
<em>[그림] 클래스 컴포넌트에서는 useState() Hook을 호출할 수 없다는 에러</em></p>
<p>억지로 호출을 해보려고 해도 해당 방식은 React에서 허락하지 않는 호출 방식이기 때문에 위와 같은 에러를 브라우저 전면에 띄운다.
해당 에러를 삭제하면 컴포넌트 자체가 렌더링이 되지 않는 것을 볼 수 있다.</p>
<h3 id="hook-사용-규칙">Hook 사용 규칙</h3>
<p>Hook을 사용할 때는 두 가지 규칙을 준수해야만 한다.
우리가 Hook을 직접 만들거나, 혹은 작성하면서 자주 볼 수 있는 에러들과 함께 규칙들을 살펴보도록 하겠다.</p>
<h3 id="1-리액트-함수의-최상위에서만-호출해야-합니다">1. 리액트 함수의 최상위에서만 호출해야 합니다.</h3>
<p>반복문, 조건문, 중첩된 함수 내에서 Hook을 실행하면 예상한 대로 동작하지 않을 우려가 있다.</p>
<pre><code class="language-jsx">...
if(counter) {
    const [sample, setSample] = useState(0);
}
...</code></pre>
<p>예를 들어 count가 있을 때 sample이라는 state를 사용하고 싶어서 조건문 안에 useState() hook을 불러왔다고 가정해 봅시다.(이런 식의 가정은 애초부터 틀린 가정이다.
이런 식으로 호출을 하게 되면 React의 동작 방식에 거스르기 때문에 React는 에러를 출력한다.</p>
<p><img src="https://velog.velcdn.com/images/edith_0318/post/784ecd3a-4fff-4f79-9b02-516de96fc1a1/image.png" alt="">
<em>[그림] useState를 조건문 안에서 호출하면 안 된다는 Error</em></p>
<p>컴포넌트 안에는 useState나 useEffect 같은 Hook들이 여러 번 사용될 수 있는데, React는 이 Hook을 호출되는 순서대로 저장을 해놓는다.</p>
<p>그런데 조건문, 반복문 안에서 Hook을 호출하게 되면 호출되는 순서대로 저장을 하기 어려워지고, 결국 예기치 못한 버그를 초래하게 될 수 있다.</p>
<h3 id="2-오직-리액트-함수-내에서만-사용되어야-합니다">2. 오직 리액트 함수 내에서만 사용되어야 합니다.</h3>
<p>이는 리액트 함수형 컴포넌트나 커스텀 Hook이 아닌 다른 일반 JavaScript 함수 안에서 호출해서는 안 된다는 의미이다.</p>
<pre><code class="language-jsx">...
window.onload = function () {
    useEffect(() =&gt; {
        // do something...
    }, [counter]);
}
...</code></pre>
<p>예를 들어 window의 요소가 모두 준비가 되면 useEffect()가 호출되었으면 좋겠다고 생각해서 함수를 작성했다고 가정해 보자.
이 또한 React의 동작 방식에 위배되기 때문에 React는 에러를 출력합니다.</p>
<p><img src="https://velog.velcdn.com/images/edith_0318/post/362abc97-b1f5-4883-9893-c48eb6eb1ad4/image.png" alt="">
<em>[그림] window.onload라는 함수에서 useEffect를 호출하면 안 된다는 error</em></p>
<p>애초에 Hook은 React의 함수 컴포넌트 내에서 사용되도록 만들어진 메소드이기 때문에 근본적으로 일반 JavaScript 함수 내에서는 정상적으로 돌아가지 않는다.
따라서 이 규칙 또한 반드시 준수해야 하는 규칙이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[React Diffing Algorithm]]></title>
            <link>https://velog.io/@edith_0318/React-Diffing-Algorithm</link>
            <guid>https://velog.io/@edith_0318/React-Diffing-Algorithm</guid>
            <pubDate>Fri, 20 Jan 2023 05:16:56 GMT</pubDate>
            <description><![CDATA[<p>React가 기존 가상 DOM과 변경된 새로운 가상 DOM을 비교할 때, React 입장에서는 변경된 새로운 가상 DOM 트리에 부합하도록 기존의 UI를 효율적으로 갱신하는 방법을 알아낼 필요가 있었다. 
즉 하나의 트리를 다른 트리로 변형을 시키는 가장 작은 조작 방식을 알아내야만 했는데, 알아낸 조작 방식 알고리즘은 O(n^3)의 복잡도를 가지고 있었다.</p>
<p>만약 이 알고리즘을 그대로 React에 적용한다면 1000개의 엘리먼트를 실제 화면에 표시하기 위해 10억(1000^3)번의 비교 연산을 해야만 한다. 
사실 이것은 너무 비싼 연산이기 때문에 React는 두 가지의 가정을 가지고 시간 복잡도 O(n)의 새로운 휴리스틱 알고리즘(Heuristic Algorithm)을 구현해낸다.</p>
<p>두 가지 가정은 이것이다.</p>
<ul>
<li>각기 서로 다른 두 요소는 다른 트리를 구축할 것</li>
<li>개발자가 제공하는 key 프로퍼티를 가지고, 여러 번 렌더링을 거쳐도 변경되지 말아야 하는 자식 요소가 무엇인지 알아낼 수 있을 것</li>
</ul>
<p>실제 이 두 가정은 거의 모든 실제 사용 사례에 들어맞게 된다. 
여기서 React는 비교 알고리즘(Diffing Algorithm)을 사용한다.</p>
<h2 id="react가-dom-트리를-탐색하는-방법">React가 DOM 트리를 탐색하는 방법</h2>
<p>React는 기존의 가상 DOM 트리와 새롭게 변경된 가상 DOM 트리를 비교할 때, 트리의 레벨 순서대로 순회하는 방식으로 탐색한다. 
즉 같은 레벨(위치)끼리 비교한다는 뜻이다. 
이는 너비 우선 탐색(BFS)의 일종이라고 볼 수 있다.
<img src="https://velog.velcdn.com/images/edith_0318/post/dd94b00f-db48-41fc-9781-b6e6a140e22c/image.png" alt="">
<em>[그림] React의 DOM 트리 순회 방식</em></p>
<p>React는 이런 식으로 동일 선상에 있는 노드를 파악한 뒤 다음 자식 세대의 노드를 순차적으로 파악해 나간다.</p>
<h3 id="다른-타입의-dom-엘리먼트인-경우">다른 타입의 DOM 엘리먼트인 경우</h3>
<p>그런데 DOM 트리는 각 HTML 태그마다 각각의 규칙이 있어 그 아래 들어가는 자식 태그가 한정적이라는 특징이 있다. 
(예를 들어 <code>&lt;ul&gt;</code> 태그 밑에는 <code>&lt;li&gt;</code> 태그만 와야 한다던가, <code>&lt;p&gt;</code> 태그 안에 <code>&lt;p&gt;</code> 태그를 또 쓰지 못하는 것이다.) 
자식 태그의 부모 태그 또한 정해져 있다는 특징이 있기 때문에, 부모 태그가 달라진다면 React는 이전 트리를 버리고 새로운 트리를 구축해버린다.</p>
<pre><code class="language-js">&lt;div&gt;
    &lt;Counter /&gt;
&lt;/div&gt;

//부모 태그가 div에서 span으로 바뀝니다.
&lt;span&gt;
    &lt;Counter /&gt;
&lt;/span&gt;</code></pre>
<p>이렇게 부모 태그가 바뀌어버리면, React는 기존의 트리를 버리고 새로운 트리를 구축하기 때문에 이전의 DOM 노드들은 전부 파괴된다. 
부모 노드였던 <code>&lt;div&gt;</code>가 <code>&lt;span&gt;</code>으로 바뀌어버리면 자식 노드인 <code>&lt;Counter /&gt;</code>는 완전히 해제된다. 
즉 이전 <code>&lt;div&gt;</code> 태그 속 <code>&lt;Counter /&gt;</code>는 파괴되고 <code>&lt;span&gt;</code> 태그 속 새로운 <code>&lt;Counter /&gt;</code>가 다시 실행된다.
새로운 컴포넌트가 실행되면서 기존의 컴포넌트는 완전히 해제(Unmount)되어버리기 때문에 <code>&lt;Counter /&gt;</code>가 갖고 있던 기존의 state 또한 파괴된다.</p>
<h3 id="같은-타입의-dom-엘리먼트인-경우">같은 타입의 DOM 엘리먼트인 경우</h3>
<p>반대로 타입이 바뀌지 않는다면 React는 최대한 렌더링을 하지 않는 방향으로 최소한의 변경 사항만 업데이트한다. 
이것이 가능한 이유는 앞서 React가 실제 DOM이 아닌 가상 DOM을 사용해 조작하기 때문이다. 
업데이트 할 내용이 생기면 virtual DOM 내부의 프로퍼티만 수정한 뒤, 모든 노드에 걸친 업데이트가 끝나면 그때 단 한번 실제 DOM으로의 렌더링을 시도한다.</p>
<pre><code class="language-js">&lt;div className=&quot;before&quot; title=&quot;stuff&quot; /&gt;

//기존의 엘리먼트가 태그는 바뀌지 않은 채 className만 바뀌었습니다.
&lt;div className=&quot;after&quot; title=&quot;stuff&quot; /&gt;</code></pre>
<p>  React는 두 요소를 비교했을 때 className만 수정되고 있다는 것을 알게 돈다. 
  className before와 after는 각자 이런 스타일을 갖고 있다고 해보자.</p>
<pre><code class="language-js">//className이 before인 컴포넌트
&lt;div style={{color: &#39;red&#39;, fontWeight: &#39;bold&quot;}} title=&quot;stuff&quot; /&gt;

//className이 after인 컴포넌트
&lt;div style={{color: &#39;green&#39;, fontWeight: &#39;bold&quot;}} title=&quot;stuff&quot; /&gt;</code></pre>
<p>두 엘리먼트를 비교했을 때 React는 정확히 color 스타일만 바뀌고 있음을 눈치챈다. 
그래서 React는 color 스타일만 수정하고 fontWeight 및 다른 요소는 수정하지 않는다. 
이렇게 하나의 DOM 노드를 처리한 뒤 React는 뒤이어서 해당 노드들 밑의 자식들을 순차적으로 동시에 순회하면서 차이가 발견될 때마다 변경한다. 
이를 재귀적으로 처리한다고 표현한다.</p>
<h3 id="자식-엘리먼트의-재귀적-처리">자식 엘리먼트의 재귀적 처리</h3>
<p>예를 들면 이렇게 자식 엘리먼트가 변경이 된다고 가정해보자.</p>
<pre><code class="language-JS">&lt;ul&gt;
  &lt;li&gt;first&lt;/li&gt;
  &lt;li&gt;second&lt;/li&gt;
&lt;/ul&gt;

//자식 엘리먼트의 끝에 새로운 자식 엘리먼트를 추가했습니다.
&lt;ul&gt;
  &lt;li&gt;first&lt;/li&gt;
  &lt;li&gt;second&lt;/li&gt;
  &lt;li&gt;third&lt;/li&gt;
&lt;/ul&gt;</code></pre>
<p>React는 기존 <code>&lt;ul&gt;</code>과 새로운 <code>&lt;ul&gt;</code>을 비교할 때 자식 노드를 순차적으로 위에서부터 아래로 비교하면서 바뀐 점을 찾는다. 
그렇기 때문에 예상대로 React는 첫 번째 자식 노드들과 두 번째 자식 노드들이 일치하는 걸 확인한 뒤 세 번째 자식 노드를 추가한다.</p>
<p>이렇게 React는 위에서 아래로 순차적으로 비교하기 때문에, 이 동작 방식에 대해 고민하지 않고 리스트의 처음에 엘리먼트를 삽입하게 되면 이전의 코드에 비해 훨씬 나쁜 성능을 내게 된다.</p>
<pre><code class="language-JS">&lt;ul&gt;
  &lt;li&gt;Duke&lt;/li&gt;
  &lt;li&gt;Villanova&lt;/li&gt;
&lt;/ul&gt;

//자식 엘리먼트를 처음에 추가합니다.
&lt;ul&gt;
  &lt;li&gt;Connecticut&lt;/li&gt;
  &lt;li&gt;Duke&lt;/li&gt;
  &lt;li&gt;Villanova&lt;/li&gt;
&lt;/ul&gt;</code></pre>
<p>이렇게 구현하게 되면 React는 우리의 기대대로 최소한으로 동작하지 못하게 된다. 
React는 원래의 동작하던 방식대로 처음의 노드들을 비교하게 된다.
처음의 자식 노드를 비교할 때, <code>&lt;li&gt;</code>Duke<code>&lt;/li&gt;</code> 와 <code>&lt;li&gt;</code>Connecticut<code>&lt;/li&gt;</code>로 자식 노드가 서로 다르다고 인지하게 된 React는 리스트 전체가 바뀌었다고 받아들인다. 즉 <code>&lt;li&gt;</code>Duke<code>&lt;/li&gt;</code>와 <code>&lt;li&gt;</code>Villanova<code>&lt;/li&gt;</code>는 그대로기 때문에 두 자식 노드는 유지시켜도 된다는 것을 깨닫지 못하고 전부 버리고 새롭게 렌더링 해버린다. 
이는 굉장히 비효율적인 동작 방식이다.</p>
<p>그래서 React는 이 문제를 해결하기 위해 key라는 속성을 지원합니다. 이는 효율적으로 가상 DOM을 조작할 수 있도록 한다. 
만일 개발할 당시 key라는 속성을 사용하지 않으면 React 에서 key값을 달라고 경고문을 띄우는 것도 이 때문인 것이다. key값이 없는 노드는 비효율적으로 동작할 수 있기 때문이다.</p>
<h3 id="키key">키(key)</h3>
<p>만약 자식 노드들이 이 key를 갖고 있다면, React는 그 key를 이용해 기존 트리의 자식과 새로운 트리의 자식이 일치하는지 아닌지 확인할 수 있다.</p>
<pre><code class="language-JS">&lt;ul&gt;
  &lt;li key=&quot;2015&quot;&gt;Duke&lt;/li&gt;
  &lt;li key=&quot;2016&quot;&gt;Villanova&lt;/li&gt;
&lt;/ul&gt;

//key가 2014인 자식 엘리먼트를 처음에 추가합니다.
&lt;ul&gt;
  &lt;li key=&quot;2014&quot;&gt;Connecticut&lt;/li&gt;
  &lt;li key=&quot;2015&quot;&gt;Duke&lt;/li&gt;
  &lt;li key=&quot;2016&quot;&gt;Villanova&lt;/li&gt;
&lt;/ul&gt;</code></pre>
<p>React는 <code>key</code> 속성을 통해 ‘2014’라는 자식 엘리먼트가 새롭게 생겼고, ‘2015’, ‘2016’ 키를 가진 엘리먼트는 그저 위치만 이동했다는 걸 알게 된다. 
따라서 React는 기존의 동작 방식대로 다른 자식 엘리먼트는 변경하지 않고 추가된 엘리먼트만 변경한다. 
이 key 속성에는 보통 데이터 베이스 상의 유니크한 값(ex. Id)을 부여해주면 된다. 
키는 전역적으로 유일할 필요는 없고, 형제 엘리먼트 사이에서만 유일하면 된다.</p>
<p>만약 이런 유니크한 값이 없다면 최후의 수단으로 배열의 인덱스를 key로 사용할 수 있다. 
다만 배열이 다르게 정렬될 경우가 생긴다면 배열의 인덱스를 key로 선택했을 경우는 비효율적으로 동작할 것이다. 
왜냐하면 배열이 다르게 정렬되어도 인덱스는 그대로 유지되기 때문이다. 
인덱스는 그대로지만 그 요소가 바뀌어버린다면 React는 배열의 전체가 바뀌었다고 받아들일 것이고, 기존의 DOM 트리를 버리고 새로운 DOM 트리를 구축해버리기 때문에 비효율적으로 동작하는 것이다.</p>
]]></description>
        </item>
    </channel>
</rss>