<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>a_in.log</title>
        <link>https://velog.io/</link>
        <description>프론트엔드 개발 연습장 ✏️ 📗</description>
        <lastBuildDate>Thu, 23 Jan 2025 15:48:44 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>a_in.log</title>
            <url>https://velog.velcdn.com/images/a_in/profile/bc5e097d-f0d2-436c-a578-f205159215d3/image.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. a_in.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/a_in" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[강제로 갈아탄 Obsidian - Dataview로 가계부 정리하기]]></title>
            <link>https://velog.io/@a_in/obsidian-financial-ledger-with-dataview</link>
            <guid>https://velog.io/@a_in/obsidian-financial-ledger-with-dataview</guid>
            <pubDate>Thu, 23 Jan 2025 15:48:44 GMT</pubDate>
            <description><![CDATA[<p>노션을 잘 쓰고 있던 와중에 무료 블록을 다 사용해버렸다는 문구가 뜨며 저는 이제 Obsidian(이하 옵시디언)으로 갈아타게 되었습니다.</p>
<p><img src="https://velog.velcdn.com/images/a_in/post/b55fa435-ede2-42ee-987d-413fb5971c94/image.png" alt=""></p>
<p>그동안 옵시디언을 주 노트앱으로 써보고 싶었지만 뭐언가 노션에서 쓰던 것처럼 마크다운만으로는 예쁘게 정리가 안되어서 미루고 미뤄왔는데요, 노션이 유료가 되버리면서(ㅠ) 옵시디언을 쓰기로 마음 먹었습니다. 이제부터 저의 것으로 만들어보려 합니다.</p>
<h2 id="메모앱-근데-이제-플러그인을-곁들인">메모앱, 근데 이제 플러그인을 곁들인..</h2>
<blockquote>
<p><em>Obsidian is the private and flexible writing app that adapts to the way you think.</em>
<em><a href="https://obsidian.md/">Obsidian.md</a></em></p>
</blockquote>
<p><a href="https://obsidian.md/">옵시디언</a>은 노션과 같은 매모앱입니다. 노션과 다른점이 있다면, 훨씬 자유롭다는 것입니다. 테마, 폰트 등 내 마음대로 꾸밀 수 있고 템플릿 만들기, 데이터뷰 조회, 폴더 정렬 등 다양한 기능들을 플러그인 형태로 자유롭게 다운받아 사용할 수 있습니다.</p>
<p>이번 글에선 노션에서 Database를 사용해 만든 가계부를 옵시디언의 Dataview로 만드는 방법을 소개해볼까합니다.</p>
<p>직접 플러그인을 만들어 사용하고 싶지만 우선은 있는걸 활용해서 만들어봅시다!</p>
<h2 id="dataview">Dataview</h2>
<p>저는 노션에서 표보기 형태로 가계부를 써왔습니다.
고정지출이 얼마나 있고, 합해서 얼마이고, 이체가 됐는지에 대한 여부 등등 돈을 애껴쓰기 위해 달마다 적어서 관리하고 있습니다.</p>
<p><img src="https://velog.velcdn.com/images/a_in/post/12a9f9b6-76db-437d-af63-cfaf78b780f0/image.png" alt="노션 가계부 표 데이터베이스 활용"></p>
<p>대문자 P인 저에게는 요 기능만큼 잘 써지던 가계부 템플릿이 없었기에 옵시디언에서도 그동안 써오던 형식으로 만들어보려고합니다.
.
.
.
.
.</p>
<p>엿보기!</p>
<p><img src="https://velog.velcdn.com/images/a_in/post/f3efe030-dbbd-4f5c-a0ca-d1735a283186/image.png" alt="옵시디언 Dataview 가계부"></p>
<p>.
.
.
.
.</p>
<h2 id="dateview-플러그인">Dateview 플러그인</h2>
<p><a href="obsidian://show-plugin?id=dataview">Dataview</a> 라는 플러그인을 사용하였는데, 노션처럼 테이블에서 데이터를 직접 수정하는게 아니더라구요.
그래서 기존 템플릿대로 만드는 방법을 못찾나 생각하던 찰나 다시 생각해보니 노션도 어쨌든 각 항목마다 페이지가 있고 페이지의 property가 테이블의 열로 들어갑니다.</p>
<p>그렇다면!
하나의 행에 해당하는 페이지를 직접 만들어서 테이블로 만들면 될 것 같습니다.</p>
<h2 id="테이블을-그리기-위한-페이지-생성">테이블을 그리기 위한 페이지 생성</h2>
<ul>
<li>우선 생활비, 고정지출, 저축 등등 항목들을 페이지로 만듭니다.
<img src="https://velog.velcdn.com/images/a_in/post/9416ba93-bc09-49b0-b3d6-5b6fec731d4b/image.png" alt="가계부 항목 생성"></li>
<li>페이지에 property를 만듭니다. 저의 경우 소비 카테고리, 소비내역, 지출경로 등을 추가합니다.
<img src="https://velog.velcdn.com/images/a_in/post/336baeda-9c62-4a54-ade4-18e03828e88f/image.png" alt="가계부 페이지 프로퍼티"></li>
</ul>
<h2 id="테이블-그리기">테이블 그리기</h2>
<p>테이블을 조회할 페이지를 만들고 테이블을 그리는 코드를 작성합니다.
제가 사용한것은 dataview의 자바스크립트 문법이며, 기본 문법은 이러합니다.</p>
<pre><code class="language-js">```dataviewjs
const table = await dv.query(`
TABLE WITHOUT ID 
  file.link as Name
  cost as Cost
  transfer as 이체
  category as Category
  card as Card
  assigned as Assigned

FROM &quot;02. Area/Money/2025/Feb&quot;
SORT category desc
`)```</code></pre>
<ul>
<li><strong>dv.query</strong> - dataview의 query 메소드로 테이블을 그릴 수 있음.</li>
<li><strong>TABLE</strong> - 테이블 뷰를 쓸 때 사용하는 용어다. TABLE외에도 LIST 등이 있음.</li>
<li><strong>WITHOUT ID</strong> - WITHOUT ID 없이 그린다면 파일이름과 링크가 걸려있는 FILE 열이 자동으로 생김. 이 열을 표시하고 싶지 않다면 WITHOUT ID를 추가 하면 되고, 다른 이름으로 표시하고 싶다면 file.link as Name으로 설정하면 됨.</li>
<li><strong>as</strong> - property 이름을 추가해서 테이블 헤더에 원하는 이름으로 표시 할 수 있음. ex: <code>cost as Cost</code> 를 넣는다면 페이지에 cost property가 들어가고, 테이블 헤더에는 &quot;Cost&quot;라는 이름으로 표시됨.</li>
<li><strong>FROM</strong> - 어느 폴더에서 페이지들을 가져올건지 경로를 기입</li>
<li><strong>SORT</strong> - 정렬을 할 수 있습니다.</li>
</ul>
<ol start="4">
<li>Header와 Cell들을 집어 넣습니다.<pre><code class="language-js">if ( table.successful ) {
dv.table(table.value.headers, table.value.values)
} else {
dv.paragraph(&quot;~~~~\n&quot; + table.error + &quot;\n~~~~&quot;)
}</code></pre>
<img src="https://velog.velcdn.com/images/a_in/post/ce0ac3ab-2992-442c-a3f1-4bbe89fe9c87/image.png" alt="가계부 테이블 뼈대"></li>
</ol>
<h2 id="꾸미기">꾸미기</h2>
<p>제가 필요한 가계부 정리 기능은 이게 전부이긴 합니다.
하지만 저는 이쁜게 최고이기 때문에 숫자도 천단위로 나누고, 카테고리도 칩으로 만들고 테이블도 테이블답게 꾸며보겠습니다.</p>
<pre><code class="language-js">
```dataviewjs
const query = QUERY_ARRAY.map((q) =&gt; `${q.dataKey} as ${q.name}`).join(&#39;, \n&#39;)
const table = await dv.query(`
TABLE WITHOUT ID
  file.link as Name
  cost as Cost
  transfer as 이체
  category as Category
  card as Card
  assigned as Assigned

FROM &quot;02. Area/Money/2025/Feb&quot;
SORT category desc
`)

if ( table.successful ) {
  const sumOfCost = table.value.values.map(a =&gt; a[1]).reduce((tmp, curr) =&gt; tmp + curr, 0).toLocaleString() 
  const tableHeaders = table.value.headers.map((header) =&gt; {
    return `&lt;span style=&#39;margin:14px 0;font-weight:400; font-size: 16px; &#39;&gt;${header}&lt;/span&gt;`
  })

  table.value.values.push([&quot;&lt;span style=&#39;float: right&#39;&gt;&lt;strong&gt;Total:&lt;/strong&gt;&lt;/span&gt;&quot;, `&lt;span style=&#39;font-weight:700;&#39;&gt;${sumOfCost}&lt;/span&gt;`])

  const tableValues = table.value.values.map((value) =&gt; {
    const cost = value[1]
    const transferList = value[2]
    const categoryList = value[3]
    const cardList = value[4]

    const transferBGColor = {
        자동이체: &quot;#134E6C&quot;,
        예정: &quot;#595959&quot;,
        완료: &quot;#1E7C19&quot;
    }[transferList?.[0]]
    const categoryBGColor = {
        저축: &quot;#6E3D3D&quot;,
        생활비: &quot;#2B586D&quot;,
        고정지출: &quot;#7F6F1B&quot;,
        월별: &quot;#0F4622&quot;
    }[categoryList?.[0]]
    const cardBGColor = {
        카카오뱅크: &quot;#7F6F1B&quot;,
        토스페이: &quot;#2B586D&quot;,
        신한K패스: &quot;#0F4622&quot;,
    }[cardList?.[0]]

    const transferChip = getRoundChip({ list: transferList, bgColor: transferBGColor })
    const categoryChip = getRectangleChip({ list: categoryList, bgColor: categoryBGColor })
    const cardChip = getRectangleChip({ list: cardList, bgColor: cardBGColor })

    const filtered = [
        &quot;&lt;span style=&#39;color: black&#39;&gt;&quot; + value[0] + &quot;&lt;/span&gt;&quot;,
        `&lt;span style=&#39;float: right&#39;&gt;₩ ${value[1].toLocaleString()}&lt;/span&gt;`,
        !!transferList ? transferChip : &quot;&quot;,
        !!categoryList ? categoryChip : &quot;&quot;,
        !!cardList ? cardChip : &quot;&quot;,
        ...value.slice(5, value.length - 1),
        value.at(-1) === false ? &quot;⬜️&quot; : value.at(-1) === true ? &quot;✅&quot; : &quot;&quot;
    ]
    return filtered
  })

  dv.table(tableHeaders, tableValues) 

} else {
  dv.paragraph(&quot;~~~~\n&quot; + table.error + &quot;\n~~~~&quot;)
}


// UTILS
function getRoundChip ({ list, bgColor }) {
    return `&lt;span style=&#39;white-space: nowrap;background-color: ${bgColor}; padding: 4px 8px; border-radius: 12px;font-weight:600&#39;&gt;${list}&lt;/span&gt;`
}
function getRectangleChip ({ list, bgColor }) {
    return  `&lt;span style=&#39;white-space: nowrap;background-color: ${bgColor}; padding: 2px 8px; border-radius: 4px;font-weight:600&#39;&gt;${list}&lt;/span&gt;`
}```</code></pre>
<p>cost 값들에 toLocaleString 함수를 넣어 숫자를 보기 쉽게 만들었고, 고정지출 내역 합계도 넣어봤습니다.
카테고리별로 색깔이 다른 칩을 만들고, assigned 값에 따라 체크박스와 체크된 박스를 표시해줍니다.</p>
<p><img src="https://velog.velcdn.com/images/a_in/post/47f7fb7e-fa48-410f-869f-dadb17b9f637/image.png" alt=""></p>
<p>오! 그럼 이제 조금은 보기 좋아졌죠?
저는 여기서 만족하지 못하고 테이블도 꾸며보려합니다. 테이블의 경우 dataview의 테이블을 직접 건들여야해서 css snippets를 추가해야합니다.</p>
<h2 id="css-파일-추가">CSS 파일 추가</h2>
<p><code>cmd + ,</code> &gt; Appearance &gt; CSS snippets 에서 토글을 열고 snippets 폴더안에 css 파일을 추가합니다.</p>
<p><img src="https://velog.velcdn.com/images/a_in/post/d395217d-a32b-4e36-a8e2-6972f5746eb4/image.png" alt="CSS snippets css 파일 추가 경로"></p>
<pre><code class="language-css">.dataview.table-view-table {
  // 테이블의 틀을 그립니다. border, border-radius 등등
}

.dataview.table-view-table tr {
  // 테이블 안의 cell 프레임을 꾸밀 수 있습니다.
}

.dataview.table-view-table th {
  // 테이블 안의 헤더를 꾸밀 수 있습니다.
}

.dataview.table-view-table td {
  // 테이블 안의 cell을 꾸밀 수 있습니다.
}</code></pre>
<h2 id="최종">최종!</h2>
<p><img src="https://velog.velcdn.com/images/a_in/post/f3efe030-dbbd-4f5c-a0ca-d1735a283186/image.png" alt="옵시디언 Dataview 가계부"></p>
<hr>
<p>그럼 이제 해당 항목의 페이지로 가서 property를 수정하면, 테이블에서도 한눈에 볼 수 있게 됩니다!</p>
<table>
<thead>
<tr>
<th>Before</th>
<th>After</th>
</tr>
</thead>
<tbody><tr>
<td><img src="https://velog.velcdn.com/images/a_in/post/d094c653-03f4-419c-9b22-b36f701a48e2/image.png" alt="관리비 항목 이체 property 수정전"></td>
<td><img src="https://velog.velcdn.com/images/a_in/post/8015847f-2719-4f0d-8004-cf0cac3848b3/image.png" alt="관리비 항목 이체 property 수정후"></td>
</tr>
</tbody></table>
]]></description>
        </item>
        <item>
            <title><![CDATA[자바스크립트의 선언자 - var, let, const]]></title>
            <link>https://velog.io/@a_in/javascript-declarations-var-let-const</link>
            <guid>https://velog.io/@a_in/javascript-declarations-var-let-const</guid>
            <pubDate>Sun, 08 Dec 2024 05:58:48 GMT</pubDate>
            <description><![CDATA[<p>변수라는 것은 프로그램을 짤 때, 알고리즘을 표현하기 위해서 꼭 필요한 존재입니다.
프로그래밍에서 데이터를 저장하고 관리하기 위해 사용되며, 프로그램이 실행되는 동안 값이 계속 바뀌거나 유지되어야 하는 경우, 이를 효율적으로 다루기 위해 변수가 필요합니다</p>
<h2 id="선언">선언</h2>
<p>변수를 선언하려면 선언자를 붙여서 변수를 선언합니다.</p>
<pre><code class="language-tsx">var 변수; // undefined</code></pre>
<p>할당 값 없이 변수만 선언할 경우, str이라는 변수에는 할당된 값이 없기 때문에 &quot;정의되지 않은&quot; 상태의 타입인 undefined가 들어갑니다.</p>
<h2 id="할당">할당</h2>
<p>변수에 값을 할당하려면 대입 연산자를 사용하여 할당합니다.</p>
<pre><code class="language-tsx">var 변수이름 = 할당할 값</code></pre>
<pre><code class="language-tsx">// ex:
var num = 100;
var animals = [&quot;🐶&quot;, &quot;🐱&quot;, &quot;🐵&quot;];
var dog = { name: &quot;Pet&quot;, age: 13, gender: &quot;male&quot; };
...</code></pre>
<h2 id="var-let-const">var, let, const</h2>
<p>ECMAScript 6부터는 <code>var</code>로 선언했을때의 단점을 보완한 <code>let</code>과 <code>const</code>라는 변수 선언자가 추가되었습니다.</p>
<h3 id="var">var</h3>
<ol>
<li>중복 선언이 가능합니다.<pre><code class="language-tsx">var cake = &quot;🎂&quot;;
var cake = &quot;🍰&quot;;</code></pre>
하지만 이때 중복으로 선언된 변수는 마지막 변수가 모든 중복된 변수를 덮어씌우기 때문에 마지막에 할당한 값인 조각케이크(🍰)가 출력됩니다.
만약에 짱구가 코드의 상단부에서 cake라는 변수에 홀케이크로 할당했는데, 훈이가 그것을 보지 못하고 하단부에서 조각케이크로 다시 할당을 해버린다면 짱구는 황당할 것입니다.</li>
</ol>
<ol start="2">
<li><p>유효범위가 전체이기 때문에 선언 전에 접근이 가능합니다(호이스팅).
<code>var</code>라는 선언자는 좋게 말하면 자유롭습니다. 선언한 위치 전체가 유효범위가 됩니다.
함수바깥에서 선언한 경우 프로그램의 전체가 유효범위가 되고, 함수안에서 선언했다면 지역 변수가 되어 함수 내 전체가 유효범위가 됩니다.</p>
<blockquote>
<p>⚡️ <strong>호이스팅이란?</strong>
호이스팅이란 것은 선언부가 유효범위의 상단으로 끌어올려지기 때문에, 선언 전에 변수를 사용 및 참조 할 수 있게 됩니다.</p>
<ul>
<li>선언 전에 변수를 사용할 수 있습니다.</li>
<li>선언 전에 ReferenceError를 던지지않고 변수를 참조 할 수 있습니다. 대신 값은 항상 <code>undefined</code> 입니다.</li>
<li>선언 전의 범위에 영향을 줄 수 있습니다.</li>
<li>선언의 부수 효과는 해당 선언을 포함하는 코드의 나머지를 평가하기 전에 발생합니다.
<em><a href="https://developer.mozilla.org/en-US/docs/Glossary/Hoisting">MDN - Hoisting</a></em></li>
</ul>
</blockquote>
<pre><code class="language-tsx">console.log(dog); // undefined
var dog = &quot;🐶&quot;
console.log(dog); // 🐶</code></pre>
<pre><code class="language-tsx">// dog 변수가 호이스팅되어 상단으로 끌어올려짐
var dog;
// 선언만 됐으므로 undefined가 됨.
console.log(dog);
var dog = &quot;🐶&quot;
console.log(dog);</code></pre>
</li>
</ol>
<p>이러한 특징들 때문에 뜻하지 않은 버그를 발생 시킬 수 있고, 혼란을 줄 수 있습니다.
이 때문에 <code>let</code>과 <code>const</code>가 탄생하며 조금 더 안전하게 코드를 작성 할 수 있게 됩니다.</p>
<h3 id="let-const">let, const</h3>
<ol>
<li><p>중복으로 선언하지 못합니다. (문법에러가 발생합니다.)</p>
<pre><code class="language-tsx">let dog = &quot;🐶&quot;;
let dog = &quot;🐕‍🦺&quot;;
// Uncaught SyntaxError: Identifier &#39;dog&#39; has already been declared</code></pre>
</li>
<li><p><code>let</code>과 <code>const</code>는 유효범위가 선언된 위치의 &quot;전체 범위&quot;인 <code>var</code>과 다르게 <strong>블록 유효 범위</strong>를 갖습니다. 또, 선언한 범위에 <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let#temporal_dead_zone_tdz">Temporal Dead Zone(일시적 사각지대)</a>이 생깁니다. 따라서, 선언 전에는 사용 및 참조를 하지 못합니다.
<img src="https://velog.velcdn.com/images/a_in/post/1280942b-2e45-419e-aa1d-5976322226e7/image.png" alt="TDZ"></p>
<pre><code class="language-ts">// var
function varFunc () {
console.log(cat);
var cat = &quot;🐱&quot;;
return cat;
}
varFunc();
// undefined
// 🐱</code></pre>
<pre><code class="language-ts">// let / const
function letConstFunc () {
console.log(cat);
let cat = &quot;🐱&quot;;
return cat;
}
letConstFunc();
// Uncaught ReferenceError: Cannot access &#39;cat&#39; before initialization</code></pre>
<p><strong>여기서!</strong> 호이스팅이 안된다고 할 수는 없는 경우가 있습니다.</p>
<pre><code class="language-tsx">const monkey = &quot;🐵&quot;;
{
console.log(monkey);
const monkey = &quot;🦧&quot;;
}
// Uncaught ReferenceError: Cannot access &#39;unicorn&#39; before initialization</code></pre>
<p><code>const</code>는 블록유효 범위를 가집니다. 따라서 위 코드에서는 블록안에서 monkey를 출력하려고 할때 상위 스코프에 있는 monkey(🐵)를 출력해야 할 것 같은데, 그렇지 않고 에러를 발생 시킵니다.
이는 const로 선언된 변수가 있을 때 Temporal Dead Zone을 만드는 것처럼, 선언된 스코프를 &#39;오염&#39;시키기 때문에 블록내에서 아직 초기화 되지 않은 monkey(🦧)를 가져오면서 에러를 던지게 됩니다.</p>
<p><img src="https://velog.velcdn.com/images/a_in/post/45ade764-1871-4f02-8648-2df9b87b2221/image.png" alt="const TDZ Uncaught ReferenceError"></p>
</li>
</ol>
<h3 id="let과-const의-차이점">let과 const의 차이점</h3>
<h4 id="1-초기화">1. 초기화</h4>
<p><code>let</code>은 초기값 없이 일단 변수만 선언할 수 있습니다.</p>
<pre><code class="language-ts">let cat;
let carrot, apple;
...</code></pre>
<p><code>const</code>는 반드시 값을 넣어 초기화를 해야합니다.</p>
<pre><code class="language-ts">// ✅
const cat = &quot;meow&quot;;
// ❌
const cat; // Uncaught SyntaxError: Missing initializer in const declaration</code></pre>
<h4 id="2-재할당">2. 재할당</h4>
<p><code>let</code>은 한번 선언한 변수에 다른 값을 재할당할 수 있습니다.</p>
<pre><code class="language-tsx">let i = 0;
while(i &lt; 3) {
  i += 1;
  console.log(i);
};
// 1
// 2
// 3</code></pre>
<p><code>const</code>는 상수의 의미이기 때문에 한번 선언한 변수에 다른 값을 재할당할 수 <strong>없</strong>습니다.</p>
<pre><code class="language-tsx">const i = 0;
while(i &lt; 3) {
  i += 1;
  console.log(i);
};
// Uncaught TypeError: Assignment to constant variable.</code></pre>
<blockquote>
<p>⚡️ <strong><code>const</code>로 선언한 값이 객체인 경우</strong>
<code>const</code>로 선언한 값이 객체인 경우에는 마찬가지로 완전히 새롭게 재할당 하는것은 안되지만, 객체의 프로퍼티는 수정할 수 있습니다.</p>
</blockquote>
<pre><code class="language-ts">// ✅
const animals = [&quot;🐭&quot;, &quot;🐮&quot;, &quot;🐯&quot;, &quot;🐇&quot;, &quot;🐲&quot;];
animals.push(&quot;🐍&quot;)
console.log(animals); // [&quot;🐭&quot;, &quot;🐮&quot;, &quot;🐯&quot;, &quot;🐇&quot;, &quot;🐲&quot;, &quot;🐍&quot;];</code></pre>
<pre><code class="language-ts">// ❌
const animals = [&quot;🐭&quot;, &quot;🐮&quot;, &quot;🐯&quot;, &quot;🐇&quot;, &quot;🐲&quot;];
animals = [&quot;🐭&quot;, &quot;🐮&quot;, &quot;🐯&quot;, &quot;🐇&quot;, &quot;🐲&quot;, &quot;🐍&quot;];
// Uncaught TypeError: Assignment to constant variable.</code></pre>
<h2 id="함수-리터럴-feat-화살표-함수">함수 리터럴 (feat. 화살표 함수)</h2>
<p>함수를 선언하는 방식으로는 함수 선언식 말고도 <strong>함수 표현식</strong>이 있습니다.
변수에 함수를 할당하는 방식이며 함수 리터럴로 할당을 할 수 있습니다.</p>
<pre><code class="language-ts">// 함수 선언문
function sayMeow () {
  console.log(&quot;meow&quot;);
};</code></pre>
<p>또는 화살표 함수로 할당할 수도 있습니다.</p>
<pre><code class="language-ts">// 익명함수를 sayMeow라는 변수에 할당
const sayMeow = function() {
  console.log(&quot;meow&quot;);
};
// 화살표 함수
const sayMeow = () =&gt; {
  console.log(&quot;meow&quot;);
};</code></pre>
<p>함수 선언문은 이전에 언급했던 <code>var</code>키워드로 선언한 변수와 동일하게 호이스팅이 되지만, 함수 표현식의 경우 호이스팅이 되지 않습니다.
변수에 함수를 할당하는 것이기 때문에 변수에 할당하고 나서야 함수는 그 변수의 이름을 가지게 되며, 해당 이름으로 호출 할 수 있게 됩니다.</p>
<pre><code class="language-ts">sayMeow(); // Uncaught ReferenceError: sayWaffle is not defined
const sayMeow = function () {
  console.log(&quot;meow&quot;);
}</code></pre>
<h2 id="참고">참고</h2>
<ul>
<li><a href="https://developer.mozilla.org/en-US/docs/Glossary/Hoisting">MDN - Hoisting</a></li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/var">MDN - var</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[자바스크립트의 데이터 타입 - 원시타입과 참조타입]]></title>
            <link>https://velog.io/@a_in/javascript-data-type</link>
            <guid>https://velog.io/@a_in/javascript-data-type</guid>
            <pubDate>Thu, 05 Dec 2024 17:21:14 GMT</pubDate>
            <description><![CDATA[<p>우리가 다루는 데이터들을 메모리에 저장하기 위해서는 변수에 할당을 하고, 필요할 경우 가공을 하고, 또 재사용을 합니다. 에러 없이 데이터를 다루기 위해서는 데이터의 타입에 대해서도 알아야 할 필요가 있는데요, 특히 참조 타입의 경우, 잘 모르고 데이터를 가공할 경우 뜻하지 않은 에러를 발생시킬 수 있습니다.</p>
<div align="center">
  <img src="https://velog.velcdn.com/images/a_in/post/5b313d9f-b3c5-4d19-99aa-839d0dd1efc1/image.jpg" width="400" height="400" />
</div>

<p>자바스크립트에서 데이터의 타입을 어떻게 분류하는지 알아보고자 예시를 들며 정리하였습니다.</p>
<h1 id="자바스크립트에서의-데이터-타입">자바스크립트에서의 데이터 타입</h1>
<p>다른 언어에서는 <code>int</code>, <code>byte</code>와 같은 특정 타입의 변수가 있는 반면, 자바스크립트에서는 변수에 타입이 없어 모든 타입을 할당 할 수 있습니다. 그리고 할당 할 수 있는 값에는 원시 타입과 참조 타입 두개의 타입으로 구분되며, 이는 참조하는 방식 기준으로 구분됩니다.</p>
<h2 id="원시-타입">원시 타입</h2>
<p>원시 값은 <strong>불변</strong>값입니다.<span style="color: gray"> (불변값이란 값 자체를 바꿀수 없다는 것을 뜻합니다.)</span> 값은 메모리 스택 영역에 저장됩니다.</p>
<p>원시타입에는 숫자, 문자열, 논리값, 특수한 값(<code>undefined</code>, <code>null</code>), 그리고 ECMAScrip6에 추가된 심벌 타입이 포함됩니다.</p>
<h3 id="메소드와-속성">메소드와 속성</h3>
<p>이러한 원시 타입들은 메소드나 내장 속성이 없지만, 값에서 속성에 접근하게 되면 자바스크립트는 그 값을 해당 타입의 래퍼 객체(Object wrapper)로 자동으로 감싸서 객체의 속성에 접근할 수 있게 됩니다.</p>
<pre><code class="language-ts">const name = &#39;rachel&#39;;
console.log(name.length) // 6</code></pre>
<p>위의 코드 예제처럼 원시값인 name의 length 속성에 접근 할 때, 자바스크립트는 name에 String() 레퍼 객체로 감싸줌으로써 객체로 만들어 메소드와 속성에 접근 할 수 있게 됩니다.</p>
<pre><code class="language-ts">// same
const name = &#39;rachel&#39;;
console.log(String(name).length) // 6</code></pre>
<p>이는 숫자, 논리값 또한 마찬가지입니다.</p>
<pre><code class="language-ts">Number(2)
Boolean(true)
...</code></pre>
<h2 id="참조-타입">참조 타입</h2>
<p>참조 타입은 객체 타입으로도 불리며 원시타입을 제외한 모든 객체가 참조타입으로 분류됩니다. 객체, 배열, 함수, 생성자 등이 참조 타입에 포함되며, 해당 값들을 변수에 할당할 시, 변수 메모리에는 객체가 저장되어있는 메모리의 주소값(위치 정보)을 참조하게 됩니다.</p>
<pre><code class="language-ts">const photo = { createdAt: &quot;2024-12-05&quot;, name: &quot;my-photo&quot; };
const file = photo;</code></pre>
<p><img src="https://velog.velcdn.com/images/a_in/post/ad9be05a-0214-4239-a317-78018ab8a5aa/image.png" alt="객체가 참조되는 과정 시각화"></p>
<p>여러개의 변수가 같은 객체 값을 참조하고 있다면, 어느 하나라도 변경이 일어나면 다른 변수에서도 변경이 발생합니다. 값의 주소를 참조하고 있어 한개의 값만을 읽고 사용하기 때문입니다.</p>
<pre><code class="language-ts">file.name = &quot;modified-photo&quot;;
console.log(photo) // { createdAt: &quot;2024-12-05&quot;, name: &quot;modified-photo&quot; }
console.log(photo.name) // &quot;modified-photo&quot;</code></pre>
<h3 id="함수의-예시">함수의 예시</h3>
<p>함수에 전달하는 인수에 객체를 전달할때에도 참조값이 되는 것은 마찬가지입니다.
<code>parameter(매개변수)</code>는 변수이고, 인자를 전달할때 매개변수에도 할당(참조)이 됩니다.</p>
<pre><code class="language-ts">const initialObj = {x: 1, y: 1}
const result = add(initialObj);

function add (obj) {
  obj.sum = obj.x + obj.y
  return obj;
};

console.log(initialObj) // {x: 1, y: 1, sum: 2}
console.log(result) // {x: 1, y: 1, sum: 2}
</code></pre>
<p>위 코드에서는, 객체를 전달하여 객체 안의 x값과 y값을 더하고 합을 계산하는 함수 add가 있습니다. </p>
<p>add 함수 내부를 보면 매개변수의 값들을 더한 후, sum이라는 키를 만들어 합계값을 할당하고 있습니다. 그렇기 때문에, <code>{x: 1, y: 1}</code>를 넣었을때 <code>{x: 1, y: 1, sum:2}</code> 가 반환되는게 당연합니다. 하지만 이를 <code>initialObj</code>라는 변수에 할당하였고 <code>initialObj</code>와 매개변수 <code>obj</code>는 같은 객체 주소를 바라보고 있기 때문에, add 함수 안에서 바뀐 객체가 <code>initialObj</code>에서도 바뀌게 됩니다.</p>
<blockquote>
<p>** ⚡️ 인자가 원시값인 경우는?**
인자가 원시값인 경우엔 위에서 원시타입에 대해 설명한 바와 똑같이 작동합니다.
어떠한 함수에서 x와 y를 매개변수로 받고 x나 y의 값을 변경할때, 매개변수는 바뀌겠지만 해당 함수에 전달한 원시값은 바뀌지 않습니다.</p>
</blockquote>
<pre><code class="language-ts">function getX (x) {
  x = 10;
  return x;
};
const _x = 1;
const result = getX(_x);
console.log(_x); // 1
console.log(result); // 10</code></pre>
<h3 id="배열의-예시">배열의 예시</h3>
<p>배열 또한 참조 타입에 해당되기 때문에 원본 객체를 참조 하고 있는 변수를 건들인다면 원본 객체까지 영향이 갑니다.</p>
<pre><code class="language-ts">const students = [
  { name: &quot;chandler&quot; },
  { name: &quot;amy&quot; },
  { name: &quot;zoe&quot; }
]

const sortedStudents = students.sort((a, b) =&gt; a.name.localeCompare(b.name));

console.log(students); 
// [
//  { name: &quot;amy&quot; },
//  { name: &quot;chandler&quot; },
//  { name: &quot;zoe&quot; }
// ]
console.log(sortedStudents);
// [
//  { name: &quot;amy&quot; },
//  { name: &quot;chandler&quot; },
//  { name: &quot;zoe&quot; }
// ]</code></pre>
<p>위 코드처럼 students라는 배열을 정렬하고 싶을때, sort 함수를 바로 사용하게 되면 students 객체까지 정렬이 되버려 정렬이 안되어 있는 원본 객체는 사용하지 못하게 됩니다.</p>
<blockquote>
<p>⚡️ <strong>sort 말고 toSorted</strong>
최근에 추가된 sort의 복사 대응 버전 toSorted를 사용하면, 정렬된 새로운 배열을 반환하여 참조없이 사용할 수 있습니다.
<a href="https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Array/toSorted">MDN - Array.prototype.toSorted()</a></p>
</blockquote>
<h2 id="ps-얕은-복사--깊은-복사">p.s. 얕은 복사 / 깊은 복사</h2>
<p>원본 객체의 참조를 끊고 객체를 복사할 하려면 얕은 복사나 깊은 복사를 해야합니다.</p>
<p><strong>얕은 복사</strong>란 객체에서 중첩 첫번째 단계까지만 참조를 끊는 것입니다.</p>
<h3 id="얕은-복사의-예시-objectassign">얕은 복사의 예시 (Object.assign())</h3>
<pre><code class="language-ts">const initialData = {
  name: &quot;rachel&quot;,
  age: 30,
  child: {
    name: &quot;Emma&quot;,
    gender: {
      name: &#39;female&#39;
    }
  }
};

const data = Object.assign({}, initialData);
_data.name = &quot;ross&quot;;
_data.child.name = &quot;Ben&quot;;
_data.child.gender = &quot;male&quot;;

console.log(data);
// {
//  name: &quot;rachel&quot;,
//  age: 30,
//  child: {
// **changed
//    name: &quot;Ben&quot;,
//    gender: {
//      name: &#39;male&#39;
//    }
//  }
// };</code></pre>
<p>여기서 첫번째 단계에는 name과 age가 있고, 그 다음 단계에는 child의 name이 있습니다. 이 data를 얕은 복사 하게 된다면 child의 name부터 바뀌게 됩니다.</p>
<h3 id="깊은-복사의-예시-jsonparse-jsonstringyfy">깊은 복사의 예시 (JSON.parse, JSON.stringyfy)</h3>
<p><strong>깊은 복사</strong>는 중첩이 몇단계가 되었든 모두 복사하여 새로운 객체를 생성합니다.</p>
<pre><code class="language-ts">const initialData = {
  name: &quot;rachel&quot;,
  age: 30,
  child: {
    name: &quot;Emma&quot;,
    gender: {
      name: &#39;female&#39;
    }
  }
};

const data = JSON.parse(JSON.stringify(initialData));
_data.name = &quot;ross&quot;;
_data.child.name = &quot;Ben&quot;;
_data.child.gender = &quot;male&quot;;

console.log(data);
// {
//  name: &quot;rachel&quot;,
//  age: 30,
//  child: {
//    name: &quot;Emma&quot;,
//    gender: {
//      name: &#39;female&#39;
//    }
//  }
// };</code></pre>
<p>위 코드처럼 원본 객체에는 아무런 영향이 가지 않는 걸 볼 수 있습니다.</p>
<hr>
<p>이 글을 본 분들은 자바스크립트 기본기를 탄탄하게 세워 뜻하지 않은 에러로 불필요한 디버깅을 하지 않길 바랍니다.
🙏🏻</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[git 명령어 Cheat Sheet]]></title>
            <link>https://velog.io/@a_in/git-command-Cheat-Sheet</link>
            <guid>https://velog.io/@a_in/git-command-Cheat-Sheet</guid>
            <pubDate>Tue, 24 Oct 2023 12:47:41 GMT</pubDate>
            <description><![CDATA[<h2 id="git-branch">git branch</h2>
<p><strong>1. branch 리스트 보기</strong></p>
<pre><code class="language-bash">git branch</code></pre>
<p><strong>2. branch 생성</strong></p>
<pre><code class="language-bash">git branch BRANCH_NAME
git branch feat/1</code></pre>
<p><strong>3. branch 지우기</strong></p>
<ul>
<li>변경사항이 없는 브랜치 지우기<pre><code class="language-bash">git branch -d BRANCH_NAME
git branch -d feat/1</code></pre>
</li>
<li>변경사항이 있거나 없거나 강제로 지우기<pre><code class="language-bash">git branch -D BRANCH_NAME
git branch -D feat/1</code></pre>
</li>
</ul>
<p><strong>4. branch 여러개 지우기</strong></p>
<pre><code class="language-bash">git branch | grep &quot;BRANCH_NAME&quot; | xargs git branch -D
git branch | grep &quot;feature&quot; | xargs git branch -D</code></pre>
<p>feature가 포함되는 브랜치 전부 삭제</p>
<h2 id="git-checkout">git checkout</h2>
<p><strong>1. 로컬에서 브랜치 이동</strong></p>
<pre><code class="language-bash">git checkout BRANCH_NAME
git checkout feat/1</code></pre>
<p><strong>2. 로컬에서 브랜치 생성 및 해당 브랜치로 이동</strong></p>
<pre><code class="language-bash">git checkout -b BRANCH_NAME
git checkout -b feat/1</code></pre>
<p><strong>3. 원격에만 있고 로컬엔 없는 브랜치로 이동 (switch안됨)</strong></p>
<pre><code class="language-bash">git fetch
git remote update</code></pre>
<p>원격 상태를 업데이트 한 후에 checkout</p>
<pre><code class="language-bash">git checkout REMOTE_BRANCH_NAME
git checkout feat/1</code></pre>
<h2 id="git-commit--git-push">git commit / git push</h2>
<p><strong>1. stage에 올리기</strong></p>
<pre><code class="language-bash">git add FILE_PATH
git add .
git add src/datas/menu.ts</code></pre>
<p><strong>2. stage에 올라가있는지 아닌지 확인(수정된 파일 확인)</strong></p>
<pre><code class="language-bash">git status</code></pre>
<p><strong>3. 커밋푸시했는데 안 올라가있을 때(유실됐을때) 커밋 내역 확인</strong></p>
<pre><code class="language-bash">git reflog</code></pre>
<p><strong>4. fetch 하고 origin 코드 pull 땡기기</strong></p>
<pre><code class="language-bash">git fetch
git pull origin BRANCH</code></pre>
<p>*git pull 은 단축키 ggl로 대체가능</p>
<ul>
<li>협업 할 때는 원격에 있는 다른 사람의 코드를 항상 내 로컬 코드로 업데이트 해줘야 하기 때문에 git pull을 수시로 해준다.</li>
<li>내 코드를 다 커밋했는데 원격에 업데이트 된 코드가 있을 때는 push가 안된다. 이 때 pull을 땡기면 내가 이미 커밋한 상태에서 업데이트 된 원격 코드가 로컬로 들어오기 때문에 이 코드들도 하나의 커밋으로 올려줘야한다. (이렇게 되면 남이 작업한 것들이 내 커밋이 됨)</li>
<li>내가 작업중이던 코드들은 stash해두고 git pull을 해준다음, 다시 stash pop을 하여 병합.</li>
</ul>
<p><strong>5. 커밋하기</strong></p>
<ul>
<li>쌍 따옴표 바로 뒤에 오는 텍스트는 제목이 되고, 줄바꿈을 두번하여 커밋 내용 작성.</li>
<li>한번만 줄바꿈하면 커밋 제목으로 인식</li>
</ul>
<pre><code class="language-bash">git commit -m &quot;{COMMIT_MSG}&quot;
git commit -m &quot;Feat: commit title
dquote &gt;
dquote &gt; commit body commit body commit body&quot;</code></pre>
<p><strong>6. 작업한 브랜치로 푸시하기</strong></p>
<pre><code class="language-bash">git push origin BRANCH_NAME
git push origin feat/1</code></pre>
<p>*git push는 단축키 ggp로 대체가능</p>
<p><strong>7. 원격 코드 로컬로 가져오기</strong></p>
<pre><code class="language-bash">git pull origin BRANCH_NAME
git pull origin develop</code></pre>
<p><strong>8. 브랜치에서 작업 후 PR 안날리고 develop에 머지하기</strong></p>
<ul>
<li>PR을 안날리고 메인에 바로 머지 하려면 <code>git merge</code> 명령어 사용<pre><code class="language-bash">%feat/1 git push origin feat/1 
%feat/1 git checkout develop
%develop git merge feat/1
%develop git push origin develop</code></pre>
</li>
</ul>
<p><strong>9. PR 날렸는데 자동으로 머지 안된다고 표시 될 때</strong></p>
<ul>
<li>Resolve Conflict를 눌러 충돌을 해결하고, 충돌을 해결했다는 버튼(Mark as Resolve)을 클릭</li>
<li>완료 후 머지</li>
</ul>
<p><strong>10. 브랜치에서 작업 후 PR 날리고 원격에서 머지했을 때, 원격에 있는 develop 브랜치의 코드 로컬로 가져오기</strong></p>
<ul>
<li>원격에 머지했다고 해서 로컬에서도 자동으로 머지 되는 것이 아님<pre><code class="language-bash">%feat/1 git checkout develop
%develop git pull origin develop</code></pre>
</li>
</ul>
<p><strong>11. 방금 올린 마지막 커밋 되돌리기(Abort Commit)</strong></p>
<ul>
<li>push 다 했는데 로컬에 원격과 fetch되지 않은 코드들이 있어 push가 되지 않을 때<pre><code class="language-bash">git reset --soft HEAD^</code></pre>
</li>
<li>push 한 코드 되돌리는 방법은 커밋이 꼬이기 때문에 협업하는 상황에서는 쓰지 않도록 해야함</li>
</ul>
<h2 id="git-stash">git stash</h2>
<p><strong>1. git stash</strong></p>
<ul>
<li>내가 작업한 코드를 임시로 다른 장소에 저장한다. (stash는 사전적으로 &quot;안전한 곳에 넣어두다&quot; 라는 뜻)<pre><code class="language-bash">git stash</code></pre>
</li>
</ul>
<p><strong>2. git stash list</strong></p>
<ul>
<li>stash 된 코드들을 리스트로 볼 수 있음.<pre><code class="language-bash">stash@{0}: ~
stash@{1}: ~</code></pre>
</li>
</ul>
<p>*<em>4. git stash apply *</em></p>
<ul>
<li>마지막에 stash한 코드를 stash list에서 꺼내오기<pre><code class="language-bash">git stash apply</code></pre>
</li>
<li>특정 stash한 코드를 stash list에서 꺼내오기<pre><code class="language-bash">git stash apply stash@{1}</code></pre>
</li>
<li>stash@{1}를 꺼내옴</li>
</ul>
<p><strong>3. git stash pop</strong></p>
<ul>
<li>마지막에 stash 한 코드를 꺼내고 stash list에서 제거 (apply와는 다르게 저장한 곳에서도 삭제)<pre><code class="language-bash">git stash pop</code></pre>
</li>
</ul>
<h2 id="origin-새로-등록">origin 새로 등록</h2>
<ul>
<li><p>기존 remote 삭제</p>
<pre><code class="language-bash">git remote remove origin</code></pre>
</li>
<li><p>remote 등록</p>
<pre><code class="language-bash">git remote add origin REMOTE_URL</code></pre>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[23년 5월 회고]]></title>
            <link>https://velog.io/@a_in/2023-may-review</link>
            <guid>https://velog.io/@a_in/2023-may-review</guid>
            <pubDate>Mon, 19 Jun 2023 17:20:50 GMT</pubDate>
            <description><![CDATA[<h2 id="회고">회고</h2>
<p>7월이 오기 전에 5월 회고를 작성해보려한다. 5월은 4월달부터 시작한 프리온보딩 인턴십 덕분에 다시 적극적으로 코딩 공부를 시작할 수 있었는데, 이에 대한 계기를 적어보려 한다.</p>
<h3 id="알고리즘-연습문제-풀이-재시작">알고리즘 연습문제 풀이 재시작</h3>
<p>작년 초에 하다가 버거워서 잠시 멈췄던 알고리즘 연습을 2023년 5월 20일에 다시 시작하게 됐다. 하루하루 기록을 남기고 잔디를 보면서 원동력을 갖기 위해 푼 문제는 GitHub 알고리즘 레파지토리에 올리기로 정했다. 이번에는 하루에 하나씩 꾸준히 하기 위해 프로그래머스 level 1로 연습하기 시작했다. 문제와 풀이를 완전히 이해하기 위해 문제설명, 예제 등을 기록하고, 내가 푼 풀이와 다른 사람이 푼 풀이를 markdown으로 작성하여 커밋한다.</p>
<p>작년에는 leetcode로 easy문제를 푸는데도 기발한 문제와 edge case들이 많아 일찍 버거워했던 것 같아 그나마 좀 덜 한 프로그래머스부터 시작해보려 한다. 적응이 되면 다시 leetcode로 돌아갈 예정이다.</p>
<h3 id="블로그-다시-꾸준히">블로그 다시 꾸준히</h3>
<p>블로그를 한번 쓰기 시작하면 반나절을 잡아먹는 바람에 블로그 작성은 큰 짐으로 남아있던 것 같다. 예전에도 하루에 한번 쓰겠다고 다짐했었는데 그런 식으로 하면 의미 없는 블로그를 쓰게 될 것 같아 방식을 조금 바꾸려고 한다.</p>
<p>꾸준히는 쓰되, 의미없는 블로그는 쓰지 않는다. 내가 직접 경험해 보고, 직접 도입해본 기술에 대해서만 정리하고, 내가 직접 마주한 문제에 대해서 삽질 하는 과정을 블로그로 담을 예정이다. 그 외의 내가 구현한 코드만 달랑 올려 놓는다거나 책이나 강사가 말한 내용을 그대로 옮겨 적는 블로그는 쓰지 않겠다는 것이다.</p>
<p>나중에 봤을 때도 지우기 아까운 블로그를 쓰는 것을 목표로 하겠다.</p>
<h3 id="인턴십-우수-교육생">인턴십 우수 교육생</h3>
<p>4월에 진행한 원티드 프리온보딩 프론트엔드 인턴십(<a href="https://velog.io/@a_in/april-retrospectives">회고</a>)에서 우수 교육생으로 선정되었다. 300명 정도 되는 지원자 중에 10명이 선정되는 혜택이기 때문에 기분이 꽤 좋았다. 이를 원동력 삼은 덕분에 알고리즘과 블로그를 지금까지도 꾸준히 진행 할 수 있었던 것 같다. </p>
<h3 id="사이드-프로젝트">사이드 프로젝트</h3>
<p>현재 진행하고 있는 사이드 프로젝트에 SWR이나 React-query를 적용해보려 한다. 아직까지 SWR의 필요성까지만 느껴 React-query는 보류하고 있었는데, 배워두면 나쁠 게 없을 것 같아 React-query 사용도 고민해 보고있다. 만약 도입하게 된다면 React-query에 대해 조금 더 공부해 보고 적용해보겠다.</p>
<h3 id="벌써-6월-중순">벌써 6월 중순...</h3>
<p>취직하겠다고 한게 작년 겨울이였던 것 같은데 계속 부족하다고 생각해 지원도 안하고 있다가 이제서야 지원을 시작했다. 지금도 부족한게 많아 공부를 더 하고 싶지만 우선순위(취직)를 먼저 해치워야 할 것 같다.</p>
<h3 id="이번-달에-해보고-싶은-것">이번 달에 해보고 싶은 것</h3>
<p><strong>Three.js</strong>
먼저 예전에 채용공고에서 봤던 스택인 Three.js를 공부해보고 싶다. 나는 시각적인 요소에 예민하고 웹 사이트에 심플한 애니메이션을 넣는 것에도 욕심이 많기 때문에 Three.js 배워놓으면 써먹을 일이 많을 것 같다! 시간 날 때 강의 보면서 간단하게 사용법을 익혀야겠다.</p>
<h3 id="시간-관리의-중요성">시간 관리의 중요성</h3>
<p>최근 시간 계획표를 엄청 잘 짜놓은 트위터를 보았다. 나 같았으면 일주일동안 했을 일을 하루만에 하는 걸 보고 좀... 자신을 뒤돌아보게 됐다.</p>
<p>나는 아침형 인간이 아니라 일찍 자고 아침에 일어나는 것을 잘 못한다 (중요한 일정 있을 때를 제외하고). 그래서 저녁 11시에 자도 다음날 13시에 일어나고, 다음날 아침 6시에 자도 그날 13시에 일어난다. 일찍 자고 일찍 일어나는 것을 한 2주동안 하다보면 습관이 된다는데 나는 그게 잘 안된다.</p>
<p>그 날의 할일을 적어놓지 않으면 까먹을 수 있을 뿐더러 우선순위를 정하지 않았기 때문에 시간낭비를 할 수도 있다고 느꼈다. 장기적인 일은 조금 더 세분화해서 적어놓고, 매일 해야 하는 일은 하루일과 중 제일 처음 할 일으로 두려고 한다.</p>
<p>그래서 일단 시간계획을 짜는 것부터 연습 한 다음, 일찍 일어나는 것을 연습해야겠다.</p>
<h2 id="마무리">마무리</h2>
<p>추석이 오기 전에 취직했으면 한다..🍁</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[프로그래머스/탐욕법(Greedy)] Level 2 | 조이스틱 (JavaScript)]]></title>
            <link>https://velog.io/@a_in/Programmers-Greedy-Level-2-Joystick-JavaScript</link>
            <guid>https://velog.io/@a_in/Programmers-Greedy-Level-2-Joystick-JavaScript</guid>
            <pubDate>Sat, 17 Jun 2023 15:12:18 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>풀이는 <a href="https://bbeeyaks-moment.tistory.com/entry/%EA%B7%B8%EB%A6%AC%EB%94%94-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EC%A1%B0%EC%9D%B4%EC%8A%A4%ED%8B%B1-js">bbeyak</a>님의 풀이와 <a href="https://ghost4551.tistory.com/113">rio(리우)</a>님의 풀이를 참고하였습니다.</p>
</blockquote>
<h3 id="문제-설명">문제 설명</h3>
<p>조이스틱으로 알파벳 이름을 완성하세요. 맨 처음엔 A로만 이루어져 있습니다.
ex) 완성해야 하는 이름이 세 글자면 AAA, 네 글자면 AAAA</p>
<p>조이스틱을 각 방향으로 움직이면 아래와 같습니다.</p>
<blockquote>
<p>▲ - 다음 알파벳
▼ - 이전 알파벳 (A에서 아래쪽으로 이동하면 Z로)
◀ - 커서를 왼쪽으로 이동 (첫 번째 위치에서 왼쪽으로 이동하면 마지막 문자에 커서)
▶ - 커서를 오른쪽으로 이동 (마지막 위치에서 오른쪽으로 이동하면 첫 번째 문자에 커서)</p>
</blockquote>
<p>예를 들어 아래의 방법으로 &quot;JAZ&quot;를 만들 수 있습니다.</p>
<blockquote>
<ul>
<li>첫 번째 위치에서 조이스틱을 위로 9번 조작하여 J를 완성합니다.</li>
</ul>
</blockquote>
<ul>
<li>조이스틱을 왼쪽으로 1번 조작하여 커서를 마지막 문자 위치로 이동시킵니다.</li>
<li>마지막 위치에서 조이스틱을 아래로 1번 조작하여 Z를 완성합니다.
따라서 11번 이동시켜 &quot;JAZ&quot;를 만들 수 있고, 이때가 최소 이동입니다.</li>
</ul>
<p>만들고자 하는 이름 name이 매개변수로 주어질 때, 이름에 대해 조이스틱 조작 횟수의 최솟값을 return 하도록 solution 함수를 만드세요.</p>
<p><strong>제한사항</strong></p>
<ul>
<li>name은 알파벳 대문자로만 이루어져 있습니다.</li>
<li>name의 길이는 1 이상 20 이하입니다.</li>
</ul>
<p>입출력 예</p>
<table>
<thead>
<tr>
<th>name</th>
<th>return</th>
</tr>
</thead>
<tbody><tr>
<td>&quot;JEROEN&quot;</td>
<td>56</td>
</tr>
<tr>
<td>&quot;JAN&quot;</td>
<td>23</td>
</tr>
</tbody></table>
<br />

<hr>
<br />

<h3 id="문제-해석">문제 해석</h3>
<p>우리는 처음에 A로만 이루어져있던 문자를 인자로 들어온 알파벳 이름으로 바꿔주는게 목표이다.
이름 길이 만큼 A로 이루어져 있는데, 예를 들면 이름을 JAZ으로 바꿔야 할 때 처음에 AAA로 이루어진 문자열이 표시되고 이를 이름으로 바꿔줘야 한다.</p>
<p><img src="https://velog.velcdn.com/images/a_in/post/bfc07ab5-0e04-4f43-9fb4-6164ed4aec1c/image.png" alt=""></p>
<p>🕹️ -&gt; 이 조이스틱으로 커서를 조작해야 하는데 조작횟수를 최소한으로 하여 이름을 만들어야 한다.
이름을 만드는데 필요한 조작은 두가지이다.</p>
<ol>
<li><p>알파벳을 바꿔줄 때 조이스틱을 위로 움직여 알파벳 순으로 움직이거나, 아래로 움직여 알파벳 반대순으로 움직인다.
<img src="https://velog.velcdn.com/images/a_in/post/95182ce0-010e-42bd-b0ee-bdd2d59717be/image.png" alt=""></p>
</li>
<li><p>조이스틱을 오른쪽으로 움직여 다음 알파벳으로 넘어가고, 왼쪽으로 움직여 마지막 알파벳으로 이동한다.
예를 들어, 우리가 바꿔줘야할 이름이 <code>JAZ</code>이라면,</p>
<ul>
<li><code>A</code>에서 <code>J</code>까지 아홉 번 조작(BCDEFGHIJ)</li>
<li>왼쪽으로 조작하여 마지막 알파벳으로 이동할 때 한 번 조작</li>
<li>마지막 알파벳을 <code>Z</code>으로 바꿔줄 때 <code>A</code>에서 <code>Z</code>로 알파벳 반대순으로 이동하여 한 번 조작
총 <strong>열한 번</strong> 조작이 된다.</li>
</ul>
</li>
</ol>
<p>여기서 두 번째 <code>A</code>로 이동하지 않고 마지막으로 이동한 이유는 <code>A</code>는 알파벳 조작을 해줄 필요가 없기 때문에, <code>A</code>를 마주쳤을때 <code>A</code>를 통과하거나 뒤로 돌아갈 수 있는 선택지가 주어진다.</p>
<p>만약, 첫 번째 알파벳에서 두 번째 알파벳인 <code>A</code>를 통과하여 세번째로 간다면, 이동하는데 두 번의 조작이 필요하지만 첫 번째 알파벳에서 <code>A</code>를 거치지 않고 바로 마지막 알파벳으로 돌아간다면 한 번의 조작만 필요하다.</p>
<p>그림으로 설명해보자면 이런식이다.
조이스틱의 움직임을 화살표로 표시했고, 파란 박스는 cursor(커서), 초록색 <code>+{숫자}</code>로 조작 횟수를 나타내보았다. ↓
<img src="https://velog.velcdn.com/images/a_in/post/2fbd8558-4e07-42f2-b105-c63908017998/image.png" alt="JAZ 그림으로 설명">
<span style="color: gray">그림 출처: 본인</span></p>
<h3 id="접근-시도">접근 시도</h3>
<p>접근을 해보긴 해봤는데...</p>
<pre><code>  // 첫번째 문자부터 차례대로 조작한다.
  // 첫번째 문자가 A인 경우 반대편으로 간다.
  // 오른쪽으로 가는게 최선인 경우는 현재 커서가 문자열의 앞부분에 있을 경우.(i &lt; Math.floor(name.length / 2))
  // 왼쪽으로 가는게 최선인 경우는 현재 커서가 문자열의 뒷부분에 있을 경우. i &gt;= Math.floor(name.length / 2)
  // 26개 알파벳중에 13(78N) 이전 알파벳이면 위로 가고 이후 알파벳이면 아래로 간다.</code></pre><p>나중에 정답을 보니 접근을 잘못 한 듯 하다.</p>
<br />

<hr>
<br />

<h3 id="풀이">풀이</h3>
<pre><code class="language-js">function solution(name) {
  var answer = 0;
  let min = name.length - 1;

  for (let i = 0; i &lt; name.length; i++) {
    let currentAlphabet = name.charCodeAt(i);

    if (currentAlPhabet &lt; 78) {
      answer += currentAlphabet % 65;
    } else {
      answer += 91 - currentAlphabet;
    }

    let nextIndex = i + 1;

    while (nextIndex &lt; name.length &amp;&amp; name.charCodeAt(nextIndex) === 65) {
      nextIndex += 1;
    }
    min = Math.min(
      min,
      i * 2 + name.length - nextIndex, // 먼저 오른쪽으로 가기
      i + (name.length - nextIndex) * 2 // 처음부터 반대로 가기
    );
  }
  answer += min;
  return answer;
}</code></pre>
<br />

<hr>
<br />

<h3 id="실제-접근-방법-풀이-설명">실제 접근 방법 (풀이 설명)</h3>
<ol>
<li>최종 목표는 <code>알파벳 이동 횟수 + 커서 이동 횟수</code>를 누적하여 반환하는 것이다.</li>
<li>커서는 첫번째 문자부터 시작한다.</li>
<li>문자열에 <code>A</code>가 없다면 <code>문자열 길이 - 1</code>가 최소 횟수이다. (첫번째 문자에 커서가 있는 상태이기 때문에 마이너스 1을 해야 함)<pre><code class="language-js">let min = name.length -1</code></pre>
</li>
<li>따라서, 문자열에 <code>A</code>가 포함되어있을 때는 커서가 <code>A</code>를 거치는 식이 빠를지, 오른쪽으로 조작하여 <code>A</code> 직전까지 갔다가 돌아가는게 빠를지, 아예 처음부터 왼쪽으로(뒤로) 갔다가 앞으로 돌아오는게 빠를지 비교를 해야한다.</li>
<li><strong>알파벳</strong>을 조작할 때에는 간단하다.
26개의 알파벳중 정 가운데에 있는 13번째 알파벳인 <code>N</code>을 기준으로, <code>N</code>보다 작으면 (<code>A~M</code>) 알파벳순으로 조작하는게 빠르고, <code>N</code>보다 크거나 같으면 (<code>N~Z</code>) 알파벳 반대순으로 조작하는게 빠르다.<pre><code class="language-js">for (let i = 0; i &lt; name.length; i++) {
let currentAlPhabet = name.charCodeAt(i);
// 반복문을 돌면서 현재 커서가 위치한 알파벳이 N보다 작으면 [커서가위치한알파벳]과 [A]사이를 얼만큼 이동해야 하는지를 계산해준다.
if (currentAlPhabet &lt; 78) { // 78은 N의 아스키코드
 // 나눈 후 나머지로 계산해줘도 되고, else문안의 코드처럼 빼기를 해주어도 된다.
 answer += currentAlPhabet % 65; // 65는 A의 아스키 코드
} else {
 // 현재 커서가 위치한 알파벳이 N보다 크면 [Z]와 [커서가위치한알파벳]사이를 얼만큼 이동해야 하는지를 계산해준다.
 answer += 91 - currentAlPhabet; // 91은 Z의 아스키 코드
}
}</code></pre>
</li>
<li>알파벳 조작을 할때에는 N을 중심으로 하였듯, <strong>커서</strong>를 이동할 때에는 <strong>A 친구들</strong>을 기준으로 앞의 문자들에서 커서를 조작할 횟수가 더 많은지, 마지막 문자로 이동하여 조작할 횟수가 더 많은지 봐야한다.
그 전에, 현재 커서가 위치한 알파벳 다음으로 <code>A</code> 친구가 오는지 확인하고, A 친구의 바로 뒤의 오는 인덱스를 알아낸다.<pre><code class="language-js">let nextIndex = i + 1; // A의 뒤 인덱스를 찾을 nextIndex
</code></pre>
</li>
</ol>
<p>// 현재 커서가 위치한 알파벳의 다음 알파벳이 A인지 확인한다. A면 nextIndex에 1을 더해준다.
// 만약 JAZ라면 0번째 인덱스인 J 뒤에 A가 하나 있기 때문에 nextIndex는 2가 되어있을테고 2번째 인덱스는 Z이다.
// 이 nextIndex는 나중에 [문자열길이] - [nextIndex]를 하여 마지막 A 뒤에 오는 알파벳 수를 알아낼 수 있다.
while (nextIndex &lt; name.length &amp;&amp; name.charCodeAt(nextIndex) === 65) {
  nextIndex += 1;
}</p>
<pre><code>
이제 최소로 조작하는 횟수를 찾아내야하는데, \[처음에 `A`를 거쳐서 쭉 이동했던 횟수\]와 \[ 오른쪽으로 이동했을 때의 횟수\], \[처음부터 반대로 갔을 때의 횟수\] 이 세가지를 **비교**한다.
```js
min = Math.min(
  min, // 그냥 오른쪽으로 쭉 가는 횟수
  i * 2 + name.length - nextIndex, // 먼저 오른쪽으로 가기
  i + (name.length - nextIndex) * 2 // 처음부터 반대로 가기
);</code></pre><p>이게 대체 뭔 소리냐하면!</p>
<p>예시를 들어보겠다.</p>
<br />

<hr>
<br />


<h3 id="비교-예시">비교 예시</h3>
<p><img src="blob:https://velog.io/65ac2de2-57ed-4592-a197-78deaa1a64aa" alt="A 친구들">
이 사진에서 <code>MONAAAJOE</code> 비교를 해보자. A 친구들 세명이 있다고 치고 우선 이들을 투명인간 취급해준다.</p>
<p><img src="https://velog.velcdn.com/images/a_in/post/28ab40b8-e3d7-4071-992d-8fbc3a56cd34/image.jpeg" alt=""></p>
<ul>
<li><code>M</code>에서 <code>A</code> 직전까지 가는데 <strong>두 번</strong>, 뒤로 돌아가기 위해서 다시 M까지 가는데 <strong>두 번</strong>, 뒤로 돌아가서 <code>A</code> 직전까지 가는데(<code>JOE</code>) <strong>세 번</strong>의 조작이 필요하다. 총 <strong>일곱 번</strong>의 조작이 필요하다.</li>
</ul>
<hr>
<p><img src="https://velog.velcdn.com/images/a_in/post/fdf51e4b-079b-45b7-8fe0-4466cc9776f5/image.jpeg" alt=""></p>
<ul>
<li><p>반대로, <code>M</code>에서 바로 마지막 문자로 이동하여 세번째 <code>A</code>의 직전인 <code>J</code>로 가보자. 그러면 <strong>세 번</strong>의 조작이 필요하다. 다시 <code>MON</code>을 세기 위해서 처음 문자인 <code>M</code>으로 돌아오는데만 <strong>세 번</strong>의 조작이 필요하다. 그리고 다시 첫번째 <code>A</code>직전까지 세면 <strong>두 번</strong>의 조작이 필요하여 총 <strong>여덟번</strong>의 조작이 필요하다.</p>
</li>
<li><p>마지막으로, <code>A</code>를 거쳐서 오른쪽으로 쭉 커서를 조작하면 여덟번의 조작이 필요하다. 그래서 결론은 <code>MONAAAJOE</code>는 앞에서 먼저 돌고, 마지막 문자열로 가야 최소한의 횟수로 조작할 수 있다.</p>
<pre><code>M -&gt; O -&gt; N -&gt; O -&gt; M -&gt; E -&gt; O -&gt; J 가 커서 조작 횟수가 적다!</code></pre><p>여기까지 비교하는 예시였다.</p>
</li>
</ul>
<br />

<hr>
<br />


<h3 id="이걸-어떻게-연산-할-것인가">이걸 어떻게 연산 할 것인가?</h3>
<p><strong>오른쪽으로 먼저 커서를 조작했을 때</strong></p>
<ul>
<li>우선, A 친구들 왼쪽에 있는 <code>MON</code>에서는 <code>M</code>에서 <code>N</code>으로, 다시 <code>N</code>에서 <code>M</code>으로 돌아오는데까지 4번의 조작이 필요하다.</li>
<li>첫번째 <code>A</code>의 직전 알파벳인 <code>N</code>의 인덱스는 2이다. 이 인덱스 곱하기 2를 하면 <code>A</code> 직전까지 갔다가 다시 돌아오는 횟수를 구할 수 있다. (<code>왔다 -&gt; 한번, 갔다 -&gt; 두번</code> 그래서 곱하기 2)</li>
<li>앞에서 가져온 횟수 더하기 <code>A</code> 친구들 뒤에있는 알파벳의 수를 하면 결과가 나온다.<pre><code class="language-js">i * 2 + name.length - nextIndex
// `A` 앞의 알파벳 수 =&gt; i
// 왔다갔다 해야하는 횟수 =&gt; 2
// `A` 뒤의 알파벳 수 =&gt; name.length - nextIndex</code></pre>
</li>
</ul>
<p><strong>처음부터 거꾸로 이동하여 조작했을 때</strong></p>
<ul>
<li>첫번째 알파벳부터 시작해서 문자열의 맨 마지막으로 이동한 후, 마지막 <code>A</code>뒤에 있는 알파벳까지 돌고, 다시 문자열의 첫번째 알파벳으로 돌아오는 횟수는:</li>
<li>마지막 <code>A</code> 뒤에 있는 알파벳의 수에 2를 곱하여 왔다갔다 하는 수를 구할 수 있다.</li>
<li><code>A</code> 친구들 뒤에 있는 알파벳에 다녀오는 수는 구했으니 <code>A</code> 친구들 앞에 있는 알파벳을 더해주면 결과가 나온다.<pre><code class="language-js">i + (name.length - nextIndex) * 2
// `A` 앞의 알파벳 수 =&gt; i
// `A` 뒤의 알파벳 수 =&gt; name.length - nextIndex
// 왔다갔다 해야하는 횟수 =&gt; 2</code></pre>
</li>
</ul>
<p>이상으로, 프로그래머스 자그마치 레벨 2의 Greedy 탐욕법 문제, <strong>조이스틱</strong>의 (내 나름대로) 해석이였다...</p>
<br />

<hr>
<br />

<p><strong>코드 + 주석</strong></p>
<pre><code class="language-js">function solution(name) {
  var answer = 0;
  // 뒤로 돌아가지 않고 조작했을 때의 최소 횟수는 [문자 length - 1]
  // 처음 알파벳부터 다음 알파벳으로 넘어가는 조작 횟수부터 시작하니 length - 1
  // 예를 들어 길이가 3인 문자면 1-&gt;2로 이동하는 할 때 +1, 2-&gt;3으로 이동할때 +1이기 떄문에 length - 1을 해줘야 함.
  let min = name.length - 1;

  for (let i = 0; i &lt; name.length; i++) {
    let currentAlPhabet = name.charCodeAt(i);

    // 현재알파벳이 N(26개 알파벳에서 13번째 알파벳)보다 작으면(A~M) 뒤로 돌아갈때가 앞으로 가는것보다 빠름.
    // 이동하는 만큼의 조작 횟수를 answer에 저장. (여기서 마이너스를 해도 되고 나눈 후의 나머지로(%) 계산 해도 된다.)
    if (currentAlPhabet &lt; 78) {
      answer += currentAlPhabet % 65;
    } else {
      // N보다 크거나 같으면(N~Z)
      // A부터 시작해서 조작해야하는데 뒤로 돌아가기 때문에 A를 제외하고 Z부터 세기 때문에 Z - 현재알파벳을 계산.
      answer += 91 - currentAlPhabet;
    }

    // i의 다음 인덱스가 A이면 하나의 혹은 연속된 A 다음에 오는 알파벳의 인덱스를 가리킨다.
    let nextIndex = i + 1;

    // 현재알파벳이 마지막 알파벳이 될 때까지 &amp;&amp; 다음알파벳으로 A가 나올때까지 nextIndex += 1
    // nextIndex가 A가 아니면 넘어가고, nextIndex에 A가 나온다면 nextIndex += 1을 하여 A의 다음 인덱스도 A인지 확인한다.
    // -&gt; 다음 문자열들에서 A를 찾는 작업
    while (nextIndex &lt; name.length &amp;&amp; name.charCodeAt(nextIndex) === 65) {
      nextIndex += 1;
    }

    // length - nextIndex는 뒤로 쭉 갔을 때의 길이(A를 통과해서 갔을 때).
    min = Math.min(
      min,
      i * 2 + name.length - nextIndex, // 먼저 오른쪽으로 가기
      // 이 경우는 A의 앞에 있는 알파벳들이 뒤에 있는 알파벳의 수보다 적을 경우 최소가 된다.
      // 앞에서 갔다가 뒤돌아오는 횟수 (A 뭉떵이를 기준으로 앞에 알파벳 &lt; 뒤에 알파벳일 때 앞에서 A 직전까지 갔다가 다시 돌아오기 때문에 곱하기 2를 해준다.)
      // 예를 들어 CDAAJJJJ이면 JJJJ보다 CD가 짧기 때문에 D로 갔다가 다시 뒤돌아서 마지막인 J로 가야한다.
      // C에서 D까지 조작해서 +1, 다시 D에서 C로 되돌아갈 때 + 1, 따라서 i * 2를 해줘야 한다.
      // 여기서 [문자열의 길이]에서 [마지막에 위치한 A의 바로 뒤에 있는 문자의 인덱스]를 빼주면 A 뒤에 있는 알파벳들의 길이를 구할 수 있다. 예시에서는 JJJ이기 때문에 3.
      i + (name.length - nextIndex) * 2 // 처음부터 반대로 가기
      // 이 경우는 A의 앞에 있는 알파벳들보다 뒤에 있는 알파벳들의 수가 적을 경우 최소가된다.
      // 예를 들어, JJJJAACD 일 때, A의 앞에 문자열이 뒤보다 많기 때문에
      // 네번째 J까지 갔다가 다시 앞으로 돌아가는것(8회)보다 처음부터 CD를 돌고 다시 JJJJ로 돌아가는게 횟수가 적다(7회)
    );
  }
  answer += min;
  return answer;
}</code></pre>
<h3 id="참고">참고</h3>
<ul>
<li><p><a href="https://school.programmers.co.kr/learn/courses/30/lessons/42860">코딩테스트 연습 - 조이스틱 | 프로그래머스</a></p>
</li>
<li><p><a href="https://bbeeyaks-moment.tistory.com/entry/%EA%B7%B8%EB%A6%AC%EB%94%94-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EC%A1%B0%EC%9D%B4%EC%8A%A4%ED%8B%B1-js">bbeyak - [그리디] 프로그래머스 &#39;조이스틱&#39; - js</a></p>
</li>
<li><p><a href="https://ghost4551.tistory.com/113">rio(리우) - [프로그래머스 JavaScript] 조이스틱</a></p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Figma] 재사용 컴포넌트를 만들어보자! (Button 편)]]></title>
            <link>https://velog.io/@a_in/Figma-making-reusable-Button-design-component</link>
            <guid>https://velog.io/@a_in/Figma-making-reusable-Button-design-component</guid>
            <pubDate>Mon, 29 May 2023 22:04:36 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>본 게시글은 <a href="https://www.youtube.com/watch?v=WOQ48AL1_Rk&amp;list=PLfrKiHMMe1cgRRLzX0NImqw-7ZOacocN8&amp;index=11">연정&#39;s Figma</a>님의 Youtube를 참고하여 작성한 글입니다.</p>
