<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>Do Everything</title>
        <link>https://velog.io/</link>
        <description>Backend Engineer | Sungkyunkwan University</description>
        <lastBuildDate>Tue, 17 Feb 2026 02:59:22 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <copyright>Copyright (C) 2019. Do Everything. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/code_prince" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[C] 포인터 헷갈리는 사람들 들어오세요.]]></title>
            <link>https://velog.io/@code_prince/C-%ED%8F%AC%EC%9D%B8%ED%84%B0-%ED%97%B7%EA%B0%88%EB%A6%AC%EB%8A%94-%EC%82%AC%EB%9E%8C%EB%93%A4-%EB%93%A4%EC%96%B4%EC%98%A4%EC%84%B8%EC%9A%94</link>
            <guid>https://velog.io/@code_prince/C-%ED%8F%AC%EC%9D%B8%ED%84%B0-%ED%97%B7%EA%B0%88%EB%A6%AC%EB%8A%94-%EC%82%AC%EB%9E%8C%EB%93%A4-%EB%93%A4%EC%96%B4%EC%98%A4%EC%84%B8%EC%9A%94</guid>
            <pubDate>Tue, 17 Feb 2026 02:59:22 GMT</pubDate>
            <description><![CDATA[<p>C언어의 상징이자 생명과도 같은 존재 포인터. 
그러나 학습자들에겐 분노 유발자이자 암덩어리 같은 존재일 것이다. </p>
<p>무수히 많은 학습자료들이 존재하지만, 
현학적이고 눈에 들어오지 않는 것들이 태반이다. </p>
<p>그래서 마음먹었다. 
정말 이해하기 쉽게 포인터의 전반적인 내용을 정리해보기로. </p>
<p>단, 독자들이 포인터의 개념정도는 알고 있다는 전제 하에 작성할 것이다.  </p>
<h2 id="1-선언">1. 선언</h2>
<p>포인터도 하나의 변수인데, 특정 변수의 주소값을 저장하는 변수다. </p>
<p>변수 이름 앞에 <code>*</code> 를 붙여 포인터 변수를 선언할 수 있고,
<code>&amp;</code> 를 붙여 주소를 넘겨줄 수 있다. </p>
<pre><code class="language-c">int a = 1;
int *p = &amp;a;</code></pre>
<p>포인터변수p에는 변수a의 주소값이 담긴다. </p>
<p>가령, a라는 변수가 100번지에 저장되어 있다면, 
p의 값은 100이 되는 것. </p>
<pre><code class="language-c">printf(&quot;%p\n&quot;, &amp;a); //0x7ffc6193e28c
printf(&quot;%p\n&quot;, p);  //0x7ffc6193e28c</code></pre>
<p><code>a</code> 의 주소와 <code>p</code> 의 값이 같다. 기억하자. 포인터 변수에는 주소가 담긴다. </p>
<h2 id="2-역참조">2. 역참조</h2>
<p>현학적인 글들을 비판해놓고 이런 용어를 사용하게 된 것에 유감이다. </p>
<pre><code>int a = 1;
int *p = &amp;a;</code></pre><p>위의 코드를 보고, 아래의 출력  결과를 예상해보자. </p>
<pre><code class="language-c">printf(&quot;%p\n&quot;, p); 
printf(&quot;%d\n&quot;, *p);  </code></pre>
<p>첫 코드는 이제 익숙하다. <code>a</code> 라는 변수의 주소값이 출력될 것이다. 
두 번째는? <code>p</code> 를 출력하는 것이 아니라 <code>*p</code> 를 출력하고 있다. </p>
<p>결과는 <code>1</code> 이다.</p>
<p>포인터를 선언하는 그 순간을 제외하고는 <code>*</code> 을 붙이면
주소가 아니라 <strong>그 주소에 들어있는 실제 값</strong>을 의미한다.</p>
<p>즉 <code>a</code>의 값인 <code>1</code> 이 되는 것. </p>
<p>이렇게, <code>*</code> 을 사용해서 포인터가 가리키는 원래 변수에 접근하는 것을
<strong>역참조</strong>라고 부른다.</p>
<p>아래 코드의 출력 결과를 예상해보자. </p>
<pre><code>int a  = 1;
int *p = &amp;a;

*p = 10;
printf(&quot;%d\n&quot;, a);</code></pre><p>정답은 10이다. </p>
<p><code>*p = 10;</code> 에서 <code>a</code> 의 값도 <code>10</code> 으로 바뀐다. </p>
<p>왜? </p>
<p>포인터를 선언하는 딱 그 순간 외에는 <code>*</code> 을 붙인다는 것은 곧 
그 포인터가 가리키는 변수인 <code>a</code> 를 사용하겠다는 의미이기 때문. </p>
<h2 id="3-null-포인터">3. NULL 포인터</h2>
<p>아무것도 가리키지 않는 포인터를 널 포인터라고 부른다. </p>
<p>아래와 같이 작성하면 된다. </p>
<pre><code class="language-c">int *p = NULL;</code></pre>
<p>포인터를 만들어두긴 했는데, 당장은 가리킬 대상이 없다면 
위의 코드처럼 <code>NULL</code> 을 명시적으로 할당해주는 것이 좋다. </p>
<pre><code class="language-c">int *p1;
printf(&quot;%p\n&quot;, p1); //0x7ffd264491a0

int *p2 = NULL;
printf(&quot;%p\n&quot;, p2); //(nil)</code></pre>
<p><code>p1</code> 은 가리키는 변수가 없음에도 어딘지 모를 쓰레기 주소가 출력되었고,
<code>p2</code> 는 명시적으로 <code>NULL</code> 을 주어 <code>nil</code> 이 출력되었다. </p>
<p>따라서, 가리키는 것이 없는 상황에서는 선언과 동시에 값을 <code>NULL</code> 로 초기화해야 한다.
이 경우 메모리상에서 <code>0번지</code> 에 저장된다. </p>
<h2 id="4-더블-포인터">4. 더블 포인터</h2>
<p>포인터도 결국 변수다.
즉 포인터도 메모리상에 저장된 주소값이 존재할거다. </p>
<p>더블 포인터는 포인터의 주소를 담는 포인터다. </p>
<p>더블 포인터라는 이름에 맞게 
선언과 역참조시에도 <code>*을 두 개</code>  써주면 된다. </p>
<pre><code class="language-c">  int a = 1;
  int *p1 = &amp;a;
  int **p2 = &amp;p1;

  printf(&quot;a의 주소값:   %p\n&quot;, &amp;a);
  printf(&quot;p1에 담긴 값: %p\n&quot;, p1);
  printf(&quot;p1의 주소값 : %p\n&quot;, &amp;p1);
  printf(&quot;p2에 담긴 값: %p\n&quot;, p2);

  printf(&quot;*p1의 결과:  %d\n&quot;, *p1);
  printf(&quot;**p2의 결과: %d\n&quot;, **p2);
</code></pre>
<p>위의 출력 결과는 아래와 같다.</p>
<pre><code>a의 주소값:   0x7ffdb212f584
p1에 담긴 값: 0x7ffdb212f584
p1의 주소값 : 0x7ffdb212f588
p2에 담긴 값: 0x7ffdb212f588
*p1의 결과:  1
**p2의 결과: 1</code></pre><p>포인터 <code>p1</code> 은 <code>a</code> 의 주소를,
더블포인터 <code>p2</code> 는 포인터인 <code>p1</code> 의 주소를 저장한다. </p>
<p>p1의 역참조인 <code>*p1</code> 은 곧 a의 값이므로 <code>1</code> 이다. 
p2의 역참조인 <code>**p2</code> 도 값이 동일하게 <code>1</code> 이 나왔다. </p>
<p>그렇다. 위의 상황에서, <code>**p2 == *p1 == a</code> 이다. 
<code>**p2 == a</code> 이므로, 아래의 코드처럼 더블포인터로 a의 값을 수정할 수 있다</p>
<pre><code class="language-c">  int a = 1;
  int *p1 = &amp;a;
  int **p2 = &amp;p1;

  **p2 = 10;
  printf(&quot;a값:%d\n&quot;, a);</code></pre>
<p>출력 결과 a는 10이 될 것이다. </p>
<h2 id="5-포인터와--연산-p--i란-대체-무엇인가">5. 포인터와 + 연산 (p + i란 대체 무엇인가?)</h2>
<p>포인터에는 주소값이 들어있다.
주소값에 1을 더하면, 다음 번지의 주소값이 나온다. </p>
<p>말로는 어려우니 예제 코드와 함께 살펴보겠다.</p>
<pre><code class="language-c">  char a = &#39;a&#39;;
  char *p1 = &amp;a;

  printf(&quot;%d\n&quot;, (int)p1);
  printf(&quot;%d\n&quot;, (int)(p1 + 1));
  printf(&quot;%d\n&quot;, (int)(p1 + 2));
  printf(&quot;%d\n&quot;, (int)(p1 + 3));</code></pre>
<p>변수 a가 <code>691378895</code> 번지에 저장되어 있다고 가정하면, 
위의 출력 결과는 어떻게 될지 예상해보자. </p>
<p>답은 아래와 같다. </p>
<pre><code>691378895
691378896
691378897
691378898</code></pre><p>즉, <code>p + 1</code> 의 의미는, <strong>다음 번지의 주소</strong>를 알려달라는 것이다.
<code>p1 + 3</code> 은 691378895에 3을 더한 값인 691378898가 되는 것이다. </p>
<p>아래의 출력 결과도 예상해보길 바란다. 
변수 a는 <code>140731385761612</code>번지에 저장되었다고 가정한다. </p>
<pre><code class="language-c">  int a = 1;
  int *p = &amp;a;

  printf(&quot;%ld\n&quot;, (long)p);
  printf(&quot;%ld\n&quot;, (long)(p + 1));
  printf(&quot;%ld\n&quot;, (long)(p + 2));
  printf(&quot;%ld\n&quot;, (long)(p + 3));</code></pre>
<p>답은 다음과 같다. </p>
<pre><code>140731385761612
140731385761616
140731385761620
140731385761624</code></pre><p>위의 예제와 달리, <code>+1</code> 을 했는데 주소값이 1이 아니라 <code>4</code> 가 늘어났다. 
이유는 자료형에 있다.</p>
<p>위의 예제는 자료형이 char였고, 지금의 예제는 int이다. 
char는 1바이트, int는 4바이트!</p>
<p>char의 경우 데이터 하나가 들어가기 위한 공간이 1바이트이므로,
다음 번지(공간이라 생각하자)의 주소값도 1이 늘어난다.</p>
<p>반면 int는 데이터를 저장하기 위해 4바이트의 공간이 필요하기 때문에,
다음 번지는 주소값이 4가 늘어난다. </p>
<p>정리하면,<code>p + 1</code> 은 다음 번지의 주소를 의미하고, 
그 값은 <strong>자료형의 크기를 고려</strong>하여 결정된다. </p>
<p>이를 일반화하면,
*<em><code>p + i</code> 의 실제 값은 `p + i</em>n` **가 되는 것이다. (n은 자료형의 크기: byte)</p>
<h2 id="6-배열과-주소">6. 배열과 주소</h2>
<p>아래의 배열은 메모리상에 어떻게 저장될까?
상상하며 직접 그려보길 바란다. </p>
<pre><code class="language-c"> int arr[3] = {1, 2, 3};</code></pre>
<p>첫 원소의 주소값을 편의상 100이라 하면, 아래와 같을 것이다. 
기억해야 할 것은, 두 번째 요소는 101번지가 아니라 104번지라는 것. </p>
<p><img src="https://velog.velcdn.com/images/code_prince/post/06f16245-563e-421d-b406-b42de421d5c7/image.png" alt="">
엥? 싶다면 5번 내용 다시 읽어보기</p>
<p>짚고 넘어가야 할 것은,
배열의 각 원소들은 각자의 주소값을 가진다는 것. </p>
<h2 id="7-배열의-이름--첫-원소의-주소">7. 배열의 이름 == 첫 원소의 주소</h2>
<p>6번 주제에서 배열의 각 원소는 각자의 주소값을 갖는다는걸 확인했다. </p>
<p>포인터에 배열의 이름을 할당해주면, 
<strong>그 배열의 첫 원소의 주소값이 담긴다</strong>. </p>
<p>말이 또 복잡해지려 하니 예제 코드와 함께 살펴보자. </p>
<pre><code class="language-c">  int arr[3] = {1, 2, 3};
  int *p = arr;</code></pre>
<p>배열 <code>arr</code> 는 원소가 3개이므로 주소값도 3개일텐데 포인터<code>p</code> 에 어떻게 주소를 다 할당할까?
결론은 배열의 이름을 넘겨주면 첫 원소(<code>arr[0]</code>)의 주소값이 <code>p</code> 에 담긴다. </p>
<p>여기서 주의할 점은, 배열의 주소를 줄 땐 예외적으로 <code>&amp;</code> 기호를 사용하지 않는다.
그냥 배열의 이름만 넘겨주면 된다. </p>
<p>이제, <code>p</code> 를 통해서 <code>arr</code> 라는 배열의 각 원소에 접근할 수도, 수정할 수도 있다. </p>
<p>아래 코드들의 출력 결과를 예상해보길 바란다. 
편의상 <code>arr[0]</code> 의 주소값은 100이라 가정한다. </p>
<pre><code class="language-c">  int arr[3] = {1, 2, 3};
  int* p = arr;

  printf(&quot;arr[0]의 주소값: %p\n&quot;, &amp;arr[0]);
  printf(&quot;p의 값: %p\n&quot;, p);

  printf(&quot;arr[1]의 주소값: %p\n&quot;, &amp;arr[1]);
  printf(&quot;p + 1의 값: %p\n&quot;, p + 1);

  printf(&quot;arr[2]의 값: %d\n&quot;, *(arr + 2));
  printf(&quot;*(p+2)의 값: %d\n&quot;, *(p + 2));</code></pre>
<p>정답은 아래와 같다</p>
<pre><code>100
100
104
104
3
3</code></pre><p>중요한 부분만 정리하고 넘어가자. </p>
<ol>
<li><p><code>printf(&quot;p + 1의 값: %p\n&quot;, p + 1);</code> 는 배열의 두 번째 원소 <code>arr[1]</code> 의 주소값을 의미한다. </p>
<p>*<em>즉, <code>p + i</code> 는 <code>&amp;arr[i]</code> 와 같다. *</em></p>
</li>
<li><p><code>printf(&quot;*(p+2)의 값: %d\n&quot;, *(p + 2));</code> 는 배열의 세 번째 원소 <code>arr[2]</code> 의 값(역참조)을 의미한다. </p>
</li>
</ol>
<p><strong>즉, <code>*(p + i)</code> 는 <code>arr[i]</code> 와 같다.</strong></p>
<ol start="3">
<li><code>printf(&quot;arr[2]의 값: %d\n&quot;, *(arr + 2));</code> 는 는 배열의 세 번째 원소 <code>arr[2]</code> 의 값을 의미한다. 배열의 이름인 <code>arr</code> 는 <code>arr[0]</code> 의 주소를 의미하고, <code>+2</code> 는 다다음 번지인 <code>arr[2]</code> 의 주소를 의미하기 때문이다. </li>
</ol>
<p>또, 포인터도 인덱싱을 통해 배열에 접근할 수 있다. 
<strong>한마디로, <code>p[i]</code> 는 <code>arr[i]</code> 이다.</strong></p>
<pre><code class="language-c">  int arr[3] = {1, 2, 3};
  int* p = arr;

  p[2] = 100;

  printf(&quot;arr[2]의 값: %d\n&quot;, arr[2]);</code></pre>
<p>위 코드를 수행하면 arr[2] 는 100이 된다. </p>
<p><strong>결론: arr가 배열이고 *p = arr일 때, p[i] == arr[i] == *(p + i) == *(arr + i)</strong></p>
<h2 id="8-배열포인터">8. 배열포인터</h2>
<p>배열포인터는 뒤에 나올 포인터 배열과 완전 다르다. </p>
<p>배열 포인터는 배열 전체를 가리킨다. 
배열의 이름이 배열의 첫 원소의 주소를 가리키는 것과 달리
배열포인터는 배열 전체의 주소를 가리킨다. </p>
<p>배열 포인터는 다음과 같이 만들어줄 수 있다.
<code>int (*p)[3] = &amp;arr;</code> 
위 구문은 3칸짜리 int형 배열 전체를 가리키는 배열포인터 <code>p</code> 를 만든 것이다.</p>
<p>중요한 점은 배열포인터의 이름에 소괄호를 반드시 적어주어야 하며, 
주소를 받을 배열 이름 앞에 <code>&amp;</code> 를 붙여줘야 한다는 것이다. </p>
<p>말이 어렵기 때문에 직관적인 코드와 살펴보자. 
아래의 코드를 이해하면, &quot;배열 전체를 가리킨다&quot; 는 것의 의미를 이해할 수 있다. </p>
<p>편의상 <code>arr[0]</code> 의 주소값을 100이라 한다. </p>
<pre><code class="language-c">  int arr[3] = {1, 2, 3};

  int *p1 = arr;
  int (*p2)[3] = &amp;arr;

  printf(&quot;%ld\n&quot;, (long)(p1));
  printf(&quot;%ld\n&quot;, (long)(p1 + 1));
  printf(&quot;%ld\n&quot;, (long)(p2 + 1));</code></pre>
<p>포인터 p1은 우리가 지금까지 본 방식으로, 배열의 이름을 전달하는 일반적인 포인터다.
이 경우 p1에는 <code>arr[0]</code> 의 주소값이 할당된다.</p>
<p>p2가 바로 배열포인터다. 소괄호와 &amp;를 사용해야 한다는 점을 다시 한 번 기억하자.
이 경우, p2에도 <code>arr[0]</code> 의 주소가 할당된다.</p>
<p>차이는 <code>+1</code> 의 동작방식에 있다. 
<code>p1 + 1</code> 은 배열 내부에서 다음 원소를 의미한다. 즉, <code>104</code> 가 된다. </p>
<p>반면, <code>p2</code> 는 배열 전체를 하나로 보기 때문에, 
<code>p2 + 1</code> 을 해주면 <strong>배열 전체 크기만큼의 주소값이 늘어난다</strong>. 즉, <code>112</code> 가 된다.
(배열의 각 원소는 int이므로 4바이트, 원소가 3개이므로 배열은 12바이트) </p>
<p>추가로, 배열 포인터에서는 다음과 같은 방식으로 배열의 각 요소에 접근할 수 있다
<code>(*p)[i]</code></p>
<p>아래와 같이 배열포인터로 배열의 각 원소에 접근 할 수 있다. </p>
<pre><code class="language-c">  int arr[2] = {1, 2};
  int (*p)[2] = &amp;arr;

  for (int i = 0; i &lt; 2; i++) {
    printf(&quot;%d\n&quot;, (*p)[i]);
  }</code></pre>
<p>배열포인터의 개념이 어렵지만, 
일단 무엇인지는 어느정도 정리가 됐다. </p>
<p>그렇다면, 대체 어디에 쓰려고 이런 복잡한 녀석을 배워햐 한단 말인가?</p>
<p>배열포인터는 2차원 배열의 각 행 하나 하나를 가리키기 위한 용도로 자주 사용한다. 
조금 더 자세히 말하면, <strong>함수의 파라미터로 2차원 배열을 넘겨줄 때 사용</strong>하게 된다. 
이 예제는 뒤에서 살펴볼 것이다.</p>
<h2 id="9-2차원-배열의-주소">9. 2차원 배열의 주소</h2>
<p>2차원 배열이 메모리상에 어떻게 저장되는지 짚고 넘어가자. </p>
<pre><code class="language-c">int arr[2][2] = {{1, 2}, {3, 4}};</code></pre>
<p>다음과 같은 2차원 배열은 메모리상에 아래와 같이 저장된다. 
<img src="https://velog.velcdn.com/images/code_prince/post/18adc3f9-8e8a-4361-bcba-a54af98088de/image.png" alt=""></p>
<p>한마디로, <code>arr[0][1] + 1</code> 은 <code>arr[1][0]</code> 이 된다. </p>
<h2 id="10-배열포인터-사용예제">10. 배열포인터 사용예제</h2>
<p>8번 주제에서 배열포인터는 2차원 배열을 다루기 위해 사용한다고 했다. 
이번 토픽에서는 배열포인터의 사용 예제를 살펴본다. </p>
<p>아래는 이차원 배열을 파라미터로 받아 각 원소들을 출력하는 함수이다.</p>
<pre><code class="language-c">void print_2D_array(int rows, int cols, int (*arr_ptr)[cols]) {
  for (int i = 0; i &lt; rows; i++) {
    for (int j = 0; j &lt; cols; j++) {
      printf(&quot;%d&quot;, arr_ptr[i][j]);
    }
    printf(&quot;\n&quot;);
  }
}</code></pre>
<p>위의 함수에서 rows와 cols는 배열의 행과 열의 수를 의미한다. 
배열포인터 arr_ptr은 원소의 개수가 cols개인 배열의 주소를 가리킨다. </p>
<p>출력문에서, <code>printf(&quot;%d&quot;, arr_ptr[i][j]);</code> 는 <code>printf(&quot;%d&quot;, (*(arr_ptr + i))[j]);</code> 혹은 <code>printf(&quot;%d&quot;, *(*(arr + i) + j));</code> 로 바꿔도 무방하다. </p>
<p>배열 포인터가 이차원 배열을 가리키면, 배열포인터는 이차원 배열의 첫 원소의 주소인
0행 1차원 배열의 주소를 가리킨다. 즉, <code>+i</code> 는 행의 이동, <code>+j</code> 는 행 내에서 열의 이동이다. </p>
<p>물론 위처럼 배열 포인터를 사용하지 않고 명시적으로 파라미터에 이차원 배열임을 직접 명시하여 작성할 수도 있다. 아래와 같이 말이다. </p>
<pre><code class="language-c">void print_2D_array(int rows, int cols, int arr[][cols]) {
  for (int i = 0; i &lt; rows; i++) {
    for (int j = 0; j &lt; cols; j++) {
      printf(&quot;%d&quot;, arr[i][j]);
    }
    printf(&quot;\n&quot;);
  }
}</code></pre>
<p>하지만 이렇게 작성해도 내부적으로 arr는 배열포인터가 된다. 
조금 더 엄밀히 말하면, arr도 결국 컴파일러에 의해 배열포인터로 변환된다. </p>
<p>그러니 두 작성법 모두 알아두길 바란다. </p>
<h2 id="11-포인터배열">11. 포인터배열</h2>
<p>포인터배열은 배열포인터랑 전혀 다른 개념이다. 
혼동해선 안된다.</p>
<p>포인터배열은 포인터로 이루어진 배열이다.
원소가 포인터라는 뜻. </p>
<p>다음과 같이 선언할 수 있다. </p>
<pre><code class="language-c">int *p[5];</code></pre>
<p>위 코드에서 p는 포인터배열이다. 
정확히는, int형 변수의 주소를 담는 포인터 5개로 이루어진 배열이다.</p>
<p>포인터 배열 선언법을 일반화하면 아래와 같다. 
<code>자료형 *이름[원소개수]</code></p>
<p>배열 포인터의 선언법과 무엇이 다른지 보이는가?
바로 보이지 않는다면 배열 포인터 섹션으로 다시 올라가길 바란다. </p>
<p>정답은 소괄호의 유무에 있다.
<code>int (*p)[3]</code> 과 <code>int *p[3]</code> 은 전혀 다르다! </p>
<p>포인터배열을 구성하는 포인터가 가리키는 주소에 들어있는 실제 값에 접근할 때도
포인터의 역참조 문법이 동일하게 적용된다.</p>
<p>다음과 같이 n번째 원소 포인터가 가리키는 주소지의 실제 값을 받아올 수 있다. 
<code>*p[n]</code></p>
<p>배열 포인터의 역참조인 <code>(*p)[n]</code> 과 헷갈리지 않도록 주의하자. </p>
<h2 id="12-함수포인터">12. 함수포인터</h2>
<p>함수도 메모리상에 저장되기에, 주소값을 갖는다.
함수의 주소를 저장하는 변수를 함수포인터라고 부른다. </p>
<p>함수 포인터는 다음과 같이 선언할 수 있다. 
<code>리턴타입 (*포인터이름)(매개변수타입);</code>
소괄호를 빼먹어선 안되니 꼼꼼히 챙겨두자. </p>
<p>예를 들어 다음과 같은 함수가 있다고 할 때,
이 함수의 주소를 가리키는 포인터를 어떻게 만들 수 있을지 먼저 생각해보기 바란다. </p>
<pre><code class="language-c">int adder(int v1, int v2) {
    return v1 + v2; 
}</code></pre>
<p>이 함수의 주소를 담을 포인터의 이름을 p라 하면, 
함수포인터 p는 아래와 같이 작성할 수 있다.</p>
<p><code>int (*p)(int, int);</code></p>
<p>이제, 만든 함수 포인터에 adder함수의 주소를 할당해주자. 
<code>p = adder;</code> 와 같이 함수의 이름을 전달하면 된다. </p>
<p>이제 p로 adder함수에 접근이 가능해졌다. 
즉, 함수포인터 p 로 adder함수를 호출할 수 있다. </p>
<pre><code class="language-c">int adder(int v1, int v2) {
    return v1 + v2; 
}

int (*p)(int, int);
p = adder;

int result = p(1,2)</code></pre>
<p>위의 코드에서 <code>p(1, 2)</code> 는 <code>adder(1, 2)</code> 와 완전히 동일한 것이다. </p>
<p>조금 더 응용해서,
함수의 매개변수로 함수포인터를 전달할 수도 있다. </p>
<p>간단한 예제를 살펴보자. </p>
<pre><code class="language-c">int add(int v1, int v2) {
  return v1 + v2;
}

int minus(int v1, int v2) {
  return v1 - v2;
}

int add_or_minus(int(*ptr)(int, int), int v1, int v2) {
  return ptr(v1, v2);
}</code></pre>
<p>위의 add_or_minus 함수의 첫 번째 파라미터를 보자.
반환타입이 int이고, 2개의 int형 파라미터를 갖는 함수에 대한 포인터다. </p>
<p>이제, add_or_minus함수를 아래와 같이 호출할 수 있다.
<code>add_or_minus(add, 20, 10);</code>
<code>add_or_minus(minus, 20, 10);</code>
첫 인자로 <code>add</code> 와 <code>minus</code> 함수 이름을 전달할 수 있는 것이다. </p>
<h2 id="13-문자열과-포인터">13. 문자열과 포인터</h2>
<p>문자열도 배열이다. char형의 배열.</p>
<p>문자열을 선언하는 두 가지 방식을 정리하고 넘어가보자. </p>
<h4 id="방법1---배열">방법1 - 배열</h4>
<p>가장 일반적인 방식으로, 아래의 두 가지 형태로 작성 가능하다.</p>
<pre><code class="language-c">char name[100] = {&#39;L&#39;, &#39;e&#39;, &#39;e&#39;};</code></pre>
<pre><code class="language-c">char name[100] = &quot;Lee&quot;;</code></pre>
<p>만약 배열의 크기를 타이트하게 잡을 것이라면, null문자의 공간만 잘 생각해주자.</p>
<h4 id="방법2---포인터">방법2 - 포인터</h4>
<p>포인터를 사용해서도 문자열을 선언할 수 있다. </p>
<pre><code class="language-c">char *name = &quot;Lee&quot;;</code></pre>
<h3 id="두-방식의-차이점">두 방식의 차이점</h3>
<p>두 방식은 여러 결정적인 차이점을 갖는다. 
숙지하고, 상황에 맞게 사용하자.
<img src="https://velog.velcdn.com/images/code_prince/post/64d6e065-13ca-48b5-b923-5eb6e16d3450/image.png" alt=""></p>
<p>표의 내용에 약간의 설명을 더하면,
배열로 선언하면 인덱싱을 통해 문자열의 일부 값을 변경하는 것은 가능하지만,
완전히 새로운 문자열을 새로 할당하는 것은 제한된다.</p>
<p>예를 들어, 아래와 같이 인덱싱을 통해 문자열을 수정하는 것은 가능하다. </p>
<pre><code class="language-c">char name[100] = &quot;kim&quot;;

name[0] = &#39;l&#39;;
name[1] = &#39;e&#39;;
name[1] = &#39;e&#39;;</code></pre>
<p>하지만, 아래와 같은 코드는 에러가 난다. </p>
<pre><code class="language-c">char name[100] = &quot;kim&quot;;

name = &quot;lee&quot;; //에러!!!!!! </code></pre>
<p>포인터로 선언한 문자열은 그 반대이다. </p>
<p>예를 들어, 아래와 같이 인덱싱을 통해 문자열을 수정하는 것은 제한된다. </p>
<pre><code class="language-c">char *name = &quot;kim&quot;;
name[0] = &#39;l&#39;; //에러!</code></pre>
<p>하지만, 아래와 같이 통째로 다른 문자열을 할당하는 것은 가능하다. 
문자열이 수정되는 것이 아니라, 포인터가 가리키는 주소지가 완전히 바뀌어버리는 것이다. 
이 개념을 정확히 이해하길 바란다. </p>
<p>포인터로 문자열을 선언하면, 처음엔 kim이라는 문자열의 주소를 가리키다가,
lee라는 새로운 문자열을 할당하면 lee라는 문자열의 주소를 가리키도록 포인터의 방향이 바뀌는 것이라 이해하면 된다. </p>
<pre><code class="language-c">char *name = &quot;kim&quot;;
name = &quot;lee&quot;; //가능! 포인터의 주소가 바뀜 </code></pre>
<h2 id="14-구조체와-포인터">14. 구조체와 포인터</h2>
<p>구조체도 당연히 주소를 갖는다. 
고로, 구조체의 주소도 포인터에 담을 수 있다. </p>
<p>구조체 포인터의 선언법은 아래와 같이 일반화 해볼 수 있다.
<code>구조체이름 *포인터이름 = &amp;구조체변수이름</code></p>
<p>예제 코드로 살펴보자. </p>
<pre><code class="language-c">typedef struct {
  char name[30];
  int age;
} User;

int main() {
  User user1 = {&quot;홍길동&quot;, 20};
  User *p = &amp;user1;

  printf(&quot;%s\n&quot;, (*p).name);
  printf(&quot;%s\n&quot;, p -&gt; name);
}</code></pre>
<p>위 코드에서 p는 구조체변수 user1의 주소를 저장하는 구조체포인터다.
따라서, 출력문에서처럼 user1 대신 p로도 구조체의 멤버변수에 접근할 수 있다. </p>
<p>구조체에 대한 내용을 숙지하고 있다 생각하지만,
중요한 내용이니 정리해보자. </p>
<p>구조체의 포인터 멤버변수가 가리키는 주소지에 들어있는 실제 값에 접근하려면 
아래와 같은 두 가지 방식 중 하나를 사용해야 한다. </p>
<p><code>(*p).name</code> or <code>p-&gt;name</code></p>
<h2 id="15-함수-call-by-address">15. 함수 call-by-address</h2>
<p>C에서 함수를 호출하는 두 가지 방식이 있다.</p>
<p>함수를 호출할 때 인자에 값을 넘겨주는 call-by-value 방식과,
인자로 주소값만 넘겨주는 call-by-address방식. </p>
<p>이 역시 함수를 공부하면서 등장하는 내용이기에,
어느정도의 내용은 알고 있다는 전제 하에, 포인터와 관련된 내용에 무게를 두어 정리하려 한다. </p>
<p>이번 포스팅의 내용을 모두 이해했다면, 
함수를 call-by-address방식으로 호출한다는 것은 인자에 포인터나 배열의 이름을 전달한다는 것과 같은 의미인 것을 이해할 수 있을 것이다. </p>
<p>중요한 것은, 뭐하러 주소값을 전달하느냐는 것이다. 
표로 정리하면 아래와 같다. </p>
<p><img src="https://velog.velcdn.com/images/code_prince/post/1767e43d-93e0-4625-ad57-7ebeead824a0/image.png" alt=""></p>
<p>가장 중요한 것은, 주소값을 전달해 함수 안에서 값을 수정하면, 
그 수정 내역이 전역적으로 반영된다는 것이다. </p>
<p>예제 코드로 이해해보자. </p>
<pre><code class="language-c">#include &lt;stdio.h&gt;

// 1. Call by Value
void levelUp_Value(int level) {
    level += 1;
    printf(&quot;  [Value 함수 안] 레벨 업 시도... 현재 레벨: %d\n&quot;, level);
}

// 2. Call by Address
void levelUp_Address(int *level_ptr) {
    *level_ptr += 1;
    printf(&quot;  [Address 함수 안] 레벨 업 시도... 현재 레벨: %d\n&quot;, *level_ptr);
}

int main() {
    int myLevel = 10;

    printf(&quot;--- 게임 시작! 현재 내 레벨: %d ---\n\n&quot;, myLevel);

    // [CASE 1] Call by Value
    printf(&quot;1. Call by Value 호출 중...\n&quot;);
    levelUp_Value(myLevel);
    printf(&quot;&gt;&gt; 호출 후 메인 함수 레벨: %d (변화 없음!)\n\n&quot;, myLevel);

    // [CASE 2] Call by Address
    printf(&quot;2. Call by Address 호출 중...\n&quot;);
    levelUp_Address(&amp;myLevel); // 주소를 넘김
    printf(&quot;&gt;&gt; 호출 후 메인 함수 레벨: %d (레벨 업 성공!)\n&quot;, myLevel);

    return 0;
}</code></pre>
<p>위 코드에서 핵심은 함수를 호출하는 부분에 있다. 
<code>[CASE1]</code> 에서는 <code>levelUp_Value(myLevel);</code> 이렇게 값을 직접 전달했고,
<code>[CASE2]</code> 에서는 <code>levelUp_Address(&amp;myLevel);</code> 이렇게 주소를 전달했다. </p>
<p><code>[CASE1]</code> 이 Call by Value, <code>[CASE2]</code>가 Call by Address 방식이 된다. </p>
<pre><code class="language-c"> levelUp_Value(myLevel);
 printf(&quot;&gt;&gt; 호출 후 메인 함수 레벨: %d (변화 없음!)\n\n&quot;, myLevel);</code></pre>
<p>이 경우, 함수에는 데이터의 값이 복사되어 전달되고,
함수 안에서 level의 값을 1 증가시켰지만, 그 작업은 해당 함수 안에서만 유효하다. 
levelUp_Value함수 밖인 main함수 안에서 level을 출력해보니 그대로 10인 것을 확인할 수 있을 것이다. (직접 실행해보길 바란다)</p>
<pre><code class="language-c"> levelUp_Address(&amp;myLevel); // 주소를 넘김
 printf(&quot;&gt;&gt; 호출 후 메인 함수 레벨: %d (레벨 업 성공!)\n&quot;, myLevel);</code></pre>
<p>이 경우 <code>&amp;</code> 기호를 통해 <code>myLevel</code> 의 값인 <code>10</code> 이 아니라,
<code>myLevel</code> 이라는 변수가 저장된 메모리상의 주소값을 전달하고 있다. </p>
<p>고로, <code>levelUp_Address</code> 함수는 복사된 값을 수정하는게 아니라, 
함수 호출시 들어온 주소지를 타고 넘어가 그 주소지에 있는 변수의 값을 바꾸는 것이다.</p>
<p>고로,  <code>levelUp_Address</code> 함수 밖인 <code>main</code> 함수에서도 <code>level</code> 의 값이 <code>11</code> 로 증가한 것을 확인할 수 있을 것이다. </p>
<p>정리하면, <strong>주소값을 전달하면 함수에서의 작업내용이 전역적으로 반영</strong>된다는 것!
상황에 맞게 잘 사용하자. </p>
<h2 id="16-마치며">16. 마치며..</h2>
<p>포인터의 개념은 처음에 다소 생소하게 다가오지만,<br>Python이나 JavaScript같은 언어만 사용해 온 사람들에게는 신선한 자극을 주기도 한다. </p>
<p>&quot;주소&quot; 라는 개념 자체가 어색하겠지만, 
이를 이해하는 순간 소스코드를 바라보는 안목이 아주 조금은 높아질 것이다. </p>
<p>프로그래머들은 항상 why, how에 집중하여 끊임없이 생각하는 사람이 되어야 할 것이다. </p>
<p>멋진 척 가득했지만 나도 배우는 대학생일 뿐이다.
현학적으로 보였다면 고개 숙여 사과한다. 너그러이 봐주길 바란다. </p>
<p>이상으로 글을 마친다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[JavaScript] 브라우저와 Node.js의 이벤트 루프]]></title>
            <link>https://velog.io/@code_prince/JavaScript-%EB%B8%8C%EB%9D%BC%EC%9A%B0%EC%A0%80%EC%99%80-Node.js%EC%9D%98-%EC%9D%B4%EB%B2%A4%ED%8A%B8-%EB%A3%A8%ED%94%84</link>
            <guid>https://velog.io/@code_prince/JavaScript-%EB%B8%8C%EB%9D%BC%EC%9A%B0%EC%A0%80%EC%99%80-Node.js%EC%9D%98-%EC%9D%B4%EB%B2%A4%ED%8A%B8-%EB%A3%A8%ED%94%84</guid>
            <pubDate>Wed, 21 Aug 2024 11:44:48 GMT</pubDate>
            <description><![CDATA[<h1 id="1-자바스크립트의-두-런타임">1. 자바스크립트의 두 런타임</h1>
<p>런타임을 직관적인 말로 바꾸어 표현하면, &quot;실행기&quot; 라고 할 수 있습니다. </p>
<p>우리가 일상에서 사용하는 크롬 등의 브라우저는 대표적인 자바스크립트 런타임입니다. </p>
<p>즉, 브라우저는 자바스크립트의 실행기인 셈입니다. </p>
<p>Node.js역시 자바스크립트 런타임입니다.
브라우저와는 또 다른 자바스크립트 실행기인 것이죠.</p>
<p>가끔, Node.js를 서버 프레임워크라고 말하는 사람들이 있습니다. 
하지만 노드js는 라이브러리도, 프레임워크도 아닙니다.</p>
<p>정확히 표현하면, Node.js는 그저 자바스크립트를 실행해주는 실행기일 뿐이고,
브라우저 런타임에선 불가능하던 서버 단의 코드도 실행해줄 수 있는 실행기인 셈입니다. </p>
<p>노드JS는 브라우저와 완전히 다른 별개의 실행기라는 사실을 미리 정리해두고 넘어가겠습니다. </p>
<h1 id="2-자바스크립트는-싱글-스레드-언어">2. 자바스크립트는 싱글 스레드 언어</h1>
<p>자바스크립트는 싱글 스레드 언어입니다. 
이는 곧 오직 하나의 호출 스택만을 갖는 언어라는 의미기도 합니다.</p>
<p>조금 간단하게 얘기해보면, 자바스크립트는 언어 본질상으론 한 번에 하나의 작업만을 수행할 수 있습니다. </p>
<p>그런데, 이러한 자바스크립트의 특성은 실제 서비스에서 엄청난 문제를 발생시킬 수 있습니다. </p>
<p>간단한 예를 들어볼까요?</p>
<pre><code class="language-jsx">//예제 코드 제공 - chat gpt

import React, { useEffect } from &#39;react&#39;;

function App() {
  const blockingFetch = () =&gt; {
    // 아주 긴 동기적 작업 시뮬레이션
    const start = Date.now();
    while (Date.now() - start &lt; 5000) {
      // 5초 동안 CPU를 점유
    }

    // 실제 fetch 호출
    fetch(&#39;https://jsonplaceholder.typicode.com/posts/1&#39;)
      .then(response =&gt; response.json())
      .then(data =&gt; {
        console.log(data);
        alert(&#39;Fetch finished!&#39;);
      });
  };

  useEffect(() =&gt; {
    // 컴포넌트가 마운트될 때 블로킹 fetch 호출
    blockingFetch();
  }, []);

  const handleClick = () =&gt; {
    alert(&#39;Button clicked!&#39;);
  };

  return (
    &lt;div&gt;
      &lt;h1&gt;JavaScript Single Thread Example&lt;/h1&gt;
      &lt;button onClick={handleClick}&gt;Click Me!&lt;/button&gt;
    &lt;/div&gt;
  );
}

export default App;
</code></pre>
<p>위의 코드에서 while문을 의도적으로 사용하여 자바스크립트의 비동기 처리를 막아보았습니다. 대표적인 비동기 API인 fetch도, while문 안에선 동기적으로 동작합니다. </p>
<p>다시 코드로 돌아와, 데이터를 불러오는 시간이 아주 길게 걸린다고 가정해보겠습니다. 또한, 현재 while문의 존재로 인해 코드는 동기적으로 동작합니다. </p>
<p>문제는 버튼에서 발생합니다. 
가령 데이터를 불러오는데 시간이 4초 걸린다고 가정하면,</p>
<p>이론상 자바스크립트는 싱글 스레드 언어이기에 데이터를 불러오기 시작한 시점부터 4초동안 다른 작업을 수행할 수 없습니다. </p>
<p>그렇다면, 4초동안 유저는 버튼을 클릭하는 등의 그 어떤 동작도 하지 못하며 그저 가만히 기다려야 하죠.</p>
<p>이런 말도 안 되는 상황이 발생한다면, 성질 급한 한국인들은 물론 전 세계의 서비스 유저들이 이탈할 것입니다. </p>
<p>이상적으론, 데이터를 불러오는 동안에도 버튼을 누르거나 검색을 하는 등, 사용자는 다른 작업들을 문제 없이 수행할 수 있어야 합니다. </p>
<p>즉, 여러 동작을 동시에 처리할 수 있어야 합니다. 
싱글 스레드 언어인 자바스크립트로 어떻게 동시 처리 기능을 구현할 수 있을까요?</p>
<p>정답은 자바스크립트의 런타임인 브라우저와 노드 JS 각각이 가진 추가적인 기능을 활용하는 것입니다. </p>
<h1 id="3-브라우저와-노드js가-여러-작업을-동시에-처리하는-방법">3. 브라우저와 노드JS가 여러 작업을 동시에 처리하는 방법</h1>
<p>결론적으로, 시간이 오래 걸리는 fetch, axios등의 AJAX작업, 이벤트 핸들링 작업, 타이머, Promise 등의 코드들은 일반적인 코드가 수행되는 호출스택과 다른 장소에서 처리됩니다. </p>
<p>조금 더 자세히, 브라우저와 노드JS각각이 어떻게 싱글 스레드 언어인 JS의 한계를 극복하여 동시성 처리를 성공적으로 해낼 수 있는 것인지, 내부 동작 원리를 살펴보겠습니다. </p>
<h2 id="3-1-브라우저의-이벤트-루프">3-1. 브라우저의 이벤트 루프</h2>
<p>JS의 런타임 중, 먼저 브라우저를 살펴보겠습니다. </p>
<p>브라우저 환경에서 자바스크립트를 실행할 경우 시스템의 구조는 아래와 같습니다. </p>
<p><img src="https://velog.velcdn.com/images/code_prince/post/d9d1d9eb-aa26-46af-8d24-327413ed3d5d/image.jpeg" alt=""></p>
<p>주요 구성 요소로는, <code>Heap</code> , <code>Call Stack</code>, <code>Web APIs</code>, <code>Task Queue</code>, <code>Event Loop</code> 가 있습니다. </p>
<p>이중  <code>Web APIs</code> 는 자바스크립트의 내장 기능이 아닌, 브라우저가 제공하는 기능입니다. </p>
<p>각각의 기능에 대해 자세히 살펴본 후, 위의 요소들이 어떻게 유기적으로 동작하는지 최종 정리하겠습니다. </p>
<p><code>챕터 3-1-3</code> 에서 완벽하게 깔끔정리 하였으니, 
우선은 많은 내용이 마구 쏟아져도, 포기하지 말고 각각을 이해하려 노력해주세요.</p>
<h3 id="3-1-1-힙-메모리와-호출-스택">3-1-1. 힙 메모리와 호출 스택</h3>
<p>힙 메모리에는 함수나 변수와 관련된 정보들이 저장됩니다. </p>
<pre><code class="language-js">function foo() {
  console.log(&quot;hi&quot;);
}</code></pre>
<p>위와 같이 foo라는 함수가 정의되면, 
힙 메모리에 foo함수에 관한 내용들이 저장됩니다. </p>
<p>호출 스택은, 실행해야 하는 코드가 쌓이는 공간입니다. </p>
<pre><code class="language-js">foo();</code></pre>
<p>위와 같이 함수를 호출하는 코드를 만나면, 
호출 스택에 <code>foo()</code> 가 들어옵니다.</p>
<p>참고로, 특정 파일이 실행되면 호출스택엔 가장 먼저
<code>anonymous(main함수)</code> 가 들어오고, 파일의 모든 코드가 종료되면 호출 스택에서 빠져나갑니다. </p>
<p><img src="https://velog.velcdn.com/images/code_prince/post/10a28fa8-779d-4173-aa30-39754209ff59/image.jpeg" alt=""></p>
<p>자바스크립트 엔진은 호출 스택에 있는 코드를 하나씩 꺼내어 수행합니다.
이때, 호출 스택에 있는 코드를 수행하기 위해 필요한 함수나 변수 등의 정보는 
힙 메모리에서 찾아 참조합니다. </p>
<h3 id="3-1-2-web-apis백그라운드-태스크-큐-이벤트루프">3-1-2. Web APIs(백그라운드), 태스크 큐, 이벤트루프</h3>
<p>자바스크립트 언어 자체는 싱글 스레드이지만, 자바스크립트 엔진이 브라우저의 Web APIs로 특정 코드를 보내 별도의 스레드를 사용하기에, 동시 작업 처리가 가능합니다. </p>
<p>여기서 Web APIs는, 자바스크립트 엔진인 V8자체의 기능이 아닌, 브라우저가 제공하는 기능입니다. </p>
<p>Web APIs는 C++로 작성되어 있기에, 멀티스레딩이 가능합니다. </p>
<p>호출스택에서 Web APIs로 보내지는 코드들은 다음과 같습니다. </p>
<blockquote>
<ol>
<li>타이머 API:  setTimeout, setInterval, clearTimeout, clearInterval</li>
<li>네트워크 API: fetch, XMLHttpRequest</li>
<li>DOM API: addEventListener, querySelector등등</li>
<li>DB요청</li>
<li>Promise의 then, catch, finally절</li>
</ol>
</blockquote>
<p>이 외에도 많은 것들이 Web APIs로 보내져 V8엔진의 호출 스택과 병렬로 별도의 공간에서 처리됩니다. </p>
<p>이해를 돕기 위해 간단한 예제 코드를 보겠습니다. 
창의력이 부족한 저는 이번에도 예제 코드를 지피티 스승님께 받아오겠습니다.</p>
<p>지피티 선생님께서 주신 코드를 살펴보며, 직접 브라우저와 엔진이 되어 처리 순서를 익혀보겠습니다. </p>
<pre><code class="language-js">console.log(&#39;Start&#39;);

setTimeout(() =&gt; {
    console.log(&#39;Timeout 1&#39;);
}, 0);

console.log(&#39;End&#39;);</code></pre>
<p>자, 우선 가장 먼저 아래의 코드가 실행되어야 합니다. </p>
<pre><code class="language-js">console.log(&#39;Start&#39;);</code></pre>
<p>이는 백그라운드로 넘어가지 않는 일반 코드이므로, 
V8엔진의 호출 스택에서 바로 수행됩니다. </p>
<p><img src="https://velog.velcdn.com/images/code_prince/post/df43e9fd-79ef-44ba-a006-9b18d6756146/image.jpeg" alt=""></p>
<p>파일이 실행되었으니 main함수인 <code>anonymous</code>가 호출스택에 가장 먼저 쌓이고,
그 위에 <code>console.log(&#39;Start&#39;);</code> 가 들어옵니다.</p>
<p>이후 콘솔창에 <code>&quot;start&quot;</code> 라는 문자열이 찍히고,
해당 코드는 모두 실행되었으니 호출 스택에서 <code>console.log(&#39;Start&#39;);</code> 는 사라집니다. </p>
<p>해당 코드가 수행된 이후 각각의 공간은 아래와 같은 상태가 됩니다. </p>
<p><img src="https://velog.velcdn.com/images/code_prince/post/f531aeed-ad5e-4392-a06e-47f6a2649337/image.jpeg" alt=""></p>
<p>이제, 아래의 코드를 실행할 차례입니다.</p>
<pre><code class="language-js">setTimeout(() =&gt; {
    console.log(&#39;Timeout 1&#39;);
}, 0);</code></pre>
<p>이번엔 타이머와 관련된 코드가 들어왔습니다. 
우선 호출 스택에 <code>setTimeout()</code> 이 잠깐 들어옵니다. </p>
<p>그랬더니, V8엔진이 말합니다
&quot;타이머 고객님께서는 옆동네 Web APIs를 이용해주세요.&quot;</p>
<p>이를 현학적으로 &quot;자바스크립트 엔진이 작업을 브라우저에 위임했다&quot; 고 표현하기도 합니다. </p>
<p>어쨌든 타이머는 <code>WebAPIs</code> 로 이동합니다. 
타이머의 시간이 100초든 0초든 타이머 코드는 V8엔진에서 무조건 <code>Web APIs</code> 로 보내버립니다. </p>
<p>백그라운드에 머무는 동안, 타이머에 지정된 시간을 흘려보냅니다.
호출 스택엔 <code>console.log(&quot;End&quot;);</code> 가 들어오고, 바로 실행됩니다. </p>
<p>다만, <code>console.log(&quot;End&quot;);</code> 가 0초짜리 타이머가 Web APIs에 있는 시점에 실행될지, 태스크 큐로 이동한 후에 실행될지 여부는 브라우저의 종류와 환경(브라우저 버전, 사양 등)에 따라 다를 수 있습니다. </p>
<p>확실한 것은, 타이머의 시간이 0초더라도 Web APIs로 한 번 이동한 코드들은 호출 스택이 모두 비워진 후에야 실행될 수 있기에, <code>console.log(&quot;End&quot;);</code> 는 <code>console.log(&quot;Timeout1&quot;);</code> 보다 무조건 먼저 실행된다는 것입니다.</p>
<p><img src="https://velog.velcdn.com/images/code_prince/post/939c5176-6d16-4fd2-bcec-db76d67a1c2a/image.jpeg" alt=""></p>
<p>위의 코드에서는 백그라운드에서 0초의 시간을 대기한 후, 타이머의 콜백함수가 Web APIs에서 태스크 큐로 이동합니다. </p>
<p>브라우저의 Web APIs가 열일하는 동안, V8엔진도 열심히 호출 스택의 코드들을 실행시킵니다. 호출 스택의 <code>console.log(&quot;End&quot;);</code> 도 실행되어 호출 스택에서 제거됩니다. </p>
<p>위의 사진에선 <code>console.log(&quot;End&quot;);</code> 를 호출 스택에 남겨두었는데, 위에서 언급하였듯 <code>console.log(&quot;End&quot;);</code> 가 0초짜리 타이머가 Web APIs에 있는 시점에 실행될지, 태스크 큐로 이동한 후에 실행될지 여부는 브라우저의 종류와 환경(브라우저 버전, 사양 등)에 따라 다를 수 있는데, 위의 경우엔 후자의 상황을 가정한 것이라고 생각해주시면 됩니다. </p>
<p>아무튼 현재 논의의 핵심은, Web APIs에서 타이머에 지정된 시간이 모두 흐르면, 타이머의 콜백함수가 태스크 큐로 이동한다는 사실입니다. </p>
<p>사실 타이머의 시간을 0초로 지정한 예시는, 이 내용을 처음 접하는 사람들에게 꽤나 어렵게 다가갑니다. </p>
<p>조금 더 쉬운 예시로서 만일 타이머에 지정된 시간이 5초였다면, Web APIs에서 5초를 대기한 후 타이머의 콜백이 태스크 큐로 전달됩니다. </p>
<p>지금까지의 논의를 통해 정리할 수 있는 사실은, ** 태스크 큐는 백그라운드(Web APIs)의 콜백함수가 보관되는 공간** 이라는 것입니다. </p>
<p>타이머의 시간이 모두 흐른 뒤 각각의 공간의 상태는 아래와 같습니다. </p>
<p><img src="https://velog.velcdn.com/images/code_prince/post/57d5b9b1-44a9-48e7-bdc7-56e1d630136d/image.jpeg" alt=""></p>
<p>타이머의 콜백인 <code>console.log(&quot;Timeout1&quot;);</code> 이 백그라운드에서 태스크 큐로 이동하였습니다. </p>
<p>브라우저가 열심히 작업하는 동안, V8엔진도 열심히 일을 합니다. 타이머를 애초에 Web APIs로 위임해버렸으니, 백그라운드에서 타이머 코드가 돌아가는 동안 V8엔진은 <code>console.log(&quot;End&quot;);</code> 를 수행한 것입니다.</p>
<p>콘솔창에 End를 찍고, 호출 스택에서 <code>console.log(&quot;End&quot;);</code> 가 사라집니다. </p>
<p>이벤트 루프는 호출스택을 계속 주시하다가, <code>anonymous</code> 를 제외하고는 호출 스택이 비어있는지 확인합니다. </p>
<p><code>anonymous</code> 외에 아무런 요소도 호출 스택에 남아있지 않다면, 이벤트 루프는 태스크 큐의 요소들을 V8의 호출 스택으로 올려보냅니다. </p>
<p><code>console.log(&quot;End&quot;);</code> 를 수행한 이후엔, 호출 스택이 비어있으니 이를 이벤트 루프가 감지하여 태스크 큐의 <code>console.log(&quot;Timeout1&quot;);</code> 을 V8의 호출스택으로 보내줍니다.</p>
<p>현 시점에서의 각 공간의 상태는 아래와 같습니다. </p>
<p><img src="https://velog.velcdn.com/images/code_prince/post/24017eff-75e5-4922-84aa-ace88364d64a/image.jpeg" alt=""></p>
<p>이후  <code>console.log(&quot;Timeout1&quot;);</code> 이 실행되어 콘솔창에 <code>&quot;Timeout1&quot;</code> 이 출력되고, 해당 파일의 모든 코드가 실행되었으니 <code>anonymous</code> 도 사라집니다.</p>
<p>결론적으로 </p>
<pre><code class="language-js">console.log(&#39;Start&#39;);

setTimeout(() =&gt; {
    console.log(&#39;Timeout 1&#39;);
}, 0);

console.log(&#39;End&#39;);</code></pre>
<p>이 코드의 실행 결과는 
<code>Start, End, Timeout1</code> 순으로 출력됩니다.</p>
<p>타이머가 0초임에도, Web APIs로 한 번 보내진 코드들은
호출스택이 비어야 이벤트루프가 호출스택으로 보내주기 때문에
End보다 늦게 출력된다는 사실을 기억해야 합니다. </p>
<h3 id="3-1-3-브라우저-동시성-처리-동작원리-깔끔정리">3-1-3. 브라우저 동시성 처리 동작원리 깔끔정리</h3>
<ol>
<li><p>일반적인 코드들은 호출 스택에서 처리된다</p>
</li>
<li><p>타이머, AJAX, 이벤트, 프로미스 등의 코드가 호출스택에 들어오면 V8엔진은 이를 직접 처리하지 않고 브라우저의 Web APIs로 보내버린다(위임한다). 이는 자바스크립트 엔진 바깥의 공간이기에, 자바스크립트가 싱글스레드 언어임에도 동시 처리가 가능한 비결이다</p>
</li>
<li><p>Web APIs에서 지정된 타이머 시간이 전부 흐르거나, AJAX요청시 데이터를 다 받았거나, 이벤트가 실제로 발생한 경우, 콜백함수가 태스크 큐로 전달된다</p>
</li>
<li><p>이벤트 루프는 호출스택이 비었는지 주시하다가, 호출스택이 빈 경우 태스크 큐의 코드들을 내부적 우선순위에 맞추어 호출스택으로 올려보낸다. </p>
</li>
</ol>
<h3 id="3-1-4-태스크-큐의-우선순위">3-1-4. 태스크 큐의 우선순위</h3>
<p>위의 3-1-3챕터의 4번 항목에서, 이벤트 루프는 호출스택이 비었는지 주시하다가, 호출스택이 빈 경우 태스크 큐의 코드들을 내부적 우선순위에 맞추어 호출스택으로 올려보낸다고 하였습니다. </p>
<p>그런데, 가만 생각해보면 태스크 큐는 FIFO자료구조인 <code>큐(QUEUE)</code> 인데, 
먼저 들어온 것이 최상의 우선순위를 갖는 것이 아니라면, 뭔가 이상합니다. </p>
<p>사실, Web APIs의 콜백은, 하나의 태스크 큐로 전달되는 것이 아니라, 
<code>(macro)태스크 큐</code>, <code>마이크로 태스크 큐</code>, <code>애니메이션 프레임 큐</code> 이렇게 3개의 큐로 전달됩니다. </p>
<p>이들 큐들 사이에 우선순위가 존재하고, 각 큐에선 당연히 FIFO방식으로 동작합니다. </p>
<p>이들 각 큐에 대해 정리해보겠습니다. </p>
<p>아래와 같이 세 개의 큐는 <strong>고유한 우선 순위</strong>를 부여받습니다.</p>
<p><img src="https://velog.velcdn.com/images/code_prince/post/3da84c08-ffbb-4525-8112-97f7ef258d81/image.jpeg" alt=""></p>
<p>Promise의 핸들러인 then, catch, finally와, DOM요소의 변화를 감지하는 MutationObserver가 호출스택이 비워진 후 최고의 우선순위를 갖습니다. </p>
<p>이후, 애니메이션 프레임 큐가 호출 스택으로 올라갈 기회를 얻습니다. </p>
<p>마지막으로 타이머나 AJAX, 클릭 이벤트의 콜백함수 등이 담길 (Macro)태스크 큐가 이벤트 루프의 조사를 받습니다. </p>
<p>깔끔하게 정리하면,
브라우저 상에서 자바스크립트를 실행할 경우 코드의 실행 순서는 아래와 같습니다. </p>
<blockquote>
<ol>
<li>호출 스택</li>
<li>마이크로 태스크 큐: Promise 핸들러, MutationObserver</li>
<li>애니메이션프레임 큐</li>
<li>(매크로)태스크 큐: AJAX(fetch등..), 이벤트 발생처리, 타이머</li>
</ol>
</blockquote>
<p>그렇다면, 이를 통해 얻을 수 있는 교훈이 하나 있습니다. </p>
<p>바로, 브라우저 상에선 타이머가 우리가 지정한 시간보다 더 오랜 시간을 기다려야 할 수도 있다는 것입니다. </p>
<p>타이머 코드가 실행되기 위해선 최종적으로 타이머의 콜백이 호출스택으로 올라가야 하는데, 
타이머의 콜백이 있는 매크로 태스크 큐보다 높은 우선순위를 갖는 큐들에서 오랜 시간이 소요된다면, 타이머에 지정한 시간 후에 콜백이 동작하지 않을 수 있겠습니다.</p>
<h2 id="3-2-nodejs의-이벤트-루프">3-2. Node.js의 이벤트 루프</h2>
<p>이 글의 1장에서 굳이 브라우저와 노드js에 관한 설명을 했는데,
바로 지금 활용하기 위함입니다. </p>
<p>노드JS는 브라우저와 독립된, 하나의 실행기(런타임)이라고 하였습니다 </p>
<p>즉, 노드 JS로 자바스크립트를 구동시킨다면, 브라우저의 기능인 Web APIs가 존재하지 않습니다. </p>
<p>자바스크립트 자체는 싱글 스레드 언어인데, 노드JS는 논블로킹I/O를 어떻게 구현시킬 수 있는 것일까요?</p>
<p>정답은 <code>libuv</code> 라이브러리입니다. </p>
<p>브라우저가 아니기에 Web APIs는 없지만, 멀티스레딩을 지원하는 C언어로 작성된 libuv라이브러리가 노드js환경에서 Web APIs의 역할을 대체합니다.</p>
<p>노드JS자체적으로 멀티스레딩의 구현이 불가능하냐고 묻는다면, 그렇진 않습니다.
사실 노드JS 런타임 자체는 워커스레드의 존재로 멀티스레딩이 가능합니다.</p>
<p>다만, 코드가 복잡해지기에 워커스레드는 잘 사용하지 않습니다. </p>
<p>아무쪼록, 멀티스레딩은 노드 자체의 기능을 활용하지 않고, C언어로 구현된 별도의 외부 라이브러이에 위임하는 구조라는 것을 기억해야 합니다. </p>
<p>브라우저와의 또 다른 차이점이라면, Node.js는 서버측 프로그래밍을 위해 개발되었기에, 브라우저 환경과 달리 자바스크립트 엔진이 애니메이션프레임큐를 사용하지 않습니다.</p>
<p>노드JS의 내부 구조를 다이어그램으로 시각화하면 아래와 같습니다. </p>
<p><img src="https://velog.velcdn.com/images/code_prince/post/057a7b9d-4f06-4524-88e2-03ec6b2acd92/image.jpeg" alt=""></p>
<h1 id="4-결론">4. 결론</h1>
<p>자바스크립트는 싱글 스레드 언어지만, 
브라우저 런타임에선 Web APIs,
노드 JS 런타임에선 libuv의 도움을 얻어</p>
<p>자바스크립트 V8엔진 외부의 공간에서도 일부 코드를 수행하며
동시성 처리를 구현할 수 있습니다. </p>
<p>감사합니다. </p>
<p>참고한 자료: <a href="https://www.youtube.com/watch?v=8aGhZQkoFbQ">https://www.youtube.com/watch?v=8aGhZQkoFbQ</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[proxy server]]></title>
            <link>https://velog.io/@code_prince/NGINX-Nginx-1-%ED%94%84%EB%A1%9C%EB%8D%95%EC%85%98-%EB%8B%A8%EA%B3%84%EC%97%90%EC%84%9C-WAS-%EC%95%9E%EC%97%90-%EC%9B%B9-%EC%84%9C%EB%B2%84%EB%A5%BC-%EB%91%90%EB%8A%94-%EC%9D%B4%EC%9C%A0</link>
            <guid>https://velog.io/@code_prince/NGINX-Nginx-1-%ED%94%84%EB%A1%9C%EB%8D%95%EC%85%98-%EB%8B%A8%EA%B3%84%EC%97%90%EC%84%9C-WAS-%EC%95%9E%EC%97%90-%EC%9B%B9-%EC%84%9C%EB%B2%84%EB%A5%BC-%EB%91%90%EB%8A%94-%EC%9D%B4%EC%9C%A0</guid>
            <pubDate>Thu, 27 Jun 2024 09:17:55 GMT</pubDate>
            <description><![CDATA[<p>안녕하세요, 
취미로 여행 관련 웹 서비스를 개발한지 어느덧 3개월차,</p>
<p>문득 이런 생각이 들더군요.</p>
<blockquote>
<p>개발 지식을 갖춘 악의적인 사용자가 나의 서버에 접근하는 것을 어떻게 방어할 것인가? 실제 서비스들은 프로덕션 단계에서 어떻게 서버를 외부의 접근으로부터 보호하는가 ? </p>
</blockquote>
<p>해답은 프록시 서버였습니다. </p>
<p>제 서비스는 React로 프론트엔드를, Django로 백엔드를 개발하고 있습니다.</p>
<p>정말 부끄럽지만 오늘 이전의 저는, 
모든 웹은 그저 클라이언트와 서버 이렇게 두 개체 사이의 통신이라고 단순하게 생각하고 있었습니다. 
<img src="https://velog.velcdn.com/images/code_prince/post/775a19bc-2de9-4245-a847-bfe2e096c41e/image.JPG" alt="">
이렇게 유치원생도 알 것 같은 유치한 구조로 말이죠. </p>
<p>물론 기본적인 구조는 저 사진과 같지만, 
전 프로덕션 단계에서 프록시 서버를 배치할 생각 조차도 하지 못했습니다.</p>
<p>캐싱, 로드밸런싱, 암호화와 보안 자체를 생각하지 못했던 것입니다.</p>
<p>그래서 오늘은 proxy서버란 무엇이고, proxy서버를 서비스에 배치함으로 얻을 수 있는 이점은 무엇인지 알아보겠습니다. </p>
<h2 id="proxy-서버란">proxy 서버란?</h2>
<p>proxy는 대리인을 뜻하는 영어단어입니다. 
proxy 서버는 &quot;대리 서버&quot; 정도가 되겠네요.</p>
<p>그렇다면 프록시 서버는 무엇을 대신한다는 걸까요?</p>
<p>여러분의 이해를 돕기 위해, 제가 만든 다이어그램을 함께 살펴보겠습니다. 
<img src="https://velog.velcdn.com/images/code_prince/post/5eb4103e-43dd-433e-a050-19b6e1c3f5cf/image.JPG" alt="">
이렇게, 클라이언트 단에서 서버와 직접 통신하는 것이 아니라,
사이에 프록시 서버를 두어 클라이언트는 백엔드 서버가 아닌 프록시 서버와 소통하는 것입니다. </p>
<p>비유하자면, 백엔드 서버는 조직의 우두머리이고 프록시 서버는 조무래기입니다.
클라이언트는 대장인 백엔드 서버와 싸울 수 조차 없습니다. 앞에 부하 병사들이 가로막고 있기 때문이죠. </p>
<p>정리하면, 클라이언트와 서버는 직접 통신하지 않습니다. 프록시를 매개로 두 단계에 걸쳐 통신합니다. </p>
<h2 id="프록시-서버의-종류----forward-reverse-proxy">프록시 서버의 종류 -  forward, reverse proxy</h2>
<p>이제 막 프록시를 이해했는데, 아직 알아야 할 내용이 많이 남아있습니다. 
우선, 프록시 서버의 종류를 알아보겠습니다. </p>
<p>프록시 서버는 forward와 reverse이렇게 두 가지입니다. </p>
<p>이 둘의 차이점은, 어떤 상황에 쓰이냐에 있습니다. </p>
<p><code>forward proxy</code> : 클라이언트가 서버로 <code>Request</code> 를 보낼 때 사용됨
<code>reverse proxy</code> : 서버가 클라이언트로 <code>Response</code> 를 보낼 때 사용됨</p>
<p>이해를 높이고자, 다이어그램을 하나 그려보았는데요.
함께 살펴보겠습니다.
<img src="https://velog.velcdn.com/images/code_prince/post/29aeeaa1-ed56-4995-9d0e-5cb1e095d0b4/image.jpeg" alt="">
클라이언트가 서버로 &quot;이미지를 띄워줘!&quot; 라는 요청을 보냈다면,
클라이언트의 요청은 우리 웹서비스의 백엔드 서버로 가는 것이 아니라,
백엔드 서버 앞단에 세워둔 대리 서버인 forward proxy서버로 갑니다. </p>
<p>그 후, forward proxy 서버가 받은 요청을 다시 backend server로 보냅니다. </p>
<p>이제, 백엔드 서버에서 Response를 보낼 땐, 바로 클라이언트로 보내지 않고, 
백엔드 서버인 web application server(WAS) 앞단에 세워둔 reverse proxy server로 보내고, reverse proxy서버는 받은 응답을 다시 클라이언트에 전달합니다. </p>
<p>즉, 요청을 보내거나 응답을 받을 때, 클라이언트는 서버에 직접 접근하지 못합니다. </p>
<h2 id="프록시-서버를-사용하는-이유">프록시 서버를 사용하는 이유</h2>
<p>proxy 서버를 사용하면 무엇이 이득일까요?
또, 언제 사용해야 할까요?</p>
<h2 id="1-보안-강화">1. 보안 강화</h2>
<p>제가 생각하기론 프록시 서버를 두는 가장 큰 이유는 사용자와 서버 및 데이터베이스 보안을 강화하기 위함이 아닌가 싶습니다. </p>
<p>각각에 대해 조금 더 자세히 알아보겠습니다. </p>
<h3 id="1-1-forward-proxy---user-safety">1-1. forward proxy - user safety</h3>
<p>포워드 프록시 서버를 배치하면, 백엔드 서버에 요청을 보낸 최종 주체는 클라이언트가 아닌 forward proxy입니다. 즉, 해커가 서버에 요청을 보낸 클라이언트의 IP를 탈피해도, 이는 포워드 프록시의 주소일 뿐입니다. </p>
<p>이런 방식으로, 서비스 유저들의 정보를 보호해줄 수 있습니다. </p>
<h3 id="1-2-reverse-proxy---server-db-safety">1-2. reverse proxy - server, DB safety</h3>
<p>리버스 프록시 서버를 배치하면, 클라이언트와 만나 Response를 전달해주는 상대는 백엔드 서버가 아닌 리버스 프록시 서버가 됩니다. 즉, 클라이언트 단에서 응답을 보낸 상대의 IP주소 등의 정보를 얻었다 하더라도, 이는 WAS앞의 프록시의 정보일 뿐입니다. </p>
<p>백엔드 서버의 IP주소를 숨겼으니, DDoS공격이나 기타 공격으로부터 우리의 서버를 지킬 수 있습니다. 백엔드 서버는 데이터베이스 서버와도 연결되어 있는만큼, 보안이 정말 중요하다 생각됩니다.</p>
<p>어찌되었든 이렇게 하여, 서버를 클라이언트로부터 보호할 수 있습니다. </p>
<h2 id="2-로드밸런싱">2. 로드밸런싱</h2>
<p>이건 reverse proxy에 해당하는 내용입니다. 
실제로 대부분의 서비스는 리버스 프록시 서버에 nginx나 apache와 같은 웹서버를 배치하여 로드밸런싱을 지원하고 있다고 합니다. </p>
<p>트래픽이 많아지면 서버를 여러 대 구축해야 할 것이고,
병목현상이나 서버의 부하를 막기 위해 트래픽을 여러 서버에 고르게 분산시켜주는 로드밸런싱을 proxy서버에게 시킬 수 있습니다. </p>
<h2 id="3-캐싱">3. 캐싱</h2>
<p>캐싱이란, 자주 사용되는 데이터를 임시로 저장해두는 것이란걸 모두 알고 계실 겁니다.</p>
<p>포워드와 리버스 모두에서, 자주 사용되는 데이터를 프록시 서버에 미리 저장해두면,
이를 다시 로드할 필요 없이 빠르게 재사용할 수 있어 자원을 절약하고 서비스를 최적화 할 수 있습니다. </p>
<p>예를 들어, 프론트 단에서 API로 받아온 호텔 정보를 캐싱해두어 API fetching을 다시 할 필요가 없으니 시간을 절약할 수 있습니다.</p>
<h1 id="마무리">마무리</h1>
<p>이렇게, 오늘날의 많은 서비스들은 서버와 클라이언트 사이에 프록시 서버를 두어
보안과 성능을 모두 강화하고 있습니다.</p>
<p>저도 조만간 제 여행 웹서비스에 reverse proxy 서버를 배치하려고 합니다.
웹 서버인 nginx를 배치하려 하는데, 조만간 nginx 포스팅 시리즈도 연재할 예정이니, 많은 관심 부탁드립니다. </p>
<p>감사합니다. </p>
<p>참고한 영상: <a href="https://www.youtube.com/watch?si=PHd_o42CwMjRgbgV&amp;v=9t9Mp0BGnyI&amp;feature=youtu.be">https://www.youtube.com/watch?si=PHd_o42CwMjRgbgV&amp;v=9t9Mp0BGnyI&amp;feature=youtu.be</a></p>
<h1 id="비고---web-server-vs-backend-serverwas">비고 - web server vs Backend server(WAS)</h1>
<p>비고: 포스팅에서 &quot;WAS&quot; 라는 개념이 등장했는데, 이는 web application server의 줄임말입니다. 저의 경우 Django로 백엔드를 개발하고 있으니, 개발 단계에서는 Django  개발 서버가 WAS가 됩니다. </p>
<p>웹 애플리케이션 서버(백엔드서버)와 웹서버(리버스프록시의 역할을 자연스레 하게 됨)는 아예 다른 개념입니다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[React] All About React Hook Rules (feat. lexical scope, impure function, spread assignment)]]></title>
            <link>https://velog.io/@code_prince/React-All-About-React-Hook-Rules-feat.-lexical-scope-impure-function-spread-assignment</link>
            <guid>https://velog.io/@code_prince/React-All-About-React-Hook-Rules-feat.-lexical-scope-impure-function-spread-assignment</guid>
            <pubDate>Tue, 25 Jun 2024 11:37:31 GMT</pubDate>
            <description><![CDATA[<p>Meta 공식 가이드에서 제공하는 React Hook 사용규칙을 정리해볼게요.</p>
<h1 id="hook-전반에-관한-규칙">hook 전반에 관한 규칙</h1>
<p>먼저, 리액트 훅이라면 useState든 useEffect든 종류에 상관없이 지켜야 할 약속을 알아볼게요.</p>
<h3 id="1-react-hook은-리액트-컴포넌트-함수에만-사용하라">1. React Hook은 리액트 컴포넌트 함수에만 사용하라</h3>
<p>리액트 hook은, 일반 자바스크립트 함수에서는 사용할 수 없습니다. 
오직 리액트 컴포넌트에서만 사용할 수 있는데요.</p>
<p>그렇다면, 일반 자바스크립트 함수와 리액트 컴포넌트 함수(함수형 컴포넌트)는 어떻게 구분해야 할까요?</p>
<p>간단합니다! 함수의 이름이 대문자로 시작하면서 JSX를 return한다면, 
그건 리액트 컴포넌트겠네요!</p>
<p>이제, 지금까지의 이야기를 코드로 살펴볼게요.</p>
<h3 id="잘못된-코드">잘못된 코드</h3>
<p>아래는 잘못된 코드입니다. </p>
<pre><code class="language-jsx">import React, { useState } from &#39;react&#39;;

// 일반 JavaScript 함수
function calculateSum(a, b) {
  // 오류 발견!
  const [sum, setSum] = useState(0);

  setSum(a + b);
  return sum;
}</code></pre>
<p>어디가 잘못된 부분인지 보이시나요?</p>
<p>네 맞습니다! 이 함수는 JSX를 리턴하지도 않고, 이름도 소문자로 시작하는 일반 자바스크립트 함수입니다. </p>
<p>이런 일반 자바스크립트 함수에서 react hook인 useState를 사용할 수 없습니다. </p>
<h3 id="2-react-hook은-리액트-컴포넌트-함수의-최상위-렉시컬-스코프에서만-사용하라setstate-제외">2. React Hook은 리액트 컴포넌트 함수의 최상위 렉시컬 스코프에서만 사용하라(setState 제외)</h3>
<p>렉시컬 스코프는, 함수의 위치에 따라 결정되는 scope를 의미합니다. 
그냥 scope라고 생각하셔도 큰 문제는 없습니다. </p>
<p>만일 누군가 &quot;최상위 렉시컬 스코프를 갖는 변수&quot; 라고 말하면, 
중첩된 함수 중 가장 외곽에 선언된 변수를 의미한다고 생각하면 됩니다. </p>
<p>개발을 하다 보면, 함수 안에 함수를 사용하는 경우는 정말 많습니다.
당장 리액트 컴포넌트만 하더라도 하나의 함수이고,
 그 안에 여러 이벤트 핸들러 함수를 정의하죠.</p>
<p> 그럼, 최외곽 함수인 리액트 컴포넌트와, 
 리액트 컴포넌트 함수 안에 있는 이벤트 핸들러 함수 중 어디에 hook을 선언하느냐에 따라 
 scope가 달라질 수 있겠다는  것을 쉽게 이해할 수 있습니다. </p>
<p> React 의 hook은, 컴포넌트 내에서 최 외곽의 스코프에 사용해야 합니다. 
 아래는 잘못된 용례입니다. </p>
<h3 id="잘못된-코드1">잘못된 코드1</h3>
<pre><code class="language-jsx">import { useState } from &quot;react&quot;;
export default function ControlledComp() {


  const handleRangeChange = (e) =&gt; {
    const [userSatisfactionNum, setUserSatisfactionNum] = useState(0);
    setUserSatisfactionNum((prev) =&gt; e.target.value);
  };

  const handleSubmit = (e) =&gt; {
    e.preventDefault();
  };

  return (
    &lt;fieldset&gt;
      &lt;form onSubmit={handleSubmit}&gt;
        &lt;label htmlFor=&quot;slider&quot;&gt;회원님의 만족도를 입력해주십사. &lt;/label&gt; &lt;br /&gt;
        &lt;h2&gt;현재 만족도: {userSatisfactionNum}점&lt;/h2&gt;
        &lt;input
          id=&quot;slider&quot;
          type=&quot;range&quot;
          min={0}
          max={10}
          value={userSatisfactionNum}
          onChange={handleRangeChange}
        /&gt;
        &lt;br /&gt;
        &lt;input
          type=&quot;submit&quot;
          disabled={userSatisfactionNum &gt;= 8 ? false : true}
        /&gt;
      &lt;/form&gt;
    &lt;/fieldset&gt;
  );
}
</code></pre>
<p>이상한 부분을 잘 발견하셨나요?</p>
<pre><code class="language-jsx"> const handleRangeChange = (e) =&gt; {
    const [userSatisfactionNum, setUserSatisfactionNum] = useState(0);
    setUserSatisfactionNum((prev) =&gt; e.target.value);
  };
</code></pre>
<p>네, 바로 이 부분입니다. 
리액트 hook인 useState를 컴포넌트 함수 내에 사용하고 있네요.</p>
<p>이렇게 되면, handleRangeChange 함수는 컴포넌트 함수 안에 있는 함수이기에, 
이 hook의 스코프(렉시컬 스코프)는 이 함수로 제한되는 문제가 생깁니다. </p>
<p>따라서, 다음과 같이 useState의 위치를 컴포넌트 최상위 스코프로 이동시켜야 합니다. </p>
<h3 id="잘못된-코드-2">잘못된 코드 2</h3>
<pre><code class="language-jsx">if user {
  useEffect(() =&gt; {/*생략*/},[user])
}</code></pre>
<p>리액트 훅인 useEffect를 if문 안에 사용하고 있으니 오류입니다. </p>
<p>리액트 훅은 if, for, function 등 블록 형태의 그 어떤 곳 내부에도 작성하시면 안 됩니다. 최상위 렉시컬 스코프에 꼭 적어주세요.</p>
<p>참고로, setState함수는 블록문 내에서 써도 아무런 상관이 없으며,
오히려 조건문 없이 쓰는게 이상합니다. 이 녀석은 예외란 점도 잘 기억해두기로 하겠습니다.</p>
<h3 id="바른-코드">바른 코드</h3>
<pre><code class="language-jsx">import { useState } from &quot;react&quot;;
export default function ControlledComp() {
  const [userSatisfactionNum, setUserSatisfactionNum] = useState(0);

  const handleRangeChange = (e) =&gt; {

    setUserSatisfactionNum((prev) =&gt; e.target.value);
  };

  const handleSubmit = (e) =&gt; {
    e.preventDefault();
  };

  return (
    &lt;fieldset&gt;
      &lt;form onSubmit={handleSubmit}&gt;
        &lt;label htmlFor=&quot;slider&quot;&gt;회원님의 만족도를 입력해주십사. &lt;/label&gt; &lt;br /&gt;
        &lt;h2&gt;현재 만족도: {userSatisfactionNum}점&lt;/h2&gt;
        &lt;input
          id=&quot;slider&quot;
          type=&quot;range&quot;
          min={0}
          max={10}
          value={userSatisfactionNum}
          onChange={handleRangeChange}
        /&gt;
        &lt;br /&gt;
        &lt;input
          type=&quot;submit&quot;
          disabled={userSatisfactionNum &gt;= 8 ? false : true}
        /&gt;
      &lt;/form&gt;
    &lt;/fieldset&gt;
  );
}
</code></pre>
<p>수정된 부분이 보이시나요?</p>
<pre><code class="language-jsx">export default function ControlledComp() {
  const [userSatisfactionNum, setUserSatisfactionNum] = useState(0);

  const handleRangeChange = (e) =&gt; {
 //중략......</code></pre>
<p>ControlledComp 함수형 컴포넌트의 최상위 렉시컬 스코프(가장 바깥의 scope)로 리액트 훅이 잘 이동된 것을 확인할 수 있습니다.</p>
<h1 id="useeffect-훅과-관련된-규칙">useEffect 훅과 관련된 규칙</h1>
<h3 id="1-impure-function은-useeffect훅과-사용하라">1. impure function은 useEffect훅과 사용하라</h3>
<p>순수하지 않은 함수란 무엇일까요?</p>
<p>순수한 함수란, side effect가 없는 함수이고,
순수하지 않은 함수란, side effect를 갖는 함수라고 정의합니다.</p>
<p>그런데, 자바스크립트에서 말하는 side effect란 무엇인지
쉽게 와닿지 않으실 것 같습니다. </p>
<p>그래서 먼저, side effect에 대해 알아보겠습니다. </p>
<h3 id="side-effect의-의미와-순수한-함수-순수하지-않은-함수">side effect의 의미와 순수한 함수, 순수하지 않은 함수</h3>
<p>정의를 내려보면 다음과 같습니다. </p>
<blockquote>
<p>side effect: 함수가 함수 외부의 값을 변경하거나, 동작하는데 코드 외부의 것을 필요로 하는 것(외부 자원에 의존하는 것)</p>
</blockquote>
<p>조금 쉽게, 제가 상상할 수 있는 가장 쉬운 상황으로 예를 들어보겠습니다. </p>
<pre><code class="language-jsx">const initialVal = 0;

const func1 = () =&gt; {
  return initialVal += 1;
}

const func2 = (num1, num2) =&gt; {
  return num1 + num2
}
</code></pre>
<p>1번 함수는 initialVal이라는 전역변수를 수정하고 있는데요,
이는 func1함수 자기 자신 바깥의 변수를 수정하고 있는 것이네요. </p>
<p>이런 경우 함수 밖의 변수를 수정하는 side effect가 발생했으니 함수 func1은 &quot;순수하지 않은 함수다&quot; 라고 합니다. </p>
<p>2번 함수는 함수 외의 값을 수정하지도 않고,  함수가 동작하는데 함수 외부의 API나 그 어느 도움도 필요하지 않습니다. </p>
<p>이런 함수를 두고 &quot;side effect가 없는 함수다, 순수한 함수다, pure한 함수다&quot; 라고 말합니다. </p>
<p>즉, 어떤 함수가 외부의 값을 수정하지 않고, 외부 값이나 API등의  외부 자원에 의존하지 않고 동작하는 함수는 전부 pure한 함수입니다. </p>
<p>그럼, 개발을 하며 자주 사용할 함수 중 대표적인 비순수함수엔 뭐가 있을까요?</p>
<pre><code class="language-js">fetch(&#39;https://api.example.com/data&#39;)
  .then(response =&gt; response.json())
  .then(data =&gt; console.log(data));</code></pre>
<p>fetch는 인자로 전달된 외부 주소와 통신합니다.
즉, 외부 자원으로부터 데이터를 얻어오고 있죠.</p>
<p>함수가 동작하는데, 함수 바깥의 자원에 의존하고 있습니다. 
(외부 자원의 도움 없이 100% 동작하지 못합니다)</p>
<p>이런 경우 fetch함수는 side effect를 가진, impure한 함수라고 말합니다. </p>
<p>이제, 어느정도 느낌이 옵니다. </p>
<p>외부와 통신하는 모든 상황들(DB통신, 백-프론트엔드 통신, API사용 등등...)을 처리하는 함수는 순수하지 않은 함수들이니,
meta의 가이드에 따라 useEffect 훅에 넣어 사용하는 것이 좋겠습니다. </p>
<h3 id="impure한-함수를-왜-useeffect훅과-사용해야-하는가">impure한 함수를 왜 useEffect훅과 사용해야 하는가?</h3>
<p>대체 이렇게 함으로 얻을 수 있는 이점이 무엇일까요?</p>
<p>우선, 아래의 코드를 살펴볼게요.</p>
<pre><code class="language-jsx">import React, { useState } from &#39;react&#39;;

function FetchDataComponent() {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);

  const fetchData = async () =&gt; {
    try {
      const response = await fetch(&#39;https://api.example.com/data&#39;);
      const data = await response.json();
      setData(data);
      setLoading(false);
    } catch (error) {
      console.error(&#39;Error fetching data:&#39;, error);
      setLoading(false);
    }
  };


  fetchData(); 

  return (
    &lt;div&gt;
      {loading ? (
        &lt;p&gt;Loading...&lt;/p&gt;
      ) : (
        &lt;div&gt;
          &lt;h1&gt;Data fetched:&lt;/h1&gt;
          &lt;pre&gt;{JSON.stringify(data, null, 2)}&lt;/pre&gt;
        &lt;/div&gt;
      )}
    &lt;/div&gt;
  );
}

export default FetchDataComponent;
</code></pre>
<p>리액트 컴포넌트인 <code>fetchDataComponent</code> 안에, impure한 함수 fetchData가 존재합니다. </p>
<p>그런데, fetchData함수를 useEffect훅 없이 사용하고 있습니다. </p>
<pre><code class="language-js">fetchData(); </code></pre>
<p>이렇게요. </p>
<p>리액트 컴포넌트에서는, useEffect훅 안에 있지 않은 모든 함수들을 컴포넌트가 렌더링 될 때마다 다시 실행시킵니다. </p>
<p>그런데, 리액트 컴포넌트가 재 렌더링 되는 상황을 생각해보면...
state값이나 prop값이 바뀌기만 하면 컴포넌트는 재 렌더링 되는데, 
이때마다 fetch 작업을 다시 해준다는 것은 불필요하며 비용을 낭비하는 행위입니다. </p>
<p>이해를 위해 fetchData함수는 인자로 전달받은 url로 이동해 50만개의 호텔 정보 데이터를 받아온다고 생각해볼게요. </p>
<p>이때, 컴포넌트 내의 어떤 state하나만 변경되어도, 컴포넌트는 내부적으로 다시 렌더링 될 것이고, 컴포넌트 내의 함수들은 전부 재실행되어야 합니다. </p>
<p>50만개의 데이터를 이미 불러왔는데, state하나가 바뀔 때마다 또 다시 50만개의 데이터를 불러와야 한다면, 이는 상당히 비효율적이며 성능을 저하시키는 일입니다. </p>
<p>그래서, useEffect훅의 의존성 배열에 특정 상태를 전달해,
fetching 작업을 다시 하는 것에 타당성을 부여하는 특정 상태변화에만 반응해
컴포넌트 재 렌더링 때마다 불필요하게 fetch하는 것을 막을 수 있습니다. </p>
<p>즉, 앱의 성능 최적화에 도움이 됩니다. </p>
<p>아래는 수정된 코드입니다. </p>
<pre><code class="language-jsx">import React, { useState, useEffect } from &#39;react&#39;;

function FetchDataComponent() {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);

  const fetchData = async () =&gt; {
    try {
      const response = await fetch(&#39;https://api.example.com/data&#39;);
      const data = await response.json();
      setData(data);
      setLoading(false);
    } catch (error) {
      console.error(&#39;Error fetching data:&#39;, error);
      setLoading(false);
    }
  };

  useEffect(() =&gt; {fetchData()}, [/*반응할 state*/]);



  return (
    &lt;div&gt;
      {loading ? (
        &lt;p&gt;Loading...&lt;/p&gt;
      ) : (
        &lt;div&gt;
          &lt;h1&gt;Data fetched:&lt;/h1&gt;
          &lt;pre&gt;{JSON.stringify(data, null, 2)}&lt;/pre&gt;
        &lt;/div&gt;
      )}
    &lt;/div&gt;
  );
}

export default FetchDataComponent;</code></pre>
<p>변화한 부분만 보면,</p>
<pre><code class="language-jsx">useEffect(() =&gt; {fetchData()}, [/*반응할 state*/]);</code></pre>
<p>이렇게 useEffect훅의 두 번째 인자인 의존성 배열에 특정 state변수를 주어
해당 state가 변화하는 순간 외에는 컴포넌트 전체가 다시 렌더링 된다 하더라도, fetchData함수가 다시 실행되는 것을 막아 효율을 증가시킬 수 있습니다. </p>
<h1 id="usestate-훅과-관련된-규칙">useState 훅과 관련된 규칙</h1>
<h3 id="1-array-object-타입의-state는-spread연산자를-이용해-객체나-배열을-새로-만들어-수정하라">1. array, object 타입의 state는 spread연산자를 이용해 객체나 배열을 새로 만들어 수정하라</h3>
<p>number나 string타입의 데이터가 아니라, array나 object를 useState훅의 초기값으로 사용한다 해볼게요. </p>
<p>이 땐, state의 값을 수정할 때, state를 직접 변경하지 않고, spread연산자를 사용해 기존의 값은 그대로 받아오고, 수정 사항을 반영한 새로운 배열이나 객체를 직접 생성해 setState함수에 넣어주어야 합니다. </p>
<p>하나씩 살펴봅시다. </p>
<h3 id="예제-1---state에-값을-추가하기">예제 1 - state에 값을 추가하기</h3>
<p>먼저 아래의 코드를 볼까요?</p>
<pre><code class="language-jsx">import React, { useState } from &#39;react&#39;;

function ArrayStateExample() {
  const [items, setItems] = useState([&#39;apple&#39;, &#39;banana&#39;, &#39;cherry&#39;]);

  const addItem = () =&gt; {
    setItems([...items, &#39;orange&#39;]);
  };

  return (
    &lt;div&gt;
      &lt;h1&gt;Items&lt;/h1&gt;
      &lt;ul&gt;
        {items.map((item, index) =&gt; (
          &lt;li key={index}&gt;{item}&lt;/li&gt;
        ))}
      &lt;/ul&gt;
      &lt;button onClick={addItem}&gt;Add Item&lt;/button&gt;
    &lt;/div&gt;
  );
}

export default ArrayStateExample;
</code></pre>
<p>저희가 주목해볼 부분은 아래와 같습니다. </p>
<pre><code class="language-jsx">const [items, setItems] = useState([&#39;apple&#39;, &#39;banana&#39;, &#39;cherry&#39;]);

  const addItem = () =&gt; {
    //items배열에 망고 추가하고 싶음
  };</code></pre>
<p>세 개의 과일을 담고 있는 array state인 items에 망고를 추가하고 싶다고 해보겠습니다. </p>
<p>state인 items의 값을 변경하려 할 때, items는 배열이니,</p>
<pre><code class="language-js">items.push(&#39;mango&#39;);</code></pre>
<p>이렇게 수정할 수도 있겠다는 생각이 들죠.</p>
<p>하지만, 이렇게 하면 리액트의 패러다임 중 하나인 &quot;불변성 원칙&quot; 을 우리도 모르는 새 어기게 됩니다. </p>
<p>이 문제를 어떻게 해결해야 할까요?</p>
<p>생각보다 간단하게, JS의 spread(전개할당)연산자를 사용하면 됩니다. </p>
<pre><code class="language-js">const [items, setItems] = useState([&#39;apple&#39;, &#39;banana&#39;, &#39;cherry&#39;]);

  const addItem = () =&gt; {
    setItems([...items, &#39;mango&#39;]);
  };</code></pre>
<p>이렇게, 기존의 items배열이 아닌, 새로운 배열을 생성하되, ...연산자로 기존 items배열의 값은 모두 가져올 수 있습니다. </p>
<p>이렇게 하면, 리액트의 상태불변성 원칙을 고수하며 배열에 요소를 추가할 수 있습니다.</p>
<h3 id="예제-2---값을-수정하기">예제 2 - 값을 수정하기</h3>
<p>아래와 같이 spread연산자를 이용해
불변성 원칙을 고수하며 값을 변경할 수 있습니다. </p>
<p>버튼을 누르면, person 객체의 age값을 하나 늘린 값으로 수정하려 합니다. </p>
<pre><code class="language-jsx">import React, { useState } from &#39;react&#39;;

function ObjectStateExample() {
  const [person, setPerson] = useState({ name: &#39;John&#39;, age: 30 });

  const incrementAge = () =&gt; {
    // 새로운 객체를 생성하여 상태를 업데이트
    setPerson({
      ...person,
      age: person.age + 1
    });
  };

  return (
    &lt;div&gt;
      &lt;h1&gt;Person Information&lt;/h1&gt;
      &lt;p&gt;Name: {person.name}&lt;/p&gt;
      &lt;p&gt;Age: {person.age}&lt;/p&gt;
      &lt;button onClick={incrementAge}&gt;Increment Age&lt;/button&gt;
    &lt;/div&gt;
  );
}

export default ObjectStateExample;
</code></pre>
<p>주목해볼 부분은 아래와 같습니다. </p>
<pre><code class="language-jsx">const incrementAge = () =&gt; {
    // 새로운 객체를 생성하여 상태를 업데이트
    setPerson({
      ...person,
      age: person.age + 1
    });
  };</code></pre>
<p><code>person.age += 1</code> 이런 식으로 state변수에 직접 접근하지 않고, </p>
<p><code>...person</code> 으로 값을 받아와 새로운 객체에 넣고, 이미 존재하는 key name age에 접근하여 값을 수정하고 있는 것을 확인할 수 있습니다. </p>
<h1 id="마무리">마무리</h1>
<p>이렇게 오늘은 굉장히 많은 react hook 관련 규칙을 알아보았습니다. </p>
<p>순수함수, 렉시컬스코프, 스프레드 연산 등 자바스크립트의 문법들이 다소 등장했는데,
이 내용들에 익숙하지 않으시다면 꼭 다시 점검해보시면 좋겠습니다. </p>
<p>이상으로 포스팅을 마칩니다. 
감사합니다</p>
<p>예제코드 제공: chat GPT
참고한 영상: <a href="https://www.youtube.com/watch?v=cd3P3yXyx30">https://www.youtube.com/watch?v=cd3P3yXyx30</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[React] CSS-in-JS로 CSS를 JSX내에서 사용하기 (feat. styled-components)]]></title>
            <link>https://velog.io/@code_prince/React-CSS-in-JS%EB%A1%9C-CSS%EB%A5%BC-JSX%EB%82%B4%EC%97%90%EC%84%9C-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0-feat.-styled-components</link>
            <guid>https://velog.io/@code_prince/React-CSS-in-JS%EB%A1%9C-CSS%EB%A5%BC-JSX%EB%82%B4%EC%97%90%EC%84%9C-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0-feat.-styled-components</guid>
            <pubDate>Thu, 20 Jun 2024 08:52:47 GMT</pubDate>
            <description><![CDATA[<p>안녕하세요. 
오늘은 React로 개발할 때, 스타일링을 편하게 할 수 있는 방법에 대해 알아보겠습니다. </p>
<p>평소 저희는 .js나 .jsx파일에 로직을 구현하고,
별도의 css파일을 만들어 이를 import했죠. </p>
<p>이 경우, 두 개 이상의 파일을 교차하며 개발해야 하기에 불편함이 따르죠.
css-in-js라 불리는 여러 기법들을 사용하면, 
jsx파일 내에서 별도의 css파일 없이 css 스타일링을 적용할 수 있습니다. </p>
<h1 id="css-in-js를-사용하지-않은-일반적인-경우">css-in-js를 사용하지 않은 일반적인 경우</h1>
<p>다음은 CSS-in-JS를 사용하지 않은 일반적인 경우의 모습입니다. </p>
<pre><code class="language-jsx">import &#39;./index.css&#39;;

&lt;div className=&quot;container&quot;&gt;
  &lt;h1&gt;전통적인 방식&lt;/h1&gt;
&lt;/div&gt;</code></pre>
<p>css파일을 불러오고, 스타일을 적용하고 싶은 html태그에 className속성을 주어
css파일에 생성해둔 스타일을 적용해줬죠. </p>
<p>이렇게 두 개의 파일을 오가며 개발하는 것이 불편하다면, 아래의 이야기에 집중해주세요!</p>
<h1 id="css-in-js-01-js-객체-활용">CSS-in-JS 01. JS 객체 활용</h1>
<p>css를 자바스크립트 객체 형식으로 표현하면, jsx 내부에서 css를 사용할 수 있습니다. 
아래는 예제 코드입니다. </p>
<pre><code class="language-jsx">export default const MenuBar = () =&gt; {
  const menuStyle = {
    backgroundColor: &quot;white&quot;,
    border: &quot;2px solid black&quot;,
    margin: &quot;2px 1px 1px 3px&quot;,
    }

  return(
    &lt;div style={menuStyle}&gt;
      &lt;ul&gt;
        &lt;li&gt;item1&lt;/li&gt;
        &lt;li&gt;item2&lt;/li&gt;
        &lt;li&gt;item3&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/div&gt;
  )
}</code></pre>
<p>menuStyle이라는 객체에 css스타일을 작성해주고, 
이를 div태그의 style속성에 전달해주면 됩니다. </p>
<p>주의할 점은, 객체 안에 css를 작성해줄 땐, css의 속성명을 케밥케이스가 아닌 카멜케이스로 작성해줘야 한다는 것입니다. </p>
<p>border-top이 아닌, borderTop이 되어야겠죠?
또, 속성명과 속성값도 자바스크립트 객체의 key-value형식으로 작성해주시면 됩니다. 
속성값은 따옴표에 감싸주세요!</p>
<h1 id="css-in-js-02-styled-components">CSS-in-JS 02. styled-components</h1>
<p>저의 경우 프로젝트에서 이 친구를 가장 많이 사용했습니다. 
styled-components는 별도의 설치가 필요합니다. </p>
<h3 id="라이브러리-설치">라이브러리 설치</h3>
<p>터미널에 다음과 같이 입력해주세요</p>
<pre><code>npm install styled-components</code></pre><p>만약 타입스크립트로 프로젝트를 진행하신다면, 
타입 정의를 위해 아래의 작업도 함께 진행해주세요.</p>
<pre><code>npm install @types/styled-components</code></pre><p>자! 모든 준비가 끝났다면, 바로 시작해봅시다.</p>
<h3 id="코드-예제">코드 예제</h3>
<p>위의 JS객체를 이용한 스타일링에서 살펴봤던 아래의 예제코드를 styled-components방식으로 바꿔보겠습니다. </p>
<p>상기를 위해 앞서 살펴봤던 코드를 다시 불러와봅시다</p>
<pre><code class="language-jsx">export default const MenuBar = () =&gt; {
  const menuStyle = {
    backgroundColor: &quot;white&quot;,
    border: &quot;2px solid black.&quot;,
    margin: &quot;2px 1px 1px 3px&quot;,
    }

  return(
    &lt;div style={menuStyle}&gt;
      &lt;ul&gt;
        &lt;li&gt;item1&lt;/li&gt;
        &lt;li&gt;item2&lt;/li&gt;
        &lt;li&gt;item3&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/div&gt;
  )
}</code></pre>
<p>이제 이 친구를 styled-components라이브러리를 이용해 수정해보겠습니다.
아래는 수정된 코드입니다. </p>
<pre><code class="language-jsx">import styled from &#39;styled-components&#39;;

const MenuStyle = styled.div`
  background-color: white;
  border: 2px solid black;
  margin: 2px 1px 1px 3px;
  `;

export default function MenuBar() {
  return(
    &lt;MenuStyle&gt;
     &lt;ul&gt;
        &lt;li&gt;item1&lt;/li&gt;
        &lt;li&gt;item2&lt;/li&gt;
        &lt;li&gt;item3&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/MenuStyle&gt;
  )}</code></pre>
<p>자 우선, styled-components는 별도로 설치한 라이브러리니까, 
당연히 import를 해줘야 사용할 수 있는데요,</p>
<pre><code class="language-js">import styled from &#39;styled-components&#39;;</code></pre>
<p>styled라는 녀석엔 여러 html태그들이 들어있습니다. 
styled.div라고 입력하면, 컴파일러는 JSX내에 있더라도, html의 div태그를 의미함을 알아챌 수 있습니다. </p>
<pre><code class="language-jsx">const MenuStyle = styled.div`
  background-color: white;
  border: 2px solid black;
  margin: 2px 1px 1px 3px;
  `;</code></pre>
<p>이렇게 상수 형식으로 스타일을 직접 생성할 수 있습니다. 
MenuStyle이란 이름은 제가 만든 하나의 태그로서, 
제가 지정한 스타일을 그대로 적용받은 HTML의 div태그가 되는 것입니다. </p>
<p>스타일을 작성할 땐, 백틱 표시를 하고 써주셔야 합니다!</p>
<p>또, 위에서 살펴본 자바스크립트 객체를 사용했을 때완 달리, 
&quot;-&quot; 를 사용하는 기존 css의 케밥 케이스 네이밍 룰을 그대로 따릅니다. 
속성값을 적을 때도 따옴표 없이 그냥 써줍니다. </p>
<pre><code class="language-jsx">&lt;MenuStyle&gt;
  &lt;ul&gt;
    &lt;li&gt;item1&lt;/li&gt;
    &lt;li&gt;item2&lt;/li&gt;
    &lt;li&gt;item3&lt;/li&gt;
  &lt;/ul&gt;
&lt;/MenuStyle&gt;</code></pre>
<p>이렇게, MenuStyle태그로 감싸주면 완성입니다. 
다시 말하지만, 여기서의 MenuStyle태그는 &quot;저만의 스타일을 장착한 div태그&quot; 입니다!</p>
<h1 id="마무리">마무리</h1>
<p>이렇게 오늘은 별도의 스타일링 파일을 따로 만들지 않고,
작업중인 JSX파일 내에서 한 번에 요소에 스타일을 적용시키는 방법들을 살펴봤습니다. </p>
<p>자바스크립트 파일 안에서 css를 사용한다 하여
위의 방식들을 css-in-js라고 부릅니다.</p>
<p>살펴본 기법들 외에도, 무수히 많은 css-in-js방식들이 있으니,
궁금하신 분들은 찾아보시면 좋을 것 같습니다. </p>
<p>감사합니다!</p>
]]></description>
        </item>
    </channel>
</rss>