<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>문.log</title>
        <link>https://velog.io/</link>
        <description>호기심 많은 청년</description>
        <lastBuildDate>Fri, 08 Nov 2024 01:31:50 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>문.log</title>
            <url>https://velog.velcdn.com/images/_mo__on/profile/a2bb7e6b-a2d0-4ae7-af28-b726663dc9dc/image.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. 문.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/_mo__on" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[아주 쉽게 알아보는 SQL(feat.SSD,RAM)]]></title>
            <link>https://velog.io/@_mo__on/%EC%95%84%EC%A3%BC-%EC%89%BD%EA%B2%8C-%EC%95%8C%EC%95%84%EB%B3%B4%EB%8A%94-SQLfeat.SSDRAM</link>
            <guid>https://velog.io/@_mo__on/%EC%95%84%EC%A3%BC-%EC%89%BD%EA%B2%8C-%EC%95%8C%EC%95%84%EB%B3%B4%EB%8A%94-SQLfeat.SSDRAM</guid>
            <pubDate>Fri, 08 Nov 2024 01:31:50 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/_mo__on/post/86954bde-7044-4e49-9fae-a2a4030f86c9/image.jpg" alt=""></p>
<p>평소 데이터베이스와 SQL에 대해 막연히 생각하시는 분들이 계시거나
얘네가 뭘 하는 녀석들인지 쉽게 알아보면 좋을거 같아 가볍게 글로 정리했습니다.</p>
<p>먼저 데이터베이스와 SQL을 다뤄보기 전, 운영체제에서 어떻게 데이터가 저장되는지 보겠습니다.</p>
<h2 id="데이터-초기화와-저장-장치">데이터 초기화와 저장 장치</h2>
<h3 id="데이터는-왜-초기화-되는가">데이터는 왜 초기화 되는가?</h3>
<p>컴퓨터 시스템에서 데이터를 저장하고 다루는 방식은 하드웨어의 특성과 밀접한 관련이 있습니다. 
데이터가 초기화되는 이유는 주로 <strong>하드웨어적인 이유</strong>에 있습니다. 
하드디스크(HDD/SSD)와 메모리(RAM)의 차이를 생각해볼까요?</p>
<p>코드를 작성하면 코드는 보통 SSD에 저장됩니다. 
프로그램이 실행되면 SSD에 있는 데이터가 <strong>RAM</strong>으로 올라갑니다. 
이 과정에서 중요한 점은, <strong>RAM에 올라간 데이터는 프로그램이 재시작되면 리셋된다는 점</strong>입니다. 
즉, 실행 도중에 생성되거나 변경된 변수들은 프로그램이 종료되면 사라지게 됩니다.</p>
<p>이러한 이유 때문에 영구적으로 데이터를 저장하기 위해서는 데이터를 HDD나 SSD 같은 
영구 저장 장치에 저장해야 하며, 이를 가능하게 하는 시스템이 <strong>데이터베이스입니다</strong>.</p>
<h3 id="그래서-ram을-왜-씀">그래서 RAM을 왜 씀?</h3>
<p>RAM은 SSD보다 <strong>속도가 훨씬 빠르기 때문</strong>입니다. 
데이터를 읽고 쓰는 속도가 수십 배에서 수백 배 빠른 RAM 덕분에 프로그램의 성능이 크게 향상됩니다. 
SSD에 데이터를 영구적으로 저장해 두고, 실행할 때는 RAM에 올려서 빠르게 처리하는 구조가 일반적입니다. 
이렇게 프로그램이 종료되더라도 데이터를 유지하기 위해 <strong>SQL(Structured Query Language)</strong>과 같은 
시스템이 필요합니다. SQL은 영구적인 데이터 저장과 관리를 위한 가장 전통적이고 흔히 사용되는 방법입니다.</p>
<h3 id="sqlstructured-query-language">SQL(<strong>Structured Query Language)</strong></h3>
<p>SQL은 데이터를 저장하고 조작하기 위한 언어로, 데이터베이스와 소통하기 위해 사용됩니다.
SQL의 기본 구조는 데이터를 테이블로 관리하며, 각 테이블은 행(Row)과 열(Column)로 구성되어 있습니다.</p>
<p>SQL에서 흔히 사용하는 네 가지 주요 명령어: <strong>SELECT, INSERT, UPDATE, DELETE</strong>.</p>
<h3 id="table"><strong>TABLE</strong></h3>
<p><strong>테이블</strong>은 데이터를 담는 기본적인 구조입니다. 각 테이블은 행과 열로 구분됩니다.
아주 간단히 엑셀 시트를 떠올리면 이해하기 쉽습니다. 
각 행은 데이터의 한 항목을 나타내고, 각 열은 데이터의 속성을 나타냅니다.</p>
<p><img src="https://prod-files-secure.s3.us-west-2.amazonaws.com/035a9866-66ae-41da-a963-460ef23556af/f230e737-e4af-4f7b-80ec-a3734d26dbc3/Screenshot-from-2023-04-05-17-15-53.png" alt="Screenshot-from-2023-04-05-17-15-53.png"></p>
<h3 id="select"><strong>SELECT</strong></h3>
<p><strong>SELECT</strong>는 테이블에서 데이터를 조회할 때 사용됩니다. 특정 열을 선택해 데이터를 가져올 수 있습니다.</p>
<pre><code class="language-sql">SELECT { column } FROM { table }

// 예시
SELECT id, author, title, content FROM Post</code></pre>
<p>위 예시는 &quot;Post&quot; 테이블에서 &quot;id&quot;, &quot;author&quot;, &quot;title&quot;, &quot;content&quot; 열의 데이터를 모두 조회합니다. 결과는 JSON 형식과 유사하게 받아볼 수 있어 웹 개발에서도 자주 사용됩니다.</p>
<h3 id="insert"><strong>INSERT</strong></h3>
<p><strong>INSERT</strong>는 새로운 데이터를 테이블에 추가할 때 사용됩니다.</p>
<pre><code class="language-sql">INSERT INTO { table } { column 1, column 2 }
VALUES { value 1, value 2....}