</blockquote>
<p>원티드에서 주관하는 컴포넌트와 스토리북에 대한 챌린지 사전과제를 수행하기 위해 Figma 사용법에 대해 공부해보았다. Figma를 사용해보긴 했지만 제대로 알지 못하고 써봤기 때문에 디자인 시안을 제대로 만들지 못할 것 같아 간단한 강의를 보았는데...지금까지 1%도 제대로 못 쓰고 있던 것이다. 기억해놓으면 아주 유용하게 사용할 수 있을 것 같아 정리해보려고 한다. 기록해놓지 않으면 분명 까먹어버릴것...</p>
<h2 id="1-material-icons-가져오기">1. Material Icons 가져오기</h2>
<p><strong>Figma</strong>에서는 아이콘을 바로 사용할 수 있는 것이 아니라 외부에서 가져와야 한다. Google Fonts에서 <strong>Material Icons</strong>를 가져와 사용할 수 있다.</p>
<ul>
<li><p>&quot;Google Fonts Material Icons&quot;를 검색하여 <a href="https://fonts.google.com/icons?icon.style=Filled&amp;icon.set=Material+Icons">Material Symbols and Icons - Google Fonts</a>로 이동한다.</p>
</li>
<li><p>1번 드롭다운에서 <strong>Meterial Icons</strong>를 선택한다.</p>
</li>
<li><p>2번 <strong>Figma file</strong>버튼을 클릭하여 피그마에서 아이콘 파일을 열 수 있도록 한다.↓
<img src="https://velog.velcdn.com/images/a_in/post/a37327e3-95ab-4747-83f5-1fa5fcb2eda6/image.png" alt="Google Fonts 아이콘"></p>
</li>
<li><p>파란색 버튼 <strong>Open in Figma</strong>(또는 Duplicate)을 클릭하여 Figma로 이동한다.↓
<img src="https://velog.velcdn.com/images/a_in/post/8345a296-9738-4510-9487-7e549f7eff15/image.png" alt="Figma file 버튼 클릭 시 이동하는 페이지"></p>
</li>
<li><p>Figma app에서 열면 Drafts에 Icons에 관한 페이지가 들어온다.↓
<img src="https://velog.velcdn.com/images/a_in/post/4fdd3260-d90e-4ac8-87fa-84aa0952ddbd/image.png" alt="Material Design Page"></p>
</li>
<li><p>그러면 왼쪽 사이드바에서 <strong>Admin only</strong>를 선택하여 아이콘 파일을 연다.</p>
</li>
<li><p>그리고 아이콘 파일들을 전체선택하여 복사해준다.(<code>cmd</code> + <code>c</code>)↓
<img src="https://velog.velcdn.com/images/a_in/post/4d795808-4899-4ec9-bef6-de33b237ce71/image.png" alt="Admin only Page"></p>
</li>
<li><p>작업을 진행하고 있는 draft로 이동하여 Icon 전용 page를 생성한 다음 해당 페이지에 붙여넣기 한다.(<code>cmd</code> + <code>v</code>)↓
<img src="https://velog.velcdn.com/images/a_in/post/ba61ee29-3fde-4a19-a609-13ce4262ecb4/image.png" alt="Icon page에 붙여넣기"></p>
</li>
</ul>
<hr>
<h2 id="2-layout-잡기">2. Layout 잡기</h2>
<p>html로 버튼을 만들자면 <strong>container</strong> &gt; <strong>icon</strong> &gt; <strong>text</strong> 순으로 버튼을 만들 것이다. 하지만 디자인에서 레이아웃을 잡을 때는 다르게 생각해야 한다. icon과 text를 먼저 만들고, 두 요소를 그룹핑 해줘야 한다.</p>
<h3 id="아이콘-가져오기">아이콘 가져오기</h3>
<ul>
<li>단축키 <code>shift</code> + <code>i</code>로 Resources 창을 열어준다.</li>
<li><strong>components</strong> 탭에서 사용하고 싶은 아이콘을 검색하여 가져온다.</li>
</ul>
<h3 id="텍스트와-아이콘-정렬">텍스트와 아이콘 정렬</h3>
<ul>
<li>단축키 <code>T</code> 를 눌러 텍스트를 입력해준다. 이 텍스트는 버튼의 라벨(Label)이 되겠다.</li>
<li>아이콘과 라벨를 한꺼번에 드래그하고 단축키 <code>shift</code> + <code>A</code>를 이용해 컨테이너로 감싸는 것과 동시에 레이아웃을 잡아준다.</li>
<li>오른쪽 Design 패널의 <strong>Auto Layout</strong> 탭에서 중앙정렬해준다.</li>
<li>아이콘과 라벨 사이의 여백(<strong>Horizontal space between items</strong>)을 넣어준다.</li>
<li><strong>Horizontal padding</strong>과 <strong>Vertical padding</strong>을 넣어준다.
<img src="https://velog.velcdn.com/images/a_in/post/33065692-b28b-420f-97b4-b21dab7ad491/image.png" alt="중앙 정렬"></li>
<li>텍스트 <strong>Line Height</strong>을 100%로 주면 폰트 사이즈와 폰트의 높이 수치가 동일해진다.</li>
<li>텍스트 <strong>Align Middle</strong>으로 설정하면 컨테이너의 높이가 변경되어도 텍스트는 항상 중앙정렬된다.</li>
<li>텍스트 <strong>Auto Width</strong>으로 설정하면 줄바꿈 되지 않도록 자동으로 너비가 설정된다.
<img src="https://velog.velcdn.com/images/a_in/post/5df7653d-427e-4b80-8541-23374712be4a/image.png" alt="Text 설정"></li>
</ul>
<p><strong>❕버튼에 관한 Tip</strong></p>
<ul>
<li><code>shift</code> + <code>option</code>을 누른 상태에서 마우스로 패딩을 늘려주면 좌우상하 패딩이 일정하게 늘어난다.<ul>
<li><code>option</code>만 누른 상태에서 마우스로 패딩을 늘려주면 좌우 또는 상하 패딩이 일정하게 늘어난다.</li>
</ul>
</li>
<li><strong>Corner Radius</strong>는 999를 주어 최대한 둥글게 만들 수 있다.</li>
</ul>
<h3 id="아이콘-레이아웃-설정">아이콘 레이아웃 설정</h3>
<ul>
<li><strong>Constrain Proportions</strong>을 활성화한 상태에서 아이콘의 너비를 조정하면 width만 바꿔도 height도 비율에 맞게 조정된다. 활성화 하지 않는다면 <strong>width</strong>와 <strong>height</strong>를 각각 설정해 줄 수 있다.
<img src="https://velog.velcdn.com/images/a_in/post/62d45b5f-3063-41de-9451-6a4e3b931317/image.png" alt="Constrain Proportions"></li>
</ul>
<h3 id="아이콘과-라벨의-높이-맞추기">아이콘과 라벨의 높이 맞추기</h3>
<ul>
<li>아이콘과 라벨의 높이가 다르면 아이콘이 토글 될 때 버튼의 높이가 달라질 수 있다.</li>
<li>이 두 요소 사이에 <strong>Frame</strong>을 넣어 높이를 맞추는 치트키를 사용하여 아이콘과 라벨의 높이를 맞출 수 있다. 이 막대기는 높이가 더 높은 요소의 높이로 맞춰주면 된다.
<img src="https://velog.velcdn.com/images/a_in/post/0d69c222-1358-4cd2-8474-f2acb466dbc9/image.png" alt="Base"></li>
<li>그 다음으로, 이 투명 막대기와 라벨을 <code>shift</code> + <code>A</code> 단축키를 이용해 <strong>Auto layout</strong>을 잡은 다음 막대기와 라벨 사이에 있던 여백을 0으로 없애준다. 그러면 아이콘과 라벨의 높이가 같아진 것을 볼 수 있다.
<img src="https://velog.velcdn.com/images/a_in/post/d244e6bb-439d-4ef9-9abe-f8c1cd6cb9ae/image.png" alt=""></li>
</ul>
<h3 id="layers에-이름-붙여주기">Layers에 이름 붙여주기</h3>
<ul>
<li>Layer에 이름을 붙여준다. 아이콘에는 Icon, 텍스트에는 Label, 막대기에는 Base로 붙여준다.
<img src="https://velog.velcdn.com/images/a_in/post/8c4d2ff9-c356-48f2-a5ce-975d27cbe602/image.png" alt="Layer에 이름 붙여주기"></li>
</ul>
<hr>
<h2 id="3-컴포넌트-만들기">3. 컴포넌트 만들기</h2>
<p>이번 글의 주인공 <strong>컴포넌트</strong>를 만들어보겠다. 디자인에서 컴포넌트도 개발과 마찬가지로 재사용할 수 있도록 생성한다. 색상이나 스타일이 다른 컴포넌트를 만들어 놓으면 클릭 한번만으로 상황에 따른 버튼을 가져올 수 있다. (진작에 이 강의 볼걸그랬다)</p>
<p>상황에 따라서 필요한 버튼 디자인들을 각각 만든 다음, 컴포넌트로 만든다. 그리고 필요한 곳에서 인스턴스를 만든 다음 상황에 맞는 색상과 스타일로 커스텀 한 다음 사용한다. 마치 class를 instance로 만들어서 제각각에서 사용할 수 있는 것처럼.</p>
<h3 id="컴포넌트-만들기">컴포넌트 만들기</h3>
<ul>
<li>Figma 상단 바에 <strong>Create component</strong> 버튼을 클릭하거나 단축키 <code>cmd</code> + <code>option</code> + <code>k</code> 를 눌러 버튼을 <strong>컴포넌트로</strong> 만든다.</li>
</ul>
<p>이렇게 한번 컴포넌트를 만들면 왼쪽 <strong>Assets</strong>패널에 Button 컴포넌트를 드래그 앤 드롭하여 인스턴스로 가져올 수 있다.
<img src="https://velog.velcdn.com/images/a_in/post/04bf86b8-c8d1-4401-93b8-b1188798bc89/image.png" alt="Assets에 있는 버튼 컴포넌트"></p>
<h3 id="인스턴스">인스턴스</h3>
<ul>
<li><strong>메인 컴포넌트로 이동하기.</strong>
인스턴스 버튼을 포커스한 상태에서 Design 패널을 보면 <code>◇ Button ∨</code> 이렇게 생긴 버튼이 있을 것이다. 그 오른쪽에 다이아몬드 모양 버튼을 클릭하면 해당 인스턴스의 메인 컴포넌트로 이동하게 된다. 지도의 현재위치로 이동하기 같은 기능을 가진 버튼이다.</li>
<li><strong>인스턴스의 아이콘과 라벨 변경하기.</strong><ul>
<li>아이콘을 더블클릭하면 Design 패널에 아이콘을 변경할 수 있는 드롭다운이 생긴다. 변경하면 메인 컴포넌트의 아이콘은 그대로이고, 인스턴스만 바뀐 것을 볼 수 있다.</li>
<li>라벨을 더블클릭x2 하면 텍스트를 수정할 수 있다. 아이콘과 마찬가지로 메인 컴포넌트는 바뀌지 않고 인스턴스만 바뀐다. (라벨을 수정하는 것을 override라고 한다.)
<img src="https://velog.velcdn.com/images/a_in/post/c0f8805e-c624-40cd-8041-b0f913cc8803/image.png" alt="인스턴스 아이콘과 라벨 변경"></li>
<li>Design 패널의 컴포넌트 더보기를 클릭하여 <strong>Reset all changes</strong>로 모든 변경사항을 원래대로 되돌려놓을 수 있다.
<img src="https://velog.velcdn.com/images/a_in/post/e60f178f-a586-49b1-bcc7-c448275dff60/image.png" alt="Reset all changes"></li>
</ul>
</li>
</ul>
<hr>
<h2 id="4-variants">4. Variants</h2>
<p><strong>Variants</strong>는 상황에 따라 사용하기 위해 미리 다른 스타일들을 만들어 놓는데, 이 각각의 스타일을 가진 디자인을 variants라고 한다. 예를 들면 배경색이 꽉 차있는 버튼, 테두리 색상만 있는 버튼, 또는 아이콘만 있는 버튼 등 여러가지 스타일의 variants를 만든 다음, 인스턴스를 생성하여 상황에 맞게 variants를 골라 사용한다.</p>
<h3 id="variants-만들기">Variants 만들기</h3>
<ul>
<li>variant를 만들 컨테이너를 클릭한 다음, Figma 중앙상단에 <strong>add variant</strong> 버튼을 클릭하면 <strong>Component set</strong>이 형성되며 기존에 있던 컨테이너는 <strong>Default</strong>가 되고 나중에 추가된 컨테이너는 <strong>Variants</strong>가 된다.</li>
<li><strong>Variants</strong>를 다른 스타일로 바꿔준다.
<img src="https://velog.velcdn.com/images/a_in/post/a969dc59-f2fa-4b06-94c7-ee41addae2d7/image.png" alt="variants"></li>
<li>이제 인스턴스를 포커스 해보면 Design 패널에 Property 영역이 생기는데 여기서 스타일을 선택하면 된다.
<img src="https://velog.velcdn.com/images/a_in/post/fd4b8346-cc4e-4be5-9b37-ffa31ac59b63/image.png" alt="property 드롭다운"></li>
</ul>
<h3 id="컴포넌트-및-variants-이름-변경">컴포넌트 및 Variants 이름 변경</h3>
<ul>
<li><strong>컴포넌트 이름 변경.</strong>
메인 컴포넌트를 클릭하여 Current variant 영역에 property라고 되어있던 이름을 Style로 설정해준다. (스타일에 관한 variant이기 때문이다.)
<img src="https://velog.velcdn.com/images/a_in/post/ec503cad-bc89-4d0e-b762-c0a8e107e497/image.png" alt="Current variant name"></li>
<li><strong>variants 이름 변경.</strong>
Default, variant로 해주기 보다는 색상이 꽉차있는 <strong>filled</strong>와 아웃라인에 색상이 있는 <strong>outlined</strong>로 직관적이게 이름을 변경해준다.
<img src="https://velog.velcdn.com/images/a_in/post/edb5b6d9-5302-4abc-8709-49d38c03ff3d/image.png" alt=""></li>
</ul>
<hr>
<p>여기까지 컴포넌트, variants를 설정해주는 방법을 정리해보았다. Property에서 설정할 수 있는 Variants, Boolean, Instance Swap 등등 나머지 Property에 대해서는 다음 편에서 정리해보겠다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Next.js: Local Font를 tailwindCSS와 같이 사용하면 발생하는 이슈]]></title>
            <link>https://velog.io/@a_in/Next.js-Local-Fonts-nextfont-with-tailwind</link>
            <guid>https://velog.io/@a_in/Next.js-Local-Fonts-nextfont-with-tailwind</guid>
            <pubDate>Sun, 28 May 2023 17:44:16 GMT</pubDate>
            <description><![CDATA[<blockquote>
</blockquote>
<p>본 글은 Next.js 12버전을 기반으로 작성된 글입니다.</p>
<blockquote>
<p>+ <strong>2023.06.14 업데이트</strong>
Next.js 공식 디스코드 help-forum에 문의한 결과 Next.js contributor 중 한 분이 직접 코멘트를 달아주셨다.
우선 웹 페이지 endpoint에 <code>/font</code>가 들어갈 수 있어서 pages 폴더 안에 폰트 파일들을 넣기보다는 바깥으로 빼는 게 좋다고 한다.
그리고 내가 마주한 문제는 Next.js의 버그인 것 같다고 한다.
아직 정확한 답이 나오지 않은 상태여서 추후 다시 업데이트하겠다!</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/a_in/post/80eb45f5-7fc2-40e7-9466-e64a5f0f6f13/image.png" alt="Rafael Almeida&#39;s answer"></p>
<h1 id="nextjs의-폰트-최적화">Next.js의 폰트 최적화</h1>
<p>Next.js를 사용한다면 이미지 최적화와 마찬가지로 폰트 최적화도 빼먹을 수 없다.
폰트 최적화를 하려면 <code>next/font</code>를 다운로드 받아서 사용할 수 있는데, 여기서 지원해주는 기능은 구글 폰트(Google Fonts)와 로컬 폰트(Local Fonts)이다. <em>(참고로 Next.js 13버전은 <code>next/font</code>가 처음에 같이 설치 된다고 한다.)</em></p>
<p>본 글에서는 구글 폰트는 다루지 않고, <strong>로컬 폰트를 적용하기 전과 적용하면서 발생한 이슈에 대해</strong> 다뤄볼 예정이다.</p>
<h2 id="로컬-폰트-이전의-font-face">로컬 폰트 이전의 @font-face</h2>
<p>Next.js의 로컬 폰트를 사용하기 전에는 global.css에 <code>@font-face</code>를 만들어서 폰트를 cdn으로 .eot, .woff2, .woff, .ttf 파일 전부 가져와서 설정해주고 그 폰트를 <code>html</code>과 <code>body</code>, <code>font-family</code> 속성에 넣어주었다.
<img src="https://velog.velcdn.com/images/a_in/post/243a8afb-9dee-42d6-80c1-dbf194fded9e/image.png" alt=""></p>
<p>cdn으로 가져오다보니 페이지를 딱 열었을 때 기본 폰트였다가 새로 적용한 폰트를 적용할 때 깜빡임이 발생한다.(Flash of Unstyled Text). 물론 페이지 이동 시에는 그런 깜빡임이 없고 새로고침 할때만 보이지만 나에겐 너무 거슬렸기에 다른 방법을 찾아야 했다.</p>
<p><img src="https://velog.velcdn.com/images/a_in/post/8fe9a49e-cc0b-45c8-91b0-d47e7babbc8d/image.gif" alt="폰트 깜빡임 현상 gif"></p>
<p>그러다가 Next.js의 로컬 폰트 관련 공식 문서를 보고 바로 이 방법으로 채택하였다. </p>
<h2 id="nextfont-채택-이유">@next/font 채택 이유</h2>
<p>Next.js 공식 문서에 의하면:</p>
<blockquote>
</blockquote>
<ul>
<li><code>next/font</code> will automatically optimize your fonts (including custom fonts) and remove external network requests for improved privacy and performance.</li>
<li><code>next/font</code> includes built-in automatic self-hosting for any font file. This means you can optimally load web fonts with zero layout shift, thanks to the underlying CSS size-adjust property used.</li>
<li>This new font system also allows you to conveniently use all Google Fonts with performance and privacy in mind. CSS and font files are downloaded at build time and self-hosted with the rest of your static assets. No requests are sent to Google by the browser.
<a href="https://nextjs.org/docs/pages/building-your-application/optimizing/fonts">Next.js - Font Optimization</a></li>
</ul>
<p>요약해보자면, <code>next-font</code>를 사용하면 자동으로 최적화 해주고, 레이아웃 이동 없이 폰트를 적용할 수 있다고 한다. 만약 구글 폰트를 사용한다면 구글에 폰트를 받아오는 요청을 따로 하지 않고 CSS와 폰트 파일이 빌드타임에 같이 다운로드 된다고 한다.
빌드타임에 같이 받아오기 때문에 폰트가 다 적용된 다음에 요소들이 화면에 뿌려지면서 깜빡임 현상이 없어진다.</p>
<p>실제로 적용해보았을 때, 폰트가 다 로드되면 화면에 뿌려지기 때문에 FOUT 현상은 없어진다.</p>
<h2 id="문제의-다이나믹-라우팅">문제의 다이나믹 라우팅</h2>
<p><code>next/font</code>를 사용하여 FOUT현상을 해결하였다고 안심했는데, 며칠 후 다이나믹 라우팅 페이지로 이동 시에 로컬 폰트가 적용이 안되는 이슈를 발견하였다. 이번에는 FOUT 현상이 일어나는 것도 아니고 그냥 로컬 폰트가 적용이 안된다.</p>
<p><del>찾아보니, 다이나믹 라우팅 페이지로 이동 시 페이지가 다시 새로 로드 되기 때문에 폰트도 다시 받아온다는데 다시 받아올 때 폰트가 페이지보다 느리게 로드 되어서 기본폰트가 적용된다는 것이다.</del>
다이나믹 라우팅 페이지로 이동 시, 페이지가 새로 로드 되는건 아니고</p>
<p>이를 해결하기 위해서 몇가지를 시도해보았다.</p>
<h2 id="시도">시도</h2>
<ul>
<li>display: &#39;swap&#39; 적용 ✅</li>
<li>localFonts에 variables를 적용하여 main태그에 넣기</li>
<li>global.css font-family속성에 variable 적용</li>
<li>_document.tsx html에 적용</li>
</ul>
<h3 id="display에-swap-적용">display에 &#39;swap&#39; 적용</h3>
<pre><code class="language-js">import localFont from &#39;@next/font/local&#39;;

const myFont = localFont({
  src: [
    {
      path: &#39;./fonts/myFont.woff&#39;,
      weight: &#39;400&#39;,
    },
    {
      path: &#39;./fonts/myFont.woff&#39;,
      weight: &#39;600&#39;,
    },
    {
      path: &#39;./fonts/myFont.woff&#39;,
      weight: &#39;700&#39;,
    },
  ],
  display: &#39;swap&#39;,
});
</code></pre>
<p><a href="https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display">font-display</a>라는 css 속성이 있다. 이 속성은 폰트가 로드 되기 전후를 감지하여 로컬 폰트를 자동으로 로드 시키는 속성이다. 
속성값은 총 5개로 <code>block</code>, <code>swap</code>, <code>auto</code>, <code>fallback</code>, 그리고 <code>optional</code>이 있다. 이 5개 중 브라우저의 기본동작을 따르는 <code>auto</code>를 제외하고 <code>swap</code> 속성값이 그나마 기본폰트를 보여줬다가 로컬 폰트로 보여주는 속성이다. 나머지는 로컬 폰트가 로드되지 않으면 페이지 이동 시 깜빡임이 심하기 때문에 <code>swap</code>으로 적용하였다.</p>
<p>하지만, 다이나믹 라우팅 페이지에서 FOUT가 된다면 첫 화면에서 FOUT 현상이 발생하는 것과 거의 똑같은 상황이 아닌가?
라는 생각이 들어 FOUT 현상을 아예 없애버리기 위해서 갖은 시도를 해보았다.</p>
<h3 id="localfonts-variables-만들기">localFonts variables 만들기</h3>
<p><code>next/font</code>의 localFonts를 만들 때에는 객체안에 variables를 넣어 변수처럼 사용할 수 있다고 한다.</p>
<pre><code class="language-js">const myFont = localFont({
  ...
  variable: &#39;--font-myfont&#39;,
});
</code></pre>
<p>하지만 변수를 설정하는 것부터 적용이 안된다. <a href="https://nextjs.org/docs/pages/building-your-application/optimizing/fonts">Next.js의 공식문서</a>에 나와있는대로 적용해봤는데 안된다... 뭔가 단단히 꼬인듯하다. 이 부분은 나중에 작은 프로젝트에서 다시 시도해보려고 한다.</p>
<h3 id="globalcss-font-family에-variables-적용">global.css font-family에 variables 적용</h3>
<p>이 부분도 마찬가지로 variables가 적용이 안되기 때문에 시도 실패다. </p>
<h3 id="_document-파일에서-html에-폰트-적용">_document 파일에서 html에 폰트 적용</h3>
<p>만약 루트에 폰트를 적용해야 한다면, html에 바로 적용해야겠다는 생각이 들어 _document에 html에 className으로 적용해보았다.</p>
<pre><code class="language-ts">import { Html, Head, Main, NextScript } from &#39;next/document&#39;;
import Script from &#39;next/script&#39;;
import localFont from &#39;@next/font/local&#39;;

const myFont = localFont({
  src: [
    {
      path: &#39;./fonts/myFont.woff&#39;,
      weight: &#39;400&#39;,
    },
    {
      path: &#39;./fonts/myFont.woff&#39;,
      weight: &#39;600&#39;,
    },
    {
      path: &#39;./fonts/myFont.woff&#39;,
      weight: &#39;700&#39;,
    },
  ],
  display: &#39;swap&#39;,
});
export default function Document() {
  return (
    &lt;Html lang=&quot;ko&quot; className={`${myFont.className}`}&gt;
      &lt;Head&gt;
        &lt;Script&gt;&lt;/Script&gt;
      &lt;/Head&gt;
      &lt;body&gt;
        &lt;Main /&gt;
        &lt;NextScript /&gt;
      &lt;/body&gt;
    &lt;/Html&gt;
  );
}
</code></pre>
<p>하지만 _document 파일은 외부 리소스를 동적으로 로드하는 작업은 지원하지 않는다. 때문에 로컬 폰트를 로드하려고 하면 에러가 발생한다.</p>
<p><img src="https://velog.velcdn.com/images/a_in/post/6d719404-844b-41c1-9c93-6564ce416161/image.png" alt="_document 파일 에러 스크린샷"></p>
<h2 id="tailwindcss의-문제">tailwindCSS의 문제?</h2>
<p>tailwindCSS는 빌드타임에 css파일이 만들어지는 것이 아니라 동적으로 className에 스타일링 값을 집어넣는 것이기 때문에 다이나믹 라우팅 시 빌드 되지 않는것으로 판단된다. 그래서 @font-face를 global.css에 넣으면 다이나믹 라우팅 페이지로 이동할 때도 잘 적용이 되는데 css파일에서 폰트를 로드하는게 아닌 _app 파일에서 동적으로 className을 넣으니 발생하는 문제같다.</p>
<p>현재로써는 font-display 속성으로 초기 페이지에는 바로 적용되고, 다이나믹 라우팅 페이지에서는 기본 폰트가 적용됐다가 로컬폰트가 로드되면 적용하는 방식을 선택했지만, 썩 마음에 들지 않는다.</p>
<p>tailwindCSS의 jit모드로 시도해보고, 안되면 font-face로 다시 바꿔야 할 듯하다. next/font 쓰고 싶어서 고집부리다가 UX만 망칠 것 같다. 이래서 한가지 기술에 고집부리지 말라고 하나보다.ㅎㅎ!</p>
<p>FOUT 현상을 없애고 UX를 개선하기 위해 시도를 더 해볼 것이며, 이에 대한 과정은 2편에서 풀도록 하겠다.</p>
<h2 id="참고">참고</h2>
<ul>
<li><a href="https://nextjs.org/docs/pages/building-your-application/optimizing/fonts">Next.js Font Optimization</a></li>
<li><a href="https://www.youtube.com/watch?v=L8_98i_bMMA">Using Fonts in Next.js - Lee Robinson</a></li>
<li><a href="https://stackoverflow.com/questions/74607996/how-to-add-custom-local-fonts-to-a-nextjs-13-tailwind-project">How to add custom local fonts to a Nextjs 13 Tailwind project - stackoverflow</a></li>
<li><a href="https://mikebifulco.com/posts/custom-fonts-with-next-font-and-tailwind">Add custom fonts to Next.js sites with Tailwind using next.font - Mike Bifulco</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[23년 4월 회고 (with 원티드 프리온보딩 프론트엔드 인턴십)]]></title>
            <link>https://velog.io/@a_in/2023-april-review</link>
            <guid>https://velog.io/@a_in/2023-april-review</guid>
            <pubDate>Fri, 26 May 2023 18:10:50 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>본 게시글은 개인이 작성한 회고입니다.</p>
</blockquote>
<p>원티드에서는 달마다 여러가지 챌린지들이 올라옵니다. 그동안 계속 지나쳐오다가 4월에 프리온보딩 <strong>인턴십</strong>이라는 새로운 키워드로 올라온 것을 보고 관심이 생겨 시작하게 되었습니다. 현재 세션이 끝나 수료에 가까워지고 있습니다.</p>
<p>해당 활동은 처음 참여해본 개발 관련 외부 활동인데다가 짧은 한달이였지만 <strong>지난 6개월보다 배운 지식과 역량이 많다</strong>고 느껴 회고를 남겨보고자합니다.</p>
<h2 id="지원-동기">지원 동기</h2>
<p>본 인턴십에 지원하기 전을 잠시 회상해보면 인턴십 하기 참 잘했다는 생각이 듭니다.
개발 지식이 없는 상태에서 1년 가까이 생활코딩, 노마드 코더, 각종 유투브 강의 등으로 <code>HTML</code>과 <code>CSS</code>, <code>JavaScript</code> 공부를 하다가 <code>React</code>에 들어서면서 잠시 방황하는 시기를 가졌습니다. 인턴십 지원하기 전 3개월 정도부터 다시 마음을 잡고 <code>Next.js</code>와 <code>TypeScript</code>를 적용한 사이드 프로젝트를 하던 참이였습니다. </p>
<p>그동안 혼자서 공부하다보니 새로운 기술을 배우는게 느리다고 느껴졌고, 커뮤니티도 뭣도 아는게 없었던지라 에러나 궁금증은 구글링으로 해결했습니다. 
연관성이 있을진 모르겠지만 저는 외국어에 관심이 많아 배우고 싶은 언어가 많습니다<span style="color: gray">(프랑스어, 이탈리아어, 일본어, 러시아어 등등 세상에 있는 언어는 다 배우고 싶음).</span> 프로그래밍 언어도 언어라 그런지 외국어를 배우고 싶은 것처럼 새로운 기술을 많이 배우고 사용하고 싶은데 혼자서 하니 왠지 모르게 템포가 느린 느낌도 있었고, 다른 사람의 학습 방법이 궁금하기도 하여 커뮤니티나 동료학습이 굉장히 필요한 상태였습니다.</p>
<p>그러다가 4월 초에 깨톡 광고란에서 해당 인턴십을 처음으로 보게 되었습니다. 지원 시 사전과제도 함께 제출해야 됐기 때문에 커리큘럼도 기초가 쌓여있는 수강생이 들을 만한 수준이었습니다. 당시 사이드 프로젝트밖에 하는게 없었던 저에게는 온라인 세션이었던 점, 한달 코스였던 점, 그리고 비용이 들지 않았던 점이 굉장히 와닿았고, 인턴십 취지였던 <strong>동료 학습</strong>이 굉장히 좋은 경험이 될 것 같아 지원하게 되었습니다. 그리고 실제로, 예상했던것보다 <strong>10배</strong>는 더 좋은 경험이 되었습니다. 🤩</p>
<h2 id="교육-과정">교육 과정</h2>
<p>강의는 4주간 총 24시간 진행되었습니다. 주에 세션이 두번 있었으며 강사님의 열정으로 가끔은 3시간 짜리 강의가 4시간이 되기도 했습니다. 중요한 부분 놓치지 않고 자세하게 설명해주시고 여느곳에서도 들을 수 없었던 지식과 꿀팁들을 굉장히 세세하게, 이해하기 쉽게, 초심자에게 설명해주시는 것처럼 가르쳐주셔서 알찬 한달이 되었습니다.</p>
<p>과제는 총 세번 주어졌는데 세번 다 밤을 새서 과제를 수행했습니다. 허리가 폴더폰처럼 반으로 접혀질 듯 아팠지만 동기들과 같이 하다보니 재밌었고, 배우게 된 기술들도 많아 의미있는 시간이였습니다. 👵🏻</p>
<h2 id="느낀-점">느낀 점</h2>
<h3 id="협업">협업</h3>
<p>본 인턴십의 핵심 취지였던 <strong>동료학습</strong>이 이번 경험에서 꽤나 인상 깊었습니다. 저는 그다지 친화력이 좋지않다고 생각했고 (MBTI에 그렇게 취해있진 않지만) 극 I가 나올 정도로 내성적인 사람이었는데, 코로나로 인해 오랬동안 집구석에 틀어박혀있었어서 그런지 오히려 팀 활동에 적극적인 제 모습에 놀랐습니다. 아마 좋은 팀원분들을 만난 것도 있고, 집중해서 빠른시일 내에 결과물을 내야하는 상황이여서 그런 것일 수도 있지만, 어쨌든 본 인턴십을 통해서 저의 다른 모습을 볼 수 있었던 것이 이번 활동 최고의 아웃풋이였습니다.</p>
<p>개발공부를 처음 시작했을 때, 프론트엔드 개발자는 여러 사람과 협업을 해야한다는 말을 들었을 때가 기억나는데요, 그때는 커뮤니케이션에 대한 두려움이 있어서 협업이라는 것을 할 수 있을까 과하게 걱정을 했던 때가 생각이 납니다. 하지만 지금, 그런 생각을 했다는 것 자체를 완전히 까먹고 오히려 즐기고 있네요... 신기합니다.
처음으로 협업을 경험하면서 부족한 점이 많았지만, 팀 활동 경험이 많으신 분들을 만나 <em>&quot;어떻게 상대방에게 본인의 의견을 잘 전달해야 할지&quot;</em>, <em>&quot;내가 어떤 사람이 되어야 팀 분위기가 좋아질지&quot;</em>, <em>&quot;어떤 말투가 분위기를 흐리고, 어떤 말투를 써야 내 뜻이 잘 표현될지&quot;</em> 같은 생각을 하게 되면서 제 자신도 조금 더 돌아보게 되고, 성장하는 시간이 되었습니다. 결과적으로, 남의 좋은 습관은 배우고, 고쳐야 할 부분은 고치고, 잘못된 부분은 인정하고 똑같은 실수를 반복하지 않기 위해 더 책임감을 가지는 사람이 되어 개발자로서 큰 한 발자국 나아갔다고 생각합니다. <del>🎵암온더-넥스ㅌ-레vel🎶</del> 💃🏻</p>
<h3 id="기술적-역량">기술적 역량</h3>
<p>강의 세션때 강사님께서 굉장히 세세하게 설명해주신 덕분에 정말 많은 것을 배울 수 있었습니다. 세션도 세션이지만 저보다 더 지식이 많으신 팀원분들과 코드리뷰를 하며, 기술에 대해 사소한 의견도 마구마구 나누다 보면 언제부턴가 기술에 대해 깊게 고민하게 됩니다.
음...지금 생각해보면 강사님의 빅픽쳐였던것 같습니다. 왜냐하면 과제중 하나가 프레임워크를 쓰지 않고 프레임워크의 기능을 직접 만들어보는 거였으니 자연스럽게 깊게 고민하게 되는 것이 당연한것 같기도 합니다.</p>
<h3 id="나">나</h3>
<p>본 활동을 통해 다행히도 방황하던 시절을 완전히 끝내고 계속 달릴 수 있게 되었습니다. 이전에 규칙적으로 하고 있던 코딩테스트 연습 하루 한 문제 풀기, 블로그 일주일에 한 번 작성하기 등 조각나 있었던 규칙들을 다시 맞추어 끼우고 있습니다. 한달동안 하드스킬도, 소프트스킬도 많이 향상시켰지만 제일 많이 성장한 것은 제 자신이라고 봅니다. 예전에는 급급한 마음으로 &quot;이것도 해야되고 저것도 해야되는데 난 아직 요것밖에 못했는데 어떡하지&quot;라는 생각을 항상 달고 살았는데 지금은 안정이 되었습니다.</p>
<p>예전보다 나아진 점이 있다면 &quot;자신감&quot;인 것 같습니다. 예전에는 주변에서 잘한다 잘한다 해도 믿지 않고 제 자신에게 낮은 점수를 매겼습니다. 하지만 이번에 과제 수행 중에 Best Practice로 선정된 것을 계기로 팀 활동에 더 적극적으로 참여하게 되었고 그로 인해 제 자신이 이전보다 더 나아진 사람이라는 것을 느꼈습니다.</p>
<p>본 세션은 마무리 되었지만, 계속해서 나아가기 위해 꾸준히 외부 활동에 적극적으로 참여할 예정입니다. 🚀</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[데이터 가져오기 전까지 로딩 띄우는 useLoading 커스텀 훅 만들기 (TypeScript)]]></title>
            <link>https://velog.io/@a_in/Using-useLoading-Custom-Hook-TypeScript</link>
            <guid>https://velog.io/@a_in/Using-useLoading-Custom-Hook-TypeScript</guid>
            <pubDate>Sun, 21 May 2023 16:45:21 GMT</pubDate>
            <description><![CDATA[<p>데이터를 가져올 때 로딩을 띄우고 싶다면 React Hook의 useState를 활용해서 사용할 수 있다.</p>
<p>로딩 상태값을 만들어서 데이터를 가져오기전까지 로딩을 띄우고, 다 가져왔으면 로딩을 없애주는 로직을 useState 훅을 사용하여 많이 사용해 왔을 것이다.</p>
<pre><code class="language-ts">const [loading, setLoading] = useState(false);
const [data, setData] = useState([]);

useEffect(() =&gt; {
  setLoading(true);
  setError(undefined);
  fetch(`...`)
    .then((res) =&gt; res.json())
    .then((data) =&gt; {
      setData(data);
    })
    .catch((error) =&gt; {
      console.error(error);
    })
    .finally(() =&gt; setLoading(false));
}, [...]);</code></pre>
<p>하지만 가져와야 하는 데이터가 많다면 똑같은 로직을 여러개 만들어야 한다. 데이터를 인자로 전달하여 <code>isLoading</code>과 <code>data</code>를 반환하는 커스텀 훅을 만들어 보았다.</p>
<h2 id="⏳-isloading-만들기">⏳ isLoading 만들기</h2>
<pre><code class="language-ts">// ./src/hooks/useLoading.ts
const useLoading = () =&gt; {
 const [isLoading, setIsLoading] = useState(false);
}</code></pre>
<h3 id="1-useloading-네이밍">1. useLoading 네이밍</h3>
<p>커스텀 훅이니 네이밍을 할 때 앞에 <code>use</code>를 붙여서 hooks 폴더 안에 만들어준다.
그리고 최상단에 로딩을 띄울 상태값 <code>isLoading</code>을 false로 초기값을 설정하여 만들어준다.</p>
<h3 id="2-isloading">2. isLoading</h3>
<p>이 상태값은 데이터를 받아왔는지 여부에 따라서 true, false가 변경된다(값이 변경되는 부분은 아래에서 다룰 예정이다). isLoading이 true라면 로딩 아이콘을 랜더링 하며, false라면 받아온 데이터를 뿌려준다.</p>
<h2 id="📩-콜백함수-받아오기">📩 콜백함수 받아오기</h2>
<pre><code class="language-ts">// ./src/hooks/useLoading.ts
const useLoading = (action: (...args: ArgumentType) =&gt; Promise&lt;T&gt;) =&gt; {
 const [isLoading, setIsLoading] = useState(false);
}</code></pre>
<h3 id="1-콜백함수-action">1. 콜백함수 action</h3>
<p><code>action</code>은 인자로 받아오는 콜백함수로 데이터를 처리하는 함수를 받는다. 이 콜백함수는 데이터를 가져와서 상태 값에 저장한 함수를 말한다. 예를 들면 이런 함수다.</p>
<pre><code class="language-ts">const handleGetTodoList = async() =&gt; {
  const data = await getTodoList();
  setData(data || [])
}</code></pre>
<p>이렇게 데이터를 가져오고 처리하는 함수를 useLoading 커스텀 훅에 전달해준다. </p>
<h3 id="2-generic-type">2. Generic Type</h3>
<p>콜백함수 action의 반환 값은 사용하는 곳마다 다른 타입을 받아올 수 있도록 하기 위해서 Generic을 사용해준다.</p>
<pre><code class="language-ts">const [...] = useLoading&lt;void&gt;(...)</code></pre>
<p>이렇게 useLoading을 사용하는 곳에서 <code>void</code> 타입을 전달해주면 useLoading의 제너릭을 사용한 곳에 <code>void</code>가 들어간다고 볼 수 있다:</p>
<pre><code class="language-ts">const useLoading = (action: (...args: ArgumentType) =&gt; Promise&lt;void&gt;) =&gt; {
 const [isLoading, setIsLoading] = useState(false);
}</code></pre>
<h2 id="💭-데이터를-처리할-함수-만들기-handledata">💭 데이터를 처리할 함수 만들기: handleData</h2>
<p>데이터를 호출 하기 전후로 로딩을 처리할 <code>handleData</code> 함수를 만들어준다.</p>
<pre><code class="language-ts">const handleData = async (...args: ArgumentType) =&gt; {
    setIsLoading(true);
    return await action(...args).finally(() =&gt; setIsLoading(false));
  };</code></pre>
<h3 id="1-args">1. ...args</h3>
<p>handleData 함수안에서 인자로 받아온 콜백함수 action을 호출하기 때문에 (useLoading에서 받아온 인자 action의 모든 인자) args를 똑같이 전개연산자로 넣어준다. 그런 다음 콜백함수 action을 호출하는 부분에서 args를 전달해준다.</p>
<h3 id="2-setloading">2. setLoading</h3>
<p>useLoading 훅의 하이라이트이다. 로딩을 띄우고 데이터를 받아오는 일을 이 함수가 수행한다. 아래 단계를 거친다.</p>
<ul>
<li>false였던 값을 true로 바꿔줌으로써 데이터를 받아오기 전까지 로딩을 띄워준다.</li>
<li>콜백함수 action을 호출하여 데이터를 받아온다.</li>
<li>마지막으로, 데이터를 받아오는 action이 잘 수행이 됐다면 로딩 대신 데이터를 띄워주도록 <code>isLoading</code>을 false로 바꾸어준다.</li>
</ul>
<h2 id="✅-isloading과-handledata-반환">✅ isLoading과 handleData 반환!</h2>
<p>마지막으로 여러 곳에서 사용할 수 있도록 isLoading과 handleData를 반환해준다. handleData를 반환해주는 이유는 사용자의 인터렉션으로 form이 제출 됐다던가, 어떤 버튼을 클릭했을 때 handleData로 데이터를 요청하고 받기위함이다.</p>
<pre><code class="language-ts">...
return [isLoading, handleData];</code></pre>
<h2 id="💡-전체코드">💡 전체코드</h2>
<pre><code class="language-ts">import { useState } from &#39;react&#39;;

type UseLoadingReturnType&lt;T&gt; = [boolean, (...args: any[]) =&gt; Promise&lt;T&gt;];

const useLoading = &lt;T&gt;(
  action: (...args: any[]) =&gt; Promise&lt;T&gt;
): UseLoadingReturnType&lt;T&gt; =&gt; {
  const [isLoading, setIsLoading] = useState(false);

  const handleData = async (...args: any[]) =&gt; {
    setIsLoading(true);
    return await action(...args).finally(() =&gt; setIsLoading(false));
  };

  return [isLoading, handleData];
};

export default useLoading;</code></pre>
<h2 id="✨-사용은-어떻게">✨ 사용은 어떻게?</h2>
<p><code>useLoading</code>을 다시 한번 설명하자면, 데이터를 받아오기 전까지 로딩을 띄우고 데이터를 성공적으로 받아오면 데이터를 뿌리는 커스텀 훅이다.</p>
<p>그래서 우리가 useLoading에서 사용할 수 있는 값은 <strong>데이터를 받아오는지 모니터링 하면서 로딩을 결정하는 Boolean 값 isLoading</strong>, 그리고 사용자 인터렉션이 있을 때 데이터를 가져오는 <strong>handleData 함수</strong>, 이 두개다.</p>
<p>Todo 리스트를 추가한다고 치자.</p>
<pre><code class="language-ts">// Todo.tsx
const [isLoading, createTodoLists] = useLoading(handleCreateTodos);

const handleSubmit = (e: FormEvent&lt;HTMLFormElement&gt;) =&gt; {
  e.preventDefault();
  createTodos(inputText, setTodos);
}

return (
  &lt;&gt;
  {isLoading ? &lt;Spinner /&gt; : &lt;TodoData /&gt;}
  &lt;/&gt;
);</code></pre>
<p>이런 식으로 사용할 수 있겠다.</p>
<p>만약 로딩 로직을 커스텀 훅으로 빼지 않았다면 <code>handleSubmit</code> 함수안에 <code>setLoading</code>과 <code>handleCreate()</code> 등 로딩과 데이터를 처리하는 코드들이 뭉쳐있어 가독성이 떨어질 수 있다.</p>
<hr>
<h2 id="마무리">마무리</h2>
<p><code>useLoading</code>은 로딩만 처리하는 커스텀 훅으로 단순하게 만들수도 있겠지만 로딩만 처리하기엔 useState로 쓰는 것과 코드 상의 차이가 없다고 느껴졌다. 그래서 주로 데이터를 받아올 때 만들어지는 것을 보고 데이터 로딩 훅을 만들어봤다.
실제로 CRUD 같은 하나 이상의 요청을 각각 받아와야 할 때, 로딩을 4곳에서 4번을 만드는 게 아닌 useLoading 훅을 사용하면 코드가 훨씬 간결해진다.</p>
<p>이상으로, useLoading 커스텀 훅에 대한 소개 및 사용법을 정리해보았다.</p>
<h2 id="참고">참고</h2>
<ul>
<li><a href="https://blog.logrocket.com/advanced-react-hooks-creating-custom-reusable-hooks/">Advanced React Hooks: Creating custom reusable Hooks - Lawrence Eagles</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Shallow copy (MDN 번역)]]></title>
            <link>https://velog.io/@a_in/Shallow-copy-MDN-%EB%B2%88%EC%97%AD</link>
            <guid>https://velog.io/@a_in/Shallow-copy-MDN-%EB%B2%88%EC%97%AD</guid>
            <pubDate>Wed, 10 May 2023 08:46:26 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>본 글은 면접을 대비하여 MDN에 설명된 Shallow copy를 번역한 글입니다.</p>
