<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>기분따라 코딩</title>
    <link>https://cocoder16.tistory.com/</link>
    <description>개발 블로그. 공부한 것을 남겨야겠다.</description>
    <language>ko</language>
    <pubDate>Tue, 14 Apr 2026 18:34:00 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>cocoder16</managingEditor>
    <item>
      <title>리액트 모달창 만들기 2가지 방법</title>
      <link>https://cocoder16.tistory.com/84</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;방법 1. 직접 모달창 컴포넌트 만들기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 모달창으로 사용할 UI 컴포넌트를 만들기 위해 Modal.jsx 파일을 생성합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fdfdfd; color: #000000;&quot;&gt;* Modal.jsx&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1675083046198&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function Modal() {
  return (
    &amp;lt;div&amp;gt;
      Modal
    &amp;lt;/div&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fdfdfd; color: #000000;&quot;&gt;이 모달 컴포넌트를 렌더링할 부모 컴포넌트에서는 모달이 열려있는지, 닫혀있는지에 대한 상태를 관리하고 그 상태 값을 모달 컴포넌트에 전달해야 합니다. 모달 컴포넌트에 부모로부터 받을 수 있는 props 코드를 추가하겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fdfdfd; color: #000000;&quot;&gt;* Modal.jsx&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1675083215374&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function Modal({ isOpen }) {
  return (
    &amp;lt;div style={{ display: isOpen ? &quot;block&quot; : &quot;none&quot; }}&amp;gt;
      Modal
    &amp;lt;/div&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fdfdfd; color: #000000;&quot;&gt;CSS의 display 속성을 이용하였습니다. 결과적으로 isOpen이 true이면 display의 값은 &quot;block&quot;이 되어 사용자의 화면에 모달창이 나타나고 isOpen이 false이면 사라집니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fdfdfd; color: #000000;&quot;&gt;다음으로 모달창을 닫는 버튼을 추가해야 합니다. 버튼을 클릭할 때 isOpen값을 false로 변경해야 합니다. 그런데 isOpen은 모달의 부모 컴포넌트에서 관리하는 상태값이므로, 이것을 변경하는 행위를 담당하는 메서드는 부모 컴포넌트에서 정의해야 합니다. 따라서 이 메서드도 props로 받도록 합니다. 메서드명을 closeModal이라고 하겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fdfdfd; color: #000000;&quot;&gt;* Modal.jsx&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1675083419640&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function Modal({ isOpen, closeModal }) {
  return (
    &amp;lt;div style={{ display: isOpen ? &quot;block&quot; : &quot;none&quot; }}&amp;gt;
      &amp;lt;p&amp;gt;Modal&amp;lt;/p&amp;gt;
      &amp;lt;button onClick={closeModal}&amp;gt;Close&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}

export default Modal;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fdfdfd; color: #000000;&quot;&gt;외부에서 사용할 수 있게 export 코드도 추가하였습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fdfdfd; color: #000000;&quot;&gt;외부에서 모달 컴포넌트를 렌더링하는 방법은 다음과 같습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fdfdfd; color: #000000;&quot;&gt;* App.jsx&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1675084728566&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useState } from &quot;react&quot;;

import Modal from &quot;./Modal&quot;;

function App() {
  const [isModalOpen, setIsModalOpen] = useState(false);
  
  const openModal = () =&amp;gt; setIsModalOpen(true);
  const closeModal = () =&amp;gt; setIsModalOpen(false);
  
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;button onClick={openModal}&amp;gt;Open Modal&amp;lt;/button&amp;gt;
      &amp;lt;Modal isOpen={isModalOpen} closeModal={closeModal} /&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}

export default App;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fdfdfd; color: #000000;&quot;&gt;isOpen 상태와 그 값을 변경할 수 있는 메서드들을 선언하여 UI에 연결하였습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fdfdfd; color: #000000;&quot;&gt;다음으로 모달에 존재해야하는 내용을 렌더링해야 합니다. 모달 컴포넌트를 공용 UI 컴포넌트로 만들어 어디서든 재사용가능하도록 만들기 위해서는 데이터를 외부로부터 받아오도록 하는 것이 좋습니다. 그래서 이것도 props로 받도록 하겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fdfdfd; color: #000000;&quot;&gt;* Modal.jsx&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1675083698809&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function Modal({ isOpen, content, closeModal }) {
  return (
    &amp;lt;div style={{ display: isOpen ? &quot;block&quot; : &quot;none&quot; }}&amp;gt;
      &amp;lt;div&amp;gt;{content}&amp;lt;/div&amp;gt;
      &amp;lt;button onClick={closeModal}&amp;gt;Close&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;html이나 다른 컴포넌트 등을 렌더링할 수 있게 자유도를 더 높이려면 children을 사용할 수도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fdfdfd; color: #000000;&quot;&gt;* Modal.jsx&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1675083952674&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function Modal({ isOpen, children, closeModal }) {
  return (
    &amp;lt;div style={{ display: isOpen ? &quot;block&quot; : &quot;none&quot; }}&amp;gt;
      &amp;lt;div&amp;gt;{children}&amp;lt;/div&amp;gt;
      &amp;lt;button onClick={closeModal}&amp;gt;Close&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;content props를 쓴 컴포넌트와 children을 사용하는 컴포넌트는 부모 컴포넌트에서 각각 다음과 같은 사용법의 차이가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* content를 사용한 경우&lt;/p&gt;
&lt;pre id=&quot;code_1675084223225&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useState } from &quot;react&quot;;

import Modal from &quot;./Modal&quot;;

function App() {
  const [isModalOpen, setIsModalOpen] = useState(false);
  
  const openModal = () =&amp;gt; setIsModalOpen(true);
  const closeModal = () =&amp;gt; setIsModalOpen(false);
  
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;button onClick={openModal}&amp;gt;Open Modal&amp;lt;/button&amp;gt;
      &amp;lt;Modal isOpen={isModalOpen} content=&quot;Hi. It's me.&quot; closeModal={closeModal} /&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* children을 사용한 경우&lt;/p&gt;
&lt;pre id=&quot;code_1675084327952&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useState } from &quot;react&quot;;

import Modal from &quot;./Modal&quot;;

function App() {
  const [isModalOpen, setIsModalOpen] = useState(false);
  
  const openModal = () =&amp;gt; setIsModalOpen(true);
  const closeModal = () =&amp;gt; setIsModalOpen(false);
  
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;button onClick={openModal}&amp;gt;Open Modal&amp;lt;/button&amp;gt;
      &amp;lt;Modal isOpen={isModalOpen} closeModal={closeModal}&amp;gt;
      	&amp;lt;h2&amp;gt;hi&amp;lt;/h2&amp;gt;
        &amp;lt;p&amp;gt;it's me&amp;lt;/p&amp;gt;
      &amp;lt;/Modal&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;children을 사용한 방식은 Modal 마크업 안에 마크업으로 내용을 작성할 수 있어 자유도가 더 높습니다. 해당 내용은 Modal.jsx에서 {children}이라고 쓴 부분에서 렌더링됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;검은 백그라운드 위에 모달창을 중앙에 띄우도록 CSS코드를 추가하겠습니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* Modal.jsx&lt;/p&gt;
&lt;pre id=&quot;code_1675139810775&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function Modal({ isOpen, content, closeModal }) {
  return (
    &amp;lt;div
      style={{
        display: isOpen ? &quot;block&quot; : &quot;none&quot;,
      }}
    &amp;gt;
      &amp;lt;div
        style={{
          position: &quot;fixed&quot;,
          top: 0,
          left: 0,
          width: &quot;100vw&quot;,
          height: &quot;100vh&quot;,
          backgroundColor: &quot;rgba(0, 0, 0, 0.35)&quot;,
        }}
      &amp;gt;&amp;lt;/div&amp;gt;
      &amp;lt;div
        style={{
          position: &quot;absolute&quot;,
          top: &quot;50%&quot;,
          left: &quot;50%&quot;,
          transform: &quot;translate(-50%, -50%)&quot;,
          width: 800,
          maxWidth: &quot;100%&quot;,
          maxHeight: &quot;90%&quot;,
          overflowY: &quot;auto&quot;,
          backgroundColor: &quot;white&quot;,
        }}
      &amp;gt;
        &amp;lt;div&amp;gt;{content}&amp;lt;/div&amp;gt;
        &amp;lt;button onClick={closeModal}&amp;gt;Close&amp;lt;/button&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;방법 2. UI 라이브러리 사용하기 - Material UI (MUI)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Material UI (MUI)를 사용하는 방법은 편의성이 좋은 세련된 UI를 그대로 사용할 수 있어 편리합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MUI를 yarn으로 설치하는 방법입니다. 터미널에서 입력합니다. &lt;a href=&quot;https://mui.com/material-ui/getting-started/installation/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;공식 홈페이지 - 설치법&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1675085317375&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;yarn add @mui/material @emotion/react @emotion/styled&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MUI가 제공하는 Modal을 import 하는 방법입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1675085233800&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import Modal from &quot;@mui/material/Modal&quot;;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같이 바로 사용할 수 있습니다. 다른 MUI의 사용법들은 설명을 생략하겠습니다. MUI에 대해 더 자세한 설명은 &lt;a href=&quot;https://cocoder16.tistory.com/81&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;이 포스트&lt;/a&gt;나 &lt;a href=&quot;https://mui.com/material-ui/getting-started/overview/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;MUI 공식 홈페이지&lt;/a&gt;를 참고해 주세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* App.jsx&lt;/p&gt;
&lt;pre id=&quot;code_1675085751926&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useState } from &quot;react&quot;;
import Box from &quot;@mui/material/Box&quot;;
import Button from &quot;@mui/material/Button&quot;;
import Typography from &quot;@mui/material/Typography&quot;;
import Modal from &quot;@mui/material/Modal&quot;;

function App() {
  const [isModalOpen, setIsModalOpen] = useState(false);
  
  const openModal = () =&amp;gt; setIsModalOpen(true);
  const closeModal = () =&amp;gt; setIsModalOpen(false);
  
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;Button onClick={openModal}&amp;gt;Open modal&amp;lt;/Button&amp;gt;
      &amp;lt;Modal
        open={isModalOpen}
        onClose={closeModal}
      &amp;gt;
        &amp;lt;Box&amp;gt;
          &amp;lt;Typography variant=&quot;h6&quot; component=&quot;h2&quot;&amp;gt;
            hi
          &amp;lt;/Typography&amp;gt;
          &amp;lt;Typography sx={{ mt: 2 }}&amp;gt;
            it's me
          &amp;lt;/Typography&amp;gt;
        &amp;lt;/Box&amp;gt;
      &amp;lt;/Modal&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}

export default App;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Modal은 children을 사용하여 자유롭게 내용물을 렌더링할 수 있습니다. 위 예시 코드에서는 MUI가 제공하는 다른 UI들로 내용물을 채워봤습니다. open과 onClose는 MUI에서 제공하는 api이므로 이것을 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모달을 공용 컴포넌트로 만들어 사용하려면 직접 만든 방식에서 한 것처럼 컴포넌트를 따로 분리합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* CustomModal.jsx&lt;/p&gt;
&lt;pre id=&quot;code_1675085601785&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import Modal from &quot;@mui/material/Modal&quot;;

function CustomModal({ isOpen, closeModal, children }) {
  return (
    &amp;lt;Modal open={isOpen} onClose={closeModal}&amp;gt;
      {children}
    &amp;lt;/Modal&amp;gt;
  );
}

export default CustomModal;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공용 디자인을 입히려면 공용 컴포넌트 내에서 디자인합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* CustomModal.jsx&lt;/p&gt;
&lt;pre id=&quot;code_1675086280245&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import Modal from &quot;@mui/material/Modal&quot;;
import Paper from &quot;@mui/material/Paper&quot;;

function CustomModal({ isOpen, closeModal, children }) {
  return (
    &amp;lt;Modal open={isOpen} onClose={closeModal}&amp;gt;
      &amp;lt;Paper
        elevation={2}
        sx={{
          position: &quot;absolute&quot;,
          top: &quot;50%&quot;,
          left: &quot;50%&quot;,
          transform: &quot;translate(-50%, -50%)&quot;,
          width: 800,
          maxWidth: &quot;100%&quot;,
          maxHeight: &quot;90%&quot;,
          overflowY: &quot;auto&quot;,
        }}
      &amp;gt;
        {children}
      &amp;lt;/Paper&amp;gt;
    &amp;lt;/Modal&amp;gt;
  );
}

export default CustomModal;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부모 컴포넌트에서는 mui의 Modal 모듈을 직접 임포트하는 것이 아니라 이 컴포넌트를 임포트하여 사용하면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* App.jsx&lt;/p&gt;
&lt;pre id=&quot;code_1675087350038&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useState } from &quot;react&quot;;
import Box from &quot;@mui/material/Box&quot;;
import Button from &quot;@mui/material/Button&quot;;
import Typography from &quot;@mui/material/Typography&quot;;

import CustomModal from &quot;./CustomModal&quot;;

function App() {
  const [isModalOpen, setIsModalOpen] = useState(false);
  
  const openModal = () =&amp;gt; setIsModalOpen(true);
  const closeModal = () =&amp;gt; setIsModalOpen(false);
  
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;Button onClick={openModal}&amp;gt;Open modal&amp;lt;/Button&amp;gt;
      &amp;lt;CustomModal
        isOpen={isModalOpen}
        closeModal={closeModal}
      &amp;gt;
        &amp;lt;Box&amp;gt;
          &amp;lt;Typography variant=&quot;h6&quot; component=&quot;h2&quot;&amp;gt;
            hi
          &amp;lt;/Typography&amp;gt;
          &amp;lt;Typography sx={{ mt: 2 }}&amp;gt;
            it's me
          &amp;lt;/Typography&amp;gt;
        &amp;lt;/Box&amp;gt;
      &amp;lt;/CustomModal&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}

export default App;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>React.js</category>
      <category>Material UI</category>
      <category>modal</category>
      <category>MUI</category>
      <category>react</category>
      <category>모달</category>
      <author>cocoder16</author>
      <guid isPermaLink="true">https://cocoder16.tistory.com/84</guid>
      <comments>https://cocoder16.tistory.com/84#entry84comment</comments>
      <pubDate>Mon, 13 Feb 2023 08:00:31 +0900</pubDate>
    </item>
    <item>
      <title>ChatGPT를 프로그래밍에서 활용할 수 있을까?</title>
      <link>https://cocoder16.tistory.com/85</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;ChatGPT에 대해서&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;openai가 내놓은 인공지능 챗봇, 지금은 무료 버전만 있는데 나중에 유료 버전도 나온다고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ChatGPT는 대화를 위해 최적화된 언어 모델로, 주로 정보를 얻기 위한 대화를 하기 위해 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대화는 한 번의 응답으로 끝나는 게 아니라, 지속적으로 이어나갈 수 있으며 문맥에 따른 후속 대답을 끌어낼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;또한 로봇은 실수를 인정하기도 하고, 잘못된 전제를 반박하기도 하고, 부적절한 요청을 거절하기도 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모델은 지도학습과 강화학습으로 파인 튜닝했으며, 지도학습의 경우, 인간 트레이너가 사용자와 ChatGPT 양쪽 모두를 연기하는 대화를 모델에 입력했고, 강화 단계에서는 인간 트레이너들이 모델이 이전 대화에서 만든 응답들에 대해 순위를 매겼습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ChatGPT는 openai 홈페이지에 들어가 간단하게 구글 로그인을 한 후 이용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://openai.com/blog/chatgpt/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://openai.com/blog/chatgpt/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1675219927758&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;ChatGPT: Optimizing Language Models for Dialogue&quot; data-og-description=&quot;We&amp;rsquo;ve trained a model called ChatGPT which interacts in a conversational way. The dialogue format makes it possible for ChatGPT to answer followup questions, admit its mistakes, challenge incorrect premises, and reject inappropriate requests. ChatGPT is &quot; data-og-host=&quot;openai.com&quot; data-og-source-url=&quot;https://openai.com/blog/chatgpt/&quot; data-og-url=&quot;https://openai.com/blog/chatgpt/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/IJ5Yb/hyRsql9xob/EjCTo8jImYOV35Ttzb5201/img.jpg?width=2047&amp;amp;height=2047&amp;amp;face=0_0_2047_2047,https://scrap.kakaocdn.net/dn/9WAbG/hyRss5k5EF/87JavVF9VvtDSTkeLX99e0/img.jpg?width=2047&amp;amp;height=2047&amp;amp;face=0_0_2047_2047&quot;&gt;&lt;a href=&quot;https://openai.com/blog/chatgpt/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://openai.com/blog/chatgpt/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/IJ5Yb/hyRsql9xob/EjCTo8jImYOV35Ttzb5201/img.jpg?width=2047&amp;amp;height=2047&amp;amp;face=0_0_2047_2047,https://scrap.kakaocdn.net/dn/9WAbG/hyRss5k5EF/87JavVF9VvtDSTkeLX99e0/img.jpg?width=2047&amp;amp;height=2047&amp;amp;face=0_0_2047_2047');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;ChatGPT: Optimizing Language Models for Dialogue&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;We&amp;rsquo;ve trained a model called ChatGPT which interacts in a conversational way. The dialogue format makes it possible for ChatGPT to answer followup questions, admit its mistakes, challenge incorrect premises, and reject inappropriate requests. ChatGPT is&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;openai.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;한계&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;openai에서는 ChatGPT가 아직 부족한 점이 많다는 것과 나아가야 할 길을 알고 있으며 이에 대해 공시하고 있습니다. openai에서 밝히고 있는 현재 수준의 ChatGPT가 가지고 있는 한계점들은 다음과 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. ChatGPT는 때때로 그럴듯하게 들리지만 부정확하거나 말도 안 되는 답변을 작성한다. 다음과 같은 이유로 이 문제를 해결하는 것은 어렵다. (1) RL 훈련 중에는 현재 진실의 원천이 없다. (2) 모델을 더 신중하게 훈련시키면 올바르게 대답할 수 있는 질문을 거부하게 된다. (3) 이상적인 답변은 인간이 아는 게 아니라 모델이 아는 것에 의존하기 때문에, 지도 훈련은 모델을 잘못되게 만들 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. ChatGPT는 입력 구문을 수정하거나 동일한 프롬프트를 여러 번 시도하는 데 민감하다. 예를 들어, 질문의 한 구문이 주어지면 모델은 답을 모른다고 할 수 있지만, 약간의 구문수정을 하면 정확하게 대답할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;3. 이 모델은 종종 지나치게 장황하고 특정 구문을 과도하게 사용한다. 예를 들면 openai에 의해 훈련된 언어모델이라고 명시하는 것 같은 문구들이다. 이러한 문제는 인간 트레이너는 응축된 답변보다는 더 긴 답변을 선호하기 때문에, 이런 트레이닝 데이터를 사용했다는 것과 지나친 최적화 문제로 인해 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;4. 이상적인 모델은 사용자가 모호한 질문을 제공할 때 질문을 명확히 하기 위해 반문할 것이다. 하지만 현재 ChatGPT모델은 모호한 질문이 들어와도 보통 사용자가 의도한 것을 추측하여 답변한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;5. 모델이 부적절한 요청을 거부하도록 노력했지만, 때로는 유해한 지시에 응답하거나 편향된 행동을 보일 수 있다. Moderation API를 사용하여 특정 유형의 안전하지 않은 콘텐츠를 경고하거나 차단하고 있지만, 현재로서는 잘못된 부정 및 긍정이 있을 것으로 예상한다. 우리(openai)는 이 시스템을 개선하기 위한 지속적인 작업을 돕기 위해 사용자 피드백을 수집하고 싶다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여담으로 ai산업은 완전 자율적으로 학습하고 행동할 수 있는 이상적인 ai수준에 도달하기에 현상태는 아직 한참 모자라지만, 그래도 유의미한 진취는 계속 이뤄지고 있는 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div class=&quot;revenue_unit_item adsense  responsive&quot;&gt;&lt;ins class=&quot;adsbygoogle&quot; style=&quot;display: block;&quot; data-ad-host=&quot;ca-host-pub-9691043933427338&quot; data-ad-client=&quot;ca-pub-9807016842906892&quot; data-ad-format=&quot;auto&quot;&gt;&lt;/ins&gt;
&lt;script&gt;(adsbygoogle = window.adsbygoogle || []).push({}); &lt;/script&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;활용법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ChatGPT는 주로 정보를 전달하는 대화에 능숙하고 실제로 그렇게 활용하라고 만들어졌습니다. 사실과 정보의 전달에는 강한 강점을 가지고 있으나, 감정적인 대화는 불가능하여 이런 대화를 시도하면 거절의 답변을 들을 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;837&quot; data-origin-height=&quot;210&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nzEV8/btrXIyZLaUM/xSpSDWRzFRjiD2ASvtAkBK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nzEV8/btrXIyZLaUM/xSpSDWRzFRjiD2ASvtAkBK/img.png&quot; data-alt=&quot;감정적인 대화를 시도하자 거절하는 ChatGPT&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nzEV8/btrXIyZLaUM/xSpSDWRzFRjiD2ASvtAkBK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnzEV8%2FbtrXIyZLaUM%2FxSpSDWRzFRjiD2ASvtAkBK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;594&quot; height=&quot;149&quot; data-origin-width=&quot;837&quot; data-origin-height=&quot;210&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;감정적인 대화를 시도하자 거절하는 ChatGPT&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정보를 물어보면 정성껏 답변해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1007&quot; data-origin-height=&quot;409&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xCTxX/btrXJWFQxXH/vK0rIH4kLtPSyAQBtic781/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xCTxX/btrXJWFQxXH/vK0rIH4kLtPSyAQBtic781/img.png&quot; data-alt=&quot;openai에 대해 알려줘&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xCTxX/btrXJWFQxXH/vK0rIH4kLtPSyAQBtic781/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxCTxX%2FbtrXJWFQxXH%2FvK0rIH4kLtPSyAQBtic781%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;727&quot; height=&quot;295&quot; data-origin-width=&quot;1007&quot; data-origin-height=&quot;409&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;openai에 대해 알려줘&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;후속질문을 하면 이전 대화와의 맥락에 맞게 답변을 해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;909&quot; data-origin-height=&quot;670&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cNi6vq/btrXHSxWtZ0/XmGr28LftJWXDYI27wvV3K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cNi6vq/btrXHSxWtZ0/XmGr28LftJWXDYI27wvV3K/img.png&quot; data-alt=&quot;그들(openai)에겐 어떤 상품이 있어?&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cNi6vq/btrXHSxWtZ0/XmGr28LftJWXDYI27wvV3K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcNi6vq%2FbtrXHSxWtZ0%2FXmGr28LftJWXDYI27wvV3K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;689&quot; height=&quot;508&quot; data-origin-width=&quot;909&quot; data-origin-height=&quot;670&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그들(openai)에겐 어떤 상품이 있어?&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;코딩도 해준다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ChatGPT는 뛰어난 코딩실력도 가지고 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파이썬으로 크리스마스 트리를 그려보라고 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;906&quot; data-origin-height=&quot;788&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bfPWeF/btrXJ3dT24k/uPKzpASMS7REw4DtplgpN1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bfPWeF/btrXJ3dT24k/uPKzpASMS7REw4DtplgpN1/img.png&quot; data-alt=&quot;파이썬으로 크리스마스 트리를 그려줘&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bfPWeF/btrXJ3dT24k/uPKzpASMS7REw4DtplgpN1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbfPWeF%2FbtrXJ3dT24k%2FuPKzpASMS7REw4DtplgpN1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;660&quot; height=&quot;574&quot; data-origin-width=&quot;906&quot; data-origin-height=&quot;788&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;파이썬으로 크리스마스 트리를 그려줘&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라이브러리나 프레임워크도 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;490&quot; data-origin-height=&quot;76&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d7cM7G/btrXHAYAVKU/WWkhHpGZotkuD46d3hziBk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d7cM7G/btrXHAYAVKU/WWkhHpGZotkuD46d3hziBk/img.png&quot; data-alt=&quot;react로 투두리스트 만들어줘&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d7cM7G/btrXHAYAVKU/WWkhHpGZotkuD46d3hziBk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd7cM7G%2FbtrXHAYAVKU%2FWWkhHpGZotkuD46d3hziBk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;458&quot; height=&quot;71&quot; data-origin-width=&quot;490&quot; data-origin-height=&quot;76&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;react로 투두리스트 만들어줘&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리액트로 투두리스트를 만들어달라고 했는데, 생각보다 깔끔한 코드를 뽑아냈습니다. 결과는 이미지가 길어서, 코드를 복붙해왔습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1675222093472&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import React, { useState } from &quot;react&quot;;

const App = () =&amp;gt; {
  const [tasks, setTasks] = useState([]);
  const [newTask, setNewTask] = useState(&quot;&quot;);

  const addTask = (e) =&amp;gt; {
    e.preventDefault();
    setTasks([...tasks, newTask]);
    setNewTask(&quot;&quot;);
  };

  const removeTask = (index) =&amp;gt; {
    setTasks([...tasks.slice(0, index), ...tasks.slice(index + 1)]);
  };

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h1&amp;gt;To-Do List&amp;lt;/h1&amp;gt;
      &amp;lt;form onSubmit={addTask}&amp;gt;
        &amp;lt;input
          type=&quot;text&quot;
          value={newTask}
          onChange={(e) =&amp;gt; setNewTask(e.target.value)}
        /&amp;gt;
        &amp;lt;button type=&quot;submit&quot;&amp;gt;Add Task&amp;lt;/button&amp;gt;
      &amp;lt;/form&amp;gt;
      &amp;lt;ul&amp;gt;
        {tasks.map((task, index) =&amp;gt; (
          &amp;lt;li key={index}&amp;gt;
            {task}
            &amp;lt;button onClick={() =&amp;gt; removeTask(index)}&amp;gt;X&amp;lt;/button&amp;gt;
          &amp;lt;/li&amp;gt;
        ))}
      &amp;lt;/ul&amp;gt;
    &amp;lt;/div&amp;gt;
  );
};

export default App;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ChatGPT의 코딩실력이 너무 뛰어난 것 같아서 더 어려운 프로그래밍을 시켜봤습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;894&quot; data-origin-height=&quot;768&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/biNBuu/btrXIP8lQMq/Lb3dkkMq7FKbsbbgyuckp0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/biNBuu/btrXIP8lQMq/Lb3dkkMq7FKbsbbgyuckp0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/biNBuu/btrXIP8lQMq/Lb3dkkMq7FKbsbbgyuckp0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbiNBuu%2FbtrXIP8lQMq%2FLb3dkkMq7FKbsbbgyuckp0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;722&quot; height=&quot;620&quot; data-origin-width=&quot;894&quot; data-origin-height=&quot;768&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;841&quot; data-origin-height=&quot;407&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bvJ3Iu/btrXJfFGvfI/4zVvyNKLha8zgdAU7kjlo0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bvJ3Iu/btrXJfFGvfI/4zVvyNKLha8zgdAU7kjlo0/img.png&quot; data-alt=&quot;MS 주식 자동매매 코드를 짜달라는 부탁에 대한 답변&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bvJ3Iu/btrXJfFGvfI/4zVvyNKLha8zgdAU7kjlo0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbvJ3Iu%2FbtrXJfFGvfI%2F4zVvyNKLha8zgdAU7kjlo0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;709&quot; height=&quot;343&quot; data-origin-width=&quot;841&quot; data-origin-height=&quot;407&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;MS 주식 자동매매 코드를 짜달라는 부탁에 대한 답변&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요구사항이 너무 추상적이었나 봅니다. 더 세부적인 지시를 많이 해야 만들 수 있을 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;개발자는 ChatGPT를 어떻게 활용할 수 있을까?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발자로서 ChatGPT를 활용할 수 있는 방법을 계속 생각해보고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단은 역시 정보제공에 능한 만큼, 유용한 기술이나 라이브러리 등을 탐색할 때 쓰기 좋았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(최신 트렌드는 따라잡기 힘든데, 2021년까지의 데이터만 학습데이터로 사용했기 때문입니다. 이 부분은 유료버전이 해결할거라고 기대합니다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 달력을 만들고 싶은데 적합한 리액트 UI라이브러리를 찾아봤습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;904&quot; data-origin-height=&quot;665&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tjRFX/btrXHPutqYH/Zex4MfASjVik9aJAh3KAXk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tjRFX/btrXHPutqYH/Zex4MfASjVik9aJAh3KAXk/img.png&quot; data-alt=&quot;리액트로 달력을 만들고 싶은데 적당한 UI 라이브러리없니?&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tjRFX/btrXHPutqYH/Zex4MfASjVik9aJAh3KAXk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtjRFX%2FbtrXHPutqYH%2FZex4MfASjVik9aJAh3KAXk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;648&quot; height=&quot;477&quot; data-origin-width=&quot;904&quot; data-origin-height=&quot;665&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;리액트로 달력을 만들고 싶은데 적당한 UI 라이브러리없니?&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;891&quot; data-origin-height=&quot;68&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c8mohp/btrXIndb3z9/EJfRyH6AZsxACa7yy4XedK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c8mohp/btrXIndb3z9/EJfRyH6AZsxACa7yy4XedK/img.png&quot; data-alt=&quot;하나만 골라줘&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c8mohp/btrXIndb3z9/EJfRyH6AZsxACa7yy4XedK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc8mohp%2FbtrXIndb3z9%2FEJfRyH6AZsxACa7yy4XedK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;599&quot; height=&quot;46&quot; data-origin-width=&quot;891&quot; data-origin-height=&quot;68&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;하나만 골라줘&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;908&quot; data-origin-height=&quot;733&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b92C93/btrXHWUrATJ/Xp4dXkEdY0J6bQP4LGQVt1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b92C93/btrXHWUrATJ/Xp4dXkEdY0J6bQP4LGQVt1/img.png&quot; data-alt=&quot;답변&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b92C93/btrXHWUrATJ/Xp4dXkEdY0J6bQP4LGQVt1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb92C93%2FbtrXHWUrATJ%2FXp4dXkEdY0J6bQP4LGQVt1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;691&quot; height=&quot;558&quot; data-origin-width=&quot;908&quot; data-origin-height=&quot;733&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;답변&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 다른 활용법은 역시 코딩을 직접 시키는 것입니다. 하지만 거대한 프로젝트를 온전히 맡기기에는 어렵고, 작은 코드 조각이나 샘플 코드를 얻어내기는 쉬웠습니다. 위에 예시가 있으니 예시는 넘어가겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 또 생각해 본 활용처는 프로그래밍 문제를 어떻게 해결해야 할지 가이드를 받고 싶을 때 질문을 하는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;A라는 문제를 풀고 싶은데, 어떤 방식으로 접근해야 할까?&quot; 하는 질문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 ios 알람 앱을 만들고 싶은데 어떤 순서로 해야 할지 물어봤습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;739&quot; data-origin-height=&quot;63&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ckciPH/btrXN2rSmxH/BNNi1rkXde0cfV0YV1i59K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ckciPH/btrXN2rSmxH/BNNi1rkXde0cfV0YV1i59K/img.png&quot; data-alt=&quot;IOS 알람앱 만들고 싶어. 어떻게 만들지?&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ckciPH/btrXN2rSmxH/BNNi1rkXde0cfV0YV1i59K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FckciPH%2FbtrXN2rSmxH%2FBNNi1rkXde0cfV0YV1i59K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;739&quot; height=&quot;63&quot; data-origin-width=&quot;739&quot; data-origin-height=&quot;63&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;IOS 알람앱 만들고 싶어. 어떻게 만들지?&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;914&quot; data-origin-height=&quot;790&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Hxk5l/btrXIyZSjfx/I0ehA1YRa39Hpsb454PfD1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Hxk5l/btrXIyZSjfx/I0ehA1YRa39Hpsb454PfD1/img.png&quot; data-alt=&quot;답변&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Hxk5l/btrXIyZSjfx/I0ehA1YRa39Hpsb454PfD1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHxk5l%2FbtrXIyZSjfx%2FI0ehA1YRa39Hpsb454PfD1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;706&quot; height=&quot;610&quot; data-origin-width=&quot;914&quot; data-origin-height=&quot;790&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;답변&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이외에도 더 있을 것 같은데, ChatGPT는 여러 방면에 유용하여 개발자도 충분히 잘 활용할 수 있는 좋은 기술이 될 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Info</category>
      <category>ChatGPT</category>
      <category>openai</category>
      <category>챗봇</category>
      <author>cocoder16</author>
      <guid isPermaLink="true">https://cocoder16.tistory.com/85</guid>
      <comments>https://cocoder16.tistory.com/85#entry85comment</comments>
      <pubDate>Mon, 6 Feb 2023 08:00:36 +0900</pubDate>
    </item>
    <item>
      <title>시계열 데이터베이스 influxDB 소개 및 사용법</title>
      <link>https://cocoder16.tistory.com/83</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;시계열 데이터베이스(TSDB)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시계열 데이터(Time series data)란 시간이 흐름에 따라 발생하는 데이터를 의미합니다. 예를 들면 시간이 흐름에 따라 변하는 온도, 풍향 같은 기후 데이터나 주가 데이터 등이 시계열 데이터입니다. 시계열 데이터는 기본적으로 데이터의 양이 방대하고 끊임없이 증가합니다. 기록되는 각각의 데이터들은 시간 종속성을 가진다는 특성도 있습니다. 관계형 데이터베이스(RDB)나 다른 NoSQL로는 이러한 시계열 데이터를 다루기에 큰 불편함이 있기 때문에 시계열 데이터베이스가 따로 생기게 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시계열 데이터베이스(TSDB)는 시계열 데이터를 효율적으로 다루기위해 고안된 데이터베이스입니다. 시간이 흐름에 따라 많은 양의 데이터가 끊임없이 쌓이기 때문에 쓰기 성능에 최적화가 되어있습니다. RDB는 인덱스가 걸려있으면 데이터의 양이 커질수록 쓰기 성능은 점점 저하되지만 TSDB의 인덱스는 이런 경우에도 성능이 떨어지지 않도록 만들어졌습니다. TSDB는 시간에 따라 저장공간을 분리하고 시간으로 쿼리를 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TSDB는 쓰기와 조회에 대해서는 고도의 최적화를 했습니다. 반면 업데이트와 삭제에 대한 성능은 포기할 수밖에 없습니다. 하지만 이것은 그리 큰 문제가 되지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시계열 데이터의 특징은 일단 시간에 따른 데이터가 발생하면 그 데이터가 변경되는 일은 없다는 것입니다. 예를 들어 오늘 오후 1시 서울의 온도가 섭씨 30도였으면 그 사실은 시간이 지나도 변하지 않습니다. 오늘 삼성전자의 주가가 5만원이었다는 사실이 내일 변하지 않습니다. 이러한 특성으로 인해 시계열 데이터는 변경되거나 삭제될 일이 거의 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;influxDB 소개&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.influxdata.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.influxdata.com/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1667296017146&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;InfluxDB: Open Source Time Series Database | InfluxData&quot; data-og-description=&quot;InfluxDB is the platform for building &amp;amp; operating time series applications. Monitor metrics &amp;amp; events, get real-time visibility into stacks, sensors &amp;amp; systems.&quot; data-og-host=&quot;www.influxdata.com&quot; data-og-source-url=&quot;https://www.influxdata.com/&quot; data-og-url=&quot;https://www.influxdata.com/home/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/lkFrR/hyQqYvUNrZ/ZghUrSvG8HsI0KpDakRkGk/img.png?width=1200&amp;amp;height=628&amp;amp;face=0_0_1200_628,https://scrap.kakaocdn.net/dn/N0vNr/hyQpqt4KPR/GKXRzxZsYHB8bjZvJmhdDk/img.png?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400&quot;&gt;&lt;a href=&quot;https://www.influxdata.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.influxdata.com/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/lkFrR/hyQqYvUNrZ/ZghUrSvG8HsI0KpDakRkGk/img.png?width=1200&amp;amp;height=628&amp;amp;face=0_0_1200_628,https://scrap.kakaocdn.net/dn/N0vNr/hyQpqt4KPR/GKXRzxZsYHB8bjZvJmhdDk/img.png?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;InfluxDB: Open Source Time Series Database | InfluxData&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;InfluxDB is the platform for building &amp;amp; operating time series applications. Monitor metrics &amp;amp; events, get real-time visibility into stacks, sensors &amp;amp; systems.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.influxdata.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;influxDB는 대표적인 TSDB중 하나입니다. 사용하기 쉽고 인기가 많으며 지속적으로 업데이트가 되고 있습니다. 다양한 플러그인과 툴을 제공하며 그 모든 것들에 대한 문서도 잘 정리되어있습니다. influxDB Cloud도 있기 때문에 클라우드를 사용할 수도 있습니다. 다만 현재 기준으로 클라우드 로케이션이 북미, 유럽, 호주에만 존재한다는 아쉬운 제약이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;influxDB는 내부적으로 Go언어로 만들었습니다. &lt;span&gt;오픈소스이고 설치와 사용이 편리합니다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;Http api와 각종 프로그래밍 언어에서 편리하게 사용할 수 있는 influx client를 제공합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모델링 면에서는 MongoDB처럼 스키마를 따로 정의하지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿼리 면에서는 sql과 비슷한 쿼리언어를 제공합니다. 또한 자동으로 집계 데이터를 계산하는 연속 쿼리는 자주 집계 쿼리를 사용하는 것보다 편리함을 줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Metrics와 Events를 수집하고 리포팅하는 Telegraf, Real-time 스트리밍 데이터 전송 엔진인 Kapacitor, 각종 시각화를 해주는 툴인 Chronograf 등 수많은 유용한 플러그인들을 제공하고 있으며 이에 대한 사용법이나 튜토리얼도 문서화가 잘되어있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;influxDB의 구조&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;관게형 데이터베이스(RDB)에 비유하여 influxDB의 구조를 설명합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 119px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;RDB&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;influxDB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;database&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;bucket&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;table&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;measurement&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;column&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;key&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;PK or indexed column&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;tag key&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;unindexed column&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;field key&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;record&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;point&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고로 bucket은 database와 데이터 보존 기간 정책인 &lt;span style=&quot;background-color: #ffffff; color: #131313;&quot;&gt;retention &lt;/span&gt;policy가 합쳐진 개념입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보존 기간이 지나면 데이터는 자동으로 삭제됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;&lt;b&gt;* 예시 - 한국의 기후&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시 데이터에서 입력된 값들은 제가 임의로 작성해본 값이며, 실제 데이터는 아닙니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;bucket - korea&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;measurement - climate(기후)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;tag key - region(지역)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;field key - temperature(온도), wind_direction(풍향), wind_speed(풍속), humidity(습도)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- point example&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style8&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 16.6667%;&quot;&gt;time&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%;&quot;&gt;region&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%;&quot;&gt;temperature&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%;&quot;&gt;wind_direction&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%;&quot;&gt;wind_speed&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%;&quot;&gt;humidity&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 16.6667%;&quot;&gt;&lt;span style=&quot;color: #555555;&quot;&gt;2022-11-11T12:00:00Z&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%;&quot;&gt;Seoul&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%;&quot;&gt;19&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%;&quot;&gt;45&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%;&quot;&gt;20&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%;&quot;&gt;10&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- measurement data exmaple&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 138px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 16.6667%; height: 17px;&quot;&gt;time&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; height: 17px;&quot;&gt;region&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; height: 17px;&quot;&gt;temperature&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; height: 17px;&quot;&gt;wind_direction&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; height: 17px;&quot;&gt;wind_speed&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; height: 17px;&quot;&gt;humidity&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 35px;&quot;&gt;
&lt;td style=&quot;width: 16.6667%; height: 35px;&quot;&gt;&lt;span style=&quot;color: #555555;&quot;&gt;2022-11-11T12:00:00Z&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; height: 35px;&quot;&gt;Seoul&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; height: 35px;&quot;&gt;19&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; height: 35px;&quot;&gt;45&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; height: 35px;&quot;&gt;20&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; height: 35px;&quot;&gt;10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 35px;&quot;&gt;
&lt;td style=&quot;width: 16.6667%; height: 35px;&quot;&gt;&lt;span style=&quot;color: #555555;&quot;&gt;&lt;span style=&quot;color: #555555;&quot;&gt;2022-11-11T12:00:00Z&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; height: 35px;&quot;&gt;Busan&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; height: 35px;&quot;&gt;20&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; height: 35px;&quot;&gt;70&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; height: 35px;&quot;&gt;34&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; height: 35px;&quot;&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 16.6667%; height: 17px;&quot;&gt;&lt;span style=&quot;color: #555555;&quot;&gt;&lt;span style=&quot;color: #555555;&quot;&gt;2022-11-11T12:01:00Z&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; height: 17px;&quot;&gt;Seoul&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; height: 17px;&quot;&gt;19&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; height: 17px;&quot;&gt;46&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; height: 17px;&quot;&gt;22&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; height: 17px;&quot;&gt;10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 16.6667%;&quot;&gt;&lt;span style=&quot;color: #555555;&quot;&gt;&lt;span style=&quot;color: #555555;&quot;&gt;&lt;span style=&quot;color: #555555;&quot;&gt;2022-11-11T12:01:00Z&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%;&quot;&gt;Busan&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%;&quot;&gt;21&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%;&quot;&gt;70&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%;&quot;&gt;31&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%;&quot;&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- region == Seoul로 필터 조회해서 뽑은 time series&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 16.6667%; height: 17px;&quot;&gt;time&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; height: 17px;&quot;&gt;temperature&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; height: 17px;&quot;&gt;wind_direction&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; height: 17px;&quot;&gt;wind_speed&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; height: 17px;&quot;&gt;humidity&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 35px;&quot;&gt;
&lt;td style=&quot;width: 16.6667%; height: 35px;&quot;&gt;&lt;span style=&quot;color: #555555;&quot;&gt;2022-11-11T12:00:00Z&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; height: 35px;&quot;&gt;19&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; height: 35px;&quot;&gt;45&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; height: 35px;&quot;&gt;20&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; height: 35px;&quot;&gt;10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 16.6667%; height: 17px;&quot;&gt;&lt;span style=&quot;color: #555555;&quot;&gt;&lt;span style=&quot;color: #555555;&quot;&gt;2022-11-11T12:01:00Z&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; height: 17px;&quot;&gt;19&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; height: 17px;&quot;&gt;46&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; height: 17px;&quot;&gt;22&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; height: 17px;&quot;&gt;10&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보통 이렇게 카테고라이징이 필요한 key를 tag key로 설정합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿼리문은 flux라는 언어로 작성하며 바로 위 예시의 쿼리문은&amp;nbsp;다음과 같습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1667299775846&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from(bucket:&quot;korea&quot;)
    |&amp;gt; range(start: 2022-11-11T12:00:00Z, stop: 2022-11-11T12:01:00Z)
    |&amp;gt; filter(fn:(r) =&amp;gt; r._measurement == &quot;climate&quot;)
    |&amp;gt; filter(fn:(r) =&amp;gt; r.region == &quot;Seoul&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;influxDB 설치&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 공식 홈페이지 링크는 mac os, linux, windows, docker, kubernetes, raspberry pi에서 설치법을 안내하고 있습니다. &lt;span style=&quot;color: #ee2323;&quot;&gt;(참고로 influxDB 버전이 바뀌면 링크 주소가 달라집니다. 시간이 지나면 링크가 안맞을테니 이점 양해 바랍니다. 앞으로 나올 다른 링크들도 마찬가지입니다.)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.influxdata.com/influxdb/v2.5/install/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://docs.influxdata.com/influxdb/v2.5/install/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1667300521982&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Install InfluxDB | InfluxDB OSS 2.4 Documentation&quot; data-og-description=&quot;Thank you for your feedback! Let us know what we can do better:&quot; data-og-host=&quot;docs.influxdata.com&quot; data-og-source-url=&quot;https://docs.influxdata.com/influxdb/v2.4/install/&quot; data-og-url=&quot;https://docs.influxdata.com/influxdb/v2.4/install/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://docs.influxdata.com/influxdb/v2.4/install/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.influxdata.com/influxdb/v2.4/install/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Install InfluxDB | InfluxDB OSS 2.4 Documentation&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Thank you for your feedback! Let us know what we can do better:&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.influxdata.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;윈도우 기준으로 설치 순서는 influxDB와 CLI를 다운로드 받아 설치하고, DB를 setup합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.influxdata.com/influxdb/v2.5/install/?t=CLI+Setup#set-up-influxdb&quot;&gt;https://docs.influxdata.com/influxdb/v2.5/install/?t=CLI+Setup#set-up-influxdb&lt;/a&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1667351554139&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Install InfluxDB | InfluxDB OSS 2.5 Documentation&quot; data-og-description=&quot;Thank you for your feedback! Let us know what we can do better:&quot; data-og-host=&quot;docs.influxdata.com&quot; data-og-source-url=&quot;https://docs.influxdata.com/influxdb/v2.5/install/?t=CLI+Setup#set-up-influxdb&quot; data-og-url=&quot;https://docs.influxdata.com/influxdb/v2.5/install/?t=CLI+Setup#set-up-influxdb&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://docs.influxdata.com/influxdb/v2.5/install/?t=CLI+Setup#set-up-influxdb&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.influxdata.com/influxdb/v2.5/install/?t=CLI+Setup#set-up-influxdb&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Install InfluxDB | InfluxDB OSS 2.5 Documentation&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Thank you for your feedback! Let us know what we can do better:&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.influxdata.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 가이드대로 따라하여 DB를 setup하면 config, org, user, bucket, token이 생성됩니다. 그러면 이제부터 influxDB에 본격적으로 데이터를 쓰고 조회하는 것이 가능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;데이터베이스 서버 실행법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;influxDB를 잘 설치했다면 윈도우 기준으로 &quot;C:\Program Files\InfluxData&quot; 경로에 설치가 되었을 것입니다. 터미널을 하나 실행시키고 &quot;C:\Program Files\InfluxData\influxdb&quot; 경로에 진입 후 다음 명령어를 입력하면 서버가 실행됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1667351262662&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;influxd&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버를 실행했다면 cli를 사용할 수 있습니다. 터미널 탭을 하나 더 열어서 &quot;C:\Program Files\InfluxData\influx&quot; 경로에 진입 후 cli를 사용할 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;cli 인터페이스 reference&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.influxdata.com/influxdb/v2.5/reference/cli/influx/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://docs.influxdata.com/influxdb/v2.5/reference/cli/influx/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1667351467417&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;influx - InfluxDB command line interface | InfluxDB OSS 2.5 Documentation&quot; data-og-description=&quot;Thank you for your feedback! Let us know what we can do better:&quot; data-og-host=&quot;docs.influxdata.com&quot; data-og-source-url=&quot;https://docs.influxdata.com/influxdb/v2.5/reference/cli/influx/&quot; data-og-url=&quot;https://docs.influxdata.com/influxdb/v2.5/reference/cli/influx/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://docs.influxdata.com/influxdb/v2.5/reference/cli/influx/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.influxdata.com/influxdb/v2.5/reference/cli/influx/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;influx - InfluxDB command line interface | InfluxDB OSS 2.5 Documentation&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Thank you for your feedback! Let us know what we can do better:&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.influxdata.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;보안&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;influxDB의 클라이언트와 서버는 http로 통신합니다. https를 사용하기 위해 TSL가 필요합니다. influxDB에 TSL를 사용하는 방법은 아래 링크에 가이드되어있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.influxdata.com/influxdb/v2.5/security/enable-tls/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://docs.influxdata.com/influxdb/v2.5/security/enable-tls/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1667352183837&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Enable TLS/SSL encryption | InfluxDB OSS 2.5 Documentation&quot; data-og-description=&quot;Thank you for your feedback! Let us know what we can do better:&quot; data-og-host=&quot;docs.influxdata.com&quot; data-og-source-url=&quot;https://docs.influxdata.com/influxdb/v2.5/security/enable-tls/&quot; data-og-url=&quot;https://docs.influxdata.com/influxdb/v2.5/security/enable-tls/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://docs.influxdata.com/influxdb/v2.5/security/enable-tls/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.influxdata.com/influxdb/v2.5/security/enable-tls/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Enable TLS/SSL encryption | InfluxDB OSS 2.5 Documentation&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Thank you for your feedback! Let us know what we can do better:&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.influxdata.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DB에 대한 접근 권한별 토큰 설정 방법은 여기에 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.influxdata.com/influxdb/v2.5/security/tokens/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://docs.influxdata.com/influxdb/v2.5/security/tokens/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1667362681260&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Manage API tokens in InfluxDB | InfluxDB OSS 2.5 Documentation&quot; data-og-description=&quot;Thank you for your feedback! Let us know what we can do better:&quot; data-og-host=&quot;docs.influxdata.com&quot; data-og-source-url=&quot;https://docs.influxdata.com/influxdb/v2.5/security/tokens/&quot; data-og-url=&quot;https://docs.influxdata.com/influxdb/v2.5/security/tokens/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://docs.influxdata.com/influxdb/v2.5/security/tokens/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.influxdata.com/influxdb/v2.5/security/tokens/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Manage API tokens in InfluxDB | InfluxDB OSS 2.5 Documentation&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Thank you for your feedback! Let us know what we can do better:&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.influxdata.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;GUI 모니터링 툴 사용하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;influxDB를 모니터링할 수 있는 툴은 웹으로 제공됩니다. localhost:8086에 접속하면 다음과 같이 생긴 웹페이지를 만날 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1821&quot; data-origin-height=&quot;864&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cTDRcP/btrP8JPogsf/CNcMeyz5h9XSi2EeSHlwm1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cTDRcP/btrP8JPogsf/CNcMeyz5h9XSi2EeSHlwm1/img.png&quot; data-alt=&quot;influxDB GUI&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cTDRcP/btrP8JPogsf/CNcMeyz5h9XSi2EeSHlwm1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcTDRcP%2FbtrP8JPogsf%2FCNcMeyz5h9XSi2EeSHlwm1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1821&quot; height=&quot;864&quot; data-origin-width=&quot;1821&quot; data-origin-height=&quot;864&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;influxDB GUI&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서는 대시보드, 쿼리, 데이터 시각화, 태스크, 알림 등 다양한 기능들을 GUI로 다룰 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;714&quot; data-origin-height=&quot;269&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pstaU/btrP9jibXp8/eM1R5BkS0wBgnAzlXTA4o1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pstaU/btrP9jibXp8/eM1R5BkS0wBgnAzlXTA4o1/img.png&quot; data-alt=&quot;데이터 시각화 예시 이미지&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pstaU/btrP9jibXp8/eM1R5BkS0wBgnAzlXTA4o1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpstaU%2FbtrP9jibXp8%2FeM1R5BkS0wBgnAzlXTA4o1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;714&quot; height=&quot;269&quot; data-origin-width=&quot;714&quot; data-origin-height=&quot;269&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;데이터 시각화 예시 이미지&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Shard와 Shard group&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아까 influxDB의 구조를 RDB에 비유하여 설명할 때, bucket을 database에 보존 기간 정책(&lt;span style=&quot;background-color: #ffffff; color: #131313;&quot;&gt;retention&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;policy)이 합쳐진 개념이라고 했는데, 이 보존 기간 정책은 shard group duration과 연결됩니다. 즉, bucket을 생성하면 shard group duration도 설정됩니다. 또한 retention policy를 변경하면 shard group duration도 변경됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;664&quot; data-origin-height=&quot;301&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bI0l4x/btrP8K1Rmng/SMaZVfy8r8vk9FWubHKnK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bI0l4x/btrP8K1Rmng/SMaZVfy8r8vk9FWubHKnK0/img.png&quot; data-alt=&quot;retention policy와 shard group duration는 연결되어있다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bI0l4x/btrP8K1Rmng/SMaZVfy8r8vk9FWubHKnK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbI0l4x%2FbtrP8K1Rmng%2FSMaZVfy8r8vk9FWubHKnK0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;507&quot; height=&quot;230&quot; data-origin-width=&quot;664&quot; data-origin-height=&quot;301&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;retention policy와 shard group duration는 연결되어있다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;shard group은 shard를 담고 있으며 shard는 여러 개의 series를 담고 있습니다. 시간이 shard group duration만큼 지나면 shard group은 메모리에서 디스크로 옮겨집니다. 이런 방식으로 시간이 지나도 메모리에 적재되는 데이터의 양을 제한할 수 있으며 처리량을 통제할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;retention policy를 설정하는 방법은 아래 링크에 가이드되어있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.influxdata.com/influxdb/v2.5/organizations/buckets/create-bucket/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://docs.influxdata.com/influxdb/v2.5/organizations/buckets/create-bucket/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1667354300911&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Create a bucket in InfluxDB | InfluxDB OSS 2.5 Documentation&quot; data-og-description=&quot;Thank you for your feedback! Let us know what we can do better:&quot; data-og-host=&quot;docs.influxdata.com&quot; data-og-source-url=&quot;https://docs.influxdata.com/influxdb/v2.5/organizations/buckets/create-bucket/&quot; data-og-url=&quot;https://docs.influxdata.com/influxdb/v2.5/organizations/buckets/create-bucket/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://docs.influxdata.com/influxdb/v2.5/organizations/buckets/create-bucket/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.influxdata.com/influxdb/v2.5/organizations/buckets/create-bucket/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Create a bucket in InfluxDB | InfluxDB OSS 2.5 Documentation&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Thank you for your feedback! Let us know what we can do better:&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.influxdata.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Database</category>
      <category>influxdb</category>
      <category>TSDB</category>
      <author>cocoder16</author>
      <guid isPermaLink="true">https://cocoder16.tistory.com/83</guid>
      <comments>https://cocoder16.tistory.com/83#entry83comment</comments>
      <pubDate>Mon, 7 Nov 2022 08:08:12 +0900</pubDate>
    </item>
    <item>
      <title>[React] formik으로 폼(form) 만들기</title>
      <link>https://cocoder16.tistory.com/82</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;formik 소개&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;formik은 리액트에서 form을 다루는 코드들을 쉽게 작성할 수 있도록 도와주는 라이브러리입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;formik은 리액트에서 form을 다룰 때 3가지 까다로운(성가신) 점들을 해결해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;1. form의 state 관리 및 form의 안팎에서 value 접근&lt;br /&gt;2. 벨리데이션(&lt;span&gt;Validation&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;과 에러 메시지&lt;br /&gt;3. submit 다루기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;formik을 사용하면 form에서 사용하는 값들을 formik 내부에서 자체적으로 관리합니다. 고로 state를 따로 선언하고 관리할 필요가 없어집니다. 상태 값들을 formik 내부에서 관리하기 때문에 이 값을 얻기 위해서는 formik이 제공하는 api들을 이용해 함수를 호출하면 됩니다.&lt;br /&gt;form과 관련하여 내부에서 관리하는 상태들로는 각 input field에 들어있는 사용자가 입력하는 value들, UI와 사용자의 접촉여부, 밸리데이션&amp;nbsp;통과 여부,&amp;nbsp;submit&amp;nbsp;진행상태&amp;nbsp;등이&amp;nbsp;있습니다. &lt;br /&gt;또한,&amp;nbsp;값을&amp;nbsp;입력해주는&amp;nbsp;setValues,&amp;nbsp;밸리데이션을&amp;nbsp;해주는&amp;nbsp;validateForm,&amp;nbsp;폼에&amp;nbsp;들어있는&amp;nbsp;값들을&amp;nbsp;초기화해주는&amp;nbsp;resetForm&amp;nbsp;등 &lt;br /&gt;편리한 함수들을 제공합니다. 뭔가 다양한 것들을 제공하는 것은 알겠는데 역시 예제를 봐야겠습니다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;재사용이 가능한 form UI component 만들기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 도메인 지식이 관여하지 않는 순수 UI 컴포넌트를 만드려고합니다. 이렇게&amp;nbsp;만들면&amp;nbsp;어떤&amp;nbsp;도메인에서든&amp;nbsp;이&amp;nbsp;form&amp;nbsp;컴포넌트를&amp;nbsp;재사용할&amp;nbsp;수&amp;nbsp;있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 리액트 프로젝트를 준비하고 formik을 설치합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1664701606053&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npx create-react-app formik-ex
cd formik-ex

yarn add formik&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 form 컴포넌트를 만듭니다.&lt;br /&gt;&lt;br /&gt;* src/components/form/CustomForm.jsx&lt;/p&gt;
&lt;pre id=&quot;code_1664698672281&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { Formik, Form } from &quot;formik&quot;;

import Spinner from &quot;../Spinner&quot;;

function CustomForm({
  initialValues,
  validationSchema,
  handleSubmit,
  children,
  style,
}) {
  const onSubmit = (values, actions) =&amp;gt; {
    handleSubmit(values, actions)
      .then(() =&amp;gt; {
        actions.resetForm({
          values: initialValues,
        });
      })
      .finally(() =&amp;gt; {
        actions.setSubmitting(false);
      });
  };

  return (
    &amp;lt;Formik
      initialValues={initialValues}
      validationSchema={validationSchema}
      onSubmit={onSubmit}
    &amp;gt;
      {({ isSubmitting }) =&amp;gt; {
        return (
          &amp;lt;Form style={style}&amp;gt;
            {isSubmitting &amp;amp;&amp;amp; &amp;lt;Spinner /&amp;gt;}
            {children}
          &amp;lt;/Form&amp;gt;
        );
      }}
    &amp;lt;/Formik&amp;gt;
  );
}

export default CustomForm;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;initialValues는 form을 처음 렌더링할 때 미리 입력되어있는 초기값입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;validationSchema는 벨리데이션을 할 때 통과해야 하는 포맷을 정의해놓은 객체입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;onSubmit은 form에서 submit 이벤트가 발생할 때 formik에서 실행할 함수입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;submit handler는 props로 받도록 하고, onSubmit안에서 그것을 실행하도록 하였습니다. 또한 submit성공 시 form안에 있는 값들을 초기화시키고, submit 진행상태를 나타내는 submitting을 false로 변경하도록 하였습니다. submit이 발생하면 submitting이 true로 변경되는데 그것을 false로 되돌리는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 이것을 응답 대기중인 상태를 화면에 표시하기 위해 사용하였습니다. 코드에서 렌더링 하는 부분을 보면 Form안에 isSubmitting일 때 Spinner를 렌더링 하도록 하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spinner는 react-loader-spinner를 사용하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1664699200608&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;yarn add react-loader-spinner&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* src/components/Spinner.jsx&lt;/p&gt;
&lt;pre id=&quot;code_1664699479815&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { Oval } from  'react-loader-spinner'

function Spinner() {
  return (
    &amp;lt;Oval
      height={80}
      width={80}
      color=&quot;#4fa94d&quot;
      wrapperStyle={{}}
      wrapperClass=&quot;&quot;
      visible={true}
      ariaLabel='oval-loading'
      secondaryColor=&quot;#4fa94d&quot;
      strokeWidth={2}
      strokeWidthSecondary={2}
    /&amp;gt;
  );
}

export default Spinner;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로 text input field를 만들겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* src/components/form/TextField.jsx&lt;/p&gt;
&lt;pre id=&quot;code_1664699876614&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useField, useFormikContext } from &quot;formik&quot;;

function TextField(props) {
  const [field, meta] = useField(props);
  const { isSubmitting } = useFormikContext();

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;input
        type=&quot;text&quot;
        {...field}
        {...props}
        onChange={event =&amp;gt; {
          field.onChange(event);
        }}
        disabled={isSubmitting}
      /&amp;gt;
      {meta.touched &amp;amp;&amp;amp; meta.error &amp;amp;&amp;amp; &amp;lt;div className=&quot;error&quot;&amp;gt;{meta.error}&amp;lt;/div&amp;gt;}
    &amp;lt;/div&amp;gt;
  );
}

export default TextField;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;useField hook으로 받은 field를 input 태그에 넘겨서 input태그를 formik의 field로서 기능할 수 있도록 만듭니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 props들도 넘겨줘서 해당 컴포넌트에 필요한 props들을 다 적용받을 수 있게 자유도를 줬습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;onChange에서는 field.onChange에 event를 넘겨 formik에서 필요한 로직을 실행하도록 하였고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;isSubmitting 상태일 때에는 disabled로 만들어 인풋을 제어할 수 없도록 하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;meta.touched는 인풋을 사용자가 한번이라도 만진 적이 있는지의 여부를 불리언 값으로 나타냅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;meta.error는 벨리데이션에 통과하지 못한 상태를 불리언 값으로 나타냅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 사용자가 만진 적이 있는 인풋인데 벨리데이션을 통과하지 못하면 에러 메시지를 표시하도록 하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;formik으로 만든 공통 컴포넌트들을 import 하기 쉽도록 한곳에 모아 관리하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* src/components/form/index.jsx&lt;/p&gt;
&lt;pre id=&quot;code_1664703194072&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import CustomForm from &quot;./CustomForm&quot;;
import TextField from &quot;./TextField&quot;;

export { CustomForm, TextField };&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;이제&amp;nbsp;지금까지&amp;nbsp;만든&amp;nbsp;컴포넌트들을&amp;nbsp;어떻게&amp;nbsp;재사용할&amp;nbsp;수&amp;nbsp;있는지&amp;nbsp;확인하기&amp;nbsp;위해&amp;nbsp;다음&amp;nbsp;스탭으로&amp;nbsp;넘어가겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;회원가입 폼 만들기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구체적인 도메인 지식이 있는 요구사항이 들어간 폼을 만들겠습니다. 회원가입 폼, 로그인 폼, 상품 구매 폼, 게시글 작성 폼 등등 수없이 많은 구체적으로 정의할 수 있는 요구사항들이 있습니다. 이것들은 모두 앞서 만든 재사용 가능한 컴포넌트들을 불러와 쉽게 구현할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금은&amp;nbsp;회원가입&amp;nbsp;폼을&amp;nbsp;예제로&amp;nbsp;같이&amp;nbsp;만들어보려고&amp;nbsp;합니다. &lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;요구사항 정의&lt;/b&gt; &lt;br /&gt;&lt;br /&gt;이메일: 필수값, 이메일 형식 &lt;br /&gt;비밀번호: 필수값, 8자 이상, 영문/숫자만 가능&lt;br /&gt;비밀번호 확인: 필수값, 비밀번호 일치 여부 판단&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;벨리데이션 스키마 만들기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;yup 소개&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;yup은 리액트에서만 사용가능한 라이브러리는 아니고 자바스크립트를 이용하는 라이브러리입니다. &lt;br /&gt;밸리데이션을 위해 거쳐야하는 테스트들과 통과 실패 시 보여줄 에러 메시지를 스키마로 정의할 수 있습니다. &lt;br /&gt;빌더 패턴을 사용하기 때문에 아주 가독성 좋은 간결한 코드를 쓸 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;yup을 설치합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1664702665238&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;yarn add yup&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;yup을 이용해 만든 위 요구사항이 반영된 벨리데이션 스키마 코드는 다음과 같습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1664700941139&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const validationSchema = Yup.object({
    email: Yup.string()
      .required(&quot;이메일을 입력해주세요.&quot;)
      .matches(/^[0-9a-zA-Z]([-_.]?[0-9a-zA-Z])*@[0-9a-zA-Z]([-_.]?[0-9a-zA-Z])*.[a-zA-Z]{2,3}$/i, &quot;올바른 이메일 형식이 아닙니다.&quot;),
    password: Yup.string()
      .required(&quot;비밀번호를 입력해주세요.&quot;)
      .matches(/^[0-9a-zA-Z]{8,}$/, &quot;비밀번호는 영문/숫자만 가능, 8자 이상입니다.&quot;),
    passwordVerified: Yup.string()
      .required(&quot;비밀번호를 확인해주세요.&quot;)
      .oneOf([Yup.ref(&quot;password&quot;)], &quot;비밀번호가 일치하지 않습니다.&quot;),
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;회원가입 폼 컴포넌트 만들기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;지금까지 만든 것들을 이용해 회원가입 폼 컴포넌트를 만들어봅니다.&lt;br /&gt;&lt;br /&gt;* src/signUp/Form.jsx&lt;/p&gt;
&lt;pre id=&quot;code_1664704681558&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useMemo } from &quot;react&quot;;
import * as Yup from &quot;yup&quot;;

import { CustomForm, TextField } from &quot;../components/form&quot;;

function Form() {
  const initialValues = useMemo(() =&amp;gt; {
    return {
      email: &quot;&quot;,
      password: &quot;&quot;,
      passwordVerified: &quot;&quot;,
    };
  }, []);
  const validationSchema = Yup.object({
    email: Yup.string()
      .required(&quot;이메일을 입력해주세요.&quot;)
      .matches(
        /^[0-9a-zA-Z]([-_.]?[0-9a-zA-Z])*@[0-9a-zA-Z]([-_.]?[0-9a-zA-Z])*.[a-zA-Z]{2,3}$/i,
        &quot;올바른 이메일 형식이 아닙니다.&quot;
      ),
    password: Yup.string()
      .required(&quot;비밀번호를 입력해주세요.&quot;)
      .matches(
        /^[0-9a-zA-Z]{8,}$/,
        &quot;비밀번호는 영문/숫자만 가능, 8자 이상입니다.&quot;
      ),
    passwordVerified: Yup.string()
      .required(&quot;비밀번호를 확인해주세요.&quot;)
      .oneOf([Yup.ref(&quot;password&quot;)], &quot;비밀번호가 일치하지 않습니다.&quot;),
  });

  const handleSubmit = async (values, actions) =&amp;gt; {
    alert(&quot;회원가입 submit 완료&quot;);
  };

  return (
    &amp;lt;CustomForm
      initialValues={initialValues}
      validationSchema={validationSchema}
      handleSubmit={handleSubmit}
      style={{ display: &quot;flex&quot;, flexDirection: &quot;column&quot; }}
    &amp;gt;
      &amp;lt;h2&amp;gt;회원가입&amp;lt;/h2&amp;gt;
      &amp;lt;div&amp;gt;
        &amp;lt;label&amp;gt;
          이메일:
          &amp;lt;TextField type=&quot;email&quot; name=&quot;email&quot; label=&quot;이메일&quot; /&amp;gt;
        &amp;lt;/label&amp;gt;
      &amp;lt;/div&amp;gt;
      &amp;lt;div&amp;gt;
        &amp;lt;label&amp;gt;
          비밀번호:
          &amp;lt;TextField type=&quot;password&quot; name=&quot;password&quot; label=&quot;비밀번호&quot; /&amp;gt;
        &amp;lt;/label&amp;gt;
      &amp;lt;/div&amp;gt;
      &amp;lt;div&amp;gt;
        &amp;lt;label&amp;gt;
          비밀번호 확인:
          &amp;lt;TextField
            type=&quot;password&quot;
            name=&quot;passwordVerified&quot;
            label=&quot;비밀번호 확인&quot;
          /&amp;gt;
        &amp;lt;/label&amp;gt;
      &amp;lt;/div&amp;gt;
      &amp;lt;button type=&quot;submit&quot; style={{ width: 50 }}&amp;gt;
        계속
      &amp;lt;/button&amp;gt;
    &amp;lt;/CustomForm&amp;gt;
  );
}

export default Form;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것과 똑같은 방식으로 재사용 가능한 컴포넌트들을 이용하여 로그인 폼, 게시글 작성 폼 등등 만들고 싶은 폼들을 만들 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 App.js에서 회원가입폼을 렌더링 하도록 만듭니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* App.js&lt;/p&gt;
&lt;pre id=&quot;code_1664705467863&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import SignUpForm from &quot;./signUp/Form&quot;;

function App() {
  return (
    &amp;lt;div className=&quot;App&quot;&amp;gt;
      &amp;lt;SignUpForm /&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}

export default App;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div class=&quot;revenue_unit_item adsense  responsive&quot;&gt;&lt;ins class=&quot;adsbygoogle&quot; style=&quot;display: block;&quot; data-ad-host=&quot;ca-host-pub-9691043933427338&quot; data-ad-client=&quot;ca-pub-9807016842906892&quot; data-ad-format=&quot;auto&quot;&gt;&lt;/ins&gt;
&lt;script&gt;(adsbygoogle = window.adsbygoogle || []).push({}); &lt;/script&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;동작여부 확인해보기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발서버를 띄워서 잘 작동하는지 직접 테스트해보겠습니다. &lt;br /&gt;터미널에서 yarn start를 입력하여 개발서버를 실행합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;localhost:3000에&amp;nbsp;접속했을&amp;nbsp;때,&amp;nbsp;우리가&amp;nbsp;만든&amp;nbsp;화면이&amp;nbsp;정상적으로&amp;nbsp;보여야&amp;nbsp;합니다. &lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;311&quot; data-origin-height=&quot;284&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bYBJBr/btrNv9iUyn5/MivQpMMLm1GkI4KJfm8431/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bYBJBr/btrNv9iUyn5/MivQpMMLm1GkI4KJfm8431/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bYBJBr/btrNv9iUyn5/MivQpMMLm1GkI4KJfm8431/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbYBJBr%2FbtrNv9iUyn5%2FMivQpMMLm1GkI4KJfm8431%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;283&quot; height=&quot;258&quot; data-origin-width=&quot;311&quot; data-origin-height=&quot;284&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이메일 인풋을 한번 클릭한 다음, 바깥 여백 영역을 클릭해봅니다. 그러면 벨리데이션이 작동하여 이메일 인풋에만 다음과 같이 에러 메시지가 등장합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;214&quot; data-origin-height=&quot;274&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/y5n5P/btrNvZtLKIp/U4gU3x2Z4H4pg3zO5fkXk1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/y5n5P/btrNvZtLKIp/U4gU3x2Z4H4pg3zO5fkXk1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/y5n5P/btrNvZtLKIp/U4gU3x2Z4H4pg3zO5fkXk1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fy5n5P%2FbtrNvZtLKIp%2FU4gU3x2Z4H4pg3zO5fkXk1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;152&quot; height=&quot;195&quot; data-origin-width=&quot;214&quot; data-origin-height=&quot;274&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;등록&amp;nbsp;버튼을&amp;nbsp;누르면&amp;nbsp;벨리데이션&amp;nbsp;결과에&amp;nbsp;따른&amp;nbsp;에러 메시지가&amp;nbsp;등장합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;236&quot; data-origin-height=&quot;302&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kPt5p/btrNDV3248K/mi2MGxaKaNxEclHnteUrak/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kPt5p/btrNDV3248K/mi2MGxaKaNxEclHnteUrak/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kPt5p/btrNDV3248K/mi2MGxaKaNxEclHnteUrak/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkPt5p%2FbtrNDV3248K%2Fmi2MGxaKaNxEclHnteUrak%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;182&quot; height=&quot;233&quot; data-origin-width=&quot;236&quot; data-origin-height=&quot;302&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;잘못된 이메일 형식과 비밀번호를 입력하면 상황에 맞는 다른 에러 메시지가 나옵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;355&quot; data-origin-height=&quot;320&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bhh1gT/btrNAwXZKzB/PfPItrsDwmM8fAGMBHWnjk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bhh1gT/btrNAwXZKzB/PfPItrsDwmM8fAGMBHWnjk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bhh1gT/btrNAwXZKzB/PfPItrsDwmM8fAGMBHWnjk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbhh1gT%2FbtrNAwXZKzB%2FPfPItrsDwmM8fAGMBHWnjk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;258&quot; height=&quot;233&quot; data-origin-width=&quot;355&quot; data-origin-height=&quot;320&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;이번엔&amp;nbsp;올바른&amp;nbsp;형식으로&amp;nbsp;입력하고&amp;nbsp;등록&amp;nbsp;버튼을&amp;nbsp;누릅니다.&amp;nbsp;그러면&amp;nbsp;성공했다는&amp;nbsp;알랏이&amp;nbsp;뜹니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1289&quot; data-origin-height=&quot;285&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/deaGNp/btrNwnH1lv9/fZTFpxvUO2IYoMuPWEqvt0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/deaGNp/btrNwnH1lv9/fZTFpxvUO2IYoMuPWEqvt0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/deaGNp/btrNwnH1lv9/fZTFpxvUO2IYoMuPWEqvt0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdeaGNp%2FbtrNwnH1lv9%2FfZTFpxvUO2IYoMuPWEqvt0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1289&quot; height=&quot;285&quot; data-origin-width=&quot;1289&quot; data-origin-height=&quot;285&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Advance&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지&amp;nbsp;만든&amp;nbsp;폼에&amp;nbsp;더&amp;nbsp;예쁜&amp;nbsp;디자인을&amp;nbsp;적용하기&amp;nbsp;위해&amp;nbsp;사용해볼만한&amp;nbsp;UI&amp;nbsp;라이브러리들이&amp;nbsp;있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;b&gt;Material UI&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;material&amp;nbsp;design을&amp;nbsp;적용한&amp;nbsp;리액트&amp;nbsp;UI&amp;nbsp;라이브러리입니다. &lt;br /&gt;주로&amp;nbsp;input,&amp;nbsp;button&amp;nbsp;등을&amp;nbsp;디자인하는데&amp;nbsp;매우&amp;nbsp;유용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;933&quot; data-origin-height=&quot;136&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/2WSgj/btrNEwJTm8L/KeRjJIadEfE8etqjBch2c0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2WSgj/btrNEwJTm8L/KeRjJIadEfE8etqjBch2c0/img.png&quot; data-alt=&quot;MUI Text field&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2WSgj/btrNEwJTm8L/KeRjJIadEfE8etqjBch2c0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2WSgj%2FbtrNEwJTm8L%2FKeRjJIadEfE8etqjBch2c0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;578&quot; height=&quot;84&quot; data-origin-width=&quot;933&quot; data-origin-height=&quot;136&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;MUI Text field&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;297&quot; data-origin-height=&quot;69&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Z5YEh/btrNwkEr6dv/qDEtAtxzOPJZzD7rYU6D80/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Z5YEh/btrNwkEr6dv/qDEtAtxzOPJZzD7rYU6D80/img.png&quot; data-alt=&quot;MUI Button&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Z5YEh/btrNwkEr6dv/qDEtAtxzOPJZzD7rYU6D80/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZ5YEh%2FbtrNwkEr6dv%2FqDEtAtxzOPJZzD7rYU6D80%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;297&quot; height=&quot;69&quot; data-origin-width=&quot;297&quot; data-origin-height=&quot;69&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;MUI Button&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://cocoder16.tistory.com/81&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://cocoder16.tistory.com/81&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1667365977684&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;리액트 UI 디자인 고민 Material-UI로 해결하기&quot; data-og-description=&quot;디자인 고민을 덜어낼 수 있는 material design 소개 Material design은 구글이 개발한 디자인 언어입니다. 그냥 디자인이 아니라 디자인 언어라고 소개하는 점이 흥미롭습니다. 구글이 Material design을 만&quot; data-og-host=&quot;cocoder16.tistory.com&quot; data-og-source-url=&quot;https://cocoder16.tistory.com/81&quot; data-og-url=&quot;https://cocoder16.tistory.com/81&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dxhb8r/hyQqSwbsLh/ZdYDu3gB2mUeOkCXQZNhfk/img.png?width=424&amp;amp;height=81&amp;amp;face=0_0_424_81,https://scrap.kakaocdn.net/dn/bWPxC0/hyQqJ0iYqi/XNKOxE0GKABSPLECxNaVu0/img.png?width=424&amp;amp;height=81&amp;amp;face=0_0_424_81,https://scrap.kakaocdn.net/dn/bMcNiZ/hyQqMQh4th/zUceTGkBavT4HgkZLh6WwK/img.png?width=1721&amp;amp;height=845&amp;amp;face=0_0_1721_845&quot;&gt;&lt;a href=&quot;https://cocoder16.tistory.com/81&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://cocoder16.tistory.com/81&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dxhb8r/hyQqSwbsLh/ZdYDu3gB2mUeOkCXQZNhfk/img.png?width=424&amp;amp;height=81&amp;amp;face=0_0_424_81,https://scrap.kakaocdn.net/dn/bWPxC0/hyQqJ0iYqi/XNKOxE0GKABSPLECxNaVu0/img.png?width=424&amp;amp;height=81&amp;amp;face=0_0_424_81,https://scrap.kakaocdn.net/dn/bMcNiZ/hyQqMQh4th/zUceTGkBavT4HgkZLh6WwK/img.png?width=1721&amp;amp;height=845&amp;amp;face=0_0_1721_845');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;리액트 UI 디자인 고민 Material-UI로 해결하기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;디자인 고민을 덜어낼 수 있는 material design 소개 Material design은 구글이 개발한 디자인 언어입니다. 그냥 디자인이 아니라 디자인 언어라고 소개하는 점이 흥미롭습니다. 구글이 Material design을 만&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;cocoder16.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://mui.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://mui.com/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1664705812409&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;MUI: The React component library you always wanted&quot; data-og-description=&quot;MUI provides a simple, customizable, and accessible library of React components. Follow your own design system, or start with Material Design.&quot; data-og-host=&quot;mui.com&quot; data-og-source-url=&quot;https://mui.com/&quot; data-og-url=&quot;https://mui.com/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bi0ni9/hyPY7IeITk/63XHGLm7tuGjk0UZxyxyzk/img.jpg?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/dii7pC/hyP0FXDGnm/2fdjYlgeN88HnMUjExpGuk/img.jpg?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640&quot;&gt;&lt;a href=&quot;https://mui.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://mui.com/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bi0ni9/hyPY7IeITk/63XHGLm7tuGjk0UZxyxyzk/img.jpg?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/dii7pC/hyP0FXDGnm/2fdjYlgeN88HnMUjExpGuk/img.jpg?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;MUI: The React component library you always wanted&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;MUI provides a simple, customizable, and accessible library of React components. Follow your own design system, or start with Material Design.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;mui.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;sweetalert2&lt;/b&gt; &lt;br /&gt;&lt;br /&gt;submit&amp;nbsp;이후&amp;nbsp;받은&amp;nbsp;응답에&amp;nbsp;따라&amp;nbsp;띄워줄&amp;nbsp;수&amp;nbsp;있는&amp;nbsp;alert을&amp;nbsp;예쁘게&amp;nbsp;꾸며줄&amp;nbsp;수&amp;nbsp;있는&amp;nbsp;라이브러리입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;767&quot; data-origin-height=&quot;557&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rBIaT/btrNwkLdLf3/JwIbG3sq7xxYXQJSOr4PY0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rBIaT/btrNwkLdLf3/JwIbG3sq7xxYXQJSOr4PY0/img.png&quot; data-alt=&quot;sweetalert example&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rBIaT/btrNwkLdLf3/JwIbG3sq7xxYXQJSOr4PY0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrBIaT%2FbtrNwkLdLf3%2FJwIbG3sq7xxYXQJSOr4PY0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;443&quot; height=&quot;322&quot; data-origin-width=&quot;767&quot; data-origin-height=&quot;557&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;sweetalert example&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://sweetalert2.github.io/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://sweetalert2.github.io/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1664705903480&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;SweetAlert2&quot; data-og-description=&quot;A beautiful, responsive, customizable and accessible (WAI-ARIA) replacement for JavaScript's popup boxes&quot; data-og-host=&quot;sweetalert2.github.io&quot; data-og-source-url=&quot;https://sweetalert2.github.io/&quot; data-og-url=&quot;https://sweetalert2.github.io/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/clb93A/hyPYZXIt6G/6CVCDBijuOWwVOnjKY3Kpk/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/uP15C/hyPYWmpGyG/67WDMuZT45mgTVa4oJ1NCK/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/cByHbV/hyPY8mPsry/LXK5T6jv1Jmn9uq5kupKKk/img.png?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400&quot;&gt;&lt;a href=&quot;https://sweetalert2.github.io/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://sweetalert2.github.io/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/clb93A/hyPYZXIt6G/6CVCDBijuOWwVOnjKY3Kpk/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/uP15C/hyPYWmpGyG/67WDMuZT45mgTVa4oJ1NCK/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640,https://scrap.kakaocdn.net/dn/cByHbV/hyPY8mPsry/LXK5T6jv1Jmn9uq5kupKKk/img.png?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;SweetAlert2&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;A beautiful, responsive, customizable and accessible (WAI-ARIA) replacement for JavaScript's popup boxes&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;sweetalert2.github.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;axios&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 포스팅 예제에서는 form submit할 때 async 함수를 호출해서 프로미스를 리턴하는 것으로 끝냈지만, 대부분 실제 애플리케이션에서는 서버에 데이터를 전송하는 형태입니다. react에서 form 제출 시 서버와 통신을 하기 위해서 ajax를 사용합니다. axios는 ajax를 쉽게 사용할 수 있는 라이브러리라서 소개합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/axios/axios&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/axios/axios&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1664706008008&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - axios/axios: Promise based HTTP client for the browser and node.js&quot; data-og-description=&quot;Promise based HTTP client for the browser and node.js - GitHub - axios/axios: Promise based HTTP client for the browser and node.js&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/axios/axios&quot; data-og-url=&quot;https://github.com/axios/axios&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/HKS2E/hyPZbDRJRk/dE2nkFU0TKdI498ntfnBdk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/axios/axios&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/axios/axios&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/HKS2E/hyPZbDRJRk/dE2nkFU0TKdI498ntfnBdk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - axios/axios: Promise based HTTP client for the browser and node.js&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Promise based HTTP client for the browser and node.js - GitHub - axios/axios: Promise based HTTP client for the browser and node.js&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>React.js</category>
      <category>form</category>
      <category>formik</category>
      <author>cocoder16</author>
      <guid isPermaLink="true">https://cocoder16.tistory.com/82</guid>
      <comments>https://cocoder16.tistory.com/82#entry82comment</comments>
      <pubDate>Mon, 17 Oct 2022 08:08:22 +0900</pubDate>
    </item>
    <item>
      <title>리액트 UI 디자인 고민 Material-UI로 해결하기</title>
      <link>https://cocoder16.tistory.com/81</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;디자인 고민을 덜어낼 수 있는 material design 소개&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Material design&lt;/b&gt;은 구글이 개발한 &lt;b&gt;&lt;i&gt;디자인 언어&lt;/i&gt;&lt;/b&gt;입니다. 그냥 디자인이 아니라 디자인 언어라고 소개하는 점이 흥미롭습니다. 구글이 Material design을 만든 목적은 디자인의 원칙과 기술적, 과학적 혁신이 결합된 새로운 시각적 언어를 만들기 위함이라고 합니다. 그리고 우리가 보통 의사소통을 위해 사용하는 한국어, 영어 등과 마찬가지로 오픈소스로 공개하여 누구든지 사용 가능하도록 하고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 시각적 언어는 구체적으로 유저 인터페이스(UI) 디자인의 최상의 결과물을 지원하는 가이드라인, 컴포넌트, 도구들의 시스템을 가지고 있습니다. 오픈 소스로 공개되어 있기 때문에 진짜 사람들이 사용하는 언어처럼 디자이너와 개발자 사이의 협업을 간소화하는 것을 돕습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;react에서 material desgin을 사용할 수 있는 MUI&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Material-UI 혹은 줄여서 MUI라고 부르는 것은 react에서 material design을 사용할 수 있는 UI 라이브러리입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 라이브러리는 material design이 적용된 UI 컴포넌트들, 그리고 그 컴포넌트를 제어할 수 있는 API, 그리고 커스터마이징 옵션들을 제공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MUI에서 제공하는 수많은 UI 중에 일부를 이미지로 구경해본 후 사용법을 알아보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;424&quot; data-origin-height=&quot;81&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/9lRWr/btrNCgHaUtJ/SNp0MXpWZ7jMyjReTA84B0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9lRWr/btrNCgHaUtJ/SNp0MXpWZ7jMyjReTA84B0/img.png&quot; data-alt=&quot;MUI button&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9lRWr/btrNCgHaUtJ/SNp0MXpWZ7jMyjReTA84B0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9lRWr%2FbtrNCgHaUtJ%2FSNp0MXpWZ7jMyjReTA84B0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;345&quot; height=&quot;66&quot; data-origin-width=&quot;424&quot; data-origin-height=&quot;81&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;MUI button&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;324&quot; data-origin-height=&quot;72&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pDXc7/btrNDVv4rlR/f1EiOE6yBtV5ocBWK9NbEk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pDXc7/btrNDVv4rlR/f1EiOE6yBtV5ocBWK9NbEk/img.png&quot; data-alt=&quot;MUI button with icon&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pDXc7/btrNDVv4rlR/f1EiOE6yBtV5ocBWK9NbEk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpDXc7%2FbtrNDVv4rlR%2Ff1EiOE6yBtV5ocBWK9NbEk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;266&quot; height=&quot;59&quot; data-origin-width=&quot;324&quot; data-origin-height=&quot;72&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;MUI button with icon&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;177&quot; data-origin-height=&quot;181&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zBFO0/btrNCg8glMq/KX6cE6KOjlrADvDXe0hHK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zBFO0/btrNCg8glMq/KX6cE6KOjlrADvDXe0hHK0/img.png&quot; data-alt=&quot;checkbox&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zBFO0/btrNCg8glMq/KX6cE6KOjlrADvDXe0hHK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzBFO0%2FbtrNCg8glMq%2FKX6cE6KOjlrADvDXe0hHK0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;143&quot; height=&quot;146&quot; data-origin-width=&quot;177&quot; data-origin-height=&quot;181&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;checkbox&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;488&quot; data-origin-height=&quot;108&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bubUmd/btrNDUqoSYH/7JLXqej5DwCZKqUhPAL7h1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bubUmd/btrNDUqoSYH/7JLXqej5DwCZKqUhPAL7h1/img.png&quot; data-alt=&quot;floating action button&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bubUmd/btrNDUqoSYH/7JLXqej5DwCZKqUhPAL7h1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbubUmd%2FbtrNDUqoSYH%2F7JLXqej5DwCZKqUhPAL7h1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;357&quot; height=&quot;79&quot; data-origin-width=&quot;488&quot; data-origin-height=&quot;108&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;floating action button&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;323&quot; data-origin-height=&quot;60&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LxJhG/btrNwkEknO2/wkQnhcGnyaTWsKDtRNgJHK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LxJhG/btrNwkEknO2/wkQnhcGnyaTWsKDtRNgJHK/img.png&quot; data-alt=&quot;switches&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LxJhG/btrNwkEknO2/wkQnhcGnyaTWsKDtRNgJHK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLxJhG%2FbtrNwkEknO2%2FwkQnhcGnyaTWsKDtRNgJHK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;291&quot; height=&quot;54&quot; data-origin-width=&quot;323&quot; data-origin-height=&quot;60&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;switches&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1046&quot; data-origin-height=&quot;527&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LX0sz/btrNvZHd17c/Ux5QNEr3mHlgp6ADi1cHok/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LX0sz/btrNvZHd17c/Ux5QNEr3mHlgp6ADi1cHok/img.png&quot; data-alt=&quot;data table&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LX0sz/btrNvZHd17c/Ux5QNEr3mHlgp6ADi1cHok/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLX0sz%2FbtrNvZHd17c%2FUx5QNEr3mHlgp6ADi1cHok%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;550&quot; height=&quot;277&quot; data-origin-width=&quot;1046&quot; data-origin-height=&quot;527&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;data table&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;MUI 컴포넌트 사용법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;react 프로젝트는 세팅되있다고 가정하고 진행하겠습니다. CRA나 Next로 쉽게 세팅할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;cli로 MUI를 설치합니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1664691468140&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;yarn add @mui/material @emotion/react @emotion/styled&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;emotion이 아니라 styled-component를 사용하고 싶다면 위가 아니라 다음과 같이합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1664691499945&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;yarn add @mui/material @mui/styled-engine-sc styled-components&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 구경한 예시 이미지 중에 아이콘이 들어간 UI들도 있었습니다. 아이콘을 사용하고 싶다면 Material icons를 설치합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1664691546351&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;yarn add @mui/icons-material&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 MUI 홈페이지에서 사용하고 싶은 컴포넌트들을 쇼핑(?)해봅니다. 공짜 쇼핑이니 즐겨봅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://mui.com/material-ui/react-autocomplete/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://mui.com/material-ui/react-autocomplete/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1664692309323&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;React Autocomplete component - Material UI&quot; data-og-description=&quot;The autocomplete is a normal text input enhanced by a panel of suggested options.&quot; data-og-host=&quot;mui.com&quot; data-og-source-url=&quot;https://mui.com/material-ui/react-autocomplete/&quot; data-og-url=&quot;https://mui.com/material-ui/react-autocomplete/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/c7WcOU/hyPYYEkQQW/cz9sC91FQ6ZEe1mXhxD2X0/img.png?width=820&amp;amp;height=820&amp;amp;face=0_0_820_820,https://scrap.kakaocdn.net/dn/bHO7Yu/hyPY4xOJVS/7dHP8kqfoEAKDmrRW0xNg0/img.png?width=820&amp;amp;height=820&amp;amp;face=0_0_820_820&quot;&gt;&lt;a href=&quot;https://mui.com/material-ui/react-autocomplete/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://mui.com/material-ui/react-autocomplete/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/c7WcOU/hyPYYEkQQW/cz9sC91FQ6ZEe1mXhxD2X0/img.png?width=820&amp;amp;height=820&amp;amp;face=0_0_820_820,https://scrap.kakaocdn.net/dn/bHO7Yu/hyPY4xOJVS/7dHP8kqfoEAKDmrRW0xNg0/img.png?width=820&amp;amp;height=820&amp;amp;face=0_0_820_820');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;React Autocomplete component - Material UI&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;The autocomplete is a normal text input enhanced by a panel of suggested options.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;mui.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 홈페이지에 접속한 후 왼쪽에 있는 메뉴 중에 components를 눌러서 쓰고 싶은 컴포넌트가 있는지 살펴봅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;394&quot; data-origin-height=&quot;934&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QGkFm/btrNwnHTJnQ/Q35IqiFso1yr2pz9NGKU7K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QGkFm/btrNwnHTJnQ/Q35IqiFso1yr2pz9NGKU7K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QGkFm/btrNwnHTJnQ/Q35IqiFso1yr2pz9NGKU7K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQGkFm%2FbtrNwnHTJnQ%2FQ35IqiFso1yr2pz9NGKU7K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;248&quot; height=&quot;588&quot; data-origin-width=&quot;394&quot; data-origin-height=&quot;934&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버튼 컴포넌트를 보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1900&quot; data-origin-height=&quot;942&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/F8Tl7/btrNyGfC1Xh/2WWoCgnArN6U5wuYFrmB1k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/F8Tl7/btrNyGfC1Xh/2WWoCgnArN6U5wuYFrmB1k/img.png&quot; data-alt=&quot;MUI Button component&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/F8Tl7/btrNyGfC1Xh/2WWoCgnArN6U5wuYFrmB1k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FF8Tl7%2FbtrNyGfC1Xh%2F2WWoCgnArN6U5wuYFrmB1k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;713&quot; height=&quot;353&quot; data-origin-width=&quot;1900&quot; data-origin-height=&quot;942&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;MUI Button component&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버튼 컴포넌트를 사용하기 위해 리액트 프로젝트에서 다음과 같이 불러옵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1664692576145&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import Button from &quot;@mui/material/Button&quot;;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;홈페이지에 있는 디자인과 같은 버튼을 쓰고 싶어서 코드를 그대로 복붙하여 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1530&quot; data-origin-height=&quot;530&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/biV8c9/btrNAyaly4W/U0BmJCbIhmV3jGgxDfKQxK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/biV8c9/btrNAyaly4W/U0BmJCbIhmV3jGgxDfKQxK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/biV8c9/btrNAyaly4W/U0BmJCbIhmV3jGgxDfKQxK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbiV8c9%2FbtrNAyaly4W%2FU0BmJCbIhmV3jGgxDfKQxK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;569&quot; height=&quot;197&quot; data-origin-width=&quot;1530&quot; data-origin-height=&quot;530&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1664692853819&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;Button variant=&quot;contained&quot;&amp;gt;Contained&amp;lt;/Button&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 컴포넌트도 사용해보려고 합니다. 모달과 체크박스도 사용하고 싶어서 불러옵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1664692723698&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import Modal from &quot;@mui/material/Modal&quot;;
import Checkbox from &quot;@mui/material/Checkbox&quot;;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모달과 체크박스도 마찬가지로 홈페이지에 있는 예시코드를 그대로 사용하면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@mui/material을 전체를 안불러오고 컴포넌트별로 각각 import 하는 이유는 트리 쉐이킹이 되지 않아, 모든 컴포넌트를 import 하면 빌드 타임이 너무 오래 걸리기 때문입니다. 그래서 귀찮더라도 사용하고 싶은 컴포넌트만 각각 import 해주는 것이 좋습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Mateiral icons 사용하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아이콘 쇼핑을 할 수 있는 곳은 여기입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://mui.com/material-ui/material-icons/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://mui.com/material-ui/material-icons/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1664693208687&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Material Icons - Material UI&quot; data-og-description=&quot;2,100+ ready-to-use React Material Icons from the official website.&quot; data-og-host=&quot;mui.com&quot; data-og-source-url=&quot;https://mui.com/material-ui/material-icons/&quot; data-og-url=&quot;https://mui.com/material-ui/material-icons/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/FjF5R/hyPYVVa6oM/lDOvhEMS6nZ5As8rq5w7M1/img.png?width=820&amp;amp;height=820&amp;amp;face=0_0_820_820,https://scrap.kakaocdn.net/dn/s5hRb/hyPYZJ3tE7/gs2zB7kKpKb2wCMp7cYbOk/img.png?width=820&amp;amp;height=820&amp;amp;face=0_0_820_820&quot;&gt;&lt;a href=&quot;https://mui.com/material-ui/material-icons/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://mui.com/material-ui/material-icons/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/FjF5R/hyPYVVa6oM/lDOvhEMS6nZ5As8rq5w7M1/img.png?width=820&amp;amp;height=820&amp;amp;face=0_0_820_820,https://scrap.kakaocdn.net/dn/s5hRb/hyPYZJ3tE7/gs2zB7kKpKb2wCMp7cYbOk/img.png?width=820&amp;amp;height=820&amp;amp;face=0_0_820_820');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Material Icons - Material UI&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;2,100+ ready-to-use React Material Icons from the official website.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;mui.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1721&quot; data-origin-height=&quot;845&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cgOzUV/btrNAwKlWZn/dJPUT3PafoumgMQahFdd60/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cgOzUV/btrNAwKlWZn/dJPUT3PafoumgMQahFdd60/img.png&quot; data-alt=&quot;material icons&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cgOzUV/btrNAwKlWZn/dJPUT3PafoumgMQahFdd60/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcgOzUV%2FbtrNAwKlWZn%2FdJPUT3PafoumgMQahFdd60%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1721&quot; height=&quot;845&quot; data-origin-width=&quot;1721&quot; data-origin-height=&quot;845&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;material icons&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원하는 아이콘을 찾아볼 수 있습니다. 종류가 워낙에 많아서 키워드를 검색해서 찾는 방법을 추천드립니다. 아이콘 이름을 잘 지어놔서 검색으로 보통 원하는 것을 쉽게 찾을 수 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 사람이 팔을 벌리고 있는 이 아이콘을 사용해보려 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;998&quot; data-origin-height=&quot;366&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bS5YgN/btrNv00rBc3/hVykkITV8sgvFjogSw1lk1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bS5YgN/btrNv00rBc3/hVykkITV8sgvFjogSw1lk1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bS5YgN/btrNv00rBc3/hVykkITV8sgvFjogSw1lk1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbS5YgN%2FbtrNv00rBc3%2FhVykkITV8sgvFjogSw1lk1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;598&quot; height=&quot;219&quot; data-origin-width=&quot;998&quot; data-origin-height=&quot;366&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클릭을 하면 다음과 같은 모달창이 열리고 import 하는 코드와 예시 이미지들이 나옵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;850&quot; data-origin-height=&quot;642&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bshQUg/btrNAMl5Yft/BZXlMSgRYoQR8igbaId3q0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bshQUg/btrNAMl5Yft/BZXlMSgRYoQR8igbaId3q0/img.png&quot; data-alt=&quot;accessibility icon&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bshQUg/btrNAMl5Yft/BZXlMSgRYoQR8igbaId3q0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbshQUg%2FbtrNAMl5Yft%2FBZXlMSgRYoQR8igbaId3q0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;577&quot; height=&quot;436&quot; data-origin-width=&quot;850&quot; data-origin-height=&quot;642&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;accessibility icon&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저 import 코드를 복붙하면 컴포넌트 사용했을 때처럼 똑같이 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1664693543938&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import AccessibilityIcon from '@mui/icons-material/Accessibility';

function MyComponent() {
  return &amp;lt;AccessibilityIcon color=&quot;primary&quot; /&amp;gt;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;커스터마이징&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Material UI는 Material design에 default style을 입힌 것을 제공하고 있습니다. MUI Base는 default style이 없는 상태로 제공합니다. 우리는 Material UI를 사용하면 default style을 사용하여 디자인에 대한 고민을 엄청나게 줄일 수 있습니다. 하지만, 아무래도 계속 앱을 만들다 보면 디자인을 커스터마이징을 하고 싶을 때가 있습니다. 그럴 땐 MUI Base를 사용하는 것이 아니라, Material UI의 default style을 그대로 사용하면서 약간의 변경만 주는 커스터마이징을 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1603&quot; data-origin-height=&quot;943&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cdpUiN/btrNwjemGaV/l4pqpqqUAeJC5jxtXHc7E1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cdpUiN/btrNwjemGaV/l4pqpqqUAeJC5jxtXHc7E1/img.png&quot; data-alt=&quot;customization 메뉴&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cdpUiN/btrNwjemGaV/l4pqpqqUAeJC5jxtXHc7E1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcdpUiN%2FbtrNwjemGaV%2Fl4pqpqqUAeJC5jxtXHc7E1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;605&quot; height=&quot;356&quot; data-origin-width=&quot;1603&quot; data-origin-height=&quot;943&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;customization 메뉴&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;MUI 홈페이지에서 customization 메뉴를 보면 커스터마이징할 수 있는 옵션들을 제공합니다. 커스터마이징 하는 방법은 theme라는 context를 정의하는 것이고, palette 등 각 소메뉴들은 다 theme에서 필요한 것들을 정의하는 방법을 알려주고 있습니다. 예를 들면 MUI 컴포넌트에서 color prop의 &quot;primary&quot;값이 어떤 색상인지, 텍스트를 사용하는 Typography 컴포넌트에서 사용되는 h2라던가 특정 태그의 font size 등을 어떻게 사용할 것인지 등, 디테일한 부분들을 전역으로 정의할 수 있습니다. 이렇게 정의한 옵션들은 최종적으로 context로 생성하여 provider로 넘겨줄 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간략한 theme 생성 코드의 예시를 보여드리겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1664694316264&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { createTheme, responsiveFontSizes } from &quot;@mui/material/styles&quot;;

const theme = responsiveFontSizes(
  createTheme({
    palette: 
      primary: {
          light: &quot;#757ce8&quot;,
          main: &quot;#3f50b5&quot;,
          dark: &quot;#002884&quot;,
          contrastText: &quot;#fff&quot;,
      },
      info: {
        dark: &quot;#ab003c&quot;,
        main: &quot;#f50057&quot;,
        light: &quot;#f73378&quot;,
      },
    },
    typography: {
      fontFamily: &quot;'Noto Sans KR', sans-serif&quot;,
      h2: {
        fontSize: &quot;2rem&quot;,
        textAlign: &quot;center&quot;,
        fontWeight: &quot;bold&quot;,
        marginBottom: 8,
      },
      caption: {
        fontSize: &quot;0.8rem&quot;,
      },
    },
    components: {
      MuiButton: {
        variants: [
          {
            props: { variant: &quot;outlined&quot;, size: &quot;large&quot; },
            style: {
              borderRadius: 12,
              fontSize: 18,
            },
          },
        ],
      },
    },
  }),
);

export default theme;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;responsiveFontSize를 사용하면 폰트 사이즈를 뷰포트의 크기에 따라 자동으로 변동하는 반응형을 제공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1046&quot; data-origin-height=&quot;556&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eE6WyM/btrNwE3FJj0/mm8fi6V9WLClEJk1AkLsWk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eE6WyM/btrNwE3FJj0/mm8fi6V9WLClEJk1AkLsWk/img.png&quot; data-alt=&quot;responsiveFontSzie가 제공하는 반응형 font size&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eE6WyM/btrNwE3FJj0/mm8fi6V9WLClEJk1AkLsWk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeE6WyM%2FbtrNwE3FJj0%2Fmm8fi6V9WLClEJk1AkLsWk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;612&quot; height=&quot;325&quot; data-origin-width=&quot;1046&quot; data-origin-height=&quot;556&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;responsiveFontSzie가 제공하는 반응형 font size&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 외에도 위 예시코드는 primary color와 info color의 재정의, Typography의 약간의 커스터마이징, MUI Button 컴포넌트의 스타일링 커스터마이징이 들어갔습니다. MuiButton의 Variants 속성은 props로 특정 값들을 받을 때 어떤 스타일을 오버라이딩하겠다라는 의미입니다. 위 코드대로면 props 값으로 variant: &quot;outlined&quot;, size: &quot;large&quot;를 받은 Button 컴포넌트는 border-radius: 12, font-size: 18이라는 CSS값이 오버라이딩 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1664695095730&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;Button variant=&quot;outlined&quot; size=&quot;large&quot;&amp;gt;둥근 버튼&amp;lt;/Button&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 식으로 다른 것들도 default style을 입맛에 맞게 변경할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 생성한 theme context를 App 컴포넌트에서 provider로 제공하는 코드는 다음과 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1664695298564&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { ThemeProvider } from &quot;@mui/material/styles&quot;;

import theme from &quot;src/utils/contexts/Theme&quot;;

function App() {
  return 
    &amp;lt;ThemeProvider theme={theme}&amp;gt;
      &amp;lt;div id=&quot;app&quot;&amp;gt;
        App
      &amp;lt;/div&amp;gt;
    &amp;lt;/ThemeProvider&amp;gt;
  );
}

export default App;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>React.js</category>
      <category>Material Design</category>
      <category>Material UI</category>
      <category>MUI</category>
      <author>cocoder16</author>
      <guid isPermaLink="true">https://cocoder16.tistory.com/81</guid>
      <comments>https://cocoder16.tistory.com/81#entry81comment</comments>
      <pubDate>Mon, 10 Oct 2022 08:08:30 +0900</pubDate>
    </item>
    <item>
      <title>TDD vs BDD, 무엇을 사용해야 할까?</title>
      <link>https://cocoder16.tistory.com/77</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;TDD(Test Driven Development)란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TDD는 보통 애플리케이션 코드를 작성하기 전에 테스트 코드를 먼저 작성하는 규칙을 지키는 것으로 알려져 있습니다. 이것은 테스트 코드 그 자체와는 상관이 없고 개발 습관에 대한 것입니다. TDD의 교과서인 &lt;b&gt;&lt;i&gt;켄트 벡&lt;/i&gt;&lt;/b&gt;의 &lt;b&gt;&lt;i&gt;테스트 주도 개발&lt;/i&gt;&lt;/b&gt;이라는 책을 보면 TDD의 정수를 제대로 맛볼 수 있을 것이라고 생각합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TDD 개발 습관을 가지기 위해서는 다음의 TDD 사이클을 반복하는 습관을 가지면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;TDD 사이클&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 테스트 케이스를 하나 추가한다. &lt;br /&gt;- 모든 테스트를 실행하고 새로 추가한 것이 실패하는지 확인한다. &lt;br /&gt;- 테스트를 통과하기 위한 코드를 작성한다.&lt;br /&gt;- 모든 테스트를 실행하고 전부 성공하는지 확인한다. &lt;br /&gt;- 리팩토링을 통해 중복을 제거한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트 코드 작성 -&amp;gt; 애플리케이션 코드 작성 -&amp;gt; 리팩터링의 순환 반복입니다. 이 순환 반복에서&amp;nbsp;TDD에서 반드시 지켜야 할 두 가지 법칙이 도출됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 어떤 코드건 작성하기 전에 실패하는 자동화된 테스트를 작성하라. &lt;br /&gt;2. 중복을 제거하라.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;BDD(Behavior Driven Development)란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애플리케이션 코드를 작성하기 전에 코드가 수행할 행위(요구사항)를 먼저 작성하는 개발 습관입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트 코드가 지니는 장점이 많으므로 요구사항을 테스트 코드로 작성합니다. 그러면 BDD는 TDD와 상당한 교집합을 가지게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BDD가 말하는 요구사항은 어떤 행동으로 인해 일어나는 시나리오를 의미합니다. 따라서 주로 유닛 테스트보다는 기능 테스트나 엔드 투 엔드 테스트로 작성합니다. 또한 BDD는 TDD에 비해 요구사항에 중점을 두기 때문에, 요구사항에 대한 이해를 명확히 할 수 있으며 이는 기획자, 테스터 등 비개발자와의 협업 프로세스를 도와줍니다. 비개발자와의 협업을 돕기 위해 테스트 코드를 보편 언어(Ubiquitous language)로 작성하는데 이는 &lt;a href=&quot;https://cocoder16.tistory.com/78&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;DDD(Domain Driven Design)&lt;/a&gt;에서 차용된 개념입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;TDD와 BDD의 공통점&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 둘은 개발 습관을 의미합니다. BDD도 TDD의 주기와 법칙을 따릅니다. BDD는 TDD를 기반으로 탄생한 개념이기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 코딩해야할 To do list를 작성합니다. 그리고 리스트 중 지금 먼저 할 것을 선택합니다. 코드가 만족해야 할 사항을 테스트 코드로 작성합니다. 실패하는 테스트 케이스가 생겼으니 이제 애플리케이션 코드를 작성합니다. 테스트 케이스가 통과하면 리팩터링을 진행합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;TDD와 BDD의 차이점&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트 코드는 어플리케이션 코드를 더 이해하기 쉬운 언어로 설명합니다. 하지만 이 둘은 설명하는 방식이 각자 다릅니다. 설명하는 방식이 다르니 코드 컨벤션도 다릅니다.&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TDD는 테스트 코드를 모듈 단위로 작성하므로 개발자가 읽는 코드입니다. 따라서 개발자에게 친화적인 언어로 작성합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BDD는 테스트 코드를 시나리오 단위로 작성하므로 개발자뿐만 아니라 기획자, 테스터와 같은 비개발자도 읽을 수 있습니다. 따라서 누구나 읽을 수 있는 보편 언어로 작성합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단한 계산기 프로그램에서 더하기에 대한 테스트 케이스를 추가하는 경우의 예시를 들겠습니다. 예시 코드는 특정 언어의 문법을 사용한 것은 아니므로 수도 코드로 봐주시기 바랍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발자에게 친화적인 테스트 코드 컨벤션은 다음과 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1662618517822&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;describe(&quot;Calculator&quot;, () =&amp;gt; {
  describe(&quot;plus&quot;, () =&amp;gt; {
    it(&quot;should return 200, when parameter is 100, 100&quot;, () =&amp;gt; {
      assert.equal(Calculator.plus(100, 100), 200);
    });
  });
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유닛 테스트를 하나 작성했습니다. Caculator라는 클래스에 정의된 plus라는 메서드에 대한 테스트 케이스 예시입니다. parameter, return과 같이 함수에서의 개발 용어를 직접적으로 사용하는 개발자 친화적인 테스트 네임을 가집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보편 언어로 작성된 테스트 코드 컨벤션은 다음과 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1662618540458&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;describe(&quot;app&quot;, () =&amp;gt; {
  test(&quot;Given empty input, When user input 100 + 100, Then output show 200&quot;, () =&amp;gt; {
    // Given
    input.hasValue(0);

    // When
    user = new User();
    user.input(100, 100);

    // Then
    expect(output.value).to.eqaul(200);
  });
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞선 것과 다르게 테스트 네임은 기술적인 개발 용어가 하나도 들어있지 않습니다. 요구사항에 대한 케이스를 하나 기술해놨는데 이것은 비개발자도 충분히 읽을 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드 컨벤션은 개발자의 취향과 철학에 따라 달라질 수 있으며 절대적인 법칙은 없습니다. 따라서 반드시 위 수도 코드대로 작성해야 하는 것은 아니며 하나의 예시로서 참고만 해주시면 좋겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;무엇을 사용해야할까?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TDD와 BDD는 개발 습관입니다. BDD는 TDD를 기반으로 탄생했으며 이 둘은 같은 개발 습관을 공유합니다. 이 개발 습관을 체화하면 코딩하는 과정에서 탄탄한 설계를 동시에 해내는 경험을 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TDD를 사용한다면 유닛 단위의 개발이 정확하고 탄탄하게 진행될 수 있습니다. 그리고 자연스럽게 클린 아키텍처를 지향하게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BDD를 사용한다면 요구사항을 구체적으로 관리하며 개발할 수 있습니다. 주의할 점은 이때 유닛 테스트를 하지 않으면 각 모듈 간의 결합도가 강해질 수 있습니다. 행동 시나리오 하나에는 보통 여러 함수들의 로직이 서로 상호작용하기 때문에, 각각을 따로 분리해 테스트 코드를 작성하지 않으면 그런 위험이 생깁니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추천해드리는 한 가지 방법은 TDD와 BDD 둘을 상호 보완하도록 사용하는 것입니다. 방법은 BDD 사이클 안에 TDD 사이클을 두는 것입니다. 요구사항이 하나 주어질 때, BDD로 테스트를 작성하고 TDD를 하면서 개발하다가 마지막에 요구사항을 작성한 BDD의 테스트가 통과하는 것을 확인하는 것을 거대한 사이클 하나로 정할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프론트엔드 진영에서는 UI 혹은 View레이어에서 매우 강한 결합도를 가지는 설계를 하여 모듈의 유닛 테스트가 어려운 경우가 많이 보입니다. 이런 설계를 가질 때에는 UI 혹은 View 레이어에서의 모듈들은 BDD만 하는 것이 유리할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Info</category>
      <category>테스트</category>
      <category>테스트 주도 개발</category>
      <category>행동 주도 개발</category>
      <author>cocoder16</author>
      <guid isPermaLink="true">https://cocoder16.tistory.com/77</guid>
      <comments>https://cocoder16.tistory.com/77#entry77comment</comments>
      <pubDate>Mon, 3 Oct 2022 08:08:25 +0900</pubDate>
    </item>
    <item>
      <title>DDD(Domain Driven Design) 진입장벽 극복하기</title>
      <link>https://cocoder16.tistory.com/78</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;도메인 주도 설계(DDD, Domain Driven Design) 소개&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;도메인이 무엇인가요?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도메인의 사전적 의미는 '소프트웨어로 해결해야 할 문제의 영역'입니다. 일반적으로 요구사항이라고 불리는 것들이며 이것을 도메인으로 풀어내는 것은 카테고리화와 비슷합니다. 예를 들어 'A회원이 B상품을 구매한다'는 문제는 회원과 상품이라는 두 개의 명사와 구매한다라는 동사로 분석할 수 있고, 이는 회원, 상품, 주문, 결제 등으로 상세화할 수 있는데 이것들을 도메인이라고 부릅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;도메인 주도 설계란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도메인 주도 설계는 도메인이 설계를 만든다는 것으로 데이터베이스의 엔터티가 설계를 만드는 것과는 다른 패러다임을 제시하는 개념입니다. 즉, 모델은 데이터베이스의 엔터티를 의미하는 것이 아니라, 요구사항을 담고 있으며, 업무 규칙을 담고 있습니다. 도메인 주도 설계를 하면 유사한 업무끼리 나눠서 설계할 수 있습니다. 주문으로 추상화된 업무들은 주문 도메인에서, 결제로 추상화된 업무들은 결제 도메인에서 설계됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;도메인과 객체의 관계&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도메인과 객체는 어떻게 다르고 둘은 무슨 관계일까요? 객체는 도메인을 표현하는 수단입니다. 객체는 상태, 행동, 식별자를 지니는데 도메인은 객체의 저 3요소로 풀이할 수 있습니다. 또한 객체의 이름을 도메인으로 지어 객체를 도메인으로 부르고 설명할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;DDD(Domain Driven Design)의 필요성&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 커뮤니케이션의 용이함&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도메인 주도 설계에서 코드는 도메인을 표현합니다. 따라서 개발자끼리 혹은 개발자와 도메인 전문가끼리의 커뮤니케이션은 이해하기 어려운 코드 상의 용어가 아닌 도메인 용어로 할 수 있습니다. 이 목적을 달성하기 위해 &lt;b&gt;보편 언어(Ubiquitous language)&lt;/b&gt;를 사용해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- 실천법 -&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) 보편 언어는 도메인 전문가, 개발자 모두가 이해할 수 있는 언어입니다. 모델에서부터 보편 언어를 사용합니다. 그리고 모델을 언어의 근간으로 사용해야 합니다. 팀 내 모든 의사소통과 코드에서 해당 언어를 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2) 모델을 보편 언어로 표현하므로 보편 언어의 변화는 모델의 변화를 의미합니다. 기획자와 의사소통을 하다가 기존에 사용하던 보편 언어가 변경되었음을 알아챘다면 모델을 수정해야 한다는 것으로 받아들여야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3) 모델은 높은 수준의 정책에 해당하므로 이것이 변경되면 대부분의 코드를 변경해야 합니다. 보편 언어를 변경하면 그에 맞게 코드에서 해당 네임을 일괄 수정해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 더 좋은 가독성을 가진 코드와 더 쉽게 습득하는 도메인 지식&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드를 도메인 용어로 작성하기 때문에 도메인의 지식을 갖출수록 코드를 읽기 편해집니다. 프로젝트 중간에 투입되더라도 해당 도메인 지식을 가지고 있으면 도메인 지식을 기반으로 코드를 읽을 수 있습니다. 또한 코드를 분석하면 동시에 도메인 분석이 되기 때문에 코드 분석만으로도 쉽게 도메인 지식을 습득할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- 실천법 -&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) 위에서 언급했던 보편 언어를 사용합니다. 일관된 네이밍 규칙으로 도메인을 코드로 기술합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2) 도메인의 구현을 다른 관심사와 분리합니다. 이를 위해 도메인 계층을 따로 분리합니다. 도메인 계층은 모델이 살아있는 곳입니다. 이곳에서는 업무 개념과 업무 상황에 관한 정보, 업무 규칙을 표현합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3) 비즈니스 로직을 구현하는 응용 계층을 도메인 계층과 분리합니다. 이곳에서는 소프트웨어가 수행할 작업을 정의하고 객체가 문제를 해결합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도메인에 관련된 코드가 상당한 양의 도메인과 관련이 없는 다른 코드를 통해 널리 확산될 경우 도메인에 관련된 코드를 확인하고 추론하기가 굉장히 힘들어집니다. 따라서 이렇게 도메인 계층을 따로 분리하여 가독성을 키웁니다.&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도메인 계층은 응용 계층보다 더 높은 수준의 정책을 가지므로 응용 계층은 도메인 계층에 의존하지만 도메인 계층은 응용 계층에 의존하지 않습니다. 의존성 역전 법칙을 지킵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4) 반복적인 리팩터링을 합니다. 리팩터링 타이밍은 다음과 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 현재 팀에서 도메인을 이해하고 있는 바가 설계에 표현돼 있지 않은 경우 &lt;br /&gt;- 중요한 개념이 설계상에 암시적으로 표현돼 있는 경우 (그리고 개념을 명확하게 표현할 수 있는 방법이 보이는 경우) &lt;br /&gt;- 설계상의 중요한 부분을 더욱 유연하게 만들 기회가 보이는 경우&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 관심사의 분리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도메인 별로 각 개발팀이 업무를 맡아 자신의 도메인만 관심사를 두어 일을 할 수 있습니다. 서로 다른 도메인의 변경사항에 의해 자신이 맡은 도메인에 오류가 발생하지 않도록 결합도를 끊어 인터페이스만 제공하여 모델의 무결성을 유지할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- 실천법 -&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) 제한된 컨텍스트(bounded context)를 사용합니다. 다수의 모델이 사용될 때, 어떤 컨텍스트에서 어떤 모델을 사용해서는 안되는지 코드 기반이나 데이터베이스 스키마와 같은 물리적인 형태에서 명시적으로 정의합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2) 하나의 컨텍스트는 하나의 개발팀이 담당합니다. 단, 하나의 개발팀은 여러 개의 컨텍스트를 담당할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3) 컨텍스트&amp;nbsp;경계 내에서는 모델을 엄격하게 일관된 상태로 유지하고 경계 바깥의 이슈 때문에 초점이 흐려지거나 혼란스러워져서는 안 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4) 컨텍스트간의 관계를 그린 컨텍스트 맵(context map)을 그립니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5) 각 컨텍스트마다 CI/CD를 따로 구축합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 실천법은 마이크로 서비스 아키텍처(MSA)를 하는 것과 같은 방향입니다. MSA는 DDD와 함께 진행하면 더욱 효과적이게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;DDD의 진입장벽이 높은 이유&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 애자일 방법론 적용&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DDD의 전제조건 두 가지가 있습니다. 첫째는 애자일 방법론을 사용하는 것이고, 둘째는 도메인 전문가와 개발자는 서로 긴밀하게 커뮤니케이션해야 한다는 것입니다. 일단 애자일 방법을 사용하는 팀에서의 경험이 없으면 이것이 막연하게 느껴지는 것이 문제입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 설계의 어려움&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설계는 보통 경력이 풍부한 개발자가 하기 때문에 주니어 개발자들은 설계 자체가 어려워서 DDD까지는 막연하게 느껴지는 것이 문제입니다. 도메인 계층과 제한된 컨텍스트를 제대로 사용하려면 클린 아키텍처에 대한 지식과 SOLID에 대한 이해를 가지고 있어야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. Aggregate&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도메인 객체의 생명주기와 관련된 두 가지 문제가 있습니다. 도메인 객체의 무결성을 유지하는 것과 생명주기 관리의 복잡성으로 인해 모델이 난해해지는 것입니다. 이것을 해결하는 방법이 Aggregate, Factory, Repository를 추가하는 것입니다. Aggreate를 모델링하고 설계에 Factory와 Repository를 추가하면 모델 객체의 생명주기 동안 그것들을 체계적이고 의미 있는 단위로 조작할 수 있습니다. 그런데 Aggregate를 설계하기 위해 필요한 기반 지식이 많습니다. 트랜잭션, 엔터티, 불변 객체, 가비지 컬렉션에 대해 이해하고 있어야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;진입장벽을 극복하는 방법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회사에서 적용하기 전에 스스로 개인 프로젝트로 연습해봅니다. 애자일을 혼자서 연습하는 방법은 최소한의 요구사항만 가지고 개발을 마치고, 또 다른 요구사항을 추가하고 그것을 개발하는 사이클을 반복해보는 것입니다. 이것이 반복될 때마다 설계가 변경될텐데, 그 방식에 적응하는 것입니다. 이런 방식에 적응하기 위해 SOLID원칙을 공부하고 계속 적용하려고 노력해봅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보편 언어 사용법을 깨닫기 위해 클린 코드에 대한 이해가 있으면 더 좋지만 그렇지 않아도 시작할 수 있습니다. 도메인을 누구나 알아볼 수 있는 이름으로 작명하는 것을 먼저 해봅니다. 그리고 그것으로 모든 클래스, 객체, 메서드 등 코드 네이밍에 적용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;소프트웨어 개발 철학과 관련된 수많은 개념이 DDD로부터 파생되었습니다. 사실 그런 것들을 공부하다 보면 알게 모르게 DDD에 젖어들고 있는 것입니다. TDD, &lt;a href=&quot;https://cocoder16.tistory.com/79&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;클린 코드&lt;/a&gt;, &lt;a href=&quot;https://cocoder16.tistory.com/80&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;클린 아키텍처&lt;/a&gt;, 리팩터링, 객체지향 프로그래밍 등을 먼저 공부해보고 오면 더 넓은 시야로 DDD를 바라볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 포스트에서 DDD에 대해 더 많은 구체적인 내용을 다루지는 못했습니다. 하지만 이 글로 인해 조금이라도 DDD에 대해 쉽고 편하게 느껴지시면 좋겠다는 생각입니다. DDD에 대해 깊게 공부하려면 &lt;b&gt;&lt;i&gt;에릭 에반스&lt;/i&gt;&lt;/b&gt;의 &lt;b&gt;&lt;i&gt;도메인 주도 설계&lt;/i&gt;&lt;/b&gt;를 읽어보시기를 추천해드립니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Info</category>
      <category>Ubiquitous language</category>
      <category>도메인 주도 설계</category>
      <category>애자일</category>
      <author>cocoder16</author>
      <guid isPermaLink="true">https://cocoder16.tistory.com/78</guid>
      <comments>https://cocoder16.tistory.com/78#entry78comment</comments>
      <pubDate>Mon, 26 Sep 2022 08:08:16 +0900</pubDate>
    </item>
    <item>
      <title>매우 설득력있는 클린 아키텍처</title>
      <link>https://cocoder16.tistory.com/80</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;클린 아키텍처의 목표&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;클린 아키텍처에서 말하는 소프트웨어 아키텍처의 목표는 다음과 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;b&gt;'필요한 시스템을 만들고 유지보수하는 데 투입되는 인력을 최소화하는 데 있다.'&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;인력을 최소화한다는 것은 비용을 최소화한다는 의미이고 이것은 경제적인 관점에서 바라본 목표입니다. 즉, 클린 아키텍처가 지향하는 바는 소프트웨어의 개발과 유지보수의 비용을 최소화하는 것입니다. 따라서 클린 아키텍처는 개발자의 허영심을 채우기 위한 것이 아닙니다. 이것은 손익을 계산하는 경영자가 관심을 가져야 할 내용입니다. 우리 개발자들은 클린 아키텍처를 공부하여 관리자에게 이것의 경제적인 이점을 어필하여 회사의 성장과 개발 역량의 향상을 모두 쟁취하는 이상적인 길을 걸어갈 수 있습니다. 마지막으로 책에서 나온 다음의 문구로 클린 아키텍처의 목표를 다시 한번 정의해봅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;빨리 가는 유일한 방법은 제대로 가는 것이다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;동작하는 소프트웨어 vs 변경하기 쉬운 소프트웨어&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;후자가&amp;nbsp;더&amp;nbsp;중요하다.&lt;/b&gt; &lt;br /&gt;&lt;br /&gt;완벽하게 동작하지만 수정이 불가능한 프로그램은 요구사항이 변경되면 동작하지 않게 됩니다.&lt;br /&gt;반면 동작은 하지 않지만 변경이 쉬운 프로그램은 돌아가게 만들 수 있고 변경사항이 발생해도 동작하도록 유지보수할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것은 너무 극과 극을 비교하여 극단적인 것처럼 보이지만,&amp;nbsp; 원래 극과 극을 비교해야 뭐가 더 중요한지 단순하고 명쾌하게 판단할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;위에서 말한 것과 같은 변경이 완전히 불가능한 프로그램이란 사실 존재하지 않지만, 수정이 현실적으로 불가능한 시스템은 존재합니다. 변경에 드는 비용이 변경으로 창출되는 수익을 초과하는 경우입니다. 다시 말하지만 다소 경제적인 관점에서 바라볼 필요가 있습니다. 종합하자면 이 문제는 다음과 같이 바꿔쓸 수 있습니다.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;행위는&amp;nbsp;긴급하다&amp;nbsp;vs&amp;nbsp;아키텍처는&amp;nbsp;중요하다&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상황에 따라 긴급한 것을 먼저 처리해야 할 때도 있지만, 그럴 때조차도 중요한 것에 대해 늘 생각하고 있어야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;OOP(객체 지향 프로그래밍) vs FP(함수형 프로그래밍)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현대 소프트웨어 개발에서는 이 둘을 적절히 섞어 쓰고 있습니다. 사실은 둘 중 하나만 쓰고 나머지 하나를 배제하는 게 훨씬 어렵습니다. 왜냐하면 이것은 2차원 평면좌표에서 x축, y축 위에 점을 정확히 찍는 것과 같기 때문입니다. 어떤 언어를 사용하던 보통은 둘을 자연스레 같이 사용하게 됩니다. 여러 프레임워크, 라이브러리를 사용하면 더욱 그러하게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;둘의 철학은 각자 장점이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OOP는 다형성으로 의존성 역전을 만듭니다. 이것으로 배포 독립성, 개발 독립성을 가질 수 있습니다. 이것은 관심사의 분리, 도메인 주도 설계, 마이크로 서비스와 같은 설계 방법들로 확장할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FP는 변수는 변경되지 않습니다. 가변 변수가 일으킬 수 있는 모든 문제들을 최소화할 수 있습니다. 또한 FP는 유닛 테스트, 클린 코드에 상당한 이점을 가져옵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;SOLID 원칙 지키기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;클린 아키텍처에서는 다음과 같은 비유를 사용하고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;- 좋은 벽돌: &lt;a href=&quot;https://cocoder16.tistory.com/79&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;클린 코드&lt;/a&gt;&lt;br /&gt;- 좋은 방: 클린 중간 크기의 모듈 &lt;br /&gt;- 좋은 빌딩: 클린 아키텍처 &lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;벽돌이 가장 작은 단위의 코드라고 한다면 방은 컴포넌트보다는 작은 중간 수준의 모듈을 의미합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좋은 벽돌들로 좋은 방을 만들기 위해 SOLID를 반드시 지켜야 한다고 합니다. 그러면 중간 수준의 소프트웨어는 다음과 같은 &lt;b&gt;목적&lt;/b&gt;을 달성할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 변경에 유연하다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 이해하기 쉽다 &lt;br /&gt;- 많은 소프트웨어 시스템에 사용될 수 있는 컴포넌트의 기반이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;SOLID 원칙&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. SRP: 단일 책임 원칙&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;각&amp;nbsp;소프트웨어&amp;nbsp;모듈은&amp;nbsp;변경의&amp;nbsp;이유가&amp;nbsp;하나,&amp;nbsp;단&amp;nbsp;하나여야만&amp;nbsp;한다.&lt;/b&gt; &lt;br /&gt;&lt;br /&gt;변경의 이유란 이들 사용자와 이해관계자 집단을 가리킵니다. 이들을 &lt;b&gt;액터&lt;/b&gt;라고 부릅니다. 따라서 SRP는 다음과 같이 바꿔쓸 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;b&gt;하나의&amp;nbsp;모듈은&amp;nbsp;하나의,&amp;nbsp;오직&amp;nbsp;하나의&amp;nbsp;액터에&amp;nbsp;대해서만&amp;nbsp;책임져야&amp;nbsp;한다.&lt;/b&gt; &lt;br /&gt;&lt;br /&gt;단일 액터를 책임지는 코드를 함께 묶어주는 힘이 바로 응집성입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;위반 징후&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 우발적 중복이 발생한다.&lt;br /&gt;- 서로 다른 개발자가 다른 액터에 대한 개발 변경사항을 수정하기 위해 수정을 하고 병합할 때 서로 충돌이 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;실천방법&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;- 서로 다른 액터를 뒷받침하는 코드를 서로 분리하기 &lt;br /&gt;- 데이터와 메서드를 분리하기 &lt;br /&gt;- 서로 다른 액터들을 서로 다른 클래스로 만들기 &lt;br /&gt;- 이러면 여러 클래스를 인스턴스화하고 추적해야 한다는 단점이 있다. &lt;br /&gt;- 이 단점을 극복하는 기법으로 퍼사드 패턴이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. OCP: 개방-폐쇄 원칙&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;소프트웨어&amp;nbsp;개체는&amp;nbsp;확장에는&amp;nbsp;열려&amp;nbsp;있어야&amp;nbsp;하고,&amp;nbsp;변경에는&amp;nbsp;닫혀&amp;nbsp;있어야&amp;nbsp;한다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;기존 코드를 수정하기보다는 반드시 새로운 코드를 추가하는 방식으로 시스템의 행위를 변경할 수 있도록 설계해야만 소프트웨어 시스템을 쉽게 변경할 수 있습니다. 소프트웨어 아키텍처가 훌륭하다면 요구사항을 확장할 때 변경되는 코드의 양은 최소화될 것입니다. 이상적인 변경량은 0입니다. OCP의 목표는 시스템을 확장하기 쉬운 동시에 변경으로 인해 시스템이 너무 많은 영향을 받지 않도록 하는 데 있습니다. 저수준 컴포넌트에서 발생한 변경으로부터 고수준 컴포넌트를 보호할 수 있는 형태의 의존성 계층구조가 만들어지도록 해야 합니다. 이를 이해하기 위해 밑에서는 DIP에 대해 알아볼 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;위반&amp;nbsp;징후&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;- 요구사항을 살짝 확장하는 데 소프트웨어를 엄청나게 수정해야 한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;실천방법&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- SRP와 DIP를 지킴으로써 요구사항 변경 시 코드 변경량을 최소화할 수 있다. &lt;br /&gt;- SRP 적용: 책임 분리를 추상화 수준으로 그려본다. &lt;br /&gt;- DIP 적용: 각 컴포넌트의 계층 구조를 만든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 보호의 계층 구조는 '수준'이라는 개념을 바탕으로 생성된다. &lt;br /&gt;- 가장 상위 컴포넌트(가장 높은 수준의 개념)는 OCP를 가장 잘 준수한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 가장 하위 컴포넌트(가장 낮은 수준의 개념)는 OCP를 가장 준수하지 못한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. LSP: 리스코프 치환 원칙&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;b&gt;서브&amp;nbsp;타입에&amp;nbsp;관한&amp;nbsp;원칙이다.&amp;nbsp;상호&amp;nbsp;대체&amp;nbsp;가능한&amp;nbsp;구성요소를&amp;nbsp;이용해&amp;nbsp;소프트웨어&amp;nbsp;시스템을&amp;nbsp;만들&amp;nbsp;수&amp;nbsp;있으려면,&amp;nbsp;이들&amp;nbsp;구성요소는&amp;nbsp;반드시&amp;nbsp;서로&amp;nbsp;치환&amp;nbsp;가능해야&amp;nbsp;한다.&lt;/b&gt; &lt;br /&gt;&lt;br /&gt;&lt;b&gt;치환&amp;nbsp;원칙&lt;/b&gt; &lt;br /&gt;&lt;br /&gt;S&amp;nbsp;타입의&amp;nbsp;객체&amp;nbsp;o1&amp;nbsp;각각에&amp;nbsp;대응하는&amp;nbsp;T&amp;nbsp;타입&amp;nbsp;객체&amp;nbsp;o2가&amp;nbsp;있고,&amp;nbsp;T&amp;nbsp;타입을&amp;nbsp;이용해서&amp;nbsp;정의한&amp;nbsp;모든&amp;nbsp;프로그램&amp;nbsp;P에서&amp;nbsp;o2의&amp;nbsp;자리에&amp;nbsp;o1을&amp;nbsp;치환하더라도&amp;nbsp;P의&amp;nbsp;행위가&amp;nbsp;변하지&amp;nbsp;않는다면,&amp;nbsp;S는&amp;nbsp;T의&amp;nbsp;하위&amp;nbsp;타입이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;치환&amp;nbsp;가능성을&amp;nbsp;조금이라도&amp;nbsp;위배하면&amp;nbsp;시스템&amp;nbsp;아키텍처가&amp;nbsp;오염되어&amp;nbsp;상당량의&amp;nbsp;별도&amp;nbsp;메커니즘을&amp;nbsp;추가해야&amp;nbsp;할&amp;nbsp;수&amp;nbsp;있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;실천방법&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;- 상속 사용 &lt;br /&gt;- 잘 정의된 인터페이스와 그 인터페이스의 구현체끼리 상호 치환 가능하게 하기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. ISP: 인터페이스 분리 원칙&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;b&gt;소프트웨어&amp;nbsp;설계자는&amp;nbsp;사용하지&amp;nbsp;않은&amp;nbsp;것에&amp;nbsp;의존하지&amp;nbsp;않아야&amp;nbsp;한다.&lt;/b&gt; &lt;br /&gt;&lt;br /&gt;일반적으로, 필요 이상으로 많은 걸 포함하는 모듈에 의존하는 것은 해로운 일입니다. 소스 코드 의존성의 경우 이는 분명한 사실인데, 불필요한 재컴파일과 재배포를 강제하기 때문입니다. 하지만 더 고수준인 아키텍처 수준에서도 마찬가지 상황이 발생합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;실천방법&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 부피가 큰 모듈을 전부 임포트하지 않고 필요한 것만 임포트하기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. DIP: 의존성 역전 원칙&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;고수준&amp;nbsp;정책을&amp;nbsp;구현하는&amp;nbsp;코드는&amp;nbsp;저수준&amp;nbsp;세부사항을&amp;nbsp;구현하는&amp;nbsp;코드에&amp;nbsp;절대로&amp;nbsp;의존해서는&amp;nbsp;안&amp;nbsp;된다.&amp;nbsp;대신&amp;nbsp;세부사항이&amp;nbsp;정책에&amp;nbsp;의존해야&amp;nbsp;한다.&lt;/b&gt; &lt;br /&gt;&lt;br /&gt;우리가 의존하지 않도록 피하고자 하는 것은 바로 변동성이 큰 구체적인 요소입니다. 이 구체적인 요소는 자주 변경될 수밖에 없는 모듈들입니다. 예를 들어 인터페이스는 구현체보다 변동성이 낮습니다. 따라서 구현체가 인터페이스에 의존하고 반대는 일어나지 않도록 해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;실천방법&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;- 변동성이 큰 구체 클래스를 참조하지 말기&lt;br /&gt;- 변동성이 큰 구체 클래스로부터 파생하지 말기&lt;br /&gt;- 구체 함수를 오버라이드 하지 말기&lt;br /&gt;- 구체적이며 변동성이 크다면 절대로 그 이름을 언급하지 말기&lt;br /&gt;- 추상 팩토리 패턴 사용하기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;컴포넌트 수준에서 적용하는 SOLID원칙&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SOLID는 컴포넌트 수준에서도 적용될 수 있습니다. 여기서 &lt;b&gt;컴포넌트&lt;/b&gt;란 &lt;span style=&quot;background-color: #ffffff; color: #24292f;&quot;&gt;시스템의 구성 요소로 배포할 수 있는 가장 작은 단위를 의미합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #24292f;&quot;&gt;다음은 &lt;b&gt;컴포넌트 응집도와 관련된 세 가지 법칙&lt;/b&gt;입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. REP: 재사용/릴리스 등가 원칙&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;재사용 단위는 릴리스 단위와 같습니다. 컴포넌트를 구성하는 모든 모듈은 서로 공유하는 중요한 테마나 목적이 있어야 합니다.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;2. CCP: 공통 폐쇄 원칙 &lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동일한 이유로 동일한 시점에 변경되는 클래스를 같은 컴포넌트로 묶어야 합니다. 서로 다른 시점에 다른 이유로 변경되는 클래스는 다른 컴포넌트로 분리해야 합니다. 이것은 단일 책임 원칙의 컴포넌트 버전입니다. SRP는 단일 클래스는 변경의 이유가 여러 개 있어서는 안 된다고 말하듯이, CCP는 단일 컴포넌트는 변경의 이유가 여러 개 있어서는 안 된다고 말하는 것입니다. 개방 폐쇄 원칙과도 밀접하게 관련되어 있습니다. OCP의 폐쇄와 CCP의 폐쇄는 같은 뜻입니다.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;3. CRP: 공통 재사용 원칙 &lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴포넌트 사용자들을 필요하지 않은 것에 의존하게 강요하지 말아야 합니다. 인터페이스 분리 원칙의 컴포넌트 버전입니다. ISP는 사용하지 않은 메서드가 있는 클래스에 의존하지 말라고 조언합니다. 마찬가지로 CRP는 사용하지 않는 클래스를 가진 컴포넌트에 의존하지 말라고 조언합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 &lt;span style=&quot;background-color: #ffffff; color: #24292f;&quot;&gt;&lt;b&gt;컴포넌트 사이의 관계와 관련된 세 가지 법칙&lt;/b&gt;입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff; color: #24292f;&quot;&gt;1. ADP: 의존성 비순환 원칙&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #24292f;&quot;&gt; &lt;br /&gt;컴포넌트 의존성 그래프에 순환이 있어서는 안 됩니다. 순환 문제가 있으면 컴포넌트 빌드 순서를 정할 수가 없고, 릴리즈가 어려워지고, 테스트가 어려워집니다. 순환을 끊기 위해 의존성 역전 원칙을 적용합니다. &lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #24292f;&quot;&gt;컴포넌트 의존성 다이어그램은 애플리케이션의 빌드 가능성과 유지보수성을 보여주는 지도입니다.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;2. SDP: 안정된 의존성 원칙 &lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #24292f;&quot;&gt;안정성의 방향으로(더 안정된 쪽에) 의존해야 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff; color: #24292f;&quot;&gt;3. SAP:&amp;nbsp;안정된&amp;nbsp;추상화&amp;nbsp;원칙 &lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #24292f;&quot;&gt;컴포넌트는 안정된 정도만큼만 추상화되어야 합니다. 가장 높은 단계의 컴포넌트의 추상화 정도가 가장 큽니다. 가장 낮은 단계의 컴포넌트는 추상화 정도가 가장 작습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div class=&quot;revenue_unit_item adsense  responsive&quot;&gt;&lt;ins class=&quot;adsbygoogle&quot; style=&quot;display: block;&quot; data-ad-host=&quot;ca-host-pub-9691043933427338&quot; data-ad-client=&quot;ca-pub-9807016842906892&quot; data-ad-format=&quot;auto&quot;&gt;&lt;/ins&gt;
&lt;script&gt;(adsbygoogle = window.adsbygoogle || []).push({}); &lt;/script&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;좋은 아키텍처가 되는 방법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좋은 아키텍처가 되기 위해서 실천해야 하는 것들입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 앞서 살펴본 SOLID원칙을 지켜 코드를 짜고 설계해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 세부사항에 대한 결정을 가능한 한 오랫동안 미룰 수 있는 방향으로 정책을 설계합니다. 세부사항은 정책보다 낮은 단계에 있기 때문입니다.&lt;br /&gt;3. 정책은 세부사항에 관한 어떠한 지식도 갖지 못하게 되며 어떤 경우에도 세부사항에 의존하지 않게 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 개발 초기에는 선택사항을 열어둡니다. 고수준의 정책은 이런 것들을 신경 써서는 안 됩니다. &lt;span&gt;(데이터베이스 종류, 웹 서버, REST, 프레임워크)&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;5. 시스템이 다음과 같은 특징을 갖도록 합니다.&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;- &lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;프레임워크 독립성: 아키텍처는 프레임워크의 존재 여부에 의존하지 않는다. 프레임워크를 도구로 사용할 수 있다.&lt;br /&gt;- 테스트 용이성: 업무 규칙은 UI, 데이터베이스, 웹 서버 또는 여타 외부 요소가 없이도 테스트할 수 있다.&lt;br /&gt;- UI 독립성: 시스템의 나머지 부분을 변경하지 않고도 UI를 쉽게 변경할 수 있다.&lt;br /&gt;- 데이터베이스 독립성: 업무 규칙은 데이터베이스에 결합되지 않는다.&lt;br /&gt;- 모든 외부 에이전시에 대한 독립성: 실제로 업무 규칙은 외부 세계와의 인터페이스에 대해 전혀 알지 못한다 &lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;MSA(마이크로 서비스 아키텍처)의 문제점과 클린 MSA&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;잘 나가는 자사 서비스 기업, 유니콘 기업들이 MSA를 너도나도 도입하면서 다른 개발 회사들과 개발자들도 모두 MSA를 자신의 기술로 만들기 위해 노력하는 듯합니다. 그런데 사실 MSA는 개발 시간뿐만 아니라 시스템 자원 측면에서도 비용이 많이 들어갑니다. 물론 비용을 감당할 수 있다면 MSA는 관심사의 분리와 자원의 분리로 인해 유지보수성을 가져다줄 수 있습니다. 하지만 이것도 안정적인 설계가 되었을 때의 이야기입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MSA를 모노리틱 구조와 떼어내어 생각할 수 있는 동떨어진 별개의 신기술로 생각해서는 안됩니다. 클린 아키텍처를 가진 시스템은 다음과 같습니다. 시스템은 초반에 모노리틱 구조로 출발할 수 있습니다. 이후에는 시스템이 점점 커지면서 독립적으로 배포 가능한 단위들의 집합으로 성장하고, 마이크로 서비스 수준까지 성장할 수 있습니다. 그리고 이것을 나중에 상황이 바뀌었을 때 다시 모노리틱 구조로 합칠 수도 있어야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지의 내용은 &lt;b&gt;&lt;i&gt;로버트 C. 마틴&lt;/i&gt;&lt;/b&gt;의 &lt;i&gt;&lt;b&gt;클린 아키텍처&lt;/b&gt;&lt;/i&gt;를 요약해본 것으로 책에서 더 자세한 내용을 볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Info</category>
      <category>MSA</category>
      <category>SOLID</category>
      <category>로버트 C. 마틴</category>
      <category>클린 아키텍처</category>
      <author>cocoder16</author>
      <guid isPermaLink="true">https://cocoder16.tistory.com/80</guid>
      <comments>https://cocoder16.tistory.com/80#entry80comment</comments>
      <pubDate>Mon, 19 Sep 2022 08:08:36 +0900</pubDate>
    </item>
    <item>
      <title>클린 코드, 제대로 알고 실천하기</title>
      <link>https://cocoder16.tistory.com/79</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;클린 코드에 대한 오해와 진실&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;오해1. 클린 코드에 신경을 쓰면 개발 시간이 느려진다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;너무 코드를 깨끗하고 예쁘게 짜는 것에만 치중하면 정작 필요한 기능 구현과 로직 작성에 걸리는 시간이 오래 걸린다고 생각할 수 있습니다. 하지만 깨끗하고 예쁜 코드는 오히려 개발을 빠르게 하기 위해서 중요합니다. 개발자는 코딩을 할 때, 코드를 읽는 시간 대 코드를 짜는 시간 비율이 10대 1을 훌쩍 넘습니다. 즉, 새 코드를 짜면서 끊임없이 반복하여 기존 코드를 읽습니다. 실제로 개발할 때, 코드를 작성하는 시간보다 새로 작성할 코드를 기존 코드 베이스에 녹여낼 방법을 찾기 위해 기존 코드를 읽는 시간이 훨씬 길다는 것을 조금 신경 써보면 금방 눈치챌 수 있습니다. 따라서 더 읽기 쉬운 코드는 개발 속도를 향상시킵니다. 또한, 클린 코드를 좋아하는 사람들이 너무 변수 네이밍에 지나치게 집착하기 때문에 시간낭비를 한다는 것도 사실이 아닙니다. 클린 코드에 익숙해지면 &lt;span&gt;오히려 변수명 등 작명에 고민하는 시간이 거의 없어집니다. 몇 가지 규칙에 의해서 기계적으로 작명할 수 있어서 네이밍을 빨리 할 수 있기 때문입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;오해2. 클린 코드는 타인에 대한 배려이므로 협업 상황에서만 좋다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더 읽기 쉬운 코드는 협업 상대에 대한 배려라고 인식하는 경우도 있습니다. 물론 맞는 말이지만 혼자서 개인 프로젝트를 하더라도 자신이 짠 코드를 며칠 뒤에 다시 봤을 때 읽기 힘든 경우를 겪어보신 분들이 많을 것입니다. 내가 대충 짠 코드도 내가 짠 것이기 때문에 나중에 보면 알아볼 수 있다는 것은 큰 교만입니다. 실제로 해보면 이것은 쉽지 않습니다. 따라서 클린 코드는 나를 위한 배려이며 나를 위해서 실천해야 하는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;오해3. 클린 코드는 코드의 가독성에만 영향을 줄 뿐, 설계와는 관련이 없다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;더 읽기 쉬운 코드는 더 고치기 쉬운 코드를 의미하기도 합니다. 더 고치기 쉬운 코드는 더 좋은 설계를 가졌다는 뜻입니다. ETC(easier to change)는 좋은 설계의 핵심입니다. 따라서 클린 코드는 좋은 설계의 핵심입니다. 설계를 잘하기 위해서 종종 &lt;a href=&quot;https://cocoder16.tistory.com/78&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;DDD(Domain Driven Design)&lt;/a&gt;같은 것을 도입하기도 하는데, DDD에서 보편 언어를 모든 코드에서 사용하라는 원칙이 있습니다. DDD에서도 좋은 설계를 위해서는 누구나 쉽게 읽을 수 있는 언어로 코드를 작성해야 한다고 말하고 있는 것입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;또한 클린 코드는 SOLID를 기반으로 함수와 클래스가 지켜야 할 규칙들을 제시하고 있습니다. SOLID는 좋은 설계를 위한 기본 조건 중 하나입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;오해4. 클린 코드는 변수명 같은 것만 신경 쓸 뿐, 코드의 논리적 전개의 퀄리티를 높이지 않는다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클린 코드는 코드의 논리적 가독성을 높입니다. 추상화 수준에 따라 코드를 전개하는 내려가기 규칙을 지키면 코드는 일관된 논리를 가지게 되므로 다음 라인에 어떤 코드가 나올지 예상하면서 읽을 수 있게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;클린 코드 실천 마인드&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나쁜 코드로 치르는 대가는 큽니다. 시간이 지날수록 생산성은 극도로 악화됩니다. 많은 시간이 지난 후에는 리팩터링 비용이 처음부터 다시 시작하는 비용보다 커질 수도 있습니다. 따라서 나쁜 코드는 보이는 즉시 제거하는 것이 베스트입니다. 이를 위해 &lt;b&gt;보이스카우트 규칙&lt;/b&gt;을 제시합니다. 보이스카우트 규칙은 '캠프장은 처음 왔을 때보다 더 깨끗하게 해놓고 떠나라.'입니다. 마찬가지로 체크아웃할 때보다 더 깨끗한 코드를 체크인한다면 코드는 절대 나빠지지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일정에 쫓기더라도 좋은 코드를 작성해야 합니다. 관리자들은 늘 일정과 요구사항을 강력하게 밀어붙이는데 그것은 그들의 책임이기 때문입니다. 반면 좋은 코드를 사수하는 것은 우리 개발자들의 책임입니다. 나쁜 코드의 위험을 이해하지 못하는 관리자의 말을 그대로 따르는 것은 우리의 책임 영역을 버리고 관리자의 책임 영역만 도와주는 것입니다. 그러다가 나중에 돌이킬 수 없을 정도로 코드가 나빠져서 비용 문제가 표면 위로 나오기 시작하면 관리자는 그것을 개발자의 탓으로 할 것입니다. 그리고 우리는 그것을 변명할 수 없습니다. 따라서 우리의 책임을 다하기 위해 관리자와 소통하고 협상하는 스킬을 가져야만 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;구체적인 실천 방안&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;일관된 네이밍 규칙 정하기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일관된 규칙이 있으면 네이밍에 고민하는 시간을 줄일 수 있습니다. Restful api나 Domain Driven Design을 해보셨다면 뛰어난 프로그래머들이 네이밍 규칙에 어떤 철학을 적용하기를 원하는지 감을 잡을 수 있을지도 모릅니다. 일반적으로 널리 사용되는 규칙으로는 함수는 동사로 시작한다라던가, 클래스나 객체는 명사 이름을 사용한다와 같은 것들이 있습니다. 매 프로젝트마다 불편했던 네이밍 규칙들을 개선해나가면서 자신만의 규칙을 정립해보세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;코드 오토 포매팅 적용하기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;IDE는 유용합니다. 들여쓰기 범위, 문단 간 띄어쓰기 등 지켜졌으면 좋겠는 규칙들을 자동화시킬 수 있습니다. 특히 IDE에서 파일을 save할 때 오토 포매팅을 하도록 설정해서 사용해보세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;주석 사용하지 말기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주석을 사용하는 일반적인 이유는 코드의 품질이 나쁘기 때문입니다. 따라서 주석을 사용하고 있다면 주석을 제거하기 위해 코드를 어떻게 개량할 수 있는지를 고민해봐야 합니다. 대부분의 경우 네이밍을 잘하고 모듈을 작은 단위로 쪼개어 사용하다보면 주석의 많은 부분이 해소됩니다. 그리고 테스트 코드가 주석의 좋은 대안이 될 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;테스트 코드 쓰기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트 코드는 코드의 품질을 극도로 올릴 수 있는 수단입니다. 테스트 코드는 본 코드보다 가독성이 좋으며, 본 코드에 유연성, 유지보수성, 재사용성을 제공해줍니다. 본 코드 전에 테스트 코드를 먼저 작성하고, 테스트의 실행을 자동화하여 코드의 변경이 있을 때마다 테스트의 통과 여부를 바로바로 알 수 있도록 합니다. 이것을 효율적으로 하기 위해서 테스트의 실행은 빨라야 하며, 테스트가 스스로 통과/실패를 판단할 수 있는 자가검증을 할 수 있어야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;함수, 클래스, 모듈은 SOLID를 지키기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단일 책임 원칙은 모듈의 변경 이유를 하나로 제한합니다. 모듈의 책임이 명확해지므로 변수명 작명이 쉬워집니다. 간결한 이름이 떠오르지 않는다면 모듈의 크기가 너무 커서 그렇습니다. 모듈의 이름이 모호하다면 모듈의 책임이 너무 많아서 그렇습니다. 또한 단일 책임 원칙을 위해 함수는 사이드 이팩트를 만들지 않아야 합니다. 이것을 지키는 것은 예측가능성을 높여줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개방 폐쇄 원칙은 수정을 쉽게 만듭니다. 리스코프 치환 원칙은 유연성을 제공합니다. 인터페이스 분리 원칙은 불필요한 코드를 제거합니다. 의존성 역전 원칙은 안정된 추상화를 만듭니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;오류 코드 대신 예외 코드 사용하기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;try블록은 트랜잭션과 비슷합니다. try블록 안에 코드가 실패하면 catch에서는 애플리케이션의 상태를 try실행 이전 상태로 일관성있게 유지해야 합니다. try-catch-finally문부터 작성하면 try블록에서 무슨 일이 생기든 호출자가 기대하는 상태를 정의하기 쉬워집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;null을 전달하거나 반환하지 말기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;null 반환은 일거리를 늘릴 뿐만 아니라 문제를 호출자에게 떠넘깁니다. 좋은 방법은 null대신 예외나 특수 사례 객체를 반환하는 것입니다. 예를 들어 정상적인 경우 배열을 반환하기로 되어있다면 null이 아니라 빈 배열을 반환할 수 있는지를 검토해봅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;동시성 방어 하기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단일 책임 원칙을 지켜 동시성 코드와 동시성이 아닌 코드를 분리합니다. 동시성 하나만 다루는 것도 충분히 어렵기 때문입니다. 객체는 불변 객체를 사용합니다. 읽기 전용으로만 사용하면 동시 업데이트의 문제를 해소할 수 있습니다. 스레드도 마찬가지로 가능한 독립적으로 구현하여 공유자원을 사용하지 않습니다. 만약 공유자원이 있다면 최대한 줄이고 캡슐화합니다. 언어, 라이브러리, 실행 모델을 이해하고 올바른 동시성 코드를 작성합니다. 동기화하는 메서드들 사이의 의존관계를 이해하고 동기화하는 부분을 최대한 작게 만듭니다. 시스템을 종료하는 코드는 초기부터 고민하고 구현합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 포스트는&lt;b&gt;&lt;i&gt;&amp;nbsp;로버트 C. 마틴&lt;/i&gt;&lt;/b&gt;의 &lt;i&gt;&lt;b&gt;클린 코드&lt;/b&gt;&lt;/i&gt;를 읽고 그 내용을 기반으로 재창작한 글입니다. 책에서는 훨씬 더 자세하게 클린 코드의 중요성과 구체적인 가이드라인들을 배울 수 있습니다. 아직 &lt;span&gt;클린 코드에 감이 안온다거나 더 자세하게 배워보고 싶다면 이 책을 읽는 것은 최고의 선택지가 될 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Info</category>
      <category>로버트 C. 마틴</category>
      <category>클린 코드</category>
      <author>cocoder16</author>
      <guid isPermaLink="true">https://cocoder16.tistory.com/79</guid>
      <comments>https://cocoder16.tistory.com/79#entry79comment</comments>
      <pubDate>Mon, 12 Sep 2022 08:08:47 +0900</pubDate>
    </item>
    <item>
      <title>Jest + react testing library로 react 테스트 코드 작성하기</title>
      <link>https://cocoder16.tistory.com/73</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;무엇을 테스트할까?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React 컴포넌트의 인터페이스와 렌더링, 유저와의 상호작용 이후에 변화하는 데이터(상태) 그리고 네비게이팅과 라우터 경로에 따른 렌더링 테스트를 할 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;UI 컴포넌트&lt;/b&gt; 테스트는 매우 상당히 복잡합니다. 우선, 조건에 따른 렌더링을 테스트합니다. props와 state가 조건으로 주어지며, 더 넓게 보면 브라우저의 종류와 viewport의 크기도 조건으로 고려합니다. 그리고, 유저에게 인터페이스를 제공하기 때문에 유저가 UI에 할 수 있는 모든 행동 시나리오를 테스트합니다. 이 경우 네이밍이나 단언 컨벤션은 TDD가 아닌 BDD를 따르는 것이 좋습니다. 보편 언어(&lt;span style=&quot;background-color: #ffffff; color: #4d5156;&quot;&gt;Ubiquitous Language&lt;/span&gt;)를 사용하여 비개발자도 읽을 수 있도록 유저 입장에서 시나리오를 작성하는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 컴포넌트는 합성구조로 이루어져 있다는 점으로 인해 많은 stub or mock 객체들을 필요로 하게 됩니다. UI는 가장 낮은 단계에서 가장 강한 의존성을 가지는 계층이기 때문에 http request, redux store, stylesheet 등 외부에 의존성을 가지는 것들이 상당히 많습니다. 따라서 테스트를 위해 컴포넌트를 setup하기 위해 필요한 configuration도 복잡합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유저가 UI와 상호작용을 하면 해당 UI가 도메인과 결합되어 있는 경우 데이터가 변화하는 시나리오가 많습니다. React에서는 이 데이터를 상태로서 관리하는데, 의존성을 편하게 관리하기 위해 상태 관리 라이브러리를 보편적으로 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서는 상태관리 라이브러리인 &lt;b&gt;Redux&lt;/b&gt;를 테스트 하는 방법을 소개할 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 &lt;b&gt;react-router&lt;/b&gt;를 네비게이팅(UI를 통한 url이동)과 url 경로(path) 변화에 따른 렌더링 테스트를 할 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;무엇을 테스트하지 않을까?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 스냅샷 테스트&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴포넌트 렌더링의 결과는 단언으로 self-validating 하는 것을 추구합니다. 따라서 스냅샷 테스팅 기법은 소개하지 않으려고 합니다. 스냅샷 테스트는 렌더링 결과를 파일로 저장하여 다음에 테스트할 때 렌더링 결과가 기존 결과와 다르면 차이점을 보여주고 update를 할 것인지 말 것인지 개발자가 수작업으로 판단하여 결과를 갱신해줘야 합니다. 개발자가 수작업으로 결과를 비교하면 개발자의 판단이 개입되며 이 판단은 100% 믿을 수가 없는 것이고 개발자의 작업 효율도 굉장히 떨어집니다. 따라서 저는 좋은 테스트는 self-validating 해야 한다고 믿습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 리덕스 액션&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리덕스는 flux 패턴으로 데이터가 단방향으로 흐릅니다. (action -&amp;gt; dispatch -&amp;gt; store -&amp;gt; view)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유저가 행하는 시나리오에 대해 테스트를 하는 BDD는 flux 패턴 전체를 커버합니다. 반면 각 모듈을 개발할 때마다 그 전에 테스트를 작성해야 하는 TDD는 action, dispatch, reducer 각각에 대해 유닛 테스트를 작성해야 하나 고민이 듭니다. 이에 대해 제가 내린 결론은 reducer에 대해서만 유닛 테스트를 작성하자입니다. action과 action 생성 함수는 typescript의 인터페이스를 잘 사용하고 있다면 굳이 테스트로 커버칠 코드가 없다고 느꼈습니다. dispatch는 UI와 결합되어있고 실제로 UI에서 호출하기 때문에 BDD로 커버하는게 더 유리하다고 생각합니다. 마찬가지로 action 또한 BDD로 커버가 됩니다. 반면 reducer는 데이터를 가공하는 로직을 담고 있기 때문에 유닛 테스트를 작성하는게 유리하다고 생각합니다. reducer는 순수함수기때문에 실제로 유닛 테스트를 작성하는게 매우 쉽기도 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;프로젝트 시작&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Create React App 으로 프로젝트를 시작하면 jest와 react/testing-library가 기본적으로 내장되어있습니다. 이 환경에서 실습을 하려고 합니다. 또한 typescript를 사용해 진행하겠습니다. 터미널에서 다음 명령어를 실행하여 프로젝트를 시작합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1656485470607&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npx create-react-app jest-react --template typescript&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;컴포넌트 렌더링 테스트&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴포넌트 렌더링 테스트를 self-validating 할 수 있는 테스트로 작성해보겠습니다. 테스트 방법은 다음과 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. default props와 함께 컴포넌트를 마운트합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. DOM query를 하여 얻은 실제값과 기대값을 비교합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 비교 대상은 텍스트, 이미지 등 사용자에게 반드시 보여야 하는 요소들입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시로 score를 props로 받아 출력하는 Score 컴포넌트를 개발해보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인터페이스를 먼저 작성합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* Score.tsx&lt;/p&gt;
&lt;pre id=&quot;code_1656486469007&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export interface Props {
  score: number;
}

function Score({ score }: Props) {
  return &amp;lt;div&amp;gt;{score}&amp;lt;/div&amp;gt;;
}

export default Score;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Score 컴포넌트는 score props 인터페이스를 가집니다. 렌더링 부분은 컴파일 에러 방지를 위해 임의로 div태그와 score값을 넣었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 테스트 코드를 작성합니다. 인터페이스를 임포트하여 인터페이스를 지키며 테스트를 작성합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* Score.test.tsx&lt;/p&gt;
&lt;pre id=&quot;code_1656485244569&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { render, screen } from &quot;@testing-library/react&quot;;

import Score, { Props } from &quot;./Score&quot;;

describe(&quot;Score&quot;, () =&amp;gt; {
  const initialProps: Props = {
    score: 0,
  };

  it(&quot;Render initial score&quot;, () =&amp;gt; {
    render(&amp;lt;Score {...initialProps} /&amp;gt;);

    expect(screen.getByText(initialProps.score + &quot;점&quot;)).toBeInTheDocument();
  });
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 터미널에서 yarn test를 입력하여 테스트를 실행합니다. 본 코드 전에 테스트 코드를 먼저 작성했기 때문에 실패한 테스트가 나타납니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;939&quot; data-origin-height=&quot;785&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eArjqJ/btrF4GT2Tec/eM1oQb7fivPz5k9BCnjQ61/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eArjqJ/btrF4GT2Tec/eM1oQb7fivPz5k9BCnjQ61/img.png&quot; data-alt=&quot;Score 컴포넌트 test 실패&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eArjqJ/btrF4GT2Tec/eM1oQb7fivPz5k9BCnjQ61/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeArjqJ%2FbtrF4GT2Tec%2FeM1oQb7fivPz5k9BCnjQ61%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;573&quot; height=&quot;479&quot; data-origin-width=&quot;939&quot; data-origin-height=&quot;785&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Score 컴포넌트 test 실패&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 테스트를 통과시키기 위해 본 코드를 마저 작성합니다. 간단한 예시기 때문에 &quot;점&quot;이라는 텍스트만 추가하면 테스트를 통과시킬 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* Score.tsx&lt;/p&gt;
&lt;pre id=&quot;code_1656487082404&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export interface Props {
  score: number;
}

function Score({ score }: Props) {
  return &amp;lt;div&amp;gt;{score}점&amp;lt;/div&amp;gt;;
}

export default Score;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트가 통과된 모습입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;520&quot; data-origin-height=&quot;262&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bPVcDK/btrF4e4OZMM/BQU5DaxZ2Tq9oPVkjTh1uK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bPVcDK/btrF4e4OZMM/BQU5DaxZ2Tq9oPVkjTh1uK/img.png&quot; data-alt=&quot;Score 컴포넌트 테스트 통과&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bPVcDK/btrF4e4OZMM/BQU5DaxZ2Tq9oPVkjTh1uK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbPVcDK%2FbtrF4e4OZMM%2FBQU5DaxZ2Tq9oPVkjTh1uK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;353&quot; height=&quot;178&quot; data-origin-width=&quot;520&quot; data-origin-height=&quot;262&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Score 컴포넌트 테스트 통과&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div class=&quot;revenue_unit_item adsense  responsive&quot;&gt;&lt;ins class=&quot;adsbygoogle&quot; style=&quot;display: block;&quot; data-ad-host=&quot;ca-host-pub-9691043933427338&quot; data-ad-client=&quot;ca-pub-9807016842906892&quot; data-ad-format=&quot;auto&quot;&gt;&lt;/ins&gt;
&lt;script&gt;(adsbygoogle = window.adsbygoogle || []).push({}); &lt;/script&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;리덕스 테스트&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리덕스 툴킷을 설치합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1656495144166&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;yarn add @reduxjs/toolkit react-redux&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리덕스는 서문에서 말했다시피 리듀서 함수만 테스트를 할 것입니다. 또한 redux toolkit을 사용하여 보일러 플레이트를 최소화할 것입니다. 먼저 store configuration코드를 작성할 것인데 redux toolkit 1.8.x 버전 기준으로 다음과 같으며 해당 코드는 &lt;a href=&quot;https://redux-toolkit.js.org/tutorials/quick-start#create-a-redux-store&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;redux 공식문서&lt;/a&gt;에 가이드되어있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* store/index.ts&lt;/p&gt;
&lt;pre id=&quot;code_1656487583746&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { configureStore } from &quot;@reduxjs/toolkit&quot;;

export const configuration = {
  reducer: {},
};

export const store = configureStore(configuration);

// Infer the `RootState` and `AppDispatch` types from the store itself
export type RootState = ReturnType&amp;lt;typeof store.getState&amp;gt;;

export type AppDispatch = typeof store.dispatch;

export type ReduxAction&amp;lt;Payload&amp;gt; = {
  type: string;
  payload: Payload;
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 score라는 데이터를 리덕스 스토어에서 관리하며, 어떤 액션을 디스패치하면 해당 score값을 더하는 flux패턴을 하나 만드려고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;score에 대한 flux패턴을 가지고 있는 store/score.ts 파일을 만들어 state타입, 액션과 액션 생성 함수 그리고 리듀서를 선언합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* store/score.ts&lt;/p&gt;
&lt;pre id=&quot;code_1656490610890&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { createAction, createReducer } from &quot;@reduxjs/toolkit&quot;;

import { ReduxAction } from &quot;.&quot;;

// state 타입 정의
export type Score = number;

export type ScoreState = {
  score: Score;
};

// 액션 정의
export const actionTypes = {
  ADD: &quot;SCORE/ADD&quot;,
};

// 액션 생성 함수
export const action = {
  addScore: createAction&amp;lt;number&amp;gt;(actionTypes.ADD),
};

// 초기 state
const initialState: ScoreState = {
  score: 0,
};

// 리듀서
export const reducer = {
  add: (state: ScoreState, action: ReduxAction&amp;lt;unknown&amp;gt;) =&amp;gt; {},
};

const scoreReducer = createReducer(initialState, (builder) =&amp;gt; {
  builder.addCase(action.addScore, reducer.add);
});

export default scoreReducer;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;SCORE/ADD&quot;라는 액션에 대한 리듀서 함수는 redcuer.add입니다. 이제&amp;nbsp;reducer.add에 대한 테스트 코드를 작성할 것입니다. 원하는 기능은 새로운 숫자를 받았을 때 해당 수를 score에 더하는 것입니다. 초기 score 값 0에 1을 더하면 score 값이 1이 되는 정상값 테스트 케이스를 하나 추가하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* score.test.ts&lt;/p&gt;
&lt;pre id=&quot;code_1656490661043&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { reducer, ScoreState, Score, actionTypes } from &quot;./score&quot;;
import { ReduxAction } from &quot;.&quot;;

describe(&quot;score reducers&quot;, () =&amp;gt; {
  let state: ScoreState = {
    score: 0,
  };

  beforeEach(() =&amp;gt; {
    state = {
      score: 0,
    };
  });

  test(&quot;Given score is 0, When add score 1, Then score is 1&quot;, () =&amp;gt; {
    const action: ReduxAction&amp;lt;Score&amp;gt; = {
      type: actionTypes.ADD,
      payload: 1,
    };

    reducer.add(state, action);

    expect(state.score).toEqual(1);
  });
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 해당 테스트를 통과시키기 위해 리듀서 함수를 작성합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* store/score.ts&lt;/p&gt;
&lt;pre id=&quot;code_1656490980605&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export const reducer = {
  add: (state: ScoreState, action: ReduxAction&amp;lt;Score&amp;gt;) =&amp;gt; {
    state.score += action.payload;
  },
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 테스트가 통과합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Ajax 테스트&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ajax를 이용한 http 통신 테스트는 mock객체를 이용합니다. 우리가 개발 중인 리액트 애플리케이션은 외부 API를 통제할 수 없기 때문에 외부 API는 mock 객체로 만들어서 테스트를 합니다. 따라서 어떤 요청을 보낼 때 어떤 응답을 받을 것인지 mock객체로 미리 작성해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;외부 API가 정상적으로 동작하는지 아닌지는 우리가 테스트해야 할 관심사가 아닙니다. 따라서 ajax 테스트를 따로 작성하기보다는 유저 입장에서의 시나리오를 작성하는 컴포넌트의 기능 테스트에서 ajax 요청이 로직 안에 들어간다면 해당 요청을 mocking 하는 것이 우리가 할 일입니다. 바로 뒤에 나올 컴포넌트 기능 테스트에 예제가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;컴포넌트 기능 테스트&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 리덕스와 ajax에 의존하는 컴포넌트를 만들어서 컴포넌트의 통합 테스트를 하려고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 우선 Score 컴포넌트의 상위 컴포넌트인 ScoreBoard 컴포넌트를 만들겠습니다. 이 컴포넌트는 score값을 리덕스로 관리하며 Score 컴포넌트에게 score값을 props로 내려줍니다. 또한 +버튼을 가지고 있어 해당 버튼을 누르면 아까 만들었던 SCORE/ADD 액션이 dispatch 되도록 하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 또한 ScoreBoard컴포넌트는 외부 API에 요청을 보내 초기 score 값을 받아오도록 하겠습니다. 이를 위해 리덕스에는 또 다른 액션과 리듀서가 추가되어야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다소 복잡해지는데 1번부터 차근차근 진행하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 ScoreBoard 컴포넌트와 테스트 파일을 만듭니다. 그리고 + 텍스트를 렌더링하는지 테스트합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* ScoreBoard.test.tsx&lt;/p&gt;
&lt;pre id=&quot;code_1656491948433&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { render, screen } from &quot;@testing-library/react&quot;;

import ScoreBoard from &quot;./ScoreBoard&quot;;

describe(&quot;ScoreBoard&quot;, () =&amp;gt; {
  it(&quot;Render add button&quot;, () =&amp;gt; {
    render(&amp;lt;ScoreBoard /&amp;gt;);

    expect(screen.getByText(&quot;+&quot;)).toBeInTheDocument();
  });
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* ScoreBoard.tsx&lt;/p&gt;
&lt;pre id=&quot;code_1656492013877&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function ScoreBoard() {
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;button type=&quot;button&quot;&amp;gt;+&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}

export default ScoreBoard;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 버튼을 누를 때마다 score가 5씩 증가하도록 만들겠습니다. score는 컴포넌트 내부가 아닌 리덕스 스토어 안에 있기 때문에,&amp;nbsp;test 파일에서 컴포넌트를 마운트할 때, store를 provider로 제공하도록 해야 합니다. 또한 userEvent를 이용해 +에 대해 클릭이벤트를 발생시킨 후 변경된 score를 렌더링하는지 테스트합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* ScoreBoard.test.tsx&lt;/p&gt;
&lt;pre id=&quot;code_1656494853220&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { render, screen } from &quot;@testing-library/react&quot;;
import userEvent from &quot;@testing-library/user-event&quot;;
import { Provider } from &quot;react-redux&quot;;
import { configureStore } from &quot;@reduxjs/toolkit&quot;;

import ScoreBoard from &quot;./ScoreBoard&quot;;
import { configuration } from &quot;../store&quot;;

describe(&quot;ScoreBoard&quot;, () =&amp;gt; {
  it(&quot;Render add button&quot;, () =&amp;gt; {
    render(
      &amp;lt;Provider store={configureStore(configuration)}&amp;gt;
        &amp;lt;ScoreBoard /&amp;gt;
      &amp;lt;/Provider&amp;gt;
    );

    expect(screen.getByText(&quot;+&quot;)).toBeInTheDocument();
  });

  test(&quot;Given score is 0, When click +, Then score is 5&quot;, () =&amp;gt; {
    render(
      &amp;lt;Provider store={configureStore(configuration)}&amp;gt;
        &amp;lt;ScoreBoard /&amp;gt;
      &amp;lt;/Provider&amp;gt;
    );
    expect(screen.getByText(&quot;0점&quot;)).toBeInTheDocument();
    
    userEvent.click(screen.getByText(&quot;+&quot;));

    expect(screen.getByText(&quot;5점&quot;)).toBeInTheDocument();
  });
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;store에 score 리듀서를 추가하기 위해 configuration을 수정합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* store/index.ts&lt;/p&gt;
&lt;pre id=&quot;code_1656497429731&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export const configuration = {
  reducer: {
    score: scoreReducer,
  },
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ScoreBoard 컴포넌트에 리덕스 로직을 결합합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* ScoreBoard.tsx&lt;/p&gt;
&lt;pre id=&quot;code_1656494891051&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useDispatch, useSelector } from &quot;react-redux&quot;;

import Score from &quot;./Score&quot;;
import { action } from &quot;../store/score&quot;;
import { RootState, AppDispatch } from &quot;../store&quot;;

function ScoreBoard() {
  const score = useSelector((state: RootState) =&amp;gt; state.score.score);
  const dispatch = useDispatch&amp;lt;AppDispatch&amp;gt;();

  const onClick = () =&amp;gt; {
    dispatch(action.addScore(5));
  };

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;button type=&quot;button&quot; onClick={onClick}&amp;gt;
        +
      &amp;lt;/button&amp;gt;
      &amp;lt;Score score={score} /&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}

export default ScoreBoard;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리덕스의 flux 패턴 전체 테스트는 이렇게 컴포넌트 기능 테스트 안에 녹아들게 됩니다. 이것은 컴포넌트가 리덕스와 가지는 결합성 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로 ajax 통신을 위해 axios 모듈을 설치합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1656495095254&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;yarn add axios&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로 외부 API를 통해 score 값을 받아오도록 리덕스에 로직을 추가합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* store/score.ts&lt;/p&gt;
&lt;pre id=&quot;code_1656495637204&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import {
  createAction,
  createReducer,
  createAsyncThunk,
} from &quot;@reduxjs/toolkit&quot;;
import axios from &quot;axios&quot;;

import { ReduxAction } from &quot;.&quot;;

export type Score = number;

export type ScoreState = {
  score: Score;
};

export const actionTypes = {
  ADD: &quot;SCORE/ADD&quot;,
  GET: &quot;SCORE/GET&quot;,
};

export const action = {
  addScore: createAction&amp;lt;number&amp;gt;(actionTypes.ADD),
  getScore: createAsyncThunk(
    actionTypes.GET,
    async (_, { rejectWithValue }) =&amp;gt; {
      return axios
        .get(&quot;http://localhost:4000/score&quot;)
        .then((response) =&amp;gt; response.data.score);
    }
  ),
};

const initialState: ScoreState = {
  score: 0,
};

export const reducer = {
  add: (state: ScoreState, action: ReduxAction&amp;lt;Score&amp;gt;) =&amp;gt; {
    state.score += action.payload;
  },
  get: (state: ScoreState, action: ReduxAction&amp;lt;Score&amp;gt;) =&amp;gt; {
    state.score = action.payload;
  },
};

const scoreReducer = createReducer(initialState, (builder) =&amp;gt; {
  builder
    .addCase(action.addScore, reducer.add)
    .addCase(action.getScore.fulfilled, reducer.get);
});

export default scoreReducer;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리듀서의 테스트 케이스는 다음과 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* store/score.test.ts&lt;/p&gt;
&lt;pre id=&quot;code_1656495699128&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;it(&quot;get initial score 100&quot;, () =&amp;gt; {
    const action: ReduxAction&amp;lt;Score&amp;gt; = {
      type: actionTypes.GET,
      payload: 100,
    };

    reducer.get(state, action);

    expect(state.score).toEqual(100);
  });
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 마지막으로 ScoreBoard가 마운트될 때 요청을 보내 초기 score값을 가져오도록 하겠습니다. ScoreBoard 테스트 케이스를 추가합니다. 그전에 axios mock 객체를 쉽게 만들어주는 라이브러리인 axios-mock-adapter를 설치합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1656495885933&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;yarn add -D axios-mock-adapter&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* ScoreBoard.test.tsx&lt;/p&gt;
&lt;pre id=&quot;code_1656496656093&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { render, screen } from &quot;@testing-library/react&quot;;
import userEvent from &quot;@testing-library/user-event&quot;;
import { Provider } from &quot;react-redux&quot;;
import { configureStore } from &quot;@reduxjs/toolkit&quot;;
import axios from &quot;axios&quot;;
import MockAdapter from &quot;axios-mock-adapter&quot;;

import ScoreBoard from &quot;./ScoreBoard&quot;;
import { configuration } from &quot;../store&quot;;

describe(&quot;ScoreBoard&quot;, () =&amp;gt; {
  // (생략)
  
  it(&quot;get initial score 100 and api be called once&quot;, async () =&amp;gt; {
    const mock = new MockAdapter(axios, { delayResponse: 100 });
    mock.onGet(&quot;http://localhost:4000/score&quot;).reply(200, { score: 100 });

    render(
      &amp;lt;Provider store={configureStore(configuration)}&amp;gt;
        &amp;lt;ScoreBoard /&amp;gt;
      &amp;lt;/Provider&amp;gt;
    );

    await screen.findByText(&quot;100점&quot;);
    expect(mock.history.get.length).toBe(1);
  });
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;mock 객체를 만들어서 http://localhost:4000/score로 요청을 보내면 { score: 100 } 데이터를 응답받도록 합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 mock 객체는 요청을 받은 후 응답을 내리기까지 100ms의 딜레이를 가집니다. (delayResponse: 100)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 screen.findByText() 메서드는 해당 값을 찾을 때까지 기다립니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;mock 객체는 받은 요청들을 history에 저장합니다. 요청이 불필요하게 여러 번 가지 않도록 하기 위해 1회만 요청을 하는지도 테스트합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1656577141184&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;expect(mock.history.get.length).toBe(1);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트를 통과시키기 위해 ScoreBoard 컴포넌트가 마운트되면 해당 api로 요청을 보내도록 만듭니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* ScoreBoard.tsx&lt;/p&gt;
&lt;pre id=&quot;code_1656496845138&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useEffect } from &quot;react&quot;;
import { useDispatch, useSelector } from &quot;react-redux&quot;;

import Score from &quot;./Score&quot;;
import { action } from &quot;../store/score&quot;;
import { RootState, AppDispatch } from &quot;../store&quot;;

function ScoreBoard() {
  const score = useSelector((state: RootState) =&amp;gt; state.score.score);
  const dispatch = useDispatch&amp;lt;AppDispatch&amp;gt;();

  const onClick = () =&amp;gt; {
    dispatch(action.addScore(5));
  };

  useEffect(() =&amp;gt; {
    dispatch(action.getScore());
  }, [dispatch]);

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;button type=&quot;button&quot; onClick={onClick}&amp;gt;
        +
      &amp;lt;/button&amp;gt;
      &amp;lt;Score score={score} /&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}

export default ScoreBoard;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ScoreBoard 컴포넌트가 마운트되면 getScore를 dispatch하고 이때 정의된 액션 생성 함수에서 API로 요청을 보내는데 요청에 대해 실제 응답값을 받는 것이 아니라 mock객체가 { score: 100 }를 응답합니다. 이것을 리듀서가 처리하고 score 값은 100이 되어 컴포넌트는 리렌더링 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;localhost:4000 서버를 띄워놓지 않았기에 터미널에서 이에 대한 에러를 뱉을 수 있지만, 우리는 그냥 localhost:4000이라는 API 서버가 있다고 가정하고 무시하고 진행합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;응답이 실패하는 경우에 대해 테스트는 다음과 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;응답이 실패하면 '로딩 실패'라는 텍스트를 띄우겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1656574555435&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;test(&quot;When fail to get initial score, Then show error message and api be called once&quot;, async () =&amp;gt; {
  const mock = new MockAdapter(axios, { delayResponse: 100 });
  mock.onGet(&quot;http://localhost:4000/score&quot;).reply(500);

  render(
    &amp;lt;Provider store={configureStore(configuration)}&amp;gt;
      &amp;lt;ScoreBoard /&amp;gt;
    &amp;lt;/Provider&amp;gt;
  );

  await screen.findByText(&quot;로딩 실패&quot;);
  expect(mock.history.get.length).toBe(1);
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* ScoreBoard.tsx&lt;/p&gt;
&lt;pre id=&quot;code_1656574622851&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useState } from &quot;react&quot;;

// (생략)

function ScoreBoard() {
  // (생략)
  const [errorMessage, setErrorMessage] = useState&amp;lt;string&amp;gt;(&quot;&quot;);

  // (생략)

  useEffect(() =&amp;gt; {
    dispatch(action.getScore())
      .unwrap()
      .catch((error) =&amp;gt; {
        setErrorMessage(&quot;로딩 실패&quot;);
      });
  }, [dispatch]);

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;button type=&quot;button&quot; onClick={onClick}&amp;gt;
        +
      &amp;lt;/button&amp;gt;
      &amp;lt;Score score={score} /&amp;gt;
      &amp;lt;div&amp;gt;{errorMessage &amp;amp;&amp;amp; errorMessage}&amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div class=&quot;revenue_unit_item adsense  responsive&quot;&gt;&lt;ins class=&quot;adsbygoogle&quot; style=&quot;display: block;&quot; data-ad-host=&quot;ca-host-pub-9691043933427338&quot; data-ad-client=&quot;ca-pub-9807016842906892&quot; data-ad-format=&quot;auto&quot;&gt;&lt;/ins&gt;
&lt;script&gt;(adsbygoogle = window.adsbygoogle || []).push({}); &lt;/script&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;리액트 라우터 테스트&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리액트 라우터를 설치합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1656497289340&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;yarn add react-router-dom&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 가지 path를 만드려고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 root인 &quot;/&quot; 경로에는 score 페이지로 이동할 수 있는 링크를 가집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 &quot;/score&quot; 경로는 ScoreBoard 컴포넌트를 렌더링하도록 하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;루트 경로에 렌더링해줄 Home 컴포넌트를 만들겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* Home.test.tsx&lt;/p&gt;
&lt;pre id=&quot;code_1656520254888&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { render, screen } from &quot;@testing-library/react&quot;;

import Home from &quot;./Home&quot;;

describe(&quot;Home&quot;, () =&amp;gt; {
  it(&quot;Render initial score&quot;, () =&amp;gt; {
    render(&amp;lt;Home /&amp;gt;);

    expect(screen.getByText(&quot;go to score&quot;)).toBeInTheDocument();
  });
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* Home.tsx&lt;/p&gt;
&lt;pre id=&quot;code_1656520347466&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { Link } from &quot;react-router-dom&quot;;

function Home() {
  return &amp;lt;Link to=&quot;/score&quot;&amp;gt;go to score&amp;lt;/Link&amp;gt;;
}

export default Home;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;react-router-dom의 api를 사용하고 있기 때문에 테스트를 통과하지 못합니다. 테스트 코드에서 location context를 제공하도록 코드를 변경합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* Home.test.tsx&lt;/p&gt;
&lt;pre id=&quot;code_1656520409735&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { render, screen } from &quot;@testing-library/react&quot;;
import { createMemoryHistory } from &quot;history&quot;;
import { Router } from &quot;react-router-dom&quot;;

import Home from &quot;./Home&quot;;

describe(&quot;Home&quot;, () =&amp;gt; {
  it(&quot;Render initial score&quot;, () =&amp;gt; {
    const history = createMemoryHistory();

    render(
      &amp;lt;Router location={history.location} navigator={history}&amp;gt;
        &amp;lt;Home /&amp;gt;
      &amp;lt;/Router&amp;gt;
    );

    expect(screen.getByText(&quot;go to score&quot;)).toBeInTheDocument();
  });
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 링크를 누를 때 &quot;/score&quot;경로로 이동하는 테스트 케이스를 추가합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* Home.test.tsx&lt;/p&gt;
&lt;pre id=&quot;code_1656571632282&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;test(&quot;When click 'go to score' link, Then navigate to '/score'&quot;, () =&amp;gt; {
  const history = createMemoryHistory();

  render(
    &amp;lt;Router location={history.location} navigator={history}&amp;gt;
      &amp;lt;Home /&amp;gt;
    &amp;lt;/Router&amp;gt;
  );

  userEvent.click(screen.getByText(&quot;go to score&quot;));
  
  expect(history.location.pathname).toBe(&quot;/score&quot;);
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴포넌트는 준비되었습니다. 이제 라우트 파일을 하나 따로 만들 것이고, 라우트 파일에 대한 테스트 케이스는 두 가지가 추가됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;/&quot;경로에 진입했을 때 렌더링 상태와 &quot;/score&quot;경로에 진입했을 때 렌더링 상태입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* MainRoutes.test.tsx&lt;/p&gt;
&lt;pre id=&quot;code_1656497958041&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { render, screen } from &quot;@testing-library/react&quot;;
import { createMemoryHistory } from &quot;history&quot;;
import { Router } from &quot;react-router-dom&quot;;
import { Provider } from &quot;react-redux&quot;;
import { configureStore } from &quot;@reduxjs/toolkit&quot;;

import { configuration } from &quot;./store&quot;;
import MainRoutes from &quot;./MainRoutes&quot;;

describe(&quot;MainRoutes&quot;, () =&amp;gt; {
  test(&quot;'/' render Home&quot;, () =&amp;gt; {
    const history = createMemoryHistory();
    history.push(&quot;/&quot;);

    render(
      &amp;lt;Router location={history.location} navigator={history}&amp;gt;
        &amp;lt;Provider store={configureStore(configuration)}&amp;gt;
          &amp;lt;MainRoutes /&amp;gt;
        &amp;lt;/Provider&amp;gt;
      &amp;lt;/Router&amp;gt;
    );

    expect(screen.getByTestId(&quot;home&quot;)).toBeInTheDocument();
  });

  test(&quot;'/score' render ScoreBoard&quot;, () =&amp;gt; {
    const history = createMemoryHistory();
    history.push(&quot;/score&quot;);

    render(
      &amp;lt;Router location={history.location} navigator={history}&amp;gt;
        &amp;lt;Provider store={configureStore(configuration)}&amp;gt;
          &amp;lt;MainRoutes /&amp;gt;
        &amp;lt;/Provider&amp;gt;
      &amp;lt;/Router&amp;gt;
    );

    expect(screen.getByTestId(&quot;score-board&quot;)).toBeInTheDocument();
  });
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 요소를 렌더링할지는 각 컴포넌트 테스트에서 이미 했기 때문에, 라우터 테스트에서는 각 경로에 따라 어떤 컴포넌트를 렌더링하는지를 테스트합니다. 따라서 Home 컴포넌트와 ScoreBoard 컴포넌트에 data-testid를 붙여줍니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* Home.tsx&lt;/p&gt;
&lt;pre id=&quot;code_1656573738978&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function Home() {
  return (
    &amp;lt;Link data-testid=&quot;home&quot; to=&quot;/score&quot;&amp;gt;
      go to score
    &amp;lt;/Link&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* ScoreBoard.tsx&lt;/p&gt;
&lt;pre id=&quot;code_1656573759768&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// (생략)
  return (
    &amp;lt;div data-testid=&quot;score-board&quot;&amp;gt;
      &amp;lt;button type=&quot;button&quot; onClick={onClick}&amp;gt;
        +
      &amp;lt;/button&amp;gt;
      &amp;lt;Score score={score} /&amp;gt;
      &amp;lt;div&amp;gt;{errorMessage &amp;amp;&amp;amp; errorMessage}&amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  );&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MainRoutes 컴포넌트로 테스트 케이스를 통과시킵니다. 원래는 하나의 테스트케이스를 쓸 때마다 테스트를 통과시키기 위한 코드를 구현하는 것이 바람직하지만 코드를 올리기 위한 편의 때문에 한 번에 두 케이스를 써서 올렸습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* MainRoutes.tsx&lt;/p&gt;
&lt;pre id=&quot;code_1656573776089&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { Routes, Route } from &quot;react-router-dom&quot;;

import Home from &quot;./components/Home&quot;;
import ScoreBoard from &quot;./components/ScoreBoard&quot;;

function MainRoutes() {
  return (
    &amp;lt;Routes&amp;gt;
      &amp;lt;Route path=&quot;/&quot; element={&amp;lt;Home /&amp;gt;} /&amp;gt;
      &amp;lt;Route path=&quot;score&quot; element={&amp;lt;ScoreBoard /&amp;gt;} /&amp;gt;
    &amp;lt;/Routes&amp;gt;
  );
}

export default MainRoutes;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마무리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기까지 각 유닛을 개발하는 과정에서 테스트 코드를 어떤 식으로 작성할지 봤습니다. 그런데 아직 최상위 컴포넌트인 App.tsx를 개발하지 않았습니다. 최상위 컴포넌트를 테스트한다는 것은 모든 컴포넌트가 조합된 상태에서의 통합 테스트를 의미하기 때문에 따로 다루지 않았습니다. 저는 이에 대한 테스트는 E2E 테스트를 하는 것이 적합하다고 보고 있고 E2E 테스트는 지금까지 작성한 유닛 테스트와는 독립적으로 작성하는 것이기에 이번 포스팅에서 같이 다루지 않는 것이 덜 혼란스럽겠다는 생각입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 컴포넌트가 합성된 통합 테스트를 하지 않은 것과 더불어 또 아직 하지 않은 것이 있습니다. 지금 테스트 코드는 컴포넌트를 셋업 하는데 많은 양의 코드를 중복 사용하고 있습니다. 이 중복을 제거하여 보일러 플레이트를 간략화하는 작업을 해놓아야 앞으로 테스트를 계속 추가하는데 공수가 줄어들 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체 코드는 &lt;a href=&quot;https://github.com/cocoder16/jest-on-react&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;깃허브 링크&lt;/a&gt;에서 볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>React.js</category>
      <category>@testing-library/react</category>
      <category>axios test</category>
      <category>JEST</category>
      <category>react</category>
      <category>react component test</category>
      <category>react testing library</category>
      <category>redux test</category>
      <category>userEvent</category>
      <category>리액트 유닛 테스트</category>
      <category>리액트 테스트 코드</category>
      <author>cocoder16</author>
      <guid isPermaLink="true">https://cocoder16.tistory.com/73</guid>
      <comments>https://cocoder16.tistory.com/73#entry73comment</comments>
      <pubDate>Mon, 4 Jul 2022 08:00:57 +0900</pubDate>
    </item>
    <item>
      <title>MongoDB 설치 및 사용법 (실행 및 기본 쿼리)</title>
      <link>https://cocoder16.tistory.com/72</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;MongoDB 설치&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 공식 홈페이지에서 다운로드할 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.mongodb.com/try/download/community&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.mongodb.com/try/download/community&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1648689570858&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;MongoDB Community Download&quot; data-og-description=&quot;Download the Community version of MongoDB's non-relational database server from MongoDB's download center.&quot; data-og-host=&quot;www.mongodb.com&quot; data-og-source-url=&quot;https://www.mongodb.com/try/download/community&quot; data-og-url=&quot;https://www.mongodb.comtry&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/qTm8H/hyNR22Mj9Z/eiUsHWEVPFcFuxuKGfiboK/img.png?width=1200&amp;amp;height=601&amp;amp;face=0_0_1200_601&quot;&gt;&lt;a href=&quot;https://www.mongodb.com/try/download/community&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.mongodb.com/try/download/community&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/qTm8H/hyNR22Mj9Z/eiUsHWEVPFcFuxuKGfiboK/img.png?width=1200&amp;amp;height=601&amp;amp;face=0_0_1200_601');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;MongoDB Community Download&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Download the Community version of MongoDB's non-relational database server from MongoDB's download center.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.mongodb.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MSI 패키지로 운영체제를 잘 골라서 다운로드하면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설치가 완료되었다면 아마도 윈도우라면 Program Files/MongoDB/server/버전/bin 경로가 있을 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터를 저장할 폴더를 만들기 위해 bin 디렉터리에서 data/db 디렉터리를 새로 생성해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;MongoDB 사용법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;서버 실행 방법&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;터미널에서 MongoDB 설치 경로의 bin폴더로 이동합니다. 그리고 다음 명령어를 입력하여 DB 서버를 실행할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1648689838106&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;mongod --dbpath &quot;bin디렉터리경로\data\db&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB 서버의 기본 포트 번호는 27017입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;클라이언트 실행 방법&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;터미널 창 혹은 패널을 하나 더 열고, 서버 실행할 때와 마찬가지로 bin 디렉터리로 이동한 후 다음 명령어를 실행합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1648690036883&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;mongo&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트에서는 쿼리를 날릴 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;MongoDB 기본 쿼리문&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;database 사용 명령어&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 존재하는 db 목록보기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1648690061615&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;show dbs&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 현재 사용중인 db명 보기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1648690712459&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;db&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 사용 중인 db 변경하기, 없는 db명이면 새로 db를 생성하고 변경합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1648690761509&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;use 데이터베이스명&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;collection 사용 명령어&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 존재하는 collection 목록보기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1648690847965&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;show collections&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- collection 생성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1648690804203&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;db.createCollection(&quot;콜렉션명&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- collection 제거&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1648690863416&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;db.콜렉션명.drop()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;document 사용 명령어&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- document 1개 이상 추가&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;insert는 document를 1개 혹은 여러 개를 추가할 수 있는 메서드입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1648692052849&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;db.콜렉션명.insert([document1, ...])&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 모든 document select&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1648692173474&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;db.콜렉션명.find()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- document 조건 조회&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1648692248323&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;db.콜렉션명.find({필터})&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필터는 { fieldname: value } 형태로 조건을 넣어주면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 fruits collection에서 count가 1인 document들을 조회하는 쿼리문은 다음과 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1648692325485&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;db.fruits.find({ count: 1 })&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;{}는 조건이 없는 것과 같습니다. 따라서 모든 document를 조회하는 find()와 find({})는 결과가 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- projection fields&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원하는 field만 선택하기 위해서는 find 메서드의 두 번째 파라미터를 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 다음은 fruits collection에서 count가 1인 document들의 color field만 조회하는 쿼리문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1648692739694&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;db.fruits.find({ count: 1 }, { color: 1 })&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- document update&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;updateOne은 조건에 맞는 최초로 발견된 document 1개만 update 하는 메서드입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 fruits collection에서 color가 &quot;red&quot;인 document 1개를 &quot;blue&quot;로 바꾸는 쿼리문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1648693200210&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;db.fruits.updateOne({color: &quot;red&quot;}, {$set: {color: &quot;blue&quot;}})&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조건에 맞는 모든 document를 update 하려면 updateMany를 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1648693280137&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;db.fruits.updateMany({color: &quot;red&quot;}, {$set: {color: &quot;blue&quot;}})&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조건에 맞는 document가 없는 경우는 아무 update도 일어나지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 upsert 옵션을 추가하면 조건에 맞는 document가 없을 때 새 document를 insert 하도록 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- document 제거&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;remove 메서드는 document를 제거하는 메서드입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1648693666787&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;db.콜렉션명.remove({})&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;{}는 모든 걸 선택하는 필터기 때문에 위 쿼리문은 해당 콜렉션에 있는 모든 document를 전부 삭제합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;compass로 접속하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MongoDB를 설치하면 설치 과정 중에 MongoDB compass도 같이 설치가 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;compass는 MongoDB 클라이언트를 사용할 수 있는 GUI 프로그램입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;database, collection, document CRUD에 대해 직관적인 UI 조작을 제공하고 있고, 쿼리문을 날리기도 편하게 되어있습니다. aggregations, schema, index, validation 등에 대해서도 쉬운 UI를 제공하고 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Database</category>
      <category>MongoDB</category>
      <category>MongoDB 사용법</category>
      <category>Mongodb 설치</category>
      <category>Mongodb 실행</category>
      <category>MongoDB 쿼리</category>
      <author>cocoder16</author>
      <guid isPermaLink="true">https://cocoder16.tistory.com/72</guid>
      <comments>https://cocoder16.tistory.com/72#entry72comment</comments>
      <pubDate>Mon, 4 Apr 2022 08:00:30 +0900</pubDate>
    </item>
    <item>
      <title>파이썬 openpyxl로 엑셀 다루기</title>
      <link>https://cocoder16.tistory.com/71</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;엑셀을 쉽게 다루는 파이썬 모듈 openpyxl&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;openpyxl은 엑셀 데이터의 읽고, 쓰고, 수정하기, 엑셀 파일을 열고, 수정하고, 저장하기 등등 엑셀로 할 수 있는 모든 것들을 쉽게 할 수 있도록 API를 제공해주는 파이썬 라이브러리입니다. 공식 문서 링크는 다음과 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://openpyxl.readthedocs.io/en/stable/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://openpyxl.readthedocs.io/en/stable/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1647931006591&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;openpyxl - A Python library to read/write Excel 2010 xlsx/xlsm files &amp;mdash; openpyxl 3.0.9 documentation&quot; data-og-description=&quot;Install openpyxl using pip. It is advisable to do this in a Python virtualenv without system packages: Warning To be able to include images (jpeg, png, bmp,&amp;hellip;) into an openpyxl file, you will also need the &amp;ldquo;pillow&amp;rdquo; library that can be installed with: &quot; data-og-host=&quot;openpyxl.readthedocs.io&quot; data-og-source-url=&quot;https://openpyxl.readthedocs.io/en/stable/&quot; data-og-url=&quot;https://openpyxl.readthedocs.io/en/stable/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://openpyxl.readthedocs.io/en/stable/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://openpyxl.readthedocs.io/en/stable/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;openpyxl - A Python library to read/write Excel 2010 xlsx/xlsm files &amp;mdash; openpyxl 3.0.9 documentation&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Install openpyxl using pip. It is advisable to do this in a Python virtualenv without system packages: Warning To be able to include images (jpeg, png, bmp,&amp;hellip;) into an openpyxl file, you will also need the &amp;ldquo;pillow&amp;rdquo; library that can be installed with:&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;openpyxl.readthedocs.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;pip로 설치하는 방법은 다음과 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1647931260673&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;pip install openpyxl&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;엑셀 데이터 가져오기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미 있는 엑셀 파일을 가져오기 위해서 openpyxl의 load_workbook 함수를 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1647933695183&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from openpyxl import load_workbook

# excel_path = &quot;엑셀파일의경로&quot;
workbook = load_workbook(excel_path)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;엑셀 파일은 여러 시트들을 가질 수 있습니다. 특정 시트에 다음과 같은 방법으로 접근할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1647933923602&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# sheet_name = &quot;시트 이름&quot;
worksheet = workbook[sheet_name]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시트까지는 접근했는데 시트 안에서 특정 셀의 값을 가져오는 방법은 다음과 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1647933991257&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# i = 행 인덱스
# j = 열 인덱스
cell_value = worksheet.cell(row=i, column=j).value&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;엑셀에 대해 필요한 작업들을 다 마친 후에는 워크북을 닫아줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1647934162766&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;workbook.close()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;엑셀 데이터 수정하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;엑셀 데이터를 수정하기 위해 존재하는 엑셀 파일을 위에서 설명한 방법대로 열어줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1647934438805&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from openpyxl import load_workbook

# excel_path = &quot;엑셀파일의경로&quot;
# sheet_name = &quot;시트 이름&quot;
workbook = load_workbook(excel_path)
worksheet = workbook[sheet_name]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제부터 이 워크시트에 5행5열부터 6행6열까지 총 2*2 개 데이터를 수정해야 하는 상황을 가정하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수정할 새 데이터는 다음과 같습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1647934663390&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;a b
c d

new_data = [[&quot;a&quot;, &quot;b&quot;], [&quot;c&quot;, &quot;d&quot;]]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2차원 데이터이므로 단순히 이중 포문으로 코드를 만들 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1647935068277&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;row_start_index = 5
column_start_index = 5
row_size = 2
column_size = 2
new_data = [[&quot;a&quot;, &quot;b&quot;], [&quot;c&quot;, &quot;d&quot;]]

for row_index in range(0, row_size):
    for column_index in range(0, column_size):
        worksheet.cell(
            row=row_start_index + row_index,
            column=column_start_index + column_index,
            value=new_data[row_index][column_index],
        )&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좀 더 유연하게 모든 2차원 리스트에 대해 수정할 수 있는 함수를 만들었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1647935390453&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;def update_range(
    new_data: list,
    excel_path: str,
    sheet_name: str,
    row_start_index: int,
    column_start_index: int,
):
    if not new_data:
        return

    workbook = load_workbook(excel_path)
    worksheet = workbook[sheet_name]

    row_size = len(new_data)
    column_size = len(new_data[0])
    
    for row_index in range(0, row_size):
        for column_index in range(0, column_size):
            worksheet.cell(
                row=row_start_index + row_index,
                column=column_start_index + column_index,
                value=new_data[row_index][column_index],
            )

    workbook.save(excel_path)
    workbook.close()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터를 수정한 후에는 workbook.save(새 엑셀 경로)을 실행해야 저장 완료가 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;셀의 값들 삭제하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;엑셀 셀의 값들을 행 기준으로 삭제하는 방법입니다. worksheet 의 delete_rows 메소드를 활용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1647936113691&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;def delete_rows(excel_path: str, sheet_name: str, index: int = 1, amount: int = 1):
    workbook = load_workbook(excel_path)
    worksheet = workbook[sheet_name]
    worksheet.delete_rows(index, amount)

    workbook.save(excel_path)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 index = 5, amount = 10 이면 5번째 행부터 14번째 행까지 총 10개의 행이 삭제됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;reference: &lt;a href=&quot;https://github.com/cocoder16/python_modules/blob/main/handle_excel_in_openpyxl/handle_excel_in_openpyxl.py&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;github repo&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Python</category>
      <category>Excel</category>
      <category>openpyxl</category>
      <category>python</category>
      <category>파이썬으로 엑셀</category>
      <author>cocoder16</author>
      <guid isPermaLink="true">https://cocoder16.tistory.com/71</guid>
      <comments>https://cocoder16.tistory.com/71#entry71comment</comments>
      <pubDate>Mon, 28 Mar 2022 08:00:24 +0900</pubDate>
    </item>
    <item>
      <title>파이썬으로 윈도우 업무 자동화하기</title>
      <link>https://cocoder16.tistory.com/70</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;아나콘다 및 파이썬 프로그램 준비&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파이썬 개발환경과 실행환경 모두 아나콘다 가상환경으로 통일하면 편리합니다. 그래서 아나콘다를 설치하여 가상환경을 만듭니다. 다른 포스팅에서 파이썬 아나콘다 가상환경 사용법에 대해 정리해놨습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://cocoder16.tistory.com/68&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;파이참에서 아나콘다 가상환경 사용하기&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로 업무 자동화 로직이 작성된 파이썬 파일이 필요합니다. 이미 완성되어있다고 가정하고 저는 간단하게 hello python을 출력하는 예시 프로그램을 준비하겠습니다. main.py를 엔트리 파일로 잡았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* main.py&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1646200329938&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;def main():
  print(&quot;hello python&quot;)

if __name__ == &quot;__main__&quot;:
    main()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;배치파일 만들기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메모장을 열고 다음과 같이 작성합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* main.bat&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1646200512446&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&quot;C:\Users\사용자명\anaconda3\python.exe&quot; &quot;프로젝트디렉터리경로\main.py&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫번째 경로는 아나콘다 설치 디렉터리에 있는 python.exe 경로를 쓰고, 두 번째 경로는 업무 자동화를 위해 만든 파이썬 프로그램의 엔트리 파일 경로를 써줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;혹은 새로 생성한 가상환경을 사용하고 싶다면 다음과 같이 아나콘다를 실행하여 conda명령어를 사용하는 스크립트를 작성합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* main.bat&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1646200929934&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;call C:\Users\사용자명\anaconda3\Scripts\activate.bat

call conda activate 가상환경명
call cd 프로젝트 절대경로
call python main.py
call conda deactivate&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;둘다 엔트리 파일인 main.py를 실행하는 방식이지만 두 번째 방식은 conda 명령어를 작성하고 특정 가상환경을 사용할 수 있다는 점이 다릅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내용을 작성했으면 메모장을 저장할 때 파일형식을 &quot;모든 파일&quot;로 하고, 파일 이름에 .bat을 붙여서 저장합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;279&quot; data-origin-height=&quot;71&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dDVTah/btruNW9sx70/fZFXi0dqku4KKaD5PaQyM0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dDVTah/btruNW9sx70/fZFXi0dqku4KKaD5PaQyM0/img.png&quot; data-alt=&quot;메모장 저장&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dDVTah/btruNW9sx70/fZFXi0dqku4KKaD5PaQyM0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdDVTah%2FbtruNW9sx70%2FfZFXi0dqku4KKaD5PaQyM0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;204&quot; height=&quot;52&quot; data-origin-width=&quot;279&quot; data-origin-height=&quot;71&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;메모장 저장&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;윈도우 작업 스케줄러에서 작업 등록하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;윈도우 검색 창에 &lt;b&gt;작업 스케줄러&lt;/b&gt;를 치고 열어줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;426&quot; data-origin-height=&quot;116&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/RBJB1/btruMn7n08R/iknO0djK7gAMLS8HiONVVk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/RBJB1/btruMn7n08R/iknO0djK7gAMLS8HiONVVk/img.png&quot; data-alt=&quot;작업 스케줄러&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/RBJB1/btruMn7n08R/iknO0djK7gAMLS8HiONVVk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRBJB1%2FbtruMn7n08R%2FiknO0djK7gAMLS8HiONVVk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;195&quot; height=&quot;53&quot; data-origin-width=&quot;426&quot; data-origin-height=&quot;116&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;작업 스케줄러&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작업 스케줄러를 열었으면 우측에서 &lt;b&gt;작업 만들기&lt;/b&gt;를 찾아 클릭합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;350&quot; data-origin-height=&quot;601&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6uGWB/btruwIcYuvz/8GNwRb3OOpNwNyd3UtcHA0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6uGWB/btruwIcYuvz/8GNwRb3OOpNwNyd3UtcHA0/img.png&quot; data-alt=&quot;작업 만들기&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6uGWB/btruwIcYuvz/8GNwRb3OOpNwNyd3UtcHA0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6uGWB%2FbtruwIcYuvz%2F8GNwRb3OOpNwNyd3UtcHA0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;164&quot; height=&quot;282&quot; data-origin-width=&quot;350&quot; data-origin-height=&quot;601&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;작업 만들기&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;일반&lt;/b&gt; 탭에서는 이름에는 스케줄러 이름을 적어주고 보안에서는 가장 높은 수준의 권한으로 실행을 체크합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;724&quot; data-origin-height=&quot;136&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/56EXK/btruLsA559k/6HuhgOHSwTezQbt4hCvbc0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/56EXK/btruLsA559k/6HuhgOHSwTezQbt4hCvbc0/img.png&quot; data-alt=&quot;일반 탭 옵션값&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/56EXK/btruLsA559k/6HuhgOHSwTezQbt4hCvbc0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F56EXK%2FbtruLsA559k%2F6HuhgOHSwTezQbt4hCvbc0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;405&quot; height=&quot;76&quot; data-origin-width=&quot;724&quot; data-origin-height=&quot;136&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;일반 탭 옵션값&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;트리거&lt;/b&gt; 탭에서는 &lt;b&gt;새로 만들기&lt;/b&gt;를 눌러 새 트리거를 만들어주는데 프로그램이 실행될 주기와 시각을 지정합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;670&quot; data-origin-height=&quot;219&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/NLsc7/btruVXTfqLf/Tagik01smpVIyVM8htD7j1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/NLsc7/btruVXTfqLf/Tagik01smpVIyVM8htD7j1/img.png&quot; data-alt=&quot;새 트리거&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/NLsc7/btruVXTfqLf/Tagik01smpVIyVM8htD7j1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNLsc7%2FbtruVXTfqLf%2FTagik01smpVIyVM8htD7j1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;450&quot; height=&quot;147&quot; data-origin-width=&quot;670&quot; data-origin-height=&quot;219&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;새 트리거&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;동작&lt;/b&gt; 탭에서는 &lt;b&gt;새로 만들기&lt;/b&gt;를 눌러 새 동작을 만들어주는데 &lt;b&gt;프로그램/스크립트&lt;/b&gt;에는 배치파일의 경로(배치파일이 위치한 디렉터리 경로 + \배치파일명.bat)를 써주고 &lt;b&gt;시작 위치에는&lt;/b&gt; 배치파일이 위치한 디렉터리 경로를 써줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;558&quot; data-origin-height=&quot;319&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bRmQDF/btruWkU6Kxi/sancmKL6EAdiqtD1dXTcik/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bRmQDF/btruWkU6Kxi/sancmKL6EAdiqtD1dXTcik/img.png&quot; data-alt=&quot;새 동작&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bRmQDF/btruWkU6Kxi/sancmKL6EAdiqtD1dXTcik/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbRmQDF%2FbtruWkU6Kxi%2FsancmKL6EAdiqtD1dXTcik%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;355&quot; height=&quot;203&quot; data-origin-width=&quot;558&quot; data-origin-height=&quot;319&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;새 동작&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;조건&lt;/b&gt;과 &lt;b&gt;설정 탭은&lt;/b&gt; 하드웨어 절전모드 지원상태나 프로그램 특성을 고려해 옵션 값을 고릅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 해서 새 작업을 등록하면, 이제 정해진 시각이 될 때마다 자동으로 파이썬 프로그램이 실행될 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파이썬으로 업무 자동화 로직을 만들면 이런 식으로 작업 스케줄러에 등록하여 특정 시각마다 자동으로 실행하도록 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Python</category>
      <category>배치파일</category>
      <category>아나콘다</category>
      <category>업무자동화</category>
      <category>윈도우 스케줄러</category>
      <category>작업 스케줄러</category>
      <category>파이썬</category>
      <author>cocoder16</author>
      <guid isPermaLink="true">https://cocoder16.tistory.com/70</guid>
      <comments>https://cocoder16.tistory.com/70#entry70comment</comments>
      <pubDate>Mon, 21 Mar 2022 08:00:31 +0900</pubDate>
    </item>
    <item>
      <title>파이참에서 파이썬 코드 auto formatting 적용하기</title>
      <link>https://cocoder16.tistory.com/69</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Python code formatter black 사용하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공식 문서에서 black은 스스로 &lt;span style=&quot;background-color: #ffffff; color: #24292f;&quot;&gt;uncompromising (비타협적인) python code formatter라고 소개하고 있습니다. 비타협적이라는 것은 수동으로 hand-formatting 하는 대신에 black이 formatting 하는 것을 따라야 하며 커스터마이징이 쉽지 않다는 것을 뜻합니다. 실제로 configuration 없이 설치 후 바로 파이참에서 사용할 수 있었습니다. black도 당연히 PEP를 따르고 있으며, 사용해보니 꽤 가독성 좋은 formatting을 제공하고 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #24292f;&quot;&gt;pip로 설치는 다음과 같이 할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1646013360507&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;pip install black&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더 자세한 내용은 공식 문서를 참고해주세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/psf/black&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/psf/black&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1646013375268&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - psf/black: The uncompromising Python code formatter&quot; data-og-description=&quot;The uncompromising Python code formatter. Contribute to psf/black development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/psf/black&quot; data-og-url=&quot;https://github.com/psf/black&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/rLGl6/hyNxE9G9x8/Eiubiw1LmNfh0Fz6XLTM51/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640&quot;&gt;&lt;a href=&quot;https://github.com/psf/black&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/psf/black&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/rLGl6/hyNxE9G9x8/Eiubiw1LmNfh0Fz6XLTM51/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - psf/black: The uncompromising Python code formatter&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;The uncompromising Python code formatter. Contribute to psf/black development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;파이참 file watchers로 black 적용하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;black을 설치했다면, 파이참에서 파일을 세이브할 때마다 black을 이용해 자동으로 파이썬 코드를 포매팅하도록 설정할 수 있습니다. 방법은 다음과 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Settings -&amp;gt; Tools -&amp;gt; File Watchers로 들어가서 + 버튼을 눌러 템플릿을 추가합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;327&quot; data-origin-height=&quot;169&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mqLEu/btruBbFgtIK/gPlR5HXKKnZO86yL25YJxk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mqLEu/btruBbFgtIK/gPlR5HXKKnZO86yL25YJxk/img.png&quot; data-alt=&quot;파이참 File Watchers Template 추가&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mqLEu/btruBbFgtIK/gPlR5HXKKnZO86yL25YJxk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmqLEu%2FbtruBbFgtIK%2FgPlR5HXKKnZO86yL25YJxk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;255&quot; height=&quot;132&quot; data-origin-width=&quot;327&quot; data-origin-height=&quot;169&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;파이참 File Watchers Template 추가&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 아래 사진과 같이 내용을 수정합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;884&quot; data-origin-height=&quot;849&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/1ACCX/btruHxUMCDJ/hyBmq0kTqwS6h5qsDDLDf0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/1ACCX/btruHxUMCDJ/hyBmq0kTqwS6h5qsDDLDf0/img.png&quot; data-alt=&quot;black을 적용하는 템플릿&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/1ACCX/btruHxUMCDJ/hyBmq0kTqwS6h5qsDDLDf0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F1ACCX%2FbtruHxUMCDJ%2FhyBmq0kTqwS6h5qsDDLDf0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;526&quot; height=&quot;505&quot; data-origin-width=&quot;884&quot; data-origin-height=&quot;849&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;black을 적용하는 템플릿&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;File type은 Python으로, Program은 black이 설치된 경로를 넣어줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Arguments에는 $FileDirRelativeToProjectRoot$, Output paths to refresh에는 $FileDir$, Working directory에는 $ProjectFileDir$를 각각 넣어줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Advanced Options는 디폴트 상태로 저장하면 코드를 타이핑할 때마다 너무 자주 black이 적용돼서 불편함이 있습니다. 그래서 저는 파일을 저장할 때에만 black이 적용되도록 Advanced Options에서 모든 체크를 해제했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;black을 git pre-commit hook에 적용하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CI 프로세스중 하나인 pre-commit hook에 black을 적용하여 commit전에 항상 코드 포매팅 검사를 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공식 문서에 pre-commit 사용법이 자세히 나와있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://pre-commit.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://pre-commit.com/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1646014797958&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;pre-commit&quot; data-og-description=&quot;&quot; data-og-host=&quot;pre-commit.com&quot; data-og-source-url=&quot;https://pre-commit.com/&quot; data-og-url=&quot;https://pre-commit.com/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://pre-commit.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://pre-commit.com/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;pre-commit&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;pre-commit.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;pip로 pre-commit을 설치하는 명령어는 다음과 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1646014835345&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;pip install pre-commit&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;pre-commit을 설치했으면 프로젝트 루트 디렉터리에 .pre-commit-config.yaml파일을 만들고 파일 안에 configuration을 작성하고 저장합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1646014957583&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;repos:
-   repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v2.3.0
    hooks:
    -   id: check-yaml
    -   id: end-of-file-fixer
    -   id: trailing-whitespace
-   repo: https://github.com/psf/black
    rev: 21.12b0
    hooks:
    -   id: black&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로 터미널에서 pre-commit scripts를 설치합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1646015074264&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;pre-commit install&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 커밋을 할 때마다 black으로 검사를 실행할 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;961&quot; data-origin-height=&quot;170&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bzpUqL/btruvuTn9FV/qwkr2L9DLDAJXBXPzFS7v1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bzpUqL/btruvuTn9FV/qwkr2L9DLDAJXBXPzFS7v1/img.png&quot; data-alt=&quot;pre-commit 실행 결과&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bzpUqL/btruvuTn9FV/qwkr2L9DLDAJXBXPzFS7v1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbzpUqL%2FbtruvuTn9FV%2Fqwkr2L9DLDAJXBXPzFS7v1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;679&quot; height=&quot;120&quot; data-origin-width=&quot;961&quot; data-origin-height=&quot;170&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;pre-commit 실행 결과&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Python</category>
      <category>black</category>
      <category>file watchers</category>
      <category>pre-commit</category>
      <category>pycharm</category>
      <category>코드 포맷터</category>
      <category>파이썬</category>
      <author>cocoder16</author>
      <guid isPermaLink="true">https://cocoder16.tistory.com/69</guid>
      <comments>https://cocoder16.tistory.com/69#entry69comment</comments>
      <pubDate>Mon, 14 Mar 2022 08:00:04 +0900</pubDate>
    </item>
    <item>
      <title>파이참에서 아나콘다 가상환경 사용하기</title>
      <link>https://cocoder16.tistory.com/68</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;파이참 프로젝트 환경&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파이참 프로젝트를 만들 때 virtualenv, pipenv, poetry, conda 중에 환경을 선택할 수 있습니다. 이 중 conda로 프로젝트를 세팅하려고 합니다. 준비물은 파이썬, 파이참, 아나콘다가 설치되어있어야 합니다. 운영체제에 맞게 설치하면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.python.org/downloads/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;파이썬 다운로드&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1645940769241&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Download Python&quot; data-og-description=&quot;The official home of the Python Programming Language&quot; data-og-host=&quot;www.python.org&quot; data-og-source-url=&quot;https://www.python.org/downloads/&quot; data-og-url=&quot;https://www.python.org/downloads/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/eKGQV/hyNxPWY3Xb/LmaoGPwDrMG5PvIFnb7El1/img.png?width=200&amp;amp;height=200&amp;amp;face=0_0_200_200&quot;&gt;&lt;a href=&quot;https://www.python.org/downloads/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.python.org/downloads/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/eKGQV/hyNxPWY3Xb/LmaoGPwDrMG5PvIFnb7El1/img.png?width=200&amp;amp;height=200&amp;amp;face=0_0_200_200');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Download Python&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;The official home of the Python Programming Language&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.python.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.jetbrains.com/ko-kr/pycharm/download/#section=windows&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;파이참 다운로드&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1645940819100&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;다운로드 PyCharm: JetBrains가 만든 전문 개발자용 Python IDE&quot; data-og-description=&quot;&quot; data-og-host=&quot;www.jetbrains.com&quot; data-og-source-url=&quot;https://www.jetbrains.com/ko-kr/pycharm/download/#section=windows&quot; data-og-url=&quot;https://www.jetbrains.com/ko-kr/pycharm/download/#section=windows&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://www.jetbrains.com/ko-kr/pycharm/download/#section=windows&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.jetbrains.com/ko-kr/pycharm/download/#section=windows&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;다운로드 PyCharm: JetBrains가 만든 전문 개발자용 Python IDE&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.jetbrains.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a title=&quot;아나콘다 다운로드&quot; href=&quot;https://www.anaconda.com/products/individual&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;아나콘다 다운로드&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1645940840326&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Anaconda | Individual Edition&quot; data-og-description=&quot;Anaconda's open-source Individual Edition is the easiest way to perform Python/R data science and machine learning on a single machine.&quot; data-og-host=&quot;www.anaconda.com&quot; data-og-source-url=&quot;https://www.anaconda.com/products/individual&quot; data-og-url=&quot;https://www.anaconda.com/products/individual&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bp4OHb/hyNxR1yRFl/XmVmxlNtwCru1KXlQDqvYK/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/rogrK/hyNxGFKBdI/msvNdeOXgDJRtzK2AUkfj1/img.jpg?width=796&amp;amp;height=418&amp;amp;face=0_0_796_418&quot;&gt;&lt;a href=&quot;https://www.anaconda.com/products/individual&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.anaconda.com/products/individual&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bp4OHb/hyNxR1yRFl/XmVmxlNtwCru1KXlQDqvYK/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/rogrK/hyNxGFKBdI/msvNdeOXgDJRtzK2AUkfj1/img.jpg?width=796&amp;amp;height=418&amp;amp;face=0_0_796_418');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Anaconda | Individual Edition&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Anaconda's open-source Individual Edition is the easiest way to perform Python/R data science and machine learning on a single machine.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.anaconda.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;아나콘다 가상환경 만들기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설치가 완료되었으면 Anaconda Prompt를 실행해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1202&quot; data-origin-height=&quot;96&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bWRDH3/btrusPCJvvy/EFkK9viZFwdTgGHZ4tzwVk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bWRDH3/btrusPCJvvy/EFkK9viZFwdTgGHZ4tzwVk/img.png&quot; data-alt=&quot;Anaconda Prompt&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bWRDH3/btrusPCJvvy/EFkK9viZFwdTgGHZ4tzwVk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbWRDH3%2FbtrusPCJvvy%2FEFkK9viZFwdTgGHZ4tzwVk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;461&quot; height=&quot;37&quot; data-origin-width=&quot;1202&quot; data-origin-height=&quot;96&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Anaconda Prompt&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Anaconda Prompt에서는 conda 명령어를 사용할 수 있습니다. 다음 명령어를 입력하면 가지고 있는 아나콘다 가상환경 목록을 볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1645941215075&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;conda env list&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새 가상환경을 만들어보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1645941278368&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;conda create --name myEnv python=3.10&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;myEnv는 가상환경 이름입니다. 짓고 싶은 이름으로 지어주면 됩니다. python= 에는 버전명을 명시해줘야 합니다. 설치된 python버전을 입력해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 다시 conda env list로 목록을 확인해보면 새 가상환경이 추가된 것을 볼 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;아나콘다 가상환경에서 패키지 설치하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새로 만든 가상환경에 패키지를 설치하기 위해 activate 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1645941765396&quot; class=&quot;applescript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;conda activate 가상환경이름&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가상환경에 설치된 패키지 목록을 확인합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1645941929995&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;pip list&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가하고 싶은 패키지를 설치하는 방법은 다음과 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1645941949525&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;pip install 패키지명&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가상환경 안에서 파이썬 코드를 사용해보려면 python을 입력합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1645941995055&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;python&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1017&quot; data-origin-height=&quot;130&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JkLa8/btrusO4W5hk/rW29x95gwWakUKxWmZm2mk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JkLa8/btrusO4W5hk/rW29x95gwWakUKxWmZm2mk/img.png&quot; data-alt=&quot;conda 가상환경에서 python 코드 입력하기&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JkLa8/btrusO4W5hk/rW29x95gwWakUKxWmZm2mk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJkLa8%2FbtrusO4W5hk%2FrW29x95gwWakUKxWmZm2mk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;727&quot; height=&quot;93&quot; data-origin-width=&quot;1017&quot; data-origin-height=&quot;130&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;conda 가상환경에서 python 코드 입력하기&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;exit()를 실행하면 종료할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1645942253720&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;exit()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가상환경에서 나오려면 deactivate를 하면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1645941830140&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;conda deactivate&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가상환경을 삭제하는 명령어는 다음과 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1645942325991&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;conda remove --name 가상환경명 --all&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;파이참에서 아나콘다 가상환경으로 프로젝트 시작하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파이참에서 new project를 시작하면 인터프리터를 선택하라고 나옵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;996&quot; data-origin-height=&quot;749&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kPdNy/btruyN43nvg/LXjtIEj2VYxoCxEitpNm6K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kPdNy/btruyN43nvg/LXjtIEj2VYxoCxEitpNm6K/img.png&quot; data-alt=&quot;Create Project&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kPdNy/btruyN43nvg/LXjtIEj2VYxoCxEitpNm6K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkPdNy%2FbtruyN43nvg%2FLXjtIEj2VYxoCxEitpNm6K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;594&quot; height=&quot;446&quot; data-origin-width=&quot;996&quot; data-origin-height=&quot;749&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Create Project&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Location에는 새 프로젝트 디렉터리 경로를 적어주고, Previously configured interpreter에 체크를 한 후 ...을 눌러 conda 가상환경 인터프리터를 찾아줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1040&quot; data-origin-height=&quot;695&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wKZYP/btruvurmfi6/0mlu9cv4bEs2BZMzHNxnpk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wKZYP/btruvurmfi6/0mlu9cv4bEs2BZMzHNxnpk/img.png&quot; data-alt=&quot;Add Interpreter&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wKZYP/btruvurmfi6/0mlu9cv4bEs2BZMzHNxnpk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwKZYP%2Fbtruvurmfi6%2F0mlu9cv4bEs2BZMzHNxnpk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;575&quot; height=&quot;384&quot; data-origin-width=&quot;1040&quot; data-origin-height=&quot;695&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Add Interpreter&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아나콘다가 설치된 경로에서 \envs\새가상환경명\python.exe를 Interpreter 경로로 잡아줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;conda executable에는 아나콘다가 설치된 경로에서 \Scripts\conda.exe를 경로로 잡습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OK를 누른 후 Create를 눌러 프로젝트를 생성해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 파이참에서 아나콘다 가상환경으로 프로젝트를 생성했습니다. 만약 패키지 추가/업데이트/삭제가 필요하다면 파이참에서 패키지를 관리하는 방법도 있지만, 제 경험상 Anaconda Prompt에서 패키지를 관리하는 것이 더 오류 없이 잘되는 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* Anaconda Prompt에서 패키지 관리 명령어&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1645943743756&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 가상환경진입
conda activate 가상환경명

# 패키지 설치
pip install 패키지명

# 패키지 삭제
pip uninstall 패키지명

# 특정 버전으로 패키지 설치
pip install 패키지명==버전명

# 패키지 버전 업그레이드
pip install --upgrade&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;아나콘다 32비트 가상환경 만들기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대부분 운영체제를 64비트를 사용하실 텐데 32비트 가상환경이 필요한 경우가 있습니다. 이때에는 아나콘다 가상환경을 32비트로 만들어주면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가 준비물이 필요한데 파이썬 32비트 버전을 다운로드해야 합니다. 아나콘다를 32비트 버전으로 추가 설치를 할 필요는 없습니다. 32비트 버전은 파이썬만 필요합니다. 맨 위에 소개한 파이썬 다운로드 링크에서 다운로드할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그다음으로 마찬가지로 Anaconda Prompt에서 다음 명령어를 입력합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1645944038505&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;set CONDA_FORCE_32BIT=1&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로는 위에서 설명한 대로 똑같이 가상환경을 생성해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가상환경 생성이 완료되었다면 다시 64비트로 설정을 원복하는 것이 좋습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1645944097838&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;set CONDA_FORCE_32BIT=0&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;(23.11.08 32비트 가상환경 만들기 업데이트)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아나콘다 base에서 CONDA_FORCE_32BIT=1을 사용하려면 에러에 부딪힙니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 다른 방법을 소개합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방법을 사용하면 파이썬 32비트를 따로 설치하지 않아도, 아나콘다 가상환경 내에서 파이썬 32비트를 설치하여 만들 수 있으니 훨씬 간편합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;Anaconda Prompt에서 다음 순서로 명령어를 입력하여 진행하면 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1699375540029&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;conda create -n 가상환경명
conda activate 가상환경명
conda config --env --set subdir win-32    
conda install python=버전&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1번째 줄, 처음에 가상환경을 생성할 때에는 64비트로 생성이 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2번째 줄, 가상환경에 진입 후 64비트인 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3번째 줄, 32비트 서브디렉터리를 가상환경 내에 생성합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4번째 줄, 32비트로 사용하기 원하는 파이썬 버전을 설치합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면 32비트 가상환경을 생성할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;방법이 바뀌어서 그런 것은 아니지만 원래부터 주의할 점이 있었는데 그것을 첨언하자면, 아나콘다에서 32비트 가상환경을 생성할 때 릴리즈된 파이썬 버전이라고 전부 사용할 수 있지 않습니다. 따라서 32비트 설치가 잘 안 될 때에는 사용할 다른 버전을 잘 찾아봐야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Python</category>
      <category>Anaconda</category>
      <category>conda</category>
      <category>pycharm</category>
      <category>python</category>
      <category>가상환경</category>
      <category>가상환경 패키지 설치</category>
      <category>아나콘다 가상환경</category>
      <category>연동</category>
      <category>파이썬 32비트 가상환경</category>
      <category>파이참 가상환경</category>
      <author>cocoder16</author>
      <guid isPermaLink="true">https://cocoder16.tistory.com/68</guid>
      <comments>https://cocoder16.tistory.com/68#entry68comment</comments>
      <pubDate>Mon, 7 Mar 2022 08:00:07 +0900</pubDate>
    </item>
    <item>
      <title>Jest + React testing library vs Cypress 컴포넌트 테스트 비교</title>
      <link>https://cocoder16.tistory.com/67</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;리액트 컴포넌트 테스트를 위한&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;jest와 cypress의 사용 경험 비교&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;리액트 컴포넌트 테스트를 위해 &lt;b&gt;jest + react testing library&lt;/b&gt;와 &lt;b&gt;cypress&lt;/b&gt;를 각각 써본 후 느낀 점들이 있습니다. 이번 포스팅에서는 이 둘을 &lt;b&gt;개발 경험, 코드, 테스트 유효성&lt;/b&gt; 관점에서 어떤 차이가 있는지 비교해보려고 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;개발 경험 비교&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;1. 환경설정&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;jest&lt;/b&gt;는 워낙 올인원 프레임워크라 preconfiguration 코드가 필요 없고, dependencies를 추가 설치할 필요가 없습니다. 그리고 만약 CRA (Create React App)로 프로젝트를 시작하면 jest와 react testing library가 기본적으로 설치가 되어있기 때문에 CRA로 init을 하자마자 바로 테스트 코드를 작성할 수 있습니다. CRA에서 테스트코드 개발 모듈로 이 둘을 기본적으로 제공해주는 이유는 이 둘을 페이스북에서 공식적으로 사용하라고 추천하고 있기 때문인 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;cypress&lt;/b&gt;는 설치 직후 E2E 테스트 코드는 바로 작성할 수 있지만, 컴포넌트 테스트를 하려면 추가적인 의존 모듈, 플러그인, config 설정이 필요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* 추가 모듈&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1645877261452&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;yarn add -D @cypress/react @cypress/webpack-dev-server&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;cypress 컴포넌트 테스트를 하기 위해 필요한 추가 모듈, 플러그인, config 설정, 그리고 컴포넌트 테스트 작성 방법에 대해 다음 블로그 글은 쉽고 자세하게 설명해주고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.cypress.io/blog/2021/04/06/cypress-component-testing-react/#header&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.cypress.io/blog/2021/04/06/cypress-component-testing-react/#header&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1645877353251&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Getting Started with Cypress Component Testing (React)&quot; data-og-description=&quot;As of Cypress 7.0, the new Component Test Runner is now bundled with Cypress! It builds on our learnings from the original component testing implementation, which was hidden behind the &amp;#96;experimentalComponentTesting&amp;#96; flag.&quot; data-og-host=&quot;www.cypress.io&quot; data-og-source-url=&quot;https://www.cypress.io/blog/2021/04/06/cypress-component-testing-react/#header&quot; data-og-url=&quot;https://www.cypress.io/blog/2021/04/06/cypress-component-testing-react/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/uUzfQ/hyNwO5Uhz7/D8Kty47J4bNXp5TeYZzhkK/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/cUS9mZ/hyNxEgh6hj/fqdkQ5V3fXujy70ifoStZ1/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://www.cypress.io/blog/2021/04/06/cypress-component-testing-react/#header&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.cypress.io/blog/2021/04/06/cypress-component-testing-react/#header&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/uUzfQ/hyNwO5Uhz7/D8Kty47J4bNXp5TeYZzhkK/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/cUS9mZ/hyNxEgh6hj/fqdkQ5V3fXujy70ifoStZ1/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Getting Started with Cypress Component Testing (React)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;As of Cypress 7.0, the new Component Test Runner is now bundled with Cypress! It builds on our learnings from the original component testing implementation, which was hidden behind the `experimentalComponentTesting` flag.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.cypress.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;cypress는 test runner로서 jest가 아닌 mocha를 사용하고 있습니다. mocha는 프레임워크가 아닌 테스트 러너기 때문에 다른 의존 모듈들이 필요합니다. cypress가 mocha를 선택한 이유는 mocha는 라이브러리 선택 등에 대해 자유롭고 유연함을 가지고 있기 때문이라고 생각하고 있습니다. cypress는 chai, sinon 등 의존 모듈들을 내부적으로 사용하고 있기 때문에 우리가 따로 이것들을 설치할 필요는 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 개발 편의성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;jest는 터미널에서 watch &amp;amp; hot reload 모드를 사용할 수 있고, cypress는 브라우저에서 watch &amp;amp; hot reload 모드를 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;watch all은 jest, cypress 둘 다 가능하지만, cypress의 컴포넌트 테스트는 모든 테스트 파일을 hot reload 하는 hot reload all files가 안됩니다. cypress는 E2E test의 경우는 hot reload all files가 가능하지만, component test의 경우는 하나의 파일에 대해서만 hot reload를 할 수 있습니다. 프로젝트 규모가 크고 테스트 파일과 코드가 많으면 모든 파일을 hot reload 하는 것은 시간문제 때문에 선택되지 않기도 하지만, 이것은 기본적으로 휴먼 에러를 차단할 수 있는 좋은 옵션인 것은 사실입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;cypress는 브라우저 위에서 탐색/디버깅이 가능하기 때문에, 리액트 앱 로컬 서버를 띄우지 않은 상태에서도 크롬 개발자 도구를 보면서 개발이 가능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;cypress의 유닛 테스트 watch &amp;amp; hot reload 모드는 jest의 watch &amp;amp; hot reload 모드보다 메모리를 더 많이 사용합니다. 램 사양이 낮은 컴퓨터면 고려사항이 될 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;코드 비교&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;컴포넌트 테스트는 다음과 같은 과정으로 진행했으며, 스냅샷테스트는 하지 않았습니다.&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&amp;nbsp;props값에 따른 렌더링 테스트&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;액션 이후 렌더링 테스트&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 이번에 소개할 코드 목록은 다음과 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;컴포넌트 셋업 with props&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;렌더링 결과 확인&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;UI로 행동 발생&lt;/span&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단한 버튼 컴포넌트의 테스트 코드를 통해 둘을 비교해보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;jest + react testing library 버튼 컴포넌트 테스트 코드&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* 예시 코드&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1645879173057&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { fireEvent, render } from &quot;@testing-library/react&quot;;

import Button from &quot;./Button&quot;;

describe(&quot;Button&quot;, () =&amp;gt; {
  it(&quot;Given Button, When Click, Then call onClick&quot;, () =&amp;gt; {
    const onClick = jest.fn();
    const { getByText } = render(&amp;lt;Button onClick={onClick}&amp;gt;Test Button&amp;lt;/Button&amp;gt;);
    const button = getByText(/Test button/i);

    expect(button).toBeTruthy();

    fireEvent.click(button);

    expect(onClick).toHaveBeenCalledTimes(1);
  });
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 컴포넌트 셋업 with props&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴포넌트를 마운트 시키는 것은 react testing library에서 제공하는 render함수로 쉽게 할 수 있습니다. 인자 값으로 ReactElement를 전달하면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 렌더링 결과 확인&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;render함수가 반환하는 여러 메서드들이 있습니다. 이 메서드들은 DOM Element에 접근할 수 있는 여러 방법들을 제공합니다. 위 예시 코드에서는 Text로 Element에 접근하는 메서드인 getByText를 사용했습니다. 이것으로 버튼 엘리먼트를 button변수에 초기화시켰습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;렌더링 결과를 검증하기 위해서 expect()에 버튼 엘리먼트를 넣고, toBeTruthy 함수로 존재하는지를 확인했습니다. 테스트는 버튼이 존재한다면 pass, 존재하지 않는다면 fail 할 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. UI로 행동 발생&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로 react testing library에서 제공하는 &lt;b&gt;fireEvent&lt;/b&gt;함수로 버튼 엘리먼트에 click 이벤트를 발생시켰습니다. 이벤트를 발생시키는 함수로 fireEvent말고 &lt;b&gt;userEvent&lt;/b&gt;함수를 사용하는 방법도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;userEvent는 마치 사람이 직접 브라우저 상에서 행동하는 것처럼 유저 이벤트를 발생시킬 수 있습니다. 따라서 fireEvent는 코드로 명시한 click 이벤트만 발생시키지만, userEvent는 유저가 click을 할 때 실제로 발생하는 다른 이벤트들도 모두 발생합니다. 따라서 userEvent를 사용하는 것이 더 실제 환경에 가깝게 테스트를 하는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* userEvent 사용&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1645880745834&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import userEvent from &quot;@testing-library/user-event&quot;;
// ...(중략)
userEvent.click(button);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;cypress 버튼 컴포넌트 테스트 코드&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* 예시 코드&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1645879342169&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { mount } from &quot;@cypress/react&quot;;

import Button from &quot;./Button&quot;;

describe(&quot;Button&quot;, () =&amp;gt; {
  it(&quot;Given Button, When Click, Then call onClic&quot;, () =&amp;gt; {
    const onClick = cy.stub().as(&quot;clickHandler&quot;);
    mount(&amp;lt;Button onClick={onClick}&amp;gt;Test button&amp;lt;/Button&amp;gt;);

    cy.get(&quot;button&quot;).contains(&quot;Test button&quot;).click();

    cy.get(&quot;@clickHandler&quot;).should(&quot;have.been.calledOnce&quot;);
  });
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 컴포넌트 셋업 with props&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴포넌트를 마운트 시키는 것은 cypress에서 제공하는 mount함수로 쉽게 할 수 있습니다. 인자 값으로 ReactNode를 전달하면 됩니다. ReactElement를 받는 react testing library의 render함수와는 차이가 조금 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 렌더링 결과 확인&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;cypress는 DOM Element에 접근하기 위해서 단 하나의 메서드만을 사용하면 됩니다. &lt;b&gt;cy.get()&lt;/b&gt;은 jQuery의 DOM 쿼리 방식을 그대로 사용합니다. 그리고 체이닝을 할 수 있기에 contains메서드로 특정 텍스트를 가지고 있는 button을 필터링하여 얻을 수 있습니다. contains다음에 should메소드로 체이닝하면 단언을 작성할 수 있습니다. 다음과 같이 작성하면 Test button 텍스트를 가진 button 태그가 존재하는지를 확인하는 테스트가 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1645881351906&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;cy.get(&quot;button&quot;).contains(&quot;Test button&quot;).should(&quot;exist&quot;);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;cypress는 단언을 should와 and로 작성할 수 있습니다. and는 should 뒤에 추가적인 단언을 체이닝으로 작성하고 싶을 때 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1645881646752&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; cy.get(&quot;input&quot;).should(&quot;be.focused&quot;).and(&quot;have.value&quot;, &quot;Hello&quot;);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. UI로 행동 발생&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 contains 다음 체이닝으로 단언을 쓰지 않고 click 메소드로 click 이벤트를 발생시켰습니다. 이렇게 해도 contains에서 어떤 엘리먼트도 얻지 못하면 테스트는 fail 하게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;테스트 유효성&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;둘 다 DOM에서 테스트하지만 cypress는 브라우저 위에서 실행하며, 실제 유저 입장에서 다양한 브라우저와 다양한 상황에서 테스트할 수 있어서 더 유효합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;결론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;React component test를 하기 위해 &lt;/span&gt;환경설정, 개발 편의성, 예시 코드, 테스트 유효성 관점에서 jest + react testing library와 cypress를 비교해봤습니다. 어떤 관점에 더 우선순위를 두는지는 사람마다 그리고 처한 상황마다 다를 것 같습니다. React component test를 하기 위해 어떤 기술을 사용할지 선택하는 것은 개인적으로 매우 어려웠습니다. 다양한 기술들이 존재하며, 그들의 특징과 장단점이 뚜렷하기 때문에 완벽한 하나의 솔루션이 없기 때문입니다. 그래도 이런 기술 비교와 고민의 시간을 가지는 것은 다음에 진행할 리액트 프로젝트들을 위해 더 넓은 시야를 갖게 만드는 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>React.js</category>
      <category>component test</category>
      <category>CYPRESS</category>
      <category>JEST</category>
      <category>react</category>
      <category>react testing library</category>
      <category>testing-library</category>
      <category>Unit Test</category>
      <category>리액트 테스트</category>
      <category>컴포넌트 테스트</category>
      <author>cocoder16</author>
      <guid isPermaLink="true">https://cocoder16.tistory.com/67</guid>
      <comments>https://cocoder16.tistory.com/67#entry67comment</comments>
      <pubDate>Mon, 28 Feb 2022 08:00:48 +0900</pubDate>
    </item>
    <item>
      <title>div태그로 custom select 만들기</title>
      <link>https://cocoder16.tistory.com/52</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;HTML5 select 태그의 styling 한계&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTML5 select 태그는 form에서 자주 쓰이는 input user interface로 여러 옵션을 주고, 그중 선택할 수 있도록 하는 UI입니다. select의 선택지는 option태그로 만들어주는데 이 option 태그가 CSS로 스타일링하는 데에 많은 한계를 가진다는 아쉬움이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 select와 option태그로 간단한 스타일링을 한 예시입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;codepen&quot; style=&quot;height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;&quot; data-height=&quot;300&quot; data-default-tab=&quot;html,result&quot; data-slug-hash=&quot;qBXzNBY&quot; data-user=&quot;cocoder16&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;See the Pen &lt;a href=&quot;https://codepen.io/cocoder16/pen/qBXzNBY&quot;&gt; Untitled&lt;/a&gt; by cocoder16 (&lt;a href=&quot;https://codepen.io/cocoder16&quot;&gt;@cocoder16&lt;/a&gt;) on &lt;a href=&quot;https://codepen.io&quot;&gt;CodePen&lt;/a&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;option 각 블록은 height이나 padding 값이 적용되지 않고 mouseover상태에 대해서도 css로 스타일링을 할 수 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;div 태그를 이용하여 select를 구현하면 자유로운 styling이 가능하다.&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;디자인에 대해 높은 자유도를 가지는 div태그를 이용해서 select를 구현하면 styling의 한계를 벗어날 수 있습니다. 단, 이 경우 javascript를 이용해 셀렉트의 기능을 구현하여 모듈화를 시키는 작업을 해야 합니다. 일단 가장 간단한 형태의 custom select를 구현해보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 마크업을 작성합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1637745515579&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;div class=&quot;select&quot;&amp;gt;
  &amp;lt;div class=&quot;selected&quot;&amp;gt;
    &amp;lt;div class=&quot;selected-value&quot;&amp;gt;none&amp;lt;/div&amp;gt;
    &amp;lt;div class=&quot;arrow&quot;&amp;gt;&amp;lt;/div&amp;gt;
  &amp;lt;/div&amp;gt;
  &amp;lt;ul&amp;gt;
    &amp;lt;li class=&quot;option&quot;&amp;gt;none&amp;lt;/li&amp;gt;
    &amp;lt;li class=&quot;option&quot;&amp;gt;option 1&amp;lt;/li&amp;gt;
    &amp;lt;li class=&quot;option&quot;&amp;gt;option 2&amp;lt;/li&amp;gt;
    &amp;lt;li class=&quot;option&quot;&amp;gt;option 3&amp;lt;/li&amp;gt;
    &amp;lt;li class=&quot;option&quot;&amp;gt;loooooooooooooooooong text option&amp;lt;/li&amp;gt;
  &amp;lt;/ul&amp;gt;
&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;option은 unordered list 의미를 적용하여 ul, li 태그를 사용해봤습니다. 마지막 옵션 값은 일부로 길이가 긴 텍스트를 사용했는데 텍스트가 긴 경우의 퍼블리싱 이슈를 확인하기 위해서입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;classname이 selected-value인 div태그는 선택된 값을 보여줄 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;classname이 arrow인 div태그는 화살표 아이콘을 그려줄 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 이 select가 펼쳐져 있을 때의 모습을 디자인해봅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ul 태그를 사용했으니 기본 스타일인 머릿 기호와 들여 쓰기를 없애줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1637746821270&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ul {
  list-style-type: none;
  padding-left: 0px;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 div 요소들을 하나하나 차근차근 스타일링해 나갑니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1637746949656&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;.select {
  display: inline-block;
  width: 120px;
  border: 1px solid #999;
}

.select .selected {
  display: flex;
  justify-content: space-between;
  padding: 8px 5px;
}

.select .selected .selected-value {
  max-width: 90px;
}

.select .selected .arrow {
  width: 24px;
  background: url(&quot;https://uxwing.com/wp-content/themes/uxwing/download/02-arrow-direction/arrow-bottom.png&quot;) no-repeat 70% 50%;
  background-size: 50% 50%;
}

.select ul {
  width: 120px;
  border: 1px solid #999;
  position: absolute;
  background: #fff;
  border-top: none;
  margin: 1px 0 0 -1px;
  cursor: pointer;
}

.select ul li {
  padding: 8px 5px;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같은 모습이 될 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;279&quot; data-origin-height=&quot;338&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xm6W9/btrl4PQ8OdJ/NtlF4Sn2iL9fdan8K7Jt81/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xm6W9/btrl4PQ8OdJ/NtlF4Sn2iL9fdan8K7Jt81/img.png&quot; data-alt=&quot;styling을 1차적으로 한 상태&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xm6W9/btrl4PQ8OdJ/NtlF4Sn2iL9fdan8K7Jt81/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fxm6W9%2Fbtrl4PQ8OdJ%2FNtlF4Sn2iL9fdan8K7Jt81%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;191&quot; height=&quot;231&quot; data-origin-width=&quot;279&quot; data-origin-height=&quot;338&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;styling을 1차적으로 한 상태&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제점이 있습니다. 텍스트의 길이가 긴 옵션에 대해 말줄임표로 깔끔하게 처리하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1637747153511&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;.select ul li,
.select .selected .selected-value {
  white-space: nowrap; /* 줄바꿈 안함 */
  overflow: hidden;
  text-overflow: ellipsis; /* 말줄임 적용 */
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로 옵션에 마우스를 올려놨을 때 배경색을 입혀보도록 하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1637747235905&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;.select ul li:hover {
  background: rgba(168, 156, 235, 0.35)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;166&quot; data-origin-height=&quot;306&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/x2vsq/btrl439ur8E/Lac69m7ZrPVR4C5VqRMPz0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/x2vsq/btrl439ur8E/Lac69m7ZrPVR4C5VqRMPz0/img.png&quot; data-alt=&quot;완성된 모습&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/x2vsq/btrl439ur8E/Lac69m7ZrPVR4C5VqRMPz0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fx2vsq%2Fbtrl439ur8E%2FLac69m7ZrPVR4C5VqRMPz0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;100&quot; height=&quot;184&quot; data-origin-width=&quot;166&quot; data-origin-height=&quot;306&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;완성된 모습&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기까지 select 태그를 미니멀한 디자인으로 흉내를 내봤습니다. div 태그의 높은 자유도를 이용해 다양한 스타일링이 가능합니다. 따라서 정말 다양한 디자인을 구현해낼 수 있습니다. 아직 기능 구현이 남았습니다. 이제 select 태그처럼 기능을 하도록 만들기 위해 javascript 코드를 작성해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;custom select 기능 구현하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(포스팅 하단에 있는 코드펜으로 시현해볼 수 있습니다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 option은 처음엔 닫혀있는 상태를 유지해주기 위해 display: none을 추가해줍니다. 그리고 option이 열려있는 상태는 active라는 class name으로 관리하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1637747641216&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;.select ul {
  display: none;
}

.select.active ul {
  display: initial;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 select를 누르면 active classname을 추가/제거해주는 함수를 만듭니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1637747692842&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function toggleSelectBox(selectBox) {
  selectBox.classList.toggle(&quot;active&quot;);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 select click event에 toggleSelectBox를 설치해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1637748459351&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const selectBoxElements = document.querySelectorAll(&quot;.select&quot;);

selectBoxElements.forEach(selectBoxElement =&amp;gt; {
  selectBoxElement.addEventListener(&quot;click&quot;, function (e) {
    toggleSelectBox(selectBoxElement);
  });
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로 option을 선택하면 해당 값을 선택하는 함수를 작성합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1637748507093&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function selectOption(optionElement) {
  const selectBox = optionElement.closest(&quot;.select&quot;);
  const selectedElement = selectBox.querySelector(&quot;.selected-value&quot;);
  selectedElement.textContent = optionElement.textContent;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 함수를 적용하여 위에서 작성한 이벤트 핸들링 콜백 함수를 다음과 같이 수정합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1637748589997&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;selectBoxElements.forEach(selectBoxElement =&amp;gt; {
  selectBoxElement.addEventListener(&quot;click&quot;, function (e) {
    const targetElement = e.target;
    const isOptionElement = targetElement.classList.contains(&quot;option&quot;);

    if (isOptionElement) {
      selectOption(targetElement);
    }

    toggleSelectBox(selectBoxElement);
  });
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 option을 클릭하면 해당 값이 선택되며, 열렸다 닫히는 기능까지 추가된 select가 완성되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 뭔가 부족한 느낌이 듭니다. option이 펼쳐진 상태에서도 여백을 클릭해서 select를 닫는 기능을 추가하고 싶습니다. 이를 위해서는 document 객체에 click event handling을 추가합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1637748708136&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;document.addEventListener(&quot;click&quot;, function (e) {
  const targetElement = e.target;
  const isSelect = targetElement.classList.contains(&quot;select&quot;) || targetElement.closest(&quot;.select&quot;);

  if (isSelect) {
    return;
  }

  const allSelectBoxElements = document.querySelectorAll(&quot;.select&quot;);

  allSelectBoxElements.forEach(boxElement =&amp;gt; {
    boxElement.classList.remove(&quot;active&quot;);
  });
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 끝났습니다. 완성된 모습은 다음과 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;codepen&quot; style=&quot;height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;&quot; data-height=&quot;300&quot; data-default-tab=&quot;html,result&quot; data-slug-hash=&quot;bGrPeyq&quot; data-user=&quot;cocoder16&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;See the Pen &lt;a href=&quot;https://codepen.io/cocoder16/pen/bGrPeyq&quot;&gt; Untitled&lt;/a&gt; by cocoder16 (&lt;a href=&quot;https://codepen.io/cocoder16&quot;&gt;@cocoder16&lt;/a&gt;) on &lt;a href=&quot;https://codepen.io&quot;&gt;CodePen&lt;/a&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이와 같이 정말 최소한의 select기능만을 수행하는 커스텀 셀렉트를 만들어봤습니다. 그런데 여기에는 한계가 존재합니다. 해당 커스텀 셀렉트가&amp;nbsp;모듈화 되지 않았다는 것입니다. 재사용성을 높이고 사이드 이펙트를 없애기 위해서는 모듈화를 해줘야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 다행히도 우리는 이미 뛰어난 개발자들이 만든 좋은 라이브러리들을 사용할 수 있습니다. 이런 라이브러리들은 모듈화가 잘되어있고 사이드 이펙트가 없도록 꾸준히 관리되고 있으며 &lt;span style=&quot;color: #666666;&quot;&gt;(관리되고 있는 라이브러리를 사용해야 합니다.)&lt;/span&gt; 또한 지금 포스팅에서 만든 커스텀 셀렉트보다 훨씬 더 많은 기능들을 지원하기도 합니다. &quot;custom select library&quot;와 같은 키워드로 검색하면 대중적인 라이브러리들을 찾아볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Javascript</category>
      <category>CSS</category>
      <category>custom-select</category>
      <category>div로 select 만드는 방법</category>
      <category>select</category>
      <category>스타일</category>
      <category>커스텀 셀렉트</category>
      <author>cocoder16</author>
      <guid isPermaLink="true">https://cocoder16.tistory.com/52</guid>
      <comments>https://cocoder16.tistory.com/52#entry52comment</comments>
      <pubDate>Mon, 14 Feb 2022 08:00:39 +0900</pubDate>
    </item>
    <item>
      <title>[CSS] 폰트 파일 또는 무료 폰트인 구글 폰트 적용하는 방법</title>
      <link>https://cocoder16.tistory.com/53</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;폰트 파일을 적용하는 방법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;폰트 파일 준비하기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 준비한 폰트 파일을 프로젝트 폴더 내에 원하는 경로에 넣어줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;assets/fonts 와 같은 디렉토리명이 적절합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;준비한 폰트파일은 CSS 코드에서 경로를 통해 불러와 적용할 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;폰트 파일 확장자 종류&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;폰트 파일은 다양한 확장자를 가집니다. 확장자 별로 지원하는 브라우저가 다르고 그 내용은 다음과 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ttf/otf: IE9이상, 크롬, 파폭, 오페라, 사파리&lt;/li&gt;
&lt;li&gt;woff: IE9이상, 크롬, 파폭, 오페라, 사파리&lt;/li&gt;
&lt;li&gt;svg/svgz: 크롬, 오페라, 사파리&lt;/li&gt;
&lt;li&gt;woff2: 크롬, 파폭, 오페라&lt;/li&gt;
&lt;li&gt;eot: IE8이하&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;CSS에서 폰트 파일 불러와서 적용하기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@font-face {}를 통해 해당 폰트를 font-family 값으로 정의할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1637806454816&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@font-face {
  font-family: &quot;NotoSansKR&quot;; /* 사용하고 싶은 font-family명을 지정 */
  src: url(&quot;../assets/fonts/Noto_Sans_KR/NotoSansKR-Regular.otf&quot;) format(&quot;opentype&quot;); /* 폰트파일 불러오기 */
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 불러온 폰트를 적용해보겠습니다. 모든 section내에 있는 p태그에 해당 폰트를 적용하는 CSS 코드는 다음과 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1637806554733&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;section p {
  font-family: &quot;NotoSansKR&quot;;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 해당 폰트를 전역으로 적용하려면 다음과 같이 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1637806957870&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;* {
  font-family: &quot;NotoSansKR&quot;;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다양한 브라우저 지원을 위해 동일한 폰트에 대해 여러 확장자의 파일을 준비한 경우는 다음과 같이 @font-face에서 여러 리소스 경로를 명시해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1637806661667&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@font-face {
  font-family: &quot;NotoSansKR&quot;;
  src: url(&quot;../assets/fonts/Noto_Sans_KR/NotoSansKR-Black.ttf&quot;) format(&quot;truetype&quot;), 
  url(&quot;../assets/fonts/Noto_Sans_KR/NotoSansKR-Regular.otf&quot;) format(&quot;opentype&quot;);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 리소스는 콤마(,)로 구분해줄 수 있습니다. 그리고 확장자별로 다른 format값을 명시해줘야 합니다. 그 값은 다음과 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ttf: truetype&lt;/li&gt;
&lt;li&gt;otf: opentype&lt;/li&gt;
&lt;li&gt;woff: woff&lt;/li&gt;
&lt;li&gt;svg: svg&lt;/li&gt;
&lt;li&gt;eot: embedded-opentype&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;구글 폰트에서 원하는 폰트 골라서 적용하는 방법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;폰트파일을 따로 준비하지 않아도 구글 폰트에서 원하는 폰트를 골라 자신의 웹페이지에 적용할 수 있는 방법이 있습니다. 우선 구글 폰트 사이트에 접속합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://fonts.google.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://fonts.google.com/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1637807325972&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Google Fonts&quot; data-og-description=&quot;Making the web more beautiful, fast, and open through great typography&quot; data-og-host=&quot;fonts.google.com&quot; data-og-source-url=&quot;https://fonts.google.com/&quot; data-og-url=&quot;https://fonts.google.com/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/d5Dal3/hyMtxpezMx/nqkZo4LIK4VzORy1hGKQeK/img.png?width=2400&amp;amp;height=1260&amp;amp;face=0_0_2400_1260&quot;&gt;&lt;a href=&quot;https://fonts.google.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://fonts.google.com/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/d5Dal3/hyMtxpezMx/nqkZo4LIK4VzORy1hGKQeK/img.png?width=2400&amp;amp;height=1260&amp;amp;face=0_0_2400_1260');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Google Fonts&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Making the web more beautiful, fast, and open through great typography&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;fonts.google.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1869&quot; data-origin-height=&quot;939&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EoQc9/btrl6K2T6dB/HXW70sqsgBlKMaDBMtKiEK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EoQc9/btrl6K2T6dB/HXW70sqsgBlKMaDBMtKiEK/img.png&quot; data-alt=&quot;구글 폰트에 접속하면 다양한 폰트를 고를 수 있다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EoQc9/btrl6K2T6dB/HXW70sqsgBlKMaDBMtKiEK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEoQc9%2Fbtrl6K2T6dB%2FHXW70sqsgBlKMaDBMtKiEK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;671&quot; height=&quot;337&quot; data-origin-width=&quot;1869&quot; data-origin-height=&quot;939&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;구글 폰트에 접속하면 다양한 폰트를 고를 수 있다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 다양한 폰트 종류를 볼 수 있는데요. 이 중에 원하는 폰트를 골라 클릭하여 상세페이지로 들어갑니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1873&quot; data-origin-height=&quot;921&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/biTVKK/btrl9i5YFhi/vkB9Ldfpo9ZCRxKcXmCeA0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/biTVKK/btrl9i5YFhi/vkB9Ldfpo9ZCRxKcXmCeA0/img.png&quot; data-alt=&quot;폰트 상세 페이지&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/biTVKK/btrl9i5YFhi/vkB9Ldfpo9ZCRxKcXmCeA0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbiTVKK%2Fbtrl9i5YFhi%2FvkB9Ldfpo9ZCRxKcXmCeA0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;679&quot; height=&quot;334&quot; data-origin-width=&quot;1873&quot; data-origin-height=&quot;921&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;폰트 상세 페이지&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Styles라는 탭에서 원하는 문구를 타이핑하고 픽셀 값을 조절하여&amp;nbsp;폰트의 모양, 굵기, 크기를 한눈에 확인할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Thin 100, Thin100 italic 과 같이 쓰여있는 것은 CSS 코드상에서 font-weight와 font-style을 의미합니다. 이 폰트를 불러와서 적용한 후 CSS에서 font-weight 값을 100으로 주거나 font-style값을 italic으로 주는 경우 위에 표기된 것처럼 텍스트가 보이게 될 것입니다. 그런데 그전에 먼저 해당 스타일을 선택을 해줘야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1869&quot; data-origin-height=&quot;977&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sqSqi/btrl4RPuP8s/NkVsDPaBwE5W9lQov1NCl0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sqSqi/btrl4RPuP8s/NkVsDPaBwE5W9lQov1NCl0/img.png&quot; data-alt=&quot;+ Select this style을 눌러줍니다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sqSqi/btrl4RPuP8s/NkVsDPaBwE5W9lQov1NCl0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsqSqi%2Fbtrl4RPuP8s%2FNkVsDPaBwE5W9lQov1NCl0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;687&quot; height=&quot;359&quot; data-origin-width=&quot;1869&quot; data-origin-height=&quot;977&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;+ Select this style을 눌러줍니다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;선택을 한 스타일만 import 해서 사용을 할 수 있습니다. 저는 모든 스타일을 다 선택하도록 하겠습니다. 그리고 페이지 상단에서 아래 이미지에서 동그라미표 한 아이콘을 클릭해줍니다. 그러면 오른쪽에 Selected family라고 쓰여있는 창이 뜰 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1864&quot; data-origin-height=&quot;956&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/blukOV/btrl56ZqxXB/9125EeHzKBb6PTJsofCih0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/blukOV/btrl56ZqxXB/9125EeHzKBb6PTJsofCih0/img.png&quot; data-alt=&quot;CSS 코드를 생성해서 알려주는 친절함&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/blukOV/btrl56ZqxXB/9125EeHzKBb6PTJsofCih0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FblukOV%2Fbtrl56ZqxXB%2F9125EeHzKBb6PTJsofCih0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1864&quot; height=&quot;956&quot; data-origin-width=&quot;1864&quot; data-origin-height=&quot;956&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;CSS 코드를 생성해서 알려주는 친절함&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Use on the web이라고 써있는 곳에서 &amp;lt;link&amp;gt;에 체크를 하고 해당 코드를 복사하여 자신이 만들고 있는 웹페이지의 head 태그 안에 붙여 넣습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1637808378149&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;head&amp;gt;
  &amp;lt;link rel=&quot;preconnect&quot; href=&quot;https://fonts.googleapis.com&quot;&amp;gt;
  &amp;lt;link rel=&quot;preconnect&quot; href=&quot;https://fonts.gstatic.com&quot; crossorigin&amp;gt;
  &amp;lt;link href=&quot;https://fonts.googleapis.com/css2?family=Lato:ital,wght@0,100;0,300;0,400;0,700;0,900;1,100;1,300;1,400;1,700;1,900&amp;amp;display=swap&quot; rel=&quot;stylesheet&quot;&amp;gt;
&amp;lt;/head&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 구글 폰트에서 원하는 폰트를 골라 불러오는 것에 성공했습니다. CSS 코드에서도 마찬가지로 Use on the web이라고 쓰여있는 곳에서 안내된 font-family 값을 사용하면 됩니다. 저는 전역에서 해당 폰트를 적용해보겠습니다. Use on the web에서 해당 코드를 복사해서 다음과 같이 스타일시트에 붙여 넣습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1637808508191&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;* {
  font-family: 'Lato', sans-serif;
{&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아까 구글 폰트에서 모든 스타일을 선택해서 불러왔었는데, 만약 그중에 Thin 100 intalic을 사용하려면 다음과 같이 CSS 코드를 작성합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1637808634843&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;.thin.italic {
  font-weight: 100;
  font-style: italic;
{&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;같은 방식으로 font-weight와 font-style값을 변경하면 선택해서 임포트한 스타일 중에 있다면 적용이 될 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;구글 폰트 라이선스에 관하여&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구글 폰트는 기본적으로 무료로 이용이 가능합니다. 그리고 대부분 Open Font License인 것으로 보입니다. 하지만 간혹 다른 라이선스를 가진 폰트가 존재할 수도 있기 때문에 폰트의 상세페이지에서 꼭 라이센스를 확인을 하시고 사용을 하는 것이 좋겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>CSS</category>
      <category>@font-face</category>
      <category>CSS</category>
      <category>font</category>
      <category>font-face</category>
      <category>font-family</category>
      <category>ttf</category>
      <category>구글 폰트</category>
      <category>구글 폰트 적용</category>
      <category>무료 폰트</category>
      <category>폰트 파일 적용</category>
      <author>cocoder16</author>
      <guid isPermaLink="true">https://cocoder16.tistory.com/53</guid>
      <comments>https://cocoder16.tistory.com/53#entry53comment</comments>
      <pubDate>Mon, 7 Feb 2022 08:00:44 +0900</pubDate>
    </item>
    <item>
      <title>git hooks, github actions로 CI 구축하기</title>
      <link>https://cocoder16.tistory.com/57</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;github actions을 사용하면 CI/CD파이프라인을 매우 쉽게 구축할 수 있다.&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에 dev 환경에서 새로운 기술을 익히는 프로젝트를 해보면서 실제 운영서버에 배포는 하지 않지만 CI라도 구축해보고자 하였고, github actions를 통해 쉽게 해낼 수 있었습니다. 이 포스팅에서는 github actions를 이용해 CI를 구축한 방법을 기록할 것입니다. 또한 CD도 같은 방법으로 하면 되니 추후에 CI/CD파이프라인을 구축할 일이 있을 때에 github actions를 이용하면 문제없이 할 수 있겠다는 생각을 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;workflows 추가&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;github repository에 들어가면 actions탭이 있습니다. 거기에서는 여러 workflows templates를 제공해주고 있습니다. 하지만 저는 로컬에서 직접 workflows를 만들어서 리모트에 푸시하는 방법으로 진행해보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트 폴더 루트 디렉터리에 .github 라는 이름으로 폴더를 만듭니다. 그리고 그 안에 workflows라는 이름의 폴더명을 만들고 그 안에 main.yml라는 파일을 만듭니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1639115585478&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;.github
ㄴworkflows
 ㄴmain.yml&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;main.yml을 편집 모드로 열고 차근차근 작성해나가겠습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 name과 on을 적어줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* main.yml&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1639115668456&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;name: CI

on: [push, pull_request]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;name&lt;/span&gt;은 workflows의 이름으로 보기 편한 이름을 지어주면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;on&lt;/span&gt;은 이 workflow를 발생시킬 trigger event를 정의합니다. push를 하거나 pull request를 남기면 trigger 되도록 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;push와 pull request는 merge된 기준으로 code를 통합하기에 test를 실행시킬 때 merge된 상태의 코드를 test 하게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그다음 &lt;span style=&quot;color: #ee2323;&quot;&gt;jobs&lt;/span&gt;를 적어줍니다. jobs 안에는 할 작업 목록들을 적을 수 있고 이들은 병렬로 실행됩니다. 직렬로 실행하게 만들려면 의존 작업을 &lt;span style=&quot;color: #ee2323;&quot;&gt;need&lt;/span&gt;: 안에 명시해줄 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1639115951197&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;jobs:
  job1:
  job2:&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 적으면 job1과 job2는 병렬로 실행됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1639115961363&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;jobs:
  job1:
  job2:
    needs: job1&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 위와 같이 job2 안에 needs: job1를 명시해주면 job2는 job1을 마치고 난 뒤에 실행됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;job 안에는 &lt;span style=&quot;color: #ee2323;&quot;&gt;runs-on&lt;/span&gt;으로 어떤 OS에서 실행할지를 명시할 수 있습니다. 저는 간단하게 ubuntu-lateset를 사용했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1639116141392&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;jobs:
  test:
    runs-on: ubuntu-latest&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그다음으로는 &lt;span style=&quot;color: #ee2323;&quot;&gt;steps&lt;/span&gt;를 적어줍니다 해당 job이 수행할 일들을 단계별로 적는 것입니다. 저는 cypress로 test를 하는 프로젝트였고 &quot;checkout -&amp;gt; 환경변수 파일 생성 -&amp;gt; cypress install -&amp;gt; 유닛 테스트 실행 -&amp;gt; 통합 테스트 실행&quot; 순으로 step을 정의했습니다. 여기서부터는 어떤 test 프레임워크를 사용하는지, 그리고 테스트 전략에 따라 커스터마이징이 필요합니다. 저의 경우에는 &lt;a href=&quot;https://docs.cypress.io/guides/continuous-integration/github-actions&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;cypress 공식문서의 CI파트&lt;/a&gt;를 참조하여 steps들을 작성했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;checkout step은 다음과 같이 작성합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1639116435968&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;jobs:
  test:
    runs-on: ubuntu-latest
    
    steps:
      - name: Checkout
        uses: actions/checkout@v2&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;환경변수 파일은 .github/workflows 폴더 안에 scripts폴더를 만들고 그 안에 env.sh파일을 만들어서 사용했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* env.sh&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1639116535957&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#!/bin/bash 

echo REACT_APP_HOST_URL=http://localhost:3000 &amp;gt;&amp;gt; .env
echo REACT_APP_API_URL=http://localhost:4000 &amp;gt;&amp;gt; .env&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;main.yml에서는 환경변수 파일을 만드는 step을 추가합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* main.yml&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1639116593629&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;jobs:
  test:
    runs-on: ubuntu-latest
    
    steps:
      - name: Checkout
        uses: actions/checkout@v2
        
      - name: Create env file
        shell: bash
        run: |
          touch .env
          bash ./.github/workflows/scripts/env.sh&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 step들은 cypress를 설치하고 test를 실행하는 과정입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1639116752485&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;- name: Cypress install
  uses: cypress-io/github-action@v2
  with:
    # Disable running of tests within install job
    runTests: false
    build: yarn build

- name: Cypress run unit
  run: yarn test:unit:run

# Install NPM dependencies, cache them correctly
# and run all Cypress tests
- name: Cypress run integration
  uses: cypress-io/github-action@v2
  with:
    # Specify Browser since container image is compile with Firefox
    browser: firefox
    start: yarn start&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 작업한 workflows를 리모트에 푸시해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제부터는 리모트에 push되거나 pull request가 생성되면 test를 자동으로 실행하게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;로컬에서는 git hooks로 트리거를 만들 수 있다.&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리모트에서의 잦은 테스트 실패를 줄이기 위해서 로컬에서도 자동으로 테스트를 돌리도록 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;git hooks를 사용하면 commit이나 push 등의 이벤트를 hooking하여 스크립트를 실행할 수 있습니다. 이때 테스트를 실행하도록 스크립트를 작성합니다. 저는 매 커밋마다 테스트를 실행하여 테스트를 통과해야만 커밋이 완료되도록 &lt;b&gt;pre-commit&lt;/b&gt; 이벤트에 테스트를 실행하는 스크립트를 작성했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;husky를 사용하면 git hooks를 쉽게 편집할 수 있다.&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한편 husky라는 라이브러리를 사용하면 git hooks를 쉽게 편집할 수 있습니다. husky를 통해 pre-commit 스크립트를 만들어보겠습니다. 저는 자바스크립트 프로젝트를 했으므로 자바스크립트를 기준으로 설명을 진행하겠습니다. 일단 husky를 개발 의존성 모듈로 설치합니다. (&lt;a href=&quot;https://typicode.github.io/husky/#/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;husky 공식문서 링크&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1639117080155&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;yarn add -D husky&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 package.json에 script를 추가합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* package.json&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1639117544026&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;scripts&quot;: {
    &quot;prepare&quot;: &quot;husky install&quot;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 husky경로까지 입맛에 맞게 설정해주었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1639118011980&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;scripts&quot;: {
    &quot;husky:install&quot;: &quot;husky install ./src/config/husky&quot;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;터미널에서 만든 명령어를 실행합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1639117564532&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;yarn prepare&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로 pre-commit script를 생성해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1639117601669&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npx husky add .husky/pre-commit &quot;yarn test&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;yarn test가 테스트 실행 명령어일 때 저렇게 써주고, 각자 프로젝트에서 테스트 실행 명령어를 &quot;&quot;안에 적어주면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 src/config 폴더에서 husky를 관리했기 때문에 다음과 같이 파일 경로를 지정해서 만들어줬습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1639117740220&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npx husky add ./src/config/husky/pre-commit &quot;yarn test&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 파일을 열어보면 다음과 같은 스크립트가 작성되어있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* pre-commit&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1639117817289&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#!/bin/sh
. &quot;$(dirname &quot;$0&quot;)/_/husky.sh&quot;

yarn test&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 스크립트를 수정할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;pre-commit 스크립트 편집하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드 컨벤션 통일은 중요합니다. git code 변경 차이점에 진짜 변경된 코드만 남겨서 가독성에 방해를 받지 않으려면 pre-commit에서 코드 컨벤션 검사를 해주면 좋습니다. 따라서 저는 pre-commit에서 자바스크립트의 lint검사를 실행해주려고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;lint-staged 라이브러리 사용하기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;lint-staged를 git hooks와 함께 사용하면 린트 검사 자동화를 할 수 있습니다. lint-staged를 설치하고 package.json에 스크립트를 추가해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1639118413970&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;yarn add -D lint-staged&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* package.json&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1639118433999&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&quot;lint-staged&quot;: {
  &quot;*.{js,ts,tsx}&quot;: [
  	&quot;eslint --fix&quot;
  ]
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 다시 pre-commit 파일을 열어 스크립트를 한 줄 추가해줍니다. test를 실행하기 전에 lint검사를 먼저 진행하려고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* pre-commit&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1639118514407&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#!/bin/sh
. &quot;$(dirname &quot;$0&quot;)/_/husky.sh&quot;

yarn lint-staged
yarn test&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 코드 변경을 해보고 commit을 하여 터미널을 보며 pre-commit의 스크립트들이 실행되는 것을 지켜봅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>DevOps</category>
      <category>CI</category>
      <category>ci/cd</category>
      <category>CI/CD 파이프라인</category>
      <category>git hooks</category>
      <category>github actions</category>
      <category>husky</category>
      <category>lint-staged</category>
      <category>pre-commit</category>
      <category>Workflows</category>
      <category>테스트 자동화</category>
      <author>cocoder16</author>
      <guid isPermaLink="true">https://cocoder16.tistory.com/57</guid>
      <comments>https://cocoder16.tistory.com/57#entry57comment</comments>
      <pubDate>Mon, 17 Jan 2022 08:00:15 +0900</pubDate>
    </item>
    <item>
      <title>[채팅 웹사이트 구현 - 3장] React로 채팅 웹 만들기</title>
      <link>https://cocoder16.tistory.com/62</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전 장. &lt;a href=&quot;https://cocoder16.tistory.com/59&quot; rel=&quot;noopener&quot;&gt;[채팅&amp;nbsp;웹사이트&amp;nbsp;구현&amp;nbsp;-&amp;nbsp;2장]&amp;nbsp;Node.js에서&amp;nbsp;Socket.io를&amp;nbsp;이용하여&amp;nbsp;소켓&amp;nbsp;서버&amp;nbsp;만들기&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;채팅 웹사이트 프론트엔드 구현 들어가기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전 장에서 소켓 서버를 만들어 채팅 웹사이트에서 필요한 소켓 이벤트들을 정의하고 그 기능들을 구현했습니다. 이제 클라이언트 쪽도 개발하여 하나의 완전품으로서 동작할 수 있는 채팅 웹을 만들 것입니다. 프론트엔드는 React기술로 구현을 해볼 것입니다. 먼저 프로젝트 세팅은 간단하게 create-react-app으로 진행합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1639244326614&quot; class=&quot;javascript&quot; style=&quot;margin: 20px auto 0px; display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px;&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npx create-react-app chat-app&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;기술 스택 정리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Socket.io로 서버를 구현했기 때문에 클라이언트는 socket.io-client 라이브러리를 사용해야합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;양방향 통신이기 때문에 클라이언트에서 소켓 이벤트 리스너를 쓰는 방법도 백엔드 개발했을 때와 똑같습니다. 이벤트 리스너를 설치하여 이벤트가 들어오면 콜백 함수를 통해 ping-pong 하는 형태입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1639244326614&quot; class=&quot;javascript&quot; style=&quot;margin: 20px auto 0px; display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px;&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;yarn add socket.io-client&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금 만드는 앱의 규모는 작기 때문에 redux같은 상태 관리 라이브러리는 사용하는 것이 오히려 복잡도만 늘릴 뿐 관리에 도움이 되지 않기 때문에 사용하지 않겠습니다. 대신 socket 객체는 context를 사용해서 어떤 컴포넌트에서든 props로 복잡하게 받지 않아도 사용할 수 있도록 할 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;디자인은 bootstrap이 기본적으로 제공하는 디자인을 사용할 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1639244326615&quot; class=&quot;javascript&quot; style=&quot;margin: 20px auto 0px; display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px;&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;yarn add bootstrap&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;public/index.html 에서 head 태그 안에 cdn 링크를 추가해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1639244326615&quot; class=&quot;html xml&quot; style=&quot;margin: 20px auto 0px; display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px;&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;link
  href=&quot;https://cdn.jsdelivr.net/npm/bootstrap@5.0.0/dist/css/bootstrap.min.css&quot;
  rel=&quot;stylesheet&quot;
  integrity=&quot;sha384-wEmeIV1mKuiNpC+IOBjI7aAzPcEZeedi5yW5f2yOq55WWLwNGmvvx4Um1vskeMj0&quot;
  crossorigin=&quot;anonymous&quot;
/&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;환경변수 관리는 이전 장에서 한 것과 같이 env-cmd 라이브러리를 사용합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1639244326615&quot; class=&quot;javascript&quot; style=&quot;margin: 20px auto 0px; display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px;&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;yarn add env-cmd&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* .env.development&lt;/p&gt;
&lt;pre id=&quot;code_1639244326615&quot; class=&quot;html xml&quot; style=&quot;margin: 20px auto 0px; display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px;&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;REACT_APP_BACK_URL=http://localhost:4000&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;소켓 객체 생성하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;src/service/socket.js에서 소켓 객체를 생성하고 소켓과 context를 export하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* src/service/socket.js&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1639244326615&quot; class=&quot;javascript&quot; style=&quot;margin: 20px auto 0px; display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px;&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { createContext } from &quot;react&quot;;
import socketIo from &quot;socket.io-client&quot;;

export const socket = socketIo(String(process.env.REACT_APP_BACK_URL), { withCredentials: true });
export const SocketContext = createContext(socket);

socket.on(&quot;connect&quot;, () =&amp;gt; {
  console.log(&quot;socket server connected.&quot;);
});

socket.on(&quot;disconnect&quot;, () =&amp;gt; {
  console.log(&quot;socket server disconnected.&quot;);
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;App 컴포넌트에서는 SocketContext를 provider로 묶어줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 App 컴포넌트가 unmount될 때에는 소켓 연결을 끊도록 useEffect return 안에 disconnect를 실행하는 함수를 추가합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* App.jsx&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1639244326615&quot; class=&quot;javascript&quot; style=&quot;margin: 20px auto 0px; display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px;&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useEffect } from &quot;react&quot;;

import { socket, SocketContext } from &quot;src/service/socket&quot;;

function App() {
  useEffect(() =&amp;gt; {
    return () =&amp;gt; {
      socket.disconnect();
    }
  }, []);
  
  return (
    &amp;lt;SocketContext.Provider value={socket}&amp;gt;
      App
    &amp;lt;/SocketContext.Provider&amp;gt;
  );
}

export default App;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;이벤트 타입별로 기능 구현하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버를 개발할 때와 마찬가지로 이벤트 타입별로 기능 구현을 하겠습니다. 서버에서 총 4가지 타입을 정의했었는데 클라이언트에서도 이 타입들을 똑같이 정의합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1639389499620&quot; class=&quot;javascript&quot; style=&quot;margin: 20px auto 0px; display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; cursor: default; z-index: 1;&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&quot;JOIN_ROOM&quot;: 유저가 방에 참가했을 때 발생
&quot;UPDATE_NICKNAME&quot;: 유저가 닉네임을 변경했을 때 발생
&quot;SEND_MESSAGE&quot;: 유저가 메시지를 전송했을 때 발생
&quot;RECEIVE_MESSAGE&quot;: 유저가 메시지를 받을 때 발생&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JOIN_ROOM은 채팅 웹에 처음 들어왔을 때 발생시킬 것입니다. 지금 만들 웹은 원페이지이고, 로그인 기능도 따로 없습니다. 따라서 App컴포넌트가 마운트될 때 JOIN_ROOM을 emit해주려고 합니다. 그런데 이 event를 발생시킬 때 서버에 유저 닉네임을 같이 전송해줄 것이므로, useEffect는 dependency로 nickname이라는 상태 값을 가지게 될 것입니다. 그리고 이 nickname을 App컴포넌트에서 state로 관리할 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* App.jsx&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1639244326615&quot; class=&quot;javascript&quot; style=&quot;margin: 20px auto 0px; display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px;&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useState, useEffect } from &quot;react&quot;;

import { socket, SocketContext, SOCKET_EVENT } from &quot;src/service/socket&quot;;

function App() {
  const [nickname, setNickname] = useState(&quot;김첨지&quot;);

  useEffect(() =&amp;gt; {
    return () =&amp;gt; { // App 컴포넌트 unmount시 실행
      socket.disconnect();
    }
  }, []);
  
  useEffect(() =&amp;gt; {
    socket.emit(SOCKET_EVENT.JOIN_ROOM, { nickname }); // JOIN_ROOM event type과 nickname data를 서버에 전송한다.
  }, [nickname]);
  
  return (
    &amp;lt;SocketContext.Provider value={socket}&amp;gt;
      App
    &amp;lt;/SocketContext.Provider&amp;gt;
  );
}

export default App;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;event type들은 src/service/socket.js에 정의해서 관리할 것입니다. 이전 장 리팩터링에서 했던 것과 같은 방법입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* src/service/socket.js&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1639389667804&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// ...

export const SOCKET_EVENT = {
  JOIN_ROOM: &quot;JOIN_ROOM&quot;,
  UPDATE_NICKNAME: &quot;UPDATE_NICKNAME&quot;,
  SEND_MESSAGE: &quot;SEND_MESSAGE&quot;,
  RECEIVE_MESSAGE: &quot;RECEIVE_MESSAGE&quot;,
};

// ...&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로는 닉네임을 변경할 때 발생하는 UPDATE_NICKNAME 이벤트를 구현할 것입니다. 지금 구현한 코드에서는 nickname이 변경될 때마다 useEffect에서 JOIN_ROOM 이벤트를 emit해주고 있습니다. 이것을 처음에만 JOIN_ROOM이벤트를 emit하고 다음 nickname변경부터는 UPDATE_NICKNAME 이벤트를 emit하도록 하려고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UPDATE_NICKNAME 이벤트는 변경된 닉네임 값과 이전 닉네임 값을 함께 전송할 것입니다. 그래서 이전 닉네임 값을 가지는 prevNickname이라는 변수를 App 컴포넌트 내에서 선언하겠습니다. 이 값의 변경은 App 컴포넌트 리렌더링을 발생시키지 않기를 원하기 때문에 state가 아닌 ref로 관리할 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* App.jsx&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1639244326616&quot; class=&quot;javascript&quot; style=&quot;margin: 20px auto 0px; display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px;&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useState, useRef, useEffect } from &quot;react&quot;;

// ...

function App() {
  const prevNickname = useRef(null); // prevNickname 변경은 컴포넌트를 리렌더링 하지않습니다.
  const [nickname, setNickname] = useState(&quot;김첨지&quot;);

  // ...
  
  useEffect(() =&amp;gt; {
    if (prevNickname.current) {
      socket.emit(SOCKET_EVENT.UPDATE_NICKNAME, { // 서버에는 이전 닉네임과 바뀐 닉네임을 전송해줍니다.
        prevNickname: prevNickname.current,
        nickname,
      });
    } else {
      socket.emit(SOCKET_EVENT.JOIN_ROOM, { nickname });
    }
  }, [nickname]);
  
  return (
    &amp;lt;SocketContext.Provider value={socket}&amp;gt;
      App
    &amp;lt;/SocketContext.Provider&amp;gt;
  );
}

export default App;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이벤트 전송 코드는 작성했는데 닉네임을 변경할 수 있는 UI가 없습니다. 이제 src/components/NicknameForm.jsx 파일을 만듭니다. 이 컴포넌트는 닉네임을 입력할 수 있는 input과 전송할 수 있는 button을 가져야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* src/components/NicknameForm.jsx&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1639244326616&quot; class=&quot;javascript&quot; style=&quot;margin: 20px auto 0px; display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px;&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useState, useCallback } from &quot;react&quot;;

function NicknameForm({ handleSubmitNickname }) {
  const [nickname, setNickname] = useState(&quot;&quot;);

  const handleChangeNickname = useCallback(event =&amp;gt; {
    setNickname(event.target.value);
  }, []);

  const handleSubmit = useCallback(() =&amp;gt; {
    handleSubmitNickname(nickname);
    setNickname(&quot;&quot;);
  }, [handleSubmitNickname, nickname]);

  return (
    &amp;lt;form className=&quot;d-flex&quot;&amp;gt;
      &amp;lt;div className=&quot;card d-flex flex-row align-items-center&quot;&amp;gt;
        &amp;lt;label htmlFor=&quot;user-name-input&quot; style={{ width: 60 }}&amp;gt;
          닉네임
        &amp;lt;/label&amp;gt;
        &amp;lt;input
          type=&quot;text&quot;
          className=&quot;form-control w300&quot;
          id=&quot;user-name-input&quot;
          maxLength={12}
          value={nickname}
          onChange={handleChangeNickname}
        /&amp;gt;
        &amp;lt;button
          type=&quot;button&quot;
          className=&quot;btn btn-primary send-btn&quot;
          value=&quot;확인&quot;
          onClick={handleSubmit}
        /&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/form&amp;gt;
  );
}

export default NicknameForm;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;bootstrap을 사용해서 className으로 디자인을 해주고 있습니다. 추가적인 CSS 코드가 만들어서 화면에서 디자인 적용은 아직 제대로 안된 상태일 것입니다. 추가 CSS 코드는 기능 구현이 끝낸 후 소개하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;input에서 값을 타이핑할 때마다 NicknameForm 컴포넌트가 그 값을 state로 관리를 해주다가 submit을 할 때에 그 값을 App 컴포넌트에 넘겨주어, App 컴포넌트에서 선언된 nickname state값을 변경해줄 것입니다. 따라서 handleSubmitNickname은 App에서 선언합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* App.jsx&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1639244326616&quot; class=&quot;javascript&quot; style=&quot;margin: 20px auto 0px; display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px;&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useState, useRef, useCallback, useEffect } from &quot;react&quot;;

import NicknameForm from &quot;src/components/NicknameForm&quot;;

// ...

function App() {
  // ...
  
  const handleSubmitNickname = useCallback(newNickname =&amp;gt; {
      prevNickname.current = nickname;
      setNickname(newNickname);
    },
    [nickname]
  );
  
  return (
    &amp;lt;SocketContext.Provider value={socket}&amp;gt;
      &amp;lt;div className=&quot;d-flex flex-column justify-content-center align-items-center vh-100&quot;&amp;gt;
        &amp;lt;NicknameForm handleSubmitNickname={handleSubmitNickname} /&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/SocketContext.Provider&amp;gt;
  );
}

export default App;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지 구현한 내용을 테스트해보겠습니다. 백엔드 서버와 프론트엔드 서버를 둘다 실행시킵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(터미널을 열고 Node.js 소켓 서버 프로젝트 폴더 경로에서 yarn start 입력, 또 다른 터미널을 열고 프론트엔드 프로젝트 폴더 경로에서 yarn start 입력, 하여 두 서버를 실행합니다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 브라우저를 열고 localhost:3000 주소에 접속하여 클라이언트에 접속합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인풋에 닉네임을 입력하고 버튼을 누르면 소켓 서버를 실행 중인 터미널에서 로그가 출력될 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그의 세부적인 텍스트는 지금까지 따라온 코드랑 스크린샷 사이에 다소 차이가 있을 수 있습니다만.. 어쨌든 터미널에 JOIN_ROOM, UPDATE_NICKNAME 순서로 이벤트 로그가 출력되었으면 정상적으로 작동한 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;936&quot; data-origin-height=&quot;126&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dkFeQY/btrnQjWO4Gl/uz1YN1RrTtzC33Vh1hcja0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dkFeQY/btrnQjWO4Gl/uz1YN1RrTtzC33Vh1hcja0/img.png&quot; data-alt=&quot;테스트 결과 소켓 서버 로그 예시&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dkFeQY/btrnQjWO4Gl/uz1YN1RrTtzC33Vh1hcja0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdkFeQY%2FbtrnQjWO4Gl%2Fuz1YN1RrTtzC33Vh1hcja0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;578&quot; height=&quot;78&quot; data-origin-width=&quot;936&quot; data-origin-height=&quot;126&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;테스트 결과 소켓 서버 로그 예시&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 채팅메시지 리스트를 구현할 것입니다. 이 리스트는 RECEIVE_MESSAGE 이벤트와 함께 받은 데이터를 저장하며 화면 내 채팅창에 보여주는 기능을 할 것입니다. src/components/chatRoom.jsx를 만듭니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* src/components/chatRoom.jsx&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1639244326616&quot; class=&quot;javascript&quot; style=&quot;margin: 20px auto 0px; display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px;&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useState, useCallback, useEffect, useContext, useRef } from &quot;react&quot;;

import { SocketContext, SOCKET_EVENT } from &quot;src/service/socket&quot;;

function ChatRoom({ nickname }) {
  const [messages, setMessages] = useState([]);
  const chatWindow = useRef(null);
  const socket = useContext(SocketContext);

  // 새 메시지를 받으면 스크롤을 이동하는 함수
  const moveScrollToReceiveMessage = useCallback(() =&amp;gt; { 
    if (chatWindow.current) {
      chatWindow.current.scrollTo({
        top: chatWindow.current.scrollHeight,
        behavior: &quot;smooth&quot;,
      });
    }
  }, []);

  // RECEIVE_MESSAGE 이벤트 콜백: messages state에 데이터를 추가합니다.
  const handleReceiveMessage = useCallback(pongData =&amp;gt; {
      const newMessage = makeMessage(pongData); // makeMessage는 아직 구현하지 않은 함수.
      setMessages(messages =&amp;gt; [...messages, newMessage]);
      moveScrollToReceiveMessage();
    },
    [moveScrollToReceiveMessage]
  );

  useEffect(() =&amp;gt; {
    socket.on(SOCKET_EVENT.RECEIVE_MESSAGE, handleReceiveMessage); // 이벤트 리스너 설치

    return () =&amp;gt; {
      socket.off(SOCKET_EVENT.RECEIVE_MESSAGE, handleReceiveMessage); // 이벤트 리스너 해제
    };
  }, [socket, handleReceiveMessage]);

  return (
    &amp;lt;div
      className=&quot;d-flex flex-column&quot;
      style={{ width: 1000 }}
    &amp;gt;
      &amp;lt;div className=&quot;text-box&quot;&amp;gt;
        &amp;lt;span&amp;gt;{nickname}&amp;lt;/span&amp;gt; 님 환영합니다!
      &amp;lt;/div&amp;gt;
      &amp;lt;div
        className=&quot;chat-window card&quot;
        ref={chatWindow}
      &amp;gt;
        {messages.map((message, index) =&amp;gt; { 
          const { nickname, content, time } = message;
          // messages 배열을 map함수로 돌려 각 원소마다 item을 렌더링 해줍니다.
          return (
            &amp;lt;div key={index} className=&quot;d-flex flex-row&quot;&amp;gt;
              {nickname &amp;amp;&amp;amp; &amp;lt;div className=&quot;message-nickname&quot;&amp;gt;{nickname}: &amp;lt;/div&amp;gt;}
              &amp;lt;div&amp;gt;{content}&amp;lt;/div&amp;gt;
              &amp;lt;div className=&quot;time&quot;&amp;gt;{time}&amp;lt;/div&amp;gt;
            &amp;lt;/div&amp;gt;
          );
        })}
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}

export default ChatRoom;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 ChatRoom 컴포넌트는 받은 메시지들을 렌더링하는 컴포넌트입니다. 받은 메시지는 배열 자료구조로 state로 선언되어 있으며, 각 메시지들은 nickname, content, time 프로퍼티를 가지고 있어서 이 값들을 화면에 보여주고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메시지를 받는 이벤트는 RECEIVE_MESSAGE로 정의했었는데 이 이벤트를 useEffect() 안에서 컴포넌트가 마운트될 때 설치하고 언마운트될 때 해제하고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 채팅 메시지 목록 창 엘리먼트를 useRef를 이용해 참조하여, 새 메시지를 받을 때마다 스크롤을 이동하는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #24292f;&quot;&gt;moveScrollToReceiveMessage 함수를 정의했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #24292f;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;socket 객체는 props을 통해 받지 않아도 미리 만들어놨던 context로 사용할 수 있으므로 useContext를 통해 사용하고 있습니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #24292f;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;handleReceiveMessage 안에는 아직 구현하지 않은 makeMessage라는 함수가 있습니다. 이것은 서버가 던져준 데이터를 가지고 화면에 보여줄 텍스트를 가공해서 반환해주는 역할을 할 것입니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #24292f;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;서버가 미리 텍스트를 만들어서 보내주는 것이 아니라 클라이언트가 raw data를 화면에 출력할 텍스트로 가공하는 역할을 부담하는 이유는 뷰단에서만 사용될 데이터의 단순 연산에 대한 부담을 서버 컴퓨터가 갖지 않도록 하기 위해서입니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #24292f;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;makeMessage는 src/service/socket.js에서 선언하겠습니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;* src/service/socket.js&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1639390674794&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// ...

export const makeMessage = pongData =&amp;gt; {
  const { prevNickname, nickname, content, type, time } = pongData;

  let nicknameLabel;
  let contentLabel = &quot;&quot;;

  switch (type) {
    case SOCKET_EVENT.JOIN_ROOM: {
      contentLabel = `${nickname} has joined the room.`;
      break;
    }
    case SOCKET_EVENT.UPDATE_NICKNAME: {
      contentLabel = `User's name has been changed.\n ${prevNickname} =&amp;gt; ${nickname}.`;
      break;
    }
    case SOCKET_EVENT.SEND_MESSAGE: {
      contentLabel = String(content);
      nicknameLabel = nickname;
      break;
    }
    default:
  }

  return {
    nickname: nicknameLabel,
    content: contentLabel,
    time: dayjs(time).format(&quot;HH:mm&quot;),
  };
};

// ...&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #24292f;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;이제 App.jsx에서 이 ChatRoom 컴포넌트를 렌더링 해줍니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;* App.jsx&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1639244326618&quot; class=&quot;javascript&quot; style=&quot;margin: 20px auto 0px; display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px;&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// ...

import NicknameForm from &quot;src/components/NicknameForm&quot;;
import ChatRoom from &quot;src/components/ChatRoom&quot;;

// ...

function App() {
  // ...
  
  return (
    &amp;lt;SocketContext.Provider value={socket}&amp;gt;
      &amp;lt;div className=&quot;d-flex flex-column justify-content-center align-items-center vh-100&quot;&amp;gt;
        &amp;lt;NicknameForm handleSubmitNickname={handleSubmitNickname} /&amp;gt;
        &amp;lt;ChatRoom nickname={nickname} /&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/SocketContext.Provider&amp;gt;
  );
}

export default App;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기능이 잘 작동하는지 확인하기 위해 localhost:3000에 접속한 후 닉네임 변경을 해봅니다. 메시지가 화면에 있는 채팅창에 출력되면 제대로 기능하는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 마지막으로 남은 이벤트를 구현해봅시다. 유저가 메시지를 전송할 때 발생하는 SEND_MESSAGE 이벤트입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;src/components/MessageForm.jsx를 만듭니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* src/components/MessageForm.jsx&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1639244326618&quot; class=&quot;javascript&quot; style=&quot;margin: 20px auto 0px; display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px;&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useState, useCallback, useContext } from &quot;react&quot;;

import { SocketContext, SOCKET_EVENT } from &quot;src/service/socket&quot;;

function MessageForm({ nickname }) {
  const [typingMessage, setTypingMessage] = useState(&quot;&quot;);
  const socket = useContext(SocketContext);

  // textarea에서 텍스트를 입력하면 typingMessage state를 변경합니다.
  const handleChangeTypingMessage = useCallback(event =&amp;gt; {
    setTypingMessage(event.target.value);
  }, []);

 // 버튼을 누르면 실행합니다.
  const handleSendMesssage = useCallback(() =&amp;gt; {
    // 공백을 trim()으로 제거합니다.
    const noContent = typingMessage.trim() === &quot;&quot;;

    // 아무 메시지도 없으면 아무 일도 발생하지 않습니다.
    if (noContent) {
      return;
    }

    // 메시지가 있으면 nickname과 message를 SEND_MESSAGE 이벤트 타입과 함께 소켓 서버로 전송합니다.
    socket.emit(SOCKET_EVENT.SEND_MESSAGE, {
      nickname,
      content: typingMessage,
    });
    // state값은 공백으로 변경해줍니다.
    setTypingMessage(&quot;&quot;);
  }, [socket, nickname, typingMessage]);

  return (
    &amp;lt;form className=&quot;card&quot;&amp;gt;
      &amp;lt;div className=&quot;d-flex align-items-center&quot;&amp;gt;
        &amp;lt;textarea
          className=&quot;form-control&quot;
          maxLength={400}
          autoFocus
          value={typingMessage}
          onChange={handleChangeTypingMessage}
        /&amp;gt;
        &amp;lt;button
          type=&quot;button&quot;
          className=&quot;btn btn-primary send-btn&quot;
          onClick={handleSendMesssage}
        &amp;gt;
          전송
        &amp;lt;/button&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/form&amp;gt;
  );
}

export default MessageForm;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 컴포넌트는 메시지 입력창에 입력하고 있는 텍스트를 state로 관리합니다. 그리고 전송 버튼을 누르면 handleSendMessage함수가 실행되어 SEND_MESSAGE 이벤트를 nickname과 입력한 텍스트 데이터와 함께 소켓 서버로 emit합니다. ChatRoom에서 이 컴포넌트를 import 해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1639244326618&quot; class=&quot;javascript&quot; style=&quot;margin: 20px auto 0px; display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px;&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// ...

import MessageForm from &quot;src/components/MessageForm&quot;;

function ChatRoom({ nickname }) {
  // ...

  return (
    &amp;lt;div
      className=&quot;d-flex flex-column&quot;
      style={{ width: 1000 }}
    &amp;gt;
      &amp;lt;div className=&quot;text-box&quot;&amp;gt;
        &amp;lt;span&amp;gt;{nickname}&amp;lt;/span&amp;gt; 님 환영합니다!
      &amp;lt;/div&amp;gt;
      &amp;lt;div
        className=&quot;chat-window card&quot;
        ref={chatWindow}
      &amp;gt;
        {messages.map((message, index) =&amp;gt; { 
          const { nickname, content, time } = message;
          return (
            &amp;lt;div key={index} className=&quot;d-flex flex-row&quot;&amp;gt;
              {nickname &amp;amp;&amp;amp; &amp;lt;div className=&quot;message-nickname&quot;&amp;gt;{nickname}: &amp;lt;/div&amp;gt;}
              &amp;lt;div&amp;gt;{content}&amp;lt;/div&amp;gt;
              &amp;lt;div className=&quot;time&quot;&amp;gt;{time}&amp;lt;/div&amp;gt;
            &amp;lt;/div&amp;gt;
          );
        })}
      &amp;lt;/div&amp;gt;
      &amp;lt;MessageForm nickname={nickname} /&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}

export default ChatRoom;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;CSS 덧붙이기 &amp;amp; 완성&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;계속 화면을 보면서 따라오고 계셨다면 퍼블리싱이 뭔가 이상하다고 느끼셨을 것 같습니다. 사실 여태까지 미리 정의해놓은 CSS 코드들을 className 안에 사용하고 있었는데요. 작성된 CSS 코드를 이제 공개합니다. index.css에 다음 코드를 추가해주시면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* index.css&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1639244326618&quot; class=&quot;css&quot; style=&quot;margin: 20px auto 0px; display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px;&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#root {
  min-height: 100vh;
  background-color: #f8f9fa;
}

button.with-icon {
  display: flex;
}

.w300 {
  width: 300px;
}

.message-nickname {
  font-weight: bold;
  margin-right: 4px;
}

.send-btn {
  height: 38px;
  margin-left: 6px;
}

.card {
  padding: 14px;
}

.text-box {
  padding: 6px;
}

.time {
  margin-left: 4px;
  color: #aaa;
  font-size: 13px;
  line-height: 2;
}

.chat-window {
  height: 400px;
  overflow-y: auto;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;완성...!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;359&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d32Hs6/btrnCArz2Ge/6G4EbdNT4eiQJqqDTbPog1/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d32Hs6/btrnCArz2Ge/6G4EbdNT4eiQJqqDTbPog1/img.gif&quot; data-alt=&quot;완성된 채팅 웹 사용하는 모습&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d32Hs6/btrnCArz2Ge/6G4EbdNT4eiQJqqDTbPog1/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/d32Hs6/btrnCArz2Ge/6G4EbdNT4eiQJqqDTbPog1/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;359&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;359&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;완성된 채팅 웹 사용하는 모습&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div class=&quot;revenue_unit_item adsense  responsive&quot;&gt;&lt;ins class=&quot;adsbygoogle&quot; style=&quot;display: block;&quot; data-ad-host=&quot;ca-host-pub-9691043933427338&quot; data-ad-client=&quot;ca-pub-9807016842906892&quot; data-ad-format=&quot;auto&quot;&gt;&lt;/ins&gt;
&lt;script&gt;(adsbygoogle = window.adsbygoogle || []).push({}); &lt;/script&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마무리 - 프론트엔드 아쉬운 부분들&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지 Node.js로 소켓 서버를 만들고 React.js로 프론트엔드 개발을 하여 Socket.io로 실시간 양방향 통신이 가능한 채팅 웹을 만들었습니다. 프론트엔드도 마찬가지로 남아있는 아쉬운 부분들을 정리하고 마무리하려고 합니다. 이번에는 리팩터링과 UX 개선을 간단하게 얘기해보려고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 리팩터링 - 컴포넌트 구조 개선&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금 저희의 React 프로젝트 컴포넌트 구조는 다음과 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1639395887812&quot; class=&quot;javascript&quot; style=&quot;margin: 20px auto 0px; display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; cursor: default; z-index: 1;&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;App ㅡ Nicknameform
    ㄴ ChatRoom ㅡ MessageForm&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이대로도 큰 문제는 없습니다만 하나하나 뜯어보면 더 작은 단위로 분리를 하면 훨씬 가독성이 높은 컴포넌트가 될 여지가 있는 부분들이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 ChatRoom 컴포넌트의 jsx 부분을 보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* ChatRoom&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1639395887813&quot; class=&quot;javascript&quot; style=&quot;margin: 20px auto 0px; display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; cursor: default; z-index: 1;&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function ChatRoom({ nickname }) {
  // ...
  
  return (
    &amp;lt;div
      className=&quot;d-flex flex-column&quot;
      style={{ width: 1000 }}
    &amp;gt;
      &amp;lt;div className=&quot;text-box&quot;&amp;gt;
        &amp;lt;span&amp;gt;{nickname}&amp;lt;/span&amp;gt; 님 환영합니다!
      &amp;lt;/div&amp;gt;
      &amp;lt;div
        className=&quot;chat-window card&quot;
        ref={chatWindow}
      &amp;gt;
        {messages.map((message, index) =&amp;gt; { 
          const { nickname, content, time } = message;
          return (
            &amp;lt;div key={index} className=&quot;d-flex flex-row&quot;&amp;gt;
              {nickname &amp;amp;&amp;amp; &amp;lt;div className=&quot;message-nickname&quot;&amp;gt;{nickname}: &amp;lt;/div&amp;gt;}
              &amp;lt;div&amp;gt;{content}&amp;lt;/div&amp;gt;
              &amp;lt;div className=&quot;time&quot;&amp;gt;{time}&amp;lt;/div&amp;gt;
            &amp;lt;/div&amp;gt;
          );
        })}
      &amp;lt;/div&amp;gt;
      &amp;lt;MessageForm nickname={nickname} /&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;messages 배열을 map을 이용해 각 원소를 렌더링하는 부분입니다. 지금도 문제는 없지만 map함수 내의 return 안에 속하는 jsx 부분은 message item이라는 하나의 독립적 의미를 가지기 때문에 MessageItem이라는 이름의 컴포넌트로 만들어서 관리를 해주면 가독성이 훨씬 좋아집니다. 마찬가지로 이 MessageItem들을 그리는 map 함수를 포함하는 div를 MessageList라는 이름의 컴포넌트로 만들면 ChatRoom 안에 MessageList가 있고 해당 MessageList는 MessageItem을 매핑하고 있다는 가독성을 가지게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* ChatRoom 컴포넌트 jsx 개선&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1639395887813&quot; class=&quot;javascript&quot; style=&quot;margin: 20px auto 0px; display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; cursor: default; z-index: 1;&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function ChatRoom({ nickname }) {
  // ...
  
  return (
    &amp;lt;div
      className=&quot;d-flex flex-column&quot;
      style={{ width: 1000 }}
    &amp;gt;
      &amp;lt;div className=&quot;text-box&quot;&amp;gt;
        &amp;lt;span&amp;gt;{nickname}&amp;lt;/span&amp;gt; 님 환영합니다!
      &amp;lt;/div&amp;gt;
      &amp;lt;MessageList /&amp;gt;
      &amp;lt;MessageForm nickname={nickname} /&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MessageList 컴포넌트로 분리하고 보니 ChatRoom 컴포넌트는 jsx 내에서 MessageList와 MessageForm을 빼면 담당할 로직이 nickname을 렌더링 해주는 것 외에는 아무것도 없습니다. 모든 정의된 로직들이 MessageList가 담당하고 있기 때문입니다. 따라서 ChatRoom에서 return 위에 있는 모든 코드를 MessageList 안으로 옮겨주겠습니다. MessageList 컴포넌트 안에서만 동작하면 되는 코드를 더 상위 스코프인 ChatRoom에서 사용할 필요가 없기 때문입니다. 이로써 ChatRoom은 nickname props를 받아 별다른 로직없이 바로 jsx를 return하는 컴포넌트가 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* ChatRoom&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1639395887814&quot; class=&quot;javascript&quot; style=&quot;margin: 20px auto 0px; display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; cursor: default; z-index: 1;&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function ChatRoom({ nickname }) {
  return (
    &amp;lt;div
      className=&quot;d-flex flex-column&quot;
      style={{ width: 1000 }}
    &amp;gt;
      &amp;lt;div className=&quot;text-box&quot;&amp;gt;
        &amp;lt;span&amp;gt;{nickname}&amp;lt;/span&amp;gt; 님 환영합니다!
      &amp;lt;/div&amp;gt;
      &amp;lt;MessageList /&amp;gt;
      &amp;lt;MessageForm nickname={nickname} /&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 MessageList가 로직을 가져와 다음과 같이 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* MessageList&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1639395887814&quot; class=&quot;javascript&quot; style=&quot;margin: 20px auto 0px; display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; cursor: default; z-index: 1;&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useState, useCallback, useEffect, useContext, useRef } from &quot;react&quot;;

import MessageItem from &quot;src/components/chatRoom/MessageItem&quot;;
import { SocketContext, SOCKET_EVENT, makeMessage } from &quot;src/service/socket&quot;;

function MessageList() {
  const [messages, setMessages] = useState([]);
  const chatWindow = useRef(null);
  const socket = useContext(SocketContext);

  const moveScrollToReceiveMessage = useCallback(() =&amp;gt; {
    if (chatWindow.current) {
      chatWindow.current.scrollTo({
        top: chatWindow.current.scrollHeight,
        behavior: &quot;smooth&quot;,
      });
    }
  }, []);

  const handleReceiveMessage = useCallback(pongData =&amp;gt; {
      const newMessage = makeMessage(pongData);
      setMessages(messages =&amp;gt; [...messages, newMessage]);
      moveScrollToReceiveMessage();
    },
    [moveScrollToReceiveMessage]
  );

  useEffect(() =&amp;gt; {
    socket.on(SOCKET_EVENT.RECEIVE_MESSAGE, handleReceiveMessage);

    return () =&amp;gt; {
      socket.off(SOCKET_EVENT.RECEIVE_MESSAGE, handleReceiveMessage);
    };
  }, [socket, handleReceiveMessage]);

  return (
    &amp;lt;div className=&quot;chat-window card&quot; ref={chatWindow}&amp;gt;
      {messages.map((message, index) =&amp;gt; {
        return &amp;lt;MessageItem key={index} message={message} /&amp;gt;;
      })}
    &amp;lt;/div&amp;gt;
  );
}

export default MessageList;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* MessageItem&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1639396209660&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import React from &quot;react&quot;;

function MessageItem({ message }) {
  const { nickname, content, time } = message;

  return (
    &amp;lt;div className=&quot;d-flex flex-row&quot;&amp;gt;
      {nickname &amp;amp;&amp;amp; &amp;lt;div className=&quot;message-nickname&quot;&amp;gt;{nickname}: &amp;lt;/div&amp;gt;}
      &amp;lt;div&amp;gt;{content}&amp;lt;/div&amp;gt;
      &amp;lt;div className=&quot;time&quot;&amp;gt;{time}&amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}

export default React.memo(MessageItem);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MessageItem은 추가적으로 React.memo()로 감싸주어 새 메시지가 추가되었을 때, 이미 화면에 출력되어있는 MessageItem들의 리렌더링을 막고, 추가된 새 아이템만 리렌더링하도록 최적화시켰습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴포넌트의 단위를 잘 쪼개면 이렇게 컴포넌트 단위별 최적화 코드 작성도 편리해집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. UX 개선&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;input이나 textarea가 enter key를 받았을 때 그대로 submit을 해줄 수 있으면 굳이 양손으로 타자를 치다가 오른손을 마우스로 옮겨서 버튼을 클릭하는 불편한 행동을 유저 입장에서 하지 않아도 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;input과 textarea에 enter key를 처리하는 로직을 추가해보려고 합니다 방법은 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;onKeyPress event를 추가해주면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1639395887814&quot; class=&quot;javascript&quot; style=&quot;margin: 20px auto 0px; display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; cursor: default; z-index: 1;&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;input
  value={typingMessage}
  onChange={handleChangeTypingMessage}
  onKeyPress={event =&amp;gt; {
   if (event.code === &quot;Enter&quot;) {
      event.preventDefault();
      handleSendMesssage();
    }
  }}
/&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;소스코드&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작업 결과는 github 레포지토리에서 확인할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;frontend:&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://github.com/cocoder16/toy-web-socket-front&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/cocoder16/toy-web-socket-front&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;backend:&amp;nbsp;&lt;a href=&quot;https://github.com/cocoder16/toy-web-socket-back&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/cocoder16/toy-web-socket-back&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>React.js</category>
      <category>react</category>
      <category>socket.io</category>
      <category>리팩터링</category>
      <category>메신저 만들기</category>
      <category>웹소켓</category>
      <category>채팅 기능 구현</category>
      <category>채팅 웹 만들기</category>
      <category>채팅앱 만들기</category>
      <category>컴포넌트</category>
      <author>cocoder16</author>
      <guid isPermaLink="true">https://cocoder16.tistory.com/62</guid>
      <comments>https://cocoder16.tistory.com/62#entry62comment</comments>
      <pubDate>Mon, 10 Jan 2022 08:02:31 +0900</pubDate>
    </item>
    <item>
      <title>[채팅 웹사이트 구현 - 2장] Node.js에서 Socket.io를 이용하여 소켓 서버 만들기</title>
      <link>https://cocoder16.tistory.com/59</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전 장. &lt;a href=&quot;https://cocoder16.tistory.com/58&quot; rel=&quot;noopener&quot;&gt;[채팅 웹사이트 구현 - 1장] WebSocket과 Socket.io에 대해 알고 시작해볼까?&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;express로 node.js 서버 열기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트를 시작합니다. 터미널을 열고 원하는 디렉터리에서 다음 명령어를 실행합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1639240364508&quot; class=&quot;javascript&quot; style=&quot;margin: 20px auto 0px; display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; cursor: default; z-index: 1;&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm init -y&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로 express를 설치해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1639240364508&quot; class=&quot;javascript&quot; style=&quot;margin: 20px auto 0px; display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; cursor: default; z-index: 1;&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;yarn add express&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 프로젝트 폴더에 server.js를 생성하고 다음과 같이 코드를 작성합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* server.js&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1639240364508&quot; class=&quot;javascript&quot; style=&quot;margin: 20px auto 0px; display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; cursor: default; z-index: 1;&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const express = require(&quot;express&quot;);
const app = express();
const server = require(&quot;http&quot;).createServer(app);

const port = 4000;

server.listen(port, () =&amp;gt; {
  console.log(
    `##### server is running on http://localhost:4000. ${new Date().toLocaleString()} #####`
  );
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;express는 node.js로 서버를 쉽게 만들도록 도와주는 프레임워크입니다. 저렇게 간단하게 require로 express모듈을 불러와 express()로 실행해주면 서버를 만들 수 있습니다. 저는 4000번 포트를 열어 Node.js 서버를 할당해주었습니다. server.listen()에서 첫 번째 파라미터는 포트번호, 두 번째 파라미터는 서버가 열린 후 실행될 콜백 함수를 받습니다. 저는 서버가 열렸음을 알리는 콘솔 로그 출력 함수를 콜백 함수 내에 작성했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 서버를 여는 스크립트 명령어를 만듭니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* package.json&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1639240364508&quot; class=&quot;javascript&quot; style=&quot;margin: 20px auto 0px; display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; cursor: default; z-index: 1;&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;scripts&quot;: {
    &quot;start&quot;: &quot;node server.js&quot;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;터미널에 다음 명령어를 입력하여 서버를 실행해봅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1639240364508&quot; class=&quot;javascript&quot; style=&quot;margin: 20px auto 0px; display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; cursor: default; z-index: 1;&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;yarn start&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버가 정상적으로 실행되면 server.js에서 만든 다음과 같은 텍스트가 터미널에 출력될 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1639241084012&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;##### server is running on http://localhost:4000. {현재 시각} #####&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;nodemon으로 개발환경 개선하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금 상태에서는 코드의 변경내용을 서버에 반영하려면 서버를 다시 껐다 키는 작업을 손수 해줘야 합니다. 당연히 개발 피로도가 증가합니다. 하지만 nodemon을 사용하면 소스코드가 변경될 때마다 자동으로 서버를 재시작할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1639240364509&quot; class=&quot;javascript&quot; style=&quot;margin: 20px auto 0px; display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; cursor: default; z-index: 1;&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;yarn add nodemon&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;nodemon을 설치했으면 스크립트를 수정해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* package.josn&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1639240364509&quot; class=&quot;javascript&quot; style=&quot;margin: 20px auto 0px; display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; cursor: default; z-index: 1;&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&quot;scripts&quot;: {
  &quot;start&quot;: &quot;nodemon --exec node server.js&quot;
},&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버를 종료 후 다시 yarn start로 서버를 켜줍니다. 이제부터는 코드를 변경할 때마다 (파일이 저장되어 파일 체인지가 되었을 때) 서버가 자동으로 재시작될 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;socket.io 설치하고 소켓 서버 열기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;socket.io와 cors를 설치합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1639240364509&quot; class=&quot;javascript&quot; style=&quot;margin: 20px auto 0px; display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; cursor: default; z-index: 1;&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;yarn add socket.io cors&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;socket.io 라이브러리는 1장에서 소개한 대로 양방향 통신을 가능하게 만들어주는 실시간 채팅 기능을 구현하기 위해 필요한 핵심 라이브러리입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;cors는 우리가 만들 프론트엔드 URL에 대해서만 통신을 허락하고 나머지는 거부하기 위해서 필요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 server.js를 수정합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* server.js&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1639240364509&quot; class=&quot;javascript&quot; style=&quot;margin: 20px auto 0px; display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; cursor: default; z-index: 1;&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const express = require(&quot;express&quot;);
const app = express();
const server = require(&quot;http&quot;).createServer(app);
const cors = require(&quot;cors&quot;);
const socketIo = require(&quot;socket.io&quot;)(server, {
  cors: {
    origin: &quot;http://localhost:3000&quot;,
    credentials: true,
  },
});
const socket = require(&quot;./src/socket&quot;); // 이 다음으로 바로 만들 파일!

const port = 4000;

// express의 미들웨어 사용 방식!
app.use(cors({ origin: &quot;http://localhost:3000&quot;, credentials: true })); // cors 미들웨어 사용
socket(socketIo); // 이제 곧 만들 파일에서 정의할 모듈에 socketIo 객체를 전달해줄 것입니다

server.listen(port, () =&amp;gt; {
  console.log(
    `##### server is running on http://localhost:4000. ${new Date().toLocaleString()} #####`
  );
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드를 위에서부터 보면 express로 만든 서버에 socket을 열어줬고 cors로 localhost:3000 url만 통신을 허용하도록 설정했습니다. src/socket은 아직 만들진 않았지만 이제 바로 만들 것이기 때문에 미리 작성해뒀습니다. src/socket.js 파일에서 소켓의 이벤트에 따른 로직들을 작성할 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;socket event 정의하고 event에 대한 로직 작성하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 프로젝트 디렉터리에 src/socket/index.js 파일을 생성합니다. 제일 처음 작성할 이벤트 로직은 connection과 disconnection입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* src/socket/index.js&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1639393805771&quot; class=&quot;javascript&quot; style=&quot;margin: 20px auto 0px; display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; cursor: default; z-index: 1;&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;module.exports = function (socketIo) {
  socketIo.on(&quot;connection&quot;, function (socket) {
    // 클라이언트와 연결이 되면 연결된 사실을 출력합니다.
    console.log(&quot;socket connection succeeded.&quot;);
    
    socket.on(&quot;disconnect&quot;, reason =&amp;gt; {
      // 클라이언트와 연결이 끊어지면 이유를 출력해줍니다.
      console.log(`disconnect: ${reason}`);
    }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;jquery의 이벤트 리스너와 이벤트 핸들러 작성 방식과 비슷합니다. on() 메서드의 첫 파라미터에서 이벤트 타입을 적어주고 두 번째 파라미터에서 콜백 함수인 이벤트 핸들러를 적어줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;connection 이벤트는 클라이언트와 소켓 서버가 서로 연결되었을 때 발생합니다. 이때 연결되었음을 알리는 텍스트를 소켓 서버 터미널에 출력해줍니다.&amp;nbsp; disconnect는 연결이 끊겼을 때 발생합니다. 마찬가지로 연결이 끊겼음을 로그로 출력해줍니다. 이 이벤트는 연결이 끊어진 이유를 콜백 함수의 파라미터로 받을 수 있어 같이 출력해줬습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;connection과 disconnect는 socket.io에서 기본적으로 지원해주는 이벤트 타입이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면, 이제부터는 저희가 마음대로 몇 가지 이벤트를 더 정의하고 그에 대한 로직을 작성하여 채팅 애플리케이션으로서 기능할 수 있도록 본격적인 작업을 할 것입니다. 앞으로 정의할 이벤트는 다음과 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1639393805772&quot; class=&quot;javascript&quot; style=&quot;margin: 20px auto 0px; display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; cursor: default; z-index: 1;&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&quot;JOIN_ROOM&quot;: 유저가 방에 참가했을 때 발생
&quot;UPDATE_NICKNAME&quot;: 유저가 닉네임을 변경했을 때 발생
&quot;SEND_MESSAGE&quot;: 유저가 메시지를 전송했을 때 발생
&quot;RECEIVE_MESSAGE&quot;: 유저가 메시지를 받을 때 발생&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;총 4가지 이벤트를 정의할 것이고 RECEIVE_MESSAGE를 제외한 모든 이벤트들에 대해 각각 이벤트 리스너를 선언하여 로직을 작성할 것입니다. RECEIVE_MESSAGE는 클라이언트에게 보내는(emit) 용도로만 사용될 것입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 &quot;JOIN_ROOM&quot;부터 시작합니다. JOIN_ROOM은 유저가 채팅방에 참가했을 때 발생하는 이벤트입니다. 아까 만들었던 socketIo.on() 내의 콜백 함수에서 코드를 추가할 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1639393805772&quot; class=&quot;javascript&quot; style=&quot;margin: 20px auto 0px; display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; cursor: default; z-index: 1;&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;socketIo.on(&quot;connection&quot;, function (socket) {
  // ...
    
  // 구현 편의상, 모든 클라이언트의 방 번호는 모두 &quot;room 1&quot;으로 배정해줍니다.
  const roomName = &quot;room 1&quot;;

  // JOIN 이벤트 기능 추가
  socket.on(&quot;JOIN_ROOM&quot;, requestData =&amp;gt; {
    // 콜백함수의 파라미터는 클라이언트에서 보내주는 데이터입니다.
    socket.join(roomName); // user를 &quot;room 1&quot; 방에 참가시킵니다.
    const responseData = {
      ...requestData,
      type: &quot;JOIN_ROOM&quot;,
      time: new Date(),
    };
    // &quot;room 1&quot;에는 이벤트타입과 서버에서 받은 시각을 덧붙여 데이터를 그대로 전송해줍니다.
    socketIo.to(roomName).emit(&quot;RECEIVE_MESSAGE&quot;, responseData);
    console.log(`JOIN_ROOM is fired with data: ${JSON.stringify(responseData)}`);
  });
    
  // ...
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트에서 JOIN_ROOM이라는 이벤트 타입으로 데이터를 소켓 서버에 던져주면 소켓 서버는 데이터를 받아 콜백 함수를 실행합니다. 콜백 함수 안에서 RECEIVE_MESSAGE 이벤트를 emit하는 코드가 있는데 이것은 클라이언트에 이벤트를 전달하는 것입니다. 마찬가지로 클라이언트에서는 RECEIVE_MESSAGE 이벤트 리스너를 가지고 있어서 그쪽 콜백 함수가 또 실행될 것입니다. 클라이언트 구현은 서버 구현을 마친 후에 진행할 것입니다. 일단은 서버 구현을 완성해보도록 합니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로 유저가 닉네임을 변경하는 것을 의미하는 UPDATE_NICKNAME 이벤트에 대한 기능을 만들어봅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JOIN_ROOM 이벤트 리스너에 대한 코드와 같은 스코프에 작성하면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* src/socket/index.js&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1639393805773&quot; class=&quot;javascript&quot; style=&quot;margin: 20px auto 0px; display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; cursor: default; z-index: 1;&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;socketIo.on(&quot;connection&quot;, function (socket) {
  // ...
    
  socket.on(&quot;UPDATE_NICKNAME&quot;, requestData =&amp;gt; {
      const responseData = {
        ...requestData,
        type: &quot;UPDATE_NICKNAME&quot;,
        time: new Date(),
      };
      socketIo.to(roomName).emit(&quot;RECEIVE_MESSAGE&quot;, responseData);
      console.log(`UPDATE_NICKNAME is fired with data: ${JSON.stringify(responseData)}`);
  });
  
  // ...
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바로 이어서 유저가 채팅 메시지를 송신했음을 의미하는 SEND_MESSAGE 이벤트에 대한 기능도 구현해봅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1639393805773&quot; class=&quot;javascript&quot; style=&quot;margin: 20px auto 0px; display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; cursor: default; z-index: 1;&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;socket.on(&quot;SEND_MESSAGE&quot;, requestData =&amp;gt; {
  const responseData = {
    ...requestData,
    type: &quot;SEND_MESSAGE&quot;,
    time: new Date(),
  };
  socketIo.to(roomName).emit(&quot;RECEIVE_MESSAGE&quot;, responseData);
  console.log(`SEND_MESSAGE is fired with data: ${JSON.stringify(responseData)}`);
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 해서 저희가 정의한 이벤트 타입들에 대한 로직 작성도 끝이 났습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div class=&quot;revenue_unit_item adsense  responsive&quot;&gt;&lt;ins class=&quot;adsbygoogle&quot; style=&quot;display: block;&quot; data-ad-host=&quot;ca-host-pub-9691043933427338&quot; data-ad-client=&quot;ca-pub-9807016842906892&quot; data-ad-format=&quot;auto&quot;&gt;&lt;/ins&gt;
&lt;script&gt;(adsbygoogle = window.adsbygoogle || []).push({}); &lt;/script&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마무리 - 리팩터링&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 지금은 중복 코드도 많고 하드코딩되어 있는 부분들이 많아서 아쉬움이 남습니다. 리팩터링으로 아쉬운 부분들을 조금이라도 개선하고 소켓 서버 백엔드 개발은 끝내보려고 합니다. 기능이 완성된 지금 해볼 만한 가치가 있는 일을 몇 가지 제시해보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 환경변수를 따로 관리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선, 환경변수를 따로 관리하는 것입니다. server.js를 보면 포트번호와 url이 코드에 그대로 명시되어 있습니다. 지금은 로컬 호스트를 사용하지만 나중에 운영서버로 넘어가게 되면 보안상 좋지 않을 뿐더러 저런 값은 전역에서 사용하는 &quot;상수&quot;이기 때문에 전역 변수에 따로 담아 관리하는 것이 좋습니다. 또한, 앱 전역에서 같은 변수명으로 접근할 수 있기 때문에 추후 값 변경을 하게 되더라도 수정이 용이해진다는 이점도 취할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;env-cmd라는 라이브러리를 설치하면 환경변수 관리를 용이하게 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1639393864333&quot; class=&quot;javascript&quot; style=&quot;margin: 20px auto 0px; display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; cursor: default; z-index: 1;&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;yarn add env-cmd&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버 실행 전에 환경변수 파일을 사용할 수 있도록 스크립트를 수정해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* package.json&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1639393864334&quot; class=&quot;javascript&quot; style=&quot;margin: 20px auto 0px; display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; cursor: default; z-index: 1;&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&quot;scripts&quot;: {
  &quot;start&quot;: &quot;env-cmd -f .env.development nodemon --exec node server.js&quot;
},&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트 루트 디렉터리에서는 .env.development 파일을 생성합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* .env.development&lt;/p&gt;
&lt;pre id=&quot;code_1639393864335&quot; class=&quot;html xml&quot; style=&quot;margin: 20px auto 0px; display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; cursor: default; z-index: 1;&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;FRONT_URL=http://localhost:3000
BACK_URL=http://localhost:4000
PORT=4000&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 코드에서는 process.env 객체로 환경변수들에 접근할 수 있습니다. process.env.FRONT_URL, process.env.BACK_URL, process.env.PORT와 같이 사용하면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 .gitignore에서 .env.development를 추가해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나중에 개발서버와 테스트 서버, 프로덕션 서버에 대해 각각 환경변수를 따로따로 만들어줄 수도 있습니다. 자세한 것은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://www.npmjs.com/package/env-cmd&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;라이브러리 공식 문서&lt;/a&gt;를 참조하면 좋습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 이벤트 타입 정의 분리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이벤트 타입은 스트링 타입의 상수값이고 여러 곳에서 사용됩니다. 또한 이벤트 타입은 재할당이 되지도 않습니다. 이런 특성들을 가진 이벤트 타입은 변수에 할당해주면 재사용성을 가지게 되고, 값 수정 및 관리하기 편해집니다. src/socket/index.js에서 소켓 로직을 작성했던 module.export 위에 SOCKET_EVENT라는 변수로 선언하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* src/socket/index.js&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1639393864336&quot; class=&quot;javascript&quot; style=&quot;margin: 20px auto 0px; display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; cursor: default; z-index: 1;&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// ...

const SOCKET_EVENT = {
  JOIN: &quot;JOIN&quot;,
  UPDATE_NICKNAME: &quot;UPDATE_NICKNAME&quot;,
  SEND: &quot;SEND&quot;,
  RECEIVE: &quot;RECEIVE&quot;,
};

module.exports = function (socketIo) {
// ...&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이벤트 리스너 코드는 다음과 같이 리팩터링할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1639393864336&quot; class=&quot;javascript&quot; style=&quot;margin: 20px auto 0px; display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; cursor: default; z-index: 1;&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;socket.on(SOCKET_EVENT.SEND_MESSAGE, requestData =&amp;gt; {
  const responseData = {
    ...requestData,
    type: SOCKET_EVENT.SEND_MESSAGE,
    time: new Date(),
  };
  socketIo.to(roomName).emit(SOCKET_EVENT.RECEIVE_MESSAGE, responseData);
  console.log(`${SOCKET_EVENT.SEND_MESSAGE} is fired with data: ${JSON.stringify(responseData)}`);
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 중복 코드 제거 - 함수로 분리하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로직들을 보면 중복해서 사용되는 패턴들이 있습니다. 지금 만든 소켓 이벤트 콜백 함수를 보면 그렇습니다. 우리가 따로 정의한 소켓 이벤트 콜백 함수들은 특정한 흐름을 가지고 있는데 &lt;b&gt;&quot;방에 처음 참가한 유저는 room 1에 할당해주기 -&amp;gt; 이벤트 타입과 함께 받은 데이터를 가공하여 응답해줄 데이터를 만들기 -&amp;gt; 클라이언트에 RECEIVE 이벤트 emit하기 -&amp;gt; 클라이언트에 응답해준 이벤트와 데이터를 로그에 출력하기&quot;&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 같은 흐름을 가진 콜백 함수들은 패턴의 중복성을 가지게 됩니다. 이러한 중복을 제거하는 것은 코드를 더 관리하기 쉽게 만듭니다. &quot;방에 처음 참가한 유저인 경우 room 1에 할당해주기&quot; 단계는 JOIN_ROOM 이벤트일 때에만 발생할 것이지만, 나머지 단계들은 모든 이벤트 타입이 거쳐야 할 단계입니다. 그것을 고려하여 콜백 함수를 하나로 통합해봅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1639393864337&quot; class=&quot;javascript&quot; style=&quot;margin: 20px auto 0px; display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; cursor: default; z-index: 1;&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;socket.on(type, requestData =&amp;gt; {
  const firstVisit = type === SOCKET_EVENT.JOIN_ROOM;

  if (firstVisit) {
    socket.join(roomName);
  }

  const responseData = {
    ...requestData,
    type,
    time: new Date(),
  };
  socketIo.to(roomName).emit(SOCKET_EVENT.RECEIVE_MESSAGE, responseData);
  console.log(`${type} is fired with data: ${JSON.stringify(responseData)}`);
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이벤트 리스너 코드는 위와 같이 하나의 코드로 만들 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제, 위에서 이벤트 타입들을 정의한 객체 SOCKET_EVENT를 이터러블로 만들어 이벤트 리스너들을 루프로 한 번에 설치할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1639393864337&quot; class=&quot;javascript&quot; style=&quot;margin: 20px auto 0px; display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; cursor: default; z-index: 1;&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Object.keys(SOCKET_EVENT).forEach(typeKey =&amp;gt; {
  const type = SOCKET_EVENT[typeKey];

  socket.on(type, requestData =&amp;gt; {
    const firstVisit = type === SOCKET_EVENT.JOIN_ROOM;

    if (firstVisit) {
      socket.join(roomName);
    }

    const responseData = {
      ...requestData,
      type,
      time: new Date(),
    };
    socketIo.to(roomName).emit(SOCKET_EVENT.RECEIVE_MESSAGE, responseData);
    console.log(`${type} is fired with data: ${JSON.stringify(responseData)}`);
  }); 
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 해서 소켓 서버 백엔드 개발은 모두 완료되었습니다. 이제 클라이언트 쪽인 프론트엔드 개발로 넘어가보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 장. &lt;a href=&quot;https://cocoder16.tistory.com/62&quot;&gt;[채팅&amp;nbsp;웹사이트&amp;nbsp;구현&amp;nbsp;-&amp;nbsp;3장] React로 채팅 웹 만들기&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Node.js</category>
      <category>CORS</category>
      <category>Express</category>
      <category>Node.js</category>
      <category>socket.io</category>
      <category>리팩터링</category>
      <category>소켓</category>
      <category>웹소켓</category>
      <category>채팅 서버 만들기</category>
      <category>채팅 웹 만들기</category>
      <author>cocoder16</author>
      <guid isPermaLink="true">https://cocoder16.tistory.com/59</guid>
      <comments>https://cocoder16.tistory.com/59#entry59comment</comments>
      <pubDate>Mon, 10 Jan 2022 08:01:41 +0900</pubDate>
    </item>
    <item>
      <title>[채팅 웹사이트 구현 - 1장] WebSocket과 Socket.io에 대해 알고 시작해볼까?</title>
      <link>https://cocoder16.tistory.com/58</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Socket.io로 채팅 웹사이트 구현 들어가기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Socket.io를 이용해 실시간 통신이 가능한 소켓 서버와 이것과 연결된 클라이언트를 개발하여 채팅 웹사이트를 만드는 프로젝트를 여러 편의 포스팅에 걸쳐서 진행하려고 합니다. 먼저 Node.js에서 동작하는 소켓 서버를 만들 것이고, 그다음에는 React.js를 이용해 클라이언트를 만들 것입니다. 이 둘은 서로 실시간 통신이 가능할 것이고, 결과적으로 그럴싸한 그렇지만 정말 간단한 채팅 웹사이트가 탄생할 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1113&quot; data-origin-height=&quot;504&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bYq2of/btrnBtfTsRF/ZbKivdkKwjgJbpsA5u3kJ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bYq2of/btrnBtfTsRF/ZbKivdkKwjgJbpsA5u3kJ1/img.png&quot; data-alt=&quot;구현할 채팅 웹사이트 서버&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bYq2of/btrnBtfTsRF/ZbKivdkKwjgJbpsA5u3kJ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbYq2of%2FbtrnBtfTsRF%2FZbKivdkKwjgJbpsA5u3kJ1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;516&quot; height=&quot;234&quot; data-origin-width=&quot;1113&quot; data-origin-height=&quot;504&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;구현할 채팅 웹사이트 서버&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;WebSocket과 Socket.io의 차이점&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WebSocket은 양방향 메시지 송수신을 가능하게 만들기 위해 나온 HTML5 표준 기술입니다. 표준 기술이기 때문에 추가적인 라이브러리 설치 없이 사용할 수 있으나, 오래된 버전의 웹 브라우저는 이 기술을 지원하지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Socket.io는 WebSocket기술을 활용하는 라이브러리입니다. WebSocket뿐만 아니라 FalshSocket, AJAX Long Polling, AJAX Multi part Streaming, IFrame, JSONP Polling 등의 실시간 웹 기술들을 활용하여 브라우저의 종류에 상관없이 실시간 양방향 통신을 지원합니다. 라이브러리기 때문에 사용을 위해 추가 설치가 필요합니다. 또한 WebSocket보다 코드를 직관적이고 편리하게 짤 수 있으며 room이라는 기능이 있어 room별로 송신을 보낼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://socket.io/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Socket.io 공식문서&lt;/a&gt;에서 사용법과 API에 대해 찾아볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;WebSocket Handshake 원리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WebSocket의 handshake 과정은 HTTP로 이루어집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저, 클라이언트가 서버에 HTTP로 핸드셰이크 요청을 보냅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때, 잘못된 값을 가지거나 형식이 잘못된 헤더를 가진 요청을 보낸 경우, 서버는 400 Bad Request 응답을 보내 소켓을 종료시켜야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;올바른 요청을 보냈다면, 서버는 클라이언트에게 HTTP로 핸드셰이크 응답을 보냅니다.&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트가 응답을 받으면 핸드셰이크는 성공적으로 끝난 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제부터는 클라이언트와 서버가 서로 실시간으로 웹소켓 프로토콜을 통해 양방향 통신을 할 수 있게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 Handshake 원리는 &lt;a href=&quot;https://developer.mozilla.org/ko/docs/Web/API/WebSockets_API/Writing_WebSocket_servers&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;MDN Web Docs 웹소켓 서버 작성하기&lt;/a&gt;를 참조하여 요약했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 장. &lt;a href=&quot;https://cocoder16.tistory.com/59&quot; rel=&quot;noopener&quot;&gt;[채팅&amp;nbsp;웹사이트&amp;nbsp;구현&amp;nbsp;-&amp;nbsp;2장]&amp;nbsp;Node.js에서&amp;nbsp;Socket.io를&amp;nbsp;이용하여&amp;nbsp;소켓&amp;nbsp;서버&amp;nbsp;만들기&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Node.js</category>
      <category>Handshake</category>
      <category>Node.js</category>
      <category>react</category>
      <category>socket.io</category>
      <category>소켓</category>
      <category>웹소켓</category>
      <category>채팅 앱 만들기</category>
      <category>채팅 웹 만들기</category>
      <author>cocoder16</author>
      <guid isPermaLink="true">https://cocoder16.tistory.com/58</guid>
      <comments>https://cocoder16.tistory.com/58#entry58comment</comments>
      <pubDate>Mon, 10 Jan 2022 08:00:53 +0900</pubDate>
    </item>
    <item>
      <title>[웹 프론트엔드] e2e test 프레임워크인 cypress를 소개합니다</title>
      <link>https://cocoder16.tistory.com/66</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;이 포스팅 작성 시점 기준 cypress 버전은 v9.1.0 입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Cypress가 기존 test 프레임워크와 다른 점&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;e2e test(end to end test)&lt;/b&gt;는 작은 단위의 유닛 테스트가 아닌 사용자 시나리오를 통째로 실행하여 정상적으로 기능하는지를 확인하는 기능 테스트입니다. cypress는 프론트엔드 e2e test framework이며 기존 프론트엔드 테스트 프레임워크들과는 다른 차별점을 가집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저, cypress는 실제 돔에서 테스트합니다. jest를 이용한 react testing library와 같은 것도 실제 돔에서 테스트합니다만, cypress는 node위에 동작하는 서버를 띄우고 실제 브라우저까지 열어서 브라우저 위에서 테스트를 진행하고 그 위에서 time traveling, debugging, record 등을 할 수 있도록 기능을 지원합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;cypress에서 브레이크 포인트를 따로 찍을 수도 있지만, time traveling은 그 자체로도 강력한 디버깅 support입니다. 개발자는 기능 테스트의 실패를 봤을 때, 어떤 단계에서 어떻게 잘못된 것인지 GUI를 통해 세부적으로 들여다볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1911&quot; data-origin-height=&quot;495&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bcaot6/btrnVwJVHuu/eBBxhv78sfYMJnNPiwcrF0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bcaot6/btrnVwJVHuu/eBBxhv78sfYMJnNPiwcrF0/img.png&quot; data-alt=&quot;test에 실패하면 TEST BODY에서 각 단계를 클릭하여 time traveling 및 개발자 도구를 통한 디버깅을 할 수 있다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bcaot6/btrnVwJVHuu/eBBxhv78sfYMJnNPiwcrF0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbcaot6%2FbtrnVwJVHuu%2FeBBxhv78sfYMJnNPiwcrF0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1911&quot; height=&quot;495&quot; data-origin-width=&quot;1911&quot; data-origin-height=&quot;495&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;test에 실패하면 TEST BODY에서 각 단계를 클릭하여 time traveling 및 개발자 도구를 통한 디버깅을 할 수 있다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 GUI는 크롬 브라우저를 제공하기에 크롬 개발자도구를 그대로 사용하여 디버깅을 할 수 있습니다. 타임 트라블링을 할 때에는 각 단계에 대해 클릭을 하면 개발자 도구 콘솔에 그 단계에 해당하는 데이터들을 출력해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 유저 입장에서 테스트를 하기 때문에 아무리 유효한 실제 DOM이 있다고 해도 사용자가 행동할 수 없는 시나리오에 대해서는 테스트가 실패합니다. 예를 들어 checkbox input이 하나 있고 그것을 click하는 시나리오가 테스트 안에 있는 경우를 가정하겠습니다. 해당 input 엘리먼트가 존재는 하지만, css style 문제로 인해 유저 입장에서는 접근할 수 없는 위치에 있는 경우, 해당 click 이벤트는 실패하게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;816&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/o5IMi/btrn0W1FfxC/AadZAfTNWTiPEc6qiWXKz0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/o5IMi/btrn0W1FfxC/AadZAfTNWTiPEc6qiWXKz0/img.png&quot; data-alt=&quot;input 엘리먼트가 존재하지만, 유저가 접근할 수 없는 위치에 있으면 click 이벤트에 실패한다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/o5IMi/btrn0W1FfxC/AadZAfTNWTiPEc6qiWXKz0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fo5IMi%2Fbtrn0W1FfxC%2FAadZAfTNWTiPEc6qiWXKz0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1920&quot; height=&quot;816&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;816&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;input 엘리먼트가 존재하지만, 유저가 접근할 수 없는 위치에 있으면 click 이벤트에 실패한다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것은 철저하게 개발자 입장이 아닌 유저 입장에서 테스트에 성공해야한다는 철학이 반영된 것입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 cypress는 비개발자와의 좋은 협업 경험을 제공하기 위해 스크린샷과 비디오 기록(record) 기능을 제공합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Cypress가 의존하는 모듈들&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;cypress는 내부적으로 mocha, chai, sinon, jQuery 등을 번들해서 사용하고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;cypress 테스트 코드는 기본적으로 mocha 프레임워크 위에서 작성됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;assertions 문법은 chai의 것을 사용하고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;cy.stub()과 cy.spy() 함수의 경우는 sinon.js의 stub과 spy를 사용하고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DOM과 관련된 API는 jQuery의 것을 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Cypress로 e2e 테스트 하는 방식&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;cypress의 e2e 테스트는 BDD에 기반하여 다음과 같은 절차로 이루어집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1. start server&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 실제 서버가 열려있어야 합니다. 개발 서버가 열려있는 상태에서 cypress가 개발서버에 방문하여 브라우저를 띄우고 그 위에서 테스트를 하는 방식입니다. 만약 localhost:3000으로 프론트엔드 개발서버를 운영 중이라면, cypress test run 전에 3000번 서버를 먼저 열어야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2. visit your server&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;cy.visit()는 브라우저 위에서 해당 주소에 방문하는 메서드입니다. 테스트 각 블록간의 의존성을 제거하기 위해 보통 beforeEach() 안에서 사용할 일이 많을 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1639553925919&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;describe(&quot;example&quot;, () =&amp;gt; {
  beforeEach(() =&amp;gt; {
    cy.visit(&quot;http://localhost:3000);
  });
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3. user behavior&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;브라우저 위에서 유저가 행할 수 있는 행동들을 테스트 코드로 작성합니다. 유저는 UI를 통해 행동하므로 보통은 DOM query로 엘리먼트를 먼저 찾고, 그 엘리먼트에 이벤트를 가하는 형태로 액션을 취합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 checkbox input을 찾아서 click을 하는 액션은 다음과 같은 코드로 쓸 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1639554035849&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;cy.get(&quot;input[type='checkbox']&quot;).click();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;4. assertion&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유저가 행동을 취한 후 기대되는 단언들을 확인합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;checkbox input을 click한 이후에 기대되는 단언은 input이 checked상태로 변하는 것입니다. DOM query이후 단언은 체이닝 메서드인 should를 이용하여 다음과 같이 확인할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1639554158485&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;cy.get(&quot;input[type='checkbox']&quot;).should(&quot;be.checked&quot;);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 액션에서 했던 DOM query와 중복되는 코드가 있습니다. 중복은 alias를 통해 제거할 수 있습니다. as 체이닝 메서드는 alias기능을 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1639554706226&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;cy.get(&quot;input[type='checkbox']&quot;).as(&quot;checkbox&quot;).click();
cy.get(&quot;@checkbox&quot;).should(&quot;be.checked&quot;);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BDD는 특성상 테스트 코드 한 블록 내의 코드가 긴 편입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;cypress는 element나 여러 변수들을 alias로 사용할 수 있어서, 재사용의 편리성을 제공해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;api server와 통신하는 앱의 테스트 방법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;cypress 공식문서는 두가지 방향을 제시하고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 서버를 stub하지 않고 서버와 함께 진정한 의미의 e2e test를 하는 방향&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 서버를 stub하여 서버와의 의존성이 없는 프론트엔드만의 e2e test를 하는 방향&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1번의 경우 stub을 사용하지 않고 실제 백엔드 API 서버와 통신한 결과를 가지고 테스트를 수행합니다. 따라서 백엔드에 대해 의존성을 가지며, response에 대한 단언을 추가하면 백엔드에서 개발된 API까지 테스트가 가능해집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2번의 경우는 stub에 관한 코드를 작성해줘야합니다. request가 발생했을 때 그것을 감지하는 코드와 그 후, stub response data를 뿌려주는 코드가 필요합니다. 이것은 cy.intercept() 메서드로 구현할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1639555488318&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;cy.intercept(&quot;GET&quot;, &quot;/toDoList&quot;, request =&amp;gt; {
  request.reply({
    // request.reply()는 응답데이터를 stub한다
    fixture: &quot;api/responseData/toDoList/get.json&quot;,
  });
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 코드가 있는 테스트는 /toDoList 라는 엔드포인트로 get 요청이 발생하면 stub으로 미리 작성한 응답을 보내줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;cypress는 seed data만 따로 fixture라는 폴더에 관리할 수 있는데, fixture 폴더에서 관리하는 데이터는 위 코드처럼 편리한 접근성을 가지게 됩니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;cy.* 메서드의 주요 특징&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;cypress는 가상 돔이 아닌 실제 돔 위에서 테스트를 수행합니다. 여러 브라우저의 종류와 여러 버전에서 테스트를 할 수 있기 때문에, 돔 쿼리는 jQuery의 DOM traversal method들을 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DOM query 이후 받은 element와 interact 하는 것은 체이닝 메서드들을 사용해서 작성할 수 있습니다. 다음과 같은 체이닝이 가능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1639556432381&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;cy.get(&quot;input&quot;).type(&quot;Hello!&quot;);
cy.get(&quot;input&quot;).click();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;cypress가 jQuery를 번들했지만, jQuery와 다른 점은 DOM 쿼리를 성공할 때까지 retry 하다가 성공하면 다음 체이닝을 진행한다는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;cypress 메서드들이 retry하는 이유는 비동기 로직들을 가진 테스트의 결과가 random 하게 되는 것을 방지하기 위해서입니다. 즉, &lt;b&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;cy.*&lt;/span&gt; &lt;/b&gt;메서드들은 &lt;b&gt;비동기적이고, retry하며, 체이닝이 됩니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;cy.* 메서드들은 비동기적으로 실행되지만, cy.* 들이 연달아 있을 때 이들은 서로 이전 메서드의 실행이 끝나는 것을 &lt;b&gt;큐에서 기다리며 순서대로 실행됩니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 이런 serial 실행이 보장되지 않으면 다음과 같은 테스트는 실패하게 될 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1639558376789&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;cy.get(&quot;input[type='checkbox']&quot;).as(&quot;checkbox&quot;).click();
cy.get(&quot;@checkbox&quot;).should(&quot;be.checked&quot;); // serial 하지 않으면 click 이전에 checked를 확인해서 테스트 실패&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 다행히도 실제로는 cy.*() 들이 serial하게 실행될 것이기 때문에, 첫 번째 줄이 다 끝나야 두 번째 줄이 실행될 것이므로 정상적으로 통과할 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;cypress를 사용하며 느낀 점&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;cypress를 사용하면서 개발 경험과 협업 경험 모두를 섬세하게 신경쓴 프레임워크라고 느꼈습니다. BDD와 TDD가 모두 가능하며 유비쿼터스 랭귀지로 직관적으로 읽히는 코드를 작성할 수 있었습니다. 또한 각 cy.* 메서드가 serial하게 실행되기에 비동기 로직이 있어도 테스트의 논리적 흐름을 쫓아가는 데에 어려움을 겪을 일이 없었습니다. configuration으로 다양한 사용자 환경을 테스트할 수도 있고, seed 데이터 관리도 편했고, stub이나 spy기능도 사용하기 쉬웠습니다. 프론트엔드 개발 중 e2e test의 필요성을 느낀다면 꼭 한번 도전해볼 가치가 있는 기술이라고 생각이 듭니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Info</category>
      <category>BDD</category>
      <category>CYPRESS</category>
      <category>e2e 테스트</category>
      <category>end to end test</category>
      <category>react test</category>
      <category>TDD</category>
      <category>기능 테스트</category>
      <category>테스트</category>
      <category>프레임워크</category>
      <category>프론트엔드</category>
      <author>cocoder16</author>
      <guid isPermaLink="true">https://cocoder16.tistory.com/66</guid>
      <comments>https://cocoder16.tistory.com/66#entry66comment</comments>
      <pubDate>Mon, 3 Jan 2022 08:00:05 +0900</pubDate>
    </item>
    <item>
      <title>Redux Toolkit + axios로  리덕스에서 비동기 통신 사용하기</title>
      <link>https://cocoder16.tistory.com/65</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;들어가기 전에&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;이 포스팅 작성 시점 기준 패키지 버전 @reduxjs/toolkit v1.6.2, react-redux v7.2.6, axios v0.24.0입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Redux Toolkit에 대해서&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 포스팅은 보기 전에 &lt;a href=&quot;https://cocoder16.tistory.com/64&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Redux Toolkit 기본 사용법에 대해 다룬 이전 포스팅&lt;/a&gt;을 보고 오시면 좋습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;axios에 대해서&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;axios는 ajax를 쉬운 코드로 사용할 수 있도록 편의성을 제공해주는 라이브러리입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;axios는 axios() 함수를 호출하는 것으로 ajax request를 보낼 수 있으며 리턴 값이 promise이기 때문에 then, catch, finally와 같은 메소드로 프로미스 체이닝을 통해 response 후속 조치를 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* axios 프로미스 객체 사용법&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1639472031310&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;axios({
  method,
  url,
  data,
}).then(response =&amp;gt; {
  console.log(response);
}).catch(error =&amp;gt; {
  console.log(error);
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;프로젝트 세팅&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리액트 프로젝트를 CRA등으로 간편하게 세팅을 하고, redux-toolkit과 react-redux, axios를 설치해줘야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1639472913169&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npx create-react-app redux-axios-tutorial
cd redux-axios-tutorial
yarn add @reduxjs/toolkit react-redux axios&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 코드를 테스트하기 위해 axios로 통신할 API 서버가 있어야 합니다. 따로 구현하기 귀찮으니까 무료로 사용할 수 있는 API 서버로 테스트해보겠습니다. &lt;a href=&quot;https://dummy.restapiexample.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://dummy.restapiexample.com/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Redux에서 Redux Toolkit과 axios를 사용하여 비동기 통신하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리덕스 툴킷은 createAsyncThunk라는 함수를 제공해주는데 이 함수는 내부적으로 redux-thunk 라이브러리를 사용하고 있습니다. 그래서 redux-thunk를 사용해본 경험이 있다면 사용법이 익숙할 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 createAsyncThunk는 createAction처럼 액션 생성 함수이기 때문에 첫 번째 파라미터로 액션명을 받습니다. 그리고 두 번째 파라미터로 콜백 함수를 작성하여 비동기 로직을 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* createAsyncThunk 기본 사용법&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1639472343897&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const getEmployees = createAsyncThunk(&quot;GET/EMPLOYEES&quot;, async () =&amp;gt; {
  // ajax 요청하고 promise 객체를 리턴 받는 함수를 여기에 사용합니다.
}),&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저 비동기 로직이 들어갈 자리에 axios를 사용할 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1639472433751&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const getEmployees = createAsyncThunk(&quot;GET/EMPLOYEES&quot;, async () =&amp;gt; {
  return axios({
    method: &quot;get&quot;,
    url: &quot;http://dummy.restapiexample.com/api/v1/employees&quot;
  });
}),&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 이것을 리듀서와 엮어서 사용해보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* src/store/employees.js&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1639472594611&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export const action = {
  getEmployees: createAsyncThunk(&quot;GET/EMPLOYEES&quot;, async () =&amp;gt; {
    return axios({
      method: &quot;get&quot;,
      url: &quot;http://dummy.restapiexample.com/api/v1/employees&quot;
    }).then(response =&amp;gt; response.data);
  }),
};

const initialState = {
  employees: [],
};

export const reducer = {
  getEmployees: (state, action) =&amp;gt; {
    state.getEmployees = action.payload.data;
  }
};

const employeesReducer = createReducer(initialState, builder =&amp;gt; {
  builder
    .addCase(action.getEmployees.fulfilled, reducer.getEmployees)
});

export default employeesReducer;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리듀서를 사용하는 스토어는 다음과 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* src/store/index.js&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1639472758487&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { configureStore } from &quot;@reduxjs/toolkit&quot;;

import employeesReducer from &quot;src/store/employees&quot;;

export const store = configureStore({
  reducer: {
    employees: employeesReducer,
  },
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기까지 리덕스 구현은 완료했습니다. 지금까지 작성한 로직을 flux패턴을 따라 잠시 점검해보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;GET/EMPLOYEES&quot;라는 액션이 디스패치가 되면 액션 생성 함수인 createAsyncThunk의 콜백이 실행되면서 axios함수가 호출됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;axios함수는 API 서버에 get method로 요청을 보내고 그 결과를 프로미스로 반환받습니다. 응답을 성공적으로 받으면 then 메소드 내에서 response.data를 리턴하여 리듀서의 payload로 보내줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;createReducer에는 getEmployees가 성공했을 때 실행되는 getEmployees.fulfilled 액션명에 대한 리듀서가 정의되어 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리듀서 함수는 axios의 then안에서의 response.data를 payload로 받아 state.employees에 할당해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리듀서 함수가 실행된 결과 store는 employees 데이터가 들어간 상태로 업데이트가 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;요청에 실패한 경우&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;400번대, 500번대 에러 상태 코드&lt;/b&gt;를 응답받거나 요청이 &lt;b&gt;failed&lt;/b&gt;인 경우는 axios 반환 프로미스 객체에 catch로 받을 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1639473707368&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export const action = {
  getEmployees: createAsyncThunk(&quot;GET/EMPLOYEES&quot;, async (_, { rejectWithValue }) =&amp;gt; {
    return axios({
      method: &quot;get&quot;,
      url: &quot;http://dummy.restapiexample.com/api/v1/employees&quot;
    })
    .then(response =&amp;gt; response.data)
    .catch(error =&amp;gt; rejectWithValue(error.response.data));
  }),
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면 컴포넌트 단에서 사용하는 dispatch().unwrap().catch()에서 rejectWithValue() 안에 있는 값을 받을 수 있습니다. 리듀서 로직을 타지 않고 컴포넌트 단에서 처리하고 싶은 로직은 이런 식으로 처리를 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* App.js&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1639474030083&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useState, useEffect } from &quot;react&quot;;
import { useSelector, useDispatch } from &quot;react-redux&quot;;

function App() {
  const employees = useSelector(state =&amp;gt; state.employees.employees);
  const dispatch = useDispatch();
  const [errorMessage, setErrorMessage] = useState(&quot;&quot;);

  useEffect(() =&amp;gt; {
    dispatch(action.getEmployees())
      .unwrap()
      .then(response =&amp;gt; {
        console.log(&quot;### response: &quot;, response);
      })
      .catch(error =&amp;gt; {
        console.log(&quot;### error: &quot;, error);
        setErrorMessage(error.message);
      });
  }, []);

  return (
    &amp;lt;div className=&quot;App&quot;&amp;gt;
      {errorMessage &amp;amp;&amp;amp; &amp;lt;div&amp;gt;{errorMessage}&amp;lt;/div&amp;gt;}
      {employees.map(employee =&amp;gt; &amp;lt;div id={employee.id}&amp;gt;{employee.name}&amp;lt;/div&amp;gt;)}
    &amp;lt;/div&amp;gt;
  );
}

export default App;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마찬가지로 dispatch().unwrap().then() 에서는 axios().then() 에서 then() 안에서 반환하는 값을 파라미터를 통해 받아옵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 실패한 비동기 요청은 &lt;b&gt;`액션명/rejected`&lt;/b&gt; 액션을 디스패치합니다. 이에 대한 리듀서도 작성해줄 수 있습니다. 실패한 요청에 대해 리덕스 스토어 값 변경이 필요하다면 다음과 같이 사용해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1639475790704&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export const reducer = {
  // ...
  handleReject: (state, action) =&amp;gt; {
    // state 업데이트 로직
  },
};

const employeesReducer = createReducer(initialState, builder =&amp;gt; {
  builder
    .addCase(action.getEmployees.fulfilled, reducer.getEmployees)
    .addCase(action.getEmployees.rejected, reducer.handleReject) // 400, 500번대 이거나 요청이 failed 일때, rejected로 들어옴
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>React.js</category>
      <category>axios</category>
      <category>catch</category>
      <category>react</category>
      <category>redux</category>
      <category>redux toolkit</category>
      <category>redux-thunk</category>
      <category>then</category>
      <category>리듀서</category>
      <category>비동기</category>
      <category>프로미스</category>
      <author>cocoder16</author>
      <guid isPermaLink="true">https://cocoder16.tistory.com/65</guid>
      <comments>https://cocoder16.tistory.com/65#entry65comment</comments>
      <pubDate>Mon, 27 Dec 2021 08:00:57 +0900</pubDate>
    </item>
    <item>
      <title>Redux Toolkit으로 리덕스 쉽게 사용하기</title>
      <link>https://cocoder16.tistory.com/64</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;지적되어온 Redux의 문제점들&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리덕스(Redux)는 리액트(React)의 상태 관리 라이브러리 중 대표로서 굳게 자리매김하고 있었지만, 그동안 리덕스가 사용하기 너무 복잡하고 배우기 어렵다는 의견이 많았습니다. 실제로 리액트에 입문하는 사람들이 리액트를 배우다가 가장 좌절을 겪는 구간이 리덕스를 배울 때이기도 합니다. 리덕스가 높은 러닝 커브와 복잡성을 가지는 원인들을 리덕스 측에서 문서로 공식적으로 발표한 것이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1. store configuration 복잡성&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선, 리덕스는 스토어(store)를 설정하는 게 너무 복잡하다는 것입니다. 리덕스는 앱 전체에 단 하나의 스토어를 가지며 이 스토어는 리액트 앱에서 사용할 상태(state)들을 저장하고 관리합니다. 따라서 스토어는 상태 값들을 사용하고 관리하는 모든 API를 가지며 이것을 설정해주는 것은 복잡한 작업이었다는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 저는 이것은 크게 어려운 작업은 아닌 것 같고 오히려 다음 2,3번이 큰 내용이라고 봅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2. redux를 사용하기 위해선 다른 라이브러리들의 추가설치가 너무 많이 필요했다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정말 그랬습니다. redux를 잘 사용하려면 다음과 같은 패키지들을 추가 설치해줘야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- react와 바인딩하기 위한 react-redux&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 불변성을 유지하기위한 immutable/immer&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 비동기 통신을 위한 redux-thunk/redux-saga/redux-pender&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 액션 생성을 위한 redux-actions&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;심지어 리액트와 리덕스 특유의 너무 높은 자유도로 인해 프로젝트마다, 개발자 취향마다 설치하는 패키지들이 다 제각각일 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 A패키지에 대해 학습을 하여 프로젝트를 진행했다가, 다른 프로젝트로 옮겨가서는 A 패키지가 아닌 B 패키지를 학습해야 하는 일이 빈번하게 생기는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;게다가 리액트 특유의 빠르게 변화하는 생태계는 이런 지속적인 학습부담과 피로도를 더욱 가중시킵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3. 너무 많은 양의 보일러플레이트를 파일마다 반복 작성해야 했다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리덕스 액션과 리듀서들을 도메인마다 분리를 하고, 또 리액트 각 컴포넌트마다 바인딩을 하면, 엄청나게 많은 수의 파일에서 리덕스 코드를 사용하게 됩니다. 그런데 그 많은 파일에서 많은 양의 보일러 플레이트 코드를 반복해서 사용해줘야 해서 코드가 지저분해지고 관리포인트가 많다는 단점이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;심지어 2번에서 언급했던 추가 패키지들을 뭘 설치하냐에 따라 보일러플레이트 코드가 천차만별이어서 처음 프로젝트를 시작했을 때, 딱 정형화된 보일러 플레이트를 바로 작성하기도 어렵습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;게다가 리액트 생태계 특유의 빠른 발전 속도 때문에 보일러플레이트를 작성하고 뒤돌아서면 바로 레거시가 되어버리는 점도 개발과 유지보수의 피로도를 가중시킵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 원인들로 인해 리덕스는 너무 복잡하고 배우기 어렵다는 것이 사실 틀린 말은 아닙니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제점들을 날려버린 Redux Toolkit&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리덕스 툴킷(Redux Toolkit)은 위에서 언급한 단점들을 제거한 리덕스에서 공식적으로 내놓은 패키지입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리덕스 공식 문서도 리덕스를 사용할거면 이 리덕스 툴킷을 사용하는 것이 좋다고 적극 권장하고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 말한 문제점들을 어떤 식으로 해결했는지 하나씩 살펴봅시다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1. store configuration&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 스토어를 생성은 configureStore함수를 실행하여 할 수 있습니다. 이 안에서 리듀서(reducer)들을 바인딩해주면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1639463121830&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { configureStore } from &quot;@reduxjs/toolkit&quot;

export const store = configureStore({
  reducer: {},
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 리액트 앱에서는 이 store를 임포트해서 Provider로 제공해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1639463221955&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ReactDOM.render(
  &amp;lt;Provider store={store}&amp;gt;
    &amp;lt;App /&amp;gt;
  &amp;lt;/Provider&amp;gt;,
  document.getElementById('root')
)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2. 너무 많은 패키지들 추가 설치&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리덕스 툴킷은 딱 두 가지 패키지만 설치해서 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1639463294495&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;yarn add @reduxjs/toolkit react-redux&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리덕스 툴킷은 내부적으로 불변성을 지키기 위해 immer라이브러리를 사용하고 비동기 통신을 위해 redux-thunk를 사용합니다. 리덕스 툴킷은 이렇게 내부적으로 의존 라이브러리들을 사용해서 개발자는 툴킷만 설치하면 다른 패키지를 뭘 설치할지 고민할 필요가 없도록 만들었습니다. 그러므로 리덕스 툴킷을 사용할거면 추가 라이브러리 설치를 하지 말고 공식문서에서 가이드하는 대로 딱 저 두 가지만 설치를 해서 사용하는 것이 패키지 사용 통일성을 지킬 수 있어서 좋습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3. 너무 많은 양의 보일러 플레이트&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리덕스 툴킷은 보일러 플레이트 코드량을 최소화하도록 노력한 것이 보입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;flux패턴 중에 액션 생성 함수, 리듀서 코드는 다음과 같이 작성할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시 코드는 typescript를 적용했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* type.d.ts&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1639470358896&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;type ToDo = {
  id: number;
  content: string;
  checked: boolean;
};

type ToDoList = ToDo[] | [];

interface IToDoListState {
  toDoList: IToDoList;
}

interface IAction {
  type?: string;
}

interface IToggleToDoPayload {
  id: number;
  checked: boolean;
}

interface IToggleToDoAction extends IAction {
  payload: IToggleToDoPayload;
}

interface IDeleteToDoPayload {
  id: number;
}

interface IDeleteToDoAction extends IAction {
  payload: IDeleteToDoPayload;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* src/store/toDoList/index.ts&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1639466921648&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { createAction, createReducer } from &quot;@reduxjs/toolkit&quot;;

export const action = {
  toggleToDo: createAction&amp;lt;IToggleToDoPayload&amp;gt;(&quot;TOGGLE/TO_DO&quot;),
  deleteToDo: createAction&amp;lt;IDeleteToDoPayload&amp;gt;(&quot;DELETE/TO_DO&quot;),
};

const initialState: IToDoListState = {
  toDoList: [],
};

export const reducer = {
  toggleToDo: (state: IToDoListState, action: IToggleToDoAction) =&amp;gt; {
    state.toDoList.find((todo: ToDo) =&amp;gt; todo.id === action.payload.id).checked = action.payload.checked;
  },
  deleteToDo: (state: IToDoListState, action: IDeleteToDoAction) =&amp;gt; {
    state.toDoList = state.toDoList.filter((todo: ToDo) =&amp;gt; todo.id !== action.payload.id);
  },
};

const toDoListReducer = createReducer(initialState, builder =&amp;gt; {
  builder
    .addCase(action.toggleToDo, reducer.toggleToDo)
    .addCase(action.deleteToDo, reducer.deleteToDo);
});

export default toDoListReducer;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 리듀서 툴킷에서 제공하는 createAction, createReducer만 있으면 리덕스를 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빌더 패턴을 사용하여 리듀서 함수들을 추가하기 때문에 createReducer함수를 사용하는 코드가 매우 간결합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 좋은 점은 리듀서는 내부적으로 immer라이브러리를 사용하기 때문에 불변성에 대해서 신경 쓰지 않고 state를 변경시켜주어도 내부적으로 불변성을 지켜줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만든 리듀서는 스토어에 다음과 같이 추가해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* src/store/index.ts&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1639468256706&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { configureStore } from &quot;@reduxjs/toolkit&quot;;

import toDoListReducer from &quot;src/store/toDoList&quot;;

export const store = configureStore({
  reducer: {
    toDoList: toDoListReducer,
  },
});

// Infer the `RootState` and `AppDispatch` types from the store itself
export type RootState = ReturnType&amp;lt;typeof store.getState&amp;gt;;

export type AppDispatch = typeof store.dispatch;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리덕스 코드가 간결한건 알겠고, 그렇다면 리액트랑 바인딩할 때에는 보일러 플레이트가 얼마나 단순해졌는지 볼까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;List와 Item이라는 컴포넌트를 만들어 살펴보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* src/components/toDoList/List.tsx&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1639467662337&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useSelector } from &quot;react-redux&quot;;

import Item from &quot;src/components/toDoList/item&quot;;
import { RootState } from &quot;src/store&quot;;

function List() {
  const toDoList: ToDoList = useSelector((state: RootState) =&amp;gt; state.toDoList.toDoList);

  return (
    &amp;lt;div&amp;gt;
      {toDoList &amp;amp;&amp;amp; toDoList.map(toDo =&amp;gt; &amp;lt;Item key={toDo.id} toDo={toDo} /&amp;gt;)}
    &amp;lt;/div&amp;gt;
  );
}

export default List;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;store에 있는 state를 가져오기 위해서는 react-redux가 제공하는 useSelect를 사용합니다. 이때 타입스크립트를 사용한다면, store에서 만든 RootState 타입을 사용해줘야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* src/components/toDoList/Item.tsx&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1639468409155&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useDispatch } from &quot;react-redux&quot;;

import { action } from &quot;src/store/toDoList&quot;;

interface IProps {
  toDo: ToDo;
}

function Item({ toDo }: IProps) {
  const { id, content, checked } = toDo;
  const dispatch = useDispatch();

  const onToggle = (event: any) =&amp;gt; {
    dispatch(action.toggleToDo({ id, checked: event.target.checked as boolean }));
  };

  const onDeleteToDo = () =&amp;gt; {
    dispatch(action.deleteToDo({ id }));
  };

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;div&amp;gt;{id}&amp;lt;/div&amp;gt;
      &amp;lt;p&amp;gt;{content}&amp;lt;/p&amp;gt;
      &amp;lt;input type=&quot;checkbox&quot; defaultChecked={checked} onChange={onToggle} /&amp;gt;
      &amp;lt;button onClick={onDeleteToDo}&amp;gt;삭제&amp;lt;/Button&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}

export default Item;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;액션을 디스패치(dispatch) 하기 위해서는 react-redux에서 제공하는 useDispatch를 사용합니다. 위 Item 컴포넌트에서는 input check값이 변경되었을 때, 삭제 버튼을 눌렀을 때 각각 이벤트 콜백 함수 안에서 dispatch를 실행하도록 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴포넌트 단에서도 useSelect와 useDispatch를 이용하면 보일러 플레이트가 많이 간결하다는 것을 보았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 여기까지 해서 get store state -&amp;gt; dispatch(action) -&amp;gt; run reducer -&amp;gt; update store with new state -&amp;gt; view re-rendering 흐름의 flux 패턴이 구현이 모두 끝났습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마무리&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리덕스 툴킷은 이렇게 리덕스의 기존 단점들을 날려버리고 간결하고 정형화된 코드 패턴을 사용할 수 있는 패키지입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리덕스 툴킷에서 제시하는 정형화된 코드 패턴을 사용하면 개발자들 사이의 커뮤니케이션과 협업 난이도도 낮아지므로 리덕스를 사용할거면 이 툴킷을 사용하는 것이 매우 좋다고 생각이 듭니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;이 포스팅 작성시점 기준 패키지버전 @reduxjs/toolkit v1.6.2 react-redux v7.2.6 입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>React.js</category>
      <category>react</category>
      <category>react-redux</category>
      <category>redux</category>
      <category>redux toolkit</category>
      <category>리덕스 단점</category>
      <category>리덕스 러닝커브</category>
      <category>리덕스 툴킷</category>
      <category>보일러 플레이트</category>
      <author>cocoder16</author>
      <guid isPermaLink="true">https://cocoder16.tistory.com/64</guid>
      <comments>https://cocoder16.tistory.com/64#entry64comment</comments>
      <pubDate>Mon, 20 Dec 2021 08:00:04 +0900</pubDate>
    </item>
    <item>
      <title>webpack-dev-server로 프론트엔드 개발 서버 띄우기</title>
      <link>https://cocoder16.tistory.com/56</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;선행 지식인 webpack에 대해서는 &lt;a href=&quot;https://cocoder16.tistory.com/55&quot;&gt;이전 포스팅&lt;/a&gt;을 참조하면 좋을 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;webpack-dev-server란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;webpack-dev-server는 매 코드 변경마다 빌드된 결과물을 확인할 수 있는 개발용 서버를 제공해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 빌드는 시간이 오래 걸리기 때문에 webpack-dev-server는 실제 번들링 된 결과물을 파일로 생성하지 않고 메모리에 올려놓은 채 보여줍니다. 그래서 빠른 속도로 즉시 변경된 코드를 개발 서버에 반영하여 보여줄 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;webpack-dev-server 실습하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;webpack-dev-server 실습은 &lt;a href=&quot;https://cocoder16.tistory.com/55#practice&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;이전 포스팅에서 했던 실습&lt;/a&gt;을 이어서 진행합니다. 따라서 이전 실습을 다 마친 상태의 프로젝트 폴더를 준비해주세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 이어서 webpack-dev-server를 설치합니다. 터미널을 켜고 해당 프로젝트 경로로 이동 후 다음 명령어를 실행해주세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1637906934380&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;yarn add -D webpack-dev-server&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로 config 파일에 dev server 설정 코드를 추가해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* webpack.config.js&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1637907068516&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;module.exports = {
  // ...
  devServer: {
    static: {
      directory: path.resolve(__dirname, &quot;dist&quot;),
    },
    port: 3000,
  },
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;dev server가 주시할 directory를 설정하고 3000번 포트를 생성하는 설정입니다. 이제 3000번 포트로 접속하면 개발 서버에 접속할 수 있습니다. 디렉토리는 번들링 된 결과물이 저장되는 빌드 폴더를 지정합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로 dev server를 실행시킬 명령어를 작성합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* package.json&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1637907450823&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&quot;scripts&quot;: {
  &quot;start&quot;: &quot;webpack serve --open --mode=development --hot&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;webpack serve는 개발 서버를 여는 명령어입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;--open은 브라우저를 열어주는 옵션입니다. 이 옵션이 있으면 명령어 실행 시 자동으로 브라우저를 열어서 개발 서버에 접속해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발용 서버이므로 --mode=development를 추가해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;--hot은 코드가 변경될 때마다 자동으로 변경사항을 브라우저에 반영해서 보여주는 옵션입니다. 이 옵션이 있으면 브라우저에서 수동으로 새로고침을 누를 필요가 없어집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;터미널에서 만든 명령어를 실행해봅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1637907813607&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;yarn start&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;브라우저가 열리고 localhost:3000에 접속하여 리소스들이 제대로 불러와졌으면 성공입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Node.js</category>
      <category>javascript</category>
      <category>react</category>
      <category>webpack</category>
      <category>webpack-dev-server</category>
      <category>개발 서버</category>
      <category>자동 새로고침</category>
      <category>프론트엔드</category>
      <author>cocoder16</author>
      <guid isPermaLink="true">https://cocoder16.tistory.com/56</guid>
      <comments>https://cocoder16.tistory.com/56#entry56comment</comments>
      <pubDate>Mon, 13 Dec 2021 08:00:24 +0900</pubDate>
    </item>
    <item>
      <title>Javascript 모듈 다루기, 그리고 webpack으로 bundling하기</title>
      <link>https://cocoder16.tistory.com/55</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Javascript 모듈 다루기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;script를 다음과 같이 사용하는 것은 모듈화가 되어있지 않은 상태입니다. 이 상태에서는 각 script에 해당하는 js파일들은 서로 스코프를 공유하고 있습니다. 가령 다음과 같은 예시 코드에서는&amp;nbsp;scriptB.js 안에 있는 코드에서 scriptA.js에 있는 변수에 접근할 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1637828131592&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;script src=&quot;./js/scriptA.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;script src=&quot;./js/scriptB.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;script src=&quot;./js/scriptC.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;script src=&quot;./js/scriptD.js&quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 javascript ES6부터는 import/export 구문으로 모듈을 다룰 수 있게 되었습니다. 각각의 모듈은 import/export 구문에 의해서 의존성을 추적할 수 있습니다. 한편, 모듈을 html에서 사용하기 위해서는 script 태그에 type=&quot;module&quot;을 명시해줘야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* index.html&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1637828345349&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;script src=&quot;./js/index.js&quot; type=&quot;module&quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* index.js&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1637828418828&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import moduleA from &quot;./moduleA.js&quot;;

// 메인 실행 코드&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* moduleA.js&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1637828488071&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import moduleB from &quot;./moduleB.js&quot;;

const moduleA = {
	// moduleB에 의존성을 가지는 내부 로직
};

export default moduleA;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* moduleB.js&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1637828562899&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const moduleB = { };

export default moduleB;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 형태로 모듈 파일들을 구성하면 index.html가 실행되었을 때, index.js -&amp;gt; moduleA.js -&amp;gt; moduleB.js 와 같은 의존성에 의해 각각의 모듈이 실행됩니다. 모듈화가 되었기에 각각의 모듈 파일은 서로의 스코프를 공유하지 않고, 캡슐화와 정보은닉이 된 상태입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;webpack으로 bundling 하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;javascript 모듈의 기능은 bundler인 webpack을 사용하면 더욱 강력해집니다. webpack은 js모듈의 의존관계를 추적해서 하나의 파일 혹은 n개의 파일로 bundling 해줄 수 있는 도구입니다. code를 minify 시켜줄 수도 있고, 페이지에서는 빌드된 파일만을 불러오기 때문에 페이지 로딩 속도가 개선되며, 자원 소비도 줄일 수 있다는 장점이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;practice&quot; data-ke-size=&quot;size26&quot;&gt;webpack 실습&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;사전 준비 - node.js 설치 &amp;amp; 프로젝트 폴더 및 파일 준비&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;webpack은 node에서 작동하므로 사용하기 위해서 node.js를 먼저 설치해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://cocoder16.tistory.com/54&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;node.js 설치방법&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금부터 webpack으로 js 파일들을 bundling을 하는 실습을 할 건데 이것은 현재 포스팅 시점 기준 webpack 버전 v5.64.1의 예제입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 연습용 프로젝트 폴더를 하나 생성하고 터미널을 켜서 프로젝트 폴더 경로로 이동한 후 다음 명령어를 실행하여 package.json을 생성합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1637892170103&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm init -y&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 프로젝트 폴더에서 다음과 같은 디렉토리 구조로 html, js파일들을 생성합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1637891706991&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package.json
public
ㄴindex.html
src
ㄴjs
  ㄴindex.js
  ㄴmodules
    ㄴmoduleA.js
    ㄴmoduleB.js&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;총 다음 5개의 파일이 생성되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;package.json&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;public/index.html&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;src/js/index.js&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;src/js/modules/moduleA.js&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;src/js/modules/moduleB.js&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 파일별로 다음 코드를 작성합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* index.html&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1637892655427&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html lang=&quot;en&quot;&amp;gt;

&amp;lt;head&amp;gt;
  &amp;lt;meta charset=&quot;UTF-8&quot;&amp;gt;
  &amp;lt;meta http-equiv=&quot;X-UA-Compatible&quot; content=&quot;IE=edge&quot;&amp;gt;
  &amp;lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1.0&quot;&amp;gt;
  &amp;lt;title&amp;gt;Document&amp;lt;/title&amp;gt;
&amp;lt;/head&amp;gt;

&amp;lt;body&amp;gt;
&amp;lt;/body&amp;gt;

&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* index.js&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1637892715691&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import moduleA from &quot;./modules/moduleA.js&quot;;

moduleA();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* moduleA.js&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1637892727822&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import moduleB from &quot;./moduleB.js&quot;;

export default function moduleA() {
  console.log(&quot;run module A !&quot;);
  moduleB();
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* moduleB.js&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1637892759771&quot; class=&quot;javascript&quot; style=&quot;margin: 20px auto 0px; display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; cursor: default; z-index: 1;&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export default function moduleB() {
  console.log(&quot;run module B !&quot;);
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;index.html을 보면 어떠한 js파일도 import를 하고 있지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 각 js파일들은 index.js -&amp;gt; moduleA.js -&amp;gt; moduleB.js 순으로 의존관계를 맺고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것을 하나의 js파일로 번들링을 하고, index.html에서는 이 번들링 된 파일을 script태그로 불러오는 것을 모두 자동으로 할 수 있도록 webpack 세팅을 할 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div class=&quot;revenue_unit_item adsense  responsive&quot;&gt;&lt;ins class=&quot;adsbygoogle&quot; style=&quot;display: block;&quot; data-ad-host=&quot;ca-host-pub-9691043933427338&quot; data-ad-client=&quot;ca-pub-9807016842906892&quot; data-ad-format=&quot;auto&quot;&gt;&lt;/ins&gt;
&lt;script&gt;(adsbygoogle = window.adsbygoogle || []).push({}); &lt;/script&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;webpack 설치 및 js파일 빌드 세팅&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 터미널로 이 프로젝트에 개발 의존 모듈로 웹팩을 설치합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1637892268373&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;yarn add -D webpack webpack-cli&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 프로젝트 루트 디렉토리에 웹팩 설정 파일을 만듭니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* webpack.config.js&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1637893082809&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const path = require(&quot;path&quot;);

module.exports = {
  entry: {
    index: &quot;./src/js/index.js&quot;,
  },
  output: {
    path: path.resolve(__dirname, &quot;dist&quot;),
    filename: &quot;[name].bundle.js&quot;,
    clean: true,
  },
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;entry는 모듈 진입점을 의미합니다. src/js/index.js 파일을 진입점으로 잡았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;output은 번들링 된 파일에 대한 설정입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;path는 경로 설정입니다. dist폴더에 번들링 된 파일들을 생성할 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;filename은 번들링된 파일명입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;clean은 매 번들링 전에 (path에서 설정한) dist디렉토리를 청소할 것인지 여부입니다. 더 이상 사용하지 않을 파일들을 제거하기에 유용한 설정입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹팩 번들러 설정을 했으니 번들링을 실행할 스크립트 명령어를 만듭니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* package.json&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1637893282352&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;scripts&quot;: {
    &quot;build&quot;: &quot;webpack --mode=development --config ./webpack.config&quot;,
  },
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;--mode는 개발 모드와 프로덕션 모드를 설정할 수 있습니다. 저희는 개발 모드로 하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;--config는 webpack configuration 파일이 위치한 경로를 설정합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 터미널에서 package.json에서 만든 명령어를 실행해봅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1637893396699&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;yarn build&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행 결과, dist폴더가 생성되고 그 안에 index.bundle.js가 생성되었으면 제대로 번들링이 된 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;index.bundle.js는 index.js를 진입점으로 잡아 의존관계에 있는 모든 모듈들을 하나로 합친 파일입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;index.html 빌드 세팅&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹팩 번들링을 실행할 때, 번들된 js파일을 불러오는 index.html을 dist에 생성하는 세팅을 하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;플러그인이 하나 필요하므로 터미널에서 플러그인을 개발 의존 모듈로 설치해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1637893554141&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;yarn add -D html-webpack-plugin&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 configuration을 수정합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* webpack.config.js&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1637893612889&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const path = require(&quot;path&quot;);
const HtmlWebpackPlugin = require(&quot;html-webpack-plugin&quot;);

module.exports = {
  entry: {
    index: &quot;./src/js/index.js&quot;,
  },
  output: {
    path: path.resolve(__dirname, &quot;dist&quot;),
    filename: &quot;[name].bundle.js&quot;,
    clean: true,
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: &quot;./public/index.html&quot;,
    }),
  ],
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;plugins를 추가했습니다. 이제 build를 하면 public/index.html을 템플릿으로 하여 번들링 한 js파일을 import 하는 script태그를 추가한 index.html을 dist폴더에 생성할 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;터미널에서 다시 빌드를 실행해봅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1637893748637&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;yarn build&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;dist폴더에 index.html과 index.bundle.js가 생성되었으면 성공한 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Node.js</category>
      <category>html-webpack-plugin</category>
      <category>javascript</category>
      <category>Module</category>
      <category>webpack</category>
      <category>webpack.confg</category>
      <category>모듈</category>
      <category>번들링</category>
      <category>빌드</category>
      <category>웹팩 사용법</category>
      <category>웹팩 입문</category>
      <author>cocoder16</author>
      <guid isPermaLink="true">https://cocoder16.tistory.com/55</guid>
      <comments>https://cocoder16.tistory.com/55#entry55comment</comments>
      <pubDate>Mon, 6 Dec 2021 08:00:26 +0900</pubDate>
    </item>
    <item>
      <title>Node.js, npm, yarn, Homebrew, nvm 설치 방법</title>
      <link>https://cocoder16.tistory.com/54</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;설치 방법 소개&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;windows와 mac 환경으로 나뉘어서 node.js와 npm, yarn을 설치하는 방법을 정리하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;mac의 경우 homebrew와 nvm 설치방법이 추가됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Windows 유저인 경우 node.js, npm, yarn 설치 방법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;windows인 경우 다음 링크에서 LTS 버전을 설치합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://nodejs.org/ko/download/&quot;&gt;https://nodejs.org/ko/download/&lt;/a&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1637830480229&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;다운로드 | Node.js&quot; data-og-description=&quot;Node.js&amp;reg; is a JavaScript runtime built on Chrome's V8 JavaScript engine.&quot; data-og-host=&quot;nodejs.org&quot; data-og-source-url=&quot;https://nodejs.org/ko/download/&quot; data-og-url=&quot;https://nodejs.org/ko/download/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bvONsp/hyMuCQSEo5/KKLaqi2VCUHSoKwlXGpEJ0/img.png?width=224&amp;amp;height=256&amp;amp;face=0_0_224_256,https://scrap.kakaocdn.net/dn/oL4Ie/hyMuygDETs/ylkyOA0b4j3ZKwkkepKUSK/img.png?width=224&amp;amp;height=256&amp;amp;face=0_0_224_256&quot;&gt;&lt;a href=&quot;https://nodejs.org/ko/download/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://nodejs.org/ko/download/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bvONsp/hyMuCQSEo5/KKLaqi2VCUHSoKwlXGpEJ0/img.png?width=224&amp;amp;height=256&amp;amp;face=0_0_224_256,https://scrap.kakaocdn.net/dn/oL4Ie/hyMuygDETs/ylkyOA0b4j3ZKwkkepKUSK/img.png?width=224&amp;amp;height=256&amp;amp;face=0_0_224_256');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;다운로드 | Node.js&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Node.js&amp;reg; is a JavaScript runtime built on Chrome's V8 JavaScript engine.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;nodejs.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;node.js&lt;/span&gt;를 설치하면 &lt;span style=&quot;color: #ee2323;&quot;&gt;npm&lt;/span&gt;도 설치가 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로 &lt;span style=&quot;color: #ee2323;&quot;&gt;yarn&lt;/span&gt;을 설치하려면 터미널을 열고 다음 명령어를 실행합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1637830649506&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm install -g yarn&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시스템 환경 변수를 확인하고 없으면 추가해줘야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;윈도우 검색에서 &quot;시스템 환경 변수 편집&quot;을 검색해서 실행합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;618&quot; data-origin-height=&quot;699&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cRFoGi/btrl92XKFf1/f678H3Cpjx9Yt22f3A2vO0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cRFoGi/btrl92XKFf1/f678H3Cpjx9Yt22f3A2vO0/img.png&quot; data-alt=&quot;시스템 환경 변수 편집&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cRFoGi/btrl92XKFf1/f678H3Cpjx9Yt22f3A2vO0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcRFoGi%2Fbtrl92XKFf1%2Ff678H3Cpjx9Yt22f3A2vO0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;282&quot; height=&quot;319&quot; data-origin-width=&quot;618&quot; data-origin-height=&quot;699&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;시스템 환경 변수 편집&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[고급] 탭에서 &lt;b&gt;환경 변수&lt;/b&gt;를 클릭합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서부터는 보안 문제로 인해 스크린샷은 첨부하지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 개의 변수가 있는지 확인해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저, &lt;b&gt;사용자 변수&lt;/b&gt;에서 &lt;b&gt;Path&lt;/b&gt;를 찾아 누르고 &lt;b&gt;편집&lt;/b&gt; 버튼을 클릭해서 &lt;b&gt;환경 변수 편집&lt;/b&gt; 창을 띄웁니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같은 npm 경로가 없으면 하나 추가해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1637887913060&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;C:\Users\사용자명\AppData\Roaming\npm&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로, &lt;b&gt;시스템 변수&lt;/b&gt;에서 &lt;b&gt;Path&lt;/b&gt;를 찾아 누르고 &lt;b&gt;편집&lt;/b&gt; 버튼을 클릭해서 &lt;b&gt;환경 변수 편집&lt;/b&gt; 창을 띄웁니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같이 nodejs 경로가 없으면 추가해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1637888230629&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;C:\Program Files\nodejs&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;터미널을 켜고 CLI로 설치가 잘되었는지 버전과 함께 확인할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1637830685698&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;node -v
npm -v
yarn -v&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;mac 유저인 경우 node.js, npm, yarn, homebrew, nvm설치 방법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;homebrew&lt;/span&gt;를 먼저 설치해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;homebrew 홈페이지에 들어가면 터미널을 켜고 다음과 같은 명령어를 입력하여 설치하라고 쓰여있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1637888441591&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/bin/bash -c &quot;$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://brew.sh/index_ko&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://brew.sh/index_ko&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1637888454700&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Homebrew&quot; data-og-description=&quot;The Missing Package Manager for macOS (or Linux).&quot; data-og-host=&quot;brew.sh&quot; data-og-source-url=&quot;https://brew.sh/index_ko&quot; data-og-url=&quot;https://brew.sh/index_ko&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/baQhrG/hyMuKhsHPA/8yOx3NEKQ85RDoC1vf2TB0/img.png?width=1200&amp;amp;height=560&amp;amp;face=0_0_1200_560,https://scrap.kakaocdn.net/dn/fXIYu/hyMtvlTYm6/uLkoaYRMBf2w4mZYoXkB2k/img.png?width=1200&amp;amp;height=560&amp;amp;face=0_0_1200_560&quot;&gt;&lt;a href=&quot;https://brew.sh/index_ko&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://brew.sh/index_ko&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/baQhrG/hyMuKhsHPA/8yOx3NEKQ85RDoC1vf2TB0/img.png?width=1200&amp;amp;height=560&amp;amp;face=0_0_1200_560,https://scrap.kakaocdn.net/dn/fXIYu/hyMtvlTYm6/uLkoaYRMBf2w4mZYoXkB2k/img.png?width=1200&amp;amp;height=560&amp;amp;face=0_0_1200_560');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Homebrew&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;The Missing Package Manager for macOS (or Linux).&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;brew.sh&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로 node 버전 관리 도구인 &lt;span style=&quot;color: #ee2323;&quot;&gt;nvm&lt;/span&gt;을 설치해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문서에 나와있는 대로 설치를 진행해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/nvm-sh/nvm#installing-and-updating&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/nvm-sh/nvm#installing-and-updating&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1637888625338&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - nvm-sh/nvm: Node Version Manager - POSIX-compliant bash script to manage multiple active node.js versions&quot; data-og-description=&quot;Node Version Manager - POSIX-compliant bash script to manage multiple active node.js versions - GitHub - nvm-sh/nvm: Node Version Manager - POSIX-compliant bash script to manage multiple active nod...&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/nvm-sh/nvm#installing-and-updating&quot; data-og-url=&quot;https://github.com/nvm-sh/nvm&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bHXKb2/hyMuEVRgZi/90qggrPNhUQV8kDq2TZJ90/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/nvm-sh/nvm#installing-and-updating&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/nvm-sh/nvm#installing-and-updating&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bHXKb2/hyMuEVRgZi/90qggrPNhUQV8kDq2TZJ90/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - nvm-sh/nvm: Node Version Manager - POSIX-compliant bash script to manage multiple active node.js versions&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Node Version Manager - POSIX-compliant bash script to manage multiple active node.js versions - GitHub - nvm-sh/nvm: Node Version Manager - POSIX-compliant bash script to manage multiple active nod...&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1637888643464&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;bash_profile을 vim으로 열고 환경변수를 편집합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1637888760911&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;cd
vi ~/.bash_profile&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 코드를 열린 bash_profile 하단에 추가합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1637888901935&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export NVM_DIR=&quot;$([ -z &quot;${XDG_CONFIG_HOME-}&quot; ] &amp;amp;&amp;amp; printf %s &quot;${HOME}/.nvm&quot; || printf %s &quot;${XDG_CONFIG_HOME}/nvm&quot;)&quot;
[ -s &quot;$NVM_DIR/nvm.sh&quot; ] &amp;amp;&amp;amp; \. &quot;$NVM_DIR/nvm.sh&quot; # This loads nvm&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;vim에서 저장하고 나가는 명령어입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1637888926563&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;:wq&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수정한 bash_profile을 적용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1637889059214&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;source ~/.bash_profile&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;node.js&lt;/span&gt;와 &lt;span style=&quot;color: #ee2323;&quot;&gt;npm&lt;/span&gt;을 설치합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;lts버전을 설치해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1637889785680&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;nvm install --lts&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;nvm은 프로젝트 별로 다른 node버전을 적용할 수 있도록 해줍니다. node버전 변경 명령어는 다음과 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1637889855059&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;nvm use 버전명&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용 가능한 버전 목록은 다음 명령어로 확인할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1637889952823&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;nvm ls&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;yarn&lt;/span&gt; 설치 방법은 windows와 같습니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1637890031394&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm install -g yarn&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Node.js</category>
      <category>Homebrew</category>
      <category>install</category>
      <category>Mac</category>
      <category>node</category>
      <category>Node.js</category>
      <category>npm</category>
      <category>nvm</category>
      <category>Windows</category>
      <category>yarn</category>
      <category>설치 방법</category>
      <author>cocoder16</author>
      <guid isPermaLink="true">https://cocoder16.tistory.com/54</guid>
      <comments>https://cocoder16.tistory.com/54#entry54comment</comments>
      <pubDate>Mon, 29 Nov 2021 08:00:48 +0900</pubDate>
    </item>
    <item>
      <title>Javascript async/await 키워드 사용하기</title>
      <link>https://cocoder16.tistory.com/51</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;async/await 키워드는 프로미스를 비동기가 아닌 동기적으로 작동시킵니다.&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;async/await는 간단한 키워드로 비동기 작업을 수행하는 promise를 동기적으로 동작하게끔 만들 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수 앞에 async 키워드를 붙이면 그 함수 내에서 await 키워드를 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;await 키워드가 붙은 프로미스는 비동기가 아닌 동기적으로 작업을 수행합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래에 await 키워드가 붙은 프로미스와 붙지 않은 프로미스의 비교 예시에서 콘솔 출력을 보면 await이 붙은 프로미스 뒤에 있는 코드들은 프로미스 작업이 끝날 때까지 기다렸다가 실행된다는 것을 알 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* await이 붙은 프로미스와 붙지 않은 프로미스의 비교&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1631411981765&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 프로미스 함수
const promise = () =&amp;gt; new Promise((resolve, reject) =&amp;gt; {
  setTimeout(() =&amp;gt; {
    const success = Math.random() &amp;gt; -1;
    
    console.log(&quot;result: &quot;, success);
    
    if (success) {
      resolve(&quot;success&quot;);
    } else {
      reject(&quot;failure&quot;);
    }
  }, 1000);
});

const myAsync = () =&amp;gt; {
  const result = promise();
  console.log(&quot;end&quot;);
};

// async/await 을 사용
const myAsync1 = async () =&amp;gt; {
  const result = await promise();
  console.log(&quot;end&quot;);
};

myAsync();
myAsync1();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;myAsync를 실행하면 콘솔에 end가 먼저 출력되고 1초 뒤에 result: true가 출력됩니다. 프로미스는 비동기적으로 실행되기 때문에 promise() 실행 시작 후 바로 console.log(&quot;end&quot;)가 바로 실행되었기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면, myAsync1을 실행하면 콘솔에 1초 뒤에 result: true 가 먼저 출력되고 end가 나중에 출력됩니다. await키워드와 함께 쓰인 프로미스는 동기적으로 처리되기 때문에 promise() 실행완료를 기다렸다가 다음 줄인 console.log(&quot;end&quot;)가 실행되기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;async 키워드를 붙이면 그 함수는 Promise를 리턴하는 함수가 됩니다.&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;async 키워드가 붙은 함수는 promise를 리턴합니다. 따라서 함수 실행 이후 프로미스 체이닝이 가능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;체이닝을 통해 함수의 리턴값은 then 메소드의 첫번째 파라미터로 전달할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 예제에서 만든 myAsync1 함수에 리턴값을 추가하겠습니다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1631413285251&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const myAsync1 = async () =&amp;gt; {
  const result = await promise();
  console.log(&quot;end&quot;);
  
  return &quot;success!!&quot;;
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1588771044992&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;myAsync1().then(res =&amp;gt; console.log(&quot;res: &quot;, res));&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드의 실행결과 콘솔에 찍히는 순서는 다음과 같습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1631413414139&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;result: true
end
res: success!!&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;흐름은 다음과 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;myAsync1 첫째 줄에서 promise 함수가 실행됩니다. await 키워드가 있기 때문에 동기적으로 실행되고 promise 함수 내부 코드에 의해 result: true가 먼저 출력됩니다. 프로미스 실행이 끝나길 기다렸다가 끝난 이후에 myAsync1함수 내부 코드에 의해 end가 출력됩니다. 그리고 myAsync1은 &quot;success!!&quot; 결과값을 가진 프로미스 객체를 반환하고 이후 then 메소드가 실행됩니다. 결과값은 then 메소드로 전달되므로 res: success!!가 출력됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;예외처리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 promise 함수를 실패하는 경우도 발생하도록 변경하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1631413847406&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 프로미스 함수
const promise = () =&amp;gt; new Promise((resolve, reject) =&amp;gt; {
  setTimeout(() =&amp;gt; {
    const success = Math.random() &amp;gt; 0.5;
    
    if (success) {
      resolve(&quot;success&quot;);
    } else {
      reject(&quot;failure&quot;);
    }
  }, 1000);
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Math.random() &amp;gt; 0.5를 만족하지 못하면 reject(&quot;failure&quot;)를 실행하고 이것은 비동기작업 수행 실패를 의미합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 만든 myAsync1은 예제를 위한 예제로 정해진 문자열을 반환하는 것이 실용적이지 않습니다. promise 함수가 반환하는 값을 바로 반환하는 myAsync2를 만들겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1631414195546&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const myAsync2 = async () =&amp;gt; {
  return await promise();
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전에 사용했던 다음 코드에는 프로미스 수행 실패에 대한 처리가 되어있지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1631413919179&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;myAsync1().then(res =&amp;gt; console.log(&quot;res: &quot;, res));&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;catch를 추가하면 예외처리를 할 수 있습니다. 이 경우 catch 메소드는 reject()로 전달하는 값을 첫번째 파라미터로 받을 수 있습니다. myAsync2를 이용해 새로운 프로미스 체이닝 코드를 작성하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1631413736266&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;myAsync2().then(res =&amp;gt; console.log(&quot;result: &quot;, res)).catch(err =&amp;gt; console.log(&quot;error: &quot;, err));&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로미스 실행이 성공하면 result: success가 출력되고 실패하면 error: failure가 출력됩니다. 확률은 반반이기 때문에 반복해서 호출하면서 두 가지 경우를 모두 확인할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Javascript</category>
      <category>Async</category>
      <category>await</category>
      <category>javascript</category>
      <category>동기</category>
      <category>비동기</category>
      <category>프로미스</category>
      <author>cocoder16</author>
      <guid isPermaLink="true">https://cocoder16.tistory.com/51</guid>
      <comments>https://cocoder16.tistory.com/51#entry51comment</comments>
      <pubDate>Mon, 22 Nov 2021 08:00:37 +0900</pubDate>
    </item>
    <item>
      <title>Javascript 클래스나 프로토타입으로 OOP 구현하기</title>
      <link>https://cocoder16.tistory.com/50</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;객체 지향 프로그래밍 (Object Oriented Programming)의 3요소&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체 지향 프로그래밍은 컴퓨터 프로그램을 명령어의 목록으로 보는 시각에서 벗어나 객체들의 모임으로 보려는 패러다임입니다. 각 객체들은 서로 독립된 단위로 데이터를 주고받습니다. 객체 지향 프로그래밍의 3요소는 다음과 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1. 캡슐화&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로그램 내에서 어떤 기능을 목적으로 작성된 코드를 모아서 다른 곳(클래스)에서 안 보이게 숨기는 것입니다. 캡슐화를 하면 정보은닉이 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2. 상속&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클래스 사이에 부모와 자식이 존재하여 자식 클래스는 상속받은 부모 클래스의 변수 및 메소드를 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3. 다형성&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;형태가 같은데 다른 기능을 하는 것을 의미합니다. 오버라이딩과 오버로딩 두 가지로 나눌 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오버라이딩: 부모 클래스에서 정의되어 있는 내용을 자식 클래스에서 재정의하여 사용하는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오버로딩: 같은 이름을 가진 메소드를 인자 값의 종류에 따라 다른 로직을 수행하도록 분기하는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;프로토타입으로 객체 지향 프로그래밍 구현&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바스크립트 ES6버전에서 클래스 문법을 제공하기 전까지는 객체 지향 프로그래밍을 하기 위해 프로토타입 문법을 사용해야 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1. 캡슐화&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;캡슐화를 구현하기 위해 필요한 것은 정보은닉입니다. 클래스가 아닌 함수 문법에서 정보은닉을 하기 위해서 생성자 함수에서 this를 사용하지 않고 변수를 함수 스코프에 가두는 방법을 사용합니다. 생성자 함수에서 this를 사용한 변수는 public 멤버가 되어 인스턴스가 자유롭게 접근 가능하지만, 따로 변수를 만들면 인스턴스가 접근할 수 없는 private 멤버가 되어 따로 접근할 수 없게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1631403259744&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var Job = function (job) {
  var _job = job ? job : ''; // private 변수는 _로 시작하기로 네이밍규칙 정합니다. private 키워드가 따로 없기 때문입니다.
  this.job = job; // public 변수
}

var me = new Job(&quot;developer&quot;);
console.log(me._job); // undefined
console.log(me.job); // developer&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생성자 함수를 다음과 같이 같은 함수명을 사용하여 명시할 수 있습니다. 생성자 함수를 반환해줘야하므로 즉시 실행 함수인 익명 함수를 하나 만듭니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1631403473786&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var Job = (function () {
  // 인스턴스 변수
  var _job;
  var _income = 0;
  
  // 생성자 함수
  var Job = function(job) {
    _job = job ? job : '';
    this.job = job;
  }
  
  // 메소드
  Job.prototype.print = function () {
    console.log(_job, this.job, _income);
  }
  
  return Job;
})();

var me = new Job(&quot;developer&quot;);
console.log(me._job); // undefined
console.log(me.job); // developer
console.log(me._income); // undefined
console.log(Job._income); // undefined
me.print(); // developer developer 0&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2. 상속&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 예제에 부모 함수를 만들어 상속을 해보겠습니다. 우선 부모 함수로 사용할 Person 함수를 만듭니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1631405360682&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var Person = (function() {
  var _name;
  var _age;
  
  var Person = function(name, age) {
    _name = name ? name : &quot;&quot;;
    _age = age ? age : &quot;&quot;;
  }
  
  Person.prototype.introduce = function () {
    console.log(&quot;My name is &quot; + _name + &quot; and &quot; + _age + &quot; years old.&quot;);
  }
  
  return Person;
})();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 Job 함수는 Person 함수를 상속받도록 만듭니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1631405629062&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var Job = (function () {
  var _job;
  var _income = 0;
  
  var Job = function(name, age, job) {
    Person.apply(this, arguments); // 부모 멤버 초기화
    _job = job ? job : '';
    this.job = job;
  }
  
  Job.prototype = Object.create(Person.prototype); // 프로토타입 상속
  Job.prototype.constructor = Job; // 생성자 함수 자식 본인 것을 사용하도록 바로 잡기
  
  Job.prototype.print = function () {
    console.log(_job, this.job, _income);
  }
  
  return Job;
})();

var me = new Job(&quot;cocoder&quot;, &quot;N&quot;, &quot;developer&quot;);
me.print(); // developer developer 0
me.introduce(); // My name is cocoder and N years old.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Job으로 만든 me 인스턴스는 부모 함수 Person의 메소드인 introduce를 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3. 다형성&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자식 함수인 Job의 print 메소드를 introduce의 오버라이딩 메소드로 바꿔보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1631407469069&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var Job = (function () {
  var _job;
  var _income = 0;
  
  var Job = function(name, age, job) {
    Person.apply(this, arguments);
    _job = job ? job : '';
    this.job = job;
  }
  
  Job.prototype = Object.create(Person.prototype);
  Job.prototype.constructor = Job;
  
  Job.prototype.introduce = function () {
    console.log(&quot;My job is &quot; + _job + &quot; and income is &quot; + _income, &quot;.&quot;);
  }
  
  return Job;
})();

var me = new Job(&quot;cocoder&quot;, &quot;N&quot;, &quot;developer&quot;);
me.introduce(); // My job is developer and income is 0.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;클래스로 객체 지향 프로그래밍 구현&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;같은 내용을 ES6에서 도입된 클래스 문법으로 구현해보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1631407924688&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Person {
  constructor(name, age) {
    this.name = name; // public
    this.age = age;
  }
  
  introduce() {
    console.log(&quot;My name is &quot; + this.name + &quot; and &quot; + this.age + &quot; years old.&quot;);
  }
}

class Job extends Person { // 상속
  #income = 0; // ES10 문법에서는 #으로 private을 선언할 수 있는 기능이 추가되었다.

  constructor(name, age, job) {
    super(name, age);
    this.job = job;
  }
  
  introduce() { // 오버라이딩
    console.log(&quot;My job is &quot; + this.job + &quot; and income is &quot; + this.#income, &quot;.&quot;);
  }
}

var me = new Job(&quot;cocoder&quot;, &quot;N&quot;, &quot;developer&quot;);
me.introduce(); // My job is developer and income is 0.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ES6 클래스 문법에서는 private을 선언할 수 있는 기능이 없었으나 ES10에서는 &lt;span style=&quot;color: #ee2323;&quot;&gt;#&lt;/span&gt;키워드가 도입되어 private을 선언할 수 있게 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Javascript</category>
      <category>javascript</category>
      <category>OOP</category>
      <category>객체 지향 프로그래밍</category>
      <category>다형성</category>
      <category>상속</category>
      <category>오버라이딩</category>
      <category>캡슐화</category>
      <category>클래스</category>
      <category>프로토타입</category>
      <author>cocoder16</author>
      <guid isPermaLink="true">https://cocoder16.tistory.com/50</guid>
      <comments>https://cocoder16.tistory.com/50#entry50comment</comments>
      <pubDate>Mon, 15 Nov 2021 08:00:48 +0900</pubDate>
    </item>
    <item>
      <title>Javascript ES6 기초 문법 압축 정리</title>
      <link>https://cocoder16.tistory.com/49</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 변수&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ES5에서는 모든 변수는 var 키워드로 선언했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ES6에서는 var 키워드가 아닌 let과 const 키워드를 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;var를 사용할 수 없는 것은 아니지만 let과 const가 훨씬 좋기 때문에 var를 굳이 사용하지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;var vs let, const&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;var키워드로 선언된 변수는 다음과 같은 문제점이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1. 함수 레벨 스코프&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;var는 함수 단위의 스코프를 가지기 때문에 블록 단위의 스코프를 무시합니다. 이는 스코프 경계를 모호하게 하는 문제점을 가집니다. 이런 문제를 잘 보여주는 예시를 하나 보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* 예시 코드&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;codepen&quot; style=&quot;height: 234px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px&amp;nbsp;solid; margin: 1em&amp;nbsp;0; padding: 1em;&quot; data-height=&quot;234&quot; data-theme-id=&quot;dark&quot; data-default-tab=&quot;js,result&quot; data-user=&quot;cocoder16&quot; data-slug-hash=&quot;ZEbaPdv&quot; data-pen-title=&quot;ZEbaPdv&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&lt;span&gt;See&amp;nbsp;the&amp;nbsp;Pen&amp;nbsp;&lt;a href=&quot;&amp;lt;a&amp;nbsp;href=&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://codepen.io/cocoder16/pen/ZEbaPdv&lt;/a&gt;&quot;&amp;gt; &lt;br /&gt;&amp;nbsp;&amp;nbsp;ZEbaPdv&amp;nbsp;by&amp;nbsp;cocoder16&amp;nbsp;(&lt;a href=&quot;&amp;lt;a&amp;nbsp;href=&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://codepen.io/cocoder16&lt;/a&gt;&quot;&amp;gt;@cocoder16) &lt;br /&gt;&amp;nbsp;&amp;nbsp;on&amp;nbsp;&lt;a href=&quot;&amp;lt;a&amp;nbsp;href=&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://codepen.io&lt;/a&gt;&quot;&amp;gt;CodePen.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 예시는 버튼 5개에 onclick 이벤트를 장착하여 버튼을 누르면 몇 번째 버튼인지 출력되는 예제입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;for문으로 각 버튼마다 이벤트 리스너를 장착하고 그때의 i 값을 출력하도록 했기 때문에 0, 1, 2, 3, 4가 출력될 것이라 예상할 수 있습니다. 하지만 실제로 버튼을 클릭해보면 어떤 버튼을 눌러도 5가 출력된다는 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 var 키워드가 함수 레벨 스코프인 것의 문제점이 드러납니다. for문이 종료된 후의 i의 값은 5이고 이 i는 for문 밖에서도 접근 가능한 전역 변수이기 때문에 이벤트 핸들러 함수가 실행될 때 함수 내에는 i가 선언되어있지 않으므로 함수밖에 있는 전역 변수 i = 5 값을 가져오게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;let, const&lt;/span&gt;는 이런 문제점을 보완하여 &lt;b&gt;함수 레벨 스코프&lt;/b&gt;가 아닌&lt;b&gt; 블록 레벨 스코프&lt;/b&gt;를 가지고 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;블록 레벨 스코프는 {} 중괄호를 기준으로 스코프로 갖습니다. 따라서 if문, for문 등 {} 내에서 선언된 변수들은 그 안에서만 유효 범위를 가지게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 위의 예제를 제대로 작동하도록 만들어보겠습니다. var 키워드를 유지해서 위 예제를 제대로 동작하게 하려면 for문 내의 코드를 &lt;b&gt;즉시 실행 함수&lt;/b&gt;로 감싸주는 방법이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* var키워드로 위 예제 개선하기&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1588592904039&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;for (var i = 0; i &amp;lt; 5; i++) {
    (function (i) {
        btns[i].addEventListener('click', function(){
            output.innerHTML = i;
        });
    })(i);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면 실행 당시의 i값을 기억하기에 의도대로 작동할 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 이런 복잡한 방법을 쓰지 않고도 let, const가 가진 블록 레벨 스코프의 이점을 활용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* let, const키워드로 개선하기&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;codepen&quot; style=&quot;height: 232px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px&amp;nbsp;solid; margin: 1em&amp;nbsp;0; padding: 1em;&quot; data-height=&quot;232&quot; data-theme-id=&quot;dark&quot; data-default-tab=&quot;js,result&quot; data-user=&quot;cocoder16&quot; data-slug-hash=&quot;mdeqgVJ&quot; data-pen-title=&quot;mdeqgVJ&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&lt;span&gt;See&amp;nbsp;the&amp;nbsp;Pen&amp;nbsp;&lt;a href=&quot;&amp;lt;a&amp;nbsp;href=&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://codepen.io/cocoder16/pen/mdeqgVJ&lt;/a&gt;&quot;&amp;gt; &lt;br /&gt;&amp;nbsp;&amp;nbsp;mdeqgVJ&amp;nbsp;by&amp;nbsp;cocoder16&amp;nbsp;(&lt;a href=&quot;&amp;lt;a&amp;nbsp;href=&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://codepen.io/cocoder16&lt;/a&gt;&quot;&amp;gt;@cocoder16) &lt;br /&gt;&amp;nbsp;&amp;nbsp;on&amp;nbsp;&lt;a href=&quot;&amp;lt;a&amp;nbsp;href=&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://codepen.io&lt;/a&gt;&quot;&amp;gt;CodePen.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2. var 키워드 생략 및 변수 중복 선언 허용&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;var 키워드를 생략하고도 변수를 선언할 수 있기 때문에 변수 선언에 실수가 있어도 발견하기가 어려우며, 변수를 중복 선언할 수 있어 동일한 변수를 실수로 중복 사용할 수 있기 때문에 문제가 생깁니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 let과 const는 이런 것들을 허용하지 않기 때문에 &lt;b&gt;더 엄격한 체크&lt;/b&gt;가 가능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1588593371966&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;x = 1; //allowed
var x = 1;
var x = 2; //allowed

let y = 1;
let y = 2; //error

const z = 1;
const z = 2; //error&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3. 변수 호이스팅&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;var 키워드로 선언된 변수는 선언된 변수를 선언문 이전에 참조할 수 있습니다. 이것도 코드를 느슨하게 만든다는 문제가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 &lt;b&gt;let과 const키워드로 선언된 변수는 선언문 이전에 참조할 수 없습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1588593633461&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;console.log(x); //undefined
var x = 1;

console.log(y); //error
let y = 1;

console.log(z); //error
const z = 1;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;var의 경우 확인해보면 참조되더라도 할당된 값까지 알 수 있는 것은 아닙니다. x에 1을 할당하였으나 undefined로 출력되는 것을 알 수 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;엄밀히 얘기하면 let과 const도 호이스팅이 일어납니다. 뿐만 아니라 모든 선언 키워드(var, let, const, function, function*, class)는 다 호이스팅됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 let과 const는 var와 다르게 마치 호이스팅이 안 되는 것처럼 참조를 못하게 만들었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것이 가능한 이유는 let, const로 선언한 변수는 var로 선언한 변수와는 다르게 선언 단계와 초기화 단계가 분리되어 진행되기 때문에 선언 단계에 스코프에 변수를 등록은 하지만 초기화 단계는 변수 선언문에 도달했을 때 이루어지기 때문입니다. 따라서 호이스팅이 된 스코프의 시작 지점부터 초기화 시작 지점까지는 변수를 참조할 수 없게 되고 이 구간을 &lt;b&gt;일시적 사각지대(Temporal Dead Zone; TDZ)라고&lt;/b&gt; 부릅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;let vs const&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;let과 const는 var와 비교한 부분에 있어서는 전부 공통점을 가지지만 이 둘 사이에는 두 개의 차이점이 존재합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나는 let은 재할당이 가능하지만 const는 재할당이 불가능하다는 것과&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 하나는 let은 선언과 할당을 분리해도 되지만, const는 반드시 선언과 동시에 할당이 이루어져야 한다는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1588594292732&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// let

let x; // 선언과 할당을 분리해도 된다.
x = 1;
x = 2; // 재할당도 된다.

// const

const y; // error.

const y = 1; // 반드시 선언과 할당을 동시에 해야한다.
y = 2; // error. 재할당 불가능.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;const는 재할당은 금지되지만 객체에 대해서는 좀 헷갈릴 수 있는 부분이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. const는 객체에 대한 참조를 변경하지 못합니다. 이것은 재할당을 의미하기 때문입니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1631264933854&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const obj = { a: 1 };
obj = { b: 2 }; //error&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기는 헷갈리지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 그러나 const는 객체의 프로퍼티를 조작할 수는 있습니다. 새 프로퍼티의 추가, 프로퍼티 삭제, 프로퍼티 값의 변경 모두 가능합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1631264955558&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const obj = { a: 1 };

// 새 프로퍼티 추가
obj.b = 2;

// 프로퍼티 수정
obj.a = 3;

// 프로퍼티 삭제
delete obj.a;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 화살표 함수&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ES6 문법 중 화살표를 이용해 함수를 선언하는 방법은 다음과 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1588596289008&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const func = () =&amp;gt; { };&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에 function 키워드로 함수를 선언하는 방법과 비교했을 때, function키워드를 화살표 =&amp;gt;로 바꿨다고 생각하면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;화살표 함수는 다양한 형태로 사용될 수 있는데 매개변수가 하나일 때 괄호를 생략한다던가 return과 {}를 생략하는 경우도 있습니다. 하나하나 차근차근 보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 그전에 미리 알아야 할 가장 중요한 점은 화살표 함수는 익명 함수로만 사용할 수 있기 때문에 함수 선언문이 아닌 함수 표현식을 사용해야 한다는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* 예시 코드&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1588596421897&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 화살표 함수 선언 (함수표현식)
const x = () =&amp;gt; {
    return 'x';
};

// 매개변수가 하나일 때에는 소괄호 생략 가능하다.
const func = x =&amp;gt; { };
// 여러 개이면 생략 불가능하다.
const func1 = (x, y) =&amp;gt; { };

// 함수 몸체의 코드가 한 줄이고 중괄호를 생략하면 그것은 return값이 된다.
// 아래의 두 함수는 똑같은 함수다.
const func2 = () =&amp;gt; { return 'x' };
const func3 = () =&amp;gt; 'x';

// 단 객체를 리턴할 때에는 소괄호로 감싸줘야한다.
const func4 = () =&amp;gt; ({ x: 'x' });

// 여러 줄의 코드를 쓸 때는 생략없이 사용하면 된다.
const func5 = () =&amp;gt; {
    //코드
    return 'x';
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;화살표 함수 vs function 키워드로 만든 함수&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;이 둘을 언제 써야 할지 구분 짓는 기준은 &lt;span style=&quot;color: #ee2323;&quot;&gt;this&lt;/span&gt;가 바인딩되는 대상 혹은 규칙입니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;function 키워드 함수는 함수를 호출하는 맥락에 따라 this에 바인딩되는 객체가 '&lt;span style=&quot;color: #ee2323;&quot;&gt;동적&lt;/span&gt;'으로 결정됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이에 반해 화살표 함수는 this에 바인딩할 객체가 '&lt;span style=&quot;color: #ee2323;&quot;&gt;정적&lt;/span&gt;'으로 결정됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 개발자는 코드를 짤 때 함수를 만들면서 그 안에 있는 this가 어디에 바인딩될지 확정적으로 예측이 가능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;function 키워드로 만든 함수의 경우 this가 바인딩되는 규칙은 다음 글을 참조하시면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://cocoder16.tistory.com/45&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://cocoder16.tistory.com/45&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1637905104574&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Javascript에서 this가 바인딩되는 대상 정리&quot; data-og-description=&quot;자바스크립트에서 this가 가리키는 대상은 동적으로 변합니다. 자바스크립트에서 this의 바인딩은 동적입니다. 그래서 this가 어떤 객체를 가리키는지 예측이 어렵다는 단점이 있습니다. 이런 단&quot; data-og-host=&quot;cocoder16.tistory.com&quot; data-og-source-url=&quot;https://cocoder16.tistory.com/45&quot; data-og-url=&quot;https://cocoder16.tistory.com/45&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bp6qCT/hyMuEob7oR/DUVThksCJz6d7XWQbx31X1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/iuS5o/hyMuKPrtvi/oHPmzmvSoTqbd9BIccEn90/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/omCGW/hyMuA0nNBe/N0rbyLkhCHORkXoynpfTNK/img.png?width=220&amp;amp;height=220&amp;amp;face=0_0_220_220&quot;&gt;&lt;a href=&quot;https://cocoder16.tistory.com/45&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://cocoder16.tistory.com/45&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bp6qCT/hyMuEob7oR/DUVThksCJz6d7XWQbx31X1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/iuS5o/hyMuKPrtvi/oHPmzmvSoTqbd9BIccEn90/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/omCGW/hyMuA0nNBe/N0rbyLkhCHORkXoynpfTNK/img.png?width=220&amp;amp;height=220&amp;amp;face=0_0_220_220');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Javascript에서 this가 바인딩되는 대상 정리&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;자바스크립트에서 this가 가리키는 대상은 동적으로 변합니다. 자바스크립트에서 this의 바인딩은 동적입니다. 그래서 this가 어떤 객체를 가리키는지 예측이 어렵다는 단점이 있습니다. 이런 단&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;cocoder16.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;function 키워드 함수 내의 this의 바인딩 규칙은 복잡합니다. 경우의 수도 여러 가지이고 동적으로 결정되기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;화살표 함수의 경우 this가 바인딩되는 규칙은 단 한 가지입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바로 this가 있는 스코프의 바로 상위 스코프에서의 this값을 가리킵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1588597353975&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;() =&amp;gt; {
	this
	() =&amp;gt; {
		this // 이 this는 위의 this의 값을 가리킨다.
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 function 키워드 함수에서처럼 내부 함수에서 외부 함수의 this값을 사용하기 위해 다음과 같이 this를 새 변수에 할당해주는 더러워 보이는 코드를 사용하지 않아도 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1588597490137&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function outer(){
  var that = this;
  function inner(){
    console.log(that);
  }
  inner();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 화살표 함수는 function 키워드 함수처럼 call, apply, bind 메소드를 이용해서 this를 변경할 수 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;그저 바로 상위 스코프에서의 this값만을 가리킬 뿐입니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;메소드 선언&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ES5라면 메소드는 다음과 같이 만들 것입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1588597971155&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var person = {
    name: 'Kim',
    introduce: function () {
        console.log(this.name);
    }
};       

person.introduce(); // this는 메소드를 호출한 객체인 person를 가리킨다.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 코드를 실행하면 Kim 이 콘솔에 출력됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 메소드를 화살표 함수로 선언하면 다른 현상이 발생합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1588598072052&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const person_arrow = {
    name: 'Kim',
    introduce: () =&amp;gt; {
        console.log(this.name);
    }
};
        
person_arrow.introduce(); // this는 상위 스코프에서의 this이므로 window 객체를 가리킨다.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;ES6에서 메소드를 만들 때 this를 정적으로 객체에 바인딩하고 싶다면 &lt;span style=&quot;color: #ee2323;&quot;&gt;축약 메소드를&lt;/span&gt; 사용해야 합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1588598167997&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const person = {
    name: 'Kim',
    introduce () {
        console.log(this.name);
    }
};
        
person.introduce(); // this는 정적으로 person 객체에 바인딩 됩니다.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이경우에는 아까처럼 Kim이 출력됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;축약 메소드 또한 화살표 함수처럼 this가 정적으로 바인딩된다는 장점을 가지고 있기 때문에 this를 사용하기 편리합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;화살표 함수를 사용하면 안 되는 경우&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1. 메소드 선언&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 본 것처럼 메소드를 선언할 때에는 화살표 함수를 사용하지 말고 축약메소드를 사용하는 것이 좋습니다. this를 window가 아닌 메소드를 가진 객체에 바인딩할 수 있기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;화살표 함수는 정적으로 this가 바인딩되기 때문에 어디에 바인딩될지 확정적으로 예측할 수 있으므로, this가 바인딩되는 대상이 무엇인지 확인하는 것을 통해 화살표 함수를 사용해야 할 때와 아닐 때를 구별할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2. prototype에 메소드 할당&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;prototype에 메소드를 할당할 때에는 화살표 함수를 사용하지 말고 &lt;b&gt;function 키워드&lt;/b&gt;를 사용해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;화살표함수를 사용하면 this가 prototype 객체에 바인딩되는 게 아니라 상위 스코프 this(window)에 바인딩됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 function 키워드를 사용하여 맥락에 따라 동적으로 호출하는 객체에 this가 바인딩되도록 해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1588598784645&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const me = {
    name: 'cocoder'
};

Object.prototype.introduce = function () {
    console.log(`Hi ${this.name}`);
}
        
me.introduce(); // Hi cocoder&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;템플릿 리터럴 문법&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 예시 코드에서 다음 부분이 생소할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1631266336210&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;console.log(`Hi ${this.name}`);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것은 백틱(`)을 이용해 문자열을 표기하는 방법으로 ES6에 도입된 문법입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문자열 안에 ${}를 이용해 자바스크립트 코드를 삽입할 수 있으며 이 자바스크립트 코드가 반환하는 값을 문자열에 삽입할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3. 생성자 함수 생성&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;화살표 함수는 생성자 함수로 사용할 수 없습니다. prototype 프로퍼티를 가지고 있지 않기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 function 키워드를 이용하여 기존 ES5의 생성자 함수를 사용하거나 ES6에 도입된 class를 이용하여 생성자 함수를 만들어야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div class=&quot;revenue_unit_item adsense  responsive&quot;&gt;&lt;ins class=&quot;adsbygoogle&quot; style=&quot;display: block;&quot; data-ad-host=&quot;ca-host-pub-9691043933427338&quot; data-ad-client=&quot;ca-pub-9807016842906892&quot; data-ad-format=&quot;auto&quot;&gt;&lt;/ins&gt;
&lt;script&gt;(adsbygoogle = window.adsbygoogle || []).push({}); &lt;/script&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. Spread의 문법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spread는 대상을 개별 요소로 분리합니다. 대상은 반드시 iterable 객체이어야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용법은&amp;nbsp;간단합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;iterable 객체 앞에 ... 을 붙이면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1588601937560&quot; class=&quot;javascript&quot; style=&quot;margin: 20px&amp;nbsp;auto&amp;nbsp;0px; display: block; overflow: auto; padding: 15px; color: #383a42; background: #f6f7f8; font-size: 14px; border-radius: 3px; font-family: Menlo,&amp;nbsp;Consolas,&amp;nbsp;Monaco,&amp;nbsp;monospace; border: 1px&amp;nbsp;solid&amp;nbsp;#dddddd; cursor: default; z-index: 1;&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 배열
console.log(...[1,2,3])&amp;nbsp;//&amp;nbsp;1&amp;nbsp;2&amp;nbsp;3

// 문자열
console.log(...&quot;cocoder&quot;);&amp;nbsp;//&amp;nbsp;c&amp;nbsp;o&amp;nbsp;c&amp;nbsp;o&amp;nbsp;d&amp;nbsp;e&amp;nbsp;r&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Spread로 Apply 메소드 대체하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ES5에서는&amp;nbsp;배열의&amp;nbsp;각&amp;nbsp;원소들을&amp;nbsp;함수의&amp;nbsp;인자로&amp;nbsp;전달하기&amp;nbsp;위해서는&amp;nbsp;apply메소드를&amp;nbsp;사용하였습니다.&amp;nbsp;하지만&amp;nbsp;이제는&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spread로&amp;nbsp;apply&amp;nbsp;메소드를&amp;nbsp;대체할&amp;nbsp;수&amp;nbsp;있습니다.&amp;nbsp;이&amp;nbsp;둘을&amp;nbsp;비교해보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* apply 메소드 사용&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1588601937560&quot; class=&quot;javascript&quot; style=&quot;margin: 20px&amp;nbsp;auto&amp;nbsp;0px; display: block; overflow: auto; padding: 15px; color: #383a42; background: #f6f7f8; font-size: 14px; border-radius: 3px; font-family: Menlo,&amp;nbsp;Consolas,&amp;nbsp;Monaco,&amp;nbsp;monospace; border: 1px&amp;nbsp;solid&amp;nbsp;#dddddd; cursor: default; z-index: 1;&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function&amp;nbsp;plus(x,&amp;nbsp;y,&amp;nbsp;z)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;console.log(x+y+z);
}

const&amp;nbsp;arr&amp;nbsp;=&amp;nbsp;[1,&amp;nbsp;2,&amp;nbsp;3];
plus.apply(null,&amp;nbsp;arr);&amp;nbsp;// 6&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* Spread 사용&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1588601937560&quot; class=&quot;javascript&quot; style=&quot;margin: 20px&amp;nbsp;auto&amp;nbsp;0px; display: block; overflow: auto; padding: 15px; color: #383a42; background: #f6f7f8; font-size: 14px; border-radius: 3px; font-family: Menlo,&amp;nbsp;Consolas,&amp;nbsp;Monaco,&amp;nbsp;monospace; border: 1px&amp;nbsp;solid&amp;nbsp;#dddddd; cursor: default; z-index: 1;&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function&amp;nbsp;plus(x,&amp;nbsp;y,&amp;nbsp;z)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;console.log(x + y + z);
}

const&amp;nbsp;arr&amp;nbsp;=&amp;nbsp;[1,&amp;nbsp;2,&amp;nbsp;3];
plus(...arr);&amp;nbsp;// 6&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Spread로 유사 배열 객체를 객체로 만들기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ES5의 문법으로 유사 배열 객체를 배열로 만드는 방법은 다음과 같습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1588606286012&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var array = Array.prototype.slice.call(arguments); // array는 배열, arguments는 유사배열객체&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것을 사용하려면 slice와 call이라는 메소드를 기억해야 하는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spread를 사용하면 메소드를 기억하지 않고도 저것을 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1588606403638&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const array = [...arguments];&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;훨씬 간단해졌습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Spread로 배열 조작하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1) Array.prototype.concat 대체하기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;concat은 배열A 원소 끝에 배열B의 원소들을 붙여서 병합하는 메소드입니다. 이것도 Spread문법으로 쉽게 할 수 있습니다. 아래의 두 식 모두 배열A에 배열B의 원소들을 추가한 새로운 배열을 반환하는 코드입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1588604754651&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var arr1 = [1, 2, 3];
var arr2 = [4, 5];

// 배열A.concat(배열B)
console.log(arr1.concat(arr2)); // [1, 2, 3, 4, 5]

// [ ...배열A, ...배열B ]
console.log([...arr1, ...arr2]); // [1, 2, 3, 4, 5]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2) Array.prototype.push 대체하기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;push는 배열A 끝에 추가할 원소들을 따로따로 넣어서 추가하는 메소드입니다. 이것도 Spread문법으로 가능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1588605452244&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const arr = [1, 2, 3];

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

// Spread
console.log([...arr, 4, 5, 6]); // [1, 2, 3, 4, 5, 6]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3) 배열의 중간에 배열 넣기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;splice의 파라미터 값으로 Spread를 사용해서 배열의 중간에 배열을 삽입해보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1588605688699&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const arr = [1, 2, 5, 6]; // 몸체가 될 배열
const arr1 = [3, 4]; // 삽입될 배열

arr.splice(2, 0, ...arr1);
console.log(arr); // [1, 2, 3, 4, 5, 6]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. Rest 파라미터의 목적과 사용법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Rest 파라미터는 함수에서 매개변수명 앞에 ...을 붙여서 만든 매개변수를 의미합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Rest 파라미터의 목적은 함수에 전달된 파라미터들을 배열로 만드는 것입니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ES5에서 arguments라는 객체가 전달된 파라미터들을 유사 배열 객체로 다루지만 이것은 배열은 아니기 때문에 Array.prototype의 메소드를 사용하려면 유사 배열 객체를 배열로 변환하는 단계를 거쳐야만하는 불편함이 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* 유사 배열 객체를 배열로 만드는 방법&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1588600044084&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var array = Array.prototype.slice.call(arguments);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* arguments를 배열로 변환하여 이용하는 예시&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1588600156296&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function sum() {
    var array = Array.prototype.slice.call(arguments); // 유사 배열 객체를 배열로 만드는 방법
            
    return array.reduce(function (pre, cur) { // 배열 내장 메소드인 reduce를 사용
        return pre + cur;
    });
}

console.log(sum(1, 2, 3, 4)); // 10&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 Rest를 이용하면 Rest파라미터 자체가 배열이 되므로 이런 변환 과정 없이 바로 배열의 내장 메소드를 이용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* 위와 똑같은 함수를 Rest를 이용해 만들기&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1588600343585&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function sum1(...rest) {
    return rest.reduce(function (pre, cur) {
        return pre + cur;
    });
}
        
console.log(sum1(1,2,3,4)); // 10&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Rest는 다른 여러 파라미터들과 같이 사용할 수 있습니다. 그럼에도 Rest에는 한 가지 제약이 존재합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Rest는 반드시 &lt;b&gt;가장 마지막 순서의 파라미터&lt;/b&gt;로 와야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1588603569237&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function (param1, param2, paramN, ...Rest) { }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 편리해진 객체 리터럴 작성법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1) 변수명과 동일한 프로퍼티명으로 사용하기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체 리터럴 안에 프로퍼티명만 적고 값을 할당하지 않아도 그 프로퍼티명과 똑같은 변수가 있다면 그 변수의 값이 프로퍼티에 할당됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* 예시 코드&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1588751035607&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const x = 1; y = 2;
const obj = { x, y };
console.log(obj); // { x: 1, y: 2 }

// 프로퍼티 값 수정
obj.x = 3;
console.log(obj); // { x: 3, y: 2 }
console.log(x); // 1&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2) Spread 문법으로 객체 병합하기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 예제도 마찬가지었지만 리터럴 방식으로 생성했기 때문에 프로퍼티의 값을 수정해도 프로퍼티명과 같은 이름을 가진 변수가 수정되지는 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* 예시 코드&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1588751448977&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;obj1 = { a: 1, b: 2 }
obj2 = { c: 3 }

obj3 = { ...obj1, ...obj2 };
console.log(obj3); // { a: 1, b: 2, c: 3 }

// 프로퍼티 값 수정
obj3.a= 4;
console.log(obj1); // { a: 1, b: 2 }
console.log(obj3); // { a: 4, b: 2, c: 3 }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3) 축약 메소드&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체의 프로퍼티 값이 함수인 경우 그 함수를 메소드라고 합니다. 기존에 메소드를 선언하는 방법은 다음과 같았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1588751553576&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var obj = {
    method: function() {}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 선언한 메소드의 내부에 있는 this는 메소드를 호출한 객체에 동적으로 바인딩됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ES6에서는 this가 정적으로 바인딩되는 화살표 함수라는 것이 생겼으나 이것은 메소드를 만들 때 사용하기엔 적합하지 않습니다. 왜냐하면 화살표함수로 메소드를 만든다면 메소드 내부에 있는 this는 메소드 외부에 있는 this가 가리키는 대상에 바인딩될 것이기 때문에 window객체 등 원치 않은 객체에 바인딩될 확률이 매우 높습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ES6에서는 축약 메소드를 이용해 this를 정적으로 바인딩할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문법은 다음과 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1588751752782&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const person = {
    name: &quot;cocoder&quot;,
    sayName() {
    	console.log(this.name); // this는 메소드가 속한 객체(person)에 정적으로 바인딩.
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;4) 객체 리터럴을 상속하는 새로운 프로퍼티 __proto__&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ES5에서 객체 리터럴을 상속하려면 다음과 같이 해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1588752060148&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var parent = {
    name: 'parent',
    say: function() {
        console.log(name);
    }
};

var child = Object.create(parent); // parent객체를 상속받는 child객체를 생성

child.name = 'child';
child.say(); // child&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면 child객체의 프로토타입 객체는 parent 객체에 바인딩되어 상속을 구현합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ES6에서는 &lt;span style=&quot;color: #ee2323;&quot;&gt;__proto__&lt;/span&gt; 프로퍼티로 리터럴 내부에서 간단하게 부모 객체를 직접 지정할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1588752288804&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const parent = {
  name: 'parent',
  say() {
    console.log(this.name);
  }
};

const child = {
  __proto__: parent,
  name: 'child'
};

child.say(); // child&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. 디스트럭처링 (Destructuring)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;디스트럭처링은 배열이나 객체에서 필요한 요소만 추출하여 할당하고 싶은 경우에 사용할 수 있는 간편하고 유용한 문법입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;배열 디스트럭처링&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배열 디스트럭처링을 하기 위해서 할당 연산자 왼쪽에는 배열 형태의 변수 리스트를 작성합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 변수가 배열 내에서 위치한 인덱스는 할당 연산자 오른쪽에 있는 배열의 인덱스와 짝지어져 그 인덱스에 해당하는 값을 추출하여 변수에 할당합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1588752938895&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const arr = [1, 2, 3];
const [one, two, three] = arr;
console.log(one, two, three); // 1 2 3&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 변수 리스트와 배열의 길이가 일치하지 않는 경우가 있을 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;할당 연산자 왼쪽에 있는 배열의 길이가 더 긴 경우 값을 할당받지 못하는 변수의 값은 undefined가 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1588753104162&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const arr = [1, 2, 3];
const [a, b, c, d] = arr;
console.log(a, b, c, d); // 1 2 3 undefined&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;변수 리스트 중 중간에 비어있는 인덱스가 있는 경우 그 인덱스는 건너뛰고 진행됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1588753211532&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const arr = [1, 2, 3];
const [x, , z] = arr;
console.log(x, z); // 1 3&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;변수 리스트에는 각 변수의 default값을 지정하여 배열로부터 추출된 값이 없을 때 할당할 초기화 값을 지정할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1588753368695&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const arr = [1, 0, 3];
const [a, b = 2, c = 3, d = 4] = arr;
console.log(a, b, c, d); // 1 0 3 4&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 예제에서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;a는 default값이 없고 arr[0]의 값인 1이 할당됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;b는 default값이 2이지만 arr[1]의 값이 있으므로 그 값인 0이 할당됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;c도 b와 마찬가지로 default값 대신 arr[2]의 값인 3이 할당됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;d는 arr[3]이 존재하지 않아 default값인 4가 할당됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;객체 디스트럭처링&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체 디스트럭처링을 하기 위해서 할당 연산자 왼쪽에는 객체 리터럴 형태의 변수 리스트를 작성합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체 형태의 변수 리스트 안에 있는 각 변수는 할당 연산자 오른쪽에 있는 객체에서 같은 프로퍼티명을 가진 프로퍼티의 값을 추출해 할당받습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1588753743094&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const obj = { firstName: 'coder', lastName: 'co' };
const { firstName, middleName, lastName } = obj;
console.log(firstName, middleName, lastName); // coder undefined co&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체에 없는 프로퍼티를 변수 리스트에 변수명으로 써넣으면 그 변수는 undefined가 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중첩 객체를 디스트럭처링 하려면 변수 리스트 안에 변수명을 적을 때 계층구조를 적어줘야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1588753959009&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const person = {
    name: &quot;cocoder&quot;,
    address: {
        zipCode: &quot;00000&quot;,
        city: &quot;Seoul&quot;
    }
};

const { address: { city } } = person;
console.log(city); // Seoul&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 person 객체의 address프로퍼티의 city 프로퍼티의 값을 할당받기 위해 변수 리스트 안에서는 중괄호를 중첩하여 사용했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div class=&quot;revenue_unit_item adsense  responsive&quot;&gt;&lt;ins class=&quot;adsbygoogle&quot; style=&quot;display: block;&quot; data-ad-host=&quot;ca-host-pub-9691043933427338&quot; data-ad-client=&quot;ca-pub-9807016842906892&quot; data-ad-format=&quot;auto&quot;&gt;&lt;/ins&gt;
&lt;script&gt;(adsbygoogle = window.adsbygoogle || []).push({}); &lt;/script&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;7. Class&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ES5에는 없는 클래스 문법이 도입되었습니다. 클래스를 정의하고 인스턴스를 생성 하는 방법은 다음과 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* 예시 코드&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1631350449395&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Person {
  // 생성자
  constructor(name) {
    this.name = name;
  }

  introduce() {
    console.log(`My name is ${this.name}.`);
  }
}

// 인스턴스 생성
const me = new Person(&quot;cocoder&quot;);
me.introduce(); // My name is cocoder.

console.log(me instanceof Person); // true&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;static 키워드를 사용한 메소드는 정적 메소드입니다. 정적 메소드는 인스턴스에서 호출할 수 없고 클래스에서 직접 호출할 수 있습니다. 그래서 정적 메소드 내부에서 this는 인스턴스가 아닌 클래스에 바인딩됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* 예시 코드&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1631350684275&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Math {
  constructor(prop) {
    this.prop = prop;
  }

  static plus(a, b) {
    console.log(this); // 클래스에 바인딩된다.
    return a + b;
  }
}

console.log(Math.plus(3, 4)); // 7

const math = new Math();
console.log(math.plus()); // Uncaught TypeError: math.plus is not a function&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클래스의 상속은 extends 키워드로 구현할 수 있습니다. 부모 클래스와 자식 클래스를 만들어 자식 클래스에 extends 키워드를 붙입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* 예시 코드&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1631351012162&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 부모 클래스
class Person {
  // 생성자
  constructor(name) {
    this.name = name;
  }

  introduce() {
    console.log(`My name is ${this.name}.`);
  }
}

// Person을 상속받은 자식 클래스
class Worker extends Person {
  constructor(name, job) {
    super(name);
    this.job = job;
  }

  // introduce 메소드는 부모 클래스의 메소드를 오버라이딩한다.
  introduce() {
    super.introduce(); // super키워드를 이용해 오버라이딩되지 않은 부모 클래스의 메소드를 사용한다.
    console.log(`My job is ${this.job}.`);
  }
}

// 인스턴스 생성
const me = new Worker(&quot;cocoder&quot;, &quot;developer&quot;);

// 오버라이딩된 메소드 실행
me.introduce(); // My name is cocoder. My job is developer.

// me는 Worker 클래스의 인스턴스이다.
console.log(me instanceof Worker); // true
// me는 Person 클래스의 인스턴스이다.
console.log(me instanceof Person); // true&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상속은 extends로 구현하고 오버라이딩은 부모 클래스에서 사용하는 메소드와 같은 메소드명을 사용함으로써 구현할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자식 클래스의 constructor 내부에서의 super메소드는 부모 클래스의 constructor를 호출합니다. 따라서 생성된 인스턴스는 자식 클래스의 인스턴스이기도 하지만 부모 클래스의 인스턴스이기도 합니다. 또한 super키워드는 부모 클래스의 필드나 메소드를 참조할 수 있습니다. 위 예제에서 자식 클래스의 introduce 메소드 안에 있는 super.introduce()는 부모 클래스의 introduce를 실행합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;8. Promise&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Promise는 비동기 작업을 보기 편한 코드로 처리할 수 있는 기능입니다. 사용법은 다음과 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Promise 생성자 함수의 첫번째 파라미터로 비동기적으로 실행되는 함수를 넣어서 인스턴스를 만듭니다. 그러면 비동기 코드가 실행된 결과를 가진 인스턴스가 생성됩니다. 이 인스턴스 객체는 then, catch메소드를 사용하여 반환된 값에 대한 후속 처리를 보기 편한 코드로 작성할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1631352950042&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const promiseInstance = new Promise((resolve, reject) =&amp;gt; {
  setTimeout(() =&amp;gt; {
    const success = Math.random() &amp;gt; 0.5;
    
    if (success) {
      resolve(&quot;success&quot;);
    } else {
      reject(&quot;failure&quot;);
    }
  }, 1000);
});

promiseInstance.then(res =&amp;gt; {
  console.log(res);
}).catch(err =&amp;gt; {
  console.log(err);
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비동기 코드를 사용하는 자바스크립트 라이브러리들은 대부분 프로미스로 구현되어 있습니다. 프로미스는 반환값을 사용하는 인터페이스가 편리합니다. resolve()로 넘겨준 파라미터는 프로미스의 then메소드의 파라미터로 받을 수 있고, reject()로 넘겨준 파라미터는 프로미스의 catch메소드의 파라미터로 받을 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비동기 작업을 반복적으로 사용하기 위해 재사용가능한 구조로 만드려면 프로미스 생성자 함수를 반환하는 함수를 하나 만듭니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1631354942348&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const promise = () =&amp;gt; new Promise((resolve, reject) =&amp;gt; {
  setTimeout(() =&amp;gt; {
    const success = Math.random() &amp;gt; 0.5;
    
    if (success) {
      resolve(&quot;success&quot;);
    } else {
      reject(&quot;failure&quot;);
    }
  }, 1000);
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 예제와 다르게 이미 만들어진 인스턴스가 아니라 생성자 함수를 반복적으로 호출할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1631355088770&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;promise().then(res =&amp;gt; {
  console.log(res);
}).catch(err =&amp;gt; {
  console.log(err);
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Javascript</category>
      <category>const</category>
      <category>Destructuring</category>
      <category>ES6 문법</category>
      <category>let</category>
      <category>rest</category>
      <category>spread</category>
      <category>클래스</category>
      <category>프로미스</category>
      <category>호이스팅</category>
      <category>화살표 함수</category>
      <author>cocoder16</author>
      <guid isPermaLink="true">https://cocoder16.tistory.com/49</guid>
      <comments>https://cocoder16.tistory.com/49#entry49comment</comments>
      <pubDate>Mon, 8 Nov 2021 08:00:26 +0900</pubDate>
    </item>
    <item>
      <title>[JavaScript] 창과 요소의 크기, 너비값, 높이값, 좌표 구하기</title>
      <link>https://cocoder16.tistory.com/48</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;viewport의 크기 구하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뷰포트는 &lt;span style=&quot;color: #000000;&quot;&gt;화면상의 화상표시 영역을 뜻합니다.&lt;span&gt; 사용자가 디스플레이를 볼 때 켜져 있는 브라우저에서 실시간으로 보고 있는 영역을 뜻합니다.&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span&gt;뷰포트의 너비와 높이값은 각각 window객체의&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;window.innerWidth&lt;/span&gt;와 &lt;span style=&quot;color: #ee2323;&quot;&gt;window.innerHeight&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span&gt;로 받아올 수 있습니다. &lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span&gt;단위는 px입니다.&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹페이지에서 내용이 길어지면 스크롤이 생깁니다. 스크롤에는 상하스크롤과 좌우 스크롤이 있습니다. 보통 상하 스크롤은 뷰포트에서 가장 우측에, 좌우 스크롤은 뷰포트에서 가장 아래쪽에 생깁니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span&gt;뷰포트에서 우측에 있는 상하 스크롤의 영역을 뺀 너비값은 &lt;span style=&quot;color: #ee2323;&quot;&gt;document.documentElement.clientWidth&lt;/span&gt;,&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span&gt;뷰포트에서 밑에 있는 좌우 스크롤의 영역을 뺀 높이값은&amp;nbsp;&lt;span style=&quot;color: #ee2323;&quot;&gt;document.documentElement.clientHeight&lt;/span&gt;로 구합니다. &lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span&gt;마찬가지로 단위는 px입니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금 바로 여기서 개발자도구의 콘솔 창을 키신 후 지금 소개한 코드들을 하나씩 실행해보시면 값이 출력될 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1631194622319&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;window.innerWidth
window.innerHeight
document.documentElement.clientWidth
document.documentElement.clientHeight&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;element의 크기 구하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 엘리먼트 요소를 값으로 가지는 변수 x를 가정하겠습니다. element의 너비와 높이는 각각&amp;nbsp;&lt;span style=&quot;color: #f3c000;&quot;&gt;x&lt;/span&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;.style.width&lt;/span&gt;와 x&lt;span style=&quot;color: #ee2323;&quot;&gt;.style.height&lt;/span&gt;으로 구합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* 예시 코드&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1587205747459&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var video = document.querySelector('video');
console.log(video.style.width, video.style.height);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;viewport를 기준으로 element의 크기, 좌표 구하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;viewport를 기준으로 element의 크기와 오프셋를 구할 때에는 &lt;span style=&quot;color: #ee2323;&quot;&gt;getBoundingClientRect&lt;/span&gt; 메소드를 사용합니다. 메소드를 호출하면 너비, 높이, 오프셋을 반환합니다. 반환된 객체에서 너비와 높이는 &lt;span style=&quot;color: #ee2323;&quot;&gt;width, height&lt;/span&gt;프로퍼티로, 좌표는 &lt;span style=&quot;color: #ee2323;&quot;&gt;top, bottom, left, right&lt;/span&gt;로 얻을 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단위는 px입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* 예시 코드&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1587201905433&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var video = document.querySelector('video');
console.log(video.getBoundingClientRect().width);
console.log(video.getBoundingClientRect().height);
console.log(video.getBoundingClientRect().top);
console.log(video.getBoundingClientRect().bottom);
console.log(video.getBoundingClientRect().left);
console.log(video.getBoundingClientRect().right);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;document를 기준으로 element 좌표 구하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;내용이 화면보다 길어 스크롤이 있는 경우, viewport에서 벗어나서 보이지 않는 document 영역의 길이는 &lt;span style=&quot;color: #ee2323;&quot;&gt;window.pageXOffset&lt;/span&gt;과 &lt;span style=&quot;color: #ee2323;&quot;&gt;window.pageYOffset&lt;/span&gt;으로 구할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;이 값에 위에서 말한 x.getBoundingClientRect().top이나 x.getBoundingClientRect().left를 더하면 document를 기준으로 한 element의 좌표값을 구할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;&lt;span&gt;* 예시 코드&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1587204892204&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// document를 기준으로하는 엘리먼트의 top offset값
console.log(window.pageYOffset + target.getBoundingClientRect().top);
// document를 기준으로하는 엘리먼트의 left offset값
console.log(window.pageXOffset + target.getBoundingClientRect().left);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Javascript</category>
      <category>Document</category>
      <category>Element</category>
      <category>OFFSET</category>
      <category>viewport</category>
      <category>구하는법</category>
      <category>너비</category>
      <category>높이</category>
      <category>위치</category>
      <category>자바스크립트</category>
      <category>좌표</category>
      <author>cocoder16</author>
      <guid isPermaLink="true">https://cocoder16.tistory.com/48</guid>
      <comments>https://cocoder16.tistory.com/48#entry48comment</comments>
      <pubDate>Mon, 1 Nov 2021 08:00:58 +0900</pubDate>
    </item>
    <item>
      <title>[웹 개발] 스크롤 방향 판별 및 스크롤 내릴 때에만 헤더 감추기</title>
      <link>https://cocoder16.tistory.com/47</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;pageYOffset으로 스크롤 방향 판별하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스크롤 방향 판별을 하기 위해 사용할 수 있는 여러 내장 프로퍼티들이 있지만 그중에 크로스 브라우징 문제가 없는 pageYOffset을 이용한 방법을 소개하겠습니다. 이것도 IE8이하에서는 작동하지 않지만, 저 정도의 구버전은 굳이 챙기지 않도록 하겠습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저, 스크롤이 발생 중인 상태를 탐지하는 이벤트 리스너가 필요합니다. 이에 따라 필요한 이벤트는 &quot;scroll&quot; 입니다. 만들어야 하는 코드는 &quot;scroll&quot; 이벤트를 등록하는 이벤트 리스너와 이벤트가 발생할 때마다 호출될 이벤트 핸들러(콜백 함수)입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* 예시 코드&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1586707258561&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var prevScrollTop = 0;
document.addEventListener(&quot;scroll&quot;, function(){
    var nextScrollTop = window.pageYOffset || 0;
    if (nextScrollTop &amp;gt; prevScrollTop){
        // 스크롤 내리는 중에 실행코드
    } else if (nextScrollTop &amp;lt; prevScrollTop) {
        // 스크롤 올리는 중에 실행코드
    }
    prevScrollTop = nextScrollTop;
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;pageYOffset 이 0일 때가 가장 최상단 위치입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스크롤 이벤트 발생하면 nextScrollTop에 현재 pageYOffset 값을 넣고 기존에 pageYOffset값을 넣어두었던 prevScrollTop과 대소 비교하여 스크롤 방향을 판별합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;스크롤 내릴 때에만 헤더 감추기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 예시코드에서 내리는 중에는 헤더를 감추는 코드를 실행하고 올리는 중에는 다시 헤더가 나타나는 코드를 실행하면 됩니다. 그런데 우리에게는 Javascript 말고 CSS라는 또 다른 무기가 있습니다. 헤더가 나타나고 사라지는 애니메이션은 CSS로 간편하게 처리하려고 합니다. 이것을 위해서 자바스크립트에서는 헤더의 class명만 변경해주면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* Javascript 예시 코드&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1586707659970&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var header = document.querySelector('header');
var headerMoving = function(direction){
    if (direction === &quot;up&quot;){
        header.className = '';
    } else if (direction === &quot;down&quot;){
        header.className = 'scrollDown';
    }
};
var prevScrollTop = 0;
document.addEventListener(&quot;scroll&quot;, function(){ 
    var nextScrollTop = window.pageYOffset || 0; 
    if (nextScrollTop &amp;gt; prevScrollTop){
        headerMoving(&quot;down&quot;);
    } else if (nextScrollTop &amp;lt; prevScrollTop){
        headerMoving(&quot;up&quot;);
    }
    prevScrollTop = nextScrollTop;
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTML, CSS까지 추가하여 완성된 코드는 다음과 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CSS 애니메이션은 transform과 transition으로 처리했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;codepen&quot; style=&quot;height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px&amp;nbsp;solid; margin: 1em&amp;nbsp;0; padding: 1em;&quot; data-height=&quot;480&quot; data-theme-id=&quot;dark&quot; data-default-tab=&quot;js,result&quot; data-user=&quot;cocoder16&quot; data-slug-hash=&quot;WNQvGKN&quot; data-pen-title=&quot;WNQvGKN&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&lt;span&gt;See&amp;nbsp;the&amp;nbsp;Pen&amp;nbsp;&lt;a href=&quot;&amp;lt;a&amp;nbsp;href=&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://codepen.io/cocoder16/pen/WNQvGKN&lt;/a&gt;&quot;&amp;gt; &lt;br /&gt;&amp;nbsp;&amp;nbsp;WNQvGKN&amp;nbsp;by&amp;nbsp;cocoder16&amp;nbsp;(&lt;a href=&quot;&amp;lt;a&amp;nbsp;href=&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://codepen.io/cocoder16&lt;/a&gt;&quot;&amp;gt;@cocoder16) &lt;br /&gt;&amp;nbsp;&amp;nbsp;on&amp;nbsp;&lt;a href=&quot;&amp;lt;a&amp;nbsp;href=&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://codepen.io&lt;/a&gt;&quot;&amp;gt;CodePen.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Javascript</category>
      <category>javascript</category>
      <category>scroll event</category>
      <category>스크롤</category>
      <category>스크롤 방향</category>
      <category>스크롤 방향 탐지</category>
      <category>자바스크립트</category>
      <author>cocoder16</author>
      <guid isPermaLink="true">https://cocoder16.tistory.com/47</guid>
      <comments>https://cocoder16.tistory.com/47#entry47comment</comments>
      <pubDate>Mon, 25 Oct 2021 08:00:13 +0900</pubDate>
    </item>
    <item>
      <title>[Javascript] 프로토타입, 프로토타입 체이닝에 대한 글</title>
      <link>https://cocoder16.tistory.com/46</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;프로토타입(prototype)이란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바스크립트에서 모든 객체는 자신의 부모 역할을 하는 객체와 연결되어 있습니다. (가장 최상위 부모인 객체는 브라우저에서는 window, node.js에서는 global입니다.) 자바스크립트에서는 프로토타입으로 객체지향의 상속 개념을 구현하는데 이를 통해 부모 객체의 프로퍼티를 자식 객체가 사용할 수 있습니다. 프로토타입은 이런 식으로 사용되며 모든 객체에게 존재합니다. 어떤 객체가 접근할 수 있는 부모 객체를 &lt;b&gt;프로토타입 객체(prototype object) 혹은 그냥 프로토타입이라고&lt;/b&gt; 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;객체 리터럴로 객체를 생성하는 경우 그 객체의 프로토타입 객체는 Object.prototype 객체이다.&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체 리터럴 방식으로 생성한 모든 객체의 프로토타입 객체는 Object.prototype입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 말은 객체 리터럴 방식으로 생성한 모든 객체가 Object.prototype에 접근 가능하다는 것을 의미합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MDN을 보면 메소드명에 대하여 항상 prototype이 들어간 풀네임을 명시해놓는데 예를 들면 이런 식입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1631178644093&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;apply() 가 아닌 Function.prototype.apply()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 이유는 apply가 Function.prototype에 할당된 메소드이기 때문입니다. 따라서 모든 함수는 apply메소드를 사용할 수 있습니다. 모든 함수가 왜 사용할 수 있는지 더 명확하게 이해하려면 아래에 있는 프로토타입 체이닝 개념까지 봐야 확실하게 이해가 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Function.prototype과 마찬가지로 Object.prototype에는 모든 객체가 호출 가능한 내장 프로퍼티(메소드 포함)들이 포함되어 있습니다. 이걸로 인해 모든 객체는 자신이 직접 가지고 있는 프로퍼티가 아니더라도 Object.prototype에 속한 프로퍼티에 접근하여 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;생성자 함수로 객체를 생성하는 경우 그 객체의 프로토타입 객체는 생성자함수.prototype 객체이다.&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서 객체를 리터럴 방식으로 생성하는 경우 그 객체의 프로토타입 객체는 Object.prototype 이라고 했습니다. 그러면 리터럴 방식으로 생성한 객체들은 Object.prototype의 속성이나 메소드를 상속받습니다. 이건 경우의 수가 한 가지밖에 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 생성자 함수로 객체를 생성하는 경우에는 그 객체의 프로토타입 객체가 '생성자함수명.prototype'이 됩니다. 또 이 생성자함수.prototype의 프로토타입 객체는 Object.prototype입니다. 이렇게 생성자 함수를 사용하여 객체를 생성하면 Object.prototype까지 프로토타입 링크가 두 번 연결됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1631178927879&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function Factory(name){
   this.name = name;
}

var item = new Factory(&quot;coding&quot;);

console.log(item); // Factory&amp;nbsp;{name: &quot;coding&quot;}
console.log(item.__proto__); // Factory.prototype
console.log(item.__proto__.__proto__); // Object.prototype&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;프로토타입 체인 (prototype chain)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 언급했듯이 객체는 자신의 프로토타입 객체의 프로퍼티에 접근할 수 있습니다. 그냥 막 프로토타입 객체를 찾아가는건 아니고, 객체에서 프로퍼티를 접근하려 보니 자기 자신에게 그러한 프로퍼티가 없는 경우 프로토타입 객체에 혹시 그것이 있나 찾으러 가는 것입니다. 그리고 자신의 프로토타입 객체에도 그 프로퍼티가 없으면 더 상위의 프로토타입 객체에 찾으러 가서 있으면 불러옵니다. 이런 과정을 거쳐서 최종적으로는 모든 객체의 부모 객체인 Object.prototype에게까지 접근을 하게 되는데 이런 일련의 접근 프로세스를 prototype chianing이라고 부릅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Factory 생성자 함수로 item 인스턴스를 만들고 Factory.prototype, Object.prototype으로 체이닝하는 과정을 예제로 보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1631179514621&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function Factory(name){
    this.name = name;
}

// prototype 객체에 프로퍼티를 할당 할 수 있다.
Factory.prototype.introduce = function() {
    return &quot;Hi! I am &quot; + this.name + &quot;.&quot;;
}

Object.prototype.bye = function() {
    return &quot;Bye~&quot;;
}

var item = new Factory(&quot;coding&quot;);

// item의 프로퍼티 접근
console.log(item.name); // &quot;coding&quot;

// 프로토타입 체이닝 1. item에서 못찾은 메소드이므로 Factory.prototype에 접근
console.log(item.introduce()); // &quot;Hi! I am coding.&quot;

// 프로토타입 체이닝 2. item과 Factory.prototype에서 못찾은 메소드이므로 Function.prototype에 접근
console.log(item.bye()); // &quot;Bye~&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로토타입은 처음에 이해하기 매우 어렵습니다. 다음 참고자료들을 추천합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://poiemaweb.com/js-prototype&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://poiemaweb.com/js-prototype&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1631182242933&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Prototype | PoiemaWeb&quot; data-og-description=&quot;자바스크립트의 모든 객체는 자신의 부모 역할을 하는 객체와 연결되어 있다. 그리고 이것은 마치 객체 지향의 상속 개념과 같이 부모 객체의 프로퍼티 또는 메소드를 상속받아 사용할 수 있게 &quot; data-og-host=&quot;poiemaweb.com&quot; data-og-source-url=&quot;https://poiemaweb.com/js-prototype&quot; data-og-url=&quot;https://poiemaweb.com/js-prototype&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dIXNIE/hyLyTsyYwu/8kkTO9MpA4GJxEqHOXtpwk/img.jpg?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400,https://scrap.kakaocdn.net/dn/cGg3eU/hyLyVjBRzt/A63Y2FVzmAZWYSsfnkGLmk/img.jpg?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400&quot;&gt;&lt;a href=&quot;https://poiemaweb.com/js-prototype&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://poiemaweb.com/js-prototype&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dIXNIE/hyLyTsyYwu/8kkTO9MpA4GJxEqHOXtpwk/img.jpg?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400,https://scrap.kakaocdn.net/dn/cGg3eU/hyLyVjBRzt/A63Y2FVzmAZWYSsfnkGLmk/img.jpg?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Prototype | PoiemaWeb&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;자바스크립트의 모든 객체는 자신의 부모 역할을 하는 객체와 연결되어 있다. 그리고 이것은 마치 객체 지향의 상속 개념과 같이 부모 객체의 프로퍼티 또는 메소드를 상속받아 사용할 수 있게&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;poiemaweb.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;책: 인사이드 자바스크립트 - 송형주, 고현준 지음 | 한빛미디어&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Javascript</category>
      <category>javascript</category>
      <category>prototype</category>
      <category>__proto__</category>
      <category>프로토타입</category>
      <category>프로토타입 체이닝</category>
      <author>cocoder16</author>
      <guid isPermaLink="true">https://cocoder16.tistory.com/46</guid>
      <comments>https://cocoder16.tistory.com/46#entry46comment</comments>
      <pubDate>Mon, 18 Oct 2021 08:00:23 +0900</pubDate>
    </item>
    <item>
      <title>Javascript에서 this가 바인딩되는 대상 정리</title>
      <link>https://cocoder16.tistory.com/45</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;자바스크립트에서 this가 가리키는 대상은 동적으로 변합니다.&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바스크립트에서 this의 바인딩은 동적입니다. 그래서 this가 어떤 객체를 가리키는지 예측이 어렵다는 단점이 있습니다. 이런 단점을 극복하려면 this를 통제된 패턴으로만 사용하여 바인딩되는 대상을 예측 가능하도록 코드를 작성하는 것이 좋습니다. React.js와 같은 라이브러리를 사용하면 this를 사용할 용도가 명확하게 정해져 있어 통제 가능한 상태의 this만을 사용하기도 합니다. 어쨌든 바닐라 자바스크립트를 기준으로 this의 동적 바인딩에 대해서 정리해보는 시간도 한번 가지면 좋을 것 같아서 포스팅을 시작하게 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;this의 바인딩 규칙&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1. 메소드에서 사용된 this는 자신을 호출한 객체를 가리킨다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* 예시 코드&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1585811087825&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var cocoder = {
    name : &quot;cocoder&quot;
};
cocoder.method = function(){
    return &quot;My name is &quot; + this.name;
}

console.log(cocoder.method); // My name is cocoder&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2. 함수를 호출할 때 this는 자신을 호출한 객체가 window객체이므로 this는 window 객체를 가리킨다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수가 객체의 프로퍼티 값인 경우 그 프로퍼티를 메소드라고 합니다. 반면 특정 객체의 프로퍼티가 아닌 그냥 함수는 엄밀히 말하면 전역 객체인 window의 메소드가 됩니다. 따라서 this는 window를 가리키게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* 예시 코드&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1585811508187&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function func(){
  if(window === this){
  	console.log(&quot;window === this&quot;);
  } 
}
func(); // window === this
window.func(); // 사실은 전역변수 window의 메소드이다.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고로 브라우저에서는 window가 전역 객체이지만 node.js에서는 global이 전역 객체입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3. 내부 함수에서의 this는 전역 객체 window를 가리킨다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메소드 안에서 내부 함수에서의 this도 자기를 호출한 객체를 가리키지 않고 window를 가리킵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* 예시 코드&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1585811820092&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;cocoder.outer = function outer(){
  function inner(){
    console.log(this);
  }
  inner();
}
cocoder.outer(); // window&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;4. 내부 함수에서 외부 함수의 this값을 사용하려면 외부 함수의 this를 다른 변수에 할당한 다음 그 변수로 접근해야 한다&lt;/b&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;말이 조금 복잡한데 메소드의 내부 함수에서도 외부 함수에서의 this, 즉, 메소드를 호출한 객체를 this로 바인딩하여 사용하고 싶다면 외부 함수에서 this를 다른 변수에 할당해서 그 변수를 사용하라는 의미입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* 예시 코드&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1585811979349&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;cocoder.outer = function outer(){
  var that = this;
  function inner(){
    console.log(that);
  }
  inner();
}
cocoder.outer(); // cocoder&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;5. 생성자 함수로 객체를 생성한 경우 생성자 함수 내의 this는 객체가 생성되었을 때 그 객체를 가리킨다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* 예시 코드&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1585812840319&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var thisVal = null;
function constructor(){
	thisVal = this;
}
constructor();
console.log(thisVal === window); // 함수의 this는 전역객체 window를 가리킨다. 따라서 출력결과는 true

var obj = new constructor(); // 생성자 내부에서의 this는 그 객체를 가리킨다. 즉, obj에 this가 바인딩됨.
console.log(thisVal === obj); // true&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;6. apply, call을 이용하면 this의 값을 마음대로 명시할 수 있다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;apply와 call은 this에 바인딩할 객체를 명시하여 함수를 실행시킬 수 있는 Function.prototype 객체의 메소드입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;apply와 call은 용도가 똑같은 메소드입니다. 단지 사용법이 약간 다를 뿐입니다. 둘의 차이는 파라미터에 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* apply와 call 함수의 파라미터&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1585733726841&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;apply(thisArg, argArray);
call(thisArg, arg, arg, ...);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 메소드 모두 첫 번째 파라미터는 호출한 함수 내부에서 사용한 this에 바인딩할 객체를 받습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그다음 파라미터로는 호출한 함수에 전달될 값들을 받습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;apply는 전달될 값으로서 배열을 넣는 것이고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;call은 전달될 값으로서 배열을 넘기는 게 아니라 각각의 파라미터마다 따로따로 값으로 넣습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들면 인자로 1, 2, 3을 전달할 때 두 메소드에서 다음과 같이 사용됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* apply와 call의 파라미터 사용 차이점&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1585733910237&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function func() {};

func.apply(thisArg, [1,2,3]);
func.call(thisArg, 1, 2, 3);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;this를 명시하지 않을 수도 있는데 그러려면 null을 넣어주면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1585734002176&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function sum(a,b){
    return a+b;
}
console.log(sum.apply(null, [1, 2])); // 3&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Javascript</category>
      <category>apply</category>
      <category>Call</category>
      <category>javascript</category>
      <category>That</category>
      <category>this</category>
      <category>window</category>
      <category>동적 바인딩</category>
      <category>바인딩</category>
      <author>cocoder16</author>
      <guid isPermaLink="true">https://cocoder16.tistory.com/45</guid>
      <comments>https://cocoder16.tistory.com/45#entry45comment</comments>
      <pubDate>Tue, 12 Oct 2021 14:21:59 +0900</pubDate>
    </item>
    <item>
      <title>Javascript에서 즉시 실행 함수 사용하기</title>
      <link>https://cocoder16.tistory.com/44</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;즉시 실행 함수 (IIFE, Immediately Invoked Function Expression)란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 번만 실행하고 다시는 실행할 필요가 없는 함수들이 있습니다. 이럴 때 꼭 알맞은 기법이 있는데 바로 즉시 실행 함수를 만들어서 사용하는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉시 실행 함수는 함수를 정의하자마자 실행하게 만들어진 함수입니다. 뿐만 아니라 딱 1회만 실행되고 다시는 호출할 수 없도록 만들어졌습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;즉시 실행 함수의 특징&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;nbsp; &amp;nbsp; 1. 정의되자마자 바로 실행된다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;nbsp; &amp;nbsp; 2. 익명 함수를 사용한다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp; 3&lt;/span&gt;. 딱 1회만 실행되고 다시는 실행하지 않는다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;nbsp; &amp;nbsp; 4. 초기화코드나 모듈화 패턴을 만들 때 유용하게 사용된다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 즉시 실행 함수는 정의되자마자 바로 실행하도록 만들어져 있습니다. 그러므로 명백한 의도를 가지고 의도한 스코프, 의도한 코드 라인에 정확하게 선언 및 실행해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 즉시 실행 함수는 익명 함수를 이용해 만드는 것이 보통입니다. 정의하자마자 실행하고 휘발시킬 것이기 때문에 즉시 실행 함수에 함수명을 지어줄 필요가 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 즉시 실행 함수는 단 한 번만 실행하는 함수입니다. 일단 1회 실행되고 나면 해당 즉시 실행 함수에는 더 이상 볼일이 없어야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 3번과 같은 특성 때문에 즉시 실행 함수는 초기화를 하는 모듈을 만들 때 자주 사용됩니다. 초기화를 위해 1회만 실행하면 역할을 다하는 함수의 경우입니다. 모듈은 결합도를 낮춰서 외부 코드와 떼어놓고 독자적으로 여기저기서 사용하기 쉽도록 만드는 것이 핵심입니다. 즉시 실행 함수 안에 있는 변수들은 함수 외부에서 접근할 수 없기 때문에 전역 네임스페이스를 더럽히지 않습니다. 그래서 즉시 실행 함수는 라이브러리나 프레임워크 소스들에서 많이 사용됩니다. jQuery 같은 유명한 라이브러리도 즉시 실행 함수로 코드 전체를 감싸버린 형태로 만들어져 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;즉시 실행 함수 사용법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉시 실행 함수 코드는 다음과 같이 만들 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1585743683711&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;(function (params){
    // 실행코드
})(args);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 이것을 응용한 모듈 패턴은 다음과 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1585743930576&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var module = (function (){
  // private code
  return {
  	// public property or method
  }
})();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클로저와 즉시 실행 함수를 이용한 모듈 패턴입니다. 리턴 값을 변수에 저장하여 private code로 만든 프로퍼티나 메소드를 외부에서 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Javascript</category>
      <category>IIFE</category>
      <category>Immediately Invoked Function Expression</category>
      <category>javascript</category>
      <category>private</category>
      <category>모듈</category>
      <category>익명함수</category>
      <category>즉시 실행 함수</category>
      <category>초기화</category>
      <category>클로저</category>
      <author>cocoder16</author>
      <guid isPermaLink="true">https://cocoder16.tistory.com/44</guid>
      <comments>https://cocoder16.tistory.com/44#entry44comment</comments>
      <pubDate>Mon, 4 Oct 2021 08:00:18 +0900</pubDate>
    </item>
    <item>
      <title>Javascript 함수에 대해 파헤치기</title>
      <link>https://cocoder16.tistory.com/43</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;자바스크립트에서 함수 생성하는 방법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바스크립트에서 함수를 생성하는 방법은 크게 세 가지가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;1. 함수 선언문 방식&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1585636770240&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function func(){ }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;2. 함수 표현식 방식&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1585636789457&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var func = function(){ };&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;3. 생성자 함수 이용하기&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1585636830240&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var func = new Function([parameter, ...], 'code');&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1번과 2번은 함수 리터럴 방식을 이용한 방법이고 3번은 생성자 함수를 이용한 방법입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 중에 3번 생성자 함수를 이용하는 방법은 딱봐도 불편해 보입니다. 사실 함수 생성은 리터럴 방식을 사용하고 3번은 거의 사용하지 않습니다. 그래서 리터럴 방식을 이용한 1번과 2번에 대해서만 사용법을 소개하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;함수 호출하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 함수를 생성하고나면 함수를 호출할 수 있습니다. 함수는 재사용성을 강화시켜줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;A라는 기능을 담당하고 있는 어떤 함수를 일단 생성하고 나면, A 기능을 하는 코드를 또 작성할 필요 없이, 함수를 호출하기만 하면 A 기능이 실행되기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 호출할 함수를 생성해보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1631020920197&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 1. 함수 선언문 방식
function hello() {
  alert(&quot;hello!&quot;);
}

// 2. 함수 표현식 방식
var hello = function() {
  alert(&quot;hello!&quot;);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;hello!&quot;라는 문자열을 alert에 출력하는 기능을 수행하는 hello라는 함수를 만들었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수 선언문, 함수 표현식 방식 둘 중 하나만 선택해서 사용하면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수를 호출하는 방법은 함수명뒤에 ()를 붙이는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1585640231296&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;hello();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 함수를 호출하면 미리 정의되어있는 함수의 코드가 실행됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;파라미터 parameter (매개변수 혹은 인자)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수를 선언할 때 괄호 () 안에는 받을 수 있는 파라미터들을 정의할 수 있습니다. 이것은 함수를 호출할 때마다 값으로 넣어줄 수 있으며, 중괄호 {} 코드 블록 내에서 그 값을 변수로 사용할 수 있습니다. 그래서 이것을 &lt;span style=&quot;color: #ee2323;&quot;&gt;매개변수&lt;/span&gt;라고도 부릅니다. 예시로 다시한번 보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1585640573903&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function hello (name){
    alert(&quot;hello! &quot; + name + &quot;^^&quot;);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아까와 같이 &quot;hello&quot;라는 문자열을 alert에 출력하는 hello라는 함수를 정의했습니다. 아까와 다른 점은 name이라는 매개변수를 정의했다는 것입니다. 이제 함수를 호출할 때 name이라는 매개변수에 값을 넣어주면 그 값을 이용해 함수 안에 있는 코드가 실행됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수를 선언한 뒤 호출할 때 매개변수 값으로 'cocoder'를 넣어보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행결과로 alert 메시지는 &quot;hello! cocoder^^&quot;라고 출력될 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1631022502673&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;hello(&quot;cocoder&quot;);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수를 호출할 때마다 매개변수에 넣고 싶은 값을 넣을 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1631022637049&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;hello(&quot;cocoder&quot;); // hello! cocoder^^
hello(&quot;choco&quot;); // hello! choco^^
hello(&quot;javascript&quot;); // hello! javascript^^&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;return 사용법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수는 {} 코드 블록 안에서 어떤 값을 반환하여 함수 밖으로 배출할 수도 있습니다. 이것은 return 키워드로 할 수 있습니다. 함수는 코드 실행 중 return 키워드를 만나서 값을 반환하는 데에 성공하면 {} 코드 블록 안에 더 남아 있는 코드들을 굳이 실행하지 않고 함수 실행을 종료합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파라미터 a, b를 받아서 둘에 대해 + 연산을 수행한 결과를 반환하는 plus라는 함수를 만들었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 함수를 실행하여 반환받은 값을 console.log()를 통해 확인할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1585640854566&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function plus(a,b){
    return a+b;
}
console.log(plus(1,2)); // 3&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수를 실행하여 반환받은 값은 변수에 저장해 활용할 수도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1631023243539&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var result = plus(1,2);
console.log(result); // 3&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;함수 선언문 방식과 함수 표현식 방식의 차이&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;함수 호이스팅&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;함수 선언문 방식은 함수 표현식 방식과 다르게 함수 호이스팅이 발생한다&lt;/b&gt;는 차이가 있습니다. 함수 호이스팅이란 함수를 선언한 위치보다 더 윗줄에 있는 코드에서는 아직 함수가 선언되지 않았음에도 불구하고 아랫줄에 있는 함수를 호출하는 것이 가능한 현상을 의미합니다. 함수 호이스팅은 함수를 사용하기 전에 선언해야한다는 규칙을 무시하는 현상입니다. 따라서 코드의 구조를 엉성하게 만들 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* 함수 호이스팅 예시 코드&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1585639931048&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 함수 선언문 방식 - 함수호이스팅
func1();
function func1(){
    alert(&quot;함수 선언문 방식&quot;);
}
func1();

// 함수 표현식 방식
// func2(); -&amp;gt;&amp;gt; error
var func2 = function(){
    alert(&quot;함수 표현식 방식&quot;);
};
func2();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수 func1는 함수 선언문 방식으로 생성했기 때문에 함수 호이스팅이 가능합니다. 따라서 함수를 선언한 코드보다 더 윗줄에서도 함수호출이 가능합니다. 따라서 예시 코드에서 func1()는 총 두번 호출이 됩니다. 반면 함수 func2는 함수표현식 방식으로 생성했기 때문에 함수 호이스팅이 발생하지 않습니다. 따라서 함수를 선언한 위치보다 더 윗줄에서 func2()를 호출할 때 func2는 정의되지 않은 함수라는 에러가 발생합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div class=&quot;revenue_unit_item adsense  responsive&quot;&gt;&lt;ins class=&quot;adsbygoogle&quot; style=&quot;display: block;&quot; data-ad-host=&quot;ca-host-pub-9691043933427338&quot; data-ad-client=&quot;ca-pub-9807016842906892&quot; data-ad-format=&quot;auto&quot;&gt;&lt;/ins&gt;
&lt;script&gt;(adsbygoogle = window.adsbygoogle || []).push({}); &lt;/script&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;일급 객체로서의 자바스크립트 함수의 5가지 특징&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바스크립트에서 함수는 일급 객체 (first class object)라고 합니다. 그래서 자바스크립트에서 함수는 다른 언어에서의 함수보다 조금 더 독특한 특징들을 가지고 있습니다. 이번 글에서는 자바스크립트에서 함수가 일급 객체이기 때문에 가지는 특징 5가지에 대해 살펴보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1. 리터럴에 의해 생성 가능하다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수를 리터럴로 생성하는 두 가지 방법이 있습니다. 하나는 함수 선언문을 이용하는 방식이고 다른 하나는 함수 표현식을 이용하는 방식입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* 함수 선언문으로 함수 생성&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1585726371935&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function func() {}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* 함수 표현식으로 함수 생성&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1585726400376&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var func = function(){};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2. 변수나 배열의 원소, 객체의 프로퍼티 등에 할당 가능&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바스크립트에서 함수는 값이기 때문에 변수, 배열의 원소, 객체의 프로퍼티 등에 할당할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* 예시 코드&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1585727359690&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var func = function(){
    console.log(&quot;this is func&quot;);
}

// 변수에 할당
var func1 = func;

// 배열의 원소에 할당
var arr = [];
arr[0] = func;

// 객체의 프로퍼티에 할당
var obj = {};
obj.func2 = func;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3. 함수의 인자로 전달될 수 있다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바스크립트에서 함수는 값이기 때문에 함수의 인자로 전달이 가능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* 예시 코드&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1585728080417&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var callbackFunction = function(){
    console.log(&quot;callback!&quot;);
};

var greeting = function(greet, callback){
    console.log(greet);
    callback();
};

greeting(&quot;Hi.&quot;, callbackFunction); // 두번째 파라미터에 함수를 전달&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;4. 함수의 리턴 값으로 사용될 수 있다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바스크립트에서 함수는 값이기 때문에 리턴 값으로도 사용될 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* 예시 코드&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1585728535141&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var detection = {};
detection.foundEnemy = (function (){
    var foundEnemy = function(){
        alert(&quot;get away!!!&quot;);
    }
    return foundEnemy; // foundEnemy 라는 함수를 리턴값으로 사용할 수 있다.
})();

detection.foundEnemy();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;5. 동적으로 속성을 생성, 수정, 삭제할 수 있다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바스크립트에서 함수는 객체이기 때문에 객체와 마찬가지로 함수에 프로퍼티를 동적으로 생성하고 수정하고 삭제하는 작업을 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* 예시 코드&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1585729303126&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var func = function(){
    return &quot;abc&quot;;
}

// 함수에 property 추가
func.pro = &quot;abcd&quot;;
console.log(func.pro); // abcd
console.log(func().pro); // undefined, func()이 반환하는 &quot;abc&quot;는 string 타입으로 프로퍼티를 가지고 있지 않습니다.

// 함수의 property값을 수정(할당)
func.pro = &quot;가나다라&quot;;
console.log(func.pro); // 가나다라

// 함수의 property를 삭제
delete func.pro;
console.log(func.pro); // undefined&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Javascript</category>
      <category>javascript</category>
      <category>parameter</category>
      <category>return</category>
      <category>일급 객체</category>
      <category>파라미터</category>
      <category>함수</category>
      <category>함수선언문</category>
      <category>함수표현식</category>
      <category>호이스팅</category>
      <author>cocoder16</author>
      <guid isPermaLink="true">https://cocoder16.tistory.com/43</guid>
      <comments>https://cocoder16.tistory.com/43#entry43comment</comments>
      <pubDate>Mon, 27 Sep 2021 08:00:07 +0900</pubDate>
    </item>
    <item>
      <title>Javascript 배열 제대로 다루는 방법</title>
      <link>https://cocoder16.tistory.com/42</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;배열 생성하는 방법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바스크립트에서 배열을 생성하는 방법은 두 가지가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1. 배열 리터럴로 생성하는 방법&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1585618393832&quot; class=&quot;javascript&quot; style=&quot;display: block; overflow: auto; padding: 15px; color: #383a42; background: #f6f7f8; font-size: 14px; border-radius: 3px; font-family: Menlo, Consolas, Monaco, monospace; border: 1px solid #dddddd; margin: 20px auto 0px; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var abc = [&quot;a&quot;, &quot;b&quot;, &quot;c&quot;];&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[] 안에 각 원소를 , 로 구분해서 값을 직접 명시해주어 생성하는 방법입니다. abc 배열은 &quot;a&quot;, &quot;b&quot;, &quot;c&quot; 원소를 각각 0, 1, 2번 인덱스에 가지게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2. Array() 생성자 함수로 생성하는 방법&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1585618484631&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// ex1
var abc = new Array(3);

// ex2
var abc = new Array(&quot;a&quot;, &quot;b&quot;, &quot;c&quot;);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ex1처럼 배열을 선언하면 배열의 각 원소들의 값은 undefined입니다. Array() 안에 쓰인 숫자 3은 배열의 길이를 나타냅니다. 즉, abc는 길이가 3인 빈 배열이 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ex2처럼 배열을 선언하면 () 안에 작성한 값들이 배열의 각 인덱스에 각 원소 값으로 저장됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;배열에 원소 추가하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원소를 추가하는 메소드는 push, concat, unshift 등이 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #f3c000;&quot;&gt;push()&lt;/span&gt; : 배열의 끝에 원소를 하나 추가합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1585618972699&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var arr = [&quot;a&quot;,&quot;b&quot;,&quot;c&quot;];
arr.push(&quot;d&quot;); 
console.log(arr); // [&quot;a&quot;,&quot;b&quot;,&quot;c&quot;,&quot;d&quot;]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #f3c000;&quot;&gt;unshift()&lt;/span&gt; : 배열의 첫번째 인덱스에 원소를 넣고 나머지 원소들을 한 자리씩 뒤로 밉니다.&lt;/p&gt;
&lt;pre id=&quot;code_1585619896941&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var arr = [&quot;a&quot;, &quot;b&quot;, &quot;c&quot;];
arr.unshift(&quot;d&quot;);
console.log(arr); // [&quot;d&quot;, &quot;a&quot;, &quot;b&quot;, &quot;c&quot;]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #f3c000;&quot;&gt; 인덱스&lt;/span&gt;를 사용해서 추가해줄 수도 있습니다. 단, 이렇게 사용하는 것은 좋지 못합니다. 해당 인덱스에 이미 값을 가지고 있는 경우에는 추가가 아니라 수정이 발생하기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1585619987955&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var arr = [1,2,3];
arr[3] = 4; // arr = [1,2,3,4]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배열도 참조 데이터 타입이기에 &lt;a href=&quot;https://cocoder16.tistory.com/41&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;지난 포스팅&lt;/a&gt;에서 작성한 참조 데이터 타입의 특성을 그대로 따릅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배열을 값으로 가지는 변수 A를 생성하고 변수 B에 변수 A를 할당하면 변수 A와 변수 B는 같은 데이터를 참조하게 됩니다. 그래서 둘 중 하나만 수정해도 변수 A, B 둘 다 변경이 발생합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* 배열 B에 배열 A를 할당하면 둘은 같은 데이터를 참조한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1630202376094&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 배열 A 생성
var arrA = [1,2,3];

// 배열 B는 배열 A를 참조
var arrB = arrA;

// 배열 A를 수정하면 배열 B도 수정된다.
arrA.push(4);
console.log(arrA); // [1,2,3,4]
console.log(arrB); // [1,2,3,4]

// 배열 B를 수정하면 배열 A도 수정된다.
arrB.unshift(&quot;a&quot;);
console.log(arrA); // [&quot;a&quot;,1,2,3,4]
console.log(arrB); // [&quot;a&quot;,1,2,3,4]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배열의 내장메소드 중에는 사용하면 push, unshift처럼 해당 배열이 참조하고 있는 데이터 값이 변경되는 메소드들이 있는 반면 concat처럼 해당 배열이 참조하는 데이터는 변경하지 않고 새로운 메모리에 값을 가진 배열을 생성하여 반환하는 메소드들도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #f3c000;&quot;&gt;concat()&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 배열의 끝에 복수 개의 원소를 추가합니다. 복수 개의 원소는 배열에 담아서 전달합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1630201826036&quot; class=&quot;javascript&quot; style=&quot;margin: 20px auto 0px; display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; cursor: default; z-index: 1;&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var arrA = [&quot;a&quot;, &quot;b&quot;];
var arrC = arrA.concat([&quot;c&quot;, &quot;d&quot;]);
console.log(arrA); // [&quot;a&quot;,&quot;b&quot;]
console.log(arrC); // [&quot;a&quot;,&quot;b&quot;,&quot;c&quot;,&quot;d&quot;]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;arrA.concat()을 실행한 결과 arrA에는 원소가 추가되지 않았습니다. 하지만 concat을 실행하고 반환한 값을 할당한 arrC는 arrA에 &quot;c&quot;, &quot;d&quot;가 추가된 배열을 가지고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배열의 내장메소드들을 사용할 때에는 해당 메소드가 배열이 참조하고 있는 값을 변경하는 메소드인지, 아니면 변경하지 않고 새로운 메모리에 값을 가진 배열을 반환하는지 알고 사용해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;배열의 원소 수정하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #f3c000;&quot;&gt;splice(인덱스, 삭제할 개수, 그 자리에 넣을 값들)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1585620262799&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var arr = [1, 2, 3];
arr.splice(0, 1, &quot;a&quot;); 
console.log(arr); // [&quot;a&quot;, 2, 3]
arr.splice(1, 2, &quot;b&quot;, &quot;c&quot;); 
console.log(arr); // [&quot;a&quot;, &quot;b&quot;, &quot;c&quot;]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 번째 splice()를 사용할 때, 0번째 인덱스의 원소인 1을 지워주고 그 자리에 'a'를 넣어서 ['a', 2, 3] 이 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 번째 splice()를 사용할 때, 1번째 인덱스부터 원소 2개를 지워서 2와 3이 지워지고 그 자리에 'b'와 'c'를 넣어서 ['a', 'b', 'c']가 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 번째 파라미터의 값으로 0을 넣어주고 세 번째 파라미터의 값으로 어떤 값들을 넣어주면, 첫번째 파라미터 값인 인덱스부터 새로운 원소들을 넣어주고 그 뒤로 원래 원소들은 밀려나게 만듭니다. &lt;b&gt;즉, 중간에 새 원소를 삽입하는 효과가 생깁니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* 두번째 파라미터에 0을 넣어 원소 삭제 없이 배열의 중간에 새 원소 삽입하기&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1585620521539&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var arr = [1, 2, 3, 4];
arr.splice(2, 0, &quot;a&quot;, &quot;b&quot;); 
console.log(arr); // [1, 2, &quot;a&quot;, &quot;b&quot;, 3, 4]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;배열의 원소 삭제하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;splice()는 세번째 파라미터의 값을 넣어주지 않는 방법으로 삭제 기능만 수행할 수도 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1585620720456&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var arr = [1, 2, 3, 4, 5];
arr.splice(0, 1); 
console.log(arr); // [2, 3, 4, 5]
arr.splice(1, 2); 
console.log(arr); // [2, 5]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 메소드로도 제거 기능을 수행할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #f3c000;&quot;&gt;shift()&lt;/span&gt; : 첫 번째 원소를 제거하고 그 원소 값을 반환합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1585620789799&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var arr = [1, 2, 3];
var result = arr.shift(); 
console.log(result); // 1
console.log(arr); // [2, 3]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #f3c000;&quot;&gt;pop()&lt;/span&gt; : 마지막 원소를 제거하고 그 원소 값을 반환합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1585620812237&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var arr = [1, 2, 3];
var result = arr.pop(); 
console.log(result); // 3
console.log(arr); // [1, 2]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #f3c000;&quot;&gt;slice(start, end)&lt;/span&gt; :&amp;nbsp; start 인덱스부터 end -1 인덱스까지 원소를 추출한 새로운 배열을 반환합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참조하고 있는 값을 변경하지 않고 새 배열을 반환하는 경우입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1630204049791&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var arrA = [1, 2, 3, 4];
var arrB = arrA.slice(1, 3);
console.log(arrA); // [1, 2, 3, 4]
console.log(arrB); // [2, 3]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Javascript</category>
      <category>javascript</category>
      <category>Slice</category>
      <category>Splice</category>
      <category>내장 메소드</category>
      <category>배열</category>
      <category>원소 삭제</category>
      <category>원소 수정</category>
      <category>원소 추가</category>
      <category>인덱스</category>
      <category>자바스크립트</category>
      <author>cocoder16</author>
      <guid isPermaLink="true">https://cocoder16.tistory.com/42</guid>
      <comments>https://cocoder16.tistory.com/42#entry42comment</comments>
      <pubDate>Mon, 20 Sep 2021 08:00:35 +0900</pubDate>
    </item>
    <item>
      <title>Javascript 객체 제대로 다루는 방법</title>
      <link>https://cocoder16.tistory.com/41</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;자바스크립트에서 객체를 생성하는 3가지 방법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;font-size: 1.25em;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp; &amp;nbsp; 1. 객체 리터럴 이용하기&lt;/p&gt;
&lt;p style=&quot;font-size: 1.25em;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp; &amp;nbsp; 2. Object() 객체 생성자 함수 이용하기&lt;/p&gt;
&lt;p style=&quot;font-size: 1.25em;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp; &amp;nbsp; 3. 생성자 함수 이용하기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바스크립트에서 객체를 생성하는 방법은 위와 같이 3가지 방법이 있습니다. 다음은 각 방법별로 실제 코드를 어떻게 쓰는지 보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 객체 리터럴 이용하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체 리터럴이란 객체의 property, value를 명시하여 생성하는 표기법입니다. 객체 리터럴로 객체를 만드는 방법은 다음과 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1585384531945&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var obj = {
    property1 : value1,
    property2 : value2
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체에는 속성(property)과 속성에 담는 값(value)을 담을 수 있습니다. 이 값에는 원시 데이터 타입인 숫자, 문자열, 불리언, null 등도 담을 수 있고 참조 데이터 타입인 객체(배열, 함수, 객체)도 담을 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 값을 담을 수 있다고 볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. Object() 객체 생성자 함수 이용하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1585384668028&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var obj = new Object();
console.log(obj); // {}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단하게 Object를 생성하여 변수에 담았습니다. 위와 같이 생성하면 빈 객체가 생성됩니다. 이후 property를 CRUD(생성, 읽기, 수정, 삭제) 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 객체를 생성할 때 처음부터 초기값을 넣어주기 위해 리터럴을 함께 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1630157952796&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var obj = new Object({ property: &quot;abc&quot; });
console.log(obj); // {property: &quot;abc&quot;}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 생성자 함수 이용하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생성자 함수로 객체를 만드는 방법은 조금 복잡합니다. 생성자 함수를 따로 만들고 그것을 이용해 객체를 만드는 방법이기 때문입니다. 생성자 함수란 객체를 만드는 역할을 하기 위해 만들어진 함수입니다. 생성자 함수를 이용하면 공장에서 물건 찍듯이 쉽게 원하는 객체를 반복해서 만들 수 있습니다. 예시를 보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1585384945676&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function Person(){};
var me = new Person();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Person()이라는 생성자 함수를 이용해 me라는 빈 객체를 만들었습니다. 그런데 빈 객체를 만들기 위해 이렇게 복잡한 방법을 쓰지는 않겠죠. 생성자 함수를 이용하면 다음과 같은 일이 가능해집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1585385367034&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//생성자함수 Person()
var Person = function (name, region){
    this.name = name;
    this.region = region;
    this.introduce = &quot;My name is &quot; + this.name + &quot; and My region is &quot; + this.region + &quot;.&quot;;
}

//객체 생성하기
var me = new Person(&quot;cocoder&quot;, &quot;Seoul&quot;);
var you = new Person(&quot;yoon&quot;, &quot;Seoul&quot;);

console.log(me);
console.log(you);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* 결과&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;d6aacwimg.png&quot; data-origin-width=&quot;836&quot; data-origin-height=&quot;49&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mMVz6/btrdnkmkMdr/TRcoezry9mNWxRb1NsK1sk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mMVz6/btrdnkmkMdr/TRcoezry9mNWxRb1NsK1sk/img.png&quot; data-alt=&quot;위에서부터 me, you의 출력 결과&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mMVz6/btrdnkmkMdr/TRcoezry9mNWxRb1NsK1sk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmMVz6%2FbtrdnkmkMdr%2FTRcoezry9mNWxRb1NsK1sk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;836&quot; height=&quot;49&quot; data-filename=&quot;d6aacwimg.png&quot; data-origin-width=&quot;836&quot; data-origin-height=&quot;49&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;위에서부터 me, you의 출력 결과&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;introduce 프로퍼티&lt;span style=&quot;color: #9d9d9d;&quot;&gt;(이제부터 property의 한글 발음 그대로 쓰겠습니다.)&lt;/span&gt;의 값에 해당하는 자기소개 기능의 텍스트 문장을 반복해서 작성할 필요가 없습니다. 같은 기능을 하지만 서로 다른 객체를 생성자 함수를 이용해&amp;nbsp;간단하게 반복적으로 만들어내고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생성자 함수명의 첫 글자는 대문자로 사용하는 것이 좋습니다. 일종의 규칙(convention)인데 이것을 지킴으로써 코드의 가독성을 더 높일 수 있습니다. 2번 방법에서 사용한 Object함수 역시 생성자 함수입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;생성자 함수 내의 this가 가리키는 대상&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생성자 함수 내에 있는 this는 생성자 함수로 생성한 객체를 가리킵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 me 객체를 만들 때 생성자 함수 Person() 내의 this는 me 객체에 바인딩되고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;you 객체를 만들 때 생성자 함수 Person() 내의 this는 you 객체에 바인딩됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;리터럴 방식과 생성자 함수 방식의 프로토타입 차이&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체 리터럴을 이용해서 생성한 객체의 프로토타입 객체는 Object.prototype입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 생성자 함수를 이용해서 생성한 객체의 프로토타입 객체는 생성자 함수의 prototype 객체인 Person.prototype입니다. 다시 말해 생성자 함수로 만든 인스턴스 me와 you의 프로토타입 객체는 Person.prototype입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 Person.prototype의 프로토타입 객체는 Object.prototype입니다. 이것을 프로토타입 체이닝이라고 합니다. 모든 객체는 프로토타입 체이닝을 하면 결국 마지막에 Object.prototype에 도달합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;this와 prototype에 대해서는 따로 또 자세히 다루는 포스팅을 할 기회가 있으면 좋겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;객체의 속성(property)에 대해 CRUD 하는 방법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체를 생성하는 방법을 알았으므로 이제 생성된 객체의 프로퍼티를 어떻게 추가하고(Create), 읽어오고(Read), 수정하고(Update), 삭제(Delete)할 수 있는지에 대해서 다뤄보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 객체를 생성해줍니다. name이라는 프로퍼티와 값을 함께 생성했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1585396007064&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var cocoder = {
    name : 'cocoder';
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1. 객체 프로퍼티 읽기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체 프로퍼티에 접근할 수 있는 두 가지 방법이 있습니다. 바로 []표기법과 .표기법입니다. 위에서 객체 cocoder를 만들고 name 프로퍼티를 만들었는데, name 프로퍼티를 두 가지 방법으로 접근해보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* []표기법&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1585396348597&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;console.log(cocoder[&quot;name&quot;]); // &quot;cocoder&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* .표기법&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1585396374436&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;console.log(cocoder.name); // &quot;cocoder&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체에 없는 프로퍼티를 참조하면 undefined가 뜹니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1585396896832&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;console.log(cocoder.hobby); // undefined&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2. 객체 프로퍼티 갱신&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마치 변수에 새 값을 할당해주듯이 값을 넣어주면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* []표기법&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1585396993152&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;cocoder[&quot;name&quot;] = &quot;me&quot;;
console.log(cocoder.name); // &quot;me&quot;
console.log(cocoder[&quot;name&quot;]); // &quot;me&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* .표기법&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1585397013774&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;cocoder.name = &quot;me&quot;;
console.log(cocoder.name); // &quot;me&quot;
console.log(cocoder[&quot;name&quot;]); // &quot;me&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3. 객체 프로퍼티 추가&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에 객체를 선언했을 때 있는 프로퍼티만 갱신할 수 있는 것이 아닙니다. 객체는 한번 선언한 이후로 새로운 프로퍼티를 계속 추가할 수 있습니다. 방법은 프로퍼티 갱신 때와 같습니다. []표기법이나 .표기법으로 새 프로퍼티명을 만들어서 값을 넣어주면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* []표기법&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1585397238318&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;cocoder[&quot;hobby&quot;] = &quot;piano&quot;;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* .표기법&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1585397314772&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;cocoder.hobby = &quot;piano&quot;;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;4. 객체 프로퍼티 삭제&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로퍼티 삭제는 delete연산자로 하면 됩니다. delete연산자는 프로퍼티만 삭제할 뿐 객체 자체를 삭제하지는 못합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;delete 연산자로 hobby 프로퍼티를 삭제해보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* hobby 프로퍼티 삭제&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1585397648526&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;delete cocoder.hobby;
console.log(cocoder.hobby); // undefined&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div class=&quot;revenue_unit_item adsense  responsive&quot;&gt;&lt;ins class=&quot;adsbygoogle&quot; style=&quot;display: block;&quot; data-ad-host=&quot;ca-host-pub-9691043933427338&quot; data-ad-client=&quot;ca-pub-9807016842906892&quot; data-ad-format=&quot;auto&quot;&gt;&lt;/ins&gt;
&lt;script&gt;(adsbygoogle = window.adsbygoogle || []).push({}); &lt;/script&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;자바스크립트의 객체는 참조 데이터 타입입니다.&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체가 참조 데이터 타입이기에 가지는 특징들에 대해 정리를 해보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체 A를 정의한다는 것은 객체 A가 독자적으로 데이터를 가지고 있다는 의미가 아니라 어느 동떨어진 외딴곳에 데이터가 형성되었고 객체 A가 그 데이터를 참조한다는 의미입니다. 이를 그림으로 표현하면 다음과 같습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1585407712066&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var objA = {
    property: value;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;bIjicZimg.png&quot; data-origin-width=&quot;401&quot; data-origin-height=&quot;176&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pM9mb/btrdniotPdR/L3fryW9kTJrOiFZzeUakS0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pM9mb/btrdniotPdR/L3fryW9kTJrOiFZzeUakS0/img.png&quot; data-alt=&quot;객체 A를 정의하면 실제 데이터를 가지고 있는 익명 객체를 참조한다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pM9mb/btrdniotPdR/L3fryW9kTJrOiFZzeUakS0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpM9mb%2FbtrdniotPdR%2FL3fryW9kTJrOiFZzeUakS0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;401&quot; height=&quot;176&quot; data-filename=&quot;bIjicZimg.png&quot; data-origin-width=&quot;401&quot; data-origin-height=&quot;176&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;객체 A를 정의하면 실제 데이터를 가지고 있는 익명 객체를 참조한다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체 A를 정의한 이후, 객체 B에 객체 A를 할당하면 객체 B는 객체 A를 참조합니다. 그리고 객체 A를 참조하는 것은 이중 종속으로 객체 A가 참조하고 있는 가상의 익명 객체를 참조하는 것과 같습니다. 이를 그림으로 표현하면 다음과 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1630155100548&quot; class=&quot;javascript&quot; style=&quot;display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; margin: 20px auto 0px; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var objA = {
    property: value
};
var objB = objA;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;b4uF70img.png&quot; data-origin-width=&quot;640&quot; data-origin-height=&quot;400&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AhJ2Y/btrdo9ruNSm/KNzZpdI6kYBsVX0n941pD0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AhJ2Y/btrdo9ruNSm/KNzZpdI6kYBsVX0n941pD0/img.png&quot; data-alt=&quot;객체B는 객체A를 참조하므로 객체B가 참조하고 있는 실제 데이터는 익명 객체가 가지고 있는 데이터다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AhJ2Y/btrdo9ruNSm/KNzZpdI6kYBsVX0n941pD0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAhJ2Y%2Fbtrdo9ruNSm%2FKNzZpdI6kYBsVX0n941pD0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;640&quot; height=&quot;400&quot; data-filename=&quot;b4uF70img.png&quot; data-origin-width=&quot;640&quot; data-origin-height=&quot;400&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;객체B는 객체A를 참조하므로 객체B가 참조하고 있는 실제 데이터는 익명 객체가 가지고 있는 데이터다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체 A와 객체 B는 동일한 데이터를 참조하고 있습니다. 이런 경우 객체 A나 객체 B 중 하나만 수정해도 (새로 생성하는 것이 아닌 있는 객체를 수정하는 것을 의미합니다.) 그것은 그들이 참조하고 있는 익명 객체를 수정한 것과 같기 때문에 객체 A와 객체 B 둘 다 수정한 것과 같은 효과가 발생합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* 객체 A와 객체 B가 같은 객체를 참조할 때 해당 객체의 property를 수정하는 경우&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1585409348784&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//동일한 익명 객체를 참조하는 객체 A와 객체 B 생성
var a = {
    val : 1
};
var b = a;
console.log(a.val); //1
console.log(b.val); //1

//객체 A를 수정하면 객체 B도 같이 수정된다.
a.val = 2;
console.log(a.val); //2
console.log(b.val); //2

//객체 B를 수정하면 객체 A도 같이 수정된다.
b.val = 3;
console.log(a.val); //3
console.log(b.val); //3&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에는 실질적으로 같은 값을 갖는 데이터를 각각 다른 리터럴로 객체 A와 객체 B를 생성해보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* 객체 A와 객체 B를 각자 리터럴을 이용해 동일한 데이터를 넣어서 생성&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1585409522018&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var objA = {
    val : 1
};
var objB = {
    val : 1
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 경우에는 객체 A가 참조하는 익명 객체와 객체 B가 참조하는 익명 객체는 별개의 것입니다. 왜냐하면 각자 따로 리터럴을 작성했기 때문입니다. 두 리터럴이 똑같이 생긴 것과 별개로, 저 두 데이터는 서로 다른 메모리에 서로 다른 익명 객체에 저장됩니다. 따라서 객체 A를 수정해도 객체 B는 변하지 않고 객체 B를 수정해도 객체 A는 변하지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* 객체 A와 객체 B를 각각 수정해보기&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1585409707818&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 객체 A를 수정해도 객체 A는 변하지 않는다.
objA.val = 2;
console.log(objA.val); //2
console.log(objB.val); //1

// 객체 B를 수정해도 객체 A는 변하지 않는다.
objB.val = 3;
console.log(objA.val); //2
console.log(objB.val); //3&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Javascript</category>
      <category>javacript</category>
      <category>Object</category>
      <category>property</category>
      <category>객체</category>
      <category>객체 리터럴</category>
      <category>생성자 함수</category>
      <category>자바스크립트</category>
      <category>프로퍼티</category>
      <author>cocoder16</author>
      <guid isPermaLink="true">https://cocoder16.tistory.com/41</guid>
      <comments>https://cocoder16.tistory.com/41#entry41comment</comments>
      <pubDate>Mon, 13 Sep 2021 08:00:18 +0900</pubDate>
    </item>
    <item>
      <title>Javascript ES5 입문용 기초 문법 압축 정리</title>
      <link>https://cocoder16.tistory.com/40</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;개요&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글은 Javascript에 입문할 때 반드시 알아야 하는 기초 문법들을 간략하게 정리해놨습니다. 하지만 설명이 다소 콤팩트하고 배경 설명이 부족하기 때문에 정말로 아무것도 모르는 상태에서 처음 보시는 분들은 이 글이 아닌 더 잘 만들어진 메이저 자료들을 보시기를 적극 추천해드립니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 데이터 타입&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1) 자바스크립트의 데이터 타입은 아주 크게 두 가지로 나눌 수 있습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;원시 데이터 타입 vs 참조 데이터 타입&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;원시 데이터 타입은 총 5가지가 있습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;숫자(number), 문자열(string), 논리 자료형(boolean), null, undefined&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;숫자&lt;/b&gt;는 정수, 실수 모두 포함되는 타입으로 C나 Java언어처럼 여러 개의 숫자 타입이 있지 않고 오직 하나만 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;문자열&lt;/b&gt;도 마찬가지로 char가 따로 없고 문자로 되어있으면 문자열입니다. 문자열 값을 쓰려면 따옴표 안에 적어줘야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;boolean&lt;/b&gt;값은 true와 false가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;null&lt;/b&gt;은 객체인데 비어있는 변수를 의미합니다. 변수를 선언하고 null이라는 빈 값을 할당한 경우입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;undefined&lt;/b&gt;는 변수를 선언하고 값을 할당하지 않은 경우입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;null과 undefined가 미묘하게 다른데 undefined은 아직 변수에 값이 할당되지 않은 상태를 나타냅니다. 반면 null은 값이 없다는 의미의 값인 null을 할당한 것을 나타냅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 null은 객체이기 때문에 타입을 확인하면 object로 나옵니다. (자바스크립트는 타입에 대해 특이한 면이 많습니다.) null을 object와 구분하여 알려면 다음과 같이 강타입체크 연산을 해주어야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* null값을 판별하는 연산 예시&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1629460921816&quot; class=&quot;javascript&quot; style=&quot;margin: 20px auto 0px; display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var nn = null;
console.log(nn === null); // true&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드에서는 변수가 null이면 true가 출력되고 null이 아니면 false가 출력됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로 null과 undefined의 변수는 각각 어떤 식으로 만들어지는지 살펴보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* null 예시&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1585322605445&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var nn = null;
console.log(typeof nn); //object로 출력&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* undefined 예시&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1585322847229&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var empty; // 변수에 값이 할당되지 않은 상태
console.log(typeof empty); //undefined로 출력&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나머지 number, string, boolean형 값도 변수에 할당하는 모습을 한 번씩 보여드리겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* number, string, boolean형 값을 변수에 할당하는 예시&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1585323095644&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var num = 10; // 정수
var num1 = 0.1; // 실수 모두 자바스크립트에선 number형 변수로 통일
var str = 'string';&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2) 참조 데이터 타입은 객체와 같습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바스크립트에서 객체가 포함하는 범위는 객체, 배열, 함수 등을 포함합니다. 자바스크립트는 특이한 점이 많습니다. 배열과 함수도 객체에 속합니다. 자바스크립트에서 원시 데이터 타입의 변수를 제외한 모든 값은 객체입니다. 그리고 이 객체는 참조 데이터 타입입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참조 데이터 타입은 해당 변수에 실제 데이터가 할당된 것이 아니라, 어떤 메모리에 저장된 데이터를 가지고 있는 가상의 변수를 참조해서 접근하는 변수를 말합니다. 이런 특성에 대해서 이해할만한 글을 쓰면 내용이 길어지므로 지금은 생략하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1585323487301&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 객체
var object = {
    key1 : value1,
    key2 : value2
};

// 배열
var array = [1, 2, &quot;a&quot;];

// 함수
function func() {};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체의 경우 key를 프로퍼티(property)라고 부릅니다. key는 value를 담고 있는 네임이자 식별자 같은 존재입니다. &lt;span style=&quot;color: #333333;&quot;&gt;value는 원시 데이터 타입이 될 수도 있고 또 다른 객체가 될 수도 있습니다. &lt;/span&gt;즉 모든 타입의 값이 들어올 수 있습니다. 배열도 들어올 수 있고 함수도 들어올 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 연산자&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1) 산술 연산자&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사칙연산을 하는 연산자입니다. 더하기는 +, 빼기는 -, 곱하기는 *, 나누기는 /입니다. 나눗셈의 경우 몫을 구하는 연산자는 따로 존재하지 않아서 메소드를 사용해야 합니다. 하지만 나머지를 구하는 연산자는 있습니다. 바로 % 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* 사칙연산 예시&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1585364646617&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;console.log(&quot;1+1= &quot;, 1+1);
console.log(&quot;1.2-0.95= &quot;, 1.2-0.95);
console.log(&quot;2*3= &quot;, 2*3);
console.log(&quot;7/3= &quot;, 7/3);
console.log(&quot;parseInt(7/3)= &quot;, parseInt(7/3)); //몫
console.log(&quot;7%3= &quot;, 7%3); //나머지&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2) 대입 연산자&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기호는 =이고, 변수에 값을 할당하거나 참조시킬 때 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* 대입 연산 예시&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1585364747661&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 변수 x에 값을 할당
var x = 'abc';
x = 'abcde';

// 변수 x가 변수 y를 참조
var y = { a: 1 };
var x = y;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3) 비교 연산자&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 개의 값이 같은지 다른지, 혹은 대소 비교를 할 때 사용됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3-1) == 연산자는 타입 변환 후 동등 비교 (느슨한 비교)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;== 연산자는 두 개의 값이 동일한지를 비교하여 동일하면 true, 동일하지 않으면 false를 반환하는 연산자입니다. &lt;span style=&quot;color: #333333;&quot;&gt;자바스크립트는 느슨한 타입 체크 언어입니다. 그래서 다른 언어랑 다르게 특이한 점은 ==연산자로 서로 다른 타입의 데이터끼리도 비교 연산을 할 수 있다는 것입니다.&lt;span&gt; 서로 다른 데이터 타입끼리 비교할 때에는 자바스크립트가 자동으로 두 데이터를 동일한 타입으로 바꿔서 비교합니다. 예를 들어 숫자형인 3과 문자열인 '3'을 ==연산자로 비교하면 '3'을 숫자로 바꿔서 비교를 진행합니다.&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* 느슨한 타입 비교 연산 예시&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1585366350295&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;console.log(3==3);
console.log(3==&quot;3&quot;); //둘다 true&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3-2) === 연산자는 타입과 값 둘 다 비교 (엄격한 비교)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;===연산자는 ==연산자와 마찬가지로 두 피연산자가 동일하면 true 아니면 false를 반환하지만 ==연산자와 다른 점은 데이터 타입을 자동으로 변환해주지 않아서 값뿐만 아니라 타입까지도 일치하는지 여부를 판단한다는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* 엄격한 타입 비교 연산 예시&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1585366625679&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;console.log(3===3); //true
console.log(3==='3'); //false&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3-3) !==와 != 연산자&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이들은 ===나 ==와는 반대로 두 피연산자가 서로 다르면 true를 서로 같으면 false를 반환합니다. 마찬가지로 !==은 엄격한 비교이고 !=은 느슨한 비교입니다. 위에서 ===와 ==를 이해하셨다면 똑같은 내용입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* !=, !== 예시&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1585366879295&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;console.log(1 != '1'); //false
console.log(1 !== '1'); //true&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3-4) &amp;gt;, &amp;gt;=, &amp;lt;, &amp;lt;=&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대소를 비교하는 연산자들입니다. 대소 비교이므로 숫자를 비교할 때 자주 사용되긴 합니다만 문자열끼리도 비교할 수 있습니다. 문자열끼리 비교할 때에는 문자열의 길이를 비교합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;gt;는 좌측이 우측보다 크면 true를 반환,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;gt;=는 좌측이 우측보다 크거나 같으면 true를 반환,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;는 좌측이 우측보다 작으면 true를 반환,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;=는 좌측이 우측보다 작거나 같으면 true를 반환합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* 대소 비교 예시&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1585367211789&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;console.log(4 &amp;gt; 3); //true
console.log(3 &amp;gt; 3); //false
console.log(3 &amp;gt;= 3); //true
console.log('abc' &amp;lt; 'abcd'); //true
console.log('abc' &amp;lt;= 'abc'); true&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div class=&quot;revenue_unit_item adsense  responsive&quot;&gt;&lt;ins class=&quot;adsbygoogle&quot; style=&quot;display: block;&quot; data-ad-host=&quot;ca-host-pub-9691043933427338&quot; data-ad-client=&quot;ca-pub-9807016842906892&quot; data-ad-format=&quot;auto&quot;&gt;&lt;/ins&gt;
&lt;script&gt;(adsbygoogle = window.adsbygoogle || []).push({}); &lt;/script&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;4) 논리 연산자&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;font-size: 1.12em;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;4-1) ! 연산자&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;! 연산자는 논리 연산자 not과 같습니다. 따라서 true인 것은 false로 바꾸고 false인 것은 true로 바꿉니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* !연산자 예시&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1585367569395&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;console.log(!1); //1은 true이므로 !1은 false
console.log(!0); //0은 false이므로 !0은 true&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;4-2) &amp;amp;&amp;amp; 연산자&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;amp;&amp;amp;연산자는 논리 연산자 and와 같습니다. 좌항이 true이면 우항을 반환하며, 좌항이 false이면 우항을 읽지 않고 false를 반환합니다. &amp;amp;&amp;amp;연산자의 사용법은 크게 두 가지가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;논리 연산을 위해 좌항과 우항에 모두 boolean을 사용&lt;/li&gt;
&lt;li&gt;대입 연산을 위해 좌항에서 boolean or undefiend or null로 조건 체크&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좌항이 boolean이나 null이나 undefined가 아닌 경우에는 자바스크립트 특성상 타입 변환으로 인해 다양한 결과들이 나타납니다. 그로 인해 결과 예측이 어려우며 가독성이 매우 떨어지고 무엇이 목적인 코드인지 명확히 알기 어려워집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* &amp;amp;&amp;amp; 연산자 예시&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1585872608403&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 1. 논리 연산
console.log(true &amp;amp;&amp;amp; true); // true
console.log(true &amp;amp;&amp;amp; false); // false
console.log(false &amp;amp;&amp;amp; true); // false

// 2. 대입 연산을 위한 논리 연산
var func = function(){}

var var1 = true &amp;amp;&amp;amp; func;
var var2 = null &amp;amp;&amp;amp; func;
var var3 = undefined &amp;amp;&amp;amp; func;

console.log(var1); // &amp;fnof; (){}
console.log(var2); // null
console.log(var3); // undefined

// 3. 용도나 목적을 알 수 없고 결과 예측이 어렵고 가독성이 떨어지는 사용 예시
console.log(&quot;aa&quot; &amp;amp;&amp;amp; &quot;bb&quot;); // &quot;bb&quot;
console.log(&quot;&quot; &amp;amp;&amp;amp; &quot;aa&quot;); // &quot;&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;4-3) || 연산자&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;|| 연산자는 논리 연산자 or와 같습니다. &amp;amp;&amp;amp;와 비슷하게 좌항이 true이면 우항을 읽지 않고 true를 반환합니다. 좌우의 있는 두 항의 값 중 하나라도 true이면 true를 반환합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* || 연산자 예시&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1585872641685&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 1. 논리 연산
console.log(true || true); // true
console.log(true || false); // true
console.log(false || true); // true
console.log(false || false); // false

// 2. 대입 연산을 위한 논리 연산
var aa = &quot;aa&quot;;

console.log(aa || &quot;&quot;); // &quot;aa&quot;
console.log(null || &quot;&quot;); // &quot;&quot;
console.log(undefined || &quot;&quot;); // &quot;&quot;
console.log(false || &quot;&quot;); // &quot;&quot;

var bb = &quot;bb&quot;;
var var1 = aa || bb;

console.log(var1); // &quot;aa&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 조건문&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조건문은 if문과 switch문이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1) if문&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1585614449771&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;if (조건식){
    실행코드
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 if라고 적고 괄호() 안에 조건식을 적어줍니다. 조건식의 값은 true인지 아닌지를 판별합니다. (자바스크립트는 꼭 boolean이 아닌 것을 조건식에 넣어도 자동으로 형 변환하여 참 거짓을 판별합니다만 이런 사용 방식은 그다지 추천하지 않습니다.) 조건식의 값이 true이면 중괄호{} 안에 있는 코드가 실행되고 그렇지 않으면 중괄호{} 안에 있는 코드는 무시된 채 다음 코드로 넘어갑니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* 예시 코드&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1585614652425&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// ex 1
if (true){
    alert(&quot;hello&quot;); //실행 O
}

// ex 2
if (false){
    alert(&quot;hello&quot;); //실행 X
}

// ex 3
var num = 5;
if (num === 5){
    alert(&quot;hello&quot;); //실행 O
}

// ex 4
if (num !== 5){
    alert(&quot;hello&quot;); //실행 X
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;if문에는 if 뿐만 아니라 else if나 else도 같이 쓰일 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;if와 else if를 사용&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;else if는 if의 조건식의 값이 true가 아니어서 코드를 넘겼을 때 다른 조건식을 제시하기 위해 사용됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1585614954400&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;if (조건1){
    실행코드
} else if (조건2){
    실행코드
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조건문을 이와 같이 사용할 때 '조건1'이 true이면 밑에 있는 else if는 무시된 채 넘어갑니다. 즉 '조건1'이 true이면 '조건2'의 진위 여부는 판별 자체를 실행하지 않고 넘어갑니다. 반면 '조건1'이 true가 아니면 그다음 조건식인 '조건2'의 판별로 넘어갑니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* 예시 코드&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1585615248147&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// ex 1
var num = 0;
if (true){
    num++; //실행
} else if (false){
    num++; //미실행
}
alert(num); //결과 : 1

// ex 2
var num = 0;
if (true){
    num++; //실행
} else if (true){
    num++; //미실행
}
alert(num); //결과 : 1

// ex 3
var num = 0;
if (false){
    num++; //미실행
} else if (true){
    num++; //실행
}
alert(num); //결과 : 1

// ex 4
var num = 0;
if (false){
    num++; //미실행
} else if (false){
    num++; //미실행
}
alert(num); //결과 : 0&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;else if는 여러 번 반복해서 사용할 수 있습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1585615317579&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;if (조건1){
    실행코드
} else if (조건2){
    실행코드
} else if (조건3){
    실행코드
} else if (조건n){
    실행코드
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 식으로 사용하면 if문의 조건식 '조건1'이 true이면 해당 코드를 실행하고 if문을 끝냅니다. '조건1'이 true가 아니면 그다음 조건식인 '조건2'의 진위여부를 판별하고 '조건2'가 true이면 해당 코드를 실행하고 if문을 끝냅니다. 만약 '조건2'도 true가 아니면 그다음 조건식인 '조건3'의 진위여부를 판별하고 마찬가지의 프로세스를 계속 진행합니다. 이런 프로세스로 if문의 코드는 실행됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* 예시 코드&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1585615732724&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// ex 1
var code = 'c';
if (code === 'a'){
    alert(1);
} else if (code === 'b'){
    alert(2);
} else if (code === 'c'){
    alert(3);
} else if (code === 'd'){
    alert(4);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 예시 코드는 조건식 두 개를 거쳐 세 번째 조건식 code === 'c'에서 true이므로 alert(3);을 실행하고 if ~ else if문을 탈출합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;else 사용&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조건문의 마지막은 항상 else를 사용할 수 있습니다. else는 위에 있는 조건식들의 값이 전부 true가 아니라고 판별된 뒤에 실행되며 예외 케이스에 대한 처리를 위해 사용됩니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1585615897970&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 사용법 예시 if와 else
if (조건){
    실행코드
} else {
    실행코드
}

// 사용법 예시 if와 else if와 else
if (조건1){
    실행코드
} else if (조건n) {
    실행코드
} else {
    실행코드
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* 예시 코드&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1585616242020&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// ex 1
var result = false;
if (result){
    alert(&quot;통과&quot;);
} else {
    alert(&quot;실패&quot;);
}
// result 값이 false이므로 if 조건식을 지나치고 else의 코드가 실행

// ex 2
var result = 'c';
if (result === 'a'){
    alert(1);
} else if (result === 'b'){
    alert(2);
} else {
    alert(3);
}
//위의 두 조건식의 값이 모두 false이므로 else의 코드가 실행&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div class=&quot;revenue_unit_item adsense  responsive&quot;&gt;&lt;ins class=&quot;adsbygoogle&quot; style=&quot;display: block;&quot; data-ad-host=&quot;ca-host-pub-9691043933427338&quot; data-ad-client=&quot;ca-pub-9807016842906892&quot; data-ad-format=&quot;auto&quot;&gt;&lt;/ins&gt;
&lt;script&gt;(adsbygoogle = window.adsbygoogle || []).push({}); &lt;/script&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2) switch문&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용법은 다음과 같습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1585616407949&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;switch (변수){
    case 값1 :
        실행코드
        break;
    case 값2 :
        실행코드
        break;
    case 값n :
        실행코드
        break;
    default :
        실행코드
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;switch문은 해당 변수의 값에 따라 분기 처리하여 다른 코드를 실행할 수 있게 경우의 수를 나눌 때 사용합니다. 물론 이것은 if문으로도 구현할 수 있습니다. if문의 조건식을 (변수 === 값n) 로 사용하면 됩니다. 그러나 특정 변수의 값에 따라 서로 다른 처리를 하는 경우에는 switch문이 if문보다 훨씬 가독성이 뛰어납니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;switch문 사용법은 우선 switch라고 적고 () 안에 변수를 적어줍니다. 그리고 중괄호{}를 열고 각 경우의 수마다 case 값n : 을 적어줍니다. 그 값은 () 안에 적어준 변수의 값과 매칭시킬 값입니다. 매칭이 된다면 실행시킬 코드를 case 값n : 밑에 줄부터 적습니다. &lt;span style=&quot;color: #ee2323;&quot;&gt;실행코드 가장 마지막 줄에는 항상 break;를 넣어줘야 합니다.&lt;/span&gt; break를 만나야 switch문을 탈출하기 때문입니다. 만약 break;가 없다면 아래줄에 있는 case문에 대한 코드도 실행해버리는 부작용이 발생합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막에 &lt;span style=&quot;color: #ee2323;&quot;&gt;default&lt;/span&gt;는 if문의 else와 같은 존재입니다. 모든 case에 대해 만족하지 않으면 실행해줄 예외처리 코드를 쓰기 위해 필요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* 예시 코드&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1585616950217&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// ex 1
var code = 'b';
switch (code){
    case 'a' :
        alert(1);
        break;
    case 'b' :
        alert(2);
        break;
    case 'c' :
        alert(3);
        break;
    default :
        alert(&quot;need a or b or c&quot;);
}

// ex 2
var code = 'd';
switch (code){
    case 'a' :
        alert(1);
        break;
    case 'b' :
        alert(2);
        break;
    case 'c' :
        alert(3);
        break;
    default :
        alert(&quot;need a or b or c&quot;);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ex1의 경우&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;code변수의 값이 case 'b'에 해당하므로 alert(2);이 실행되고 break;를 만나서 switch문을 탈출합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ex2의 경우&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 case에 해당하지 않으므로 default : 밑에 있는 코드 alert(&quot;need a or b or c&quot;);가 실행됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 반복문&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반복문은 어떤 코드 블록을 n회 반복해서 실행할 수 있도록 하는 문법입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반복문과 같이 가장 자주 많이 쓰이는 데이터 타입은 배열입니다. 배열의 각 원소들에 동일 로직으로 처리를 하고 싶을 때, 반복문은 강력한 힘을 발휘할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1) while문으로 배열 다루기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* 예시 코드&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1585629832820&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var people = ['cocoder', 'yoon', 'hoho', 'choco'];
var i = 0;
while (i &amp;lt; people.length){
    document.write(&quot;&amp;lt;li&amp;gt;&quot; + people[i] + &quot;&amp;lt;/li&amp;gt;&quot;);
    i++;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;people 배열은 원소가 총 4개이므로 length 프로퍼티의 값은 4입니다. 이것은 마지막 인덱스인 3보다 1만큼 더 큰 숫자입니다. 인덱스는 0부터 세고 length 값은 1부터 세기 때문입니다. 따라서 while문에서 i값은 0, 1, 2, 3일 때 각각 {} 안에 있는 코드 블록을 실행합니다. 이번 예시 코드에서 {} 안에 있는 코드 내용은 li태그에 각 배열의 원소를 텍스트 컨텐트로 담아 출력하는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;bf07BJimg.png&quot; data-origin-width=&quot;137&quot; data-origin-height=&quot;117&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bAogVX/btrcHD1bfR5/VPwaxa7QkBuiasFWA3c2Vk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bAogVX/btrcHD1bfR5/VPwaxa7QkBuiasFWA3c2Vk/img.png&quot; data-alt=&quot;예시 코드 결과&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bAogVX/btrcHD1bfR5/VPwaxa7QkBuiasFWA3c2Vk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbAogVX%2FbtrcHD1bfR5%2FVPwaxa7QkBuiasFWA3c2Vk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;137&quot; height=&quot;117&quot; data-filename=&quot;bf07BJimg.png&quot; data-origin-width=&quot;137&quot; data-origin-height=&quot;117&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;예시 코드 결과&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반복문 안에 조건문을 활용할 수 있습니다. people배열에서 'c'라는 문자가 포함된 원소만 출력해보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* 예시 코드&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1585630594135&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var people = ['cocoder', 'yoon', 'hoho', 'choco'];
var i = 0;
while (i &amp;lt; people.length){
    if (people[i].includes('c')){
    	document.write(&quot;&amp;lt;li&amp;gt;&quot; + people[i] + &quot;&amp;lt;/li&amp;gt;&quot;);  
    }
    i++;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;dgOB9Jimg.png&quot; data-origin-width=&quot;117&quot; data-origin-height=&quot;49&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bXyeiI/btrcIVghoJj/7SAhVj0acdrbNNY2MR9k80/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bXyeiI/btrcIVghoJj/7SAhVj0acdrbNNY2MR9k80/img.png&quot; data-alt=&quot;예시 코드 결과&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bXyeiI/btrcIVghoJj/7SAhVj0acdrbNNY2MR9k80/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbXyeiI%2FbtrcIVghoJj%2F7SAhVj0acdrbNNY2MR9k80%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;117&quot; height=&quot;49&quot; data-filename=&quot;dgOB9Jimg.png&quot; data-origin-width=&quot;117&quot; data-origin-height=&quot;49&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;예시 코드 결과&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2) for문으로 배열 다루기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 배열의 각 원소의 값을 바꾸고 싶습니다. 가장 간단하게 생각하면 다음과 같이 각 원소의 값을 바꿀 수 있을 것입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1629471282818&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var people = ['cocoder', 'yoon', 'hoho', 'choco'];

people[0] = 'cocoder_01';
people[1] = 'yoon_02';
people[2] = 'hoho_03';
people[3] = 'choco_04';

console.log(people); // ['cocoder_01', 'yoon_02', 'hoho_03', 'choco_04']&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다소 귀찮긴 했지만 일일이 인덱스에 접근하여 값을 변경해주었습니다. 그런데 만약 배열의 길이가 십만이라면? 백만이라면? 그때에도 이렇게 손수 각 인덱스를 직접 써서 값을 변경할 수 있을까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반복문은 그래서 강력합니다. 코드를 한번만 작성하면 중복작성없이 같은 작업을 계속 수행할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 예제로&amp;nbsp;for문으로 배열의 각 원소의 값을 바꿔보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* 예시 코드&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1585631796705&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var team = ['cocoder_01', 'yoon_02', 'hoho_03', 'choco_99'];

//특정 문자열 모두 치환 함수 만들기. 내장메소드인 replace()는 첫번째문자만 치환됨.
String.prototype.replaceAll = function(org, dest) {
    return this.split(org).join(dest);
}

for (var i = 0; i &amp;lt; team.length; i++){
    team[i] = team[i].replaceAll('_', '_0');
    document.write(&quot;&amp;lt;li&amp;gt;&quot; + team[i] + &quot;&amp;lt;/li&amp;gt;&quot;);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;team이라는 배열은 4개의 원소를 가지고 있고 각 원소는 string형이며 끝자리에 _ 뒤에 두 자리의 수가 붙어있습니다. 모든 원소의 이 끝자리 수를 세 자리의 수로 만드는 작업을 반복문으로 처리를 하였습니다. 반복문을 사용하기 전에 문자열을 바꾸는 메소드인 replaceAll()을 String prototype 객체에 추가하여, 모든 string타입의 객체가 사용할 수 있도록 하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;bnsEldimg.png&quot; data-origin-width=&quot;158&quot; data-origin-height=&quot;115&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/o0qwo/btrcMZIVDLu/qaXTWJ1hBaH98FRZU354k0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/o0qwo/btrcMZIVDLu/qaXTWJ1hBaH98FRZU354k0/img.png&quot; data-alt=&quot;예시 코드 결과&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/o0qwo/btrcMZIVDLu/qaXTWJ1hBaH98FRZU354k0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fo0qwo%2FbtrcMZIVDLu%2FqaXTWJ1hBaH98FRZU354k0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;158&quot; height=&quot;115&quot; data-filename=&quot;bnsEldimg.png&quot; data-origin-width=&quot;158&quot; data-origin-height=&quot;115&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;예시 코드 결과&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3) foreach()로 배열 다루기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;foreach()는 배열 객체에만 사용 가능한 메소드입니다. 먼저 파라미터에 대해 살펴보겠습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1585632286655&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;array.forEach(callbackFunction(currentValue, index, array){}, thisArg);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파라미터로 &lt;b&gt;콜백 함수와 thisArg&lt;/b&gt;를 받아옵니다. 콜백 함수는 배열 객체에 적용시킬 함수를 의미하고 thisArg는 콜백 함수 내에서 this로 사용할 값을 명시적으로 넣어주는 용도입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;콜백 함수에도 파라미터가 3개가 있는데, &lt;span style=&quot;color: #f3c000;&quot;&gt;currentValue&lt;/span&gt;는 배열의 현재 인덱스에 해당하는 원소 값이고, &lt;span style=&quot;color: #f3c000;&quot;&gt;index&lt;/span&gt;는 현재 인덱스이고, &lt;span style=&quot;color: #f3c000;&quot;&gt;array&lt;/span&gt;는 배열 전체를 의미합니다. 콜백 함수는 배열의 각 원소에게 각각 적용됩니다. 기본 예제를 보고 감을 더 잡아보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* 예시 코드&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1585633083550&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var team = ['cocoder', 'yoon', 'hoho', 'choco'];
team.forEach(function(cv,i,arr){
    document.write('안녕하세요. 저는 ' + (i+1) + '번 선수 ' + cv +'입니다.&amp;lt;br&amp;gt;' + 
    	'저희 팀원은 ' + arr +'가 있습니다.&amp;lt;br&amp;gt;'); 
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;bqJDI3img.png&quot; data-origin-width=&quot;478&quot; data-origin-height=&quot;227&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bwpSzl/btrcNk0i0iD/d3dPrAzpYgrhmpx0rtvnbk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bwpSzl/btrcNk0i0iD/d3dPrAzpYgrhmpx0rtvnbk/img.png&quot; data-alt=&quot;예시 코드 결과&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bwpSzl/btrcNk0i0iD/d3dPrAzpYgrhmpx0rtvnbk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbwpSzl%2FbtrcNk0i0iD%2Fd3dPrAzpYgrhmpx0rtvnbk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;478&quot; height=&quot;227&quot; data-filename=&quot;bqJDI3img.png&quot; data-origin-width=&quot;478&quot; data-origin-height=&quot;227&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;예시 코드 결과&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;4) for~in문으로 배열 다루기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* 예시 코드&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1585633335738&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var arr = [&quot;a&quot;, &quot;b&quot;, &quot;c&quot;, &quot;d&quot;, &quot;e&quot;];
for (var i in arr){
    document.write(i, &quot; : &quot;, arr, &quot;&amp;lt;br&amp;gt;&quot;);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;for ~ in 문에서 i는 index가 되고, arr는 배열이 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;TieQCimg.png&quot; data-origin-width=&quot;129&quot; data-origin-height=&quot;144&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/PcWbF/btrcHEFOOEp/yl47FTJA6LxPcz0Km4u491/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/PcWbF/btrcHEFOOEp/yl47FTJA6LxPcz0Km4u491/img.png&quot; data-alt=&quot;예시 코드 결과&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/PcWbF/btrcHEFOOEp/yl47FTJA6LxPcz0Km4u491/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPcWbF%2FbtrcHEFOOEp%2Fyl47FTJA6LxPcz0Km4u491%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;129&quot; height=&quot;144&quot; data-filename=&quot;TieQCimg.png&quot; data-origin-width=&quot;129&quot; data-origin-height=&quot;144&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;예시 코드 결과&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;5) for~of문으로 배열 다루기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* 예시 코드&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1585633505448&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var arr = [&quot;a&quot;, &quot;b&quot;, &quot;c&quot;, &quot;d&quot;, &quot;e&quot;];
for (var i of arr){
    document.write(i, &quot; : &quot;, arr, &quot;&amp;lt;br&amp;gt;&quot;);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;for ~ of 문에서 i는 원소값이 되고 arr는 배열이 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;pBPhYimg.png&quot; data-origin-width=&quot;118&quot; data-origin-height=&quot;140&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OC6MY/btrcMHuZCeH/XBhfSDY5ryIb2YQi3K9Ng0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OC6MY/btrcMHuZCeH/XBhfSDY5ryIb2YQi3K9Ng0/img.png&quot; data-alt=&quot;예시 코드 결과&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OC6MY/btrcMHuZCeH/XBhfSDY5ryIb2YQi3K9Ng0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOC6MY%2FbtrcMHuZCeH%2FXBhfSDY5ryIb2YQi3K9Ng0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;118&quot; height=&quot;140&quot; data-filename=&quot;pBPhYimg.png&quot; data-origin-width=&quot;118&quot; data-origin-height=&quot;140&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;예시 코드 결과&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Javascript</category>
      <category>javascript</category>
      <category>기초</category>
      <category>문법</category>
      <category>반복문</category>
      <category>연산자</category>
      <category>입문</category>
      <category>자료형</category>
      <category>조건문</category>
      <author>cocoder16</author>
      <guid isPermaLink="true">https://cocoder16.tistory.com/40</guid>
      <comments>https://cocoder16.tistory.com/40#entry40comment</comments>
      <pubDate>Mon, 6 Sep 2021 08:00:33 +0900</pubDate>
    </item>
    <item>
      <title>[CSS 레이아웃] 푸터 (footer) 만들기</title>
      <link>https://cocoder16.tistory.com/39</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(이 포스팅은 20년 05월쯤 썼던 글의 복원입니다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;푸터(footer) 만들기 개요&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에 혼자 하고 있는 토이 프로젝트에서 만든 진짜 간단한 푸터가 있는데 기록으로 남겨봅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 생겼습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;GVmVAimg.png&quot; data-origin-width=&quot;1082&quot; data-origin-height=&quot;296&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b6avix/btrcAcWsoXf/kgEduiRJy0uk7Ysbr0fKCK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b6avix/btrcAcWsoXf/kgEduiRJy0uk7Ysbr0fKCK/img.png&quot; data-alt=&quot;테블릿, 모바일에서의 푸터(footer)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b6avix/btrcAcWsoXf/kgEduiRJy0uk7Ysbr0fKCK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb6avix%2FbtrcAcWsoXf%2FkgEduiRJy0uk7Ysbr0fKCK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1082&quot; height=&quot;296&quot; data-filename=&quot;GVmVAimg.png&quot; data-origin-width=&quot;1082&quot; data-origin-height=&quot;296&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;테블릿, 모바일에서의 푸터(footer)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반응형이라 데스크탑 모드에서는 왼쪽에 사이드바가 생겨서 다음과 같이 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;mQKCOimg.png&quot; data-origin-width=&quot;1913&quot; data-origin-height=&quot;218&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pm503/btrcB35OF99/idr6WLaoJ7D69Qykc3HHIk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pm503/btrcB35OF99/idr6WLaoJ7D69Qykc3HHIk/img.png&quot; data-alt=&quot;데스크탑에서의 푸터(footer)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pm503/btrcB35OF99/idr6WLaoJ7D69Qykc3HHIk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fpm503%2FbtrcB35OF99%2Fidr6WLaoJ7D69Qykc3HHIk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1913&quot; height=&quot;218&quot; data-filename=&quot;mQKCOimg.png&quot; data-origin-width=&quot;1913&quot; data-origin-height=&quot;218&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;데스크탑에서의 푸터(footer)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떻게 만들었는지 천천히 살펴보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;footer(푸터)의 태그 구조&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 저는 시맨틱 태그를 무척 사랑하는 '웹 표준 지킴이'기 때문에 div를 사용하지 않고 footer 태그를 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;address태그도 쓰면 좋겠지만 css귀차니즘이 발동해서 안 썼습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 푸터에 있는 링크들은 역시 시맨틱 태그인 nav태그로 그 링크들을 감싸줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 쓰고 싶은 내용을 p태그로 감싸주면 푸터 태그의 구조는 완성입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* footer&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1592581807790&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;footer&amp;gt;
    &amp;lt;nav&amp;gt;
        &amp;lt;a href='https://cocoder.tistory.com' target='_blank'&amp;gt;Blog&amp;lt;/a&amp;gt; |
        &amp;lt;a href='https://github.com/cocoder16' target='_blank'&amp;gt;Github&amp;lt;/a&amp;gt;
    &amp;lt;/nav&amp;gt;
    &amp;lt;p&amp;gt;
        &amp;lt;span&amp;gt;저자 : cocoder&amp;lt;/span&amp;gt;&amp;lt;br/&amp;gt;
        &amp;lt;span&amp;gt;이메일 : cocoder16@gmail.com&amp;lt;/span&amp;gt;&amp;lt;br/&amp;gt;
        &amp;lt;span&amp;gt;Copyright 2020. cocoder. All Rights Reserved.&amp;lt;/span&amp;gt;
    &amp;lt;/p&amp;gt;
&amp;lt;/footer&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;footer 하단에 붙이기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;footer의 핵심은 항상 하단에 붙어 있어야 한다는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 그림과 같은 레이아웃이 있을 때 section wrapper의 부피는 대부분 웹의 경우 가변적입니다. 데이터가 그때그때 다를 것이기 때문입니다. 데이터가 정말 없을 때에는 section wrapper의 height값이 극도로 작을 수도 있습니다. 이럴 때에도 footer가 하단에 딱 고정되어 붙어있기를 원합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;dLBFmoimg.png&quot; data-origin-width=&quot;893&quot; data-origin-height=&quot;376&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Ohw88/btrcACmWFYe/M7kldyLahqZ3RB08eQv7KK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Ohw88/btrcACmWFYe/M7kldyLahqZ3RB08eQv7KK/img.png&quot; data-alt=&quot;section wrapper의 height가 0일 때에도 footer가 항상 바닥에 붙어있게 해줍니다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Ohw88/btrcACmWFYe/M7kldyLahqZ3RB08eQv7KK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOhw88%2FbtrcACmWFYe%2FM7kldyLahqZ3RB08eQv7KK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;893&quot; height=&quot;376&quot; data-filename=&quot;dLBFmoimg.png&quot; data-origin-width=&quot;893&quot; data-origin-height=&quot;376&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;section wrapper의 height가 0일 때에도 footer가 항상 바닥에 붙어있게 해줍니다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것을 하기 위해 wrap의 개념을 사용할 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;body태그 바로 밑에 body태그 하위에 있는 모든 태그를 감싸는 wrapper를 만들어줍니다. (div태그)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* body 바로 아래에 위치하는 wrap 태그 생성&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1592583519866&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;body&amp;gt;
    &amp;lt;div id='wrap'&amp;gt;
        &amp;lt;!-- 대충 뭐 요소들이 가득차있음 --&amp;gt;
    &amp;lt;/div&amp;gt;
&amp;lt;/body&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저 wrap은 유일한 wrap이니 class가 아니라 id를 부여해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 푸터를 다음과 같이 wrapper의 최하단에 위치시킵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;footer와&amp;nbsp; section영역을 명확하게 구분하기 위해 section 태그도 사용해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* footer의 위치&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1629361337781&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;body&amp;gt;
    &amp;lt;div id='wrap'&amp;gt;
    	&amp;lt;section&amp;gt;
        	&amp;lt;!-- 대충 뭐 요소들이 가득차있음 --&amp;gt;
        &amp;lt;/section&amp;gt;
        &amp;lt;footer&amp;gt;
          &amp;lt;nav&amp;gt;
              &amp;lt;a href='https://cocoder.tistory.com' target='_blank'&amp;gt;Blog&amp;lt;/a&amp;gt; |
              &amp;lt;a href='https://github.com/cocoder16' target='_blank'&amp;gt;Github&amp;lt;/a&amp;gt;
          &amp;lt;/nav&amp;gt;
          &amp;lt;p&amp;gt;
              &amp;lt;span&amp;gt;저자 : cocoder&amp;lt;/span&amp;gt;&amp;lt;br/&amp;gt;
              &amp;lt;span&amp;gt;이메일 : cocoder16@gmail.com&amp;lt;/span&amp;gt;&amp;lt;br/&amp;gt;
              &amp;lt;span&amp;gt;Copyright 2020. cocoder. All Rights Reserved.&amp;lt;/span&amp;gt;
          &amp;lt;/p&amp;gt;
      &amp;lt;/footer&amp;gt;
    &amp;lt;/div&amp;gt;
&amp;lt;/body&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;id가 wrap인 div에게 다음과 같은 CSS 코드를 부여해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1592583608442&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#wrap {
    min-height: 100vh;
    position: relative;
    width: 100%;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 footer에게는 다음과 같이 해주면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1592583743920&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;footer {
	width: 100%;
	height: 110px; /* 내용물에 따라 알맞는 값 설정 */
	bottom: 0px;
	position: absolute;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;footer에는 position 속성을 부여했기 때문에 다른 영역과 겹치는 현상이 발생할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 section 영역은 footer의 height값만큼 padding값을 주어 section의 content 영역을 footer와 겹치지 않도록 만듭니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1629362904055&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;section {
	padding-bottom: 110px; /* footer의 height값과 동일 */
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;html, body태그에도 css코드를 다음과 같이 margin, padding 값을 줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1629362510095&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;html, body {
  margin: 0;
  padding: 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면 어떤 상황에서도 하단에 딱 붙는 푸터가 완성됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;section-wrapper의 크기가 작을 때에는 그것과 상관없이 화면 하단에 footer가 고정되어 있을 것이고, section-wrapper의 height이 커져 상하 스크롤이 생겼을 경우에는 스크롤을 제일 밑으로 내리면 화면 하단에 footer가 고정되어 있을 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;자!! 이제 푸터(footer)의 레이아웃이 모두 완성되었습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;남은 일은 꾸미는 것뿐입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;푸터의 영역을 표시하기 위해서 사용되는 속성은 border나 background-color가 될 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* 디자인을 입힌 CSS 코드&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1592584504103&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;html, body {
  margin: 0;
  padding: 0;
}

#wrap {
  min-height: 100vh;
  position: relative;
  width: 100%; 
  background-color: #f2f2f2;
}

section {
  padding-bottom: 105px;
}

footer {
  width: 100%;
  height: 90px;
  bottom: 0px;
  position: absolute;
  border-top: 1px solid #c4c4c4;
  padding-top: 15px;
  color: #808080;
  font-size: 11px;
}

footer a {
  display: inline-block;
  margin: 0 20px 10px 20px;
  color: #808080; font-size: 11px;
}

footer a:visited {
  color: #808080;
}

footer p {
  margin-top: 0; margin-bottom: 0;   
}

footer p span {
  display: inline-block;
  margin-left: 20px;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>CSS</category>
      <category>CSS</category>
      <category>footer</category>
      <category>layout</category>
      <category>디자인</category>
      <category>레이아웃</category>
      <category>만들기</category>
      <category>웹</category>
      <category>퍼블리싱</category>
      <category>푸터</category>
      <author>cocoder16</author>
      <guid isPermaLink="true">https://cocoder16.tistory.com/39</guid>
      <comments>https://cocoder16.tistory.com/39#entry39comment</comments>
      <pubDate>Mon, 30 Aug 2021 08:00:42 +0900</pubDate>
    </item>
    <item>
      <title>[반응형 웹개발] 모바일 우선 개발법</title>
      <link>https://cocoder16.tistory.com/38</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;모바일 우선 개발과 데스크탑 우선 개발&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반응형 웹 개발을 할 때, 모바일 우선 개발은 모바일 레이아웃 코드를 먼저 만들고, 데스크탑 레이아웃 코드를 나중에 만드는 개발 방법이고, 데스크탑 우선 개발은 데스크탑 레이아웃 코드를 먼저 만들고, 모바일 레이아웃 코드를 나중에 만드는 개발 방법입니다. 반응형 웹 개발을 하면서 여러 디바이스를 고려할 때에는 모바일 우선 개발을 할지, 데스크탑 우선 개발을 할지를 고려해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;모바일 우선 개발이 왜 더 효율적인가?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;통상적으로는 모바일 우선 개발법이 데스크탑 우선 개발법보다 더 효율적인 작업방법입니다. 더 작은 화면을 가진 모바일에서는 같은 UI에 대해 더 작은 단위로 행을 쪼개어 보여줘야 하기 때문에 더 작은 원자들로 레이아웃을 구성할 수 있기 때문입니다. 더 큰 화면에 대해서는 작은 원자들을 조립하기만 하면 됩니다. 이론이 추상적이기 때문에 부가설명을 하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리가 만드는 웹페이지의 스크롤은 대부분 상하로 올렸다 내렸다 하는 스크롤입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 디바이스의 너비에 딱 맞도록 페이지의 레이아웃을 제작합니다. 그리고 페이지의 높이는 그다지 신경 쓰지 않습니다. 상하로 움직이는 스크롤이 있기 때문이죠. 그래서 &quot;달라지는 너비에 맞게 요소들을 어느 정도까지 같은 행 안에 나열할 수 있는가?&quot; 하는 이슈가 반응형 웹 개발에서 중요하게 작용하게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 가로의 길이가 300px인 div요소 두 개가 가로로 나란히 있다고 가정하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 창의 너비가 600px (300 * 2)보다 작아졌는데도 div가 가로로 나란히 정렬되어있다면 창의 너비를 벗어나 잘리게 되고 가로 스크롤이 생겨납니다. 이러면 가독성이 떨어지기 때문에 보통은 가로 스크롤을 만들지 않고 오른쪽 div요소를 왼쪽 div요소의 밑의 칸으로 내려줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 기법을 사용한다고 할 때, 디바이스의 너비가 작을수록 즉, 데스크탑보다는 모바일인 경우에 세로로 나열되는 요소들의 개수가 더 많아지게 됩니다. 디바이스의 너비가 클수록 가로로 나열할 수 있는 요소가 많아지기 때문에 세로로 나열되는 요소들의 개수는 줄어들게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약에 데스크탑에 맞춰서 코드를 제작한 다음에 나중에 모바일에 맞춰서 제작하려면 가로로 나열된 요소를 다시 쪼개어 새로운 행을 만들어야 합니다. 그러면 HTML 태그부터 다시 수정해야 합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 모바일에 맞춰 제작하면 이미 가장 작은 단위로 요소를 쪼개 놓았기 때문에 데스크탑에 맞춰 CSS를 작성할 때 HTML 수정 작업이 줄어들어 전자의 경우보다 훨씬 작업효율이 좋아집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div class=&quot;revenue_unit_item adsense  responsive&quot;&gt;&lt;ins class=&quot;adsbygoogle&quot; style=&quot;display: block;&quot; data-ad-host=&quot;ca-host-pub-9691043933427338&quot; data-ad-client=&quot;ca-pub-9807016842906892&quot; data-ad-format=&quot;auto&quot;&gt;&lt;/ins&gt;
&lt;script&gt;(adsbygoogle = window.adsbygoogle || []).push({}); &lt;/script&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;레이아웃을 만드는 wrap의 활용&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데스크탑에서는 가로로 나열할 예정이지만 모바일에서는 세로로 나열할 요소 두 개를 생각해보겠습니다. 이 두 요소를 합쳐서 div태그로 묶어서 덩어리로 만들어줍니다. 이렇게 해서 모바일에서는 둘을 세로로 정렬했다가 창의 너비가 일정 사이즈 이상 커지면 가로로 나열되게 만들 것입니다. 이렇게 묶은 덩어리를 이제부터 wrap이라고 부르겠습니다. 그리고 묶음 덩어리를 만들기 위해 바깥쪽에 사용된 div태그에는 wrap이라는 class명을 부여하겠습니다. 그리고 이 wrap 클래스가 float: left속성을 가지게 하면 이 요소들은 창의 너비에 맞춰 알아서 가로 정렬과 세로 정렬을 유기적으로 맞출 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* 예시 코드&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1593156646884&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;!-- CSS --&amp;gt;
.wrap {
  float: left;
  width: 300px;
  height: 60px;
  margin-right: 10px;
}
.wrap.left {
  outline: 1px solid green;
}
.wrap.right {
  outline: 1px solid blue;
}

&amp;lt;!-- HTML --&amp;gt;
&amp;lt;form&amp;gt;
  &amp;lt;div class='left wrap'&amp;gt;
    &amp;lt;span&amp;gt;A: &amp;lt;/span&amp;gt;&amp;lt;input type=&quot;text&quot;&amp;gt;
  &amp;lt;/div&amp;gt;
  &amp;lt;div class='right wrap'&amp;gt;
    &amp;lt;span&amp;gt;B: &amp;lt;/span&amp;gt;&amp;lt;input type=&quot;text&quot;&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/form&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;span 태그와 input 태그는 wrap에 의해 한 덩어리로 뭉쳐져 있습니다. 이렇게 두 덩어리를 만들어서 width값을 지정하고 float: left값을 넣었습니다. 그 결과 창의 너비에 따라 input은 span과 한 줄에 나란히 있거나 span 아래에 위치하게 될 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;codepen&quot; style=&quot;height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;&quot; data-height=&quot;300&quot; data-default-tab=&quot;html,result&quot; data-slug-hash=&quot;ExmBMxp&quot; data-user=&quot;cocoder16&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;See the Pen by cocoder16 (&lt;a href=&quot;https://codepen.io/cocoder16&quot;&gt;@cocoder16&lt;/a&gt;) on &lt;a href=&quot;https://codepen.io&quot;&gt;CodePen&lt;/a&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저 두 wrap요소 뒤에 다른 요소들이 또 존재한다면 여기서 끝이 아닙니다. float는 부유하는 속성이기 때문에 다음에 오는 요소들에 부작용을 주지 않으려면 &lt;span style=&quot;color: #f89009;&quot;&gt;부유 효과를&lt;/span&gt; 없애줘야 합니다. 이때에는 두 wrap을 함께 감싸고 있는 부모 요소에서 after 가상 선택자를 넣고 부유 효과를 없애주면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1593157254217&quot; class=&quot;html xml&quot; style=&quot;display: block; overflow: auto; padding: 15px; color: #383a42; background: #f6f7f8; font-size: 14px; border-radius: 3px; font-family: Menlo, Consolas, Monaco, monospace; border: 1px solid #dddddd; margin: 20px auto 0px; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;!-- CSS --&amp;gt;
form {
  width: 90%;
  outline: 1px solid red;
}
form:after {
  clear: both; display: block; content: ''
}
.wrap {
  float: left;
  width: 300px;
  height: 60px;
  margin-right: 10px;
}
.wrap.left {
  outline: 1px solid green;
}
.wrap.right {
  outline: 1px solid blue;
}
.next {
  width: 90%;
  height: 300px;
  outline: 1px solid black;
}

&amp;lt;!-- HTML --&amp;gt;
&amp;lt;body&amp;gt;
    &amp;lt;h1&amp;gt;Wrapper 활용&amp;lt;/h1&amp;gt;
    &amp;lt;form&amp;gt;
        &amp;lt;div class='left wrap'&amp;gt;
            &amp;lt;span&amp;gt;A: &amp;lt;/span&amp;gt;&amp;lt;input type=&quot;text&quot;&amp;gt;
        &amp;lt;/div&amp;gt;
        &amp;lt;div class='right wrap'&amp;gt;
            &amp;lt;span&amp;gt;B: &amp;lt;/span&amp;gt;&amp;lt;input type=&quot;text&quot;&amp;gt;
        &amp;lt;/div&amp;gt;
    &amp;lt;/form&amp;gt;
    &amp;lt;div class='next'&amp;gt;
        &amp;lt;p&amp;gt;다음 요소&amp;lt;/p&amp;gt;
    &amp;lt;/div&amp;gt;
&amp;lt;/body&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 wrap을 form 태그가 감싸고 있으므로 form:after에서 부유 효과를 없애줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부유효과를 없애기 위한 코드는 다음과 같습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1593158439368&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;form:after {
  clear: both; display: block; content: ''
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;codepen&quot; style=&quot;height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;&quot; data-height=&quot;300&quot; data-default-tab=&quot;html,result&quot; data-slug-hash=&quot;mdmZoVy&quot; data-user=&quot;cocoder16&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;See the Pen by cocoder16 (&lt;a href=&quot;https://codepen.io/cocoder16&quot;&gt;@cocoder16&lt;/a&gt;) on &lt;a href=&quot;https://codepen.io&quot;&gt;CodePen&lt;/a&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>CSS</category>
      <category>CSS</category>
      <category>html</category>
      <category>wrap</category>
      <category>데스크탑 우선 개발</category>
      <category>레이아웃</category>
      <category>모바일 우선 개발</category>
      <category>반응형</category>
      <category>반응형 웹</category>
      <category>반응형 웹개발</category>
      <author>cocoder16</author>
      <guid isPermaLink="true">https://cocoder16.tistory.com/38</guid>
      <comments>https://cocoder16.tistory.com/38#entry38comment</comments>
      <pubDate>Mon, 23 Aug 2021 08:00:19 +0900</pubDate>
    </item>
    <item>
      <title>반응형 웹 만드는 법 (ft. 디바이스 종류별 width값)</title>
      <link>https://cocoder16.tistory.com/37</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;반응형 웹을 만드는 방법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반응형 웹이란 각 디바이스의 크기와 뷰포트의 크기를 고려하여 어떠한 크기에서도 서비스를 제대로 화면에 보여주는 웹을 말합니다. 반응형 웹은 흑과 백처럼 이분법으로 판단하여 반응형 웹인지 아닌지를 판단할 수 있는 것이 아닙니다. 흑과 백에 더해 회색도 있어 얼마나 다양한 디바이스 화면과 다양한 사이즈에 대응하고 있는지에 따라 흑에 가까워질 수도, 백에 가까워질 수도 있습니다. 그럼에도 불구하고 우리는 반응형 웹이라고 부를 수 있는 기준이 필요합니다. 통상적으로 다음 두 가지 조건에 대해 어느 정도 크게 만족하고 있으면 반응형 웹이라고 부릅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;데스크탑, 태블릿, 모바일 디바이스에 대해 각각 서비스를 문제없이 이용할 수 있는 화면을 제공한다.&lt;/li&gt;
&lt;li&gt;같은 디바이스여도 유저가 사용하기에 무리가 없는 범주에서 뷰포트(viewport) 크기를 변경하는 경우 즉시 반응하여 그에 맞게 적절한 화면을 보여준다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;반응형 웹을 만들기 위해서는 CSS 코드에서 크기를 정하는 속성 값들에 대해 % 값들을 잘 사용해주는 것은 필수입니다. 고정 크기보다는 가변 크기를 지정함으로써 다양한 창의 크기에 대해 바로 대응할 수 있습니다. CSS를 사용하는 방법은 워낙 다양하기에 내공을 끊임없이 쌓아가야 할 부분입니다. 그러므로 이번 포스팅에서는 금방 익힐 수 있으나 반응형 웹을 만들기 위한 필수 지식인 viewport와 미디어 쿼리를 다뤄볼 것입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;viewport 정의&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;viewport는 display상에서 웹을 보여주고 있는 화상표시 영역을 말합니다. 데스크탑 환경에서 사용자가 브라우저 창의 크기를 조절하면 그에 따라 보여지는 웹의 사이즈도 똑같이 변합니다. 즉, 데스크탑에서 viewport는 브라우저 창의 크기에 따라 변합니다. 모바일 환경에서는 사용자가 브라우저의 창의 크기를 조절하는 일보다는 확대와 축소를 하는 일이 더 많습니다. 확대와 축소를 해도 모바일에서의 viewport는 변하지 않습니다. 반응형 웹을 만들 때에는 이런 점들을 고려해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;viewport 관련 설정하는 법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;html의 meta tag를 이용하면 viewport와 관련된 여러 설정들을 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;head태그 안에 작성하며 name과 content 속성을 통해 원하는 설정을 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* 예시 코드&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1629037728232&quot; class=&quot;html xml&quot; style=&quot;display: block; overflow: auto; padding: 20px; color: #383a42; background: #f8f8f8; font-size: 14px; font-family: 'SF Mono', Menlo, Consolas, Monaco, monospace; border: 1px solid #ebebeb; line-height: 1.71; margin: 20px auto 0px; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1&quot;&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;viewport content 속성 값 정리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;width=device-width&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹 페이지의 너비를 디바이스 너비에 맞추겠다는 뜻입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이와 다르게 height=device-height을 사용하면 웹 페이지의 높이를 디자이스 높이에 맞추게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상하 스크롤이 익숙한 대부분의 웹은 width=device-width를 사용하면 되고, 좌우 스크롤이 주로 쓰이는 웹은 height=device-height을 사용하면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;initial-scale&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초기 화면 배율을 설정합니다. 1은 100%와 같습니다. 대부분 1을 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;minimum-scale&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최소 화면 배율 설정입니다. 화면이 극단적으로 축소되는 것을 방지해줍니다. 다행히 따로 설정하지 않아도 default값이 있습니다. 그러니 test를 해보고 필요하다고 판단되는 경우에만 사용하도록 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;maximum-scale&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반대로 최대 화면 배율 설정입니다. 화면이 극단적으로 확대되는 것을 방지해줍니다. 마찬가지로 default값이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;user-scalable=no&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유저가 크기 조절을 할 수 없게 막습니다. 반응형 웹 디자인의 목적은 사용자의 편의성을 최대한 고려하여 배려해주는 것이기 때문에 이 속성을 사용할 이유는 거의 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div class=&quot;revenue_unit_item adsense  responsive&quot;&gt;&lt;ins class=&quot;adsbygoogle&quot; style=&quot;display: block;&quot; data-ad-host=&quot;ca-host-pub-9691043933427338&quot; data-ad-client=&quot;ca-pub-9807016842906892&quot; data-ad-format=&quot;auto&quot;&gt;&lt;/ins&gt;
&lt;script&gt;(adsbygoogle = window.adsbygoogle || []).push({}); &lt;/script&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;미디어 쿼리 사용법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;미디어 쿼리는 viewport의 width나 height 값에 따라 구간별로 다른 CSS 속성을 적용하게 해주는 일종의 프로그래밍 언어의 '조건문'같은 역할을 합니다. 이것을 사용하는 전략은 크게 두 가지가 있습니다. 하나는 데스크탑 우선 개발에 따르는 것이고 다른 하나는 모바일 우선 개발에 따르는 것입니다. 데스크탑 우선 개발인 경우 데스크탑에 최적화되게 css코드를 만든 후 미디어 쿼리로 사이즈가 더 작은 디바이스에 최적화된 예외코드를 만들어줍니다. 반대로 모바일 우선 개발은 모바일에 최적화된 css코드를 만든 후 미디어쿼리로 사이즈가 더 큰 디바이스에 최적화된 예외를 만들어 줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1585070520050&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* 데스크탑 우선 개발 */

/* 데스크탑에 최적화된 CSS 코드 */
@media (max-width: 768px) {
    /* 모바일에 최적화된 예외 CSS 코드*/
}

/* 모바일 우선 개발 */

/* 모바일에 최적화된 CSS 코드 */
@media (min-width: 992px) {
    /* 데스크탑에 최적화된 예외 CSS 코드*/
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;미디어 쿼리와 오버라이딩&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;미디어 쿼리 내에 따로 작성한 CSS코드는 윗 줄에서 미리 작성해놓은 CSS코드에 오버라이딩이 됩니다. 즉, 같은 선택자, 같은 속성에 대해 위에서 작성한 속성값과 미디어쿼리 내에서의 속성값이 충돌하면 미디어쿼리 내에서 작성한 값이 선택됩니다. 물론 이것은 미디어쿼리 코드를 본래 코드보다 더 밑에 작성했을 때의 얘기입니다. CSS는 같은 선택자에 대해 더 밑에 있는 줄에서 작성한 코드의 값을 채택합니다. 미디어 쿼리는 예외를 작성하므로 상단보다는 하단에 작성하는 것이 더 적합합니다. 예외에 대해 오버라이딩을 하기 위해서 반드시 CSS 파일 내에서 가장 하단에 위치해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* 잘못된 예시 코드&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1629039626494&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@media (max-width: 500px) {
  li {
    width: 100px;
  }
}

li {
  width: 200px;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 미디어 쿼리를 본래 코드보다 더 상단에 작성하면 viewport width가 500px이하인 경우에도 미디어 쿼리 내에서 작성한 width: 100px은 적용되지 않고 더 아래쪽에 위치한 width: 200px만이 적용됩니다. 따라서 미디어쿼리는 무용지물이 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동일한 선택자에 대해 미디어쿼리 안에 있는 속성만 적용되는 게 아니라 위에서 작성한 코드도 같이 적용된다는 것도 유의해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 같은 선택자에 대해 본래 코드에서 top offset을 설정하고 미디어 쿼리에서 bottom offset을 설정해주면 top offset값과 bottom offset값 둘다 적용됩니다. 이 경우 해당 요소에 고정 height값이 적용되어 있는 상태라면, 우선 순위에 따라 bottom offset은 적용되지 않고 top offset만 적용됩니다. 예시에서는 bottom offset만 적용되기를 의도했으므로, 이런 경우에는 본래 코드에서 설정한 top offset을 초기화 시키는 코드를 미디어쿼리 내에 작성해줘야 bottom offset이 제대로 적용될 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* 예시 코드&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1629039821435&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;.snow {
  position: fixed;
  width: 30px;
  height: 30px;
  top: 10px;
}

@media (max-width: 500px) {
  .snow {
    top: initial;
    bottom: 10px;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;디바이스별 width값&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;디바이스 별로 다른 화면 대응을 하기 위해 미디어 쿼리를 쓰게 되면 디바이스별 표준 width값이 궁금하실 수 있겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 작은 디바이스: &amp;lt; 576px&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모바일 디바이스: &amp;lt;= 768px&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;태블릿 디바이스: 768px &amp;lt; x &amp;lt;= 992px&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데스크탑: 992px &amp;lt; x &amp;lt;= 1200px&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;큰 화면 데스크탑: 1200px &amp;lt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 신형 디바이스가 끊임없이 출시되기 때문에 개발시점에서 시장 상황을 잘 보고 그때그때 적절한 판단을 내리면 될 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>CSS</category>
      <category>CSS</category>
      <category>media query</category>
      <category>viewport</category>
      <category>디바이스</category>
      <category>미디어쿼리</category>
      <category>반응형</category>
      <category>반응형 웹</category>
      <category>반응형 웹 디자인</category>
      <category>뷰포트</category>
      <author>cocoder16</author>
      <guid isPermaLink="true">https://cocoder16.tistory.com/37</guid>
      <comments>https://cocoder16.tistory.com/37#entry37comment</comments>
      <pubDate>Mon, 16 Aug 2021 08:00:06 +0900</pubDate>
    </item>
    <item>
      <title>리액트 렌더링 최적화하는 8가지 방법과 고찰</title>
      <link>https://cocoder16.tistory.com/36</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;서론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글은 함수형 컴포넌트&lt;span&gt;, &lt;/span&gt;클래스형 컴포넌트 상관없이 공통적으로 적용되는 렌더링 최적화 이야기와 &lt;span&gt;hooks&lt;/span&gt;를 사용하는 함수형 컴포넌트에서 구체적으로 어떤 기능들을 사용해 렌더링 최적화를 사용할 수 있는지를 작성해봤습니다&lt;span&gt;. &lt;/span&gt;클래스형 컴포넌트에서의 구체적인 기능들은 다루지 않습니다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;예제가 많습니다. github에서 전체 코드를 볼 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;a href=&quot;https://github.com/cocoder16/react-rendering-optimization&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/cocoder16/react-rendering-optimization&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1623553579845&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;cocoder16/react-rendering-optimization&quot; data-og-description=&quot;Contribute to cocoder16/react-rendering-optimization development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/cocoder16/react-rendering-optimization&quot; data-og-url=&quot;https://github.com/cocoder16/react-rendering-optimization&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/3FlQV/hyKxGCiR81/76Zjk7kOyAtUCGuMEcQUf0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/cocoder16/react-rendering-optimization&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/cocoder16/react-rendering-optimization&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/3FlQV/hyKxGCiR81/76Zjk7kOyAtUCGuMEcQUf0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;cocoder16/react-rendering-optimization&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Contribute to cocoder16/react-rendering-optimization development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;clone을 받은 후 local에서 실행하여 실제로 동작시키면서 소스코드를 같이 보면 더 빠르게 이해할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;1. state &lt;/span&gt;선언은 어디서 하는 게 좋을까요&lt;span&gt;?&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리액트는 특정 &lt;span&gt;state&lt;/span&gt;가 변경되면 그 &lt;span&gt;state&lt;/span&gt;가 선언된 컴포넌트와 그 하위 컴포넌트들을 모두 리렌더링 시킵니다&lt;span&gt;. &lt;/span&gt;따라서 &lt;span&gt;state&lt;/span&gt;가 선언되는 위치를 잘 설계하는 것은 리렌더링 횟수에 엄청난 영향을 끼칩니다&lt;span&gt;. &lt;/span&gt;기본적으로 &lt;span&gt;state&lt;/span&gt;의 선언위치는 이렇습니다&lt;span&gt;. &lt;/span&gt;해당&lt;span&gt; state&lt;/span&gt;를 사용하는 컴포넌트들을 잘 구분해놓은 뒤 그 컴포넌트들 중 가장 최상위 컴포넌트에 선언합니다&lt;span&gt;. &lt;/span&gt;만약 그 &lt;span&gt;state&lt;/span&gt;를 사용하는 최상위 컴포넌트보다 더 상위 컴포넌트에 &lt;span&gt;state&lt;/span&gt;를 선언하면 &lt;span&gt;state&lt;/span&gt;를 사용하지 않는 더 많은 컴포넌트들이 &lt;span&gt;state&lt;/span&gt;변경에 의해 불필요한 리렌더링을 겪게 됩니다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 다음과 같은 컴포넌트 구조가 있다고 합시다.&lt;/p&gt;
&lt;pre id=&quot;code_1623553285082&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Index
ㄴGroup
ㄴUserList
  ㄴUserItem&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UserList와 UserItem에서만 사용되는 users state가 있습니다. 이 users state는 UserItem에서 보여줘야 할 데이터들을 가지고 있습니다. 이 데이터는 두 컴포넌트에서만 사용하기 때문에 그 중 가장 상위 컴포넌트인 UserList에 선언해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- UserList.js&lt;/p&gt;
&lt;pre id=&quot;code_1623553473667&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useState } from &quot;react&quot;;

import UserItem from &quot;components/section/examples/example1/UserItem&quot;;
import Button from &quot;components/atom/Button&quot;;

function UserList() {
  console.log(&quot;UserList component render&quot;);

  const [users, setUsers] = useState([
    {
      id: 0,
      name: &quot;Kim&quot;,
      age: 27,
    },
    {
      id: 1,
      name: &quot;Jo&quot;,
      age: 25,
    },
  ]);

  const addUser = () =&amp;gt; {
    setUsers([
      ...users,
      {
        id: 2,
        name: &quot;Jung&quot;,
        age: 30,
      },
    ]);
  };

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;Button
        value=&quot;새 유저 생성&quot;
        disabled={users.length &amp;gt;= 3}
        onClick={addUser}
      /&amp;gt;
      {users.map(user =&amp;gt; {
        return (
          &amp;lt;UserItem
            key={user.id}
            id={user.id}
            name={user.name}
            age={user.age}
          /&amp;gt;
        );
      })}
    &amp;lt;/div&amp;gt;
  );
}

export default UserList;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 만약 이 users state를 UserList보다 더 상위 컴포넌트인 Index에 선언하면 어떻게 될까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;users state&lt;/span&gt;가 변경되면 &lt;span&gt;index.js&lt;/span&gt;가 리렌더링되고 그 하위 컴포넌트가 모두 리렌더링 됩니다&lt;span&gt;. &lt;/span&gt;이에 따라 이전과 다르게 &lt;span&gt;users &lt;/span&gt;데이터를 사용하지 않는 &lt;span&gt;Index&lt;/span&gt;컴포넌트와 &lt;span&gt;Group &lt;/span&gt;컴포넌트까지 리렌더링이 발생하게 됩니다&lt;span&gt;. &lt;/span&gt;바람직하지 않죠&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;이번 섹션에 대한 전체 예시 코드는 &lt;a href=&quot;https://github.com/cocoder16/react-rendering-optimization/tree/main/src/components/section/examples/example1&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Example1&lt;/a&gt; 과 &lt;a href=&quot;https://github.com/cocoder16/react-rendering-optimization/tree/main/src/components/section/examples/example2&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Example2&lt;/a&gt; 에서 볼 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;2. &lt;/span&gt;객체 타입의 &lt;span&gt;state&lt;/span&gt;는 최대한 분할하여 선언합니다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체가 크고 복잡한 구조인 경우 분할할 수 있는 만큼 최대한 분할하는 것이 좋습니다&lt;span&gt;. &lt;/span&gt;해당 &lt;span&gt;state&lt;/span&gt;에서 일부의 프로퍼티만 사용하는 하위 컴포넌트가 있다면&lt;span&gt;, &lt;/span&gt;그 컴포넌트는 해당 프로퍼티가 변경될 때에만 리렌더링 되는 것이 바람직합니다&lt;span&gt;. &lt;/span&gt;만약 복잡한 객체로 선언된 &lt;span&gt;state&lt;/span&gt;를 분할하지 않으면&lt;span&gt;, &lt;/span&gt;하위 컴포넌트가 사용하지 않는 다른 프로퍼티의 값이 업데이트될 때에도 리렌더링이 발생하므로 렌더링 최적화의 대상이 됩니다&lt;span&gt;. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전 예제에서 &lt;span&gt;group data&lt;/span&gt;까지 &lt;span&gt;state&lt;/span&gt;로 관리해봅시다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같은 타입의&amp;nbsp;&lt;span&gt;state&lt;/span&gt;를 가장 상위 컴포넌트인&amp;nbsp;&lt;span&gt;index.js&lt;/span&gt;에 선언할 수 있습니다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;- index.js&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1623553799288&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useState } from &quot;react&quot;;

import Group from &quot;components/section/examples/example3/Group&quot;;
import UserList from &quot;components/section/examples/example3/UserList&quot;;

function Example3() {
  const [state, setState] = useState({
    group: {
      name: &quot;coco&quot;,
      description: &quot;rendering optimization pracitice&quot;,
    },
    users: [
      {
        id: 0,
        name: &quot;Kim&quot;,
        age: 27,
      },
      {
        id: 1,
        name: &quot;Jo&quot;,
        age: 25,
      },
    ],
  });

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;Group group={state.group} /&amp;gt;
      &amp;lt;UserList
        users={state.users}
        setUsers={newUsers =&amp;gt; {
          setState({ ...state, users: newUsers });
        }}
      /&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}

export default Example3;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 만약&lt;span&gt; users &lt;/span&gt;배열에 원소가 하나 추가되면 어떻게 될까요&lt;span&gt;?&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;users&lt;/span&gt;데이터를 이용하는 &lt;span&gt;UserList&lt;/span&gt;는 리렌더링되어야 합니다&lt;span&gt;. &lt;/span&gt;그런데 굳이 &lt;span&gt;users&lt;/span&gt;데이터를 이용하지 않는 &lt;span&gt;Group &lt;/span&gt;컴포넌트까지도 &lt;span&gt;state&lt;/span&gt;변경으로 인해 리렌더링 될 수 있습니다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번엔 &lt;span&gt;group state&lt;/span&gt;와 &lt;span&gt;users state&lt;/span&gt;를 나눠서 선언해보겠습니다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- index.js&lt;/p&gt;
&lt;pre id=&quot;code_1623553998181&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useState } from &quot;react&quot;;

import Group from &quot;components/section/examples/example4/Group&quot;;
import UserList from &quot;components/section/examples/example4/UserList&quot;;

function Example4() {
  const [group] = useState({
    name: &quot;coco&quot;,
    description: &quot;rendering optimization pracitice&quot;,
  });
  const [users, setUsers] = useState([
    {
      id: 0,
      name: &quot;Kim&quot;,
      age: 27,
    },
    {
      id: 1,
      name: &quot;Jo&quot;,
      age: 25,
    },
  ]);

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;Group group={group} /&amp;gt;
      &amp;lt;UserList users={users} setUsers={setUsers} /&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}

export default Example4;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 나눈 후 다시 &lt;span&gt;users &lt;/span&gt;배열에 원소를 하나추가하는 경우 어떻게 될까요&lt;span&gt;?&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전과 마찬가지로 &lt;span&gt;users state&lt;/span&gt;변화로 인해 &lt;span&gt;index &lt;/span&gt;컴포넌트가 리렌더링되고 이에 따라 하위 컴포넌트들이 리렌더링 되면서 &lt;span&gt;Group&lt;/span&gt;컴포넌트까지도 리렌더링이 됩니다&lt;span&gt;. &lt;/span&gt;그러면 굳이 이렇게&lt;span&gt; state&lt;/span&gt;를 분할해야하는 이유는 무엇일까요&lt;span&gt;?&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그건 바로 이렇게 분할함으로써&lt;span&gt;, &lt;/span&gt;구조적으로 &lt;span&gt;group state&lt;/span&gt;는 &lt;span&gt;Group &lt;/span&gt;컴포넌트에서만 사용하고&lt;span&gt;, users state&lt;/span&gt;는 &lt;span&gt;UserList &lt;/span&gt;컴포넌트에서만 사용한다는 것이 명확하게 보이게 되고&lt;span&gt;, &lt;/span&gt;더 하위컴포넌트에 내려서 선언해야 할 필요성을 알게 되는 데에 의의가 있습니다&lt;span&gt;. &lt;/span&gt;우리는 &lt;span&gt;state&lt;/span&gt;객체를 두개로 분할함으로써&lt;span&gt;, &lt;/span&gt;더 나은 설계를 할 수 있게 되었습니다&lt;span&gt;. &lt;/span&gt;그렇게 한 코드가 가장 첫 예시와 같습니다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 섹션에 대한 전체 예시 코드는&amp;nbsp;&lt;a href=&quot;https://github.com/cocoder16/react-rendering-optimization/tree/main/src/components/section/examples/example3&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Example3&amp;nbsp;&lt;/a&gt;과&amp;nbsp;&lt;a href=&quot;https://github.com/cocoder16/react-rendering-optimization/tree/main/src/components/section/examples/example4&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Example4&lt;/a&gt;&amp;nbsp;에서 볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;3. hooks&lt;/span&gt;에서&lt;span&gt; shouldComponentUpdate&lt;/span&gt;를 대체하는 방법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;shouldComponentUpdate&lt;/span&gt;는 클래스형 컴포넌트에서 리렌더링 여부를 결정하는 로직을 만드는 생명주기 메소드입니다. 이것은 리렌더링을 방지할 수 있기 때문에 렌더링 최적화를 위해서 클래스형 컴포넌트에서 자주 사용되던 메소드 입니다. 그러나 함수형 컴포넌트는 생명주기 메소드를 사용할 수 없기 때문에 &lt;span&gt;shouldComponentUpdate&lt;/span&gt;를 이용한 리렌더링 방지를 사용할 수 없습니다&lt;span&gt;. &lt;/span&gt;리액트 공식 문서에서는 &lt;span&gt;shouldComponentUpdate&lt;/span&gt;를 구현하는 방법으로 &lt;span&gt;React.memo&lt;/span&gt;를 제시하고 있습니다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;4. React.memo&lt;/span&gt;를 이용한 컴포넌트 메모이제이션 방법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;React.memo&lt;/span&gt;는 컴포넌트를 래핑하여 &lt;span&gt;props&lt;/span&gt;를 비교하여 리렌더링을 막을 수 있는 메모이제이션 기법을 제공하는 함수입니다&lt;span&gt;. React.memo&lt;/span&gt;는 &lt;span&gt;Hook&lt;/span&gt;이 아니기 떄문에 클래스형 컴포넌트에서도 사용할 수 있습니다&lt;span&gt;. &lt;/span&gt;함수형 컴포넌트에서는 &lt;span&gt;shouldComponentUpdate&lt;/span&gt;를 사용할 수 없는데 리액트 공식 문서에서는 그 대안으로 &lt;span&gt;React.memo&lt;/span&gt;를 제시하고 있습니다&lt;span&gt;. React.memo&lt;/span&gt;는 콜백함수를 이용해 메모이제이션을 적용할지 여부를 판단할 수도 있습니다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;이번에는 Group이 없는 더 단순한 컴포넌트 구조의 예시를 봅시다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1623554232978&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Index
ㄴUserList
  ㄴUserItem&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; React.memo&lt;/span&gt;를 이용해 &lt;span&gt;UserList&lt;/span&gt;의 길이가 변할 때 새로 변경된 &lt;span&gt;UserItem&lt;/span&gt;만 렌더링하고 기존에 이미 렌더링된 &lt;span&gt;UserItem&lt;/span&gt;들은 리렌더링 되지 않도록 만들어봅니다&lt;span&gt;.&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;- UserList.js&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1623554474837&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useState } from &quot;react&quot;;

import UserItem from &quot;components/section/examples/example5/UserItem&quot;;
import Button from &quot;components/atom/Button&quot;;

function UserList() {
  console.log(&quot;UserList component render&quot;);

  const [users, setUsers] = useState([
    {
      id: 0,
      name: &quot;Kim&quot;,
      age: 27,
      score: 80,
    },
    {
      id: 1,
      name: &quot;Jo&quot;,
      age: 25,
      score: 70,
    },
  ]);

  const addUser = () =&amp;gt; {
    setUsers([
      ...users,
      {
        id: 2,
        name: &quot;Jung&quot;,
        age: 30,
        score: 90,
      },
    ]);
  };

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;Button
        value=&quot;새 유저 생성&quot;
        disabled={users.length &amp;gt;= 3}
        onClick={addUser}
      /&amp;gt;
      {users.map(user =&amp;gt; {
        return &amp;lt;UserItem key={user.id} user={user} /&amp;gt;;
      })}
    &amp;lt;/div&amp;gt;
  );
}

export default UserList;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- UserItem.js&lt;/p&gt;
&lt;pre id=&quot;code_1623554295372&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import React from &quot;react&quot;;

function UserItem({ user }) {
  console.log(`UserItem (id: ${user.id}) component render`);

  return (
    &amp;lt;div className=&quot;user-item&quot;&amp;gt;
      &amp;lt;div&amp;gt;이름: {user.name}&amp;lt;/div&amp;gt;
      &amp;lt;div&amp;gt;나이: {user.age}&amp;lt;/div&amp;gt;
      &amp;lt;div&amp;gt;점수: {user.score}&amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}

export default React.memo(UserItem);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메모이제이션 기법을 적용했으므로 새 유저 생성 버튼을 눌러 users배열의 길이를 변화시켜 UserList.js를 리렌더링 시키더라도 새로 추가된 UserItem만 새로 렌더되고 이미 렌더된 UserItem들은 리렌더링 되지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 섹션에 대한 전체 예시코드는 &lt;a href=&quot;https://github.com/cocoder16/react-rendering-optimization/tree/main/src/components/section/examples/example5&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Example5&lt;/a&gt;&amp;nbsp;에서 볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div class=&quot;revenue_unit_item adsense  responsive&quot;&gt;&lt;ins class=&quot;adsbygoogle&quot; style=&quot;display: block;&quot; data-ad-host=&quot;ca-host-pub-9691043933427338&quot; data-ad-client=&quot;ca-pub-9807016842906892&quot; data-ad-format=&quot;auto&quot;&gt;&lt;/ins&gt;
&lt;script&gt;(adsbygoogle = window.adsbygoogle || []).push({}); &lt;/script&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;5 &lt;/span&gt;컴포넌트를 매핑할 때에는 key값으로 &lt;span&gt;index&lt;/span&gt;를 사용하지 않습니다&lt;span&gt;.&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리액트에서 컴포넌트를 매핑할 때에는 반드시 고유 &lt;span&gt;key&lt;/span&gt;를 부여하도록 강제하고 있습니다&lt;span&gt;. &lt;/span&gt;저는 얼마 전까지만 해도 &lt;span&gt;key&lt;/span&gt;값으로 배열의 &lt;span&gt;index&lt;/span&gt;값을 버릇처럼 넣었었는데 어느날 이게 얼마나 안좋은 습관인지 알게되었습니다. 어떤 배열에 중간에 어떤 요소가 삽입되면 그 중간보다 이후에 위치한 요소들은 전부 인덱스가 변경됩니다&lt;span&gt;. &lt;/span&gt;이로 인해 &lt;span&gt;key&lt;/span&gt;값이 변경되고 리마운트가 일어나게 되죠&lt;span&gt;. &lt;/span&gt;또한&lt;span&gt;, &lt;/span&gt;데이터가 &lt;span&gt;key&lt;/span&gt;와 매치가 안되어 서로 꼬이는 부작용도 발생합니다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에는 UserList에서 UserItem을 매핑할때 key에 users 배열의 index를 넣어주고, 배열의 맨 앞에 원소를 추가하는 버튼을 만들어봅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- UserList.js&lt;/p&gt;
&lt;pre id=&quot;code_1623554579476&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useState } from &quot;react&quot;;

import UserItem from &quot;components/section/examples/example6/UserItem&quot;;
import Button from &quot;components/atom/Button&quot;;

function UserList() {
  console.log(&quot;UserList component render&quot;);

  const [users, setUsers] = useState([
    {
      id: 0,
      name: &quot;Kim&quot;,
      age: 27,
      score: 80,
    },
    {
      id: 1,
      name: &quot;Jo&quot;,
      age: 25,
      score: 70,
    },
  ]);

  const addUser = () =&amp;gt; {
    setUsers([
      {
        id: 2,
        name: &quot;Jung&quot;,
        age: 30,
        score: 90,
      },
      ...users,
    ]);
  };

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;Button
        value=&quot;배열의 맨 앞에 아이템 추가&quot;
        disabled={users.length &amp;gt;= 3}
        onClick={addUser}
      /&amp;gt;
      {users.map((user, index) =&amp;gt; {
        return &amp;lt;UserItem key={index} user={user} /&amp;gt;;
      })}
    &amp;lt;/div&amp;gt;
  );
}

export default UserList;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배열의 맨 앞에 아이템 추가 버튼을 눌러봅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에 key=0과 key=1에 매치되어있던 UserItem 컴포넌트들이 연결이 끊기고 각각 key=1, key=2에 새로 매치됩니다. 이로 인해 UserItem에는 메모이제이션이 적용되었음에도 불구하고 key값이 달라졌기 때문에 새로 마운트 됩니다. 즉, 메모이제이션이 쓸모가 없어지는 것이죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 key값에 고유 id를 넣어주면, 배열의 중간에 어떤 요소가 삽입되더라도 기존에 있는 원소들이 가지고 있는 key가 끊어질 위험이 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 섹션에 대한 전체 예시코드는&amp;nbsp;&lt;a href=&quot;https://github.com/cocoder16/react-rendering-optimization/tree/main/src/components/section/examples/example6&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Example6&lt;/a&gt; 과&amp;nbsp;&lt;a href=&quot;https://github.com/cocoder16/react-rendering-optimization/tree/main/src/components/section/examples/example7&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Example7&lt;/a&gt;&amp;nbsp;에서 볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;6. useMemo&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 컴포넌트 내에 어떤 함수가 값을 리턴하는데 많은 시간을 소요한다면 이 컴포넌트가 리렌더링 될 때마다 함수가 호출되면서 많은 시간을 소요하게 될 것입니다&lt;span&gt;. &lt;/span&gt;그리고 그 함수가 반환하는 값을 하위 컴포넌트가 사용한다면 그 하위 컴포넌트는 매 함수호출마다 새로운 값을 받아 리렌더링할 것입니다&lt;span&gt;. useMemo&lt;/span&gt;는 종속 변수들이 변하지 않으면 함수를 굳이 다시 호출하지 않고 이전에 반환한 참조값을 재사용합니다&lt;span&gt;. &lt;/span&gt;즉&lt;span&gt;, &lt;/span&gt;함수 호출 시간도 세이브할 수 있고 같은 값을 &lt;span&gt;props&lt;/span&gt;로 받는 하위 컴포넌트의 리렌더링도 방지할 수 있습니다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;이번에는 Average 컴포넌트를 추가해보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1623554963558&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Index
ㄴUserList
  ㄴAverage
  ㄴUserItem&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Average는 유저들의 평균점수를 출력하고 싶습니다. users데이터가 변할 때마다 새로 평균값을 계산하여 Average에 평균값을 전달하는 방식입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- UserList.js&lt;/p&gt;
&lt;pre id=&quot;code_1623555078032&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import { useState } from &quot;react&quot;;

import Average from &quot;components/section/examples/example8/Average&quot;;
import UserItem from &quot;components/section/examples/example8/UserItem&quot;;
import Button from &quot;components/atom/Button&quot;;

function UserList() {
  console.log(&quot;UserList component render&quot;);

  const [text, setText] = useState(&quot;&quot;);
  const [users, setUsers] = useState([
    {
      id: 0,
      name: &quot;Kim&quot;,
      age: 27,
      score: 80,
    },
    {
      id: 1,
      name: &quot;Jo&quot;,
      age: 25,
      score: 70,
    },
  ]);

  const average = (function () {
    console.log(&quot;calculate average. It takes long time !!&quot;);

    return users.reduce((result, user) =&amp;gt; {
      return result + user.score / users.length;
    }, 0);
  })();

  const addUser = () =&amp;gt; {
    setUsers([
      {
        id: 2,
        name: &quot;Jung&quot;,
        age: 30,
        score: 90,
      },
      ...users,
    ]);
  };

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;div&amp;gt;
        &amp;lt;input
          type=&quot;text&quot;
          value={text}
          placeholder=&quot;아무 내용이나 입력하세요.&quot;
          onChange={event =&amp;gt; setText(event.target.value)}
        /&amp;gt;
      &amp;lt;/div&amp;gt;
      &amp;lt;Button
        value=&quot;새 유저 생성&quot;
        disabled={users.length &amp;gt;= 3}
        onClick={addUser}
      /&amp;gt;
      &amp;lt;Average average={average} /&amp;gt;
      {users.map(user =&amp;gt; {
        return &amp;lt;UserItem key={user.id} user={user} /&amp;gt;;
      })}
    &amp;lt;/div&amp;gt;
  );
}

export default UserList;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 예제에서 평균값을 구하는 함수는 간단한 함수이지만, 실제로 이 평균값을 구하는 연산이 엄청 오랜 시간이 걸린다고 가정해봅시다. 그러면 UserList 컴포넌트가 리렌더링 될 때마다 매번 이 비싼 연산을 수행해야만 합니다. 비효율적이죠 최적화가 필요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조금더 와닿는 예제가 되기 위해 input 태그를 추가했습니다. input에 텍스트를 입력할때마다 text state가 변화하도록 했습니다. 텍스트를 마구마구 입력해보세요. 그때마다 average를 매번 새로구하고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 useMemo를 적용해보겠습니다. 이것을 적용하면 dependencies에 있는 데이터가 변할 때에만 평균을 구하는 연산을 수행하도록 할 수 있습니다. dependencies에는 users state를 넣어줄 것입니다. 그러면 input에 아무리 텍스트를 입력하여 text state를 변화시키더라도 average를 구하는 함수는 실행되지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1623555323746&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  const average = useMemo(() =&amp;gt; {
    console.log(&quot;calculate average. It takes long time !!&quot;);
    return users.reduce((result, user) =&amp;gt; {
      return result + user.score / users.length;
    }, 0);
  }, [users]);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 useMemo를 이용해 불필요한 함수의 재실행은 방지했습니다. 하지만 그래도 input에 타이핑할 때마다 UserList가 리렌더링되고 그에 따라 하위컴포넌트인 Average 컴포넌트도 리렌더링됩니다. average를 구하는 연산을 수행하지 않았는데도 불필요하게 말이죠. 따라서 Average 컴포넌트에 React.memo를 이용해 메모이제이션을 적용해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Average.js&lt;/p&gt;
&lt;pre id=&quot;code_1623555429016&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import React from &quot;react&quot;;

function Average({ average }) {
  console.log(&quot;Average component render&quot;);

  return &amp;lt;div&amp;gt;평균: {average}&amp;lt;/div&amp;gt;;
}

export default React.memo(Average);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자 이제 users state가 변할때에만 average 구하는 함수가 수행되고 average값이 바뀌었을 때에만 Average 컴포넌트가 리렌더링됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 섹션에 대한 전체 예시코드는 &lt;a href=&quot;https://github.com/cocoder16/react-rendering-optimization/tree/main/src/components/section/examples/example8&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Example8&lt;/a&gt;, &lt;a href=&quot;https://github.com/cocoder16/react-rendering-optimization/tree/main/src/components/section/examples/example9&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Example9&lt;/a&gt;, &lt;a href=&quot;https://github.com/cocoder16/react-rendering-optimization/tree/main/src/components/section/examples/example10&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Example10&lt;/a&gt; 에서 볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div class=&quot;revenue_unit_item adsense  responsive&quot;&gt;&lt;ins class=&quot;adsbygoogle&quot; style=&quot;display: block;&quot; data-ad-host=&quot;ca-host-pub-9691043933427338&quot; data-ad-client=&quot;ca-pub-9807016842906892&quot; data-ad-format=&quot;auto&quot;&gt;&lt;/ins&gt;
&lt;script&gt;(adsbygoogle = window.adsbygoogle || []).push({}); &lt;/script&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;7. useCallback&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;useCallback&lt;/span&gt;도 같은 매커니즘으로 렌더링 최적화에 활용할 수 있습니다&lt;span&gt;. &lt;/span&gt;상위 컴포넌트에서 하위컴포넌트로 함수를 &lt;span&gt;props&lt;/span&gt;로 넘겨줄 때 상위 컴포넌트가 리렌더링 될 때마다 상위 컴포넌트 안에 선언된 함수를 새로 생성하기 때문에 그때마다 새 참조 함수를 하위 컴포넌트로 넘겨주게 됩니다&lt;span&gt;. &lt;/span&gt;이에 따라 하위 컴포넌트도 &lt;span&gt;props&lt;/span&gt;가 달라졌으므로 또다시 리렌더링 하게 되는 것이죠&lt;span&gt;. &lt;/span&gt;그러나&lt;span&gt; useCallback&lt;/span&gt;으로 함수를 선언해주면 종속 변수들이 변하지 않으면 굳이 함수를 재생성하지 않고 이전에 있던 참조 변수를 그대로 하위 컴포넌트에 &lt;span&gt;props&lt;/span&gt;로 전달하여 하위 컴포넌트도 &lt;span&gt;props&lt;/span&gt;가 변경되지 않았다고 인지하게 됩니다&lt;span&gt;. &lt;/span&gt;이에 따라 하위 컴포넌트의 리렌더링을 방지할 수 있습니다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에는 Button 컴포넌트에 React.memo를 적용시켰습니다. Button 컴포넌트는 onClick 함수를 props로 받습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Button.js&lt;/p&gt;
&lt;pre id=&quot;code_1623555711405&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import React from &quot;react&quot;;

function Button({ value, className, disabled, onClick, logRender }) {
  if (logRender) {
    console.log(&quot;Button component render&quot;);
  }

  return (
    &amp;lt;button
      type=&quot;button&quot;
      className={`${className} btn btn-light`}
      disabled={disabled}
      onClick={event =&amp;gt; {
        event.preventDefault();
        onClick &amp;amp;&amp;amp; onClick();
      }}&amp;gt;
      {value}
    &amp;lt;/button&amp;gt;
  );
}

export default React.memo(Button);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 onClick 함수는 UserList에서 전달해주고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UserList는 input에 타이핑을 할때마다 리렌더링이 발생합니다. (이전 예제 참고)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 리렌더링마다 addUser라는 함수를 새로 생성하여 Button 컴포넌트에 props로 전달해주고 있습니다. 이에 따라 Button 컴포넌트도 같이 덩달아 리렌더링 되는 것이죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아무리 Button 컴포넌트에 메모이제이션을 적용해도 소용없습니다. 왜냐하면 함수는 객체이고 새로 생성된 함수는 다른 참조 값을 가지기 때문에 Button 입장에서는 새로 생성된 함수를 받을 때 props가 변한 것으로 인지합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 UserList가 리렌더될때마다 addUser함수를 재생성하는 것을 막고싶습니다. 이때 useCallback을 사용하면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1623555920821&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const addUser = useCallback(() =&amp;gt; {
    setUsers([
      {
        id: 2,
        name: &quot;Jung&quot;,
        age: 30,
        score: 90,
      },
      ...users,
    ]);
  }, [users]);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;input에 타이핑을 마구 해보세요. 이제 UserList가 리렌더되어도 Button 컴포넌트는 props에 변화가 없으므로 리렌더링되지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 섹션에 대한 전체 예시코드는&amp;nbsp;&lt;a href=&quot;https://github.com/cocoder16/react-rendering-optimization/tree/main/src/components/section/examples/example11&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Example11&lt;/a&gt;,&amp;nbsp;&lt;a href=&quot;https://github.com/cocoder16/react-rendering-optimization/tree/main/src/components/section/examples/example12&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Example12&lt;/a&gt;&amp;nbsp;에서 볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;8. &lt;/span&gt;하위 컴포넌트의 &lt;span&gt;props&lt;/span&gt;로 객체를 넘겨주는 경우 새 객체 생성을 주의해야 합니다&lt;span&gt;.&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하위 컴포넌트의&lt;span&gt; props&lt;/span&gt;값으로 객체를 넘겨주는 경우가 많이 있습니다&lt;span&gt;. 이 때에는&lt;/span&gt; 컴포넌트 안에서 생성자 함수나 객체 리터럴 등으로 새로 생성한 객체를 넘겨주는 것을 주의해야 합니다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1623556224189&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 생성자 함수
&amp;lt;Component prop={new Obj(&quot;x&quot;)} /&amp;gt;
// 객체 리터럴
&amp;lt;Component prop={{property: &quot;x&quot;}} /&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하는 것은 리덕스 스토어나 &lt;span&gt;props &lt;/span&gt;혹은 선언된 &lt;span&gt;state&lt;/span&gt;에 참조하는 것이 아니라 새로 생성된 객체가 &lt;span&gt;props&lt;/span&gt;로 들어가므로 컴포넌트가 리렌더링 될 때마다 새로운 객체가 생성되어 하위 컴포넌트로 전달되므로&lt;span&gt;, &lt;/span&gt;아무리 렌더링 최적화 기법을 사용해줬다고 하더라도 하위 컴포넌트에 대한 메모이제이션이 되지않습니다. props로 전달한 객체가 동일한 값을 보유하고 있다고 하더라도 새로 생성된 객체는 이전 객체와 다른 참조 주소를 가진 객체이기 때문에 메모이제이션이 통하지 않는 것입니다&lt;span&gt;. &lt;/span&gt;따라서 생성자 함수나 객체 리터럴로 객체를 생성해서 하위 컴포넌트로 넘겨주는 방식보다는&lt;span&gt;, state&lt;/span&gt;를 그대로 하위컴포넌트에 넘겨주어 필요한 데이터 가공을 그 하위컴포넌트에서 해주는 것이 좋습니다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;이에 대한 예제가&amp;nbsp;&lt;a href=&quot;https://github.com/cocoder16/react-rendering-optimization/tree/main/src/components/section/examples/example13&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Example13&lt;/a&gt;,&amp;nbsp;&lt;a href=&quot;https://github.com/cocoder16/react-rendering-optimization/tree/main/src/components/section/examples/example14&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Example14&lt;/a&gt;&amp;nbsp;입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;결론&lt;span&gt;. &lt;/span&gt;리액트 렌더링 최적화 기본 마인드&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리액트는 단방향 하향식 데이터 흐름을 가지고 있습니다&lt;span&gt;. &lt;/span&gt;즉&lt;span&gt;, &lt;/span&gt;데이터는 부모 컴포넌트에서 자식 컴포넌트 방향으로 흘러갑니다&lt;span&gt;. &lt;/span&gt;이 데이터들&lt;span&gt;(props, state)&lt;/span&gt;의 변화는 컴포넌트를 리렌더링시킵니다. state는 그것이 선언된 컴포넌트 내에서 사용되고&lt;span&gt;, props&lt;/span&gt;는 부모 컴포넌트로부터 받은 데이터입니다&lt;span&gt;. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서, 이미 만들어진 프로젝트에서의 렌더링 최적화는 첫째&lt;span&gt;, state&lt;/span&gt;와 &lt;span&gt;props&lt;/span&gt;의 변경을 최소화하는 것과 둘째&lt;span&gt;, state&lt;/span&gt;와 &lt;span&gt;props&lt;/span&gt;의 변경에 의해 불필요한 하위 컴포넌트 리렌더링을 최소화하는 것 두 가지 방향으로 진행됩니다&lt;span&gt;. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미 만들어지지 않은 프로젝트에서의 렌더링 최적화는 프로젝트 설계가 중요합니다&lt;span&gt;. UI측면에서는&lt;/span&gt; 아토믹 디자인을 적극적으로 활용하여 컴포넌트 구조를 명확하고 직관적이고 최소화시키는 것이 좋습니다&lt;span&gt;. &lt;/span&gt;이것을 잘하면 컴포넌트 리렌더링 횟수는 획기적으로 줄일 수 있고 구조 자체가 명확하기 때문에 코드도 쉬워지고 유지보수성도 월등하게 좋아집니다&lt;span&gt;. &lt;/span&gt;데이터 측면에서는 &lt;span&gt;state&lt;/span&gt;의 적절한 설계&lt;span&gt;, API &lt;/span&gt;설계가 중요하게 작용합니다&lt;span&gt;. state&lt;/span&gt;에서는 &lt;span&gt;UI&lt;/span&gt;에서 사용하기 편리한 데이터 구조를 선언하는 것이 좋습니다&lt;span&gt;. API&lt;/span&gt;도 화면기획을 기반으로 의미론적으로 잘 분리된 형태로 쪼개서 만들어야 컴포넌트에서 &lt;span&gt;API&lt;/span&gt;로&lt;span&gt;&lt;span&gt;&amp;nbsp; &lt;/span&gt;&lt;/span&gt;요청할 때&lt;span&gt;, &lt;/span&gt;불필요한 데이터를 응답데이터로 받지 않고 필요한 데이터만 적절하게 받아 리소스와 로직 낭비를 하지 않을 수 있습니다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>React.js</category>
      <category>optimization</category>
      <category>react</category>
      <category>Rendering</category>
      <category>렌더링</category>
      <category>리액트</category>
      <category>최적화</category>
      <author>cocoder16</author>
      <guid isPermaLink="true">https://cocoder16.tistory.com/36</guid>
      <comments>https://cocoder16.tistory.com/36#entry36comment</comments>
      <pubDate>Mon, 14 Jun 2021 08:00:07 +0900</pubDate>
    </item>
    <item>
      <title>[CSS 레이아웃] display: table을 이용하여 메뉴 레이아웃 만들기</title>
      <link>https://cocoder16.tistory.com/35</link>
      <description>&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;메뉴 레이아웃 만들기의 쟁점&lt;/h2&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;메뉴 레이아웃을 만드는 것은 두 개의 과정을 거칩니다. 우선, 메뉴 요소들을 감싸는 부모 요소의 크기와 위치를 잡기입니다. 다음으로는, 각 메뉴 항목에 해당하는 자식 요소들을 일자로 정렬해줍니다. 디자인에 따라서 지그재그로 정렬할 수도 있지만, 이번 포스팅에서는 가장 기본이 되는 일자 정렬을 다룰 것입니다. 사실 지그재그 정렬도 일자 정렬을 할 줄 알아야 구현 가능합니다. 지그재그 정렬의 원리는 일자 정렬 이후 각 자식 요소에 자식 요소를 또 추가하여, 위, 아래 혹은 왼쪽, 오른쪽 정렬만 다르게 해 주면 구현되기 때문입니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;일자 정렬에는 또 두가지 종류가 있습니다. 수평 방향으로 정렬과 수직 방향으로 정렬입니다. 이 두 가지 정렬을 구현하는 데에는 사실 무수히 많은 방법이 있습니다. 그중 가장 사용하기 쉽고 부작용이 없는 방법은 display의 flex를 사용하는 것이지만 IE 같은 구 브라우저가 이것을 지원하지 않습니다. 물론 지금 IE 벗어나기 추세가 가속화되고 있기 때문에 flex를 사용하는 것은 매우 좋은 방법입니다. flex를 사용하는 방법에 대해서는 추후에 기회가 되면 포스팅을 따로 해보겠습니다. 지금 이 포스팅에서는 구 브라우저들도 지원을 하는 display의 table을 이용하는 방법을 사용해보겠습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;display: table 속성은 element가 table태그가 아님에도 table처럼 쓸 수 있게 해줍니다.&lt;/p&gt;
&lt;p&gt;display: table 속성으로 레이아웃을 만드는 장점은 일단 쉽고 직관적이라는 것과 수평 방향이든 수직 방향이든 한 줄짜리 레이아웃을 만들 때 매우 강력한 방법이라는 것입니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;div class=&quot;revenue_unit_item adsense  responsive&quot;&gt;&lt;ins class=&quot;adsbygoogle&quot; style=&quot;display: block;&quot; data-ad-host=&quot;ca-host-pub-9691043933427338&quot; data-ad-client=&quot;ca-pub-9807016842906892&quot; data-ad-format=&quot;auto&quot;&gt;&lt;/ins&gt;
&lt;script&gt;(adsbygoogle = window.adsbygoogle || []).push({}); &lt;/script&gt;
&lt;/div&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. display: table로 수평 방향 메뉴 만들기&lt;/h2&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;아이디어는 다음과 같습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;모든 메뉴를 감쌀 부모 요소를 하나 만들고 display: table 속성 값을 부여합니다.&lt;/li&gt;
&lt;li&gt;각 메뉴에게 display: table-cell 속성값을 부여하고 디자인을 합니다.&lt;/li&gt;
&lt;li&gt;각 셀간의 간격은 부모 요소에 border-spacing 속성 값을 주어 조절합니다.&lt;/li&gt;
&lt;li&gt;각 메뉴 cell과 비어있는 cell을 조화해서 table을 채울 수 있게 만들어줍니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;4번이 중요합니다. 각 cell의 너비 값을 아무리 지정하여도 가장 우선시되는 것은 각 cell의 합은 table전체에 딱 맞아떨어져야 한다는 원칙입니다. 예를 들어 table전체의 너비는 1000px인데 너비 100px짜리 cell 5개를 나열하면 500px이 남습니다. 그러면 브라우저는 이 빈칸을 비워둔 채로 놔두는 것이 아니라, 억지로 cell 5개가 table 전체에 딱 떨어지게끔 각 cell의 너비를 200px로 강제 조절해버립니다. 따라서 내가 원하는 100px너비 값이 적용되지 않는 것이죠.&lt;/p&gt;
&lt;p&gt;이렇게 내가 만드는 레이아웃에서 빈 공간이 생길 예정이면 반드시 그 빈 공간을 아무것도 없는 cell로 채워 넣어줘야 합니다. 그래야만 위의 예시의 경우 아무것도 없는 빈 cell이 나머지 500px의 너비를 가져가서 내가 원하는 너비 값을 사용할 수 있게 됩니다. &lt;b&gt;이 내용은 매우 중요합니다.&lt;/b&gt; 4번을 이해하지 못하면 display: table로 레이아웃을 만드는 과정에서 굉장히 삽질을 할 가능성이 높습니다. 관련 소스코드는 밑에 있으니 참조하시기 바랍니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. display: table로 수직 방향 메뉴 만들기&lt;/h2&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;아이디어는 수평 방향 메뉴를 만들 때와 같지만 다음 사항이 하나 추가가 됩니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;각 메뉴를 display: table-row 속성 값이 부여된 태그로 감쌉니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;왜 이렇게 해야 하는지는 table태그의 구조를 알고 있다면 바로 이해가 되실 겁니다. table 태그의 구조는 다음과 같습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1585067565978&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;table&amp;gt;
    &amp;lt;tr&amp;gt;
        &amp;lt;td&amp;gt;&amp;lt;/td&amp;gt;
    &amp;lt;/tr&amp;gt;
    &amp;lt;tr&amp;gt;
        &amp;lt;td&amp;gt;&amp;lt;/td&amp;gt;
    &amp;lt;/tr&amp;gt;
&amp;lt;/table&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이 table은 2 * 1 table을 만듭니다. tr태그는 각 행을 의미합니다. 수직방향의 메뉴를 만들 때에는 각 메뉴마다 각 행을 의미하는 display: table-row 속성 값이 있어야 하는 것입니다. 수평 방향을 만들 때 이것을 사용하지 않은 이유는 row가 1개일 때에는 굳이 없어도 잘 만들어지기 때문입니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 수평방향, 수직방향 메뉴 레이아웃 예시 코드&lt;/h2&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;총 3개의 예시를 만들었습니다. 비어 있는 cell을 어디에 만들었는지, 그에 따라 메뉴의 위치가 어떻게 바뀌는지를 봐주시고, 여백의 크기 조절을 위해 blank class가 추가된 빈 cell은 어떻게 사용되었는지를 꼭 봐주시길 바랍니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* 예시 코드 및 결과&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;codepen&quot; style=&quot;height: 402px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px&amp;nbsp;solid; margin: 1em&amp;nbsp;0; padding: 1em;&quot; data-height=&quot;402&quot; data-theme-id=&quot;dark&quot; data-default-tab=&quot;html,result&quot; data-user=&quot;cocoder16&quot; data-slug-hash=&quot;qBdJVeo&quot; data-pen-title=&quot;qBdJVeo&quot;&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&lt;span&gt;See&amp;nbsp;the&amp;nbsp;Pen&amp;nbsp;&lt;a href=&quot;&amp;lt;a&amp;nbsp;href=&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://codepen.io/cocoder16/pen/qBdJVeo&lt;/a&gt;&quot;&amp;gt; &lt;br /&gt;&amp;nbsp;&amp;nbsp;qBdJVeo&amp;nbsp;by&amp;nbsp;cocoder16&amp;nbsp;(&lt;a href=&quot;&amp;lt;a&amp;nbsp;href=&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://codepen.io/cocoder16&lt;/a&gt;&quot;&amp;gt;@cocoder16) &lt;br /&gt;&amp;nbsp;&amp;nbsp;on&amp;nbsp;&lt;a href=&quot;&amp;lt;a&amp;nbsp;href=&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://codepen.io&lt;/a&gt;&quot;&amp;gt;CodePen.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>CSS</category>
      <category>CSS</category>
      <category>Display</category>
      <category>layout</category>
      <category>Table</category>
      <category>가로</category>
      <category>메뉴</category>
      <category>세로</category>
      <category>정렬</category>
      <category>퍼블리싱</category>
      <category>하는 방법</category>
      <author>cocoder16</author>
      <guid isPermaLink="true">https://cocoder16.tistory.com/35</guid>
      <comments>https://cocoder16.tistory.com/35#entry35comment</comments>
      <pubDate>Mon, 31 May 2021 08:00:50 +0900</pubDate>
    </item>
    <item>
      <title>[CSS 레이아웃] 헤더나 푸터 화면에 고정시키기</title>
      <link>https://cocoder16.tistory.com/34</link>
      <description>&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;스크롤을 움직여도 고정되어있는 헤더와 푸터 레이아웃 만들기&lt;/h2&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;사용자가 스크롤을 움직여도 고정되어 움직이지 않는 헤더(header)와 푸터(footer)를 만들어보려고 합니다. 헤더나 푸터나 고정시키는 원리는 똑같기 때문에 같은 방법으로 고정시킵니다. 밑에 코드의 결과를 한번 테스트해보세요. 테스트는 스크롤을 움직여보면 됩니다. 그리고 소스코드를 하나하나 뜯어서 구현원리를 살펴보도록 하겠습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* 소스코드 및 결과&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;codepen&quot; style=&quot;height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px&amp;nbsp;solid; margin: 1em&amp;nbsp;0; padding: 1em;&quot; data-height=&quot;720&quot; data-theme-id=&quot;dark&quot; data-default-tab=&quot;css,result&quot; data-user=&quot;cocoder16&quot; data-slug-hash=&quot;wvaYqbg&quot; data-pen-title=&quot;wvaYqbg&quot;&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&lt;span&gt;See&amp;nbsp;the&amp;nbsp;Pen&amp;nbsp;&lt;a href=&quot;&amp;lt;a&amp;nbsp;href=&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://codepen.io/cocoder16/pen/wvaYqbg&lt;/a&gt;&quot;&amp;gt; &lt;br /&gt;&amp;nbsp;&amp;nbsp;wvaYqbg&amp;nbsp;by&amp;nbsp;cocoder16&amp;nbsp;(&lt;a href=&quot;&amp;lt;a&amp;nbsp;href=&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://codepen.io/cocoder16&lt;/a&gt;&quot;&amp;gt;@cocoder16) &lt;br /&gt;&amp;nbsp;&amp;nbsp;on&amp;nbsp;&lt;a href=&quot;&amp;lt;a&amp;nbsp;href=&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://codepen.io&lt;/a&gt;&quot;&amp;gt;CodePen.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;기본적으로 화면에 고정시키는 속성은 position: fixed를 사용합니다. 고정시킬 위치를 정해야 하기 때문에 offset값으로 header의 경우에는 top과 left를 0으로, footer의 경우에는 bottom과 left를 0으로 합니다. 헤더와 푸터의 높이값은 원하시는 대로 설정해주시면 됩니다. 이 예시에서는 고정 px값으로 height값을 주었습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;주의할 점은 그 높이값만큼 본문에는 padding값을 넣어줘야 한다는 것입니다. 본문에 padding값이 없으면 본문의 최상단이나 최하단이 헤더나 푸터에 가려지는 부분이 생기기 때문에 이를 방지하기 위해 꼭 필요합니다.&amp;nbsp;고정 헤더를 만든 경우에는 padding-top값을 고정 푸터를 만든 경우에는 padding-bottom값을 사용합니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>CSS</category>
      <category>CSS</category>
      <category>footer</category>
      <category>Header</category>
      <category>layout</category>
      <category>고정</category>
      <category>레이아웃</category>
      <category>스크롤</category>
      <category>푸터</category>
      <category>헤더</category>
      <author>cocoder16</author>
      <guid isPermaLink="true">https://cocoder16.tistory.com/34</guid>
      <comments>https://cocoder16.tistory.com/34#entry34comment</comments>
      <pubDate>Mon, 24 May 2021 08:00:31 +0900</pubDate>
    </item>
    <item>
      <title>CSS로 이미지 다루기</title>
      <link>https://cocoder16.tistory.com/33</link>
      <description>&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;배경 이미지를 넣는 CSS background-image 속성&lt;/h2&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;background-image는 이미지를 배경으로 깔기 위해 사용되는 CSS 속성입니다.&lt;/p&gt;
&lt;p&gt;사용법은 속성값으로 이미지 리소스가 있는 경로를 url로 넣어주면 됩니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* 예시코드&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1585017617107&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* CSS */
div { 
    background-image: url('https://cdn.pixabay.com/photo/2015/12/04/14/05/code-1076536_1280.jpg'); 
    width:300px; height:300px; }
&amp;lt;!-- HTML --&amp;gt;
&amp;lt;div&amp;gt;&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* 결과&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;Lt0gdimg.png&quot; data-origin-width=&quot;385&quot; data-origin-height=&quot;385&quot; width=&quot;206&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zhDxQ/btq309tBt52/b1FOVazZe9nvdKkN77Cze1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zhDxQ/btq309tBt52/b1FOVazZe9nvdKkN77Cze1/img.png&quot; data-alt=&quot;이미지가 background에 깔린 div 태그&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zhDxQ/btq309tBt52/b1FOVazZe9nvdKkN77Cze1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzhDxQ%2Fbtq309tBt52%2Fb1FOVazZe9nvdKkN77Cze1%2Fimg.png&quot; data-filename=&quot;Lt0gdimg.png&quot; data-origin-width=&quot;385&quot; data-origin-height=&quot;385&quot; width=&quot;206&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;이미지가 background에 깔린 div 태그&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;div 태그의 width와 height 값은 각각 300px입니다. 그리고&amp;nbsp;배경 이미지를 만들었는데 배경 이미지의 크기가 이 요소의 크기에 들어맞지 않아서 이미지가 잘려서 나옵니다. 그래서 사이즈 조절을 해봅니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;이미지 크기를 조절하는 background-size 속성&lt;/h2&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;값으로 contain, cover, % 를 사용할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;contain&lt;/span&gt;: 배경 이미지의 원본 비율을 유지하면서 전부 보여줍니다. 따라서 배경이미지와 요소의 가로세로 비율이 맞지 않으면 요소에 여백이 생길 수 있습니다. 혹은 이미지에 repeat 속성이 있으면 반복되는 이미지 패턴이 보이게 됩니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;cover&lt;/span&gt;: 배경이미지의 원본비율을 유지하면서 여백이 없게 빈 공간이 없도록 보여줍니다. 따라서 배경 이미지와 요소의 비율이 맞지 않으면 이미지가 잘릴 수 있습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;% %&lt;/span&gt; : 가로 세로 영역의 % 값만큼 채워줍니다. 원본 비율은 무시됩니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이것을 이용해서 위의 이미지를 수정해보겠습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* 예시 코드&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1585018455924&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* CSS */
div { 
    background-image: url('https://cdn.pixabay.com/photo/2015/12/04/14/05/code-1076536_1280.jpg'); 
    width:300px; height:300px; border: 1px solid black; }
    background-size: contain;
&amp;lt;!-- HTML --&amp;gt;
&amp;lt;div&amp;gt;&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* 결과&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;c3VtDrimg.png&quot; data-origin-width=&quot;404&quot; data-origin-height=&quot;387&quot; width=&quot;189&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b0BA7j/btq30F0A5mx/HQMaWaGF5GXT6uBbicKNpK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b0BA7j/btq30F0A5mx/HQMaWaGF5GXT6uBbicKNpK/img.png&quot; data-alt=&quot;원본 비율을 유지하며 이미지를 다 보여주고 남는 여백만큼 반복되는 패턴&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b0BA7j/btq30F0A5mx/HQMaWaGF5GXT6uBbicKNpK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb0BA7j%2Fbtq30F0A5mx%2FHQMaWaGF5GXT6uBbicKNpK%2Fimg.png&quot; data-filename=&quot;c3VtDrimg.png&quot; data-origin-width=&quot;404&quot; data-origin-height=&quot;387&quot; width=&quot;189&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;원본 비율을 유지하며 이미지를 다 보여주고 남는 여백만큼 반복되는 패턴&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;contain으로 속성 값을 부여하면 만약 여백이 생길 경우 기본적으로 여백을 반복 이미지로 채워 넣습니다. 반복을 원하지 않으면 background-repeat: no-repeat를 추가해주면 됩니다. repeat 속성에 대해서는 밑에서 설명해드리겠습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;위 코드에서 contain대신 cover를 사용한 모습입니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;bez54Uimg.png&quot; data-origin-width=&quot;390&quot; data-origin-height=&quot;387&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eiYT5q/btq38etpPFB/JLC0hWhXYsZjS9OLlRAzw0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eiYT5q/btq38etpPFB/JLC0hWhXYsZjS9OLlRAzw0/img.png&quot; data-alt=&quot;background-size: cover를 적용한 모습&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eiYT5q/btq38etpPFB/JLC0hWhXYsZjS9OLlRAzw0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeiYT5q%2Fbtq38etpPFB%2FJLC0hWhXYsZjS9OLlRAzw0%2Fimg.png&quot; data-filename=&quot;bez54Uimg.png&quot; data-origin-width=&quot;390&quot; data-origin-height=&quot;387&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;background-size: cover를 적용한 모습&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이미지의 가로세로 비율과 요소의 가로세로 비율이 안 맞아 이미지의 잘린 부분이 발생하였습니다.&lt;/p&gt;
&lt;p&gt;다음으로&amp;nbsp;%로 비율을 무시하고 영역에 꽉 맞게 넣어보겠습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* 예시 코드&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1585018907145&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* CSS */
div { 
    background-image: url('https://cdn.pixabay.com/photo/2015/12/04/14/05/code-1076536_1280.jpg'); 
    width:300px; height:300px; border: 1px solid black; }
    background-size: 100% 100%;
&amp;lt;!-- HTML --&amp;gt;
&amp;lt;div&amp;gt;&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;background-size: 100% 100%; 를 적용하여 요소의 가로 100%, 세로 100%만큼 이미지를 채워 넣기 때문에 정확히 요소의 가로세로 크기만큼 이미지가 채워집니다. 다만 이 경우 요소와 이미지의 가로세로 비율이 일치하지 않으면 이미지의 원본 가로세로 비율이 무너지게 됩니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;div class=&quot;revenue_unit_item adsense  responsive&quot;&gt;&lt;ins class=&quot;adsbygoogle&quot; style=&quot;display: block;&quot; data-ad-host=&quot;ca-host-pub-9691043933427338&quot; data-ad-client=&quot;ca-pub-9807016842906892&quot; data-ad-format=&quot;auto&quot;&gt;&lt;/ins&gt;
&lt;script&gt;(adsbygoogle = window.adsbygoogle || []).push({}); &lt;/script&gt;
&lt;/div&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;background 관련 그 외 속성들&lt;/h2&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1. background-repeat&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;배경 이미지를 요소에 넣었는데 요소에 여백이 생기는 경우 이미지를 반복하며 여백을 채우는 패턴을 형성합니다.&lt;/p&gt;
&lt;p&gt;background-repeat 속성 값이 repeat가 default로 설정되어 있기 때문인데요. background-repeat 속성 값을 달리하여 이런 패턴을 바꿀 수 있습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;background-repeat 속성 값은 다음과 같습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #f3c000;&quot;&gt;no-repeat&lt;/span&gt;: 반복 안 함&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #f3c000;&quot;&gt;repeat-x&lt;/span&gt;: 가로방향으로 반복&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #f3c000;&quot;&gt;repeat-y&lt;/span&gt;: 세로방향으로 반복&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #f3c000;&quot;&gt;repeat&lt;/span&gt;: 가로, 세로로 반복&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2. background-position&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이미지의 offset, 즉, 위치를 정하는 속성입니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;background-position: x-position y-position&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;단위는 px나 %을 사용합니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3. background-attachment&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;background-attachment: fixed 속성 값을 사용하면 스크롤을 내려도 화면에 이미지가 고정됩니다.&lt;/p&gt;
&lt;p&gt;이 속성의 default값은 scroll이고 스크롤을 내리면 이미지는 올라가는 보통의 일반적인 상황을 연출합니다.&lt;/p&gt;
&lt;p&gt;주의할 점은 fixed를 사용하면 background-size속성은 그것의 element를 기준으로 size값을 정하지 않고 viewport를 기준으로 size값을 정합니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* 예시 코드&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1585026729725&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;head&amp;gt;
    &amp;lt;style&amp;gt;
        #scroll {
            width:300px; height:300px; margin:10px;
            border: 1px solid black;
            background-image: url('https://cdn.pixabay.com/photo/2017/08/18/08/31/flowers-2654192_960_720.png'); 
            background-size: 100% 100%;
            background-attachment: scroll;
        }
        #fixed {
            background-image: url('https://cdn.pixabay.com/photo/2017/08/18/08/31/flowers-2654192_960_720.png'); 
            background-size: 100% 100%;
            background-attachment: fixed;
        }
    &amp;lt;/style&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
    &amp;lt;div id=&quot;scroll&quot;&amp;gt;&amp;lt;/div&amp;gt;
    &amp;lt;div id=&quot;fixed&quot;&amp;gt;&amp;lt;/div&amp;gt;
    &amp;lt;div style=&quot;height: 1000px;&quot;&amp;gt;&amp;lt;/div&amp;gt;
&amp;lt;/body&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* 결과&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;Iu6UHimg.gif&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;590&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bwLJee/btq4bYrP6rE/EwV0kKdEMlqmN450fEno6K/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bwLJee/btq4bYrP6rE/EwV0kKdEMlqmN450fEno6K/img.gif&quot; data-alt=&quot;backgorund-attachment: fixed 가 적용된 요소는 스크롤을 움직여도 가만히 고정되어 있습니다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bwLJee/btq4bYrP6rE/EwV0kKdEMlqmN450fEno6K/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/bwLJee/btq4bYrP6rE/EwV0kKdEMlqmN450fEno6K/img.gif&quot; data-filename=&quot;Iu6UHimg.gif&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;590&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;backgorund-attachment: fixed 가 적용된 요소는 스크롤을 움직여도 가만히 고정되어 있습니다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;background-image와 img 태그의 차이&lt;/h2&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;background-image는 요소의 배경으로 이미지를 삽입하는 것이고 img 태그는 그 자체가 요소가 됩니다.&lt;/p&gt;
&lt;p&gt;이미지 자체가 시맨틱하게 존재할 필요가 있으면 img태그를 사용해야 합니다. 또한 img태그는 검색엔진에서 이미지 검색의 대상이 되지만, background-image는 그렇지 않습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;img 태그를 원본 가로세로 비율을 유지한 채 부모 요소에 꽉 채우는 방법&lt;/h3&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;img 태그에 max-width와 max-height의 값을 100%로 제약을 주고 width와 height값을 자동으로 주면 원본 비율을 유지하면서 박스를 꽉 채우게 됩니다. 밑의 예시 코드에서 #picture 선택자의 코드를 봐주세요.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* 예시코드&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1585029531481&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;head&amp;gt;
    &amp;lt;style&amp;gt;
        #picturecell{
			width:360px; height:160px; border:1px solid red;
		}
        #picture{
			max-width:100%; max-height:100%; width:auto; height:auto;
		}
    &amp;lt;/style&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
    &amp;lt;div id='picturecell'&amp;gt;
        &amp;lt;img src='https://cdn.pixabay.com/photo/2017/08/18/08/31/flowers-2654192_960_720.png' id='picture'&amp;gt;
    &amp;lt;/div&amp;gt;
&amp;lt;/body&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* 결과&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;bk5XBOimg.png&quot; data-origin-width=&quot;463&quot; data-origin-height=&quot;216&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qF48b/btq4bOXjbyf/3ItjaDwh4H8AkeWW1Orrx1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qF48b/btq4bOXjbyf/3ItjaDwh4H8AkeWW1Orrx1/img.png&quot; data-alt=&quot;가로세로 비율을 유지한 채 부모 요소에 꽉 채워넣은 결과 오른쪽 여백이 남았습니다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qF48b/btq4bOXjbyf/3ItjaDwh4H8AkeWW1Orrx1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqF48b%2Fbtq4bOXjbyf%2F3ItjaDwh4H8AkeWW1Orrx1%2Fimg.png&quot; data-filename=&quot;bk5XBOimg.png&quot; data-origin-width=&quot;463&quot; data-origin-height=&quot;216&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;가로세로 비율을 유지한 채 부모 요소에 꽉 채워넣은 결과 오른쪽 여백이 남았습니다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #a6bc00;&quot;&gt;c.f. 원본 비율 무시하고 박스의 가로세로에 꽉 맞게 채워 넣으려면?&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;단순히 img 태그의 width와 height값을 각각 100%로 해주면 됩니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>CSS</category>
      <category>Background</category>
      <category>background-image</category>
      <category>CSS</category>
      <category>image</category>
      <category>IMG</category>
      <category>이미지</category>
      <author>cocoder16</author>
      <guid isPermaLink="true">https://cocoder16.tistory.com/33</guid>
      <comments>https://cocoder16.tistory.com/33#entry33comment</comments>
      <pubDate>Mon, 17 May 2021 08:00:13 +0900</pubDate>
    </item>
    <item>
      <title>CSS 애니메이션 만드는 방법 총정리</title>
      <link>https://cocoder16.tistory.com/32</link>
      <description>&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;CSS 애니메이션 만들기 개요&lt;/h2&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;제가 생각하기로는 애니메이션에는 크게 볼 때 두 가지 속성이 필요합니다. '시간'과 '상태'입니다. 어떤 개체가 시간이 지남에 따라 어떤 상태를 정해주면 그것이 움직임이 되고 애니메이션이 됩니다. 그리고 이것을 CSS로 코딩하여 만들어준다면, CSS에서는 크게 시간 속성과 상태 속성이 필요할 것입니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그런데 이 중 시간과 관련된 속성들은 애니메이션에서 고유적으로 사용되기는 하지만, 상태와 관련된 속성들은 사실 애니메이션이 아니더라도 이미 정적인 상태에서도 사용하고 있습니다. border, font-size, color, width, height 등등... 요소들을 디자인하기 위해 사용했던 모든 속성들이 다 그 요소의 상태를 나타냅니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;width, height이 50px 인 어떤 요소가 있다고 생각해봅시다. 시간이 0초에서 1초가 흐를때까지 이 요소의 width, height이 각각 100px로 서서히 변한다면 어떨까요? 이것이 CSS에서 말하는 애니메이션입니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;또 이런 생각을 해볼 수 있습니다. width, height 값이 시간이 지남에 따라 일정한 변화율을 가지고 변할 수도 있으나, 각 시간에 따라 변화율을 다르게 하고 싶을 수도 있습니다. 전자의 경우 0초에서 1초가 될 때까지 width, height 값이 일정한 속도로 변하는 경우를 의미합니다. 후자의 경우는 무수히 많은 경우가 있을 수 있습니다. 예를 들면 0초에 가까울 때에는 엄청나게 빠른 속도로 값이 변하다가 1초에 가까워질수록 점점 값이 변하는 속도가 느리거나, 혹은 그 반대일 수도 있습니다. CSS에서는 이렇게 시간에 따라 상태가 변하는 속도를 조절할 수 있는 time-function을 사용할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이번 포스팅에서는 이런 것들을 모두 종합하여, 시간이 지남에 따라 원하는대로 상태들을 변화시키는 멋진 애니메이션을 만들 수 있는 방법을 익힐 것입니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;변화 시간을 정하는 transition 속성&lt;/h2&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;transition은 CSS속성 값의 변화를 즉시 이루어지는 것이 아닌, 시간을 두고 서서히 이루어지게 하여 시각적으로 부드럽게 변하는 것처럼 보이게 만들어주는 속성입니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1. transition의 사용법&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;transition: (적용할 CSS 속성) 변화 시간 [timing function]&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;1) 적용할 CSS 속성&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;transition을 적용할 속성을 적습니다. 모든 속성에 적용하고 싶으면 &lt;span style=&quot;color: #f3c000;&quot;&gt;all&lt;/span&gt;이라고 씁니다.&lt;/p&gt;
&lt;p&gt;복수개의 속성에 적용하는 방법은&amp;nbsp;transition에 값을 넣지 않고 &lt;span style=&quot;color: #f3c000;&quot;&gt;transition-property&lt;/span&gt;라는 속성을 따로 만들어 거기에 속성값으로 적용할 속성들을 넣습니다. 이때 각 속성은 띄어쓰기로 구분합니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* 예시코드&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1584884257891&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* width속성에만 변환시간 적용하기 */
transition: width 1s;
/* 모든 속성에 대해 변환시간 적용하기 */
transition: all 1s;
/* 복수에 속성에 대해 변환시간 적용하기 */
transition: 1s;
transition-property: width height;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2) 변화 시간&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;필수로 넣어야 하는 값이며 단위는 s(second)나 ms(millisecond)를 사용합니다.&lt;/p&gt;
&lt;p&gt;이 시간만큼 속성의 값의 변화가 이루어집니다.&lt;/p&gt;
&lt;p&gt;각 속성별로 따로따로 지정할 수도 있습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* 예시 코드&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1584884784297&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;transition: all 2s;
transition: all 2000ms;

/* 각 속성마다 따로 지정 */
transition: width 2s, height 0.5s;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3) timing function&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;timing function은 시간에 따라 상태가 변하는 변화율을 함수로 작성하여 시간에 따른 변화 속도를 조절할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #f3c000;&quot;&gt;transition-timing-function&lt;/span&gt;속성으로 따로 작성하거나, transition에서 적용 속성과 변환시간 뒤에 연달아적는 방법이 있습니다.&lt;/p&gt;
&lt;p&gt;timing function은 사실 자주 사용하는 함수 몇 가지를 기본적으로 사용할 수 있도록 CSS에서 제공하고 있습니다. 속성 값에 함수명을 넣어서 사용할 수 있습니다. 종류로는 &lt;span style=&quot;color: #ee2323;&quot;&gt;linear, ease-in, ease-out, ease-in-out&lt;/span&gt;가 있습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;반면&amp;nbsp;&lt;span style=&quot;color: #f3c000;&quot;&gt;cubic-bezier(n, n, n, n)&lt;/span&gt;를 이용하면 간단하게 내 입맛대로 timing function을 만들어 사용할 수 있습니다.&lt;/p&gt;
&lt;p&gt;저 네 군데에 n자리에 숫자만 넣으면 됩니다. 그런데 어떤 숫자를 넣어야 어떤 효과가 나올지 알기가 힘듭니다. 그래서 내가 원하는 효과를 나타내는 n값을 쉽게 찾을 수 있는 사이트도 하나 소개합니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;cubic-bezier() 만들기&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://cubic-bezier.com&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;http://cubic-bezier.com&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1584885731064&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;website&quot; data-og-title=&quot;cubic-bezier.com&quot; data-og-description=&quot;&quot; data-og-host=&quot;cubic-bezier.com&quot; data-og-source-url=&quot;http://cubic-bezier.com&quot; data-og-url=&quot;https://cubic-bezier.com/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;http://cubic-bezier.com&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;http://cubic-bezier.com&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;cubic-bezier.com&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;cubic-bezier.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* 예시 코드&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1584885992544&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* 내장된 timing function 사용 */
transition: all 1s ease-in-out;
/* cubic-bezier() 사용 */
transition: all 1s cubic-bezier(0.8, 0.2, 0.9, 0.5);

/* 따로 쓰기 */
transition: all 1s;
transition-timing-function: cubic-bezier(0.8, 0.2, 0.9, 0.5);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;4) 그 외 속성 delay&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #f3c000;&quot;&gt;transition-delay&lt;/span&gt;의 값으로 시간 값(ms나 s단위)을 주면 그 시간만큼 딜레이를 가진 뒤에 비로소 애니메이션이 작동합니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2. 연습&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;지금까지 공부한 걸 연습하며 정리해봤습니다.&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333;&quot;&gt;CSS 코드를 보면서 결과를 예상해본 후 결과창에 있는 박스들을 클릭해보세요. 그리고 생각한 대로 효과가 적용이 되는지 확인하면 될 것 같습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;codepen&quot; style=&quot;height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px&amp;nbsp;solid; margin: 1em&amp;nbsp;0; padding: 1em;&quot; data-height=&quot;265&quot; data-theme-id=&quot;dark&quot; data-default-tab=&quot;css,result&quot; data-user=&quot;cocoder16&quot; data-slug-hash=&quot;VwLGWWd&quot; data-pen-title=&quot;VwLGWWd&quot;&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&lt;span&gt;See&amp;nbsp;the&amp;nbsp;Pen&amp;nbsp;&lt;a href=&quot;&amp;lt;a&amp;nbsp;href=&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://codepen.io/cocoder16/pen/VwLGWWd&lt;/a&gt;&quot;&amp;gt; &lt;br /&gt;&amp;nbsp;&amp;nbsp;VwLGWWd&amp;nbsp;by&amp;nbsp;cocoder16&amp;nbsp;(&lt;a href=&quot;&amp;lt;a&amp;nbsp;href=&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://codepen.io/cocoder16&lt;/a&gt;&quot;&amp;gt;@cocoder16) &lt;br /&gt;&amp;nbsp;&amp;nbsp;on&amp;nbsp;&lt;a href=&quot;&amp;lt;a&amp;nbsp;href=&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://codepen.io&lt;/a&gt;&quot;&amp;gt;CodePen.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그런데 지금까지 살펴본 transition만 가지고는 뭔가 부족한 느낌이 있습니다. 조금 더 다양한 변화 방식이 있지 않을까요?&lt;/p&gt;
&lt;p&gt;예를 들어 위 연습 예제에서는 박스가 오른쪽 아래 방향으로만 커지는데, 다른 방향으로 커지게 할 수는 없을까요? 이런 것들을 할 수 있는 방법이 있습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;div class=&quot;revenue_unit_item adsense  responsive&quot;&gt;&lt;ins class=&quot;adsbygoogle&quot; style=&quot;display: block;&quot; data-ad-host=&quot;ca-host-pub-9691043933427338&quot; data-ad-client=&quot;ca-pub-9807016842906892&quot; data-ad-format=&quot;auto&quot;&gt;&lt;/ins&gt;
&lt;script&gt;(adsbygoogle = window.adsbygoogle || []).push({}); &lt;/script&gt;
&lt;/div&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;transform으로 더 풍부한 애니메이션 만들기&lt;/h2&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;transform&lt;/span&gt;은 요소를 시각적으로 다양한 방법으로 변형시킬 수 있는 속성입니다.&lt;/p&gt;
&lt;p&gt;이 속성은 block이나 inline-block element에게만 적용됩니다. inline element에는 적용되지 않습니다.&lt;/p&gt;
&lt;p&gt;transform으로 변형 효과를 사용할 때에는 &lt;span style=&quot;color: #f3c000;&quot;&gt;변형의 기준점&lt;/span&gt;과 &lt;span style=&quot;color: #f3c000;&quot;&gt;변형 시간&lt;/span&gt;을 같이 정해줄 수 있습니다.&lt;/p&gt;
&lt;p&gt;transition만 적용한 애니메이션과 달리 transform까지 적용된 애니메이션은 더 다양한 표현 방식을 가지게 됩니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;hbox&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1. transform-origin으로 기준점 정하기&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;요소의 기준점의 default는 정중앙입니다. 따라서 해당 속성을 사용하지 않으면 정중앙을 기준으로 하여 변형이 일어납니다. 기준점이라 함은 마치 그 점을 핀셋으로 콱 집어 고정시키고 그것을 기준으로 전체 모양의 변형이 이뤄지는 것과 같은 느낌입니다. 잘 이해가 안 가셔도 상관없습니다. 밑에서 예제를 보면 직관적으로 이해를 할 수 있을 것입니다. &lt;span style=&quot;color: #ee2323;&quot;&gt;transform-origin&lt;/span&gt;의 값을 넣는 방법은 두 가지가 있습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1) 자주 사용하는 9군데 값 넣기&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;top, center, bottom, left, right를 조합하여 9군데의 기준점을 정할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;RbDzjimg.png&quot; data-origin-width=&quot;585&quot; data-origin-height=&quot;441&quot; width=&quot;277&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bD8CWR/btq3XcikA9a/zHVFXryqkeoPk0OaJOskH1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bD8CWR/btq3XcikA9a/zHVFXryqkeoPk0OaJOskH1/img.png&quot; data-alt=&quot;transform-origin 값이 각각 가리키는 위치&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bD8CWR/btq3XcikA9a/zHVFXryqkeoPk0OaJOskH1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbD8CWR%2Fbtq3XcikA9a%2FzHVFXryqkeoPk0OaJOskH1%2Fimg.png&quot; data-filename=&quot;RbDzjimg.png&quot; data-origin-width=&quot;585&quot; data-origin-height=&quot;441&quot; width=&quot;277&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;transform-origin 값이 각각 가리키는 위치&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* 예시 코드&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1584928233815&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;transform-origin: top left;
transform-origin: top center;
transform-origin: top right;
transform-origin: center left;
transform-origin: center center;
transform-origin: center right;
transform-origin: bottom left;
transform-origin: bottom center;
transform-origin: bottom right;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2) 숫자 값으로 넣기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;값으로 x축 방향으로 이동 값 y축 방향으로 이동값 [z축 방향으로 이동 값]을 차례대로 적어주면 됩니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* 예시 코드&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1584928415232&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* top right */
transform-origin: 100% 0;
/* center center */
transform-origin: 50% 50%;
/* left bottom */
transform-origin: 0 100%;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;hbox&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2. 변형시간&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;변형시간은 &lt;span style=&quot;color: #ee2323;&quot;&gt;transition&lt;/span&gt; 속성을 사용해줍니다. 변형이 즉시 일어나는 것보다 시간에 걸쳐 일어나는 것이 더 시각적으로 생동감 있어 보입니다.&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;transition과 transform-origin을 같이 사용할 때 주의할 점&lt;/span&gt;은 transition값으로 all을 사용하지 말고 transform을 사용해야만 transform-origin이 잘 적용된다는 것입니다. 만약 그렇지 않으면 변환이 다 완료가 되고 나서야 transform-origin이 적용됩니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;hbox&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3. transform 효과&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;주로 크기를 변형하는 scale, 회전을 시키는 rotate, 기울게 만드는 skew, 이동시키는 translate 등을 사용합니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* 예시 코드 및 결과&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;codepen&quot; style=&quot;height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px&amp;nbsp;solid; margin: 1em&amp;nbsp;0; padding: 1em;&quot; data-height=&quot;380&quot; data-theme-id=&quot;dark&quot; data-default-tab=&quot;css,result&quot; data-user=&quot;cocoder16&quot; data-slug-hash=&quot;RwPYyVb&quot; data-pen-title=&quot;RwPYyVb&quot;&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&lt;span&gt;See&amp;nbsp;the&amp;nbsp;Pen&amp;nbsp;&lt;a href=&quot;&amp;lt;a&amp;nbsp;href=&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://codepen.io/cocoder16/pen/RwPYyVb&lt;/a&gt;&quot;&amp;gt; &lt;br /&gt;&amp;nbsp;&amp;nbsp;RwPYyVb&amp;nbsp;by&amp;nbsp;cocoder16&amp;nbsp;(&lt;a href=&quot;&amp;lt;a&amp;nbsp;href=&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://codepen.io/cocoder16&lt;/a&gt;&quot;&amp;gt;@cocoder16) &lt;br /&gt;&amp;nbsp;&amp;nbsp;on&amp;nbsp;&lt;a href=&quot;&amp;lt;a&amp;nbsp;href=&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://codepen.io&lt;/a&gt;&quot;&amp;gt;CodePen.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;박스에 마우스를 올리면 transform효과가 나타납니다. 코드를 같이 보면서 감을 잡으시면 될 것 같습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;키프레임으로 애니메이션 함수 만들기&lt;/h2&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;위의 transition과 trasform을 가지고 우리는 충분히 원하는 애니메이션들을 만들어 낼 수 있습니다.&lt;/p&gt;
&lt;p&gt;그런데 사실 이것만 가지고는 뭔가 아쉬움이 남습니다. 자주 사용하는 애니메이션은 함수로 만들어서 계속 재사용하면 안 될까요?&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;@keyframes 정의하기&lt;/h3&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;@keyframes는 &lt;span style=&quot;color: #ee2323;&quot;&gt;프레임&lt;/span&gt;이 변함에 따라 CSS속성이 어떤 값을 가지는지를 정할 수 있습니다. 주의하셔야 합니다. 위에서 transition은 &lt;span style=&quot;color: #ee2323;&quot;&gt;시간&lt;/span&gt;이 변함에 따라 CSS속성의 값이 변하는 것이었습니다. 다른 것입니다. 즉, @keyframes 자체만으로는 아직 시간이 적용되지 않은 상태인 것입니다. 따라서 @keyframes를 가져다 쓸 때에는 여기에 애니메이션 시간까지 추가로 지정해줘야 합니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* 예시 코드&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1619881632154&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@keyframes slidein {
  from {
    margin-left: 100%;
  }

  to {
    margin-left: 0%;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;오른쪽에서 왼쪽 방향으로 들어오는 slidein이라는 애니메이션을 만들었습니다. from은 첫 프레임에서의 상태를 나타내고 to는 마지막 프레임에서의 상태를 나타냅니다. 더 구체적으로 %를 이용하여 프레임을 더 세분화할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* 예시 코드&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1619881722645&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@keyframes slidein {
  0% { margin-left: 100%; }
  30% { margin-left: 70%; }
  70% { margin-left: 30%; }
  100% { margin-left: 0%; }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;정의한 @keyframes 사용하여 애니메이션 만들기&lt;/h3&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;@keyframes를 사용하려면 animation-name 속성을 사용하면 됩니다.&lt;/p&gt;
&lt;p&gt;애니메이션 실행시간은 animation-duration 속성을 사용하면 되고 단위로는 s나 ms를 사용할 수 있습니다.&lt;/p&gt;
&lt;p&gt;키프레임 방식을 사용하더라도 timing-function을 적용할 수 있습니다. 이것은 animation-timing-function 속성을 사용하면 됩니다. 이 속성의 값으로는 위에 transition-timing-function을 쓴 것과 동일한 사용법을 가집니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* 예시 코드&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1619882909145&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;div {
  animation-name: slidein;
  animation-duration: 1s;
  animation-timing-function: ease-in;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이 외에 사용할 수 있는 다양한 애니메이션 속성들이 또 있습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;- animation-iteration-count: 애니메이션의 반복 횟수를 지정합니다. (default = 1)&lt;/p&gt;
&lt;p&gt;- animation-direction: 애니메이션의 반복 회수를 설정한 후 이 속성의 값을 설정해야 의미가 있습니다.&lt;/p&gt;
&lt;p&gt;default는 정방향의 애니메이션 실행을 의미하여, 매 애니메이션 반복마다 정방향의 애니메이션을 실행합니다.&lt;/p&gt;
&lt;p&gt;값으로 alternate를 넣으면 홀수번째 반복에서는 정방향의 애니메이션이 실행되고, 짝수번째 반복에서는 역방향의 애니메이션이 실행됩니다. 예를 들어 정방향 애니메이션에서 왼쪽에서 오른쪽으로 이동한다면 역방향에서는 오른쪽에서 왼쪽으로 이동합니다.&lt;/p&gt;
&lt;p&gt;- animation-delay: 값으로 지정한 딜레이 시간이 지난 후 애니메이션이 시작됩니다.&lt;/p&gt;
&lt;p&gt;- animation-fill-mode: 값으로 backwards를 넣으면 애니메이션이 끝난 후 요소가 원래 위치로 돌아갑니다. forwards를 넣으면 애니메이션이 끝난 후 끝난 위치에 요소가 남습니다. default는 backwards입니다.&lt;/p&gt;
&lt;p&gt;- animation-play-state: 값으로 running을 넣어주면 애니메이션이 진행상태가 되고, paused를 넣으면 애니메이션이 일시중지 상태가 됩니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* 예시 코드 및 결과&lt;/span&gt;&lt;/p&gt;
&lt;p class=&quot;codepen&quot; style=&quot;height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;&quot; data-height=&quot;265&quot; data-theme-id=&quot;dark&quot; data-default-tab=&quot;js,result&quot; data-user=&quot;cocoder16&quot; data-slug-hash=&quot;YzNoJEz&quot; data-pen-title=&quot;YzNoJEz&quot;&gt;&lt;span&gt;See the Pen &lt;a href=&quot;https://codepen.io/cocoder16/pen/YzNoJEz&quot;&gt; YzNoJEz&lt;/a&gt; by cocoder16 (&lt;a href=&quot;https://codepen.io/cocoder16&quot;&gt;@cocoder16&lt;/a&gt;) on &lt;a href=&quot;https://codepen.io&quot;&gt;CodePen&lt;/a&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>CSS</category>
      <category>animation</category>
      <category>CSS</category>
      <category>keyframes</category>
      <category>transform</category>
      <category>transition</category>
      <category>만들기</category>
      <category>방법</category>
      <category>애니메이션</category>
      <author>cocoder16</author>
      <guid isPermaLink="true">https://cocoder16.tistory.com/32</guid>
      <comments>https://cocoder16.tistory.com/32#entry32comment</comments>
      <pubDate>Mon, 10 May 2021 08:00:58 +0900</pubDate>
    </item>
    <item>
      <title>CSS 텍스트, 폰트 관련 속성 정리</title>
      <link>https://cocoder16.tistory.com/31</link>
      <description>&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;폰트 사이즈를 결정하는 font-size 속성&lt;/h2&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;font-size의 단위는 rem, px, em 세 가지가 있습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이 중 em 은 사용하지 않는 것이 좋습니다. 왜냐하면 em단위는 부모 태그의 영향을 받는 상대적인 크기를 나타내기 때문에 font-size 속성 값으로 적용된 숫자만 가지고 실제 크기를 파악하기가 어렵기 때문입니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;rem&lt;/span&gt;은 html태그에 적용된 폰트사이즈에게 영향을 받습니다. html태그의 폰트 크기에 따라서 상대적으로 크기가 결정됩니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;px&lt;/span&gt;은 모니터 상의 화소 하나의 크기에 대응하는 단위입니다. 가장 고정된 값이기 때문에 예측이 가장 쉽습니다. 다만 사용자가 글꼴의 크기를 조정할 수 없습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* 예시코드&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1584858634781&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;span style=&quot;font-size: 0.8rem&quot;&amp;gt;0.8rem&amp;lt;/span&amp;gt;&amp;lt;br&amp;gt;
&amp;lt;span style=&quot;font-size: 1rem&quot;&amp;gt;1rem&amp;lt;/span&amp;gt;&amp;lt;br&amp;gt;
&amp;lt;span style=&quot;font-size: 1.2rem&quot;&amp;gt;1.2rem&amp;lt;/span&amp;gt;&amp;lt;br&amp;gt;
&amp;lt;span style=&quot;font-size: 10px&quot;&amp;gt;10px&amp;lt;/span&amp;gt;&amp;lt;br&amp;gt;
&amp;lt;span style=&quot;font-size: 15px&quot;&amp;gt;15px&amp;lt;/span&amp;gt;&amp;lt;br&amp;gt;
&amp;lt;span style=&quot;font-size: 20px&quot;&amp;gt;20px&amp;lt;/span&amp;gt;&amp;lt;br&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* 결과&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;b7IpT6img.png&quot; data-origin-width=&quot;84&quot; data-origin-height=&quot;167&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c0IpuX/btq3DE2hU7i/b31e1q9rsvy26zOHLfZ321/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c0IpuX/btq3DE2hU7i/b31e1q9rsvy26zOHLfZ321/img.png&quot; data-alt=&quot;실행 결과&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c0IpuX/btq3DE2hU7i/b31e1q9rsvy26zOHLfZ321/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc0IpuX%2Fbtq3DE2hU7i%2Fb31e1q9rsvy26zOHLfZ321%2Fimg.png&quot; data-filename=&quot;b7IpT6img.png&quot; data-origin-width=&quot;84&quot; data-origin-height=&quot;167&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;실행 결과&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;폰트의 굵기를 결정하는 font-weight 속성&lt;/h2&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;font-weight 속성은 폰트의 굵기를 결정합니다. 속성 값으로는 숫자 형태로는 100,&amp;nbsp;200,&amp;nbsp;300,&amp;nbsp;400,&amp;nbsp;500,&amp;nbsp;600,&amp;nbsp;700,&amp;nbsp;800,&amp;nbsp;900를 사용할 수 있고 텍스트 형태로는 bold,&amp;nbsp;normal,&amp;nbsp;lighter 등을 사용할 수 있습니다. 숫자 형태의 속성 값은 숫자가 커질수록 굵은 폰트 사이즈를 의미합니다. &lt;span style=&quot;color: #333333;&quot;&gt;bold는 굵게, normal은 보통, lighter는 가늘게를 의미합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;어떤 폰트는 숫자가 커질수록 점진적으로 굵어지고 어떤 폰트는 특정 숫자가 되어서야 급격히 굵어집니다.&lt;/p&gt;
&lt;p&gt;이유는 해당 폰트가 지원하는 굵기 사이즈 개수가 다 제각각이기 때문입니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* 예시 코드&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1591513045516&quot; class=&quot;html xml&quot; style=&quot;margin: 20px auto 0px; display: block; overflow: auto; padding: 15px; color: #383a42; background: #f6f7f8; font-size: 14px; border-radius: 3px; font-family: Menlo, Consolas, Monaco, monospace; border: 1px solid #dddddd; cursor: default; z-index: 1;&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;span style=&quot;font-weight: 100&quot;&amp;gt;100&amp;lt;/span&amp;gt;&amp;lt;br&amp;gt;
&amp;lt;span style=&quot;font-weight: 200&quot;&amp;gt;200&amp;lt;/span&amp;gt;&amp;lt;br&amp;gt;
&amp;lt;span style=&quot;font-weight: 300&quot;&amp;gt;300&amp;lt;/span&amp;gt;&amp;lt;br&amp;gt;
&amp;lt;span style=&quot;font-weight: 400&quot;&amp;gt;400&amp;lt;/span&amp;gt;&amp;lt;br&amp;gt;
&amp;lt;span style=&quot;font-weight: 500&quot;&amp;gt;500&amp;lt;/span&amp;gt;&amp;lt;br&amp;gt;
&amp;lt;span style=&quot;font-weight: 600&quot;&amp;gt;600&amp;lt;/span&amp;gt;&amp;lt;br&amp;gt;
&amp;lt;span style=&quot;font-weight: 700&quot;&amp;gt;700&amp;lt;/span&amp;gt;&amp;lt;br&amp;gt;
&amp;lt;span style=&quot;font-weight: 800&quot;&amp;gt;800&amp;lt;/span&amp;gt;&amp;lt;br&amp;gt;
&amp;lt;span style=&quot;font-weight: 900&quot;&amp;gt;900&amp;lt;/span&amp;gt;&amp;lt;br&amp;gt;
&amp;lt;span style=&quot;font-weight: bold&quot;&amp;gt;bold&amp;lt;/span&amp;gt;&amp;lt;br&amp;gt;
&amp;lt;span style=&quot;font-weight: normal&quot;&amp;gt;normal&amp;lt;/span&amp;gt;&amp;lt;br&amp;gt;
&amp;lt;span style=&quot;font-weight: lighter&quot;&amp;gt;lighter&amp;lt;/span&amp;gt;&amp;lt;br&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* 결과&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;bnf16vimg.png&quot; data-origin-width=&quot;87&quot; data-origin-height=&quot;336&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rGWkh/btq3JSdrzIn/00o7b5Yh9jTcKiETFQwsPk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rGWkh/btq3JSdrzIn/00o7b5Yh9jTcKiETFQwsPk/img.png&quot; data-alt=&quot;실행 결과&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rGWkh/btq3JSdrzIn/00o7b5Yh9jTcKiETFQwsPk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrGWkh%2Fbtq3JSdrzIn%2F00o7b5Yh9jTcKiETFQwsPk%2Fimg.png&quot; data-filename=&quot;bnf16vimg.png&quot; data-origin-width=&quot;87&quot; data-origin-height=&quot;336&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;실행 결과&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;줄 간격을 결정하는 line-height의 단위&lt;/h2&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;px&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;글자 크기에 상관없이 line-height의 px값이 줄 간격이 됩니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;단위 없음(number)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;그냥 숫자만 적습니다.&lt;/p&gt;
&lt;p&gt;이 숫자는 글자 크기의 배수가 되어 줄 간격에 적용됩니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;(줄 간격) = (line-height값) x (font size)&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;%&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;글자 크기의 몇 % 인지로 줄 간격을 정합니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;참고로 line-height의 default 값은 단위 없이 1.2입니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;line-height의 상속&lt;/h2&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이 속성은 자손 태그들에게 상속이 됩니다.&lt;/p&gt;
&lt;p&gt;line-height값이 number이면 자손태그들에게 상속될 때, 자손 태그들의 폰트 사이즈가 제각각이기 때문에 간격이 각자의 font-size값에 따라 다르게 적용됩니다.&lt;/p&gt;
&lt;p&gt;하지만 line-height값이 px단위나 % 단위인 경우 자손 태그들에게 상속될 때, 부모 태그의 줄 간격과 동일하게&amp;nbsp;&lt;span style=&quot;color: #ee2323;&quot;&gt;고정 간격으로&lt;/span&gt; 적용됩니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;예를 들어 부모 요소의 글자크기가 30px, 자식요소의 글자크기가 20px일 때,&lt;/p&gt;
&lt;p&gt;부모요소의 line-height값이 1.5이면&lt;/p&gt;
&lt;p&gt;부모요소의 줄 간격은 45px, 자식요소의 줄간격은 30px입니다.&lt;/p&gt;
&lt;p&gt;부모 요소의 line-height값이 150%이면&lt;/p&gt;
&lt;p&gt;부모 요소와 자식 요소 모두 줄 간격이 45px입니다.&lt;/p&gt;
&lt;p&gt;부모 요소의 line-height값이 45px이면&lt;/p&gt;
&lt;p&gt;부모 요소와 자식 요소 모두 줄 간격이 45px입니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* 예시 코드&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1584863512835&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;p style=&quot;line-height: 1.5; font-size: 30px;&quot;&amp;gt;Lorem ipsum dolor sit amet, consectetur adipisicing elit. Eum, itaque!&amp;lt;br&amp;gt;
	&amp;lt;span style=&quot;font-size:20px&quot;&amp;gt;Lorem ipsum dolor sit amet, consectetur adipisicing elit. Voluptatum corporis veritatis dignissimos alias ab nesciunt.&amp;lt;/span&amp;gt;
&amp;lt;/p&amp;gt;
&amp;lt;p style=&quot;line-height: 150%; font-size:30px;&quot;&amp;gt;Lorem ipsum dolor sit amet, consectetur adipisicing elit. Eum, itaque!&amp;lt;br&amp;gt;
	&amp;lt;span style=&quot;font-size:20px&quot;&amp;gt;Lorem ipsum dolor sit amet, consectetur adipisicing elit. Voluptatum corporis veritatis dignissimos alias ab nesciunt.&amp;lt;/span&amp;gt;
&amp;lt;/p&amp;gt;
&amp;lt;p style=&quot;line-height: 45px; font-size:30px;&quot;&amp;gt;Lorem ipsum dolor sit amet, consectetur adipisicing elit. Eum, itaque!&amp;lt;br&amp;gt;
	&amp;lt;span style=&quot;font-size:20px&quot;&amp;gt;Lorem ipsum dolor sit amet, consectetur adipisicing elit. Voluptatum corporis veritatis dignissimos alias ab nesciunt.&amp;lt;/span&amp;gt;
&amp;lt;/p&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* 결과&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;915&quot; data-origin-height=&quot;717&quot; data-filename=&quot;b3XHGNimg.png&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bcnsJm/btq3KQAZPok/NU2yAZ23DmdVLfyhuqVb9k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bcnsJm/btq3KQAZPok/NU2yAZ23DmdVLfyhuqVb9k/img.png&quot; data-alt=&quot;실행 결과&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bcnsJm/btq3KQAZPok/NU2yAZ23DmdVLfyhuqVb9k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbcnsJm%2Fbtq3KQAZPok%2FNU2yAZ23DmdVLfyhuqVb9k%2Fimg.png&quot; data-origin-width=&quot;915&quot; data-origin-height=&quot;717&quot; data-filename=&quot;b3XHGNimg.png&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;실행 결과&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;좌우 정렬을 결정하는 text-align 속성 정리&lt;/h2&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;텍스트 좌우 정렬을 결정하기 위해서는 text-align 속성을 사용합니다.&lt;/p&gt;
&lt;p&gt;속성 값으로 각각 왼쪽 정렬은 left, 오른쪽 정렬은 right, 가운데 정렬은 center를 쓰면 됩니다.&lt;/p&gt;
&lt;p&gt;문단 내에서 각 줄마다 왼쪽 여백과 오른쪽 여백이 모두 없고 속성값으로 justify를 사용하면 됩니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* 예시 코드&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1584864197546&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;h2&amp;gt;text-align&amp;lt;/h2&amp;gt;

&amp;lt;p style=&quot;text-align:left&quot;&amp;gt;text-align:left&amp;lt;/p&amp;gt;
&amp;lt;p style=&quot;text-align:right&quot;&amp;gt;text-align:right&amp;lt;/p&amp;gt;
&amp;lt;p style=&quot;text-align:center&quot;&amp;gt;text-align:center&amp;lt;/p&amp;gt;

&amp;lt;h3&amp;gt;text-align:justify&amp;lt;/h3&amp;gt;
&amp;lt;p&amp;gt;Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ea qui eveniet laborum minima soluta reprehenderit, cumque sequi illum eaque, praesentium modi optio delectus maxime, ad! Minima quas, magnam quam, rem delectus repellendus aspernatur maiores esse impedit suscipit, officia commodi nihil quae eius optio fugiat architecto iste incidunt accusantium possimus dolorem.&amp;lt;/p&amp;gt;
&amp;lt;p style=&quot;text-align:justify&quot;&amp;gt;Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ea qui eveniet laborum minima soluta reprehenderit, cumque sequi illum eaque, praesentium modi optio delectus maxime, ad! Minima quas, magnam quam, rem delectus repellendus aspernatur maiores esse impedit suscipit, officia commodi nihil quae eius optio fugiat architecto iste incidunt accusantium possimus dolorem.&amp;lt;/p&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* 결과&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;IWze7img.png&quot; data-origin-width=&quot;948&quot; data-origin-height=&quot;507&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bQS6pE/btq3JBEhm2h/WIIkoYkhgpE3mGi37vEsMK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bQS6pE/btq3JBEhm2h/WIIkoYkhgpE3mGi37vEsMK/img.png&quot; data-alt=&quot;실행 결과&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bQS6pE/btq3JBEhm2h/WIIkoYkhgpE3mGi37vEsMK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbQS6pE%2Fbtq3JBEhm2h%2FWIIkoYkhgpE3mGi37vEsMK%2Fimg.png&quot; data-filename=&quot;IWze7img.png&quot; data-origin-width=&quot;948&quot; data-origin-height=&quot;507&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;실행 결과&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2&gt;줄 바꿈의 기준을 결정하는 word-break 속성&lt;/h2&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;word-break 속성의 값에 따라 줄바꿈 단위가 달라집니다.&lt;/p&gt;
&lt;p&gt;속성 값으로 break-all을 사용하면 글자 기준으로 줄 바꿈이 됩니다. keep-all을 사용하면 단어 기준으로 줄바꿈이 됩니다.&lt;/p&gt;
&lt;p&gt;word-break는 기본적으로 상속이 되는 속성입니다.&lt;/p&gt;
&lt;p&gt;그리고 언어마다 default 값이 다르다는 특이사항이 있습니다.&lt;/p&gt;
&lt;p&gt;가장 많이 쓰는 한글과 영어에 대해 이야기하자면, 한글은 글자 기준 줄 바꿈이 default이고, &lt;span style=&quot;color: #333333;&quot;&gt;영어는 단어 기준 줄 바꿈이 default입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>CSS</category>
      <category>align</category>
      <category>CSS</category>
      <category>font</category>
      <category>height</category>
      <category>Size</category>
      <category>text</category>
      <category>weight</category>
      <category>word-break</category>
      <author>cocoder16</author>
      <guid isPermaLink="true">https://cocoder16.tistory.com/31</guid>
      <comments>https://cocoder16.tistory.com/31#entry31comment</comments>
      <pubDate>Mon, 3 May 2021 08:00:46 +0900</pubDate>
    </item>
    <item>
      <title>[CSS 레이아웃 기본] 요소의 크기 정확하게 잡기 (box-sizing, border, margin, padding)</title>
      <link>https://cocoder16.tistory.com/30</link>
      <description>&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;box-sizing과 요소 영역의 관계에 대하여&lt;/h2&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;모든 요소들은 각자가 너비와 높이 값을 가지고 있습니다. CSS box=sizing 속성은 해당 요소의 너비와 높이를 어떻게 계산하는지를 결정하는 속성입니다. 크롬 개발자 도구에서 Elements - Styles 탭으로 들어가 가장 아래로 스크롤을 내리면 다음과 같은 그림을 볼 수 있는데, 이를 통해 요소의 너비(width)와 높이(height)를 결정할 수 있는 후보 속성들이 어떤 것들이 있는지 알 수 있습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/2Yhbb/btq2Rlgq9R8/Z9JiT5l1i92KQhgcAkoKp0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2Yhbb/btq2Rlgq9R8/Z9JiT5l1i92KQhgcAkoKp0/img.png&quot; data-alt=&quot;크롬 개발자 도구에서 본 요소의 영역 구분&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2Yhbb/btq2Rlgq9R8/Z9JiT5l1i92KQhgcAkoKp0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2Yhbb%2Fbtq2Rlgq9R8%2FZ9JiT5l1i92KQhgcAkoKp0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;크롬 개발자 도구에서 본 요소의 영역 구분&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;후보들에는 margin, border, padding css속성 값과 content 고유 영역이 있습니다. 그런데 이 중에 margin (바깥 여백)은 요소의 width와 height을 결정하는 데에 사용되지는 않습니다. 그럼 이제 margin, border, padding에 대해 각각 알아보고 그다음 box-sizing으로 요소의 크기를 결정하는 방법에 대해 알아보겠습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;테두리를 만드는 border 속성&lt;/h2&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;border속성은 요소에 테두리를 만들어주는 속성입니다.&lt;/p&gt;
&lt;p&gt;이 속성을 사용할 때 주의할 점은 border의 두께만큼 실제로 자리를 차지하기 때문에 해당 요소가 그 두께만큼 더 뚱뚱해지는 효과가 생기겠죠? 이로 인해 인접한 요소끼리 서로 자리가 밀려서 레이아웃이 깨지는 경우가 생길 수도 있습니다. 따라서 border를 사용할 때에는 border가 실제로 자리를 차지한다는 점을 꼭 상기해야 합니다.&lt;/p&gt;
&lt;p&gt;물론 이 현상은 box-sizing 속성이 default 값일 경우에만 발생하는데, 이에 대해서는 뒷부분에서 자세히 다뤄보겠습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;border 속성의 사용법은 다음과 같습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1618711462263&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;border:&amp;nbsp;두께값&amp;nbsp;line스타일&amp;nbsp;색상값;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;따로따로 사용하려면 -를 이용해 세부적으로 속성네임을 지정해주면 됩니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1618711501464&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;border-width:&amp;nbsp;두께값;
border-style: line스타일;
border-color: 색상값;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;border-width: 두께값;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;단위는 px를 주로 사용합니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;border-style: 스타일;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;값으로는 solid, dotted, dashed, double, groove, ridge, inset, outset 등을 사용합니다.&lt;/p&gt;
&lt;p&gt;이 값에 따라서 선의 종류가 실선, 점선 등으로 달라집니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;border-color: 색상값;&amp;nbsp;&lt;/span&gt;&lt;br /&gt;rgb() 나 rgba() 혹은 16진수 컬러코드를 이용해 색상값을 넣어줍니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* 예시코드&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1618711635325&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;border-width:&amp;nbsp;5px;
border-style: solid;
border-color: #ccc;

/* rgb를 이용하면 */
border-color: rgb(204, 204, 204);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;border는 기본적으로 동서남북 모든 방향으로 테두리가 생깁니다.&lt;/p&gt;
&lt;p&gt;특정 방향에만 테두리를 만들려면 다음과 같이 사용해야 합니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;border-top&lt;/p&gt;
&lt;p&gt;border-bottom&lt;/p&gt;
&lt;p&gt;border-left&lt;/p&gt;
&lt;p&gt;border-right&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* 예시 코드&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1584845350969&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;head&amp;gt;
    &amp;lt;style&amp;gt;
        div { width: 100px; height: 100px; margin: 10px; border: 1px dashed #aaa; }
        .all { border: 1px solid black; }
        .top { border-top: 1px solid black; }
        .red { border-top-color: red;}
    &amp;lt;/style&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
    &amp;lt;div class=&quot;all&quot;&amp;gt;&amp;lt;/div&amp;gt;
    &amp;lt;div class=&quot;top red&quot;&amp;gt;&amp;lt;/div&amp;gt;
&amp;lt;/body&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* 결과&lt;/span&gt;&lt;/p&gt;
&lt;div class=&quot;imageblock dual&quot; style=&quot;text-align: center;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; width=&quot;100&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xZgIH/btq2P31nRd7/BvI5NufIkNlre78HUbILsK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xZgIH/btq2P31nRd7/BvI5NufIkNlre78HUbILsK/img.png&quot; data-alt=&quot;예시코드 실행 결과 : 위가 all, 아래가 top red&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xZgIH/btq2P31nRd7/BvI5NufIkNlre78HUbILsK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxZgIH%2Fbtq2P31nRd7%2FBvI5NufIkNlre78HUbILsK%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; width=&quot;100&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;예시코드 실행 결과 : 위가 all, 아래가 top red&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;div class=&quot;revenue_unit_item adsense  responsive&quot;&gt;&lt;ins class=&quot;adsbygoogle&quot; style=&quot;display: block;&quot; data-ad-host=&quot;ca-host-pub-9691043933427338&quot; data-ad-client=&quot;ca-pub-9807016842906892&quot; data-ad-format=&quot;auto&quot;&gt;&lt;/ins&gt;
&lt;script&gt;(adsbygoogle = window.adsbygoogle || []).push({}); &lt;/script&gt;
&lt;/div&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;margin과 padding의 차이&lt;/h2&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;margin은 요소의 바깥쪽 여백의 값을, padding은 요소의 안쪽 여백의 값을 나타냅니다.&lt;/p&gt;
&lt;p&gt;따라서 margin은 요소의 바깥쪽 여백이므로 요소의 크기를 결정하는 속성에 포함되지 않습니다. 하지만 여백의 크기는 여백으로서 실효하게 됩니다.&lt;/p&gt;
&lt;div class=&quot;imageblock dual&quot; style=&quot;text-align: center;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;ecsPeaimg.png&quot; data-origin-width=&quot;622&quot; data-origin-height=&quot;628&quot; width=&quot;246&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qxRFR/btq2MAMNok7/rHB2w9s9bWe0gkJKSBAXWK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qxRFR/btq2MAMNok7/rHB2w9s9bWe0gkJKSBAXWK/img.png&quot; data-alt=&quot;margin과 padding값이 설정되어있고 그 값만큼 회색영역에 여백공간이 생겼습니다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qxRFR/btq2MAMNok7/rHB2w9s9bWe0gkJKSBAXWK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqxRFR%2Fbtq2MAMNok7%2FrHB2w9s9bWe0gkJKSBAXWK%2Fimg.png&quot; data-filename=&quot;ecsPeaimg.png&quot; data-origin-width=&quot;622&quot; data-origin-height=&quot;628&quot; width=&quot;246&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;margin과 padding값이 설정되어있고 그 값만큼 회색영역에 여백공간이 생겼습니다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;어떤 요소에 margin값과 padding값을 설정하면 그 값만큼 여백이 생겨 그 여백공간에는 다른 외부 요소가 위치할 수 없게 됩니다. 물론 position 등의 CSS 속성을 이용하면 위치시킬 수 있긴 하나, 일반적으로 CSS 속성을 따로 주지 않은 요소의 경우는 다른 요소의 여백공간에 들어가지 못합니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;margin과 padding의 사용법은 다음과 같습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1618712313539&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;margin: top여백값 right여백값 bottom여백값 left여백값;
padding: top여백값 right여백값 bottom여백값 left여백값;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;각 자리에 값을 넣어주면 됩니다. 단위는 offset단위와 같습니다. (px, %, auto)&lt;/p&gt;
&lt;p&gt;border와 마찬가지로 margin-top, padding-left 이런 식으로 margin이나 padding 옆에 -top, -right, -bottom, -left를 붙여서 사용할 수도 있습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* 예시 코드&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1584842508171&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/*top, right, bottom, left 모두에게 동일한 값 적용하기*/
margin: 50px;
padding: 10%;

/*서로 다른 값 적용하기*/
margin: 10px 0 10px 50px;
padding: 10px auto 10px 30px;

/*개별적으로 적용하기*/
margin-top: 10px;
margin-right: 5%;
padding-bottom: 40px;
padding-left: auto;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;margin, padding의 값으로 4개가 아닌 1개, 2개, 3개를 넣어줄 수도 있는데 이 경우 각각&lt;/p&gt;
&lt;p&gt;1개: top, right, bottom, left 모두에 적용&lt;/p&gt;
&lt;p&gt;2개: 첫 번째 값은 top, bottom에 적용, 두 번째 값은 right, left에 적용&lt;/p&gt;
&lt;p&gt;3개: 첫 번째 값은 top, 두번째 값은 right, left에 적용, 세 번째 값은 bottom&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이렇게 적용이 됩니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;box-sizing의 속성 값에 따른 요소 크기의 차이&lt;/h2&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이제 border, margin, padding에 대해 배웠으니 요소의 크기를 정확히 잡을 수 있습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;box-sizing의 속성은 두 가지가 있는데요. content-box (default)와 boder-box가 있습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;default인 &lt;span style=&quot;color: #ee2323;&quot;&gt;content-box&lt;/span&gt;는 요소의 컨텐트 크기만 width, height 값에 포함시킵니다.&lt;/p&gt;
&lt;p&gt;반면 &lt;span style=&quot;color: #ee2323;&quot;&gt;border-box&lt;/span&gt;는 요소의 컨텐트 크기 + padding + border까지 width, height 값에 포함시킵니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;width와 height 값이 100px인 어떤 요소가 있다고 생각해봅시다.&lt;/p&gt;
&lt;p&gt;이 요소에 padding 20px, border 5px을 부여하면 실제로 이 요소의 width, height값은&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;content-box&lt;/span&gt; 인 경우 100px + (20px * 2) + (5px * 2) = 150px이 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;border-box&lt;/span&gt; 인 경우는 100px 그대로 유지가 됩니다. 왜냐면 width에 이미 padding, border를 포함하는 것이 이 속성의 특징이기 때문이죠. 단 이 경우, padding, border의 크기만큼 실제 컨텐츠 영역은 줄어들게 되겠죠.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;content-box가 default 값이기 때문에 우리가 CSS를 다루다 보면 기본적으로는 padding과 border 속성을 부여할 때 요소의 크기가 커지게 되는 것을 경험할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;한편, margin은 어떻게 하든 width와 height 값에 포함되지는 않습니다. 말 그대로 바깥 여백이기 때문이죠.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;* 예시 코드&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1618713054601&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;div {
  width: 100px;
  height: 100px;
  padding: 20px;
  margin: 20px;
  border: 5px solid red;
}

.content-box {
  box-sizing: content-box;
  
  /* width or height: 100px + (2 * 20px) + (2 * 5px) = 150px
     Content box width or height: 100px */
}

.border-box {
  box-sizing: border-box;
  
  /* width or height: 100px
     Content box width or height: 100px - (2 * 20px) - (2 * 5px) = 50px */
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>CSS</category>
      <category>border</category>
      <category>box-sizing</category>
      <category>CSS</category>
      <category>Margin</category>
      <category>padding</category>
      <category>요소</category>
      <category>줄바꿈</category>
      <category>크기</category>
      <author>cocoder16</author>
      <guid isPermaLink="true">https://cocoder16.tistory.com/30</guid>
      <comments>https://cocoder16.tistory.com/30#entry30comment</comments>
      <pubDate>Mon, 19 Apr 2021 08:00:50 +0900</pubDate>
    </item>
  </channel>
</rss>