// 예시
INSERT INTO posts (id, author, title, content, likeCount, commentCount)
VALUES (7, &#39;조경문&#39;, &#39;SQL 기본기 정리글&#39;, &#39;정리하는 글 작성 중&#39;, 5, 3)</code></pre>
<p>이 예시는 &quot;Post&quot; 테이블에 새로운 데이터를 추가하는 과정입니다. 데이터베이스에 값을 저장할 때는 테이블 구조에 맞춰 데이터를 입력해야 합니다.</p>
<h3 id="update"><strong>UPDATE</strong></h3>
<p><strong>UPDATE</strong>는 기존 데이터를 수정할 때 사용됩니다.</p>
<pre><code class="language-sql">UPDATE { table } SET { column } WHERE { condition }

// 예시
UPDATE Post SET likeCount = 0 WHERE id = 3</code></pre>
<p>이 예시는 &quot;id&quot;가 3인 게시글의 &quot;likeCount&quot;를 0으로 업데이트합니다. 특정 조건에 맞는 데이터를 찾고, 그 데이터를 업데이트할 수 있습니다.</p>
<h3 id="delete"><strong>DELETE</strong></h3>
<p><strong>DELETE</strong>는 테이블에서 데이터를 삭제할 때 사용됩니다.</p>
<pre><code class="language-sql">DELETE FROM { table } WHERE { condition } 

// 예시
DELETE FROM Post WHERE author = &#39;조경문&#39;</code></pre>
<p>이 예시는 &quot;author&quot;가 &quot;조경문&quot;인 모든 게시글을 삭제합니다. 데이터를 삭제할 때는 조건을 정확하게 설정하지 않으면 모든 데이터가 삭제될 위험이 있으므로 주의해야 합니다.</p>
<h3 id="crud와-http-메서드의-관계">CRUD와 HTTP 메서드의 관계</h3>
<p>SQL의 주요 명령어는 <strong>CRUD</strong> 연산으로 불리기도 합니다. 
CRUD는 <strong>Create, Read, Update, Delete</strong>의 약자입니다. 
이를 HTTP 메서드와 연결해서 생각해 보면 이해가 더 쉽습니다.</p>
<ul>
<li><strong>Create (INSERT)</strong> - HTTP <strong>POST</strong></li>
<li><strong>Read (SELECT)</strong> - HTTP <strong>GET</strong></li>
<li><strong>Update (UPDATE)</strong> - HTTP <strong>PUT</strong> 또는 <strong>PATCH</strong></li>
<li><strong>Delete (DELETE)</strong> - HTTP <strong>DELETE</strong></li>
</ul>
<p>이렇게 매핑함으로써 SQL 쿼리와 REST API의 관계를 쉽게 이해할 수 있고,
궁극적으로 <strong>프론트엔드로 백엔드와 협업할 때 그들이 어떤 이야기를 하는지 알 수 있게 됩니다.</strong></p>
<h3 id="정리하자면">정리하자면?</h3>
<p>SQL은 영구적인 데이터 저장과 관리를 위한 매우 중요한 도구입니다. 
기본적인 SQL 명령어인 SELECT, INSERT, UPDATE, DELETE만 잘 이해해도 
데이터베이스와 상호작용하는 대부분의 상황을 해결할 수 있습니다. 
특히 테이블 구조와 명령어를 이해하면 더 복잡한 데이터 조작도 쉽게 다룰 수 있게 되며, 
나아가 프론트개발에서의 REST API와의 연결도 자연스럽게 이해할 수 있게 됩니다.</p>
<p>SQL을 공부하면서 실제로 데이터베이스에 쿼리를 실행해 보고, 
결과를 확인하는 과정에서 많은 것을 배울 수 있습니다. 
기초부터 차근차근 다지면서 더 나아가 SQL의 고급 기능들을 익혀보는 것도 추천드립니다.</p>
<p>(1 to many, 제1정규화, 데이터베이스 아키텍처 등 재밌는게 참 많습니다😁)</p>
<p>읽어주셔서 감사합니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[객체 리터럴과 생성자 함수: JavaScript 객체 생성 방식의 이해]]></title>
            <link>https://velog.io/@_mo__on/%EA%B0%9D%EC%B2%B4-%EB%A6%AC%ED%84%B0%EB%9F%B4%EA%B3%BC-%EC%83%9D%EC%84%B1%EC%9E%90-%ED%95%A8%EC%88%98-JavaScript-%EA%B0%9D%EC%B2%B4-%EC%83%9D%EC%84%B1-%EB%B0%A9%EC%8B%9D%EC%9D%98-%EC%9D%B4%ED%95%B4</link>
            <guid>https://velog.io/@_mo__on/%EA%B0%9D%EC%B2%B4-%EB%A6%AC%ED%84%B0%EB%9F%B4%EA%B3%BC-%EC%83%9D%EC%84%B1%EC%9E%90-%ED%95%A8%EC%88%98-JavaScript-%EA%B0%9D%EC%B2%B4-%EC%83%9D%EC%84%B1-%EB%B0%A9%EC%8B%9D%EC%9D%98-%EC%9D%B4%ED%95%B4</guid>
            <pubDate>Tue, 29 Oct 2024 02:13:48 GMT</pubDate>
            <description><![CDATA[<p>JavaScript에서 객체를 생성하는 방식은 여러 가지가 있습니다. 
이 중에서 가장 많이 사용되는 두 가지 방법은 
객체 리터럴(<code>const newObj = {}</code>)과 생성자 함수(<code>const new Something()</code>)를 이용하는 것입니다. 
이번 포스팅에서는 이 두 가지 방식의 차이점을 자세히 살펴보고, 각 방식의 특징과 장단점을 정리해 보겠습니다.
 예제 코드도 함께 소개해 드리니, 각각의 특징을 좀 더 쉽게 이해할 수 있을 거예요.</p>
<h2 id="객체-리터럴-const-newobj--">객체 리터럴 (<code>const newObj = {}</code>)</h2>
<p>객체 리터럴은 JavaScript에서 객체를 만드는 가장 간단한 방법입니다. 
다음은 객체 리터럴 방식의 특징과 예제 코드입니다.</p>
<h3 id="1-기본-사용-예시">1. 기본 사용 예시</h3>
<p>객체 리터럴을 이용해 사람 정보를 저장하는 간단한 객체를 만들어 보겠습니다.</p>
<pre><code class="language-jsx">const person = {
  name: &#39;홍길동&#39;,  // 이름 속성
  age: 30,        // 나이 속성
  greet: function() {
    console.log(`안녕하세요, 제 이름은 ${this.name}이고, 나이는 ${this.age}살입니다.`);
  }
};

// 객체 사용 예시
person.greet(); // 출력: 안녕하세요, 제 이름은 홍길동이고, 나이는 30살입니다.</code></pre>
<p>위 코드에서는 <code>{}</code>를 이용해 객체 <code>person</code>을 만들고, 속성 <code>name</code>, <code>age</code>와 메소드 <code>greet</code>를 추가했습니다. 
객체 리터럴은 이렇게 빠르고 간단한 객체 구조를 정의할 때 매우 유용합니다.</p>
<h3 id="2-동적-속성-추가">2. 동적 속성 추가</h3>
<p>객체 리터럴 방식은 필요에 따라 새로운 속성을 쉽게 추가할 수 있습니다.</p>
<pre><code class="language-jsx">person.job = &#39;소프트웨어 개발자&#39;; // 새로운 속성 추가
console.log(person.job); // 출력: 소프트웨어 개발자</code></pre>
<p>위와 같이 객체에 속성을 동적으로 추가하는 것도 가능합니다. 
이 방식은 단순히 몇 가지 속성만을 가지는 객체를 만들 때 사용하기 매우 적합합니다.</p>
<h2 id="생성자-함수-const-new-something">생성자 함수 (<code>const new Something()</code>)</h2>
<p>이제 생성자 함수 혹은 클래스를 사용한 객체 생성 방식을 살펴보겠습니다. 
이 방식은 객체를 더 유연하게 생성하고 초기화할 때 사용됩니다.</p>
<h3 id="1-생성자-함수-사용-예시">1. 생성자 함수 사용 예시</h3>
<p>먼저 생성자 함수를 이용해 여러 개의 <code>Person</code> 객체를 만들 수 있는 방법을 보여드리겠습니다.</p>
<pre><code class="language-jsx">function Person(name, age) {
  this.name = name;  // 생성자 내부에서 이름 초기화
  this.age = age;    // 생성자 내부에서 나이 초기화
  this.greet = function() {
    console.log(`안녕하세요, 제 이름은 ${this.name}이고, 나이는 ${this.age}살입니다.`);
  };
}

// 새로운 객체 생성
const hong = new Person(&#39;홍길동&#39;, 30);
const lee = new Person(&#39;이순신&#39;, 45);

// 객체 사용 예시
hong.greet(); // 출력: 안녕하세요, 제 이름은 홍길동이고, 나이는 30살입니다.
lee.greet();  // 출력: 안녕하세요, 제 이름은 이순신이고, 나이는 45살입니다.</code></pre>
<p>위 코드에서는 <code>Person</code>이라는 생성자 함수를 사용해 새로운 객체를 만들었습니다. 
<code>new Person()</code>을 호출할 때마다 <code>name</code>과 <code>age</code> 속성을 가진 새로운 객체가 생성됩니다. 이 방식은 여러 개의 유사한 객체를 만들어야 할 때 매우 유용합니다.</p>
<h3 id="2-프로토타입을-이용한-메소드-정의">2. 프로토타입을 이용한 메소드 정의</h3>
<p>생성자 함수에서 메소드를 객체마다 중복해서 정의하는 대신, 프로토타입을 활용하여 메소드를 정의할 수도 있습니다.</p>
<pre><code class="language-jsx">Person.prototype.sayJob = function(job) {
  console.log(`${this.name}은(는) ${job}으로 일하고 있습니다.`);
};

// 새로운 메소드 사용 예시
hong.sayJob(&#39;소프트웨어 개발자&#39;); // 출력: 홍길동은(는) 소프트웨어 개발자로 일하고 있습니다.
lee.sayJob(&#39;군인&#39;); // 출력: 이순신은(는) 군인으로 일하고 있습니다.</code></pre>
<p>위 예시처럼 <code>Person.prototype</code>에 메소드를 추가하면, 생성된 모든 객체가 해당 메소드를 참조할 수 있습니다. 
이 방식은 메모리 사용을 최적화하는 데 유리하며, 생성자 함수 방식의 강력함을 잘 보여줍니다.</p>
<h2 id="클래스-class-키워드-사용">클래스 (<code>class</code> 키워드 사용)</h2>
<p>ES6 이후 JavaScript에는 클래스 문법이 도입되어 생성자 함수보다 더 직관적인 객체 생성 방식을 제공합니다. 
클래스는 생성자 함수와 본질적으로 같은 역할을 하지만, 가독성이 뛰어나고 직관적인 문법을 제공합니다.</p>
<pre><code class="language-jsx">class Person {
  constructor(name, age) {
    this.name = name;  // 이름 초기화
    this.age = age;    // 나이 초기화
  }

  greet() {
    console.log(`안녕하세요, 제 이름은 ${this.name}이고, 나이는 ${this.age}살입니다.`);
  }

  sayJob(job) {
    console.log(`${this.name}은(는) ${job}으로 일하고 있습니다.`);
  }
}

// 새로운 객체 생성
const hong = new Person(&#39;홍길동&#39;, 30);
const lee = new Person(&#39;이순신&#39;, 45);

// 객체 사용 예시
hong.greet(); // 출력: 안녕하세요, 제 이름은 홍길동이고, 나이는 30살입니다.
lee.sayJob(&#39;군인&#39;); // 출력: 이순신은 군인으로 일하고 있습니다.</code></pre>
<p>클래스를 사용하면 생성자 함수보다 더 명확하게 객체와 그 초기화 과정을 표현할 수 있습니다. 
클래스 내부에는 메소드 정의가 간단하며, 재사용성도 높아 코드가 깔끔해집니다.</p>
<h2 id="결론">결론!</h2>
<p>JavaScript에서 객체를 생성하는 방법에는 객체 리터럴, 생성자 함수, 그리고 클래스를 이용하는 방식이 있습니다. 
각 방법은 사용 목적과 상황에 따라 장단점이 있습니다. </p>
<ol>
<li>객체 리터럴은 간단하고 빠르게 객체를 생성할 때 유용합니다.</li>
<li>생성자 함수는 유사한 객체를 효율적으로 생성할 때 사용됩니다. </li>
<li>클래스는 코드 가독성과 직관성을 높여 주기 때문에 더 복잡하고 다양한 초기화 로직이 필요한 경우에 적합합니다.</li>
</ol>
<p>각 방식의 차이와 사용 예제를 통해 여러분이 필요에 따라 적절한 객체 생성 방식을 선택할 수 있기를 바랍니다. 
마치 나만의 작은 공장을 만드는 것처럼, 간단한 구조가 필요할 땐 손쉽게 객체 리터럴을 사용하고, 
좀 더 복잡한 로직이나 대량 생산이 필요할 땐 생성자 함수나 클래스를 사용하는 것이 좋습니다. 
다양한 객체 생성 방식을 이해하고 활용해 보다 효율적인 코드 작성을 목표로 해 보세요!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[아주 짧게 정리한 React Query 캐싱 상태]]></title>
            <link>https://velog.io/@_mo__on/%EC%95%84%EC%A3%BC-%EC%A7%A7%EA%B2%8C-%EC%A0%95%EB%A6%AC%ED%95%9C-React-Query-%EC%BA%90%EC%8B%B1-%EC%83%81%ED%83%9C</link>
            <guid>https://velog.io/@_mo__on/%EC%95%84%EC%A3%BC-%EC%A7%A7%EA%B2%8C-%EC%A0%95%EB%A6%AC%ED%95%9C-React-Query-%EC%BA%90%EC%8B%B1-%EC%83%81%ED%83%9C</guid>
            <pubDate>Thu, 17 Oct 2024 01:28:14 GMT</pubDate>
            <description><![CDATA[<p>React Devtools를 사용하다 갑작스럽게 상태에 대한 혼란이 왔다.</p>
<p>정리하고자 복습하는 차원에서 다시 한번 짤막하게 정리해본다.</p>
<h3 id="리액트-쿼리의-state와-actions">리액트 쿼리의 State와 Actions</h3>
<p><strong>State</strong></p>
<ul>
<li><p><strong>Fresh</strong>
리액트 쿼리의 모든 데이터들은 기본적으로 fresh가 아니다. (기간 체크 가능)</p>
</li>
<li><p><strong>Stale</strong>
기회가 되면 항상 데이터를 패칭한다. 여기서 말하는 기회에는 아래와 같은 목록들이 있다.</p>
<ul>
<li><p>refetchOnWIndowFoucs : 윈도우 탭 포커스가 다시 되었을 때</p>
</li>
<li><p>retryOnMount: 컴포넌트 마운트 되었을 때</p>
</li>
<li><p>refetchOnReconnect: 인터넷 연결이 끊겼다가 다시 접속이 되었을 때,</p>
</li>
<li><p>retry: 데이터를 가져올 때 실패하면 몇번정도 가져오는지 활성화</p>
<p>이 상태를 Provider에서 설정해줄 수 있으며, 특정 기능이 필요하면 useQuery에서 오버라이드가 가능하다.</p>
</li>
</ul>
</li>
</ul>
<p><strong>Actions</strong></p>
<ul>
<li><strong>refetch</strong>
모든 데이터를 무조건 새로 가져온다.</li>
<li><strong>inValidate</strong>
Observer를 감지하다가, Observer가 1이 될 경우 데이터를 가져온다.
즉, 현재 화면에서 데이터를 사용하고 있을 때만 다시 데이터를 가져온다.<ul>
<li><strong>Oberserver란?</strong>
리액트 쿼리 데브툴에서 지금 현재 페이지에서 key값으로 들어있는 데이터를 사용하고 있는지 감지.
데이터를 사용 중 이면 1, 미사용 중이면 0으로 표시된다.</li>
</ul>
</li>
<li><strong>Reset</strong>
초기 데이터를 호출해준다.
useQuery에 initalData가 있을 땐 이를 불러주고, 없을 때는 데이터를 새롭게 가져온다.</li>
<li><strong>Trigger Loading</strong>
로딩 상태를 노출 시켜준다</li>
<li><strong>Trigger Error</strong>
에러 상태를 노출 시켜준다</li>
</ul>
<blockquote>
<p>💡 <strong>refetch와 inValidate의 차이</strong> 
refetch는 무조건 가져오지만, inValidate는 inActive 상태일 때는 가져오지 않는다.
(화면 상의 노출 유무로 생각하자. inValidate는 화면 상의 데이터가 없으면 호출 X)</p>
</blockquote>
<p>하지만 무조건적으로 inValidate가 좋은건 아니다.
예를 들어, 화면에 안 보인다고 해서 그 데이터가 안 필요한게 아니다.
<strong>다른 컴포넌트에서 전역적으로 공유하는 데이터</strong>들은 화면에 보이지 않아도 필요할 수 있다.
이런 케이스에서는 refetch를 사용하자.</p>
<p>데이터 패칭할 때 필요없는 데이터(예를 들어 게시글 - 댓글)는 enabled 해서 추가 호출을 막자</p>
<p>기존의 useSearchParams는 Readonly 이기 때문에 제거 하는 방법이 없다.
이럴 때에는 new URLSearchParams(함수) 방식으로 변형할 수 있다.</p>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[[번역]실제 사용 중인 대체 텍스트]]></title>
            <link>https://velog.io/@_mo__on/%EB%B2%88%EC%97%AD%EC%8B%A4%EC%A0%9C-%EC%82%AC%EC%9A%A9-%EC%A4%91%EC%9D%B8-%EB%8C%80%EC%B2%B4-%ED%85%8D%EC%8A%A4%ED%8A%B8</link>
            <guid>https://velog.io/@_mo__on/%EB%B2%88%EC%97%AD%EC%8B%A4%EC%A0%9C-%EC%82%AC%EC%9A%A9-%EC%A4%91%EC%9D%B8-%EB%8C%80%EC%B2%B4-%ED%85%8D%EC%8A%A4%ED%8A%B8</guid>
            <pubDate>Tue, 15 Oct 2024 03:31:08 GMT</pubDate>
            <description><![CDATA[<p><a href="https://accessibility.blog.gov.uk/2024/07/17/alternative-text-in-action/">Alternative text in action</a></p>
<p>우리는 모든 사람이 매일 콘텐츠에 대체 텍스트를 추가하도록 격려하기 위해 대체 텍스트 작성 및 사용에 대한 몇 가지 최고의 팁을 공유하고자 합니다. 우리의 팁은 사용하는 소프트웨어나 애플리케이션에 관계없이 적용할 수 있습니다.</p>
<p><img src="https://prod-files-secure.s3.us-west-2.amazonaws.com/84f2994a-af56-430a-9426-7ad9474a8b33/400928d0-99cf-4755-b023-3ff9138167dd/Untitled.png" alt="그릇 위에 빨간색, 초록색, 노란색 사과가 가득 담긴 커다란 그릇에 &#39;Alt= 고요한 바다 위로 지는 아름다운 일몰, 물에 반사된 생생한 주황색과 분홍색 빛깔&#39;이라는 말풍선이 그려져 있다."></p>
<p>그릇 위에 빨간색, 초록색, 노란색 사과가 가득 담긴 커다란 그릇에 &#39;Alt= 고요한 바다 위로 지는 아름다운 일몰, 물에 반사된 생생한 주황색과 분홍색 빛깔&#39;이라는 말풍선이 그려져 있다.</p>
<h2 id="중요한-대체-텍스트-작성"><strong>중요한 대체 텍스트 작성</strong></h2>
<p>이미지는 종종 문서, 뉴스레터 및 소셜 미디어에서 대체 텍스트(alt text) 없이 사용됩니다. 환경 및 농촌부(Defra)의 공무원이자 화면 리더 사용자인 크리스 젠킨스는 alt text를 추가하지 않는 것을 쓰레기를 버리는 것과 같다고 설명했습니다.</p>
<p>&quot;사람들은 대체 텍스트를 생략하는데, 잊었거나 추가하는 것이 중요하지 않다고 생각하기 때문입니다. 글쎄요, 저와 제 상황에 처한 사람들에게는 사람들이 그렇게 한다는 의미에서 쓰레기를 버리는 것과 같습니다. 실제로는 용납할 수 없지만, 다른 사람들이 다른 사람들이 그렇게 하는 것을 보고 &#39;음, 내가 해도 괜찮을 거야&#39;라고 생각하기 때문에, 문제는 화면 판독기 사용자가 이미지가 무엇인지 추측할 수 없다는 것입니다.&quot;</p>
<p>우리는 모든 사람이 매일 콘텐츠에 alt 텍스트를 추가하도록 격려하기 위해 alt 텍스트에 대한 몇 가지 최고의 팁을 공유하고자 합니다. 우리의 팁은 여러분이 어떤 소프트웨어나 애플리케이션을 사용하든 적용할 수 있습니다.</p>
<h2 id="우리가-사용하는-용어에-대한-참고-사항"><strong>우리가 사용하는 용어에 대한 참고 사항</strong></h2>
<p>이 기사에서 우리는 대체 텍스트를 alt 텍스트로 줄입니다. 우리는 이 용어를 다음을 포함하는 데 사용합니다.</p>
<ul>
<li>웹 페이지의 alt 텍스트 속성에 포함된 설명</li>
<li>설명에는 응용 프로그램의 대체 텍스트 상자가 포함되었습니다.</li>
<li>페이지에 포함된 이미지에 대한 설명</li>
</ul>
<h2 id="대체-텍스트의-기본-사항-알아보기"><strong>대체 텍스트의 기본 사항 알아보기</strong></h2>
<p>대체 텍스트를 처음 사용하는 분이나 다시 한번 살펴보고 싶으신 분은 다음 자료를 참고하여 소개를 확인하세요.</p>
<ul>
<li><a href="https://www.gov.uk/guidance/how-to-publish-on-gov-uk/images-and-videos">GOV.UK에 이미지와 비디오 추가</a></li>
<li><a href="https://design102.blog.gov.uk/2022/01/14/whats-the-alternative-how-to-write-good-alt-text/">대안은 무엇인가? Design102의 좋은 alt 텍스트를 쓰는 방법</a></li>
<li><a href="https://business.scope.org.uk/article/how-to-write-better-alt-text-descriptions-for-accessibility">Scope별 이미지 접근성을 위한 대체 텍스트 설명을 작성하는 방법</a></li>
</ul>
<h2 id="대체-텍스트를-작성하는-것은-쉽지만-좋은-대체-텍스트를-작성하는-것은-어려울-수-있습니다"><strong>대체 텍스트를 작성하는 것은 쉽지만 좋은 대체 텍스트를 작성하는 것은 어려울 수 있습니다.</strong></h2>
<p>대체 텍스트는 적용하기 가장 쉬운 기술 중 하나이지만 올바르게 작성하는 것은 어려울 수 있습니다. 좋은 대체 텍스트를 작성하는 것은 다음을 포함한 다양한 요소를 고려하는 것입니다.</p>
<ul>
<li>내용의 주제 또는 목적</li>
<li>이미지의 기능</li>
<li>사용자의 목표는 무엇인가</li>
<li>이미지 내용</li>
<li>나머지 페이지에는 무엇이 있나요?</li>
</ul>
<p><a href="https://www.w3.org/WAI/tutorials/images/decision-tree/">대체 텍스트 결정 트리는</a> 이미지에 텍스트가 포함되어 있는지 여부와 같은 일반적인 시나리오를 필터링하여 더 나은 대체 텍스트를 작성하는 데 도움이 될 수 있습니다. 기술을 향상시키거나 더 어려운 이미지를 돕고 싶다면 트리를 사용하세요.</p>
<h2 id="동작-중인-대체-텍스트"><strong>동작 중인 대체 텍스트</strong></h2>
<p>다양한 시나리오에서 대체 텍스트를 사용하는 몇 가지 예를 살펴보겠습니다.</p>
<h3 id="모든-사람이-액세스할-수-있도록-페이지에-대체-텍스트를-추가합니다"><strong>모든 사람이 액세스할 수 있도록 페이지에 대체 텍스트를 추가합니다.</strong></h3>
<p><a href="https://www.gov.uk/guidance/content-design/images">GDS</a> (Government Digital Service)의 이미지 가이드에서는 &#39;대체 텍스트&#39;를 비워두고 대신 본문 텍스트에 이미지에 대한 설명을 쓰라고 제안합니다. 그러면 모든 사람이 설명을 볼 수 있습니다. 페이지에 대체 텍스트를 포함하면 시력이 약하고 시각 처리 장애가 있는 사람에게도 유용하기 때문에 장벽이 더 줄어듭니다.</p>
<p>시각적으로 숨겨진 콘텐츠로 대체 텍스트를 추가하는 데 익숙하다면 습관을 깨고 페이지에 추가하기 어려울 수 있습니다. 이를 권장하기 위해 대체 텍스트가 미칠 수 있는 긍정적인 영향을 강조하고자 합니다.</p>
<p>우리는 콘텐츠 디자이너이자 접근성 전문가인 Marian Avery에게 페이지에 대체 텍스트를 포함하는 것이 왜 중요한지 물었습니다. Marian은 또한 시각 장애가 있고 화면 확대경을 사용합니다.</p>
<p>&quot;확대 사용자는 대부분 이미지가 확대되면 픽셀화되기 때문에 이미지를 보는 데 어려움을 겪는 경우가 많습니다. 시각 장애인 중 많은 사람이 보조 기술을 사용하지 않고 귀하의 페이지를 방문할 수 있습니다. 이는 특히 노인이나 새로 진단받은 사람에게 해당됩니다.</p>
<p>밝은 햇빛 아래에서 누군가가 당신의 페이지를 읽는 것도 고려해야 합니다. 보기 가장 어려운 부분은 아마도 이미지일 것입니다. 페이지에 이미지를 설명하면 모두에게 도움이 됩니다.”</p>
<p>마리안은 대체 텍스트와 페이지의 텍스트를 함께 사용하는 방법에 대한 몇 가지 실용적인 팁을 공유했습니다.</p>
<p>&quot;여전히 대체 텍스트 필드를 사용하여 사람들에게 이미지가 페이지에 설명되어 있고 해당 설명이 어디에 있는지 알릴 수 있습니다. 이는 대체 텍스트 필드에 무언가가 있는 데 익숙한 화면 판독기 사용자에게 안심을 줍니다. 설명을 반복하고 화면 판독기 사용자에게 &#39;청각적 혼란&#39;을 주는 것보다는 포인터로 사용하는 것이 좋습니다.&quot;</p>
<p>대체 텍스트가 시각적으로 숨겨져 있는 것에서 대체 텍스트가 페이지에 표시되는 것으로 이동하는 것은 새로운 관행입니다. Marian의 조언은 alt 속성을 전혀 사용하지 말라는 GDS 지침과 약간 다릅니다. 우리는 사용자 선호도와 행동에 대해 더 많이 알아낼 때까지 이 기술을 시도해 볼 만한 타당한 기술이라고 생각합니다.</p>
<p>마리안은 또한 대체 텍스트 표시 버튼의 가치를 강조했습니다.</p>
<p>“X(이전의 Twitter)와 Slack과 같은 플랫폼은 눈에 보이는 &#39;alt&#39; 버튼으로 모든 사람을 위한 이미지 설명을 제공합니다. 해당 옵션이 없는 경우, 페이지의 이미지 설명이 가장 유용한 방법입니다.”</p>
<h3 id="대체-텍스트-및-대체-텍스트에-대한-단축키를-사용하는-ai"><strong>대체 텍스트 및 대체 텍스트에 대한 단축키를 사용하는 AI</strong></h3>
<p>한 번에 많은 이미지에 대체 텍스트를 추가하는 것은 시간이 많이 걸릴 수 있습니다. 이는 인공 지능(AI)을 살펴보고, 그것이 당신을 대신해 일을 할 수 있는지 확인하게 만들 수 있습니다. AI는 이미지 인식을 사용하여 이미지에 대한 설명을 작성합니다. 간단한 해결책처럼 보일 수 있지만, 컴퓨터가 내 이미지를 설명할 수 있다면 왜 내가 개입해야 합니까?</p>
<p>문제는 컴퓨터가 종종 이미지에 포함된 내용을 매우 문자적인 설명만 제공하고 더 넓은 맥락을 고려하지 못한다는 것입니다.</p>
<p>우리는 온라인 대체 텍스트 생성기로 간단한 실험을 했습니다. 우리는 큰 흰색 그릇에 담긴 다양한 색의 사과, 빨간색, 노란색, 초록색의 이미지를 선택했습니다.</p>
<p><img src="https://prod-files-secure.s3.us-west-2.amazonaws.com/84f2994a-af56-430a-9426-7ad9474a8b33/f1dceb2d-1c63-4159-8cf1-35650cca67b9/Untitled.png" alt="Untitled"></p>
<p>대체 텍스트 생성기는 다음과 같은 결과를 생성했습니다.</p>
<ul>
<li>나무 테이블 위의 도자기 그릇에 담긴 신선한 붉은 사과.</li>
<li>빛나는 붉은 사과가 가득 담긴 나무 그릇.</li>
<li>베이지색 그릇에 익은 붉은 사과 한 줌이 담겨 있습니다.</li>
<li>고요한 바다 위로 지는 아름다운 일몰. 물에 반사된 생생한 주황색과 분홍색 빛깔이 특징입니다.</li>
</ul>
<p>처음 세 가지 설명은 틀리지 않았지만 그다지 정확하지도 않습니다. 몇 가지 설명은 나무 테이블이나 그릇을 언급하지만 사진에는 보이지 않습니다. 또한, 어떤 설명도 전시된 녹색 또는 노란색 사과를 언급하지 않습니다. 네 번째 설명은 단순히 틀렸고 쓸모가 없습니다.</p>
<p>이 실험에서 알 수 있듯이, 인간의 설명이 더 정확합니다. 우리는 명확하고 일관된 언어를 사용하여 이미지를 설명하고 주변 맥락을 고려할 수 있습니다.</p>
<p>자동 생성된 설명을 사용하여 대체 텍스트를 초안한 다음 게시하기 전에 내용이 올바르고 적절한지 수동으로 확인할 수 있습니다.</p>
<h3 id="소셜-미디어에서의-영향력-확대"><strong>소셜 미디어에서의 영향력 확대</strong></h3>
<p>모든 사람이 온라인에서 양질의 콘텐츠를 즐길 수 있어야 합니다. 소셜 미디어 게시물에 대체 텍스트를 추가하면 콘텐츠에 더 쉽게 접근할 수 있습니다. 고품질의 접근 가능한 소셜 미디어 콘텐츠를 만드는 데 필요한 대체 텍스트 &#39;필수 지식&#39;은 다음과 같습니다.</p>
<ul>
<li>간결하고 간단하게 유지하세요 - 주변 맥락을 고려하여 이미지를 설명하세요. 관련성이 없다면 모든 세부 사항을 설명할 필요는 없습니다. 대체 텍스트는 100~200자 정도로 유지하세요.</li>
<li>텍스트 이미지를 사용하는 경우 해당 텍스트를 대체 텍스트에 포함하세요.</li>
<li>검색 엔진 최적화(SEO)를 개선하기 위해 대체 텍스트를 사용하지 마세요. 이미지 설명에 키워드를 논리적으로 넣을 수 있다면 그렇게 하세요. 하지만 대신 해시태그와 수반되는 게시물 텍스트를 사용하는 것이 더 좋습니다.</li>
</ul>
<h3 id="플로우차트"><strong>플로우차트</strong></h3>
<p>플로우차트는 워크플로 또는 프로세스를 나타내는 다이어그램입니다. 이는 독자를 시각적인 방식으로 복잡한 프로세스를 안내하는 것을 목표로 하지만, 이는 시각적이지 않은 독자에게는 접근하기 어렵게 만듭니다.</p>
<p>항상 제목, 목록 또는 데이터 표를 사용하여 차트의 구조화된 텍스트 버전을 포함합니다. 이렇게 하면 복잡한 대체 텍스트를 제공할 필요가 없으며 간단히 &#39;[프로세스 이름]에 대한 흐름도, 페이지에 전체 설명&#39;이라고 말할 수 있습니다.</p>
<p>크리스 젠킨스는 다양한 방식으로 데이터를 표현하는 것의 중요성에 대해 설명합니다. &#39;사람들은 종종 차트의 복잡성이나 미묘한 차이점을 처리하는 데 시간이 필요합니다. 정보를 제공하는 방법이 많을수록 전문가든 초보자든 데이터를 이해하는 데 도움이 됩니다.&#39;</p>
<h3 id="인포그래픽"><strong>인포그래픽</strong></h3>
<p>인포그래픽은 시각적 스토리텔링에 관한 것입니다. 인포그래픽은 정보를 빠르고 &#39;한눈에&#39; 보여주기 위한 것입니다. 그러나 인포그래픽을 접근하기 쉽게 만들기 위해 2단계 접근 방식을 권장합니다. 먼저 인포그래픽을 설명하는 짧은 설명을 작성하고 간략하게 유지한 다음 긴 설명을 작성하여 이미지의 일부인 모든 텍스트를 포함하여 모든 필수 정보를 전달합니다.</p>
<p>크리스 젠킨스는 &#39;포함성과 접근성을 고려한다면 시각적 요소만 우선시하는 것이 아니라 모든 사용자 요구 사항을 고려해야 합니다. 시각적으로 접근할 수 있는 수단이 없는 사람들에게 이를 설명하는 것이 중요합니다.&#39;라고 설명합니다.</p>
<aside>
💀 쓰레기를 버리지 마세요! 대체 텍스트를 쓰세요. 이상적으로는 페이지에 쓰세요. 그러면 더 많은 사람들이 이미지를 이해할 수 있을 겁니다.
이 블로그 게시물을 대체 텍스트를 사용하지 않는 아는 사람과 공유하여, 대체 텍스트가 필요한 사람들에게 어떤 영향을 미치는지 알려주시기 바랍니다.

</aside>]]></description>
        </item>
        <item>
            <title><![CDATA[Next.js Google Fonts를 통한 폰트 최적화]]></title>
            <link>https://velog.io/@_mo__on/Next.js-Google-Fonts%EB%A5%BC-%ED%86%B5%ED%95%9C-%ED%8F%B0%ED%8A%B8-%EC%B5%9C%EC%A0%81%ED%99%94</link>
            <guid>https://velog.io/@_mo__on/Next.js-Google-Fonts%EB%A5%BC-%ED%86%B5%ED%95%9C-%ED%8F%B0%ED%8A%B8-%EC%B5%9C%EC%A0%81%ED%99%94</guid>
            <pubDate>Mon, 30 Sep 2024 05:00:44 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/_mo__on/post/6e2c5056-b10e-439c-b5f7-e91d3587c8fd/image.avif" alt=""></p>
<p>Next.js 프로젝트를 진행하면서 로컬 폰트를 사용하다가 해당 폰트가 Google Fonts에 등록된 것을 확인하신 경험이 있으신가요? 
(일년전 제 경험담 입니다.. 머쓱 ^_^ . . )
이번 포스팅에서는 로컬 폰트와 Google Fonts를 통한 폰트 사용을 비교하고, 
Next.js에서 제공하는 <code>next/font</code>를 사용하여 폰트 최적화하는 방법에 대해서 얘기 해보겠습니다😄 
더하여 폰트 최적화의 이점과 이를 통해 프로젝트의 퍼포먼스를 우아하게 향상시키는 방법에 대해 알아보겠습니다.</p>
<h2 id="nextjs에서-google-fonts를-사용하여-폰트-최적화하기">Next.js에서 Google Fonts를 사용하여 폰트 최적화하기</h2>
<p>Next.js는 웹 폰트 최적화에 도움이 되는 <code>next/font</code> 모듈을 제공합니다. 
보통 Image 모듈과 함께 가장 먼저 접하게 되고 손쉽게 사용할 수 있는 아주 강력한 모듈입니다.
이 모듈을 사용하면 로컬 폰트와 Google Fonts를 간편하게 사용할 수 있으며, 
브라우저의 렌더링 방식에 최적화된 폰트 로딩 전략을 자동으로 적용할 수 있습니다. 
이를 통해 폰트 로딩 시간을 줄이고 사용자 경험을 향상시킬 수 있습니다.</p>
<h2 id="예제-google-fonts의-jua체-적용">예제: Google Fonts의 JUA체 적용</h2>
<h3 id="1-기존-로컬-폰트-사용하기">1. 기존: 로컬 폰트 사용하기</h3>
<p>먼저, 로컬 폰트를 사용한 기존 코드를 살펴보겠습니다. 
아래 코드는 Next.js 프로젝트에서 로컬 폰트를 적용하는 방법을 보여줍니다.</p>
<pre><code class="language-tsx">
import localFont from &#39;next/font/local&#39;;

export const jua = localFont({
  src: &#39;./BMJUA.woff&#39;,
  display: &#39;swap&#39;,
  weight: &#39;100 900&#39;,
});</code></pre>
<p>로컬 폰트를 사용하는 이 방법은 폰트 파일을 직접 프로젝트에 포함하여 사용하므로 외부 요청이 필요 없습니다. 
그러나 파일 크기가 커질수록 폰트 로딩 시간이 증가하여 초기 페이지 로딩 속도에 영향을 줄 수 있습니다.</p>
<h3 id="2-개선-google-fonts-사용하기">2. 개선: Google Fonts 사용하기</h3>
<p>다음은 Google Fonts를 사용하는 개선된 방식입니다. 
Next.js의 <code>next/font/google</code>을 사용하여 Google Fonts에서 JUA체를 불러옵니다.</p>
<pre><code class="language-tsx">/font/google&#39;;

export const jua = Jua({ subsets: [&#39;latin&#39;], weight: [&#39;400&#39;] });</code></pre>
<h3 id="퍼포먼스-비교">퍼포먼스 비교</h3>
<ul>
<li><strong>로컬 폰트 사용</strong>: 로컬 폰트를 프로젝트에 포함하여 사용 시, 폰트 파일을 직접 로드해야 합니다. 이는 초기 로딩 시 폰트 파일의 크기에 따라 성능에 영향을 미칠 수 있습니다. 로컬 폰트의 파일 크기가 크다면 첫 페이지 로딩 시간이 지연될 수 있습니다.</li>
<li><strong>Google Fonts 사용</strong>: Google Fonts를 사용하면 CDN(Content Delivery Network)을 통해 최적화된 폰트 파일을 불러올 수 있습니다. 이를 통해 초기 로딩 시 브라우저가 폰트를 빠르게 가져올 수 있으며, 필요하지 않은 부분은 로드하지 않아 효율적입니다.</li>
</ul>
<p>실제 프로젝트에서 폰트 최적화 전후의 성능을 측정해보았습니다. 
그 결과, Google Fonts를 사용하여 폰트를 최적화할 경우 로딩 성능이 크게 개선되는 것을 확인할 수 있었습니다. </p>
<h3 id="1-로컬-폰트-사용-시">1. 로컬 폰트 사용 시</h3>
<ul>
<li><strong>폰트 파일 크기</strong>: 414KB</li>
<li><strong>DOM 로드 시간</strong>: 780ms</li>
</ul>
<p>로컬 폰트를 사용할 때는 폰트 파일을 프로젝트 내부에 포함하고, 초기 로딩 시 브라우저가 해당 파일을 직접 가져와야 합니다. 이 경우 폰트 파일 크기가 414KB로 상당히 큰 편이며, 이는 페이지 로딩 시 필연적으로 더 긴 시간을 유발하게 됩니다. 테스트 결과, DOM 로드 시간은 약 780ms로 나타났으며, 이는 사용자 경험에 부정적인 영향을 미칠 수 있습니다.</p>
<p><img src="https://velog.velcdn.com/images/_mo__on/post/fec58d1e-8768-43a9-b357-fc7c5096cd90/image.png" alt=""></p>
<h3 id="2-google-fonts-사용-시">2. Google Fonts 사용 시</h3>
<ul>
<li><strong>최초 파일 크기</strong>: 12KB</li>
<li><strong>분할 로드</strong>: 폰트가 10개의 파일로 분리되어 로드됨</li>
<li><strong>DOM 로드 시간</strong>: 350ms</li>
</ul>
<p>Google Fonts를 사용할 경우, 폰트를 한꺼번에 로드하는 대신 필요한 폰트만 분할하여 로드합니다. 초기 로딩 시 가장 작은 단위의 폰트 파일을 먼저 가져오고, 이후 필요한 글꼴을 여러 개의 작은 파일로 분리해 로드하는 방식으로 성능을 최적화합니다. 그 결과 최초 파일 크기는 <strong>12KB</strong>에 불과하며, <strong>분할 로딩</strong> 덕분에 브라우저가 폰트를 빠르게 로드할 수 있습니다. 이 방식으로 측정한 DOM 로드 시간은 약 350ms로, 로컬 폰트 사용 시와 비교하여 거의 절반 수준으로 단축되었습니다.</p>
<p><img src="https://velog.velcdn.com/images/_mo__on/post/aa8b6c18-e50c-4381-8930-f827e546dd74/image.png" alt=""></p>
<p>이러한 최적화는 Google Fonts의 CDN을 통한 빠른 파일 배포와 함께, 불필요한 폰트 데이터를 걸러내는 서브셋 기능을 활용한 결과입니다. 분할 로드 덕분에 브라우저가 필요한 부분만 가져와 렌더링하는 방식이 효율적으로 작동한 것을 확인할 수 있습니다.</p>
<h3 id="요약하자면">요약하자면?</h3>
<p>로컬 폰트는 큰 파일 크기 때문에 로딩 시간이 길어지고 사용자 경험을 저해할 수 있지만, Google Fonts를 활용하면 폰트 파일의 크기를 최소화하고 필요한 부분만 분할 로드할 수 있어 초기 로딩 시간을 크게 줄일 수 있습니다.</p>
<blockquote>
<p>Tip:     보다 정확한 성능 테스트를 위해서는 Lighthouse 또는 PageSpeed Insights와 같은 도구를 사용하여 정확한 로딩 시간을 측정할 수 있습니다.</p>
</blockquote>
<h2 id="nextjs에서-google-fonts-사용의-이점">Next.js에서 Google Fonts 사용의 이점</h2>
<h3 id="1-기술적-이점">1. 기술적 이점</h3>
<ul>
<li><strong>자동 최적화</strong>: Next.js의 <code>next/font/google</code>은 Google Fonts의 폰트 로딩을 최적화하여 서브셋(subset) 기능을 통해 불필요한 문자를 로드하지 않습니다. 이를 통해 폰트 파일의 크기가 줄어들어 로딩 속도가 개선됩니다.</li>
<li><strong>브라우저 캐싱</strong>: Google Fonts를 사용하면, 브라우저 캐시를 활용하여 폰트를 저장할 수 있습니다. 다른 웹사이트에서 동일한 폰트를 사용 중이라면, 이미 캐시된 폰트를 재사용하여 로딩 시간을 더욱 단축할 수 있습니다.</li>
<li><strong>자동 폰트 표시 설정</strong>: <code>display</code> 속성을 자동으로 설정하여 폰트가 로드되기 전까지 텍스트의 가시성을 보장합니다. 이는 페이지 렌더링을 지연시키지 않고 사용자 경험을 향상시키는 중요한 요소입니다.</li>
<li><strong>빌드 시 정적 에셋과 최적화</strong>: Google Fonts와 같은 외부 서비스에 대한 추가 브라우저 요청이 필요하지 않고도 폰트를 사용할 수 있게 해줍니다. 이는 CDN을 통한 글꼴 제공업체와의 데이터 공유를 제거하여 프로세스를 간소화하고 개인 정보 보호를 강화할 뿐만 아니라 전체 성능에 영향을 미칠 수 있는 로드 시간을 크게 단축합니다.</li>
<li><strong>size-adjust 속성 자동화:</strong></li>
</ul>
<p><code>@next/font</code>의 또 다른 핵심 기능은 CSS의 <code>size-adjust</code> 속성의 자동 사용 기능입니다. 이 속성은 폰트를 로딩하는 과정에서 서로 다른 폰트 크기로 인하여 발생하는 레이아웃 이동(Layout Shift)을 방지합니다. 이를 통해 사용자에게 안정적인 시각적 경험을 제공하며, 이는 사용자 유지율과 SEO 순위에 중요한 요소로 작용합니다.</p>
<h3 id="2-경험적-이점">2. 경험적 이점</h3>
<ul>
<li><strong>일관된 폰트 적용</strong>: Google Fonts를 사용하면 여러 프로젝트에서 일관된 폰트 스타일을 쉽게 적용할 수 있습니다. 특히 다양한 서체와 가중치를 사용할 수 있어 디자인의 유연성을 높입니다.</li>
<li><strong>코드 간소화</strong>: 로컬 폰트 파일을 직접 관리하지 않아도 되므로, 코드의 가독성이 향상되고 유지 보수가 편리해집니다.</li>
</ul>
<h2 id="간단한-예제">간단한 예제!</h2>
<p>Google Fonts를 사용하여 Next.js 프로젝트에서 간단한 폰트 적용 과정을 다시 한번 정리해보겠습니다.</p>
<ol>
<li>Google Fonts에서 사용할 폰트(<code>Jua</code>)를 Next.js에 가져옵니다.</li>
</ol>
<pre><code class="language-tsx">
import { Jua } from &#39;next/font/google&#39;;

export const jua = Jua({ subsets: [&#39;latin&#39;], weight: [&#39;400&#39;] });
</code></pre>
<ol>
<li>가져온 폰트를 프로젝트에 적용합니다.</li>
</ol>
<pre><code class="language-tsx">
// fonts
import { jua } from &#39;@/assets/fonts/fonts&#39;;

export default function RootLayout({
  children,
}: Readonly&lt;{
  children: React.ReactNode;
}&gt;) {
  return (
    &lt;html lang=&quot;en&quot;&gt;
      {/* &lt;body className={`${geistSans.variable} ${geistMono.variable} antialiased`}&gt; */}
      &lt;body className={`${jua.className} antialiased`}&gt;
        &lt;NextTopLoader color=&quot;#16a34a&quot; shadow=&quot;0 0 10px #16a34a,0 0 5px #16a34a&quot; /&gt;
        &lt;div id=&quot;wrap&quot; className=&quot;min-h-screen&quot;&gt;
          &lt;Header /&gt;
          {children}
          &lt;Footer /&gt;
        &lt;/div&gt;
        &lt;DialogProvider /&gt;
      &lt;/body&gt;
    &lt;/html&gt;
  );
}
</code></pre>
<p>이렇게 하면 Google Fonts를 통해 로드된 폰트가 프로젝트에 적용됩니다.</p>
<h2 id="결론">결론</h2>
<p>Next.js의 <code>next/font</code>를 사용하여 Google Fonts로 폰트를 적용하면 
로컬 폰트를 사용할 때보다 더 나은 성능을 얻을 수 있습니다. 
폰트 로딩 시간을 줄이고 사용자 경험을 개선하며, 코드를 간소화할 수 있는 것이 큰 장점입니다. </p>
<p>추가적으로 해당 글에 대하여 궁금하신 점이 있거나, NextJs에 대해서 알아보고 싶으신 분이 있으시다면
언제든지 말씀 부탁 드리겠습니다.
오늘도 읽어주셔서 감사합니다.</p>
<h2 id="참고-링크">참고 링크</h2>
<ul>
<li>Next.js Documentation - next/font</li>
<li><a href="https://fonts.google.com/">Google Fonts</a></li>
<li>Web Font Optimization - Google Developers</li>
</ul>
<h3 id="요약하자면-1">요약하자면?</h3>
<p>로컬 폰트는 큰 파일 크기 때문에 로딩 시간이 길어지고 사용자 경험을 저해할 수 있지만, Google Fonts를 활용하면 폰트 파일의 크기를 최소화하고 필요한 부분만 분할 로드할 수 있어 초기 로딩 시간을 크게 줄일 수 있습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[타입스크립트] ts-expect-error 처리]]></title>
            <link>https://velog.io/@_mo__on/%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-ts-expect-error-%EC%B2%98%EB%A6%AC</link>
            <guid>https://velog.io/@_mo__on/%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-ts-expect-error-%EC%B2%98%EB%A6%AC</guid>
            <pubDate>Fri, 20 Sep 2024 02:02:13 GMT</pubDate>
            <description><![CDATA[<h2 id="문제">문제</h2>
<p>갑작스레 빌드가 안되는 상황이 발생했다.
분명 vite + react로 작업할 때는 @ts-ignore로 빌드가 되었는데,
경고 문구가 뜨면서 빌드가 진행되지 않았다.</p>
<p>조금 더 에러 로그를 찾아보니..</p>
<pre><code>Include a description after the &quot;@ts-expect-error&quot; 
directive to explain why the @ts-expect-error is necessary. 
The description must be 3 characters or longer.</code></pre><p>직역하자면 최신 TypeScript에서는 @ts-expect-error 지시어에 3자 이상으로 설명을 추가해야된다는 말 이었다.</p>
<h2 id="해결">해결</h2>
<h3 id="수정-전">수정 전</h3>
<pre><code>// @ts-expect-error
import remarkA11yEmoji from &#39;@fec/remark-a11y-emoji&#39;;</code></pre><h3 id="수정-후">수정 후</h3>
<pre><code>// @ts-expect-error: remark-a11y-emoji has no types
import remarkA11yEmoji from &#39;@fec/remark-a11y-emoji&#39;;</code></pre><p>lint와 TS 공부를 뒷전으로 했더니 별거 아닌 에러로 삽질을 해버렸다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[프로그래머스] PCCP 기출문제 2번 / 석유 시추

]]></title>
            <link>https://velog.io/@_mo__on/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-PCCP-%EA%B8%B0%EC%B6%9C%EB%AC%B8%EC%A0%9C-2%EB%B2%88-%EC%84%9D%EC%9C%A0-%EC%8B%9C%EC%B6%94</link>
            <guid>https://velog.io/@_mo__on/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-PCCP-%EA%B8%B0%EC%B6%9C%EB%AC%B8%EC%A0%9C-2%EB%B2%88-%EC%84%9D%EC%9C%A0-%EC%8B%9C%EC%B6%94</guid>
            <pubDate>Mon, 16 Sep 2024 10:01:45 GMT</pubDate>
            <description><![CDATA[<h1 id="문제설명">문제설명</h1>
<p>세로길이가 n 가로길이가 m인 격자 모양의 땅 속에서 석유가 발견되었습니다. 석유는 여러 덩어리로 나누어 묻혀있습니다. 당신이 시추관을 수직으로 단 하나만 뚫을 수 있을 때, 가장 많은 석유를 뽑을 수 있는 시추관의 위치를 찾으려고 합니다. 시추관은 열 하나를 관통하는 형태여야 하며, 열과 열 사이에 시추관을 뚫을 수 없습니다.
<img src="https://velog.velcdn.com/images/_mo__on/post/1e24effe-22b9-410f-b95b-6ef1554d249c/image.png" alt=""></p>
<p>예를 들어 가로가 8, 세로가 5인 격자 모양의 땅 속에 위 그림처럼 석유가 발견되었다고 가정하겠습니다. 상, 하, 좌, 우로 연결된 석유는 하나의 덩어리이며, 석유 덩어리의 크기는 덩어리에 포함된 칸의 수입니다. 그림에서 석유 덩어리의 크기는 왼쪽부터 8, 7, 2입니다.</p>
<p><img src="https://velog.velcdn.com/images/_mo__on/post/733c1b6a-fde6-4325-b84d-776c47c955dd/image.png" alt=""></p>
<p>시추관은 위 그림처럼 설치한 위치 아래로 끝까지 뻗어나갑니다. 만약 시추관이 석유 덩어리의 일부를 지나면 해당 덩어리에 속한 모든 석유를 뽑을 수 있습니다. 시추관이 뽑을 수 있는 석유량은 시추관이 지나는 석유 덩어리들의 크기를 모두 합한 값입니다. 시추관을 설치한 위치에 따라 뽑을 수 있는 석유량은 다음과 같습니다.</p>
<p>*<em>시추관의 위치    획득한 덩어리    총 석유량
*</em>1    [8]    8
2    [8]    8
3    [8]    8
4    [7]    7
5    [7]    7
6    [7]    7
7    [7, 2]    9
8    [2]    2
오른쪽 그림처럼 7번 열에 시추관을 설치하면 크기가 7, 2인 덩어리의 석유를 얻어 뽑을 수 있는 석유량이 9로 가장 많습니다.</p>
<p>석유가 묻힌 땅과 석유 덩어리를 나타내는 2차원 정수 배열 land가 매개변수로 주어집니다. 이때 시추관 하나를 설치해 뽑을 수 있는 가장 많은 석유량을 return 하도록 solution 함수를 완성해 주세요.</p>
<h1 id="code">code</h1>
<pre><code>function solution(land) {
    const n = land.length;
    const m = land[0].length;

    // 방문 여부와 석유 덩어리 인덱스를 저장하는 배열
    const visited = Array.from({ length: n }, () =&gt; Array(m).fill(0));
    // 각 석유 덩어리의 크기를 저장하는 맵
    const oilMap = new Map();

    // 상하좌우 방향 벡터
    const dx = [-1, 0, 1, 0];
    const dy = [0, 1, 0, -1];

    let oilIndex = 1; // 석유 덩어리의 인덱스
    let maxOil = 0;   // 최대 석유량

    // BFS를 이용하여 석유 덩어리를 탐색하고 크기를 계산하는 함수
    function bfs(initX, initY) {
        let oilSize = 0;
        const queue = [{ x: initX, y: initY }];
        visited[initX][initY] = oilIndex;

        while (queue.length &gt; 0) {
            const { x, y } = queue.shift();

            if (land[x][y] === 1) oilSize++;

            for (let i = 0; i &lt; 4; i++) {
                const nx = x + dx[i];
                const ny = y + dy[i];

                // 격자 범위 내에 있고, 방문하지 않았으며, 석유가 있는 경우
                if (nx &gt;= 0 &amp;&amp; nx &lt; n &amp;&amp; ny &gt;= 0 &amp;&amp; ny &lt; m &amp;&amp; visited[nx][ny] === 0 &amp;&amp; land[nx][ny] === 1) {
                    visited[nx][ny] = oilIndex;
                    queue.push({ x: nx, y: ny });
                }
            }
        }

        // 석유 덩어리의 크기를 맵에 저장
        oilMap.set(oilIndex++, oilSize);
    }

    // 모든 위치를 탐색하여 석유 덩어리를 식별하고 크기를 저장
    for (let i = 0; i &lt; n; i++) {
        for (let j = 0; j &lt; m; j++) {
            if (visited[i][j] === 0 &amp;&amp; land[i][j] === 1) bfs(i, j);
        }
    }

    // 각 열에 시추관을 설치했을 때 얻을 수 있는 석유량 계산
    for (let j = 0; j &lt; m; j++) {
        let columnOil = 0;
        const uniqueOilIndices = new Set();

        // 해당 열에서 지나는 석유 덩어리의 인덱스를 수집
        for (let i = 0; i &lt; n; i++) {
            if (visited[i][j] &gt; 0) uniqueOilIndices.add(visited[i][j]);
        }

        // 중복을 제거한 석유 덩어리들의 크기를 합산
        uniqueOilIndices.forEach(index =&gt; {
            columnOil += oilMap.get(index);
        });

        // 최대 석유량 갱신
        maxOil = Math.max(maxOil, columnOil);
    }

    return maxOil;
}
</code></pre><h1 id="문제-접근-및-사용한-알고리즘">문제 접근 및 사용한 알고리즘</h1>
<p>이 문제는 2차원 격자에서 연결된 석유 덩어리를 찾아내고, 각 column을 따라 시추관을 설치했을 때 최대 석유량을 계산하는 문제였다.</p>
<h2 id="bfs를-통한-석유-덩어리-탐색">BFS를 통한 석유 덩어리 탐색</h2>
<p>격자에서 석유가 있는 위치를 방문하지 않았다면, 
BFS(너비 우선 탐색)를 이용하여 연결된 석유 덩어리를 탐색한다.
각 석유 덩어리에 고유한 인덱스를 부여하고, 해당 덩어리의 크기를 저장한다.</p>
<h2 id="열별로-시추관-설치-시-획득-가능한-석유량-계산">열별로 시추관 설치 시 획득 가능한 석유량 계산</h2>
<p>각 열에서 시추관이 지나는 석유 덩어리들의 인덱스를 수집한다.
수집된 인덱스를 기반으로 석유 덩어리들의 크기를 합산하여 해당 열에서 얻을 수 있는 총 석유량을 계산하고, 모든 열에 대해 위 과정을 수행하여 최대 석유량을 구한다.</p>
<h2 id="효율성-고려">효율성 고려</h2>
<p>이미 방문한 위치를 체크하여 중복 탐색을 방지 및 석유 덩어리의 크기를 사전 계산</p>
<h1 id="후기">후기</h1>
<p>오랜만에서 도전한 2단계여서 그런지 뇌가 많이 굳은 느낌이 들었다.
특히나 bfs로 접근하는게 맞는지 고민하는 시간이 있었다. 
처음에는 단순히 각 열마다 석유 칸의 개수를 세려고 했지만, 
석유가 상하좌우로 연결된 덩어리라는 점에서 복잡도가 증가해버렸다 ㅠ
시추관이 지나가는 열에서 어떤 석유 덩어리들을 지나는지 파악해야 했었다.
키포인트를 정리하자면 아래와 같다.</p>
<ol>
<li>BFS 알고리즘을 활용하여 석유 덩어리를 탐색하고, 각 덩어리에 고유한 인덱스를 부여하여 식별</li>
<li>visited 배열에 석유 덩어리의 인덱스를 저장함으로써, 시추관이 지나는 열에서 어떤 석유 덩어리들을 포함하는지 확인</li>
<li>Set 자료구조를 사용하여 중복된 석유 덩어리를 제외하고 합산</li>
</ol>
<p><img src="https://velog.velcdn.com/images/_mo__on/post/99ed9ee5-8546-462d-b9d5-ebdfcd536ba1/image.gif" alt=""></p>
<p>감사합니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[2024 FEConf Korea 방구석 후기]]></title>
            <link>https://velog.io/@_mo__on/2024-FEConf-Korea-%EB%B0%A9%EA%B5%AC%EC%84%9D-%ED%9B%84%EA%B8%B0-%EC%A0%84%EC%B2%B4%EB%A6%AC%EB%B7%B0</link>
            <guid>https://velog.io/@_mo__on/2024-FEConf-Korea-%EB%B0%A9%EA%B5%AC%EC%84%9D-%ED%9B%84%EA%B8%B0-%EC%A0%84%EC%B2%B4%EB%A6%AC%EB%B7%B0</guid>
            <pubDate>Mon, 02 Sep 2024 13:24:05 GMT</pubDate>
            <description><![CDATA[<h1 id="메인-링크">메인 링크</h1>
<p><a href="https://www.youtube.com/@feconfkorea/videos">FEConf Korea</a></p>
<h1 id="feconf-2024-">FECONF 2024 !</h1>
<p>주말 동안 FECONF 2024의 모든 발표 영상이 유튜브에 업로드되었습니다.</p>
<p>어제까지 모든 영상을 시청했는데, 개인적으로 흥미로워서 여러 번 다시 본 세션이 세 가지 정도 있었습니다. 
특히 WebGL을 심도 있게 다루고 디자이너와 협업했던 과정이 인상적이었습니다. 
유닛 테스트와 E2E 테스트의 각 단계에서 환경에 따라 커버리지를 처리한 발표들도 주목할 만했고,
React Native에 Web를 이식하기 까지의 과정들을 다룬 발표도 기억에 남았습니다.</p>
<p>더불어, 올해와 내년, 나아가 더 먼 미래까지 빅테크 기업들이 어떤 방향으로 나아가고 있으며
그들의 사례를 어떻게 우리에게 적용할 수 있을지에 대한 고찰도 깊이 있게 다뤄졌습니다.</p>
<p>아래는 흥미로웠던 발표들과 그에 대한 개인적인 소회를 간략히 정리해 보았습니다.</p>
<p><strong>각 아티클에 대한 상세 후기는 조만간 정리하여 찾아 뵙겠습니다!
(같이 쓰려고 했는데 너무 장문이라 따로 포스팅 하기로 하였습니다 … 🥹)</strong></p>
<h1 id="1-10만-글로벌-유저들이-생겨버린-threejs-사이드-프로젝트">1. 10만 글로벌 유저들이 생겨버린 Three.js 사이드 프로젝트</h1>
<ul>
<li>ShaderGradiente 개발기**</li>
</ul>
<h2 id="발표-소개">발표 소개</h2>
<ul>
<li>Company: 하버스쿨</li>
<li>발표자: 지용민</li>
<li>발표자료: <a href="https://feconf.framer.website/v2">https://feconf.framer.website/v2</a></li>
<li>상세후기: COMING SOON</li>
<li>링크:</li>
</ul>
<p><a href="https://www.youtube.com/watch?v=CSChpoiRTIY&amp;t=321s">https://www.youtube.com/watch?v=CSChpoiRTIY&amp;t=321s</a></p>
<h2 id="후기">후기</h2>
<p>최근에 WebGL에 많은 관심을 두고 있던 터라, 이번 발표는 개인적으로 반가운 주제였습니다. 하지만 실제 발표 내용을 들어보니 기대 이상으로 인상 깊었습니다.</p>
<p>발표의 앞부분에서는 발표자가 사이드 프로젝트를 시작하게 된 계기와 아이템 선정 과정에 대해 설명했습니다. 본업과 병행하면서 사이드 프로젝트를 어떻게 진행했는지, 그리고 아이템을 선정하기까지의 과정이 담겨 있었습니다. 결론적으로, 이 프로젝트의 목표는 <strong>3D를 활용해 그라디언트를 웹에서 더욱 매력적으로 사용하는 방법을 찾는 것</strong>이었습니다. 발표자는 Three.js와 메인 기술 스택인 Shader에 대해 집중적으로 소개하며, 이를 활용한 예시들을 설명했습니다.</p>
<p><img src="https://velog.velcdn.com/images/_mo__on/post/4ef309b1-ca9f-41f0-b408-1deb2939047f/image.avif" alt="">
<img src="https://velog.velcdn.com/images/_mo__on/post/2109733f-e376-4aef-9c8f-06cbbbf72931/image.avif" alt=""><img src="https://velog.velcdn.com/images/_mo__on/post/9c887216-57a5-40fc-b7ae-75d9a372db39/image.avif" alt=""></p>
<p>이후, FRAMER를 이용해 개발자뿐만 아니라 디자이너도 손쉽게 사용할 수 있는 <strong>Shader Gradient</strong> 플러그인을 개발하는 과정을 소개했습니다. 이 플러그인은 그라디언트를 더욱 효과적으로 구현할 수 있도록 도와줍니다.
<img src="https://velog.velcdn.com/images/_mo__on/post/65e09de7-9c8a-47f3-be31-c86abb07e997/image.png" alt=""></p>
<p>마지막에는, 플러그인 런칭 이후 얻은 성과들(깃허브 스타, 피그마 플러그인 다운로드 수, 유료화 및 제품화 과정 등)에 대해 이야기를 나눴습니다. 발표자 분이 사이드 프로젝트를 진행하면서 느낀 점들을 결론으로 남겼습니다.</p>
<p>개인적으로, 발표자 분께서 사이드 프로젝트를 처음 시작할 때 고려해야 할 중요한 내용들을 공유한 것이 매우 유익했습니다.</p>
<h3 id="사이드-프로젝트초기">사이드 프로젝트(초기)</h3>
<ol>
<li><strong>루틴을 정하자:</strong> 일정한 루틴을 가지면 프로젝트 진행이 수월해집니다.</li>
<li><strong>진짜 재밌는 걸 하자:</strong> 자기가 진정으로 즐길 수 있는 주제를 선택하세요.</li>
<li><strong>어려운 문제 해결에서 기쁨을 느끼자:</strong> 도전적인 문제를 해결하면서 성장하는 기쁨을 만끽하세요.</li>
</ol>
<h3 id="사이드-프로젝트제품화">사이드 프로젝트(제품화)</h3>
<ol>
<li><strong>Customer:</strong> 고객을 먼저 찾자. 사용할 사람, 좋아할 사람, 구매할 사람이 누구인지 명확히 하세요. 꼭 만들어야 할 가치 있는 제품인지 고민해보세요.</li>
<li><strong>Mission:</strong> 결국 우리가 해야 할 일은, 지속 가능한 동기와 믿음을 찾는 것입니다. 여러분이 지속적으로 추진할 수 있는 동기를 발견하세요.</li>
</ol>
<h3 id="느낀점">느낀점</h3>
<p>이번 발표는 사이드 프로젝트를 통해 배울 수 있는 점이 많았습니다. 특히, 3D 기술을 활용한 웹 디자인과 웹 기술의 새로운 가능성은 무궁무진 하다고 다시 한번 복기 했습니다. 더불어 사이드 프로젝트를 제품화하기 위한 구체적인 전략들도 큰 도움이 되었습니다. 발표자 분의 경험을 통해, 앞으로의 프로젝트를 진행하는 데 있어 좀 더 전략적이고 체계적으로 접근할 수 있을 것 같습니다. 또한, 자신이 정말 좋아하고 열정을 가지고 있는 주제를 선택하는 것이 참 중요한거 같습니다.</p>
<p>이번 발표는 기술적 깊이뿐만 아니라 사이드 프로젝트를 성공적으로 제품화하기 위한 전략적 접근법에 대해 많은 영감을 주었습니다.</p>
<h2 id="2-쉽고-편리한-e2e-테스트-자동화를-꿈꾸며">2. <strong>쉽고 편리한 E2E 테스트 자동화를 꿈꾸며...</strong></h2>
<h3 id="발표-소개-1">발표 소개</h3>
<ul>
<li>Company: 스티비</li>
<li>발표자: 백부석(Kianue)</li>
<li>발표자료: <a href="https://drive.google.com/file/d/1zBhGoLdVB7OCoQ7M9ECTFEe4THGfoEcF/view">https://drive.google.com/file/d/1zBhGoLdVB7OCoQ7M9ECTFEe4THGfoEcF/view</a></li>
<li>상세후기: COMING SOON</li>
<li>링크:</li>
</ul>
<p><a href="https://www.youtube.com/watch?v=HqlCfWT3oZU&amp;t=1813s">FEConf 2024 [A3] 쉽고 편리한 E2E 테스트 자동화를 꿈꾸며...</a></p>
<h3 id="앞서-설명하자면">앞서 설명하자면?</h3>
<p>프론트엔드 테스트는 웹 애플리케이션의 사용자 인터페이스(UI)와 그와 관련된 기능이 올바르게 동작하는지를 검증하는 과정을 말합니다. 이를 통해 개발자는 코드 변경이나 새로운 기능 추가 시 발생할 수 있는 오류를 조기에 발견하고 수정할 수 있습니다. 프론트엔드 테스트는 일반적으로 세 가지 주요 유형으로 나눌 수 있습니다: 유닛 테스트(Unit Test), 통합 테스트(Integration Test), 그리고 <strong>E2E 테스트(End-to-End Test)</strong>입니다.</p>
<ol>
<li><p><strong>유닛 테스트 (Unit Test)</strong></p>
<p> 유닛 테스트는 애플리케이션의 가장 작은 단위인 함수나 컴포넌트가 기대한 대로 작동하는지를 검증하는 테스트입니다. 이 테스트는 보통 각 기능이 독립적으로 작동하는지를 확인하기 위해 수행됩니다. 예를 들어, 숫자를 더하는 함수가 정확히 계산 결과를 반환하는지, 버튼 클릭 시 특정 함수가 호출되는지를 테스트할 수 있습니다. 유닛 테스트는 빠르게 실행되며, 코드의 작은 부분에 대한 안정성을 확보할 수 있습니다.</p>
</li>
<li><p><strong>통합 테스트 (Integration Test)</strong></p>
<p> 통합 테스트는 여러 유닛(함수, 모듈, 컴포넌트)이 함께 작동할 때 문제가 없는지를 검증하는 테스트입니다. 예를 들어, 사용자가 입력한 데이터를 처리하는 폼과 그 데이터를 서버에 전송하는 모듈이 함께 잘 동작하는지를 테스트합니다. 이 테스트는 유닛 테스트보다 범위가 넓으며, 시스템의 다양한 부분이 올바르게 상호작용하는지를 확인합니다.</p>
</li>
<li><p><strong>E2E 테스트 (End-to-End Test)</strong></p>
<p> E2E 테스트는 사용자 관점에서 애플리케이션 전체가 올바르게 작동하는지를 검증하는 테스트입니다. 이 테스트는 실제 사용자가 웹 애플리케이션을 사용하는 것처럼 테스트를 진행하며, 모든 기능이 통합되어 잘 작동하는지를 확인합니다. 예를 들어, 사용자가 로그인하고 데이터를 입력한 후, 데이터를 서버에 전송하고 그 결과를 화면에 표시하는 전체 흐름을 테스트할 수 있습니다. E2E 테스트는 애플리케이션의 모든 부분이 통합된 상태에서 발생할 수 있는 문제를 찾는 데 유용하지만, 테스트를 설정하고 실행하는 데 시간이 많이 걸릴 수 있습니다.</p>
</li>
</ol>
<p>프론트엔드 테스트는 각기 다른 범위와 목적을 가진 유닛 테스트, 통합 테스트, E2E 테스트를 조합하여 애플리케이션의 안정성과 품질을 높이는 데 중요한 역할을 합니다. 특히 이번 발표의 메인 주제인 E2E 테스트는 애플리케이션의 전체적인 흐름과 사용자 경험을 보장하기 위해 필수적인 테스트 방법입니다.</p>
<h3 id="후기-1">후기</h3>
<p>회사 내부에서도 E2E 테스트를 몇 차례 시도한 적이 있었습니다. 마침 이번 발표에서 사용한 E2E 테스트 툴이 Playwright와 동일하여 많은 영감을 받을 수 있었습니다. 특히, 스티비에서는 테스트 툴을 Selenium에서 Playwright로 전환하면서 겪은 노력과 극복 과정을 듣고 싶었습니다. (이번 발표에서는 TDD와 모바일 네이티브 E2E는 다루지 않았습니다.)</p>
<p>발표에서는 E2E 테스트를 통해 테스트 설계, 테스트 작성, 테스트 실행, 결과 보고의 <strong>자동화된</strong> 흐름을 어떻게 구현할 수 있는지, 그리고 테스트 결과를 더 편리하게 측정할 수 있는 방법에 대한 이야기가 주를 이루었습니다.
<img src="https://velog.velcdn.com/images/_mo__on/post/1e3474c2-738e-47f3-864e-4646f087ca16/image.png" alt=""></p>
<p>체계적인 검증을 통해 테스트 툴에서 어떤 기능을 사용할지, 어떤 부분을 디벨롭 해야할지가 주요 관심사 였습니다.</p>
<p><img src="https://velog.velcdn.com/images/_mo__on/post/1c636335-a5cf-46fd-908c-d5e86a864ec3/image.png" alt="">
<img src="https://velog.velcdn.com/images/_mo__on/post/cf2df536-3cbc-43d0-af4e-8d7c291ce367/image.png" alt=""></p>
<p>현재 스티비에서는 현재 E2E 테스트를 적용하는 범위를 점차 넓혀가고 있습니다. </p>
<p>특히, Playwright의 강력한 셀렉터와 Codegen 기능, 그리고 No-Code 툴과의 결합을 통한 테스트 자동화는 매우 인상적이었습니다. </p>
<p><img src="https://velog.velcdn.com/images/_mo__on/post/ffb9455f-3746-4846-b424-a373b0dcf9f0/image.png" alt=""></p>
<p>그러나, E2E 테스트를 전체적으로 적용하는 데는 여전히 어려움이 있습니다. 특히, 제3자가 E2E 테스트를 사용하기에는 여전히 복잡한 부분이 많습니다. 이러한 이유로,  E2E 테스트를 단계적으로 도입하며, 점진적으로 적용 범위를 확대하고 있습니다.</p>
<p><img src="https://velog.velcdn.com/images/_mo__on/post/d485c539-3a50-437f-a97d-cefeb5ec31ed/image.png" alt=""></p>
<h3 id="결론">결론</h3>
<p>이번 프로젝트를 하면서 테스트의 중요성을 많이 느꼈기에 테스트는 <strong>선택이 아닌 필수가 되야한다고 생각합니다.</strong>
(당장은 어렵겠지만 언젠가는 하나의 시스템이 된다면 이점이 너무 많을거에요)</p>
<p>솔직히 이번 세션은 테스트에 대해 딥하게 경험해본적이 없어서, 
저도 발표 프로세스를 모두 이해하지 못해 여러번 다시 시청할 예정입니다.
만족할만한 이해도가 나왔을 때 해당 섹션 또한 상세 후기 + 적용 예시 + 기대할 점 등으로 분석 해보겠습니다.</p>
<h2 id="3-react-native와-웹이-공존하는-또-하나의-방법">3. <strong>React Native와 웹이 공존하는 또 하나의 방법</strong></h2>
<h3 id="발표-소개-2">발표 소개</h3>
<ul>
<li><p>Company: 브랜더진</p>
</li>
<li><p>발표자: 강선규</p>
</li>
<li><p>발표자료: <a href="https://docs.google.com/presentation/d/1LTy75UaokveJWyLfhmf_AvtC5xr2C1rwkwYamYmxe70/edit#slide=id.g279f94fa658_1_31">https://docs.google.com/presentation/d/1LTy75UaokveJWyLfhmf_AvtC5xr2C1rwkwYamYmxe70/edit#slide=id.g279f94fa658_1_31</a></p>
</li>
<li><p>상세후기: COMING SOON</p>
</li>
<li><p>링크:</p>
<p>  <a href="https://www.youtube.com/watch?v=GyU9-pE0dAg&amp;t=14s">FEConf 2024 [A4] React Native와 웹이 공존하는 또 하나의 방법</a></p>
</li>
</ul>
<h3 id="후기-2">후기</h3>
<p>당장은 앱개발을 할 일은 없지만.. 사람일은 혹시 모르는 거니까요!
작년부터 올해까지 React Native에 말말말이 있었지만, 워낙 단단한 생태계를 가지고 있어 평소 관심이 있었습니다. 무엇보다 웹뷰 작업을 할 때도 언젠가 꼭 도움이 되겠다 싶어 집중해서 본 발표 였습니다.</p>
<p>앞에서는 기존 React Native와 webView와의 통신 방식, 거기서 파생되는 문제점을 다뤘습니다.</p>
<p><strong>기존 방식 문제 정의</strong> </p>
<ol>
<li>유지보수의 어려움(onMessage에서 무수한 분기)</li>
<li>반복적인 통신 함수 작성(양쪽 사이드에서 통신 함수 재정의)
같은 기능이 추가되었을 때, 서로 핸들링이 필요한 코드를 주기적으로 작성 해야한다.
 <img src="https://velog.velcdn.com/images/_mo__on/post/9b92fe73-5692-4cf6-b725-f5b6590f88dc/image.png" alt=""></li>
</ol>
<ol start="3">
<li>단방향 통신의 한계.
코드의 흐름이 단방향이기 때문에 코드의 안정성을 보장할 수 없다.(성공/실패 여부 확인 불가)
웹에서 PostMessage로 React Native에 어떤 action을 보냈을 때,
그에 따른 React Native의 어떤지 보장할 수 없다.</li>
<li>하위호완성<ol>
<li>app의 경우: 모바일 애플리케이션은 배포를 해도 최신을 유지 하지 못한다(앱스토어 심사)</li>
<li>web의 경우: 웹은 배포해도 최신을 유지할 수 있다</li>
<li>문제: 웹에서 새로운 네이티브 기능이 담긴 이벤트를 호출했는데 구 버전 앱을 사용중이라면?</li>
<li><strong>결론: 앱이 과거 버전일 때 웹에서 새로운 네이티브 기능 사용 여부를 파악할 수 없다</strong></li>
</ol>
</li>
</ol>
<p>이러한 문제점들을 해결하고 개선하는게 발표의 주된 골조 였습니다. 
인상 깊었던 주요 트러블슈팅을 뽑자면 아래와 같았습니다.</p>
<ol>
<li>기존 문제 방식 해결
<img src="https://velog.velcdn.com/images/_mo__on/post/d5698307-75e3-48ab-b75c-04bd50b1872f/image.png" alt=""></li>
</ol>
<ol start="2">
<li>Native - webview의 방식을 server - client(backend - frontend) 방식으로 변화 과정</li>
<li>외부 시스템에서 파생된 타입 불일치를 해결한 과정</li>
<li>React Navigation을 통합한 과정(!!)</li>
</ol>
<h3 id="결론-1">결론</h3>
<p><strong>React Native와 웹이 공존하는 또 하나의 방법은 다음과 같았습니다.</strong></p>
<ol>
<li>Type-safe한 WebView 통신 라이브러리 자체 개발(!!)</li>
<li>Typescript에서 Input을 기준으로 타입이 완성되는 경험</li>
<li>사용법  중심 설계로 더 나은 추상화</li>
<li>비슷한 개념의 라이브러리의 사용법을 분석하여 적용</li>
</ol>
<p>이번 세션은 Native + 리액트가 중점이다 보니, 따로 후기로 정리하기 애매했습니다.
역시나 기승전 상세 후기로 귀결이 되버렸네요. 그만큼 딥하게 분석하기에 너무 좋은 발표 였습니다.
특히나 최근 프로젝트에서 Typescript를 도입하면서 어떻게 개선할까 고민 했는데 좋은 아이디어를 얻은거 같아요. ㅎㅎ</p>
<hr>
<h1 id="마치며">마치며</h1>
<ul>
<li>ZEP에 관심이 있으시거나, 올해 초 ZEP PJT를 하셨던 분들에게는</li>
<li><em>메타버스 서비스(ZEP)에서 게임과 WebRTC를 함께 적용하기도*</em> 재밌을거 같아요 :)</li>
</ul>
<p>마지막으로 제 후기는 이 정도로 간추리고.. <strong>직접 영상을 보시는걸 강력히 추천드립니다!</strong></p>
<p>못다한 딥한 내용들은 상세 후기에서 따로 찾아 뵙겠습니다.</p>
<p>감사합니다.</p>
<h3 id="참고-및-출처">참고 및 출처</h3>
<p><a href="https://feconf.framer.website/v2">https://feconf.framer.website/v2</a></p>
<p><a href="https://www.youtube.com/watch?v=CSChpoiRTIY&amp;t=321s">FEConf 2024 [B2] 10만 글로벌 유저들이 생겨버린 Three.js 사이드 프로젝트 ─ShaderGradient 개발기 (feat. 프레이머)</a></p>
<p><a href="https://www.youtube.com/watch?v=GyU9-pE0dAg&amp;t=14s">https://www.youtube.com/watch?v=GyU9-pE0dAg&amp;t=14s</a></p>
<p><a href="https://www.youtube.com/watch?v=HqlCfWT3oZU&amp;t=1813s">https://www.youtube.com/watch?v=HqlCfWT3oZU&amp;t=1813s</a></p>
<p><a href="https://docs.google.com/presentation/d/1LTy75UaokveJWyLfhmf_AvtC5xr2C1rwkwYamYmxe70/edit#slide=id.g278840f104b_0_51">https://docs.google.com/presentation/d/1LTy75UaokveJWyLfhmf_AvtC5xr2C1rwkwYamYmxe70/edit#slide=id.g278840f104b_0_51</a></p>
<p><a href="https://drive.google.com/file/d/1zBhGoLdVB7OCoQ7M9ECTFEe4THGfoEcF/view">https://drive.google.com/file/d/1zBhGoLdVB7OCoQ7M9ECTFEe4THGfoEcF/view</a></p>
<p><a href="https://2024.feconf.kr/">https://2024.feconf.kr/</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[NestJS로 오픈 API 데이터를 프론트엔드로 전달하기]]></title>
            <link>https://velog.io/@_mo__on/NestJS%EB%A1%9C-%EC%98%A4%ED%94%88-API-%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%A5%BC-%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C%EB%A1%9C-%EC%A0%84%EB%8B%AC%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@_mo__on/NestJS%EB%A1%9C-%EC%98%A4%ED%94%88-API-%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%A5%BC-%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C%EB%A1%9C-%EC%A0%84%EB%8B%AC%ED%95%98%EA%B8%B0</guid>
            <pubDate>Sat, 01 Jun 2024 10:28:16 GMT</pubDate>
            <description><![CDATA[<p>백엔드에서 오픈 API 데이터를 정제하고 이를 프론트엔드에 전달하는 방법을 알아보자. 많은 개발자들이 방대한 오픈 API 데이터를 다루면서, 이를 어떻게 효과적으로 처리할지 고민하곤 한다. 이번 포스팅에서는 백엔드에서 데이터를 정제하는 이유와 방법을 구체적으로 설명하고, 이를 통해 프론트엔드에 보다 효율적으로 데이터를 전달하는 방법을 살펴보겠다.</p>
<h2 id="서론">서론</h2>
<p>오픈 API는 외부 서비스와의 통신을 통해 다양한 데이터를 가져올 수 있는 강력한 도구이다. 하지만 방대한 데이터를 효과적으로 관리하고 클라이언트에 전달하는 것은 큰 도전 과제다. 특히, 모든 정보를 데이터베이스에 저장하기 어렵고, 필요한 정보만 정제하여 클라이언트에 제공하는 것이 중요하다. 이번 포스팅에서는 백엔드에서 데이터를 정제하는 이유와 그 방법을 구체적으로 알아보겠다.</p>
<h2 id="주제">주제</h2>
<h3 id="백엔드에서-오픈-api-데이터-정제의-필요성">백엔드에서 오픈 API 데이터 정제의 필요성</h3>
<ol>
<li><strong>보안 강화</strong>: 민감한 정보가 클라이언트로 노출되지 않도록 한다.</li>
<li><strong>성능 향상</strong>: 네트워크 트래픽을 줄이고, 클라이언트의 데이터 처리 부담을 줄인다.</li>
<li><strong>일관성 유지</strong>: 여러 클라이언트에서 일관된 데이터 형식을 보장한다.</li>
<li><strong>유지 보수성</strong>: 데이터 모델 변경 시, 백엔드에서 한 번에 변경할 수 있어 유지 보수가 용이하다.</li>
</ol>
<h3 id="예제">예제</h3>
<h4 id="1-백엔드에서-오픈-api-데이터를-정제하는-방법">1. 백엔드에서 오픈 API 데이터를 정제하는 방법</h4>
<p>백엔드에서 데이터를 정제하여 필요한 정보만 프론트엔드에 전달하는 예제를 살펴보자. NestJS를 사용하여 구현된 예제를 통해 백엔드에서 데이터를 어떻게 정제하는지 알아보겠다.</p>
<pre><code class="language-typescript">// 좋아요한 장소 모델
@Entity()
export class LikedPlace {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  userId: number;

  @Column()
  placeId: number;
}

// 서비스
@Injectable()
export class PlacesService {
  constructor(
    @InjectRepository(LikedPlace)
    private likedPlaceRepository: Repository&lt;LikedPlace&gt;,
  ) {}

  async getLikedPlaces(userId: number) {
    const likedPlaces = await this.likedPlaceRepository.find({ where: { userId } });

    const detailedPlaces = await Promise.all(
      likedPlaces.map(async (place) =&gt; {
        const apiResponse = await fetch(`https://api.example.com/places/${place.placeId}`);
        const placeDetails = await apiResponse.json();
        return {
          ...place,
          ...placeDetails,
        };
      })
    );

    return detailedPlaces;
  }
}

// 컨트롤러
@Controller(&#39;places&#39;)
export class PlacesController {
  constructor(private readonly placesService: PlacesService) {}

  @Get(&#39;liked&#39;)
  async getLikedPlaces(@Query(&#39;userId&#39;) userId: string) {
    return this.placesService.getLikedPlaces(parseInt(userId));
  }
}</code></pre>
<h4 id="2-프론트엔드에서-백엔드-api를-사용하는-방법">2. 프론트엔드에서 백엔드 API를 사용하는 방법</h4>
<p>React Query를 사용하여 백엔드에서 제공하는 API를 호출하고, 데이터를 가져오는 방법을 살펴보자.</p>
<pre><code class="language-javascript">// src/hooks/useLikedPlaces.js
import { useQuery } from &#39;react-query&#39;;

const fetchLikedPlaces = async (userId) =&gt; {
  const response = await fetch(`/places/liked?userId=${userId}`);
  if (!response.ok) {
    throw new Error(&#39;Network response was not ok&#39;);
  }
  return response.json();
};

export const useLikedPlaces = (userId) =&gt; {
  return useQuery([&#39;likedPlaces&#39;, userId], () =&gt; fetchLikedPlaces(userId));
};</code></pre>
<pre><code class="language-javascript">// src/components/LikedPlaces.js
import React from &#39;react&#39;;
import { useLikedPlaces } from &#39;../hooks/useLikedPlaces&#39;;

const LikedPlaces = ({ userId }) =&gt; {
  const { data, error, isLoading } = useLikedPlaces(userId);

  if (isLoading) return &lt;div&gt;Loading...&lt;/div&gt;;
  if (error) return &lt;div&gt;Error: {error.message}&lt;/div&gt;;

  return (
    &lt;div&gt;
      &lt;h1&gt;Liked Places&lt;/h1&gt;
      &lt;ul&gt;
        {data.map((place) =&gt; (
          &lt;li key={place.id}&gt;
            {place.name} - {place.description}
          &lt;/li&gt;
        ))}
      &lt;/ul&gt;
    &lt;/div&gt;
  );
};

export default LikedPlaces;</code></pre>
<h2 id="느낀점과-결론">느낀점과 결론</h2>
<p>백엔드에서 오픈 API 데이터를 정제하는 것은 보안, 성능, 일관성, 유지 보수성 측면에서 매우 중요하다. 이번 포스팅에서 소개한 방법을 통해 방대한 데이터를 효율적으로 관리하고, 프론트엔드에 필요한 정보를 효과적으로 전달할 수 있을 것이다. 이러한 접근 방식을 통해 더욱 효율적인 개발 환경을 구축해 보자.</p>
<p>참고 자료:</p>
<ul>
<li><a href="https://docs.nestjs.com/">NestJS 공식 문서</a></li>
<li><a href="https://react-query.tanstack.com/">React Query 공식 문서</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[커스텀 훅으로 데이터 필터링 및 차트 렌더링]]></title>
            <link>https://velog.io/@_mo__on/%EC%BB%A4%EC%8A%A4%ED%85%80-%ED%9B%85%EC%9C%BC%EB%A1%9C-%EB%8D%B0%EC%9D%B4%ED%84%B0-%ED%95%84%ED%84%B0%EB%A7%81-%EB%B0%8F-%EC%B0%A8%ED%8A%B8-%EB%A0%8C%EB%8D%94%EB%A7%81</link>
            <guid>https://velog.io/@_mo__on/%EC%BB%A4%EC%8A%A4%ED%85%80-%ED%9B%85%EC%9C%BC%EB%A1%9C-%EB%8D%B0%EC%9D%B4%ED%84%B0-%ED%95%84%ED%84%B0%EB%A7%81-%EB%B0%8F-%EC%B0%A8%ED%8A%B8-%EB%A0%8C%EB%8D%94%EB%A7%81</guid>
            <pubDate>Fri, 24 May 2024 09:39:42 GMT</pubDate>
            <description><![CDATA[<p>Custom hooks <code>useCodingListFilter</code>와 훅이 독립적으로 동작할 때 발생할 수 있는 문제를 해결하는 방법에 중점을 둔다.</p>
<h2 id="문제-설명">문제 설명</h2>
<p>우선, <code>useCodingList</code> 훅은 Tanstack Query를 사용해 데이터를 캐시해주는 역할을 하는 함수이다.
<code>useCodingListFilter</code> 훅과 <code>useCodingList</code> 훅이 독립적으로 동작하고 있기 때문에, <code>useCodingListFilter</code>에서 <code>useCodingList</code>를 직접 호출하면 <code>data</code>가 아직 로드되지 않은 상태에서 훅이 호출될 수 있다. 이를 해결하기 위해 <code>useCodingList</code>에서 반환된 <code>data</code>를 <code>useCodingListFilter</code>에 전달하는 방식으로 변경해야 한다.</p>
<h2 id="해결-방법">해결 방법</h2>
<h4 id="1-데이터-필터링-훅">1. 데이터 필터링 훅</h4>
<p>데이터를 필터링하고 그룹화하기 위한 훅을 작성한다. <code>useCodingListFilter</code> 훅은 외부에서 <code>data</code>를 받아 처리하도록 한다.</p>
<pre><code class="language-typescript">import { useMemo, useReducer } from &quot;react&quot;;
import { CodingListTableItem, DataFilterType } from &quot;./types&quot;;
import { codingListFilter, initialState } from &quot;./filterReducer&quot;;

interface UseCodingListFilterProps {
  data: CodingListTableItem[];
}

export default function useCodingListFilter({ data }: UseCodingListFilterProps) {
  const [filterState, dispatch] = useReducer(codingListFilter, initialState);

  const groupedData: DataFilterType = useMemo(() =&gt; {
    const grouped: DataFilterType = {};
    data.forEach((item) =&gt; {
      const category = item.category;
      if (!grouped[category]) {
        grouped[category] = [];
      }
      grouped[category].push(item);
    });
    return grouped;
  }, [data]);

  const filteredData: DataFilterType = useMemo(() =&gt; {
    const filtered: DataFilterType = {};
    Object.keys(groupedData).forEach((category) =&gt; {
      if (filterState.categories.length === 0 || filterState.categories.includes(category)) {
        filtered[category] = groupedData[category].filter(
          (item) =&gt; filterState.statuses.length === 0 || filterState.statuses.includes(item.progress)
        );
      }
    });
    return filtered;
  }, [groupedData, filterState]);

  return {
    groupedData,
    filteredData,
    filterState,
    dispatch,
  };
}</code></pre>
<h4 id="2-컴포넌트에서의-활용">2. 컴포넌트에서의 활용</h4>
<p><code>useCodingList</code> 훅에서 <code>data</code>를 받아 <code>useCodingListFilter</code> 훅으로 전달하여 데이터를 처리한다.</p>
<pre><code class="language-tsx">import React from &quot;react&quot;;
import { useCodingList } from &quot;./hooks&quot;;
import useCodingListFilter from &quot;./useCodingListFilter&quot;;
import BarChart from &quot;./BarChart&quot;;

const CodingList = () =&gt; {
  const { data, error, status } = useCodingList();

  if (status === &#39;loading&#39;) {
    return &lt;div&gt;Loading...&lt;/div&gt;;
  }

  if (status === &#39;error&#39; || !data) {
    console.error(error);
    return &lt;div className=&quot;error-data&quot;&gt;&lt;Error message=&quot;코딩리스트를 불러오지 못했습니다.&quot; /&gt;&lt;/div&gt;;
  }

  const { groupedData, filteredData, filterState, dispatch } = useCodingListFilter({ data });

  return (
    &lt;div className=&quot;guide-container&quot;&gt;
      {/* 기타 UI 컴포넌트 */}
      &lt;div className=&quot;statistics-area&quot;&gt;
        &lt;div className=&quot;wbs-chart&quot;&gt;
          &lt;BarChart data={wbsChartData} options={wbsChartOptions} /&gt;
        &lt;/div&gt;
      &lt;/div&gt;
      {/* 기타 UI 컴포넌트 */}
    &lt;/div&gt;
  );
};

export default CodingList;</code></pre>
<h2 id="결론">결론</h2>
<p><code>useCodingListFilter</code> 훅과 <code>useCodingList</code> 훅이 서로 독립적으로 동작할 때 발생할 수 있는 문제를 해결하기 위해, <code>useCodingList</code>에서 반환된 <code>data</code>를 <code>useCodingListFilter</code>에 전달하는 방식으로 변경했다. 이 방법을 통해 데이터가 로드된 후에만 필터링 작업이 수행되도록 보장할 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[NestJS] 서비스 이해하기 및 리팩토링]]></title>
            <link>https://velog.io/@_mo__on/NestJS-%EC%84%9C%EB%B9%84%EC%8A%A4-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0-%EB%B0%8F-%EB%A6%AC%ED%8C%A9%ED%86%A0%EB%A7%81</link>
            <guid>https://velog.io/@_mo__on/NestJS-%EC%84%9C%EB%B9%84%EC%8A%A4-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0-%EB%B0%8F-%EB%A6%AC%ED%8C%A9%ED%86%A0%EB%A7%81</guid>
            <pubDate>Sun, 19 May 2024 07:03:10 GMT</pubDate>
            <description><![CDATA[<h2 id="서론">서론</h2>
<p>NestJS를 사용하면서 서비스와 컨트롤러의 역할을 명확히 이해하고, 이를 통해 코드를 보다 효율적으로 작성할 수 있다. 이번 포스팅에서는 NestJS에서 서비스가 무엇인지, 컨트롤러와 서비스의 차이점을 알아보고, 컨트롤러에 작성된 코드를 서비스로 리팩토링하는 방법을 살펴본다. 마지막으로 이를 응용한 예제를 통해 NestJS의 강력한 기능을 경험해 보자.</p>
<h3 id="nestjs에서-서비스란">NestJS에서 서비스란?</h3>
<p>NestJS에서 서비스(Service)는 비즈니스 로직을 처리하는 핵심 부분을 담당한다. 주로 데이터베이스와의 상호작용, 외부 API 호출, 복잡한 계산 등을 처리한다. 컨트롤러는 요청을 받아서 적절한 서비스로 전달하고, 서비스는 해당 로직을 실행한 후 결과를 반환한다.</p>
<h3 id="컨트롤러와-서비스의-차이점">컨트롤러와 서비스의 차이점</h3>
<ul>
<li><strong>컨트롤러(Controller)</strong>: 클라이언트의 요청을 받아서 처리하는 역할을 한다. 주로 HTTP 요청을 수신하고, 요청을 서비스로 전달하며, 서비스에서 반환된 결과를 클라이언트에게 응답으로 보낸다.</li>
<li><strong>서비스(Service)</strong>: 비즈니스 로직을 처리하는 부분이다. 데이터베이스 작업, 외부 API 호출, 복잡한 계산 등을 수행한다. 컨트롤러와는 달리, 클라이언트의 요청/응답에 직접 관여하지 않는다.</li>
</ul>
<h3 id="컨트롤러에-작성된-코드를-서비스로-리팩토링">컨트롤러에 작성된 코드를 서비스로 리팩토링</h3>
<p>다음은 컨트롤러에 작성된 코드를 서비스로 리팩토링하는 예제이다.</p>
<h4 id="기존-컨트롤러-코드">기존 컨트롤러 코드</h4>
<pre><code class="language-typescript">import { Controller, Get, Param } from &#39;@nestjs/common&#39;;
import { User } from &#39;./user.entity&#39;;

@Controller(&#39;users&#39;)
export class UserController {
    @Get(&#39;:id&#39;)
    async getUser(@Param(&#39;id&#39;) id: string): Promise&lt;User&gt; {
        // 비즈니스 로직이 직접 컨트롤러에 작성됨
        const user = await User.findOne(id);
        if (!user) {
            throw new Error(&#39;User not found&#39;);
        }
        return user;
    }
}</code></pre>
<h4 id="리팩토링-후-코드">리팩토링 후 코드</h4>
<ol>
<li><strong>서비스 생성</strong></li>
</ol>
<pre><code class="language-typescript">import { Injectable } from &#39;@nestjs/common&#39;;
import { User } from &#39;./user.entity&#39;;

@Injectable()
export class UserService {
    async findUserById(id: string): Promise&lt;User&gt; {
        const user = await User.findOne(id);
        if (!user) {
            throw new Error(&#39;User not found&#39;);
        }
        return user;
    }
}</code></pre>
<ol start="2">
<li><strong>컨트롤러 수정</strong></li>
</ol>
<pre><code class="language-typescript">import { Controller, Get, Param } from &#39;@nestjs/common&#39;;
import { UserService } from &#39;./user.service&#39;;
import { User } from &#39;./user.entity&#39;;

@Controller(&#39;users&#39;)
export class UserController {
    constructor(private readonly userService: UserService) {}

    @Get(&#39;:id&#39;)
    async getUser(@Param(&#39;id&#39;) id: string): Promise&lt;User&gt; {
        return this.userService.findUserById(id);
    }
}</code></pre>
<h3 id="사용자-생성-기능-추가">사용자 생성 기능 추가</h3>
<p>서비스를 활용하여 사용자 생성 기능을 추가해보자.</p>
<h4 id="서비스-코드">서비스 코드</h4>
<pre><code class="language-typescript">@Injectable()
export class UserService {
    async createUser(name: string, age: number): Promise&lt;User&gt; {
        const user = new User();
        user.name = name;
        user.age = age;
        await user.save();
        return user;
    }
}</code></pre>
<h4 id="컨트롤러-코드">컨트롤러 코드</h4>
<pre><code class="language-typescript">import { Controller, Post, Body } from &#39;@nestjs/common&#39;;
import { UserService } from &#39;./user.service&#39;;
import { User } from &#39;./user.entity&#39;;

@Controller(&#39;users&#39;)
export class UserController {
    constructor(private readonly userService: UserService) {}

    @Post()
    async createUser(@Body() createUserDto: { name: string, age: number }): Promise&lt;User&gt; {
        return this.userService.createUser(createUserDto.name, createUserDto.age);
    }
}</code></pre>
<h3 id="데이터베이스-연동">데이터베이스 연동</h3>
<p>이번에는 데이터베이스와 연동하여 조금 더 고급 예제를 살펴보자. 여기서는 TypeORM을 사용하여 사용자 데이터를 MySQL 데이터베이스에 저장하고 조회하는 기능을 구현해보겠다.</p>
<h4 id="설정-파일-ormconfigjson">설정 파일 (ormconfig.json)</h4>
<p>먼저, TypeORM 설정 파일을 작성한다.</p>
<pre><code class="language-json">{
  &quot;type&quot;: &quot;mysql&quot;,
  &quot;host&quot;: &quot;localhost&quot;,
  &quot;port&quot;: 3306,
  &quot;username&quot;: &quot;test&quot;,
  &quot;password&quot;: &quot;test&quot;,
  &quot;database&quot;: &quot;test&quot;,
  &quot;entities&quot;: [&quot;dist/**/*.entity.js&quot;],
  &quot;synchronize&quot;: true
}</code></pre>
<h4 id="사용자-엔티티-userentityts">사용자 엔티티 (user.entity.ts)</h4>
<p>User 엔티티를 정의한다.</p>
<pre><code class="language-typescript">import { Entity, Column, PrimaryGeneratedColumn } from &#39;typeorm&#39;;

@Entity()
export class User {
    @PrimaryGeneratedColumn()
    id: number;

    @Column()
    name: string;

    @Column()
    age: number;
}</code></pre>
<h4 id="사용자-서비스-userservicets">사용자 서비스 (user.service.ts)</h4>
<p>사용자 서비스에 데이터베이스 연동 로직을 추가한다.</p>
<pre><code class="language-typescript">import { Injectable } from &#39;@nestjs/common&#39;;
import { InjectRepository } from &#39;@nestjs/typeorm&#39;;
import { Repository } from &#39;typeorm&#39;;
import { User } from &#39;./user.entity&#39;;

@Injectable()
export class UserService {
    constructor(
        @InjectRepository(User)
        private readonly userRepository: Repository&lt;User&gt;,
    ) {}

    async findUserById(id: string): Promise&lt;User&gt; {
        const user = await this.userRepository.findOne(id);
        if (!user) {
            throw new Error(&#39;User not found&#39;);
        }
        return user;
    }

    async createUser(name: string, age: number): Promise&lt;User&gt; {
        const user = new User();
        user.name = name;
        user.age = age;
        return this.userRepository.save(user);
    }
}</code></pre>
<h4 id="사용자-컨트롤러-usercontrollerts">사용자 컨트롤러 (user.controller.ts)</h4>
<p>컨트롤러는 서비스를 호출하여 요청을 처리한다.</p>
<pre><code class="language-typescript">import { Controller, Get, Param, Post, Body } from &#39;@nestjs/common&#39;;
import { UserService } from &#39;./user.service&#39;;
import { User } from &#39;./user.entity&#39;;

@Controller(&#39;users&#39;)
export class UserController {
    constructor(private readonly userService: UserService) {}

    @Get(&#39;:id&#39;)
    async getUser(@Param(&#39;id&#39;) id: string): Promise&lt;User&gt; {
        return this.userService.findUserById(id);
    }

    @Post()
    async createUser(@Body() createUserDto: { name: string, age: number }): Promise&lt;User&gt; {
        return this.userService.createUser(createUserDto.name, createUserDto.age);
    }
}</code></pre>
<h3 id="느낀-점">느낀 점</h3>
<p>NestJS에서 서비스와 컨트롤러를 분리하면 코드의 가독성과 유지보수성이 크게 향상된다. 컨트롤러는 단순히 요청을 받아 서비스로 전달하는 역할만 하기 때문에, 비즈니스 로직이 서비스에 집중되어 더 체계적으로 관리할 수 있다. 또한, 테스트 코드 작성 시에도 서비스 단위로 테스트할 수 있어 효율적이다.</p>
<h3 id="결론">결론</h3>
<p>이번 포스팅에서는 NestJS에서 서비스의 역할과 컨트롤러와의 차이점을 알아보았다. 또한, 컨트롤러에 작성된 코드를 서비스로 리팩토링하고, 이를 응용한 예제를 통해 서비스의 활용성을 살펴보았다. 마지막으로 데이터베이스 연동을 통해 고급 예제를 다뤄보았다. 서비스와 컨트롤러를 적절히 분리하여 NestJS의 강력한 기능을 최대한 활용해보자.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[NestJS] Custom decorator 구현하기]]></title>
            <link>https://velog.io/@_mo__on/NestJS-Custom-decorator-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@_mo__on/NestJS-Custom-decorator-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0</guid>
            <pubDate>Sun, 19 May 2024 06:51:11 GMT</pubDate>
            <description><![CDATA[<h3 id="nestjs-custom-decorator-구현하기">[NestJS] Custom Decorator 구현하기</h3>
<p>NestJS를 사용하면서 데코레이터를 활용해 코드의 재사용성을 높이고 로직을 깔끔하게 정리할 수 있다. 이번 포스팅에서는 Custom Decorator를 구현하는 방법을 알아본다. 특히, 데코레이터에 path를 추가해 효율적으로 엔드포인트를 관리하는 방법을 중점적으로 다룬다.</p>
<h3 id="custom-decorator-기본-구현">Custom Decorator 기본 구현</h3>
<p>먼저, Custom Decorator를 구현하는 기본 예제를 살펴본다. 이 데코레이터는 함수가 호출될 때 인자들을 로그로 출력해주는 역할을 한다.</p>
<pre><code class="language-tsx">function log(target, name, descriptor) {
    const original = descriptor.value;
    if (typeof original === &#39;function&#39;) {
        descriptor.value = function(...args) {
            console.log(`Arguments for ${name}: ${args}`);
            return original.apply(this, args);
        }
    }
    return descriptor;
}

class Person {
    @log
    say(message) {
        console.log(message);
    }
}</code></pre>
<p>위 코드를 실행하면 <code>say</code> 메서드가 호출될 때마다 인자들이 로그에 출력된다. 이를 통해 함수 호출을 디버깅하기가 쉬워진다.</p>
<h3 id="controller에-path-추가하기">Controller에 Path 추가하기</h3>
<p>Custom Decorator를 활용해 컨트롤러에 path를 추가할 수 있다. 예를 들어, <code>@controller(&#39;post&#39;)</code>를 사용하면 엔드포인트를 <code>localhost:4000/post</code>에 매핑할 수 있다. 하위 <code>@Get</code> 데코레이터에 path를 추가하여 더 세부적인 엔드포인트를 지정할 수 있다.</p>
<pre><code class="language-tsx">@controller(&#39;post&#39;)
class PostController {
    @get(&#39;detail&#39;)
    getPostDetail() {
        // 엔드포인트: localhost:4000/post/detail
    }
}</code></pre>
<p>주의할 점은 <code>@controller(&#39;post&#39;)</code>와 <code>@get(&#39;post&#39;)</code>를 동시에 사용하면 엔드포인트가 <code>localhost:4000/post/post</code>와 같이 중첩된다는 것이다. 따라서 접두어가 붙는다는 것을 유의해야 한다.</p>
<h3 id="추가-예제-역할-기반-접근-제어-데코레이터">추가 예제: 역할 기반 접근 제어 데코레이터</h3>
<p>다음은 역할 기반으로 접근을 제어하는 Custom Decorator의 예제이다. 이 데코레이터는 특정 역할을 가진 사용자만 함수에 접근할 수 있도록 제한한다.</p>
<pre><code class="language-tsx">function role(roleName) {
    return function(target, name, descriptor) {
        const original = descriptor.value;
        if (typeof original === &#39;function&#39;) {
            descriptor.value = function(...args) {
                const userRole = this.user.role;
                if (userRole !== roleName) {
                    throw new Error(&#39;Unauthorized&#39;);
                }
                return original.apply(this, args);
            }
        }
        return descriptor;
    }
}

class AdminController {
    user = { role: &#39;admin&#39; }; // 예시로 user 객체를 정의

    @role(&#39;admin&#39;)
    deletePost() {
        console.log(&#39;Post deleted&#39;);
    }
}</code></pre>
<p>이 예제에서는 <code>@role(&#39;admin&#39;)</code> 데코레이터를 사용해 <code>deletePost</code> 메서드가 관리자 역할을 가진 사용자만 호출할 수 있도록 제한한다. 다른 역할의 사용자가 호출하면 &#39;Unauthorized&#39; 에러가 발생한다.</p>
<h3 id="느낀-점">느낀 점</h3>
<p>Custom Decorator를 사용하면 코드의 가독성과 재사용성이 높아진다. 특히, 복잡한 로직을 깔끔하게 정리하고 반복적인 코드를 줄일 수 있어서 매우 유용하다. 이번에 소개한 예제들을 통해 Custom Decorator를 직접 구현해보면서 그 효용성을 체감할 수 있었다.</p>
<h3 id="결론">결론</h3>
<p>NestJS에서 Custom Decorator를 구현하는 방법과 그 활용법에 대해 알아보았다. 기본적인 데코레이터 구현부터 Controller에 path를 추가하는 방법, 그리고 역할 기반 접근 제어 예제까지 살펴보았다. Custom Decorator는 코드의 구조를 개선하고 유지보수성을 높이는 강력한 도구이다. 다양한 상황에 맞춰 Custom Decorator를 활용해 보자.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[타입스크립트] 함수 표현식의 힘: 코드 재사용성 극대화하기]]></title>
            <link>https://velog.io/@_mo__on/%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%ED%95%A8%EC%88%98-%ED%91%9C%ED%98%84%EC%8B%9D%EC%9D%98-%ED%9E%98-%EC%BD%94%EB%93%9C-%EC%9E%AC%EC%82%AC%EC%9A%A9%EC%84%B1-%EA%B7%B9%EB%8C%80%ED%99%94%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@_mo__on/%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%ED%95%A8%EC%88%98-%ED%91%9C%ED%98%84%EC%8B%9D%EC%9D%98-%ED%9E%98-%EC%BD%94%EB%93%9C-%EC%9E%AC%EC%82%AC%EC%9A%A9%EC%84%B1-%EA%B7%B9%EB%8C%80%ED%99%94%ED%95%98%EA%B8%B0</guid>
            <pubDate>Fri, 03 May 2024 02:35:14 GMT</pubDate>
            <description><![CDATA[<p>먼저, 자바스크립트와 타입스크립트에서 함수를 정의하는 두 가지 주요 방법이 있어요.
바로 <strong>함수 선언문</strong>과 *<em>함수 표현식 *</em>입니다.</p>
<h2 id="먼저-살펴보기">먼저 살펴보기</h2>
<h3 id="함수-선언문function-declarations">함수 선언문(Function Declarations)</h3>
<p>함수 선언문은 함수 이름과 함께 정의되며, 가장 전통적인 방법으로 함수를 선언하는 방식입니다. 
이 방식의 특징은 <strong>호이스팅으로</strong> 인해 함수가 정의된 위치와 상관없이 스크립트 내 어디서든 호출할 수 있다는 점입니다.</p>
<p>예제:</p>
<pre><code class="language-typescript">function addNumbers(a: number, b: number): number {
    return a + b;
}
console.log(addNumbers(5, 3)); // 결과는 8</code></pre>
<h3 id="함수-표현식function-expressions">함수 표현식(Function Expressions)</h3>
<p>함수 표현식은 변수에 함수를 할당하는 방식으로, 함수는 익명일 수도 있고 명명될 수도 있어요. 
함수 표현식은 코드가 해당 함수에 도달할 때까지 평가되지 않기 때문에, 선언 전에 사용하려고 하면 오류가 발생합니다.</p>
<p>예제:</p>
<pre><code class="language-typescript">const multiplyNumbers = function (a: number, b: number): number {
    return a * b;
}
console.log(multiplyNumbers(4, 2)); // 결과는 8</code></pre>
<p>또는 화살표 함수를 사용한 더 간결한 함수 표현식:</p>
<pre><code class="language-typescript">const subtractNumbers = (a: number, b: number): number =&gt; a - b;
console.log(subtractNumbers(10, 6)); // 결과는 4</code></pre>
<p>이제 위에서 설명한 함수 표현식에 더 집중하여 살펴보겠습니다.</p>
<h2 id="왜-코드-재사용성이-늘어나지">왜 코드 재사용성이 늘어나지?</h2>
<pre><code class="language-typescript">const calculateVolume = function(width: number, height: number, depth: number): number {
    return width * height * depth;
}
const calculateWeight = (mass: number, gravity: number = 9.81): number =&gt; {
    return mass * gravity;
}</code></pre>
<p>여기서 <code>calculateVolume</code>과 <code>calculateWeight</code>는 함수 표현식으로 정의된 친구들이죠. TypeScript가 왜 함수 표현식을 더 선호할까요? 답은 심플합니다. 한 번 정의해두면, 그 타입을 다른 곳에서도 편하게 재활용할 수 있기 때문이에요.</p>
<p>예를 들어보겠습니다.</p>
<pre><code class="language-typescript">type OperationFn = (a: number, b: number) =&gt; number;
const add: OperationFn = (a, b) =&gt; a + b;
const subtract: OperationFn = (a, b) =&gt; a - b;</code></pre>
<p>이렇게 하면, 더하기와 빼기 기능을 하는 함수들을 같은 타입으로 관리할 수 있어서, 코드가 깔끔해지고 관리하기도 쉬워져요.</p>
<p>그리고 복잡한 로직을 포함하는 함수에도 함수 표현식이 진짜 유용한데요, 예를 들어 HTTP 요청을 처리하는 경우를 생각해봅시다.</p>
<pre><code class="language-typescript">const handleRequest: typeof fetch = async (url, options) =&gt; {
    const response = await fetch(url, options);
    if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`);
    }
    return response.json();
}</code></pre>
<p>여기서 <code>fetch</code> 함수의 타입을 활용해서 <code>handleRequest</code> 함수를 정의했죠. 
이렇게 하면 코드도 짧아지고, 무엇보다 오류를 일으킬 만한 부분을 대폭 줄일 수 있어요. 
간단하고 명확하게 처리할 수 있으니 나이스 😆 <del>~</del></p>
<h2 id="결론">결론</h2>
<p>함수 표현식은 변수에 함수를 할당함으로써 정의되고, 화살표 함수를 통해 더 간결하게 표현할 수 있어요. 
이러한 접근 방식은 특히 <strong>모듈화된 코드나 컴포넌트 기반 개발</strong>에서 <strong>함수의 재사용과 관리를 간편하게 해주며, 코드 전체의 안정성을 향상</strong>시킵니다. 
따라서 TypeScript를 사용하는 프로젝트에서는 가능한 함수 표현식을 활용하여 보다 체계적이고 효율적인 코드 작성을 지향해야 합니다.</p>
<p><strong>참고 자료)</strong>
이펙티브 타입스크립트
<a href="https://m.yes24.com/Goods/Detail/102124327">https://m.yes24.com/Goods/Detail/102124327</a>
타입스크립트 핸드북
<a href="https://www.typescriptlang.org/docs/handbook/2/functions.html">https://www.typescriptlang.org/docs/handbook/2/functions.html</a>
DevCamp
<a href="https://devcamp.com/trails/introduction-typescript/campsites/typescript-closures/guides/function-declarations-vs-expressions">https://devcamp.com/trails/introduction-typescript/campsites/typescript-closures/guides/function-declarations-vs-expressions</a>
Inpa Dev(tistory)
<a href="https://inpa.tistory.com/entry/TS-%F0%9F%93%98-%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8%EC%97%90%EC%84%9C-%ED%95%A8%EC%88%98-%EB%AC%B8%EB%B2%95-%EB%8B%A4%EB%A3%A8%EA%B8%B0-%F0%9F%92%AF-%EC%B4%9D%EC%A0%95%EB%A6%AC">https://inpa.tistory.com/entry/TS-%F0%9F%93%98-%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8%EC%97%90%EC%84%9C-%ED%95%A8%EC%88%98-%EB%AC%B8%EB%B2%95-%EB%8B%A4%EB%A3%A8%EA%B8%B0-%F0%9F%92%AF-%EC%B4%9D%EC%A0%95%EB%A6%AC</a>
김서현님(velog)
<a href="https://velog.io/@seobbang/TypeScript-%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8%EC%9D%98-%ED%83%80%EC%9E%85-%EC%8B%9C%EC%8A%A4%ED%85%9C-3">https://velog.io/@seobbang/TypeScript-%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8%EC%9D%98-%ED%83%80%EC%9E%85-%EC%8B%9C%EC%8A%A4%ED%85%9C-3</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[사이드프로젝트] NestJs로 구축하는 빵지순례(1)]]></title>
            <link>https://velog.io/@_mo__on/NestJs-NestJs%EB%A1%9C-%EA%B5%AC%EC%B6%95%ED%95%98%EB%8A%94-%EB%B9%B5%EC%A7%80%EC%88%9C%EB%A1%800</link>
            <guid>https://velog.io/@_mo__on/NestJs-NestJs%EB%A1%9C-%EA%B5%AC%EC%B6%95%ED%95%98%EB%8A%94-%EB%B9%B5%EC%A7%80%EC%88%9C%EB%A1%800</guid>
            <pubDate>Fri, 26 Apr 2024 16:35:12 GMT</pubDate>
            <description><![CDATA[<p>2025년에 다시 봅시다..</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[곧 다가올 Chrome 120 Devtools 요약본]]></title>
            <link>https://velog.io/@_mo__on/%EA%B3%A7-%EB%8B%A4%EA%B0%80%EC%98%AC-Chrome-120-Devtools-%EC%9A%94%EC%95%BD%EB%B3%B8</link>
            <guid>https://velog.io/@_mo__on/%EA%B3%A7-%EB%8B%A4%EA%B0%80%EC%98%AC-Chrome-120-Devtools-%EC%9A%94%EC%95%BD%EB%B3%B8</guid>
            <pubDate>Sun, 26 Nov 2023 13:39:11 GMT</pubDate>
            <description><![CDATA[<p>안녕하세요. 조경문 입니다.
블로그에 업로드 되는 첫 글이 되었네요.</p>
<p>현재 구글은 Chrome 118의 릴리스를 통해 탭 그룹의 혁신적인 변화를 소개하였으며, 
이어서 Chrome 119가 초기 안정 버전으로 출시되었습니다. </p>
<p>크롬 디벨로퍼를 읽던 도중, 다가오는 120 버전 업데이트에서 흥미있는 내용들이 있어 
소개해드리고자 공유드리게 되었습니다.
우선, 전체적인 카테고리를 한 문장으로 축약하자면 다음과 같습니다.</p>
<h2 id="1-전체적인-chrome-120-예정-업데이트-항목">1. 전체적인 Chrome 120 예정 업데이트 항목</h2>
<ul>
<li>Third-party cookie phaseout: 제3자 쿠키 단계적 폐지에 관한 내용입니다.</li>
<li>Privacy Sandbox Analysis Tool: 웹사이트의 쿠키 분석을 위한 도구 소개입니다.</li>
<li>Enhanced ignore listing: 더 향상된 무시 목록 기능에 대한 설명입니다.</li>
<li>New input mode toggle during remote debugging: 원격 디버깅 시 새로운 입력 모드 전환 기능입니다.</li>
<li>Elements panel updates: &#39;Elements&#39; 패널에서 &#39;#document&#39; 노드의 URL 표시와 같은 업데이트 사항입니다.</li>
<li>Effective Content Security Policy in the Application panel: &#39;Application&#39; 패널에서 효과적인 콘텐츠 보안 정책을 볼 수 있는 기능입니다.</li>
<li>Improved animation debugging: 애니메이션 디버깅 기능 개선에 관한 설명입니다.</li>
<li>&#39;Do you trust this code?&#39; dialog in Sources and self-XSS warning in Console: 소스 패널에서 나타나는 &#39;이 코드를 신뢰하십니까?&#39; 대화 상자 및 콘솔에서의 자가 XSS 경고에 대한 내용입니다.</li>
<li>Event listener breakpoints in web workers and worklets: 웹 워커와 워클릿에서의 이벤트 리스너 중단점에 대한 설명입니다.</li>
<li>New media badge for <audio> and <video>: &#39;Elements&#39; 패널에서 <audio> 및 <video> 태그에 대한 새로운 미디어 배지 기능입니다.</li>
<li>Preloading renamed to Speculative loading: &#39;Preloading&#39;이 &#39;Speculative loading&#39;으로 이름이 변경되었습니다.</li>
<li>Lighthouse 11.2.0: Lighthouse 패널이 Lighthouse 11.2.0으로 업데이트되었습니다.
Accessibility improvements: 접근성 개선 사항에 대한 설명입니다.</li>
</ul>
<p>이어서 소개드릴 항목들은 제가 조금 더 관심있는 항목들을 추려서 조금 더 심도있게 알아보았습니다.</p>
<h2 id="2-효율적인-디버깅과-개선된-사용자-경험for-ux-group">2. 효율적인 디버깅과 개선된 사용자 경험(for UX Group)</h2>
<h3 id="1-node_modules에-대한-기본-제외-패턴">1. node_modules에 대한 기본 제외 패턴:</h3>
<p>  DevTools에는 이제 디버거 설정에서 사용자 정의 제외 규칙으로 기본 정규 표현식(!!!)이 포함됩니다. 이는 node_modules 및 bower_components의 스크립트를 기본적으로 건너뛰어 코드에만 집중할 수 있게 도와줍니다. 이 기능은 설정에서 언제든지 비활성화하거나 사용자 정의할 수 있습니다​​.(on off 가능!)
  <img src="https://velog.velcdn.com/images/_mo__on/post/76dbe8cd-2ff0-4f55-b816-897fd37639e6/image.png" alt=""></p>
<h3 id="2elements-패널에서-document-노드의-url-표시">2.Elements 패널에서 #document 노드의 URL 표시:</h3>
<p>  iframe 디버깅을 용이하게 하기 위해, Elements 패널은 이제 #document 노드 옆에 documentURL을 표시합니다. 이 업데이트는 디버깅 목적으로 iframe 소스에 더 쉽게 접근할 수 있겠네요.
<img src="https://velog.velcdn.com/images/_mo__on/post/174aad4d-2224-4786-a18c-835ecfdbb58d/image.png" alt=""></p>
<h3 id="3개선된-애니메이션-디버깅">3.개선된 애니메이션 디버깅:</h3>
<p>이제 타임라인 헤더 어디에서나 클릭하여 재생 헤드를 설정할 수 있으며, 애니메이션은 이전 상태에 따라 계속 재생되거나 멈출 수 있습니다. 이미지를 보니까 트랜지션이 진행되는 동안 스냅샷으로 프레임 단위로 쪼개볼 수 있네요. 디테일 &amp; 프로트타입 작업에 도움될듯 합니다. 그리고 애니메이션 이름 열을 조정하여 전체 애니메이션 이름을 한 눈에 볼 수 있게 되네요 :)
  <img src="https://velog.velcdn.com/images/_mo__on/post/924e0154-7961-411b-ad19-53d69db0d550/image.png" alt=""></p>
<h3 id="4웹-워커-및-워클릿에서의-이벤트-리스너-중단점">4.웹 워커 및 워클릿에서의 이벤트 리스너 중단점:</h3>
<p>Sources 패널에서 이벤트 중단점을 설정할 때, 디버가가 이제는 해당 이벤트가 웹 워커나 워클릿에서 발생할 때도 일시 중지합니다. 이 기능은 웹 워커(web worker) 및 워클릿(worklet)을 사용하는 복잡한 디버깅 능력을 확장하여 더 철저한 분석을 할 수 있을거 같습니다.
<img src="https://velog.velcdn.com/images/_mo__on/post/0c577374-9c70-4390-a5b0-5087af9533ca/image.png" alt=""></p>
<p><strong>TMI</strong>
<strong>웹 워커(Web Worker)</strong>: 웹 워커는 메인 스레드와 별도로 백그라운드에서 실행되는 스크립트로, 메인 페이지의 성능에 영향을 미치지 않으면서 복잡한 계산이나 데이터 처리를 수행할 수 있게 해줍니다. 이를 통해 웹 애플리케이션의 반응성과 성능을 향상시킬 수 있습니다.</p>
<p><strong>워클릿(Worklet)</strong>: 워클릿은 CSS, 오디오 처리, 레이아웃 등과 같은 특정 작업에 맞춰 최적화된 경량 스크립트 실행 환경입니다. 이는 브라우저의 렌더링 파이프라인에 더 가까운 수준에서 작동하며, 특정 작업을 더 효율적으로 처리할 수 있도록 도와줍니다.</p>
<h2 id="요약하자면">요약하자면?</h2>
<p> /node_modules/ 및 /bower_components/ 스크립트의 자동 건너뛰기, #document 노드의 URL 표시(iframe 디버깅!), 애니메이션 탭의 개선, 그리고 웹 워커와 워클릿에서 이벤트 리스너 중단점을 통해 개발자들의 디버깅 작업을 더 효율적이게 만들어주는 업데이트가 되겠네요.</p>
<p>구체적인 설명과 더 자세한 정보들은 아래 링크에서 추가적으로 확인하실 수 있습니다.
이 글 외에도 흥미있고 재미있는 포스트들이 많으니, 시간 되실 때 한번씩 보시는걸 추천드려요 :)
글 읽어주셔서 감사하며, 편안한 밤 되시길 바라겠습니다.</p>
<p>감사합니다.</p>
<p>참고 자료)</p>
<p>What&#39;s new in DevTools (Chrome 120) - Chrome for Developers
Third-party cookie phaseout issues reported in the Issues panel, Privacy Sandbox Analysis Tool for cookies, effective Content Security Policy in the Applications panel, improved animations debugging, enhanced ignore listing, and more.</p>
<p>  <a href="https://developer.chrome.com/blog/new-in-devtools-120/">https://developer.chrome.com/blog/new-in-devtools-120/</a> </p>
<blockquote>
<p>기술에 매몰되면 안되지만, 우리는 기술에 민감해야져야만 한다.</p>
</blockquote>
]]></description>
        </item>
    </channel>
</rss>