</blockquote>
<h1 id="얕은-복사-shallow-copy">얕은 복사 (Shallow copy)</h1>
<p><strong>얕은 복사</strong>(shallow copy)로 인해 생겨난 객체는 <em>프로퍼티가 원본값 프로퍼티와 동일한 참조를 공유하는 복사본(이하 복사 값)입니다. 결과적으로, 원본값/복사 값 중 하나라도 바뀌면 다른 하나도 똑같이 바뀌게 됩니다. 이에 따라 원본값/복사 값이 우리가 원하지 않게 바뀔 수 있습니다. 이러한 동작은 원본값/복사 값이 서로 완전히 독립적인 깊은 복사(deep copy)와는 대조를 이룹니다.
_\</em>프로퍼티는 객체 안에 들어가 있는 한 쌍의 key value를 말합니다. ex: 객체: {프로퍼티이름: &quot;프로퍼티값&quot;}_</p>
<p>얕은 복사의 경우, 객체의 기존 프로퍼티 값을 부분적으로 접근하여 변경하는 것과 기존값을 완전히 새로운 값으로 할당하는 것은 다르다는 점을 이해하는 것이 중요합니다.</p>
<pre><code class="language-js">const original = [
  {
    list: [&#39;butter&#39;, &#39;flour&#39;],
  },
];

const copy = Object.assign({}, original);</code></pre>
<pre><code class="language-js">copy[0].list = [&#39;oil&#39;, &#39;flour&#39;];
// original  [{list: [&#39;oil&#39;, &#39;flour&#39;]}];
// copy  [{list: [&#39;oil&#39;, &#39;flour&#39;]}];</code></pre>
<p>예를 들어, 배열을 담은 객체를 얕은 복사하여 <code>copy</code> 객체를 만든 후, <code>copy[0].list = [&quot;oil&quot;, &quot;flour&quot;]</code>로 프로퍼티 값을 바꾸면 공유된 프로퍼티 값을 부분적으로 수정하였기 때문에 이에 상응하는 원본값의 프로퍼티 값도 바뀌게 됩니다.</p>
<pre><code class="language-js">  copy[0] = { list: [&#39;oil&#39;, &#39;flour&#39;] };
// original  [{list: [&#39;butter&#39;, &#39;flour&#39;]}];
// copy  [{list: [&#39;oil&#39;, &#39;flour&#39;]}];</code></pre>
<p>대신, <code>copy[0] = {list:[&quot;oil&quot;, &quot;flour&quot;]}</code> 이런 식으로 기존값을 완전히 새로운 값으로 할당하게 되면 원본값은 <strong>바뀌지 않습니다.</strong> 이 경우, 그저 원본값과 공유하고 있는 복사 값의 프로퍼티 값을 부분적으로 바꾼 것이 아니라 완전히 새로운 값을 <code>copy[0]</code> 복사 값 배열에만 할당하였기 때문입니다.</p>
<p>자바스크립트에서는 모든 built-in 객체 복사 연산자(object-copy operations)를 사용하여 얕은 복사를 만들어낼 수 있습니다. (<a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax">전개 연산자</a>, <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/concat">Array.prototype.concat()</a>, <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice">Array.prototype.slice()</a>, <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/from">Array.from()</a>, <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign">Object.assign()</a>, <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/create">Object.create()</a>)</p>
<h2 id="예시">예시</h2>
<p>다음 예를 보면, <code>ingredients_list</code> 배열 객체가 생성되고 이 객체를 복사하여 <code>ingredients_list_copy</code>가 생성되었습니다.</p>
<pre><code class="language-js">let ingredients_list = [
  &#39;noodles&#39;,
  {
    list: [&#39;eggs&#39;, &#39;flour&#39;, &#39;water&#39;],
  },
];

let ingredients_list_copy = Array.from(ingredients_list);
console.log(JSON.stringify(ingredients_list_copy));
// [&quot;noodles&quot;,{&quot;list&quot;:[&quot;eggs&quot;,&quot;flour&quot;,&quot;water&quot;]}]</code></pre>
<p><code>list</code> 프로퍼티 값을 복사 값인 <code>ingredients_list_copy</code>에서 바꾼다면 원본값인 <code>ingredients_list</code>에 있는 <code>list</code> 프로퍼티 값도 바뀔 것입니다.</p>
<pre><code class="language-js">ingredients_list_copy[1].list = [&quot;rice flour&quot;, &quot;water&quot;];
console.log(ingredients_list[1].list);
// Array [ &quot;rice flour&quot;, &quot;water&quot; ]
console.log(JSON.stringify(ingredients_list));
// [&quot;noodles&quot;,{&quot;list&quot;:[&quot;rice flour&quot;,&quot;water&quot;]}]</code></pre>
<p>완전히 새로운 값을 복사 값인 <code>ingredients_list_copy</code>의 첫 번째 요소에 할당한다면, 원본값 <code>ingredients_list</code>의 첫 번째 값에는 아무런 변화도 주지 않을 것입니다.</p>
<pre><code class="language-js">ingredients_list_copy[0] = &quot;rice noodles&quot;;
console.log(ingredients_list[0]);
// noodles
console.log(JSON.stringify(ingredients_list_copy));
// [&quot;rice noodles&quot;,{&quot;list&quot;:[&quot;rice flour&quot;,&quot;water&quot;]}]
console.log(JSON.stringify(ingredients_list));
// [&quot;noodles&quot;,{&quot;list&quot;:[&quot;rice flour&quot;,&quot;water&quot;]}]</code></pre>
<h2 id="원문">원문</h2>
<p><a href="https://developer.mozilla.org/en-US/docs/Glossary/Shallow_copy">MDN - Shallow copy</a></p>
<h2 id="참고">참고</h2>
<p><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax">MDN - 전개 연산자</a>
<a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/concat">MDN - Array.prototype.concat()</a>
<a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice">MDN - Array.prototype.slice()</a>
<a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/from">MDN - Array.from()</a>
<a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign">MDN - Object.assign()</a>
<a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/create">MDN - Object.create()</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Git 관리] - 혼자서 협업 연습해보기]]></title>
            <link>https://velog.io/@a_in/Git-%EA%B4%80%EB%A6%AC-%ED%98%BC%EC%9E%90%EC%84%9C-%ED%98%91%EC%97%85-%EC%97%B0%EC%8A%B5%ED%95%B4%EB%B3%B4%EA%B8%B0</link>
            <guid>https://velog.io/@a_in/Git-%EA%B4%80%EB%A6%AC-%ED%98%BC%EC%9E%90%EC%84%9C-%ED%98%91%EC%97%85-%EC%97%B0%EC%8A%B5%ED%95%B4%EB%B3%B4%EA%B8%B0</guid>
            <pubDate>Sun, 07 May 2023 18:15:51 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>본 글에서는 실제 협업할 때 Git 관리를 어떻게 해야하는지 소개하는 글이 <strong>아닌</strong> 협업 전에 연습해볼만한 방법을 소개합니다.</p>
</blockquote>
<h1 id="git-관리가-뭐죠">Git 관리가 뭐죠?</h1>
<p>혼자서 프로젝트를 해왔다면 GitHub을 사용하면서 그저 <code>commit</code> <code>push</code>만 반복해서 작업해왔을 것입니다. GitHub는 내가 만든 프로젝트를 기록하는 역할을 하는 것뿐만 아니라, 협업을 할때에도 매우 유용한 툴입니다. 이때 협업이라고 해서 10명쯤은 되야 하는 것이 아니라 2명이서 작업할때도, 심지어 혼자 작업할 때도 관리를 할 수 있다는겁니다.</p>
<p>그렇다면, 혼자서 작업할 때 <strong>관리</strong>라는 것을 해야 할 필요가 있을까요? 어차피 내가 작업해온 것들이니 내 취향대로 <code>commit</code>하고 <code>push</code>하면 되는 것 아닐까요? 코드 짜는 것도 시간 없는데 Git 관리를 꼭 해야 할까요?</p>
<p>이러한 질문들은 제 과거의 질문들이였습니다. 지금에서야 깨닫고 Git 관리하는 방법을 소개해보려고 합니다.</p>
<h2 id="git-관리-순서">Git 관리 순서</h2>
<pre><code>이슈 정의하기 -&gt; commit 쪼개고 push하기 -&gt; PR 날리기 -&gt; 코드리뷰하기 -&gt;merge하기</code></pre><p>알고리즘 강의를 보면 코드를 짜기전에 손으로 먼저 순서를 적으라는 말을 들어 볼 수 있습니다. 여기서도 먼저 작업할 내용(Issue)을 정의하고 작업을 합니다.</p>
<h3 id="1-이슈-정의하기">1. 이슈 정의하기</h3>
<p>대부분의 오픈소스를 보면 <strong>Issue</strong>는 앱 실행 도중 발생하는 문제나 제안을 적는 공간입니다. 하지만 개인 프로젝트를 할때에는 Todo List를 적는 공간으로도 사용할 수 있습니다. 추가로 Issue와 Pull Request는 마크다운으로 스타일링이 가능하여 깔끔하게 볼 수 있습니다.</p>
<p>이슈 정의 예시입니다
<img src="https://velog.velcdn.com/images/a_in/post/e39459e9-4355-492b-970d-f1c628803e1d/image.png" alt="이슈 정의 예시"></p>
<p>이슈를 적는 곳 오른쪽에는 작성자 태그, 라벨 선택, 그리고 Pull Request와 연동할 수 있는 칸이 있습니다.</p>
<blockquote>
</blockquote>
<p><strong>Assignees</strong>: 써있는 그대로 본인을 태그하면 됩니다.
<strong>Labels</strong>: documentation, bug, enhancement등 이슈내용에 맞는 라벨을 선택하면 됩니다.
<strong>Projects</strong>: 협업을 여러명이서 할 때 organization을 만들어 Project에서 일정을 관리할 수 있는데 그때 연동할 수 있습니다.
<strong>Milestone</strong>: 사용해본 적이 없으므로 생략하겠습니다.
<strong>Development</strong>: 특정 Pull Request와 연동할 수 있으며, 연동시 Pull Request가 머지 되어 닫히게 되면 해당 Issue도 자동으로 닫힙니다.</p>
<p><strong>Submit new issue</strong>를 하면 Issues에 열립니다.</p>
<p><img src="https://velog.velcdn.com/images/a_in/post/2c23bc4a-55f8-4e8e-ade1-6ef82dcd5df4/image.png" alt="이슈 open 예시"></p>
<p>이제 본인이 작성한 대로 작업을 진행하면서 완료된 task는 체크를 해나가면 됩니다.</p>
<h3 id="2-작업-branch-만들기">2. 작업 branch 만들기</h3>
<p>보통 main 브랜치는 배포시 사용하며, main브랜치에서 develop브랜치를 따로 만들어 해당 브랜치에서 개발을 진행합니다.</p>
<p>커맨드를 사용하여 브랜치를 develop 브랜치로부터 파생합니다.(브랜치 이름은 본인취향에 맞게 네이밍합니다.)</p>
<pre><code class="language-bash">%develop git checkout -b &lt;new-branch/issue-number&gt;
// 예시
%develop git checkout -b feature/1</code></pre>
<p>이제 develop 브랜치에서 모든 코드를 합칠 것이기 때문에 <code>merge</code> 할때 헷갈리지 않기 위해 default branch를 <code>develop</code> 브랜치로 변경해줍니다.</p>
<blockquote>
</blockquote>
<p><strong>Default branch 변경 방법</strong>
<code>Settings</code> -&gt; <code>General</code> -&gt; <code>Default branch</code> -&gt; <code>Switch to another branch</code> 아이콘 클릭 -&gt; 선택 후 <code>Update</code> 클릭</p>
<p>이제 <code>feature/1</code>라는 브랜치에서 작업을 한 뒤, <code>commit</code> <code>push</code>를 진행합니다.</p>
<h3 id="3-commit-쪼개기">3. commit 쪼개기</h3>
<p>commit을 쪼갠다는 것은 무슨 말일까요? 왜 쪼개는 것일까요? <strong>어떻게</strong> 쪼갠다는 것일까요?</p>
<p><strong style="background-color:rgba(230,190,10,0.5)">쪼갠다?</strong>
commit을 쪼갠다는 것은 한번에 모든 commit을 올리는 것이 아니라, 기능 단위로 쪼갠다는 것을 의미합니다.</p>
<p><strong style="background-color:rgba(230,190,10,0.5)">왜 쪼개?</strong>
a, b, c 작업을 한번에 다하고 commit을 하였는데 a까지 작업한 곳으로 돌아가고 싶어졌습니다. 하지만 a를 수정하려면 여기저기서 되돌려야 할 것이 많기 때문에 어려울 것입니다. 한마디로 <strong>수정을 쉽게 하기 위해</strong> commit을 쪼갭니다.</p>
<p>또, <strong>코드리뷰</strong>할때 보기 편한 이유도 있습니다.
아래 사진의 commit을 보면 어떤 부분을 작업했는지 알아보기 쉬울까요?</p>
<p><img src="https://velog.velcdn.com/images/a_in/post/e99eac84-1e37-4341-8f06-a66b674957dc/image.png" alt="commit 쪼개기 나쁜 예시"></p>
<p>어떤 페이지 작업이 끝났다는 commit만 있으면 나중에 디버깅도 힘들것이고, 코드리뷰를 하는 사람이 나말고 더 있다면 십몇개의 변경된 파일을 하나 하나 보면서 리뷰를 진행해야 할 것입니다.</p>
<p><img src="https://velog.velcdn.com/images/a_in/post/159db0b2-6d3e-4471-976a-9079a7e2db8f/image.png" alt="commit 쪼개기 예시"></p>
<p>위의 예시를 보면 <em>&quot;write journal form에 있는 date를 지웠구나.&quot;</em> 라고 얼추 예상할 수 있습니다. 또, 이 commit안에 다른 내용은 없고 date를 지운 내용만 있다면 훨씬 보기 수월할 것입니다.</p>
<p><strong style="background-color:rgba(230,190,10,0.5)">어떻게 쪼개?</strong>
작업을 마치면 기능 단위로 혹은 한줄로 설명할 수 있을 만한 작업들을 commit 합니다.</p>
<pre><code class="language-bash">%feature/1 git add ./src/pages/MainPage.jsx
%feature/1 git commit -m &quot;feat: implements main page UI&quot;</code></pre>
<p>이렇게 쪼개고 commit하고를 반복합니다.
다 마쳤으면 push를 합니다. 작업중인 branch를 remote에 올린 적이 없다면 <code>git push --set-upstream origin &lt;branch&gt;</code>을, remote에 branch가 올라와있다면 <code>git push</code>를 하여 <code>push</code>를 하고 Pull Request를 날립니다.</p>
<h3 id="4-pull-request-올리기">4. Pull Request 올리기</h3>
<p>줄여서 PR, 풀리퀘라고도 불리는 Pull Request를 올릴 차례입니다.
Pull Request는 다른 사람에게 자신의 코드를 보여주고 리뷰를 받기 위함입니다. 개인 프로젝트를 하고 있다면 자신의 코드를 돌아볼 수 있는 시간이 됩니다.
Issue와 동일하게 마크다운이 지원되고, Issue 번호를 &lt;#이슈번호&gt; 이렇게 적어올리면 해당 Issue와 연동이 됩니다. 이 Pull Request가 수락되어 merge를 진행한다면 연동되어있는 Issue도 자동으로 닫힙니다.</p>
<p><img src="https://velog.velcdn.com/images/a_in/post/e90e6b26-a468-4d44-9090-516c88dc4b46/image.png" alt="Pull Request 올리기 예시"></p>
<p>Pull Request 올리면 작성한 내용과 그동안 작업한 commit들, 연동된 Issue, 그리고 내 PR과 commit에 달린 댓글들을 볼 수 있습니다.</p>
<p>Issue와 동일하게 오른쪽에는 태그 항목들이 있습니다.</p>
<blockquote>
</blockquote>
<p><strong>Reviewers</strong>: 리뷰를 요청할 사람들을 태그합니다.
<strong>Assignees</strong>: Pull Request를 작성한 본인을 태그 합니다.
<strong>Labels</strong>: documentation, bug, enhancement등 Pull Request내용에 맞는 라벨을 선택하면 됩니다.
<strong>Project</strong>: Project가 있다면 연동합니다.
<strong>Milestone</strong>: 사용해본 적이 없으므로 생략하겠습니다.
<strong>Development</strong>: 특정 Issue와 연동할 수 있으며, 연동시 Pull Request가 머지 되어 닫히게 되면 해당 Issue도 자동으로 닫힙니다. 이 부분은 Pull Request 작성부분에서 Issue 번호를 언급했다면 건들이지 않아도 됩니다.</p>
<h3 id="5-코드-수정하기">5. 코드 수정하기</h3>
<p>본인의 코드를 살펴보다가 수정할 사항이 있다면 수정을 진행하고 <code>commit</code> 및 <code>push</code>를 합니다. 이때 따로 Pull Request를 만들 필요없이 자동으로 이미 작성된 Pull Request로 커밋이 들어갑니다.</p>
<h3 id="6-merge하기">6. Merge하기</h3>
<p>코드도 다 수정하고 Issue에 정의한 내용도 다 완료하였다면, Pull Request의 comment 다는 곳 바로 위에 <code>Merge Pull Request</code>버튼을 클릭하여 Merge합니다.
로컬에서는 머지해야 하는 branch로 이동하여 <code>git pull</code> 커맨드를 입력하여 원격에 있는 코드들을 땡겨옵니다.</p>
<pre><code class="language-bash">%feature/1 git checkout develop
%develop git pull</code></pre>
<p>여기까지 한 작업들을 반복해줍니다.</p>
<p>마지막으로 배포시에는 main에 코드들을 <code>merge</code> 한 뒤 main 브랜치를 default branch로 설정하여 배포합니다.</p>
<h2 id="git-관리-이유">Git 관리 이유</h2>
<p>위에 순서들을 쭉 훑고 직접 진행해보았다면 이유도 바로 알 수 있습니다. Git 관리는 나의 코드들을 관리하는 것이며, 나의 작업들을 하나하나 자세히 기록하는 일입니다. 혼자서 작업할 때에는 효과를 덜 보겠지만, 나의 코드를 보는 사람이 생겼을 때부터 관리를 해보면서 관리의 중요성을 깨닫게 됩니다.</p>
<h2 id="마무리">마무리</h2>
<p>오픈소스의 GitHub을 보면서 많이 배울 수 있습니다. 꼭 이 글처럼 진행하지 않더라도 다른 검증된 Github을 보면서 공부하고 꼭 실천해보시길 바랍니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[TypeScript 타입 총정리 (part.1)]]></title>
            <link>https://velog.io/@a_in/TypeScript-%ED%83%80%EC%9E%85-%EC%B4%9D%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@a_in/TypeScript-%ED%83%80%EC%9E%85-%EC%B4%9D%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Mon, 01 May 2023 06:01:40 GMT</pubDate>
            <description><![CDATA[<h1 id="javascript를-대신할-typescript">Javascript를 대신할 TypeScript?</h1>
<p>TypeScript에 입문을 하면 JavaScript(이하 자바스크립트)를 돌아보게 된다. 자바스크립트는 좋게말하면 굉장히 유연하고 자유로운 언어이다. 하지만 이렇게 규칙없이 자유분방하기 때문에 우리의 프로젝트에 에러를 내기 쉽다는 것이다. 우리가 만든 변수가 언제 어디서든 타입이 바뀌면 개발을 하는 (기계가 아닌)인간이 봤을 때 이 변수는 무슨 역할을 하는지 예측이 어렵고, 말도 안되는 상황에서 오류를 내지 않을 수도 있으며, 말 그대로 예측불가인 것이다. 이렇게 자유분방한 언어에 규칙을 넣어주는 것이 TypeScript(이하 타입스크립트)이다.
어떻게 보면 마블 드라마 Loki처럼 TVA가 타임라인을 바로 잡아주지 않으면 variant(변종)가 타임라인을 벗어나서 무너뜨릴 수 있다고 비유할 수 있을 것 같다 (광고아님).
다시 말해, 타입스크립트가 변수의 타입을 정해주지 않으면 우리의 코드에 빈틈을 주어서 에러를 발생시킨다.</p>
<p>예를 들어 이런 코드가 있다고 치자.</p>
<blockquote>
</blockquote>
<pre><code class="language-js">function addAge(x, y) {
  return x + y;
}</code></pre>
<p>&quot;나이 계산하는 함수아냐?&quot; 라고 생각할 수 있겠지만 그건 함수를 작성한 사람만 아는것이다. 그 사람은 문자열을 더하고 싶었던 걸지도, 아니면 숫자와 문자열을 더하고 싶었던 걸지도 모르기 때문이다. 내 나이를 말하는 문자열을 반환하고 싶어서 <code>age(18, &#39;years old&#39;);</code>로 호출 할 수 도 있는 것이고, 나의 10년후 나이를 알고 싶어서 <code>age(18, 10);</code> 이렇게 전달 할 수도 있는 것이다. 즉, 이 함수의 의도가 무엇이고, 어떤 결과값이 나올지 예측할 수가 없기 때문에 type을 정해준다.</p>
<blockquote>
</blockquote>
<pre><code class="language-ts">function addAge(x: number, y: string): string {
  return x + y;
}</code></pre>
<p>&quot;x에는 숫자를 전달해줄거고, y에는 문자열을 줄거야. 반환 값은 문자열이 되겠지.&quot; 라고 미리 말해준다면 어느정도 어떤 코드가 그려질지 예상이 간다. 적어도 y에는 숫자를 전달하면 안되고 반환되는것도 숫자는 아니라는 것을 우리는 미리 예고를 받은 셈이다.</p>
<h2 id="타입스크립트의-역할">타입스크립트의 역할</h2>
<p>타입스크립트는 우리의 코드에 타입을 넣을 수 있는 공간을 제공해 주는 것뿐만 아니라 사람이 저지를 수 있는 실수를 자바스크립트보다 더 명확하게 바로 잡아준다.
object로 예를 들어보자.</p>
<ul>
<li>객체에 없는 속성을 출력할때, 자바스크립트에서는 undefined가 출력되고, 타입스크립트는 아예 에러를 띄워준다.</li>
</ul>
<blockquote>
<pre><code class="language-ts">// 타입
type Character = {
  gender: string;
  age: number;
}
// 객체
const Mike: Character = {
  gender: &#39;male&#39;,
  age: 14,
}
console.log(myObj.gender); // male
cosnole.log(myObj.height); //&#39;Character&#39; 형식에 &#39;height&#39; 속성이 없습니다.</code></pre>
</blockquote>
<pre><code>
자바스크립트를 쓰다가 타입스크립트를 쓰면 감이 잘 안오겠지만, 반대로 타입스크립트를 쓰다가 자바스크립트를 쓰면 느낌이 확 온다. _&quot;이거 왜 에러 안띄워?&quot;, &quot;타입스크립트가 편하네...&quot;_ 라는 말이 분명히 나온다.


## Syntax

타입스크립트에서 사용할 수 있는 문법들을 알아보자.

### Primitive Type (원시 타입)
원시타입은 number, string, 그리고 boolean이 있다.

&gt;
```ts
const num: number = 2022;
const str: string = &#39;string!&quot;
const bool: boolean = true;</code></pre><p>이렇게 변수 뒤에 콜론을 붙히고 타입을 적어주면 된다. 한번 타입을 정해준 변수는 뒤에 재할당을 할 때 초기에 정해준 타입만 재할당이 가능하다.</p>
<blockquote>
</blockquote>
<pre><code class="language-ts">// ❌
let friends: string = &#39;Monica&#39;;
friends = 26; // 에러: &#39;number&#39; 형식은 &#39;string&#39; 형식에 할당할 수 없습니다.</code></pre>
<h3 id="array-type-배열-타입">Array Type (배열 타입)</h3>
<p>배열에 타입을 줄 때에는 이 변수는 배열이고, 이 배열안에 구성된 요소가 어떤 타입인지도 알려줘야 한다. 배열은 두가지 문법으로 쓰일 수 있다.</p>
<blockquote>
</blockquote>
<pre><code class="language-ts">// 숫자로 이루어진 배열
const numArr: number[] = [1, 2, 3];
const numArr2: Array&lt;number&gt; = [4, 5, 6]
// 문자열로 이루어진 배열
const strArr: string[] = [&#39;JS&#39;, &#39;TS&#39;];
const strArr2: Array&lt;string&gt; = [&#39;React&#39;, &#39;Next&#39;];
...</code></pre>
<h3 id="function-type-함수">Function Type (함수)</h3>
<p>함수에는 전달받은 인자에 타입과, 반환되는 값의 타입을 지정해야 한다. 함수에서 반환되는 값이 없으면 <code>void</code>라는 타입을 준다.
타입스크립트 공식문서에 의하면 반환되는 값의 타입은 return문 때문에 타입 추론이 된다고 한다.</p>
<blockquote>
<p><em>Much like variable type annotations, <span style='background-color: rgba(10,210,110,0.4)'>you usually don’t need a return type annotation because TypeScript will infer the function’s return type based on its return statements.</span> The type annotation in the above example doesn’t change anything.
<span style='background-color: rgba(10,210,110,0.4)'>Some codebases will explicitly specify a return type for documentation purposes, to prevent accidental changes, or just for personal preference.</span></em>
<a href="https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#return-type-annotations">TypeScript - Every Types</a></p>
</blockquote>
<blockquote>
</blockquote>
<pre><code class="language-ts">// void
const myFunction = (arg: number) =&gt; {    
  ...
}</code></pre>
<pre><code class="language-ts">const myFunction = (arg: number): string =&gt; {
  const myString = &#39;hello!&#39;
  return myString;
}</code></pre>
<h3 id="object-type-객체-타입">Object Type (객체 타입)</h3>
<p>객체 타입을 Object로 지정하는 것은 any와 같기 때문에 Object 라는 타입은 쓰지 않는다. 대신 객체 안에 있는 key와 value들의 타입을 하나하나 정해주면 된다.</p>
<blockquote>
</blockquote>
<pre><code class="language-ts">❌
const students: Object = {
  name: &#39;Monica&#39;,
  age: 26,
};</code></pre>
<pre><code class="language-ts">✅
const students: { name: string; age: number } = {
  name: &#39;Monica&#39;,
  age: 26,
};</code></pre>
<p>또는, <code>interface</code> 선언문이나 <code>type alias</code>로 자세하게 타입을 지정해주고 객체들에 알맞는 이름을 부여해야 한다.</p>
<blockquote>
</blockquote>
<pre><code class="language-ts">✅
type Students = {
  name: string;
  age: number;
}
const students: Students = {
  name: &#39;Monica&#39;,
  age: 26,
}</code></pre>
<p>지금까지 소개한 타입은 자바스크립트에서 흔히 볼 수 있는 타입을 시각화 해주었다고 볼 수 있다. 다음 타입스크립트 게시글에서는 <code>Union Types</code>, <code>Type Alias</code>, <code>Interface</code>, <code>Type Assertion</code>, <code>Literal Type</code>에 대해서 정리해보려한다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Kakao Map 전환기 (part.2)- 지도에서 특정 좌표 및 주소 가져오기]]></title>
            <link>https://velog.io/@a_in/Kakao-Map-migration-part.2-get-particular-coordinate-and-address</link>
            <guid>https://velog.io/@a_in/Kakao-Map-migration-part.2-get-particular-coordinate-and-address</guid>
            <pubDate>Tue, 21 Mar 2023 02:06:21 GMT</pubDate>
            <description><![CDATA[<h2 id="서론">서론</h2>
<p>본 게시물에서는 지도에서 특정 위치를 클릭하여 좌표를 가져온 후, 좌표에서 주소로 변환하는 작업을 기록하였다.</p>
<p>오픈 API를 적용해보면 알겠지만, 블로그만 보고 코드를 복사 붙혀넣기 하거나 대충 메서드의 역할만 파악하고 코드를 치면 안된다. 카카오 지도 API 문서에는 내가 사용하고 싶었던 기능을 친절하게 샘플과 코드 예시로 보여주지만, 무턱대고 복사 붙여넣기 하면 이 API를 30%도 제대로 사용하지 못 할 것이다.(잘못하면 한 주를 낭비한다.) 만약 이런 오픈 API를 사용하는 방법을 잘 모른다면 <em>&quot;나는 시간이 없으니 필요한 것만 딱 뽑아서 봐야지&quot;</em> 라는 생각은 접어두면 좋다.</p>
<p>항상 차근차근 API의 <strong>시작단계</strong>부터 공부하는 것을 추천한다. 카카오 지도의 <strong>시작단계</strong>는 <strong>지도 생성하기</strong> 이기 때문에 꼭 지도를 생성하는 메서드를 본 후 샘플을 보기 바란다.</p>
<ul>
<li><strong>지도 생성하기</strong> 과정을 담은 벨로그 링크:
<a href="https://velog.io/@a_in/Kakao-Map-%EC%A0%84%ED%99%98%EA%B8%B0-%EC%A7%80%EB%8F%84-%EC%83%9D%EC%84%B1%ED%95%98%EA%B8%B0-with-Errors">Kakao Map 전환기 - 지도 생성하기 (with Errors)</a></li>
<li>카카오 지도 API 문서:
<a href="https://apis.map.kakao.com/web/documentation/#Map">지도 객체 메서드</a></li>
</ul>
<hr>
<h2 id="1-api-적용하기">1. API 적용하기</h2>
<h3 id="지도에서-특정-위치를-클릭-하여-좌표-가져오기">지도에서 특정 위치를 클릭 하여 좌표 가져오기</h3>
<p>지도 생성하기 API로 생성된 지도 위에서 클릭을 하여 주소를 가져오고 싶거나, 지도를 이동시켜 범위 밖에 있던 마커를 나타내주고 싶다면 이벤트를 걸어주면 된다.</p>
<h4 id="kakaomapseventaddlistener">kakao.maps.event.addListener()</h4>
<blockquote>
</blockquote>
<p>click 이벤트는 지도를 클릭하면 발생한다.
<a href="https://apis.map.kakao.com/web/documentation/#Map_click">Map - events - click</a></p>
<pre><code class="language-ts">kakao.maps.event.addListener(target, &#39;eventName&#39;, listener);</code></pre>
<h4 id="설명">설명</h4>
<p>이벤트 리스너의 첫번째 인자로는 클릭 대상, 두번째 인자로는 이벤트 이름, 세번째로는 지도에서 이벤트가 발생하였을 때 실행될 콜백 함수를 넣어준다. 지도의 이벤트는 클릭을 제외하고도 이동 이벤드, 확대 이벤트, 영역 변경 이벤트 등이 있다.
클릭 이벤트의 listener를 담당하는 콜백함수에는 mouseEvent라는 인자가 하나 들어오는데 이 인자로 지도 좌표(latLng) 및 화면 좌표(point)를 가져올 수 있다.</p>
<h4 id="example">Example</h4>
<blockquote>
</blockquote>
<pre><code class="language-ts">kakao.maps.event.addListener(map, &#39;click&#39;, function(mouseEvent: any) {
  console.log(mouseEvent.latLng)
})</code></pre>
<p><img src="https://velog.velcdn.com/images/a_in/post/4c73e751-f7ff-48bc-935e-c47c184c18fb/image.png" alt="좌표 가져온 결과"></p>
<ul>
<li><strong>latLng</strong>: 지도에서 클릭 한 지점의 지도 좌표를 가져와주며, getLat() getLng()등 메서드로 위도와 경도를 각각 가져와 줄 수 있다.</li>
</ul>
<p>더알아보기 - <a href="https://apis.map.kakao.com/web/documentation/#MouseEvent">카카오 지도 문서 MouseEvent</a></p>
<hr>
<h3 id="좌표를-지번주소-및-도로명주소로-변환하기">좌표를 지번주소 및 도로명주소로 변환하기</h3>
<h4 id="kakaomapsservicesgeocoder">kakao.maps.services.Geocoder()</h4>
<blockquote>
</blockquote>
<p>Geocoder constructor는 주소-좌표간 변환 서비스 객체를 생성한다.</p>
<pre><code class="language-ts">let geocoder = new kakao.maps.services.Geocoder();</code></pre>
<h4 id="설명-1">설명</h4>
<p>constructor이기 때문에 새로운 생성자로 만들어 준 다음, 주소 - 좌표간 변환 메서드를 사용할 수 있다.
<a href="https://apis.map.kakao.com/web/documentation/#services_Geocoder_Methods">카카오 지도 문서 Geocoder Method</a></p>
<h4 id="example-1">Example</h4>
<blockquote>
</blockquote>
<pre><code class="language-ts">let geocoder = new kakao.maps.services.Geocoder();
geocoder.coord2Address(x, y, callback, options)</code></pre>
<ul>
<li><strong>coord2Address(x, y, callback, options)</strong>: 좌표를 매개변수로 전달하면 콜백 함수에서 인자로 결과 값을 받을 수 있다.
조금 전에 지도를 클릭하여 받은 좌표를 넣어주면 클릭한 위치의 지번 주소, 도로명 주소 그리고 우편주소까지 받아 볼 수 있다.<pre><code class="language-ts">const coords = mouseEvent.latLng
const callback = (result: {[key:string]: any}, status: any) =&gt; {
if(status === kakao.maps.services.Status.OK) {
  console.log(result);
}
}
coords2Address(coords.getLat(), coords.getLng(), callback)</code></pre>
</li>
</ul>
<p><a href="https://developers.kakao.com/docs/latest/ko/local/dev-guide#coord-to-district-sample" title="이미지를 클릭하여 REST API 응답결과를 참고 문서로 이동하세요."><img src="https://velog.velcdn.com/images/a_in/post/23b9526a-49aa-47aa-8271-1fcfcef66c19/image.png" alt="result"></a></p>
<hr>
<h3 id="지도-위에-주소-띄우기">지도 위에 주소 띄우기</h3>
<p>지도 위에 주소를 띄우기 위해 해야 할 일은, 클릭한 지점 위에 주소를 표시해줄 인포윈도우(<a href="https://apis.map.kakao.com/web/documentation/#InfoWindow">infoWindow</a>) 나 커스텀 오버레이(<a href="https://apis.map.kakao.com/web/documentation/#CustomOverlay">CustomOverlay</a>)위에 주소를 넣어주는 것이다. 인포윈도우보다 커스텀 오버레이가 더 폼나보이지만 심플해보이는 인포윈도우로 우선 주소를 띄워보겠다.</p>
<h3 id="kakaomapsinfowindowoptions">kakao.maps.InfoWindow(options)</h3>
<blockquote>
<p>주어진 객체로 인포윈도우를 생성한다.
지도 뿐만 아니라 로드뷰 위에도 올릴 수 있다.</p>
</blockquote>
<pre><code class="language-ts">let infowindow = new kakao.maps.InfoWindow(options);</code></pre>
<h4 id="설명-2">설명</h4>
<p>하나의 객체이며 new 키워드로 생성하여 메서드를 사용할 수 있다.</p>
<h4 id="example-2">Example</h4>
<pre><code class="language-ts">// 도로명이 있다면 도로명을, 없다면 지번주소를 인포윈도우에 띄워준다.
const address = result[0].road_address
      ? result[0].road_address.address_name
      : result[0].address.address_name;
// 인포윈도우를 생성해준다.
const infowindow = new kakao.maps.InfoWindow({
      map,
      position: new kakao.maps.LatLng(coords.getLat(), coords.getLng()),
      content: &#39;&lt;div&gt;&#39; + address + &#39;&lt;/div&gt;&#39;,
    });
infowindow.open(map);</code></pre>
<ul>
<li><strong>options</strong><ul>
<li>map: 인포윈도우가 올라갈 지도 (또는 로드뷰).</li>
<li>position: 인포윈도우가 올라갈 특정 좌표. (우리는 클릭한 곳에 인포윈도우를 생성할 것이기 때문에 클릭해서 받아온 좌표를 position에 넣어준다.)</li>
<li>content: HTML형식의 내용
<a href="https://apis.map.kakao.com/web/documentation/#InfoWindow">...자세히 보기</a></li>
</ul>
</li>
</ul>
<br />

<p>여기까지 인포윈도우를 생성하는 과정이였다. <span style="color:red">하지만 여기서 끝내면 클릭하는 곳마다 인포윈도우가 생겨나고 여러개의 인포윈도우가 겹칠 것이다.</span> 인포윈도우를 열기만 하고 닫지를 않았기 때문이다.</p>
<p>카카오 지도 API에는 지도에 표시되어 있는 모든 인포윈도우를 삭제해버리는 메서드는 따로 없기 때문에 다른 방법을 사용해야한다.</p>
<blockquote>
</blockquote>
<p><strong>배열을 임의로 만들어 인포윈도우를 배열 안에 넣고 하나하나 돌면서 지도에 보이지 않게 숨겨주어야 한다.</strong></p>
<p>먼저, 배열을 선언 및 초기화할 때에는 선언하는 위치를 잘 확인해야 한다. 아래 예제에서는 가독성을 위해 인포윈도우를 생성하기 바로 전에 초기화하지만, 코드 구조에 따라 초기화를 잘 해주어야 한다. <span style="color:red">인포윈도우를 지도에서 지우기도 전에 빈배열로 초기화 시켜버리면 모든 인포윈도우가 남아있을 것이다.</span></p>
<p>인포윈도우를 생성하고 지도에 표시 할 때마다 infos 배열에 인포윈도우 객체를 넣어준다.</p>
<pre><code class="language-ts">let infos: any = [];
// 인포윈도우 생성
const infowindow = new kakao.maps.InfoWindow({
      map,
      position: new kakao.maps.LatLng(coords.getLat(), coords.getLng()),
      content: &#39;&lt;div&gt;&#39; + address + &#39;&lt;/div&gt;&#39;,
    });
// 인포윈도우 지도에 표시
infowindow.open()
infos.push(infowindow);</code></pre>
<p>배열에 인포윈도우가 잘 들어갔는지 확인 한 다음, 인포윈도우를 숨겨주는 removeInfos 함수를 만든다. 이 함수는 infos배열안에 들어있는 인포윈도우를 하나하나 돌아가면서 API에서 제공해주는 메서드인 infowindow.close()를 사용해 인포윈도우를 전부 닫아주는 함수이다. 다 닫았으면 배열도 초기화 해준다.</p>
<pre><code class="language-ts">const removeInfos = () =&gt; {
  infos.forEach((info: any) =&gt; info.close());
  infos = [];
}</code></pre>
<h4 id="결과코드">결과코드</h4>
<pre><code class="language-ts">let infos: any = [];

const removeInfos = () =&gt; {
  infos.forEach((info: any) =&gt; info.close());
  infos = [];
}

const infowindow = new kakao.maps.InfoWindow({
      map,
      position: new kakao.maps.LatLng(coords.getLat(), coords.getLng()),
      content: &#39;&lt;div&gt;&#39; + address + &#39;&lt;/div&gt;&#39;,
    });

// 인포윈도우 하나 띄우기 전에 이전에 있던 인포윈도우 숨기기
removeInfos()
infos.push(infowindow);
infowindow.open()</code></pre>
<h3 id="더보기-카카오-지도-샘플---좌표로-주소-얻어내기">더보기: <a href="https://apis.map.kakao.com/web/sample/coord2addr/">카카오 지도 샘플 - 좌표로 주소 얻어내기</a></h3>
<blockquote>
</blockquote>
<p>다음 게시물에서는 키워드 검색으로 관련된 장소들을 마커를 표시하고, 마커 클릭 시 인포윈도우를 띄우는 과정을 정리해보겠다.</p>
<hr>
<h2 id="참고">참고</h2>
<ul>
<li><a href="https://apis.map.kakao.com/web/documentation/">카카오 지도 API 문서</a></li>
<li><a href="https://apis.map.kakao.com/web/sample/">카카오 지도 API 샘플</a></li>
<li><a href="https://developers.kakao.com/docs/latest/ko/local/dev-guide#coord-to-district-sample">카카오 로컬 REST API 문서</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Kakao Map 전환기 - 지도 생성하기  (with Errors)]]></title>
            <link>https://velog.io/@a_in/migrate-to-Kakao-Map-API-with-Errors</link>
            <guid>https://velog.io/@a_in/migrate-to-Kakao-Map-API-with-Errors</guid>
            <pubDate>Mon, 05 Dec 2022 21:47:22 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>본 게시물은 기존에 사용했던 Naver 지도 API에서 Kakao 지도 API로 전환하는 과정을 담았다.
<strong>사용 스택</strong>: <code>Next.js</code> <code>TypeScript</code></p>
</blockquote>
<h2 id="서론">서론</h2>
<hr>
<p>개인적인 이유로 Naver 지도 API를 사용하였다가 Kakao 지도 API로 전환하게 되었다. 다행히 지도 API를 처음 생성하는 것보다는 덜 오래 걸렸지만 사용법이 완전히 똑같지 않기 때문에 부가로 더 이해해야 할 것이 많았다. 지도 생성하기 뿐만 아니라 <strong>지도를 클릭해서 주소를 가져오는 것, 네이버에는 없었던 키워드로 주소 검색하기도 차례차례 구현해보며 &quot;Kakao Map 전환기&quot; 시리즈를 만들어 볼 예정이다.</strong></p>
<p>본 게시물에서는 script를 불러오고 지도를 생성하면서 어떤 에러를 마주했는지, Naver API와 다른점이 무엇인지를 정리해보았다.</p>
<br />

<h2 id="kakao-map-api-사용하기">Kakao Map API 사용하기</h2>
<p>Kakao Map API 사용하기부터 먼저 다뤄보겠다.</p>
<h3 id="typescript와-함께-사용하기">TypeScript와 함께 사용하기</h3>
<hr>
<p>Naver 지도 API와는 다르게 공식적으로 TypeScript와 사용할 수 있는 패키지가 없다. 그렇기 때문에 TypeScript와 같이 사용하는 방법은 두가지가 있는데, 첫번째는 전역적으로 <code>kakao</code>를 선언하는 것이 있고, 두번째로는 오픈 소스로 기여된 Kakao API의 타입이 정의 되어있는 패키지를 설치하여 사용하는 것이다.</p>
<ol>
<li><strong>kakao 선언하기</strong>
만약 TypeScript를 사용하고 있는 상태에서 script만 로드한다면 kakao가 정의되지 않았다는 에러가 발생한다. 그래서 _app.tsx에 전역적으로 any로 선언해주면 임시적으로 <code>kakao</code>에서 발생하는 타입에러를 막을 수 있다.</li>
</ol>
<pre><code class="language-tsx">// _app.tsx
declare global {
  interface Window {
    kakao: any;
  }
  const kakao: any;
}</code></pre>
<ol start="2">
<li><strong>타입 정의 패키지 설치하기</strong>
아래 명령어로 devDepandency에 <strong>kakao.maps.d.ts</strong>를 추가해주고, tsconfig.json의 compilerOptions.types 속성에 패키지를 추가한다.</li>
</ol>
<pre><code class="language-tsx">// 설치
$ npm install kakao.maps.d.ts --save-dev</code></pre>
<pre><code class="language-js">// tsconfig.json 패키지 추가
{
  ...,
  &quot;compilerOptions&quot;: {
    ...,
    &quot;types&quot;: [
      ...,
      &quot;kakao.maps.d.ts&quot;
    ]
  }
}</code></pre>
<p>(패키지에 대한 더 자세한 내용은 <a href="https://github.com/JaeSeoKim/kakao.maps.d.ts">JaeSeo Kim - kakao.maps.d.ts</a> 참고)</p>
<br />

<h3 id="script-불러오기">script 불러오기</h3>
<hr>
<pre><code class="language-html">&lt;Script type=&quot;text/javascript&quot; strategy=&quot;beforeInteractive&quot; src=&quot;//dapi.kakao.com/v2/maps/sdk.js?appkey=발급받은 JavaScript 키&amp;libraries=services&amp;autoload=false&quot;&gt;&lt;/Script&gt;</code></pre>
<ul>
<li><strong>type</strong> : script의 타입은 text/javascript로 입력해준다.</li>
<li><strong>strategy</strong> : 웹 성능 최적화를 도와줄 Next.js의 strategy 속성은 <code>beforeInteractive</code>로 값을 넣어주는데 이는 어떠한 코드보다도 <strong>먼저</strong> script를 먼저 로드하게 도와준다. (기본값은 afterInteractive로 script가 빠르게 로딩 되지만 다른 페이지에 있는 코드가 먼저 실행될 수 있다.)
자세한 설명은 <a href="https://nextjs.org/docs/basic-features/script#strategy">Next.js Docs - Strategy</a>에서 볼 수 있다.</li>
<li><strong>// 프로토콜</strong> : src를 보면 <code>//</code>로 시작하는데이 상대 프로토콜을 사용하면 사용자의 http, https 환경에 따라 자동으로 해당 프로토콜을 따라가게 된다고 설명되어있다. <a href="https://apis.map.kakao.com/web/guide/#step1">Kakao Maps API - 시작하기</a></li>
<li><strong>librabies</strong> : src url에는 sdk 뒤에 사용할 라이브러리를 불러 올 수 있다. <code>libraries=사용할라이브러리</code></li>
<li><strong>autoload</strong> : script를 동적으로 로드하게 되면 로드가 다 끝나기도 전에 kakao api를 불러오는 코드가 먼저 실행 될 수 있기 때문에 이 autoload를 false로 만들어 자동으로 로드 되는 것을 꺼준다음, 코드 실행 부분에서 <code>load</code> 메서드를 사용하면 된다.</li>
</ul>
<p><code>Next.js</code>를 사용하고 있다면, <code>_document.tsx</code> 파일로 들어가서 <code>&lt;Head&gt;</code>태그 안에 script를 넣어준다.</p>
<pre><code class="language-tsx">// _document.tsx
import { Html, Head, Main, NextScript } from &#39;next/document&#39;;
import Script from &#39;next/script&#39;;

export default function Document() {
  return (
    &lt;Html lang=&quot;ko&quot;&gt;
      &lt;Head&gt;
        &lt;Script
          type=&quot;text/javascript&quot;
          strategy=&quot;beforeInteractive&quot;
          src={`//dapi.kakao.com/v2/maps/sdk.js?appkey=${process.env.NEXT_PUBLIC_KAKAO_API_KEY}&amp;libraries=services&amp;autoload=false`}
        &gt;&lt;/Script&gt;
      &lt;/Head&gt;
      &lt;body&gt;
        &lt;Main /&gt;
        &lt;NextScript /&gt;
      &lt;/body&gt;
    &lt;/Html&gt;
  );
}</code></pre>
<br />

<h3 id="지도-생성하기">지도 생성하기</h3>
<hr>
<p>이전에 Naver 지도 API로 작업했던 것 그대로 가져와 주었다. (<a href="https://velog.io/@a_in/naver-cloud-platform-map-api-2#initmap-%EC%A7%80%EB%8F%84-%EC%B4%88%EA%B8%B0%ED%99%94-%ED%95%98%EA%B8%B0">⚡️&quot;Naver 지도 사용하기&quot;를 정리한 블로그</a>)</p>
<ol>
<li>지도를 담아줄 div를 만들어 준다. (TailwindCSS 사용)
만약 지도가 생성되기 전에 로딩 애니메이션 또는 로딩 이미지 넣고 싶다면 div요소 안에 넣어준다. (로딩 애니메이션은 <a href="https://flowbite.com/docs/components/spinner/">Flowbite - Spinner</a> 참고)</li>
</ol>
<pre><code class="language-tsx">&lt;div id=&#39;map&#39; className=&#39;w-96 h-96&#39;&gt;
  // 사용자의 위치를 가져오기 전까지는 로딩을 보여준다.
  {typeof myLocation === &#39;string&#39; &amp;&amp; &lt;Loader /&gt;}
&lt;/div&gt;</code></pre>
<ol start="2">
<li>먼저 지도를 참조할 <code>useRef</code>와 사용자의 현재 위치좌표를 가져와 줄 <code>myLocation</code>을 가져와 준다. 이 둘은 지도를 생성할 시점을 감시하기 위해 useEffect의 의존배열에 넣어준다. 사용자의 현재 위치를 가져오는 방법은 <a href="https://velog.io/@silverbeen/Naver-Map-%EC%9E%90%EC%9C%A0%EB%A1%AD%EA%B2%8C-%ED%99%9C%EC%9A%A9%ED%95%98%EA%B8%B0#3-%EC%A7%80%EB%8F%84-%EC%83%9D%EC%84%B1-%EB%A1%9C%EC%A7%81-%EB%A7%8C%EB%93%A4%EA%B8%B0">이 블로그</a>를 참고하여 커스텀 훅으로 만들어주었다.</li>
</ol>
<pre><code class="language-tsx">// src/components/KakaoMap.tsx
import { useRef } from &#39;react&#39;
import useGetCurrentLocation from &#39;../../hooks/useGetCurrentLocation&#39;;

export const KakaoMap = () =&gt; {
  const mapRef = useRef&lt;HTMLElement | null&gt;(null);
  const myLocation = useGetCurrentLocation();

  ...

  useEffect(() =&gt; {
    // 지도 생성 함수
  }, [mapRef, myLocation]);
};
</code></pre>
<br />

<ol start="3">
<li><p>지도를 생성해준다. 그리고 script가 다 로드 된 후에 코드가 실행 될 수 있도록 load 메서드를 사용해준다. <a href="https://apis.map.kakao.com/web/documentation/#load_load"><code>kakao.maps.load(callback)</code></a></p>
<pre><code class="language-tsx">export const KakaoMap = () =&gt; {
...
const initMap = () =&gt; {
 // 사용자 위치를 받아왔다면 지도를 생성한다.
   if (typeof myLocation !== &#39;string&#39;) {
      // 지도를 표시할 요소 id
     const mapContainer = document.getElementById(&#39;map&#39;),
       // 지도를 생성할 때의 옵션 (중심좌표, 확대레벨 등등)
       mapOption = {
         center: new kakao.maps.LatLng(myLocation.latitude, myLocation.longitude),
         level: 3,
       };
     // kakao.maps.Map(container, options)를 사용하여 지도를 생성한다.
     const map = new kakao.maps.Map(mapContainer as HTMLElement, mapOption);
     (mapRef as MutableRefObject&lt;any&gt;).current = map;
   }
 };

 useEffect(() =&gt; {
   // 스크립트가 다 load된 후 initMap을 실행하여 지도를 생성해준다.
 kakao.maps.load(() =&gt; initMap());
}, [mapRef, myLocation]);
};</code></pre>
</li>
</ol>
<hr>
<br />

<h2 id="naver-지도와-비교하기">Naver 지도와 비교하기</h2>
<p>Naver 지도 API와 다른 점이 크게 없어 전환 작업이 그렇게 어렵진 않았다.</p>
<ol>
<li><strong>TypeScript와 사용하기</strong>
네이버 공식 문서에는 TypeScript와 같이 사용할 수 있도록 <code>NAVER 지도 API 타입 정의 파일</code>이 있는 반면, 카카오는 아직 없는 것으로 보인다. 
Kakao devTalk에도 올라온 질문의 답변을 보면 any를 사용하라고 한다.
<a href="https://devtalk.kakao.com/t/topic/113702" title="카카오 지도 타입스크립트 질문 - DevTalk"><img src="https://velog.velcdn.com/images/a_in/post/c364cea1-ade0-423b-86e1-7aba22da0cbc/image.png" alt="카카오 지도 타입스크립트 질문 - DevTalk"></a></li>
</ol>
<br />

<ol start="2">
<li><strong>script 태그 load하기</strong>
script태그에 발급받은 JavaScript key를 넣고 사용할 library를 넣는 것은 비슷하지만, Naver에서는 스크립트 로드가 끝나면 실행할 callback함수를 바로 적어주는 것과 다르게 Kakao는 autoload를 끄는 방식이다.
그래서 kakao API를 사용할 때 autoload를 false로 설정했다면 지도를 생성하는 함수를 kakao.maps.load 메서드로 실행해주어야 한다.</li>
</ol>
<p>Naver script:</p>
<pre><code>https://openapi.map.naver.com/openapi/v3/maps.js?ncpClientId=${process.env.MAP_KEY}&amp;submodules=geocoder&amp;callback=initMap</code></pre><p>Kakao script</p>
<pre><code>https://dapi.kakao.com/v2/maps/sdk.js?appkey=${process.env.KAKAO_API_KEY}&amp;libraries=services&amp;autoload=false</code></pre><br />

<ol start="3">
<li><strong>지도 생성 parameters</strong>
Naver API로 지도를 생성할때, 첫번째 parameter에 바로 문자열 형식으로 id를 주면 되지만, Kakao API를 사용한다면, DOM에 접근하여 id가 &#39;map&#39;인 요소를 찾은 후, parameter로 넘겨야 한다.</li>
</ol>
<pre><code class="language-ts">// naver
const map = new naver.maps.Map(&#39;map&#39;);
// kakao
const map = new kakao.maps.Map(document.getElementById(&#39;map&#39;), options);</code></pre>
<p><img src="https://velog.velcdn.com/images/a_in/post/161f64a6-7100-426b-8644-7c11f98c9a2b/image.png" alt="kakao.maps.Map 첫번째 parameter를 문자열로 주었을 때 발생하는 에러"></p>
<p>또한, kakao는 map을 꾸며주는 options가 없으면 에러가 발생한다.</p>
<br />

<h2 id="결과-화면">결과 화면</h2>
<p>성공적이라면 사용자의 현재 위치를 지도로 보여준다.</p>
<p><img src="https://velog.velcdn.com/images/a_in/post/0e1c6901-4db8-47dc-8366-39d11eff70f8/image.png" alt="카카오 지도 구현 성공 시 결과 화면"></p>
<h2 id="에러-모음">에러 모음</h2>
<h3 id="1-kakao-is-not-defined--kakaomapslatlng-is-not-defined--failed-to-execute-write-on-document">1. &quot;kakao is not defined&quot; / &quot;kakao.maps.LatLng is not defined&quot; / &quot;Failed to execute &#39;write&#39; on &#39;Document&#39;&quot;</h3>
<p><img src="https://velog.velcdn.com/images/a_in/post/a39a4c97-15ef-42e8-a6a0-90db1ef6916a/image.png" alt="kakao is not defined"></p>
<p>script태그가 다 로드 되기 전에 kakao API를 사용하는 코드가 먼저 실행 됐기 때문에 발생하는 에러이거나, 타입스크립트를 기반으로 하는 프로젝트라면 타입이 지정되지 않았기 때문이다.
<strong>해볼만한 시도 1:</strong>
script태그 src 제일 뒤에 <code>autoload=false</code> 추가하기.
<strong>해볼만한 시도 2:</strong>
script태그에 strategy 속성을 추가하여 <code>beforeInteractive</code>로 설정해준다.
<strong>해볼만한 시도 3:</strong>
전역에 kakao 선언하기</p>
<pre><code class="language-ts">declare global {
  interface Window {
    kakao: any;
  }
  const kakao: any;
}</code></pre>
<h3 id="2-빈-화면">2. 빈 화면</h3>
<p><img src="https://velog.velcdn.com/images/a_in/post/6f998b6d-8095-47e2-a92e-69f9700eb115/image.png" alt="지도가 렌더링 되지 않은 빈 화면"></p>
<p>만약 useEffect의 의존배열이 빈 배열이라면 지도가 렌더링 되지 않으므로 사용자 위치를 가져왔을 때 생성된 지도를 가져올 수 있도록 참조한 지도 ref와 사용자 위치를 의존배열에 넣어준다.</p>
<pre><code class="language-tsx">useEffect(() =&gt; {
    kakao.maps.load(() =&gt; initMap());
  }, [mapRef, myLocation]);</code></pre>
<hr>
<h2 id="참고">참고</h2>
<ul>
<li><a href="https://apis.map.kakao.com/web/">Kakao Maps API - Docs</a></li>
<li><a href="https://devtalk.kakao.com/t/api-api/36757?u=doji.doo">autoload=false에 관한 DevTalk</a></li>
<li><a href="https://github.com/JaeSeoKim/kakao.maps.d.ts">JaeSeoKim - kakao.maps.d.ts</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[지도에서 클릭한 지점 주소 가져오기 | - [네이버 Map's API - Reverse Geocoding] (Next.js/TypeScript)]]></title>
            <link>https://velog.io/@a_in/naver-cloud-platform-map-api-2</link>
            <guid>https://velog.io/@a_in/naver-cloud-platform-map-api-2</guid>
            <pubDate>Fri, 25 Nov 2022 17:40:26 GMT</pubDate>
            <description><![CDATA[<blockquote>
</blockquote>
<p>본 게시물에서는 네이버 클라우드 플랫폼 Naver Map&#39;s Enterprise API를 사용하여 지도에서 주소를 가져올 수 있는 <strong>Reverse Geocoding</strong> 구현 방법을 정리하였습니다. <a href="https://navermaps.github.io/maps.js.ncp/docs/tutorial-3-geocoder-geocoding.example.html">참고문서</a>
<strong>사용 스택</strong>: Next.js, TypeScript, tailwindCSS</p>
<blockquote>
</blockquote>
<p>❗️<span style='color:red;'>2023년 1월부터 네이버 클라우드 플랫폼 Maps 서비스 중 Reverse Geocoding을 포함하여 일부 서비스에 <strong>가격변동</strong>있음</span> <a href="https://www.ncloud.com/support/notice/all/1424">공지링크</a></p>
<h2 id="서론">서론</h2>
<p>지도 API를 사용하기로 마음먹은 후부터 API를 이해하고 적용하기까지 시간이 꽤 걸렸다. 네이버 지도 문서를 옛날 것으로 보고 있다거나, 코드를 그냥 가져다 붙혀넣는 둥... 그리고 네이버 맵의 <strong>Reverse Geocoding</strong>에 대한 API 사용법과 예제가 네이버 클라우드 플랫폼 기술문서에 나와 있긴 하지만 코드에 대한 설명이 한 번에 알아보기 쉽지 않아 글로 정리해보려 한다.</p>
<p>&quot;API를 이해하고 적용하기까지 <strong>순서</strong>가 있다면 더 접하기 쉬웠을 텐데...&quot; 라고 생각하며 정리해보았다.</p>
<h2 id="1-api-등록하기">1. API 등록하기</h2>
<p>지도를 사용하기 위한 API는 NAVER Developers(네이버 로그인, 검색 등등)가 아니라 <a href="https://www.ncloud.com/">NAVER Cloud Platform</a>에서 등록해야 한다.</p>
<p><img src="https://velog.velcdn.com/images/a_in/post/c4f2f364-ed67-4f06-9e95-cb47ec3f0a31/image.png" alt="Naver Developers와 Naver Cloud Platform"></p>
<p>등록한 후 받은 <strong>Client Id</strong>를 가져와 스크립트를 호출해야 한다. 이 Client Id는 .env파일에 숨겨서 환경변수로 만들어준다.</p>
<pre><code class="language-js">// .env
CLIENT_ID=myclientid</code></pre>
<p>(등록하는 방법은 메인이 아니기 때문에 따로 정리해두었다. → <a href="https://velog.io/@a_in/naver-cloud-platform-map-api-1">API 등록하기 정리글</a>)</p>
<p>등록하고 Client Id까지 가져왔다면 API에 대해서 조금 알아가야 할 차례이다. 내가 구현할 코드를 다른 사람이 이미 구현했다고 해서 그 코드를 무턱대고 복사 붙여 넣기를 했다간 더 많은 시간을 낭비할 수 있다. </p>
<h2 id="2-api-이해하기">2. API 이해하기</h2>
<p>지도 API는 <strong>생성하고</strong> - <strong>설정(커스텀)해주고</strong> - <strong>뿌려주기</strong>의 단계를 거친다. 지도를 클릭해서 어떠한 이벤트를 발생시키려면 자바스크립트와 똑같이 Event Listener를 걸어서 그 안에 함수를 넣어주면 된다. 생김새만 다르고 움직이는 건 우리가 해왔던 것과 비슷하다.</p>
<p>이제부터 Reverse Geocoding을 적용하면서 사용한 API의 메서드, 이벤트 그리고 클래스 등을 소개해보겠다.</p>
<h3 id="-navermapsmap"># naver.maps.Map()</h3>
<blockquote>
</blockquote>
<p><strong>Map</strong> 클래스는 애플리케이션에서 지도 인스턴스를 정의합니다. 이 객체를 생성함으로써 개발자는 지정한 DOM 요소에 지도를 삽입할 수 있습니다.
<a href="https://navermaps.github.io/maps.js.ncp/docs/tutorial-Map.html">지도 생성 및 기본 동작</a></p>
<pre><code class="language-tsx">new naver.maps.Map(mapDiv, mapOptions)</code></pre>
<p><strong>설명</strong>
new 키워드를 사용하여 Map을 생성한다. 매개변수는 두 가지를 받는데 <strong>첫 번째</strong>에는 지도를 삽입할 HTML 요소의 id를 주고, <strong>두 번째</strong>에는 지도를 어떻게 만들지 옵션을 주어야 한다.</p>
<h4 id="example">Example</h4>
<pre><code class="language-tsx">// MapSearch.tsx
let nmap = new naver.maps.Map(&#39;map&#39;, {
  center: new naver.maps.LatLng(myLocation.latitude, myLocation.longitude),
  zoom: 9,
  zoomControl: true,
  size: { width: 500, height: 500 },
});

return (
  &lt;div id=&#39;map&#39;&gt;&lt;/div&gt;
);</code></pre>
<ul>
<li><strong>center</strong>: 지도의 어느 곳을 중심으로 둘 것인지 좌표로 전달한다.</li>
<li><strong>zoom</strong>: 지도의 초기 줌 레벨. 설정하지 않으면 기본값은 11이다.</li>
<li><strong>zoomControl</strong>: 확대 축소 컨트롤 바를 표시할 것인지 boolean으로 전달한다.</li>
<li><strong>size</strong>: 지도의 사이즈를 전달해준다. 만약 전달하지 않는다면 지도의 DOM 요소 CSS에 따라 결정되기 때문에 사이는 이 옵션에서 주거나 CSS 중 하나에서 전달해줘야 한다.</li>
</ul>
<p>더 알아보기 - <a href="https://navermaps.github.io/maps.js.ncp/docs/naver.maps.html#.MapOptions">naver.maps.Map - MapOptions</a></p>
<br />

<h3 id="-navermapslatlng"># naver.maps.LatLng()</h3>
<blockquote>
</blockquote>
<p><strong>LatLng</strong> 클래스는 위/경도 좌표를 정의합니다.
<a href="https://navermaps.github.io/maps.js.ncp/docs/naver.maps.LatLng.html">Class: naver.maps.LatLng</a></p>
<pre><code class="language-tsx">new naver.maps.LatLng(lat: number, lng: number)</code></pre>
<h4 id="설명">설명</h4>
<p>숫자로 된 위도와 경도를 전달해주면 좌표를 <strong>객체</strong>로 반환한다.
위에서 지도를 생성할 때, 지도의 중심을 정하는 center 옵션에 이 클래스로 좌표 객체를 만들어 전달해주는 것이다.</p>
<h4 id="example-1">Example</h4>
<pre><code class="language-tsx">new naver.maps.LatLng(37.5666103, 126.9783882);</code></pre>
<br />

<h3 id="-navermapseventaddlistener"># naver.maps.Event.addListener()</h3>
<blockquote>
</blockquote>
<p>NAVER 지도 API의 이벤트 시스템을 구현합니다. 대상 객체에서 이벤트 알림을 받아 핸들러를 호출하는 리스너를 등록합니다.
<a href="https://navermaps.github.io/maps.js.ncp/docs/naver.maps.Event.html">StaticObjects: Event</a></p>
<pre><code class="language-tsx">naver.maps.Event.addListener(target, eventName, listener);</code></pre>
<h4 id="설명-1">설명</h4>
<p>자바스크립트의 addEventListener와 같이 지도에서 일어나는 이벤트를 등록하여 줄 때 사용한다. Event에는 addListener 메서드를 제외하고도 clearListener, hasListner 등 이벤트에 관한 메서드들이 많다.
그중에서도 addListener 메서드는 지도 위에서 일어나는 이벤트를 감지하여 함수를 실행한다.</p>
<h4 id="example-2">Example</h4>
<pre><code class="language-tsx">naver.maps.Event.addListener(nmap, &#39;click&#39;, function (e) {
  alert(e.coord); // 클릭한 지점 좌표
});</code></pre>
<ul>
<li><strong>e.coord</strong>: listener에서 받아온 event 객체에서 받아올 수 있는 좌표. 지도에서 클릭한 지점의 좌표이다.</li>
</ul>
<p>더 알아보기 -<a href="https://navermaps.github.io/maps.js.ncp/docs/tutorial-UI-Event.html">UI 이벤트</a></p>
<br />


<h3 id="-navermapsinfowindow"># naver.maps.InfoWindow()</h3>
<blockquote>
</blockquote>
<p><strong>InfoWindow</strong> 클래스는 지도 위에 올리는 정보 창을 정의합니다.
<a href="https://navermaps.github.io/maps.js.ncp/docs/naver.maps.InfoWindow.html">Class: naver.maps.InfoWindow</a></p>
<pre><code class="language-tsx">new naver.maps.InfoWindow(options);</code></pre>
<p><strong>설명</strong>
new 키워드를 사용하여 정보 창을 생성한다.
아래 사진처럼 주소 말고도 우리가 입력하고 싶은 텍스트를 넣어서 정보창을 띄울 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/a_in/post/8921ee60-2822-4de8-8249-a0c08d98127e/image.png" alt="정보 창 예시"></p>
<h4 id="example-3">Example</h4>
<pre><code class="language-tsx">new naver.maps.InfoWindow({ content: &#39;&#39;, borderWidth: 0 });

infoWindow.setContent(
  [&#39;&lt;div style=&quot;padding:10px;min-width:200px;line-height:150%;&gt;&#39;,
   &#39;서울특별시 송파구 잠실동&#39;,
   &#39;&lt;/div&gt;&#39;
  ].join(&#39;&#39;),
);
infoWindow.open(nmap, latlng);</code></pre>
<ul>
<li><strong>content</strong>: 정보 창에 들어갈 string 요소이다.</li>
<li><strong>borderWidth</strong>: 정보 창의 border 굵기. borderWidth 말고도 maxWidth, anchorSkew 등 정보 창을 꾸미는 옵션들이 있다.</li>
<li><strong>setContent()</strong>: 정보 창의 content를 생성하는 메서드이다. content 옵션이 처음 infoWindow를 생성할 때 필수 요소이기 때문에 처음에 빈문자열로 초기화를 하고 나중에 setContent를 해도 된다.</li>
<li><strong>open(map, anchor?)</strong>: 정보 창이 열렸을 때 이벤트가 발생한다. 첫 번째 매개변수로 지도를 받고, 두 번째로 정보 창을 띄울 위치를 받는다. 위치는 좌표가 될 수도 있고, 마커를 생성하였다면 마커를 전달할 수도 있다.</li>
</ul>
<br />

<h3 id="-navermapsservicegeocode"># naver.maps.Service.geocode</h3>
<blockquote>
</blockquote>
<p>특정 주소의 좌표를 반환하는 <strong>geocode API</strong>를 호출합니다.
<a href="https://navermaps.github.io/maps.js.ncp/docs/naver.maps.Service.html#geocode__anchor">geocode</a></p>
<pre><code class="language-tsx">naver.maps.Service.geocode(options, callback)</code></pre>
<p>먼저, naver.maps.Service는 객체는 NAVER 지도 API v3을 이용해 호출할 수 있는 서버 API들을 메서드로 제공한다. 그 메서드 중 하나인 geocode 메서드는 주소를 전달해주면 좌표를 반환해주는 메서드이다.
첫 번째 매개변수 options 중 하나인 <strong>query</strong>에 주소를 전달해주면 callback 함수의 매개변수로 status와 response를 받는데, response 객체 안에서 좌표와 내가 검색한 주소의 전체 주소를 받아 볼 수 있다.</p>
<h4 id="example-4">Example</h4>
<pre><code class="language-tsx">naver.maps.Service.geocode(
  {
    query: address, // 주소 전달
  },
  function (status, response) {
    console.log(response); // 응답 객체
  }
);</code></pre>
<p>response 응답객체↓
<img src="https://velog.velcdn.com/images/a_in/post/588e595d-cc01-4cb9-ae90-13d206c99ddc/image.png" alt="geocode response 응답 객체"></p>
<br />

<h3 id="-navermapsservicereversegeocode"># naver.maps.Service.reverseGeocode</h3>
<blockquote>
</blockquote>
<p>특정 좌표에 해당하는 주소를 반환하는 reversegeocode API를 호출합니다.</p>
<pre><code class="language-tsx">naver.maps.Service.reverseGeocode(options, callback)</code></pre>
<p>geocode와 동작하는 방식은 같지만 <strong>reverse</strong> 단어 뜻 그대로 geocode의 반대로, 좌표를 받아 주소를 받아오는 방식이다. options 중 coords에 좌표를 전달해주면 response 객체에 주소가 결과로 들어온다. 우리는 지도 클릭 이벤트 안에 이 함수를 넣어서 주소를 가져오고 infoWindow로 띄워줄 예정이다.</p>
<h4 id="example-5">Example</h4>
<pre><code class="language-tsx">naver.maps.Service.reverseGeocode(
    {
      coords: latlng,
      orders: [naver.maps.Service.OrderType.ADDR, naver.maps.Service.OrderType.ROAD_ADDR].join(&#39;,&#39;),
    },
    function (status, response) {
      console.log(response);
    }
)</code></pre>
<ul>
<li><strong>coords: latlng</strong> : latlng은 위에서 언급한 naver.maps.LatLng 메서드로 생성하거나, naver.maps.Event.addListener에서 받아온 event 객체에서 event.coord를 넘겨주면 된다.</li>
<li><strong>orders</strong>: 좌표에서 어떤 주소로 변환할 것인지 전달한다. 기본값은 법정동과 도로명 주소이다.</li>
<li><strong>naver.maps.Service.OrderType.ADDR</strong>: 행정동</li>
<li><strong>naver.maps.Service.OrderType.ROAD_ADDR</strong>: 지번 주소</li>
</ul>
<br />

<h3 id="더보기">더보기</h3>
<p>본 게시물에서는 <strong>지도에서 클릭한 지점의 주소를 정보 창으로 띄우는 것</strong>과 , <strong>검색창에 주소를 검색하여 그 주소로 이동하여 정보 창을 띄우는 것</strong>을 정리하였기 때문에 API를 전부 자세하게 기재하진 않았다. 다른 정보는 네이버 기술문서에서 참고하면 좋다.</p>
<blockquote>
</blockquote>
<p><a href="https://navermaps.github.io/maps.js.ncp/docs/index.html">Hello, Naver Maps API - NAVER 지도 API v3</a></p>
<br />

<hr>
<br />

<h2 id="3-api-적용하기">3. API 적용하기</h2>
<p>지금부터 API를 적용해 보겠다. 구현할 내용을 다시 한번 정리해보자면,</p>
<p><strong>클릭해서 주소 가져오기</strong></p>
<ul>
<li>지도 클릭 시 좌표 가져오기 - 좌표를 주소로 변환 - 그 주소를 정보 창에 띄우기</li>
</ul>
<p><strong>검색해서 주소 가져오기</strong></p>
<ul>
<li>검색창에 주소 검색 - 주소를 좌표로 변환 - 그 좌표로 지도가 움직이도록 구현 - 해당 주소에 정보 창 띄우기</li>
</ul>
<p>총 두 가지를 차례대로 구현할 것이다.
API를 사용하여 구현하기 전에 세팅부터 시작해보겠다.</p>
<h3 id="typescript와-같이-사용하기">TypeScript와 같이 사용하기</h3>
<p>참고문서 - <a href="https://navermaps.github.io/maps.js.ncp/docs/tutorial-3-Using-TypeScript.html">TypeScript 사용</a></p>
<pre><code class="language-bash">npm i -D @types/navermaps</code></pre>
<p>Naver Map API를 타입스크립트와 같이 쓰고 싶다면 NPM 패키지를 설치하여 <strong>NAVER 지도 API 타입 정의 파일</strong>을 가져올 수 있다.</p>
<pre><code class="language-ts">const latlng: naver.maps.LatLng = new naver.maps.LatLng(latitude, longitude);</code></pre>
<p>이렇게 패키지가 제공하는 타입을 지정할 수 있다. (any 범벅을 피할 수 있다.)</p>
<h3 id="script-불러오기">script 불러오기</h3>
<p>참고문서 - <a href="https://navermaps.github.io/maps.js.ncp/docs/tutorial-2-Getting-Started.html">시작하기</a></p>
<pre><code>&lt;script type=&quot;text/javascript&quot; src=&quot;https://openapi.map.naver.com/openapi/v3/maps.js?ncpClientId=YOUR_CLIENT_ID&quot;&gt;&lt;/script&gt;</code></pre><ul>
<li><p>해당 URL을 script태그에 넣어서 불러와 준다.</p>
</li>
<li><p><code>YOUR_CLIENT_ID</code> 부분에 Application 등록 후 받은 Client Id를 넣어준다.
여기서 주의할 점은, 네이버 map의 옛날 버전이 아직 존재하여 헷갈릴 수 있다. <strong>ncpClientId=~</strong>로 넣어줘야 할 부분을 <strong>cliendId=~</strong>로 넣으면 안 되기 때문에 에러가 뜬다면 한 번쯤 확인해야 할 부분이다.</p>
</li>
<li><p>Next.js로 구현하고 있다면 _document.tsx 파일에서 스크립트를 불러와 준다.</p>
</li>
</ul>
<pre><code class="language-tsx">// pages/_document.tsx
mport { Html, Head, Main, NextScript } from &#39;next/document&#39;;
import Script from &#39;next/script&#39;;

export default function Document() {
  return (
    &lt;Html lang=&quot;ko&quot;&gt;
      &lt;Head&gt;
        &lt;Script
          strategy=&quot;beforeInteractive&quot;
          type=&quot;text/javascript&quot;
          src={`https://openapi.map.naver.com/openapi/v3/maps.js?ncpClientId=${process.env.NEXT_PUBLIC_MAP_KEY}&amp;submodules=geocoder&amp;callback=initMap`}
        &gt;&lt;/Script&gt;
      &lt;/Head&gt;
      &lt;body&gt;
        &lt;Main /&gt;
        &lt;NextScript /&gt;
      &lt;/body&gt;
    &lt;/Html&gt;
  );
}</code></pre>
<p>지도를 가져올 준비를 마쳤다면 이제부터 지도를 만들어 보자!</p>
<br />

<h3 id="현재-사용자-위치-가져오기">현재 사용자 위치 가져오기</h3>
<p><a href="https://velog.io/@silverbeen/Naver-Map-%EC%9E%90%EC%9C%A0%EB%A1%AD%EA%B2%8C-%ED%99%9C%EC%9A%A9%ED%95%98%EA%B8%B0">참고자료 - Naver Map 자유롭게 활용하기 <em>by silverbeen.log</em></a>
지도를 띄우고 초기 좌표를 사용자의 현재위치로 설정하려면 현재위치를 받아와야 한다. 여기서는 사용자의 현재위치를 여러 곳에서 불러와야 해서 <strong>Custom Hook</strong>으로 만들어 주었다.</p>
<ul>
<li>useState로 상태 값 만들기
사용자의 위치를 myLocation이라는 상태 값에 넣을 예정이다. 여기서 초깃값은 빈문자열로, 나중에는 좌표 위/경도를 가져와야 해서 타입은 미리 정해준다. 이 타입은 다른 곳에서도 많이 쓰이기 때문에 때에 맞게 import 할 수 있도록 src/lib/types.ts 파일에 따로 선언해두었다.</li>
</ul>
<pre><code class="language-tsx">// src/lib/types.ts
type Coord = { latitude: number; longitude: number } | &#39;&#39;;
// src/components/useGetCurrentLocation.ts
import { Coord } from &#39;../lib/types&#39;;

const useGetCurrentLocation = () =&gt; {
const [myLocation, setMyLocation] = useState&lt;Coord&gt;(&#39;&#39;);
}</code></pre>
<ul>
<li><p>navigator.geolocation.getCurrentPosition()
자바스크립트 내장 API로 사용자 위치를 가져온다. 성공, 실패 시에 상태 값을 변경해준다.</p>
<pre><code class="language-tsx">...
const useGetCurrentLocation = () =&gt; {
const [myLocation, setMyLocation] = useState&lt;Coord&gt;(&#39;&#39;);
useEffect(() =&gt; {
  if (navigator.geolocation) {
    // 위치 가져오기
    navigator.geolocation.getCurrentPosition(success, error);
  }

  // 성공했다면 상태 값에 사용자 현재 위치 좌표 저장
  // position은 nested Object이기 때문에 [key: string]: any 타입으로 지정해 줌.
  const success = (position: { [key: string]: any }) =&gt; {
    setMyLocation({
      latitude: position.coords.latitude,
      longitude: position.coords.longitude,
    });
  }

  // 실패했다면 상태 값에 Default 좌표 저장
  const error = () =&gt; {
    setMyLocation({ latitude: 37.3595316, longitude: 127.1052133 });
  }
}, []);

// 다른 곳에서 사용할 수 있도록 myLocation을 반환해준다.
return myLocation;
};</code></pre>
</li>
</ul>
<h3 id="initmap-지도-초기화-하기">initMap (지도 초기화 하기)</h3>
<ul>
<li><p>우리는 initMap이라는 함수 안에서 <strong>지도를 생성하고 clickEvent</strong>를 걸어줄 것이다. clickEvent를 초기에 걸어주지 않으면 이후의 clickEvent도 작동 하지 않는다. 지도는 initMap 안에서 생성하면 다른 함수에서 인자를 전달할 때 사용하지 못하기 때문에 최상위에 먼저 선언을 해준다.</p>
</li>
<li><p>지도를 생성하면 useRef를 사용하여 지도를 참조하는 mapRef의 current에 넣어주고, 이 mapRef와 사용자의 현재 위치인 myLocation을 useEffect 의존배열 안에 넣어준다. 지도가 생성되면 </p>
</li>
</ul>
<pre><code class="language-tsx">// mapSearch.tsx
const mapRef = useRef&lt;HTMLElement | null&gt;(null);
const myLocation = useGetCurrentLocation();
let nmap: naver.maps.Map;

const initMap = () =&gt; {
  if (typeof myLocation !== &#39;string&#39;) {
    nmap = new naver.maps.Map(&#39;map&#39;, {
      center: new naver.maps.LatLng(myLocation.latitude, myLocation.longitude),
      zoomControl: true,
      size: { width: 500, height: 500 },
  });

    naver.maps.Event.addListener(nmap, &#39;click&#39;, function (e) {
      // 좌표에서 주소로 변환하는 함수
      searchCoordinateToAddress(e.coord, nmap as naver.maps.Map);
    });
  }  
};

useEffect(() =&gt; {
  initMap();
}, [mapRef, myLocation])</code></pre>
<h3 id="reversegeocoding-클릭해서-주소-가져오기">reverseGeocoding (클릭해서 주소 가져오기)</h3>
<p>지도 클릭 시 정보 창안에 주소가 뜨도록, clickEvent와 infoWindow API를 사용하는 <strong>searchCoordinateToAddress</strong> 함수를 만든다.</p>
<ul>
<li>앞서 <strong>API 이해하기</strong>에서 언급했던 reverseGeocode 메서드를 사용하여 클릭한 지점의 좌표를 주소로 바꿔준다. initMap 함수에서 전달해주었던 좌표(e.coord)는 options에서 필수로 전달해줘야 하는 <code>coords</code>에 넣어준다.</li>
</ul>
<p>(여기서 도로명 주소와 지번 주소 둘 다 넣고 싶다면 반복문으로 address 안에 넣으면 됨.)</p>
<pre><code class="language-tsx">// src/lib/searchCoordinateToAddress.ts
export const searchCoordinateToAddress = (latlng: naver.maps.LatLng, nmap: naver.maps.Map) =&gt; {
  naver.maps.Service.reverseGeocode(
    // options
    {
      coords: latlng,
      orders: [naver.maps.Service.OrderType.ADDR, naver.maps.Service.OrderType.ROAD_ADDR].join(&#39;,&#39;),
    },
    // callback
    function (
      status: naver.maps.Service.Status,
      response: naver.maps.Service.ReverseGeocodeResponse,
    ) {
      // 응답을 못 받으면 &#39;Something went wrong&#39; alert 띄우기
      if (status !== naver.maps.Service.Status.OK) {
        return alert(&#39;Something went wrong!&#39;);
      }

      // 도로명 주소가 있다면 도로명 주소를, 없다면 지번 주소를 address 변수에 담는다.
      const address = response.v2.address.roadAddress
      ? response.v2.address.roadAddress
      : response.v2.address.jibunAddress;
    },
  );
};</code></pre>
<br />

<ul>
<li>주소를 가져온 후 정보 창을 띄워주기 위해 infoWindow를 생성하고 열어준다.</li>
</ul>
<pre><code class="language-tsx">export const searchCoordinateToAddress = (latlng: naver.maps.LatLng, nmap: naver.maps.Map) =&gt; {
  // infoWindow 생성
  const infoWindow = new naver.maps.InfoWindow({ content: &#39;&#39;, borderWidth: 0 });
  naver.maps.Service.reverseGeocode(
    {
      coords: latlng,
      orders: [naver.maps.Service.OrderType.ADDR, naver.maps.Service.OrderType.ROAD_ADDR].join(&#39;,&#39;),
    },
    function (
      status: naver.maps.Service.Status,
      response: naver.maps.Service.ReverseGeocodeResponse,
    ) {
      if (status !== naver.maps.Service.Status.OK) {
        return alert(&#39;Something went wrong!&#39;);
      }

      const address = response.v2.address.roadAddress
        ? response.v2.address.roadAddress
        : response.v2.address.jibunAddress;

        // infoWindow 안에 넣어줄 html을 setContent 메서드에 넣어준다.
      infoWindow.setContent(
        [&#39;&lt;div style=&quot;padding:10px;min-width:200px;line-height:150%;&quot;&gt;&#39;, address, &#39;&lt;/div&gt;&#39;].join(&#39;&#39;)
      );

        // open 메서드에 지도와 좌표를 전달하여 정보 창을 열어준다.
      infoWindow.open(nmap, latlng);
    },
  );
};</code></pre>
<br />

<h3 id="geocoding-검색으로-주소-가져오기-및-이동하기">geocoding (검색으로 주소 가져오기 및 이동하기)</h3>
<p>검색 시 주소로 좌표를 가져오는 함수를 만들기 전에 input과 이벤트를 걸어준다.</p>
<h4 id="1-input-만들기">1. input 만들기</h4>
<pre><code class="language-tsx">&lt;form onSubmit={handleSubmit}&gt;
  &lt;input type=&quot;text&quot; placeholder=&quot;(예:잠실)&quot; /&gt;
  &lt;button onClick={handleClick} type=&quot;button&quot;&gt;
    주소 검색
  &lt;/button&gt;
&lt;/form&gt;</code></pre>
<ul>
<li><strong>form</strong>에는 <strong>onSubmit</strong> 이벤트를 걸어주고, <strong>button</strong>에는 <strong>onClick</strong> 이벤트를 걸어준다.
이때, button의 type은 기본값이 submit이어서 <strong>type을 button으로 해주지 않으면 submit 이벤트가 두 번 일어나기 때문에</strong> type은 button으로 설정해둔다.<em>(예를 들어 사용자가 입력값에 무언가를 잘못 입력했을 때 &#39;주소를 정확히 입력해주세요&#39;라는 문구를 띄운다면, submit 이벤트가 두번 실행되어 이 문구가 두 번 뜬다.)</em></li>
</ul>
<p>input에 입력값을 넣고 엔터를 누르면 form이 submit 되어 onSubmit 함수가 실행되니 onSubmit 실행 함수부터 만들어보자.</p>
<blockquote>
</blockquote>
<pre><code class="language-tsx">const handleSubmit = (e: React.FormEvent&lt;HTMLFormElement&gt;) =&gt; {
  e.preventDefault();
  const inputValue = (e.target as HTMLElement).querySelector(&#39;input&#39;)?.value as string;
  // 주소에서 좌표로 바꿔주는 함수 ↓
  searchAddressToCoordinate(inputValue, nmap);
};</code></pre>
<ul>
<li>form이 submit 되면 페이지가 자동으로 새로고침 되는 기본값이 있어서, 마음에 들지 않는다면 e.preventDefault로 새로고침을 막아준다.</li>
<li>사용자의 입력값을 event 객체에서 가져온 다음 주소에서 좌표로 바꿔주는 함수 <strong>searchAddressToCoordinate</strong>의 첫 번째 인자로, 지도는 두 번째 인자로 전달해준다.</li>
</ul>
<br />

<ul>
<li><strong>button</strong>에 걸린 onClick 이벤트도 비슷한 구조로, 사용자의 입력값을 가져와 searchAddressToCoordinate 함수에 전달해준다.</li>
</ul>
<blockquote>
</blockquote>
<pre><code class="language-tsx">  const handleClick = (e: React.MouseEvent&lt;HTMLElement&gt;) =&gt; {
    const inputValue = ((e.target as HTMLElement).parentNode as HTMLElement).querySelector(&#39;input&#39;)
      ?.value as string;
    searchAddressToCoordinate(inputValue, nmap);
  };</code></pre>
<p>이렇게 간단하면 좋겠지만 input에는 함정이 있다. 바로 빈값에서 엔터를 치면 submit이 된다는 것. <em><strong>참고 ↓</strong></em></p>
<blockquote>
</blockquote>
<p><em>When there is only one single-line text input field in a form, the user agent should accept Enter in that field as a request to submit the form.</em>
 -<a href="https://www.w3.org/MarkUp/html-spec/html-spec_8.html#SEC8.2">HTML 2.0 specification (Section 8.2) - Form Submission</a></p>
<p>그래서 빈 값일 때도 submit 돼서 새로고침 되는 게 마음에 안 든다면 input을 form 안에 두 개 만들어서 하나는 숨기거나, input의 event.code가 &#39;Enter&#39;일 때 submit을 막아야 한다.</p>
<pre><code class="language-tsx">const handleKeydown = (e: React.KeyboardEvent&lt;HTMLInputElement&gt;) =&gt; {
  if (e.code === &#39;Enter&#39; &amp;&amp; (e.target as HTMLInputElement).value === &#39;&#39;) {
      e.preventDefault();
  }
}

&lt;form onSubmit={handleSubmit}&gt;
  &lt;input onKeyDown={handleKeydown} type=&quot;text&quot; placeholder=&quot;(예:잠실)&quot; /&gt;
  &lt;button onClick={handleClick} type=&quot;button&quot;&gt;
    주소 검색
  &lt;/button&gt;
&lt;/form&gt;</code></pre>
<br />

<h4 id="2-searchaddresstocoordinate주소-검색">2. searchAddressToCoordinate(주소 검색)</h4>
<p>주소로 장소를 검색하는 방법은 간단하다. Reverse Geocoding과 비슷하다. 사용자가 입력한 입력값을 넘겨주고, 지도 이동해주는 점만 다르다.</p>
<ul>
<li>사용자의 입력값(address)과 지도를 똑같이 넘겨받는다.</li>
<li>geocode API options에 query로 사용자가 입력한 주소를 넘겨준다.</li>
</ul>
<pre><code class="language-tsx">export function searchAddressToCoordinate(
  address: string,
  nmap: naver.maps.Map,
) {
  naver.maps.Service.geocode(
    {
      query: address,
    },
    function (status: naver.maps.Service.Status, response: naver.maps.Service.GeocodeResponse) {
      // 응답을 못 받으면 &#39;Something went wrong&#39; alert 띄우기
      if (status === naver.maps.Service.Status.ERROR) {
        return alert(&#39;Something went Wrong!&#39;);
      }
    }
  );
}</code></pre>
<pre><code class="language-tsx">naver.maps.Service.geocode(
    {
      query: address,
    },
    function (status: naver.maps.Service.Status, response: naver.maps.Service.GeocodeResponse) {
      if (status === naver.maps.Service.Status.ERROR) {
        return alert(&#39;Something went Wrong!&#39;);
      }

      // 주소를 도로명으로 찾을 때, 건물명까지 입력하지 않으면 응답받지 못한다.
      if (response.v2.meta.totalCount === 0) {
        return alert(&#39;no results&#39;);
      }

      const item = response.v2.addresses[0]; // 찾은 주소 정보
      const point = new naver.maps.Point(Number(item.x), Number(item.y)); // 지도에서 이동할 좌표
      const address = item.roadAddress ? item.roadAddress : item.jibunAddress;

      const infoWindow = new naver.maps.InfoWindow({
        content: [&#39;&lt;div style=&quot;padding:10px;&quot;&gt;&lt;h4&gt;&#39; + address + &#39;&lt;/h4&gt;&lt;/div&gt;&#39;].join(&#39;&#39;),
        borderWidth: 0,
      });

      // 검색한 주소를 중심으로 지도 움직이기
      nmap.setCenter(point);
      infoWindow.open(nmap, point);
    },
  );</code></pre>
<ul>
<li>참고로, <code>infoWindow.open(map, anchor)</code> 메서드의 두 번째 인자 <code>anchor</code> 자리에 <a href="https://navermaps.github.io/maps.js.ncp/docs/naver.maps.LatLng.html">LatLng</a> 또는 <a href="https://navermaps.github.io/maps.js.ncp/docs/naver.maps.Point.html">Point</a> 객체가 올 수 있다.<blockquote>
</blockquote>
<em>anchor에 LatLng 또는 Point 객체로 지도 좌표를 설정하면 해당 좌표를 가리키도록 정보 창을 위치시킵니다.</em> <a href="https://navermaps.github.io/maps.js.ncp/docs/tutorial-3-InfoWindow.html">정보 창 Tutorials</a></li>
</ul>
<br />

<hr>
<h2 id="결과물">결과물</h2>
<ul>
<li><p>도로명 + 건물명 형식으로 입력하지 않았을 때 화면
<img src="https://velog.velcdn.com/images/a_in/post/03464114-aee4-439a-bd7a-f2ca6c497897/image.png" alt="주소 잘못 찾았을 때 화면"></p>
</li>
<li><p>알맞은 형식으로 검색했을 시 화면
<img src="https://velog.velcdn.com/images/a_in/post/487bfde4-3e39-4c31-b3fb-b996cc0b68ef/image.png" alt="주소 검색 시 지도에 표시"></p>
</li>
<li><p>지도를 클릭했을 시 화면
<img src="https://velog.velcdn.com/images/a_in/post/e58fe26b-6cb0-4510-a0f9-670ea95da06c/image.png" alt="지도를 클릭 했을 시 화면"></p>
</li>
</ul>
<hr>
<br />

<h2 id="에러-모음">에러 모음</h2>
<h3 id="1-naver-is-not-defined--window-is-not-defined--cannot-read-properties-of-null-reading-map">1. naver is not defined / window is not defined / Cannot read properties of null (reading &#39;Map&#39;)</h3>
<p><strong>이슈:</strong> naver, window, Map을 못 찾는 경우 </p>
<p><img src="https://velog.velcdn.com/images/a_in/post/7ec711af-64bd-46cd-a4d4-14afd369a552/image.png" alt="Cannot find Map"></p>
<p><strong>해결:</strong> 네이버 지도 스크립트를 로드 하기 전에 API를 가져와서 발생하는 경우가 많다.
만약 지도를 비동기 방식으로 로드했다면 스크립트 뒤에 <code>&amp;callback=초기함수이름</code> 을 넣어 지도를 생성할 수 있다. </p>
<pre><code>src=`https://openapi.map.naver.com/openapi/v3/maps.js?ncpClientId=${process.env.NEXT_PUBLIC_MAP_KEY}&amp;submodules=geocoder&amp;callback=initMap`</code></pre><p>참고 - <a href="https://navermaps.github.io/maps.js.ncp/docs/tutorial-2-Getting-Started.html">NAVER Cloud Platform NAVER Map&#39;s Enterprise API - Hello, World</a></p>
<h3 id="2-네이버-지도-open-api-인증이-실패했습니다">2. 네이버 지도 Open API 인증이 실패했습니다.</h3>
<p><strong>이슈:</strong> 지도가 렌더링되는 페이지에 진입하여 현재위치 설정 후 지도가 뜨지 않고 인증 실패 화면이 뜬다. 새로고침 하면 지도가 또 제대로 뜬다.</p>
<p><img src="https://velog.velcdn.com/images/a_in/post/2ddeea4f-2384-4451-9eae-27638358c4ce/image.png" alt="네이버 지도 Open API 인증이 실패했습니다."></p>
<p><strong>해결:</strong> Application을 처음 등록할 때 <strong>Web 서비스 URL</strong>은 메인 도메인으로 입력해준다. 예를 들어 <code>http://localhost:3000</code>이면 그대로 <code>http://localhost:3000</code>로 등록해준다. 다른 페이지에서 지도를 띄운다고 <code>http://localhost:3000/map</code>이런식으로 등록해주면 위 사진처럼 인증 실패 화면이 뜬다.
<img src="https://velog.velcdn.com/images/a_in/post/c7aa53fe-c1fb-4bfd-a411-e6feb013f93e/image.png" alt="네이버 맵 서비스 환경 등록 - Web 서비스 URL 등록화면"></p>
<p>또한, script 내의 URL을 <strong>clientId</strong>가 들어간 옛날 API URL인 &quot;src=<code>https://openapi.map.naver.com/openapi/v3/maps.js?clientId=${MAP_KEY}</code>&quot; 로 했다면, 최신 버전인 <strong>ncpClientId</strong>가 들어간 src=<code>https://openapi.map.naver.com/openapi/v3/maps.js?ncpClientId=${process.env.NEXT_PUBLIC_MAP_KEY}&amp;submodules=geocoder&amp;callback=initMap</code>로 바꾸어야한다.</p>
<p>참고 - <a href="https://www.ncloud.com/support/faq/all/1047?searchKeyword=map">자주묻는질문: Web Dynamic Map에서 API 인증오류가 납니다. 어떻게 해야 하나요?</a></p>
<h3 id="3-지도-겹침--중복--한-번-이상-렌더링-이슈">3. 지도 겹침 / 중복 / 한 번 이상 렌더링 이슈</h3>
<p><strong>이슈</strong>: 지도가 한 번만 생성되어야 하는데 한 번 이상 렌더링 되어 겹침 현상. 줌인/줌아웃했을 때 지도 하나는 줌이 되고, 나머지는 줌 컨트롤이 안됨.</p>
<p><img src="https://velog.velcdn.com/images/a_in/post/0c7a2401-8697-4b6c-aff6-d04233864cc7/image.png" alt="지도 겹침 에러"></p>
<p><strong>해결</strong>:</p>
<ol>
<li><p>사용자의 현재 위치를 가져오면 딱 한 번 렌더링 되게 하도록 사용자 위치를 좌표로 담아줄 상태 값(myLocation)을 빈문자열로 초기화해주고, 위치 좌표를 받아 왔을 때 좌표 타입으로 바꿔준다. </p>
<pre><code class="language-tsx">const [myLocation, setMyLocation] = useState&lt;{latitude: number, longitude: number} | &#39;&#39;&gt;(&#39;&#39;);
if(typeof myLocation !== &#39;string&#39;) {지도 생성 및 클릭이벤트 걸어주기}</code></pre>
</li>
<li><p>지도는 한 번만 생성하기. 여러 컴포넌트에서 써야 한다고 해서 각자 다 생성하면 안 되고 부모 컴포넌트에서 전달해주는 식으로 한번 생성하여 props로 전달한다.
본인은 (지금 생각해보면 정말 바보 같지만) <code>클릭해서 주소 찾기 함수</code>에 한번, <code>검색해서 주소 찾기 함수</code>에 한 번씩 생성했었는데, 주소를 검색한 후에 클릭 이벤트가 안 먹혀서 종일 삽질했었다...</p>
</li>
</ol>
<h3 id="4-button-typesubmit">4. button type=&#39;submit&#39;</h3>
<p><strong>이슈:</strong> 주소 검색 시 제출 두 번 되는 이슈</p>
<p><strong>해결:</strong> button의 type을 submit으로 하면 인풋에 값이 있는 상태에서 엔터를 눌러 submit을 할 때, 버튼까지 같이 눌려 두번 submit 된다. 그러면 함수도 두번 실행되기 때문에 type은 button으로 수정.</p>
<h3 id="5-input-빈-값일-때-submit-발생">5. input 빈 값일 때 submit 발생</h3>
<p><strong>이슈:</strong> input이 빈 값이고, 커서가 들어간 상태에서 엔터 클릭 시 submit 됨.</p>
<p><img src="https://velog.velcdn.com/images/a_in/post/a4f0aabd-9032-45ea-b04c-9d74e2e3df65/image.png" alt="input 빈 값일 때 제출 에러 화면"></p>
<p><strong>해결:</strong> input이 form에 한 개밖에 없을 때는 자동으로 submit이 된다. submit을 막으려면 input을 두 개 만들어 하나는 시각적으로 안 보이게 하거나, onKeyDown 이벤트에 input 값이 빈 문자열일 때 preventDefault()하기.</p>
<pre><code class="language-tsx">// first solution
&lt;form&gt;
  &lt;input className=&#39;hidden&#39; type=&#39;text&#39; /&gt;
  &lt;input type=&#39;text&#39; /&gt;
&lt;/form&gt;</code></pre>
<pre><code class="language-tsx">// second solution
&lt;form&gt;
  &lt;input onKeyDown={(e: React.KeyboardEvent&lt;HTMLInputElement&gt;) =&gt; {
      if (e.code === &#39;Enter&#39; &amp;&amp; (e.target as HTMLInputElement).value === &#39;&#39;) {
        e.preventDefault();
      }
    }} type=&#39;text&#39; /&gt;
&lt;/form&gt;</code></pre>
<h2 id="참고">참고</h2>
<ul>
<li><a href="https://navermaps.github.io/maps.js.ncp/docs/tutorial-3-geocoder-geocoding.example.html">NAVER Cloud Platform NAVER Map&#39;s Enterprise API - 주소와 좌표 검색 API 사용하기</a> -&gt; 네이버 기술문서. 더 자세하고 다양한 예제가 있음.</li>
<li><a href="https://velog.io/@silverbeen/Naver-Map-%EC%9E%90%EC%9C%A0%EB%A1%AD%EA%B2%8C-%ED%99%9C%EC%9A%A9%ED%95%98%EA%B8%B0">Naver Map 자유롭게 활용하기 - silverbeen</a></li>
<li><a href="https://www.ncloud.com/support/faq/all/1047?searchKeyword=map">자주묻는질문: Web Dynamic Map에서 API 인증오류가 납니다. 어떻게 해야 하나요?</a></li>
<li><a href="https://navermaps.github.io/maps.js.ncp/docs/tutorial-2-Getting-Started.html">NAVER Cloud Platform NAVER Map&#39;s Enterprise API - Hello, World</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[네이버 클라우드 플랫폼 Application 등록하기 - Map's Enterprise API]]></title>
            <link>https://velog.io/@a_in/naver-cloud-platform-map-api-1</link>
            <guid>https://velog.io/@a_in/naver-cloud-platform-map-api-1</guid>
            <pubDate>Wed, 23 Nov 2022 13:00:58 GMT</pubDate>
            <description><![CDATA[<blockquote>
</blockquote>
<p>❗️<span style='color:red;'>2023년 1월부터 네이버 클라우드 플랫폼 Maps 서비스중 Reverse Geocoding를 포함하여 일부 서비스에 <strong>가격변동</strong>이 있으니 주의해주세요.</span> <a href="https://www.ncloud.com/support/notice/all/1424">공지링크</a></p>
<p>네이버의 맵 API를 사용을 위한 Application 등록 방법을 따로 정리 해놓았다. 맵이 아닌 다른 API를 사용하려면 아래 Application 등록 방법의 1번 사진에서 필요한 서비스를 찾으면 된다.</p>
<h2 id="application-등록-방법">Application 등록 방법</h2>
<ol>
<li><p>네이버 클라우드 플랫폼으로 들어가 <strong>서비스 - Application Service - Maps</strong> 로 들어간다. (또는 여기로 들어간다-&gt; <a href="https://www.ncloud.com/product/applicationService/maps">네이버 맵 이용신청 url</a>)
<img src="https://velog.velcdn.com/images/a_in/post/34a1a6e2-6deb-41cd-b94d-bbcd71cc50a8/image.png" alt="네이버 클라우드 플랫폼 서비스 페이지"></p>
</li>
<li><p><strong>이용 신청하기</strong>를 클릭한다.
<img src="https://velog.velcdn.com/images/a_in/post/87ea2589-89d6-41f4-84c9-1776db47737d/image.png" alt="이용 신청하기"></p>
</li>
<li><p><strong>Application 등록</strong>으로 들어간다.
<img src="https://velog.velcdn.com/images/a_in/post/cf54618a-4b85-4d50-9cba-d0cd1ef06c61/image.png" alt="Application 이름"></p>
</li>
<li><p><strong>Application 이름</strong>란에 내가 배포할 어플리케이션 이름을 입력하거나, 테스트용이면 TEST1, test ... 이런식으로 이름을 등록해준다.</p>
</li>
<li><p>우리는 움직이는 지도에서 좌표와 주소를 가져올 것이기 때문에 <strong>Service 선택</strong>에서 Maps의 Web Dynamic Map, Geocoding, Reverse Geocoding을 선택해준다. (다른것도 쓸 것 같으면 다 가져와도 됨.)
<img src="https://velog.velcdn.com/images/a_in/post/4a9b3d65-d15d-4944-8904-487ed9f18efe/image.png" alt="Service 선택"></p>
</li>
<li><p><strong>서비스 환경 등록</strong> Web 서비스 URL에는 개발하고 있는 메인도메인을 넣는다. <code>http://localhost:3000</code>이면 이 url 그대로 넣는다. 다른 페이지에서 지도를 가져온다고해서 <code>http://localhost:3000/map</code>을 넣으면 인증 실패 화면이 뜨니 제대로 넣어야 한다.
그리고 마지막에 추가 버튼까지 눌러준다.
<a href="https://www.ncloud.com/support/faq/all/1047?searchKeyword=map">Web Dynamic Map에서 API 인증오류가 납니다. 어떻게 해야 하나요?</a></p>
</li>
</ol>
<h3 id="결과">결과</h3>
<p><img src="https://velog.velcdn.com/images/a_in/post/faa4408a-3adc-45f9-b185-ee2da3df104f/image.png" alt="Application 등록 결과">
이렇게 App 등록에 성공 했다면 인증정보 버튼을 눌러 Client Id와 Client Secret을 확인할 수 있다.
<img src="https://velog.velcdn.com/images/a_in/post/c441029b-abf4-49c3-87fc-0cd45ea6dda3/image.png" alt=""></p>
<h2 id="참고">참고</h2>
<p><a href="https://guide.ncloud-docs.com/docs/naveropenapiv3-application#application%EB%93%B1%EB%A1%9D%ED%95%98%EA%B8%B0">NAVER CLOUD PLATFORM 사용가이드 - Application 사용 가이드</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[<input type='file' />태그로 이미지 preview 띄우기 (Next.js / TypeScript)]]></title>
            <link>https://velog.io/@a_in/input-typefile-image-preview-Next.js-TypeScriptTailwindCSS</link>
            <guid>https://velog.io/@a_in/input-typefile-image-preview-Next.js-TypeScriptTailwindCSS</guid>
            <pubDate>Mon, 14 Nov 2022 21:27:43 GMT</pubDate>
            <description><![CDATA[<p>본 게시물에서는 html input 태그로 이미지 파일을 여러 장 추가하고, 양식을 제출하기 전에 미리 이미지를 띄워 preview 할 수 있도록 구현한 것을 정리해 보았다.</p>
<p>구현 전에, 우리가 쓸 요소와 구조가 어떻게 이루어질지 먼저 정리해보자.</p>
<hr>
<h2 id="먼저-알아가기">먼저 알아가기</h2>
<h3 id="input-typefile--에-대해서">&lt;input type=&quot;file&quot; /&gt; 에 대해서</h3>
<p>타입이 file인 input 태그는 파일을 추가할 수 있는 기능이 들어있다. 이 태그를 사용하면 html에서 기본으로 제공해주는 버튼이 등장한다.
<img src="https://velog.velcdn.com/images/a_in/post/b7bbff0c-10f1-42b4-a542-074cfede1c5f/image.png" alt="input 태그의 기본 UI"></p>
<p>파일 선택 버튼을 클릭하여 파일을 선택하면 파일명이 나온다.
<img src="https://velog.velcdn.com/images/a_in/post/578d5576-3495-4c68-9e44-561c03434d98/image.png" alt="input 파일 한 개 선택"></p>
<h4 id="--multiple-속성">- multiple 속성</h4>
<p>만약 한꺼번에 여러 개의 파일을 선택하고 싶다면 input 태그에 
<code>multiple</code> 속성을 넣어주면 된다. </p>
<blockquote>
</blockquote>
<pre><code>&lt;input type=&quot;file&quot; multiple /&gt;</code></pre><p><img src="https://velog.velcdn.com/images/a_in/post/1bbbf8e8-fc46-4898-87ec-19c485a7bd5d/image.png" alt="파일을 여러 개 선택했을 때 화면"></p>
<p>속성 multiple이 있는 상태에서 파일을 여러 개 추가해주면 파일명이 나열 되는 게 아니고 사진과 같이 <code>파일 n개</code>로 뜬다.
<del>처음엔 multiple 속성이 파일을 스택처럼 여러 개 쌓을 수 있는 속성인 줄 알았다가 나중에서야 아닌 걸 깨달았다...</del></p>
<h4 id="--accept-속성">- accept 속성</h4>
<blockquote>
</blockquote>
<pre><code>&lt;input type=&quot;file&quot; multiple accept=&quot;image/*&quot; /&gt;</code></pre><p>어떤 형식의 파일을 받을 것인가를 적는 속성이다.
앞서 콘솔에 찍어본 e.target.file의 요소를 보면 type이라는 key가 있다. value로 내가 추가해준 파일의 형식이 들어오는데 이 value에 들어올 수 있는 값을 적어주는 곳이 바로 accept이다. 이미지 파일의 모든 형식을 받아오겠다면 <code>accept=&quot;image/*&quot;</code>, jpeg 파일과 jpg 파일만 받겠다면 <code>accept=&quot;image/jpeg,image/jpg&quot;</code>, pdf만 받겠다면 <code>accept=&quot;application/pdf&quot;</code> 이렇게 입력해주면 된다.</p>
<h4 id="--onchange-이벤트">- onChange 이벤트</h4>
<p>우리가 파일선택 버튼을 클릭한 후 파일을 선택하여 추가하는 행위는 onClick이 아닌 <strong>onChange</strong> 이벤트에 속한다. 그러므로 preview를 만드는 함수는 onChange 이벤트 안에서 이루어진다.</p>
<hr>
<h3 id="etargetfiles의-타입">e.target.files의 타입</h3>
<p>파일 선택을 하면 onChange 이벤트가 발생한다. 여기서 e.target.files을 가져와 보면 FileList 타입의 객체 안에 File 타입의 요소(우리가 선택한 파일)가 나열된다.
<img src="https://velog.velcdn.com/images/a_in/post/3351da3b-d682-4508-a87f-09bc8f855a97/image.png" alt="FileList객체"></p>
<p>우리가 해야 할 것은 이 객체를 배열로 만들어 요소 하나하나를 url로 변환해 주는 것이다.</p>
<p>객체를 배열로 변환하듯이 Array.from 메서드를 사용하여 변환하면 되는데 타입스크립트를 쓴다면 신경을 조금 써줘야 한다. onChange 이벤트에서 가져온 event부터 event.target, event.target.files까지 전부 타입을 지정해주어야 한다.
이 부분은 아래 구현 부분에서 자세히 정리하겠다.</p>
<hr>
<h2 id="구현">구현</h2>
<blockquote>
<p>❗️<strong>스타일링은 tailwindCSS를 사용하였지만 CSS가 중점이 아니기 때문에 생략.</strong></p>
</blockquote>
<h3 id="1-파일-선택-버튼-커스텀하기">1. 파일 선택 버튼 커스텀하기</h3>
<p><code>&lt;input type=&#39;file&#39; /&gt;</code> 태그에 기본으로 붙어있는 <strong>파일선택</strong> 버튼은 커스텀이 아니기 때문에 따로 새로운 버튼을 만들어주고 새로운 버튼에 클릭 이벤트 발생 시 기존 파일선택 버튼이 눌리도록 함수를 작성해주어야 한다.</p>
<br/>

<ol>
<li>우선 input 태그 display를 none으로 하여 보이지 않게 해준다. </li>
</ol>
<pre><code class="language-jsx">import React from &#39;react&#39;;

const FileUploader = () =&gt; {
  return (
    &lt;form&gt;
      &lt;input className=&quot;hidden&quot; type=&quot;file&quot; multiple accept=&quot;image/*&quot; onChange={handleChang} /&gt;
    &lt;/form&gt;
   );
}</code></pre>
<br/>

<ol start="2">
<li>내 커스텀 버튼을 만들어준다.</li>
</ol>
<p><img src="https://velog.velcdn.com/images/a_in/post/480497cc-10a8-4186-a052-a6b825ef487f/image.png" alt="사진 첨부 추가 버튼"></p>
<pre><code class="language-jsx">import React from &#39;react&#39;;

const FileUploader = () =&gt; {
  return (
    &lt;form&gt;
      &lt;div&gt;
        &lt;label htmlFor=&quot;file&quot;&gt;사진첨부&lt;/label&gt;
        &lt;div&gt;
            +
        &lt;/div&gt;
        // hidden input
        &lt;input className=&quot;hidden&quot; type=&quot;file&quot; multiple accept=&quot;image/*&quot; onChange={handleChang} /&gt;
      &lt;/div&gt;
    &lt;/form&gt;
   );
}</code></pre>
<br/>

<ol start="3">
<li>useRef 훅을 사용하여 input에 전달하고 새로 만든 버튼을 클릭하여 onClick 이벤트가 발생했을 때 click() 메서드를 실행해준다. ref는 input 태그에 들어가기 때문에 <code>&lt;HTMLInputElement&gt;</code> 타입으로 넣어준다.</li>
</ol>
<pre><code class="language-tsx">import React, { useRef } from &#39;react&#39;;

const FileUploader = () =&gt; {
  const fileRef = useRef&lt;HTMLInputElement&gt;(null);
  // input click method
  const handleClick = () =&gt; {
    fileRef?.current?.click();
  };

  return (
    &lt;form&gt;
      &lt;div&gt;
        &lt;label htmlFor=&quot;file&quot;&gt;사진첨부&lt;/label&gt;
        // onClick 이벤트
        &lt;div onClick={handleClick}&gt;
            +
        &lt;/div&gt;
        // ref 전달
        &lt;input ref={fileRef} name=&quot;file&quot; className=&quot;hidden&quot; type=&quot;file&quot; multiple accept=&quot;image/*&quot; onChange={handleChang} /&gt;
      &lt;/div&gt;
    &lt;/form&gt;
  );
};</code></pre>
<h3 id="2-이미지들이-보일-preview-자리-만들기">2. 이미지들이 보일 preview 자리 만들기</h3>
<pre><code class="language-tsx">import React, { useRef } from &#39;react&#39;;

const FileUploader = () =&gt; {
...
  return (
    &lt;form&gt;
      &lt;div&gt;
        &lt;label htmlFor=&quot;file&quot;&gt;사진첨부&lt;/label&gt;
        // 이미지 파일
        &lt;div&gt;
          &lt;img src={} width=&#39;&#39; height=&#39;&#39; alt=&#39;&#39; /&gt;
        &lt;/div&gt;
        ...
      &lt;/div&gt;
    &lt;/form&gt;
  );
};</code></pre>
<h3 id="3-이미지-파일들로-이루어진-배열-상태-값-만들기">3. 이미지 파일들로 이루어진 배열 상태 값 만들기</h3>
<p>현재 상태에서는 파일 <strong>추가</strong>가 안된다. e.target.files는 객체이며 이전 파일들이 남아있는 상태에서 쌓이는 게 아니고 <strong>파일선택</strong> 버튼을 누를 때마다 선택한 파일들이 초기화되기 때문에 배열에 내가 선택한 이미지 파일들이 stack처럼 쌓일 수 있도록 함수를 만들어줘야 한다.</p>
<ol>
<li>useState로 상태 값 만들기
e.target.files의 요소들은 앞서 언급했듯이 File 타입의 객체이기 때문에 이를 <code>URL.createObjectURL()</code> 메서드로 url로 만들어줘야 한다. <code>URL.createObjectURL()</code>로 만들어진 url은 string타입이 되므로 <code>string[]</code> 타입의 배열 상태 값을 만들어줘야 한다.</li>
</ol>
<pre><code class="language-tsx">import React, { useRef, useState } from &#39;react&#39;;

const FileUploader = () =&gt; {
  const [images, setImages] = useState&lt;string[]&gt;([]);
...
  return (...);
};</code></pre>
<h3 id="4-이미지-파일을-stack처럼-쌓기-위한-함수-만들기">4. 이미지 파일을 stack처럼 쌓기 위한 함수 만들기</h3>
<p>지금부터 input 태그의 onChange 이벤트(파일을 선택하는 행위)가 발생하면 실행될 함수를 만든다.
보기 쉽게 정리하자면:</p>
<ul>
<li>e.target.files객체를 배열로 만든다.</li>
<li>배열을 <code>map</code>으로 돌면서 <code>URL.createObjectURL()</code>로 파일들을 url을 만든 후 다시 이 url로 이루어진 배열을 만든다.</li>
<li>useState의 <code>setImages()</code> 으로 &#39;이전 파일(images) + 새로 추가한 파일(selectedFiles)&#39;을 위해 concat 메서드로 배열을 합쳐준다.</li>
</ul>
<h4 id="--handlechange">- handleChange</h4>
<ol>
<li>onChange 이벤트가 발생하면 실행될 handleChange 함수를 만들어준 뒤 <code>e</code> 객체를 넘겨주어 <code>e.target.files</code>를 가져온다.</li>
</ol>
<pre><code class="language-tsx">  const handleChange = (e: React.ChangeEvent) =&gt; {
    const targetFiles = (e.target as HTMLInputElement).files as FileList;
  }</code></pre>
<ul>
<li><p>넘겨받은 <code>e</code> 객체는 React의 <code>ChangeEvent</code> 타입에 속한다.</p>
</li>
<li><p>e.target은 원래 <code>EventTarget &amp; Element</code> 타입에 속하지만, 이 타입에는 e.target.files의 files 속성이 존재하지 않기 때문에 files가 존재하는 <code>HTMLInputElement</code>타입을 <code>as</code> 키워드로 지정해준다.
(참고:<a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/files">HTMLInputElement.files MDN</a> &amp; <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement">HTMLInputElement MDN</a>)</p>
</li>
<li><p>파일이 아직 선택되지 않아 <code>e.target.files</code>에 아무것도 없을 때는 null이 되기 때문에 현재 <code>e.target.files</code>의 타입은 <code>FileList | null</code>이 되어 있을 것이다. 만약 타입이 <code>null</code>이라면 나중에 <code>e.target.files</code>를 배열로 바꾸지 못하기 때문에 정확히 <code>FileList</code>라고 타입을 정해줘야 한다. 이번에도 <code>as FileList</code>로 타입을 정해준다.</p>
</li>
</ul>
<br/>

<ol start="2">
<li><code>FileList</code> 타입의<code>e.target.files</code> 객체를 배열로 만들어 준다.</li>
</ol>
<pre><code class="language-tsx">  const handleChange = (e: React.ChangeEvent) =&gt; {
    const targetFiles = (e.target as HTMLInputElement).files as FileList;
    const targetFilesArray = Array.from(targetFiles);
  }</code></pre>
<ul>
<li>Array.from은 <strong>iterable 객체</strong> 또는 <strong>array-like 객체</strong>를 배열로 바꿔주는 메서드이다. 
(array-like객체는 length와 index를 가진 객체_(objects with a length property and indexed elements)_를 말하는데 FileList도 이런 array-like객체이다.)<blockquote>
</blockquote>
<img src="blob:https://velog.io/ef5c44ee-cf9d-4afa-9479-5401db3a5748" alt="FileList 콘솔"></li>
</ul>
<ul>
<li>Array.from 메서드로 <code>FileList</code>를 배열로 만들면 더 이상 <code>FileList</code>타입이 아닌 <code>File[]</code>(File 타입의 요소로 이루어진 배열) 타입이 된다.<blockquote>
</blockquote>
<img src="blob:https://velog.io/e223be11-79ed-4a55-a6e9-5e7eb4c6a918" alt="File[] 콘솔"></li>
</ul>
<br/>

<ol start="3">
<li>배열로 만들어진 <code>targetFilesArray</code>를 map으로 돌려 안의 <code>File</code> 타입의 요소들을 url로 만들어준다.</li>
</ol>
<pre><code class="language-tsx">  const handleChange = (e: React.ChangeEvent) =&gt; {
    const targetFiles = (e.target as HTMLInputElement).files as FileList;
    const targetFilesArray = Array.from(targetFiles);
    const selectedFiles: string[] = targetFilesArray.map((file) =&gt; {
      return URL.createObjectURL(file);
    });
  }</code></pre>
<ul>
<li><p>selectedFiles는 url로 바뀐 요소들이 담긴 배열이기 때문에 <code>string[]</code> 타입을 주면 된다.</p>
</li>
<li><p><code>URL.createObjectURL({params})</code> 여기서 <code>{params}</code> 자리에 들어가는 타입은 <code>Blob | MediaSource</code> 이다. 하지만 우리가 위에서 집어넣은 것은 <code>File</code> 타입인데 어떻게 들어갈 수 있는것인가?
<img src="blob:https://velog.io/82f0879b-38c9-4eb2-bd6d-0a70d6ca9002" alt="createObjectURL 타입"></p>
<blockquote>
</blockquote>
<p><em>The File interface is based on Blob, inheriting blob functionality and expanding it to support files on the user&#39;s system.</em></p>
</li>
<li><p><a href="https://developer.mozilla.org/en-US/docs/Web/API/Blob">Blob MDN</a>-</p>
<p><code>File</code> 타입은 <code>Blob</code>  타입을 상속받은 타입이기 때문에 Blob타입이 들어가야 하는 <code>URL.createObject()</code> 메서드에 들어갈 수 있는 것이다.</p>
<br/>
</li>
</ul>
<ol start="4">
<li>마지막으로 images 상태 값에 이 배열을 합치면 된다.<pre><code class="language-tsx">const handleChange = (e: React.ChangeEvent) =&gt; {
 const targetFiles = (e.target as HTMLInputElement).files as FileList;
 const targetFilesArray = Array.from(targetFiles);
 const selectedFiles: string[] = targetFilesArray.map((file) =&gt; {
   return URL.createObjectURL(file);
 });
 // 합체!
 setImages((prev) =&gt; prev.concat(selectedFiles));
}</code></pre>
</li>
</ol>
<br />

<h3 id="5-배열-상태-값images을-map으로-돌려-이미지-뿌리기">5. 배열 상태 값(images)을 map으로 돌려 이미지 뿌리기</h3>
<pre><code class="language-tsx">const FileUploader = () =&gt; {
  const [images, setImages] = useState&lt;string[]&gt;([]);
  ...
  const handleChange = () =&gt; {...}

  return (
    ...
    &lt;&gt;
      {images.map((url, i) =&gt; (
        &lt;div key={url}&gt;
          &lt;input src={url} width=&#39;160&#39; height=&#39;160&#39; alt={`image${i}`}
        &lt;/div&gt;
      ))}
    &lt;/&gt;
    ...
  );
}</code></pre>
<hr>
<br />

<p>여기까지 preview를 구현해보았다. 
css를 조금만 만진다면 아름다운 자태를 뽐낼 것.</p>
<p><img src="blob:https://velog.io/c459e107-e3a9-4dc6-be5c-0048a8bb49c9" alt="adventure time gif"></p>
<p>.
.
.
.
.
.
.
+추가로 사진을 지우는 기능은 <code>&lt;img&gt;</code>태그 옆에 삭제 버튼을 만들어준 다음, <code>onClick={() =&gt; setImages(images.filter((e) =&gt; e !== url))}</code> 클릭 이벤트를 걸어주면 된다. setImages는 상태 값을 바꾸는 함수고, <code>url</code>은 상태 값 <code>images</code>에서 뿌려준 url 요소이다.
url이 삭제하기 위해 선택한 그 사진이고, 그 사진을 제외한 배열을 다시 images 상태 값에 넣겠다는 코드이다.
.
.
.
.
.
.
.</p>
<p>+추가로 가로스크롤 (tailwind)css는:</p>
<pre><code class="language-css">.scrolling-touch {
  -webkit-overflow-scrolling: touch;
}

.ms-overflow-none {
  -ms-overflow-style: none;
}

.scrollbar-hide {
  -webkit-overflow-scrolling: touch;
  -ms-overflow-style: none;
  scrollbar-width: none;
}
className=&#39;overflow-x-scroll overflow-y-hidden whitespace-nowrap scrolling-touch ms-overflow-none scrollbar-hide&#39;</code></pre>
<h2 id="참고">참고</h2>
<ul>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/files">HTMLInputElement.files MDN</a></li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement">HTMLInputElement MDN</a></li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/API/Blob">Blob MDN</a></li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/from#description">Array.from MDN</a></li>
<li><a href="https://www.youtube.com/watch?v=PDtW-XAshqs">Preview and Delete Selected Images Before Upload Using Reactjs Youtube</a></li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL">URL.createObjectURL MDN</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Next.js] Error: Abort fetching for route: "/"]]></title>
            <link>https://velog.io/@a_in/Next.js-Error-Abort-fetching-for-route</link>
            <guid>https://velog.io/@a_in/Next.js-Error-Abort-fetching-for-route</guid>
            <pubDate>Wed, 19 Oct 2022 09:02:15 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/a_in/post/1cdec466-ac36-4870-ae77-d4069e61f8b2/image.png" alt="error message"></p>
<h2 id="❗️이슈내용">❗️이슈내용</h2>
<ul>
<li>소셜 로그인을 구현하는 도중 발생</li>
</ul>
<h2 id="❗️상세내용">❗️상세내용</h2>
<ul>
<li>사용자가 소셜 로그인을 통해 로그인 및 개인정보수집 동의 후 메인 페이지로 이동하게끔 하기 위해 코드에 route.push(&#39;/&#39;)를 넣었더니 해당 에러 발생.</li>
</ul>
<h2 id="✅-원인">✅ 원인</h2>
<p>useEffect는 여러번 호출이 되는데 그 안에 router.push를 넣게 되면 페이지도 마찬가지로 여러번 이동하게 된다. 그래서 &#39;이 경로로 이동하는 것을 중단하라&#39; 라는 에러가 뜨는 것.</p>
<h2 id="✅-해결방안">✅ 해결방안</h2>
<p>Next.js에서 리렌더링이 되지 않고 페이지를 한번만 이동시키려면, Shallow Routing 옵션을 true로 바꿔주면 된다.</p>
<blockquote>
</blockquote>
<p><strong>Shallow Routing</strong>이란
<em>Shallow routing allows you to change the URL without running data fetching methods again, that includes <code>getServerSideProps</code>, <code>getStaticProps</code>, and <code>getInitialProps</code>.</em>
-<a href="https://nextjs.org/docs/routing/shallow-routing">Vercel</a></p>
<p> Shallow Routing은<code>getServerSideProps</code>와 <code>getStaticProps</code>, <code>getInitialProps</code>를 포함해서, fetching 메서드를 재시동 하지 않고 URL 경로를 이동시켜준다.</p>
<p> <img src="https://velog.velcdn.com/images/a_in/post/84296013-773e-4381-9a9e-45ed19208145/image.png" alt="shallow의 Next.js 문서 예시"></p>
<p>두번째에 undefined가 적힌 이유는 useRouter 문법이 이러하기 때문이다:</p>
<pre><code class="language-js">router.push(url, as, options);</code></pre>
<p><strong>url</strong>: 실제 이동할 경로.
<strong>as</strong>: 사이트의 url 바에 보여질 경로. 예를 들어, 실제 이동경로는 <code>myblog/mynextjsblogcontent</code> 지만, 보여지는 경로는 <code>myblog/mynextjs</code> 로 설정할 수 있다.
<strong>options</strong>: shallow같은 옵션을 설정할 수 있다. 이외에도 <code>scroll</code>, <code>getStaticProps</code>, <code>getServerSideProps</code>, <code>getInitialProps</code>, <code>locale</code> 등이 있다.
<a href="https://nextjs.org/docs/api-reference/next/router">자세한 설명은 공식문서에→</a></p>
<p>최종적으로 <code>router.push(&#39;/&#39;, undefined, { shallow: true })</code> 로 설정하여 에러를 해결하였다.</p>
<h3 id="shallow-routing의-주의사항">shallow routing의 주의사항</h3>
<p>router의 두번째 인자인 <code>as</code>에 <strong>새로운</strong> 페이지로 이동하는 경로를 설정해주면 shallow routing이 작동하지 않는다.</p>
<p><img src="https://velog.velcdn.com/images/a_in/post/8423c3d4-1d4a-4cd7-9145-e884195a815d/image.png" alt=""></p>
<hr>
<h2 id="참고">참고</h2>
<ul>
<li>Next.js <strong>Routing</strong>
<a href="https://nextjs.org/docs/routing/introduction">https://nextjs.org/docs/routing/introduction</a></li>
<li>Next.js <strong>next/router</strong>
<a href="https://nextjs.org/docs/api-reference/next/router">https://nextjs.org/docs/api-reference/next/router</a></li>
<li>Next.js <strong>Shallow Routing</strong>
<a href="https://nextjs.org/docs/routing/shallow-routing">https://nextjs.org/docs/routing/shallow-routing</a></li>
<li>stackoverflow : Next.js - Shallow routing with dynamic routes
<a href="https://stackoverflow.com/questions/61948237/next-js-shallow-routing-with-dynamic-routes">https://stackoverflow.com/questions/61948237/next-js-shallow-routing-with-dynamic-routes</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Git] 이미 push 된 커밋 메시지/제목 수정]]></title>
            <link>https://velog.io/@a_in/Git-%EC%9D%B4%EB%AF%B8-push-%EB%90%9C-%EC%BB%A4%EB%B0%8B-%EB%A9%94%EC%8B%9C%EC%A7%80%EC%A0%9C%EB%AA%A9-%EC%88%98%EC%A0%95</link>
            <guid>https://velog.io/@a_in/Git-%EC%9D%B4%EB%AF%B8-push-%EB%90%9C-%EC%BB%A4%EB%B0%8B-%EB%A9%94%EC%8B%9C%EC%A7%80%EC%A0%9C%EB%AA%A9-%EC%88%98%EC%A0%95</guid>
            <pubDate>Fri, 14 Oct 2022 06:55:03 GMT</pubDate>
            <description><![CDATA[<h2 id="이미-push-된-commit-수정">이미 push 된 commit 수정</h2>
<p>마지막에 푸쉬한 커밋을 되돌리는 것은 쉽다. Github Desktop에서 우클릭후 <code>Amend Commit...</code> 을 클릭하면 된다. 
<img src="https://velog.velcdn.com/images/a_in/post/2d125820-0c0d-474d-8bb9-bd45c97a1d31/image.png" alt=""></p>
<p>하지만 개인 프로젝트 중 간혹 가다 커밋 메시지가 마음에 안들거나 실수로 통일된 커밋 가이드에 벗어난 메시지가 거슬릴 수 있을 것이다. 그럴땐 터미널에서 몇개의 간단한 명령어로 수정이 가능하다.</p>
<h3 id="1-rebase로-커밋을-타고-올라가기">1. rebase로 커밋을 타고 올라가기</h3>
<p><code>git rebase HEAD~1 -i</code>
여기서 잠깐 rebase가 무엇인지 설명을 해보면, merge와 같은 병합방식 중 하나이다. merge는 기존 커밋기록이 남아있는 반면 rebase는 기존에 있던 것은 사라지고 새로 수정한 커밋만 남는다.</p>
<h3 id="2-pick---reword-키워드-변경">2. pick -&gt; reword 키워드 변경</h3>
<ol start="2">
<li><code>i</code> 키를 눌러 편집모드로 바꾼 뒤 수정할 커밋 앞에있는 &#39;pick&#39;을 &#39;reword&#39;로 변경</li>
</ol>
<blockquote>
</blockquote>
<p>예) 10월 8일의 커밋 메시지를 수정하려면,</p>
<pre><code class="language-bash">pick 10월 10일 커밋
pick 10월 9일 커밋
reword 10월 8일 커밋
...</code></pre>
<p> 그 다음, <code>esc</code>로 편집모드를 빠져나온 뒤 <code>:wq</code> 키를 입력 하면 수정해야할 커밋 목록이 터미널에 주루룩 뜰 것이다.</p>
<pre><code class="language-bash">10월 8일 커밋
# #을 맨앞에 쓰면 무시됨~~
# ~~~~...
...</code></pre>
<h3 id="3-메시지-수정">3. 메시지 수정</h3>
<p>여기서 다시 <code>i</code> 키를 눌러 편집모드로 바꾼 후 메시지를 수정. <strong style='background-color: yellow'>(맨 첫 문자에 &#39;#&#39;을 입력하면 그 수정이 무시가 되기 때문에 주의)</strong> 만약 연결해야하는 Issue가 있다면 메시지 뒤에 쓰기!</p>
<blockquote>
</blockquote>
<p>예)</p>
<pre><code class="language-bash">10월 8일 커밋 수정본 (#1)
# #을 맨앞에 쓰면 무시됨~~
# ~~~~
...</code></pre>
<p>다시 <code>esc</code> 키를 누르고 <code>:wq</code> 입력하여 수정사항 저장을 하면 수정된 커밋 메시지와 함께</p>
<pre><code class="language-bash">Successfully rebased and updated refs/heads/main.</code></pre>
<p>라는 문구가 뜬다.</p>
<h3 id="강제-푸쉬하기">강제 푸쉬하기</h3>
<p><code>git push --force</code>를 입력하여 강제로 푸쉬.</p>
<h2 id="❗️주의-사항">❗️주의 사항:</h2>
<ul>
<li><p>메시지 입력 시 특수문자 샵(#)을 넣으면 그 메시지가 무시가 된다. 이번에 Issue와 연결하기 위해 <strong>#1 design: work on login ui</strong> 이런식으로 작성했더니 메시지가 자꾸 바뀌질 않아서 애를 먹었다😓</p>
</li>
<li><p>기존 커밋을 수정하면 커밋 id가 바뀐다.</p>
</li>
<li><p>강제로 푸쉬하는 것(git push --force)는 협업 시 같은 레파지토리를 공유하고 있는 사람들에게는 자동으로 수정사항이 반영되지 않을 수 있기 때문에 주의해야 한다. <strong>push 할때에는 항상 신중히!</strong></p>
</li>
</ul>
]]></description>
        </item>
    </channel>
</rss>