<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>soo's</title>
        <link>https://velog.io/</link>
        <description></description>
        <lastBuildDate>Tue, 25 Mar 2025 06:13:48 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>soo's</title>
            <url>https://velog.velcdn.com/images/doh_0112/profile/c19e83e3-f187-4ca1-9072-1d0b67c7e8da/image.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. soo's. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/doh_0112" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[Next.js의 SSRF]]></title>
            <link>https://velog.io/@doh_0112/Next.js%EC%9D%98-SSRF</link>
            <guid>https://velog.io/@doh_0112/Next.js%EC%9D%98-SSRF</guid>
            <pubDate>Tue, 25 Mar 2025 06:13:48 GMT</pubDate>
            <description><![CDATA[<h1 id="🛡️-ssrfserver-side-request-forgery란-무엇인가">🛡️ SSRF(Server-Side Request Forgery)란 무엇인가?</h1>
<p>SSRF는 서버 사이드 요청 위조라고 불리며, 외부에서 악의적인 사용자가 서버에게 의도하지 않은 요청을 하도록 속이는 공격입니다.</p>
<p>보통 서버는 외부 API나 내부 서비스로 요청을 보내는 기능을 갖고 있는데, 이 요청의 목적지 URL이 사용자로부터 입력될 수 있다면, 그 입력값을 조작해서 서버가 공격자가 원하는 위치로 요청을 보내게 만들 수 있습니다.</p>
<p>예시로 보는 SSRF
예를 들어 어떤 서비스가 사용자가 입력한 이미지 URL을 기반으로 이미지를 서버에서 다운로드해 보여준다고 할 때:</p>
<pre><code>{
  &quot;imageUrl&quot;: &quot;http://example.com/image.jpg&quot;
}</code></pre><p>그런데 공격자가 여기에 다음과 같은 URL을 넣는다면?</p>
<pre><code>http://localhost:8000/admin</code></pre><p>서버는 외부가 직접 접근할 수 없는 내부 서비스에 접속하게 되고, 민감한 정보를 공격자에게 노출시킬 수도 있습니다.</p>
<h1 id="🖼️-nextjs-이미지-컴포넌트에서-왜-ssrf가-발생했을까">🖼️ Next.js 이미지 컴포넌트에서 왜 SSRF가 발생했을까?</h1>
<p>Next.js의 next/image 컴포넌트는 이미지 최적화를 위해 이미지를 서버에서 다운로드한 후 리사이징하여 클라이언트에 전달합니다. 이 과정에서 next.config.js 파일의 images.remotePatterns 또는 images.domains 옵션을 통해 허용된 외부 이미지 주소를 등록할 수 있죠.</p>
<p>문제의 원인
이 SSRF 취약점은 이미지 최적화 서버가 외부에서 입력된 URL을 충분히 검증하지 않고 직접 요청을 보냈기 때문에 발생했습니다.</p>
<p>공격자가 이미지 URL로 내부 주소(예: <a href="http://localhost">http://localhost</a>, <a href="http://metadata.google.internal">http://metadata.google.internal</a>, <a href="http://127.0.0.1:5000">http://127.0.0.1:5000</a>) 등을 입력할 경우, Next.js 서버가 해당 URL로 요청을 보내면서 SSRF가 가능해졌던 것입니다.</p>
<p>정리하자면:
next/image는 성능을 위해 서버에서 이미지를 가져옴</p>
<p>이때 URL 검증이 충분하지 않으면 SSRF가 발생할 수 있음</p>
<p>공격자는 내부 네트워크로의 요청을 유도해 민감한 데이터에 접근할 수 있음</p>
<h1 id="✅-어떻게-해결되었을까">✅ 어떻게 해결되었을까?</h1>
<p>Next.js 팀은 다음과 같은 방식으로 취약점을 수정했습니다:</p>
<ul>
<li><p>이미지 URL에 대해 호스트 검증 로직을 강화함</p>
</li>
<li><p>내부 IP 주소나 로컬 주소(localhost, 127.0.0.1 등)로의 요청을 차단함</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[flutter legacy architecture 개선]]></title>
            <link>https://velog.io/@doh_0112/flutter-structure</link>
            <guid>https://velog.io/@doh_0112/flutter-structure</guid>
            <pubDate>Thu, 02 Jan 2025 06:56:36 GMT</pubDate>
            <description><![CDATA[<p>프로젝트 규모는 중소
현재 트리</p>
<h2 id="as-is">as-is</h2>
<pre><code>📦lib
 ┣ 📂api
 ┃ ┣ 📂dto
 ┃ ┗ 📂services
 ┣ 📂auth
 ┃ ┣ 📂services
 ┃ ┣ 📂view
 ┃ ┃ ┣ 📂widgets
 ┃ ┗ 📂view_model
 ┣ 📂common
 ┣ 📂community
 ┣ 📂content
 ┣ 📂datasource
 ┃ ┣ 📂remote
 ┃ ┗ 📜dio_client.dart
 ┣ 📂enums
 ┣ 📂insight
 ┣ 📂models
 ┣ 📂mypage
 ┣ 📂onboarding
 ┣ 📂portfolio
 ┣ 📂profile_setup
 ┣ 📂thoughts
 ┣ 📜main.dart
 ┣ 📜root_page.dart
 ┗ 📜splash_screen.dart</code></pre><p>이 구조에서
<strong>datasource와 services의 역할이 혼재</strong>되어잇음
datasource는 데이터 소스(API, 로컬 DB) 호출 담당, services는 비즈니스 로직을 포함한 API 추상화 계층으로 분리해서 사용해야함</p>
<ul>
<li><p>auth 폴더
api/에서 이미 인증 관련 서비스(auth_datasource.dart, auth_service.dart)를 관리하고 있으므로 중복 코드가 발생하지 않도록 조정해야함
인증 로직은 api/에서 처리하고, auth/에는 UI와 상태 관리만 포함하도록 해야함</p>
</li>
<li><p>models 폴더
현재 models 내부에 너무 많은 도메인 모델이 평면적으로 나열되어 있음
도메인별 하위 폴더로 구분</p>
</li>
</ul>
<h2 id="to-be">to-be</h2>
<pre><code>📦lib/
├── api/                # API 관련 코드
│   ├── datasource/     # 원격 및 로컬 데이터 소스 (기존 datasource)
│   │   ├── remote/
│   ├── dto/            # dto
│   └── services/       # API 호출 추상화 계층
├── auth/               # 인증 관련 모든 코드
│   ├── services/
│   ├── view/
│   ├── view_model/
│   └── widgets/
├── common/             # 공통 코드
│   ├── constants/
│   ├── helper/         # 앱 전체 헬퍼 함수
│   ├── utils/          # 공통 유틸리티 함수
│   ├── view_model/     # 공통 뷰모델
│   └── widgets/        # 공통 위젯
├── community/          # 커뮤니티 관련 코드
│   ├── view/
│   ├── view_model/
│   └── widgets/
├── content/            # 콘텐츠 관련 코드
│   ├── view/
│   ├── view_model/
│   └── widgets/
├── enums/              # Enum 관련 코드
├── insight/            # 인사이트 관련 코드
│   ├── view/
│   ├── view_model/
│   └── widgets/
├── models/             # 도메인 모델
├── mypage/             # 마이페이지 관련 코드
│   ├── view/
│   ├── view_model/
│   └── widgets/
├── onboarding/         # 온보딩 관련 코드
│   ├── view/
│   ├── view_model/
│   └── widgets/
├── profile_setup/      # 프로필 설정 관련 코드
│   ├── view/
│   ├── view_model/
│   └── widgets/
├── thoughts/           # 생각 관련 코드
│   ├── view/
│   ├── view_model/
│   ├── widgets/
│   └── func/           # 특정 기능별 유틸리티 (필요시 유지)
├── portfolio/          # 포트폴리오 관련 코드
│   ├── view/
│   ├── view_model/
│   ├── widgets/
│   └── models/         # 포트폴리오와 관련된 도메인 모델
├── splash/             # 스플래시 스크린
├── main.dart           # 앱의 엔트리 포인트
├── root_page.dart      # 루트 페이지

</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[build error with gradle version]]></title>
            <link>https://velog.io/@doh_0112/android-studio-build-fail-with-gradle</link>
            <guid>https://velog.io/@doh_0112/android-studio-build-fail-with-gradle</guid>
            <pubDate>Tue, 17 Dec 2024 04:52:47 GMT</pubDate>
            <description><![CDATA[<h2 id="문제-상황">문제 상황</h2>
<p>내 로컬 안드스가 사용하는 jdk가 프로젝트 gradle과 호환되지 않아서 문제가 생겼음</p>
<pre><code class="language-js">FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task &#39;:gradle:compileGroovy&#39;.
&gt; BUG! exception in phase &#39;semantic analysis&#39; in source unit &#39;/Users/lms/development/flutter/packages/flutter_tools/gradle/src/main/groovy/app_plugin_loader.groovy&#39; Unsupported class file major version 65

* Try:
&gt; Run with --stacktrace option to get the stack trace.
&gt; Run with --info or --debug option to get more log output.
&gt; Run with --scan to get full insights.

* Get more help at https://help.gradle.org

BUILD FAILED in 4s
Running Gradle task &#39;assembleDebug&#39;...                              5.1s

┌─ Flutter Fix ────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ [!] Your project&#39;s Gradle version is incompatible with the Java version that Flutter is using for Gradle.        │
│                                                                                                                  │
│ If you recently upgraded Android Studio, consult the migration guide at                                          │
│ https://flutter.dev/to/to/java-gradle-incompatibility.                                                           │
│                                                                                                                  │
│ Otherwise, to fix this issue, first, check the Java version used by Flutter by running `flutter doctor           │
│ --verbose`.                                                                                                      │
│                                                                                                                  │
│ Then, update the Gradle version specified in                                                                     │
│ /Users/lms/daggle/wisdom-hub/wisdomHubApp/android/gradle/wrapper/gradle-wrapper.properties to be compatible with │
│ that Java version. See the link below for more information on compatible Java/Gradle versions:                   │
│ https://docs.gradle.org/current/userguide/compatibility.html#java                                                │
│                                                                                                                  │
│                                                                                                                  │
└─────────────────────────────────────────────────────────────────────────────</code></pre>
<p>현재 프로젝트의 Gradle version과 flutter java version이 안 맞다고함
gradle version은 7.6.3
android studio jdk는 21
호환 안됨</p>
<h2 id="시도-방법">시도 방법</h2>
<h3 id="1-jdk-8-버전-설치">1. jdk 8 버전 설치</h3>
<p>gradle과 호환되는 8버전을 설치해서 사용해봤는데
전역에서 <code>java -version</code> 하면 설정이 잘 된걸 확인할 수 있는데
vscode에서 flutter를 안드스로 launch emulator 하고 <code>flutter doctor --verbose</code> 해보면 21 버전임
몇 번 jdk 삭제/안드스 리셋 등 해보다가 안되서 이 방법 포기</p>
<h3 id="2-jdk-17-버전-사용">2. jdk 17 버전 사용</h3>
<p>이번엔 17버전을 안드스 Gradle setting에서 default로 넣어줌
<code>flutter config --jdk-dir $JAVA_HOME</code> 하고
<code>flutter doctor --verbose</code> 해보니 17로 잘 뜸..!!
하지만 현재 플젝 gradle/AGP와 호환되지 않음
compileSdkVersion이 35라 gradle/AGP을 올리기로 결정함</p>
<h4 id="2-1-gradle-80-버전-업">2-1. gradle 8.0 버전 업</h4>
<p>gradle 버전을 8.0으로 올림</p>
<pre><code class="language-properties">// gradle-wrapper.properties
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-all.zip</code></pre>
<h4 id="2-2-agp-801-버전-업">2-2. AGP 8.0.1 버전 업</h4>
<p>AGP도 8.0.1로 올림
buildscript도 추가해줌</p>
<pre><code class="language-properties">// gradle-wrapper.properties
...
plugins {
    id &quot;dev.flutter.flutter-plugin-loader&quot; version &quot;1.0.0&quot;
    id &quot;com.android.application&quot; version &quot;8.1.0&quot; apply false
    id &quot;org.jetbrains.kotlin.android&quot; version &quot;1.7.10&quot; apply false
}</code></pre>
<pre><code class="language-properties">// android/build.gradle
buildscript {
    repositories {
        google()
        mavenCentral()
    }
    dependencies {
        classpath &quot;com.android.tools.build:gradle:8.1.0&quot;
    }
}
</code></pre>
<h2 id="문제해결">문제해결</h2>
<p><img src="https://velog.velcdn.com/images/doh_0112/post/33da91c2-d512-41b4-91fb-5412a685e241/image.png" alt=""></p>
<p>빌드 성공...ㅠ
하루 걸렸다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[INP 최적화와 데이터 전달 방식]]></title>
            <link>https://velog.io/@doh_0112/INP%EC%B5%9C%EC%A0%81%ED%99%94%EC%99%80%EB%8D%B0%EC%9D%B4%ED%84%B0%EC%A0%84%EB%8B%AC%EB%B0%A9%EC%8B%9D</link>
            <guid>https://velog.io/@doh_0112/INP%EC%B5%9C%EC%A0%81%ED%99%94%EC%99%80%EB%8D%B0%EC%9D%B4%ED%84%B0%EC%A0%84%EB%8B%AC%EB%B0%A9%EC%8B%9D</guid>
            <pubDate>Mon, 02 Dec 2024 10:46:39 GMT</pubDate>
            <description><![CDATA[<h1 id="문제-상황">문제 상황</h1>
<p>P 프로젝트 내부 QA 기간 중 작가 등록 페이지에서 필드에 값 넣고 지우고 사진 등록하고 난리 부르스를 하니 버벅이는 현상 포착
performance 측정해보니 <strong>INP가 빨간불</strong>이 떠있음</p>
<blockquote>
<p><strong>INP(Interaction to Next Paint)</strong>는 사용자가 UI와 상호작용했을 때, 브라우저가 그 결과를 화면에 렌더링하기까지 걸리는 시간을 측정하는 사용자 경험 지표</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/doh_0112/post/a8e4035c-f9b6-49c4-8875-a58cb1745ead/image.png" alt=""></p>
<h1 id="문제-원인">문제 원인</h1>
<p>사진 파일을 올리고 닉네임과 작가 소개를 변경하니 해당 버벅이는 것을 확인.
코드에서 <code>useWatch</code> 훅을 사용하여 <code>modal</code> 컴포넌트로 닉네임, 사진 등의 데이터를 prop으로 전달하고 있음. <code>form</code> 컴포넌트 내부의 필드 컴포넌트들에서 form의 각 필드 값을 업데이트 하면 값을 추적하고 있던 hook에 의해 modal에 전달하던 상태 또한 업데이트 되어 리렌더링이 발생한 것.</p>
<h1 id="문제-해결">문제 해결</h1>
<p>리팩토링 전략은 다음과 같음</p>
<h3 id="데이터-전달-방식의-개선">데이터 전달 방식의 개선</h3>
<ul>
<li>상태 기반 전달 대신 컨텍스트 활용 : <code>useWatch</code>로 상위 컴포넌트에서 데이터를 추적하지 않고, <code>form 객체</code>를 그대로 자식 컴포넌트로 전달(직접 데이터를 읽도록 함)</li>
<li>불필요한 prop 제거 : 필드 값이 필요 없어진 <code>modal</code> 컴포넌트에는 <code>form</code>과 모달 상태(열고 닫음을 관장하는 상태)만 전달하여 리렌더링을 최소화</li>
</ul>
<h1 id="개선-결과">개선 결과</h1>
<h3 id="1리렌더링-감소">1.리렌더링 감소</h3>
<h3 id="2inp-수치-개선">2.INP 수치 개선</h3>
<p>이전에는 고용량 사진 업로드 후 필드 입력 시 400ms 이상의 INP 수치를 기록함
리팩토링 후, 평균 INP 수치가 130ms 이하로 감소하여 사용자 경험이 크게 향상되었다!</p>
<p><img src="https://velog.velcdn.com/images/doh_0112/post/dfa9c5ad-fa35-4adb-89ab-ba3000fdd24d/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[video 로딩 이슈에 관해서]]></title>
            <link>https://velog.io/@doh_0112/video-%EB%A1%9C%EB%94%A9-%EC%9D%B4%EC%8A%88%EC%97%90-%EA%B4%80%ED%95%B4%EC%84%9C</link>
            <guid>https://velog.io/@doh_0112/video-%EB%A1%9C%EB%94%A9-%EC%9D%B4%EC%8A%88%EC%97%90-%EA%B4%80%ED%95%B4%EC%84%9C</guid>
            <pubDate>Wed, 25 Sep 2024 11:26:57 GMT</pubDate>
            <description><![CDATA[<p>DC 서비스 진행 중
해외 유저의 비디오 재생과 관련하여 끊김과 로딩이 계속 돌아가는 이슈가 발생함</p>
<p>관련 코드를 훑어보았을 때,
코드 간의 의존성이 높았지만 해당 부분을 전부 바꾸기에는 <strong>물리적인 시간의 총량이 높아보였음</strong>
따라서 <strong>두 가지 사항을 중점</strong>으로 개선 방향을 잡음</p>
<h1 id="1-소켓-연결-최적화">1. 소켓 연결 최적화</h1>
<p>유저의 공지사항 알림으로 인해 소켓이 전역으로 연결되어 있었음
해외 유저와 비슷한 환경인 slow 3G에서 테스트 해본 결과 대역폭이 제한된 환경에서는 소켓이 네트워크에 큰 부하를 가할 수 있음을 확인함.
소켓 연결을 페이지 별로 제한하여 네트워크 대역폭을 비디오 스트리밍과 소켓 통신으로 나누어 사용하지 않도록 함. 오버헤드를 줄임으로서 비디오 세그먼트를 포함한 리소스 로드의 지연을 해소함.</p>
<h1 id="2-비디오-용량-감소">2. 비디오 용량 감소</h1>
<p>테스트 영상의 원본 크기는 46MB로 slow 3G에서 스트리밍 하기에 상당히 큰 용량임.
더 낮은 해상도나 낮은 비트 전송률을 위해 현재 상황에서 가장 빠르게 대응 가능한 방법이 원본 영상 자체의 크기를 줄이는 것이라고 판단함. 
비디오 크기 감소 -&gt; 비트 전송률 줄임 -&gt; 초당 데이터 양이 줄어서 추가 데이터를 위한 버퍼링이 최소화됨</p>
<h1 id="추후-개선-방향">추후 개선 방향</h1>
<p>추후 개선 방향으로 고민한 것은 두 가지가 있음</p>
<ol>
<li><p>화질별 영상 업로드
BO에서 화질 별로 영상을 업로드 하고 B2C 비디오 플레이어에 화질 옵션을 추가함
프론트 공수는 적고 서버 공수가 전혀 없다는 이점이 있지만, BO 관리자가 영상을 여러개 생성하여 업로드 하는 과정이 번거롭다는 단점</p>
</li>
<li><p>적응형 스트리밍 사용
BO에서 단일 비디오 업로드 -&gt; 서버에서 비디오 파일을 m3u8 형식으로 변환 (FFmpeg 명령을 통해 인코딩된 HLS 형식으로 변환하여 &#39;.m3u8&#39; 재생 목록과 &#39;.ts&#39; 세그먼트 파일 생성함) -&gt; 변환한 파일(m3u8 파일+ 세그먼트)을 s3 업로드
프론트 공수가 전혀 없으며 사용자 네트워크에 따라 비디오 품질을 자동으로 조절(*hls)된다는 장점이 있지만 서버 공수가 크다는 단점</p>
</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[turborepo 톺아보기]]></title>
            <link>https://velog.io/@doh_0112/turborepo-%ED%86%BA%EC%95%84%EB%B3%B4%EA%B8%B0</link>
            <guid>https://velog.io/@doh_0112/turborepo-%ED%86%BA%EC%95%84%EB%B3%B4%EA%B8%B0</guid>
            <pubDate>Mon, 23 Sep 2024 08:46:13 GMT</pubDate>
            <description><![CDATA[<h2 id="monolithic-architecture">Monolithic Architecture</h2>
<p>모놀리식 아키텍처를 바탕으로 레포지토리를 관리하던 방식은 소스 코드를 모듈화하지 않고 <strong>하나의 리포지터리에 모두</strong> 넣었다.
모든 코드가 단일버전으로 관리 되기에 <strong>코드 재사용성이 높지만 관심분리가 어려워 디버깅 난이도가 험했다</strong>.</p>
<p>모놀리식 아키텍처를 찾아보며 <a href="https://medium.com/coupang-engineering/how-coupang-built-a-microservice-architecture-fd584fff7f2b">쿠팡의 MSA 아키텍쳐 전환기</a>를 읽게 되었는데
쿠팡의 기존 코드는 하나의 레포 안에 관심 분리가 되어있지 않아 컴포넌트 간의 의존성의 높았다. 결제에서 일어난 부분 서비스 장애가 전체 서비스 장애로 이어지게 되어 MSA 아키텍쳐로 전환하게 되었다는 내용이다.</p>
<p>이 내용을 통해 모놀리식 아키텍쳐의 특징과 단점을 서비스 단에서 이해할 수 있었다. 모놀리식 아키텍쳐는 소프트웨어 아키텍쳐 중 하나이지만 위 예시처럼 코드 베이스 관리 방식과 밀접한 관련이 있음을 알 수 있다.</p>
<h2 id="multi-repo">Multi-Repo</h2>
<p>모놀리식 아키텍처의 높은 코드 재사용성에도 불구하고 관심분리의 하드함이라는 치명적인 단점으로 인해 멀티레포라는 코드 베이스 관리 방법론이 나타나게 된다.
멀티레포는 각기 다른 기능이나 모듈을 <strong>독립적인 레포지토리로 관리</strong>한다.
독립적인 관리로 인해 관심 분리가 잘 이루어졌지만 그로 인해 코드 단계에서의 재사용성이 아주 낮아지게 된다. </p>
<p>최근 프로젝트들은 멀티레포 방법론을 따랐는데, 독립적으로 관리하다 보니 B2C와 BO의 동일한 기능 삭제 및 추가, UI 변화가 있을 경우 공통된 코드를 두 레포지토리에서 작성할 수 밖에 없었다.
당연히 비즈니스 로직이 첨가된 유틸 함수들 또한 중복으로 생성하는 번거로움이 있었다.</p>
<p>이뿐만 아니라 배포까지도 개별적으로 관리해야 하니 1줄의 코드를 주석 처리하여 업데이트 하는 업무에서도 각각 수정 및 배포를 진행하니 짧게는 3분 길게는 10분까지 소요되는 경우도 있었다.</p>
<h2 id="mono-repo">Mono-Repo</h2>
<p>모노레포는 모놀리식 아키텍쳐와 멀티레포의 장점을 섞은 방법론이다.
<strong>하나의 레포지토리에서 기능이나 모듈을 독립적으로 관리</strong>한다.</p>
<p>하나의 레포로 관리하므로 모놀리식 아키텍처의 장점인 높은 코드 재사용성을 경험할 수 있다. 동시에 내부 코드는 기능과 목적에 따라 모듈화 시키므로 관심 분리를 통해 디버깅 용이성도 챙긴다.</p>
<p>모놀리식 아키텍쳐와 멀티레포, 모노레포를 하나의 그림으로 정리하면 아래와 같다.
<img src="https://velog.velcdn.com/images/doh_0112/post/e8820cd0-0d88-4230-ab2f-89882ace842c/image.png" alt=""></p>
<h2 id="turbo-repo">Turbo-Repo</h2>
<p>모노레포 환경에서 빌드를 지원하는 도구가 Turbo-repo 다.
터보레포는 고급 빌드 시스템을 구축하는 과정을 돕는 도구로 스크립트 설정 및 설계와 관련된 수고를 덜어준다.</p>
<p>터보레포의 주요 특징으로 4가지를 꼽아봤다.</p>
<p>첫 번째는 <strong>Incremental builds</strong>
빌드 작업 시 이미 계산된 내용은 건너 뛰는 것을 의미한다. 따라서 빠른 속도로 빌드를 진행할 수 있다.</p>
<p>두 번째는 <strong>Content-aware hasing</strong>
타임스탬프 해싱(가장 마지막으로 변경 사항이 발생한 시각을 바탕으로 모든 파일을 다시 빌드)이 아닌 콘텐츠 인식 해싱을 지원하므로 변경된 파일만 다시 빌드하게 된다.</p>
<p>세 번째는 <strong>Cloud caching</strong></p>
<p>로컬 캐시 뿐만 아니라 클라우드 상에 빌드 캐시를 공유할 수 있다.
따라서 팀원들과 빠른 빌드를 경험할 수 있다.</p>
<p>마지막으로 <strong>Parallel execution</strong>
지정된 태스크 단위로 의존성을 판단하여 최대한 병렬적으로 작업을 진행한다.
태스크 단위는 turbo.json의 파이프라인에 작성된 명령어를 기준으로 한다.</p>
<h2 id="정리">정리</h2>
<p>결론적으로 
모놀리식 아키텍쳐는 모든 주민이 함께 사는 큰 집이다. 남이 쓰는 드라이기를 내가 바로 쓸 수 있는 대신 수도가 고장나면 물과 관련된 모든 기능을 사용하지 못하듯이 모든 것이 밀접하게 연관되어 있어 문제 해결에 어려움을 겪는다.</p>
<p>멀티레포는 모든 주민이 각 층마다 나뉘어져서 사는 아파트다. 우리집 수도가 고장나도 다른 층은 문제 없이 목욕을 할 수 있지만 다른 주민이 쓰는 드라이기가 필요해도 우리집에 없기 때문에 나는 새로 사와야 한다. (유틸 함수의 중복 선언)</p>
<p>이 단점들을 해결하기 위한 모노레포는 모든 주민이 같은 건물에 거주하지만 층으로 나뉘어져있지 않고 공동 작업 공간이 분리되어 있는 곳이다. 어떤 곳에는 드라이기, 고데기, 거울 등이 모아져있어서 바로 가져다 쓸 수 있는거다. 또한 드라이기를 새로 들여오면 모든 주민들에게 새 드라이기를 사줄 필요없이 하나의 드라이기만 업데이트 해주면 모두 똑같이 새로운 드라이기를 사용하게 된다.</p>
<p>터보레포는 이런 모노레포를 더 원활하고 빠르게 이용할 수 있게 해준다. 공용 공간의 복사기에 캐싱 기능을 넣어서 누군가 인쇄한 문서를 또 인쇄하려고 하면 굳이 스캔하지 않아도 바로 인쇄할 수 있게 해주거나 a룸이 욕실 청소를 한다면 b룸을 이용하게 해주는 병렬적인 구조를 도와준다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[yarn berry 사용 중 typescript error]]></title>
            <link>https://velog.io/@doh_0112/yarn-berry-%EC%82%AC%EC%9A%A9-%EC%A4%91-typescript-error</link>
            <guid>https://velog.io/@doh_0112/yarn-berry-%EC%82%AC%EC%9A%A9-%EC%A4%91-typescript-error</guid>
            <pubDate>Tue, 05 Mar 2024 07:59:41 GMT</pubDate>
            <description><![CDATA[<p>프로젝트 파일을 클론하여서 사용하는데</p>
<blockquote>
<p>경로 /Users/내프로젝트 경로/.yarn/sdks/typescript/lib/tsserver.js이(가) 올바른 tsserver 설치를 가리키지 않습니다. 포함된 TypeScript 버전을 대신 사용합니다.</p>
</blockquote>
<p>이런 알림이 뜨더니 일반 ts, tsx 파일에서 타입스크립트 에러가 잔뜩 났다.
결론적으로 Yarn Berry의 PnP 기능과 호환되도록 설계된 TypeScript SDK가 없어서 인터페이스에 대한 참조를 못하고 에러가 났던것..!</p>
<h1>해결방법</h1>

<ol>
<li><p>yarn berry pnp 호환 typescript sdk 설치</p>
<blockquote>
<p>yarn dlx @yarnpkg/sdks vscode</p>
</blockquote>
</li>
<li><p>vscode의 setting.json 변경
나는 프로젝트 루트폴더의 vscode/setting.json을 업데이트했다</p>
<blockquote>
<p>{
&quot;typescript.tsdk&quot;: &quot;.yarn/sdks/typescript/lib&quot;,
&quot;typescript.enablePromptUseWorkspaceTsdk&quot;: true
}</p>
</blockquote>
</li>
<li><p>에디터 재시작</p>
</li>
</ol>
<p>나는 이 방법으로 해당 에러를 해결했다..
혹시 같은 에러 겪고 계시면 한 번 트라이 해보세요</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[전자서명 구현 (feat. react-signature-canvas)]]></title>
            <link>https://velog.io/@doh_0112/%EC%A0%84%EC%9E%90%EC%84%9C%EB%AA%85-%EA%B5%AC%ED%98%84-feat.-react-signature-canvas</link>
            <guid>https://velog.io/@doh_0112/%EC%A0%84%EC%9E%90%EC%84%9C%EB%AA%85-%EA%B5%AC%ED%98%84-feat.-react-signature-canvas</guid>
            <pubDate>Wed, 07 Feb 2024 07:45:18 GMT</pubDate>
            <description><![CDATA[<p>이번 프로젝트에서 전자서명 구현이 필요했다.
<code>react-signature-canvas</code> 라이브러리를 사용했는데, npm 문서는 <a href="https://www.npmjs.com/package/react-signature-canvas">여기</a>참고하면 된다.</p>
<h1 id="1-설치">1. 설치</h1>
<pre><code>yarn add react-signature-canvas</code></pre><p>위 명령어를 통해 라이브러리 설치</p>
<h1 id="2-사용">2. 사용</h1>
<pre><code class="language-javascript">import SignatureCanvas from &#39;react-signature-canvas&#39;

&lt;SignatureCanvas
        canvasProps={{
          className:
            &#39;sigCanvas&#39;,
        }}
      /&gt;</code></pre>
<p>사용법은 import하고 컴포넌트를 바로 사용해주면 된다.
해당 컴포넌트에서 <code>canvasProps</code> 이름으로 prop을 전달받고 있는데</p>
<p>아래와 같은 프롭들을 전달할 수 있다</p>
<ul>
<li>velocityFilterWeight : number, default: 0.7</li>
<li>minWidth : number, default: 0.5</li>
<li>maxWidth : number, default: 2.5</li>
<li>minDistance: number, default: 5</li>
<li>dotSize : number or function, default: () =&gt; (this.minWidth + this.maxWidth) / 2</li>
<li>penColor : string, default: &#39;black&#39;</li>
<li>throttle : number, default: 16</li>
</ul>
<br>

<p>또한 서명 동작에 대한 이벤트 리스너로 아래의 두가지 리스너가 있다.</p>
<ul>
<li>onEnd : 서명 시작된 때를 감지 </li>
<li>onBegin : 서명이 시작된 후 끝날 때를 감지</li>
</ul>
<h1 id="3-api">3. api</h1>
<p>문서를 바탕으로 제공하고 있는 api들을 정리해봤다.</p>
<ul>
<li>isEmpty() : boolean</li>
<li>clear() : backgroundColor prop을 사용해 서명 클리어</li>
<li>fromDataURL(base64String, options) : base64 문자열 이미지를 캔버스로 그려줌</li>
<li>toDataURL(mimetype, encoderOptions): 서명 이미지를 데이터 url(base64String)으로 변환</li>
<li>off(): 연결된 이벤트 핸들러 모두 해제</li>
<li>on(): 모든 이벤트 핸들러 연결</li>
<li>getTrimmedCanvas(): 모든 공백을 제거한 버전의 서명을 캔버스로 반환 </li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[shadcn/ui 사용기 ]]></title>
            <link>https://velog.io/@doh_0112/shadcnui-%EC%82%AC%EC%9A%A9%EA%B8%B0</link>
            <guid>https://velog.io/@doh_0112/shadcnui-%EC%82%AC%EC%9A%A9%EA%B8%B0</guid>
            <pubDate>Mon, 29 Jan 2024 15:07:52 GMT</pubDate>
            <description><![CDATA[<h2 id="1-shadcnui란">1. shadcn/ui란</h2>
<blockquote>
<p>This is NOT a component library. It&#39;s a collection of re-usable components that you can copy and paste into your apps</p>
</blockquote>
<p>shadcn/ui의 공식 문서에서는 위와 같이 설명하고 있다. 간단하게 tailwind의 스타일링을 곁들인 코드 모음 조각(컴포넌트)라고 생각하면 될 것 같다.</p>
<h2 id="2-사용방법">2. 사용방법</h2>
<p>일단 <code>create-next-app</code>을 통해 플젝을 만들어 놓은 전제 하에 진행된다.</p>
<h3 id="1-init">1. init</h3>
<pre><code class="language-mac-term">npx shadcn-ui@latest init

Would you like to use TypeScript (recommended)? no/yes
Which style would you like to use? › Default
Which color would you like to use as base color? › Slate
Where is your global CSS file? › › app/globals.css
Do you want to use CSS variables for colors? › no / yes
Where is your tailwind.config.js located? › tailwind.config.js
Configure the import alias for components: › @/components
Configure the import alias for utils: › @/lib/utils
Are you using React Server Components? › no / yes
</code></pre>
<p>init을 하면 위와 같은 질문들이 나온다.
상황에 따라 선택해주면 init 완.</p>
<h3 id="2-add">2. add</h3>
<p>이제 필요한 코드 조각(컴포넌트)를 내 플젝에 add 시키면 된다</p>
<pre><code>npx shadcn-ui@latest add button
</code></pre><p>예를 들어 내가 button 코드를 가져오고 싶다면 위와 같이 add 다음에 가져 오고 싶은 컴포넌트를 넣어주면 된다. 저 컴포넌트 종류는 <a href="https://ui.shadcn.com/docs/components/accordion">공식 문서</a>에 정리가 잘 되어있음</p>
<p>이렇게 다운로드 해주면 <code>components/ui</code> 경로에 <code>button.tsx</code> 가 생성된다.
코드는 아래와 같다.</p>
<pre><code class="language-typescript">const buttonVariants = cva(
  &#39;inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50&#39;,
  {
    variants: {
      variant: {
        default: &#39;bg-primary text-primary-foreground hover:bg-primary/90&#39;,
        destructive:
          &#39;bg-destructive text-destructive-foreground hover:bg-destructive/90&#39;,
        outline:
          &#39;border border-input bg-background hover:bg-accent hover:text-accent-foreground&#39;,
        secondary:
          &#39;bg-secondary text-secondary-foreground hover:bg-secondary/80&#39;,
        ghost: &#39;hover:bg-accent hover:text-accent-foreground&#39;,
        link: &#39;text-primary underline-offset-4 hover:underline&#39;,
      },
      size: {
        default: &#39;h-10 px-4 py-2&#39;,
        sm: &#39;h-9 rounded-md px-3&#39;,
        lg: &#39;h-11 rounded-md px-8&#39;,
        icon: &#39;size-10&#39;,
      },
    },
    defaultVariants: {
      variant: &#39;default&#39;,
      size: &#39;default&#39;,
    },
  }
)

export interface ButtonProps
  extends React.ButtonHTMLAttributes&lt;HTMLButtonElement&gt;,
    VariantProps&lt;typeof buttonVariants&gt; {
  asChild?: boolean
}

const Button = React.forwardRef&lt;HTMLButtonElement, ButtonProps&gt;(
  ({ className, variant, size, asChild = false, ...props }, ref) =&gt; {
    const Comp = asChild ? Slot : &#39;button&#39;
    return (
      &lt;Comp
        className={cn(buttonVariants({ variant, size, className }))}
        ref={ref}
        {...props}
      /&gt;
    )
  }
)
Button.displayName = &#39;Button&#39;

export { Button, buttonVariants }
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[setTimeout 함수를 사용하며, 이벤트 루프와 호출 스택 이해하기!]]></title>
            <link>https://velog.io/@doh_0112/setTimeout-%ED%95%A8%EC%88%98%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%98%EB%A9%B0-%EC%9D%B4%EB%B2%A4%ED%8A%B8-%EB%A3%A8%ED%94%84%EC%99%80-%ED%98%B8%EC%B6%9C-%EC%8A%A4%ED%83%9D-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@doh_0112/setTimeout-%ED%95%A8%EC%88%98%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%98%EB%A9%B0-%EC%9D%B4%EB%B2%A4%ED%8A%B8-%EB%A3%A8%ED%94%84%EC%99%80-%ED%98%B8%EC%B6%9C-%EC%8A%A4%ED%83%9D-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0</guid>
            <pubDate>Tue, 07 Nov 2023 06:29:51 GMT</pubDate>
            <description><![CDATA[<h2 id="서론">서론</h2>
<p>자바스크립트는 싱글 스레드 기반 언어로서, 한 번에 하나의 작업만을 처리할 수 있습니다. 이 글에서는 자바스크립트의 호출 스택, 이벤트 루프, 그리고 &#39;다음 틱&#39;이라는 개념을 설명하고, setTimeout(..., 0)을 사용하여 이를 처리하는 방법을 탐색합니다.</p>
<h2 id="호출-스택call-stack">호출 스택(Call Stack)</h2>
<p>호출 스택은 함수의 실행을 추적하는 자바스크립트 엔진의 주요 구성 요소입니다. 함수가 호출되면 스택의 최상위에 푸시되고, 실행이 완료되면 스택에서 팝됩니다. 이 스택 구조는 LIFO(Last In, First Out) 방식으로 작동합니다.</p>
<h2 id="이벤트-루프event-loop">이벤트 루프(Event Loop)</h2>
<p>이벤트 루프는 호출 스택이 비어 있을 때, 즉 현재 실행 중인 태스크가 없을 때 태스크 큐나 마이크로태스크 큐에 있는 작업을 호출 스택으로 옮겨 실행합니다. 이 메커니즘은 비동기 작업을 가능하게 하며, 자바스크립트가 싱글 스레드임에도 불구하고 동시성을 가질 수 있도록 합니다.</p>
<h2 id="다음-틱next-tick">다음 틱(Next Tick)</h2>
<p>&#39;다음 틱&#39;은 현재 실행 중인 태스크가 완료된 후 바로 다음에 처리될 작업을 의미합니다. setTimeout(..., 0)을 사용하면, 현재의 태스크가 끝나고 호출 스택이 비워지는 대로 콜백 함수가 태스크 큐에 추가되어 가장 먼저 처리될 예정입니다.</p>
<h2 id="settimeout-0의-활용">setTimeout(..., 0)의 활용</h2>
<p>DOM 업데이트와 같은 비동기 작업을 수행한 후에 특정 액션을 취해야 할 때, setTimeout(..., 0)은 리액트가 DOM 변화를 완료한 후 콜백 함수를 실행하도록 보장하는 방법입니다. 이는 리액트의 상태 업데이트와 DOM 업데이트 사이의 타이밍 이슈를 해결하는 데 도움을 줍니다.</p>
<h2 id="결론">결론</h2>
<p>자바스크립트에서 비동기 코드의 복잡성을 관리하고, 리액트의 상태 업데이트 후 DOM에 접근할 필요가 있을 때, 이벤트 루프와 &#39;다음 틱&#39;의 이해는 필수적입니다. setTimeout(..., 0)의 적절한 사용은 이러한 시나리오를 효과적으로 처리할 수 있는 키를 제공합니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[atomic 패턴과 폴더구조]]></title>
            <link>https://velog.io/@doh_0112/atomic-%ED%8C%A8%ED%84%B4%EA%B3%BC-%ED%8F%B4%EB%8D%94%EA%B5%AC%EC%A1%B0</link>
            <guid>https://velog.io/@doh_0112/atomic-%ED%8C%A8%ED%84%B4%EA%B3%BC-%ED%8F%B4%EB%8D%94%EA%B5%AC%EC%A1%B0</guid>
            <pubDate>Thu, 17 Aug 2023 13:51:12 GMT</pubDate>
            <description><![CDATA[<h1 id="왜-아토믹-패턴을-쓰지">왜 아토믹 패턴을 쓰지</h1>
<p>nextjs 프로젝트를 진행하면서 폴더 구조가 너무 복잡해지고 어떻게 관리할지에 대한 고민이 생겼고 디자인 패턴을 찾아보던 중에 atomic design pattern을 알게됐음</p>
<p>아토믹 디자인 사용하면 작은 구조부터 큰 구조까지 계층적으로 만들 수 있어서 재사용성이 좋아짐 재사용성이 좋으니까 당연히 유지보수도 쉬워짐 굿</p>
<h1 id="디자인-설계">디자인 설계</h1>
<p>디자인 설계는 5단계로 나뉘고 가장 작은 구조부터 큰 구조로 모아지는 형태임</p>
<ol>
<li>atom 
 : button, input과 같은 가장 작은 단위 컴포넌트</li>
<li>molecules
 : atom 조합 컴포넌트 (input+button =&gt; 검색 폼)</li>
<li>organisms
 : atom+molecules 조합 컴포넌트 (검색 폼 + 메뉴 =&gt; 헤더)</li>
<li>templates
 : 1+2+3의 조합 컴포넌트 (헤더 + 아티클 + 푸터 =&gt; 레이아웃)</li>
<li>pages
 : templates + 실제 data 컴포넌트</li>
</ol>
<h1 id="폴더-구조">폴더 구조</h1>
<p><img src="https://velog.velcdn.com/images/doh_0112/post/84d79cd2-1783-4979-bd43-7a2802748e44/image.png" alt=""></p>
<p>아토믹 디자인 패턴을 기반으로 이러한 폴더 구조를 잡게 되었음</p>
<ul>
<li>components</li>
<li>pages</li>
<li>public</li>
<li>styles</li>
<li>hooks </li>
<li>shared</li>
<li>utils</li>
</ul>
<p>위와 같은 형태로 프로젝트 폴더 구조를 만들었음
순서대로 
<code>components</code> : 컴포넌트들을 관리 폴더
<code>pages</code> : URL 경로에 대한 페이지 라우팅을 관리 폴더
<code>public</code> : 정적 파일(이미지, 폰트, 기타 정적 리소스) 관리 폴더
<code>styles</code> : 스타일 관리 폴더
<code>hooks</code> : 재사용 가능한 hooks(데이터 페칭, 상태 관리, UI 동작 등) 관리 폴더
<code>shared</code> : 프로젝트 전반에 사용되는 공통 컴포넌트(API 호출을 위한 함수, 공통으로 사용되는 상수, 타입 정의 등) 관리 폴더
<code>utils</code> : 유틸리티 함수 등의 재사용 가능한 코드 조각(날짜 형식 변환이나 데이터 파싱, 검증 로직 등) 관리 폴더</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[video.js 사용과 DOM 조작, CSS 설정 정리글]]></title>
            <link>https://velog.io/@doh_0112/video.js-%EC%82%AC%EC%9A%A9%EA%B3%BC-DOM-%EC%A1%B0%EC%9E%91-CSS-%EC%84%A4%EC%A0%95-%EC%A0%95%EB%A6%AC%EA%B8%80</link>
            <guid>https://velog.io/@doh_0112/video.js-%EC%82%AC%EC%9A%A9%EA%B3%BC-DOM-%EC%A1%B0%EC%9E%91-CSS-%EC%84%A4%EC%A0%95-%EC%A0%95%EB%A6%AC%EA%B8%80</guid>
            <pubDate>Wed, 16 Aug 2023 09:45:39 GMT</pubDate>
            <description><![CDATA[<h1 id="1-videojs">1. Video.js</h1>
<p><strong>video.js</strong>는 HTML5 기반의 웹 비디오 플레이어를 생성 및 사용할 수 있게 하는 라이브러리이다.</p>
<h2 id="1-1-video-player-생성-과정">1-1. video player 생성 과정</h2>
<p>❶ <strong>HTML 요소 생성</strong></p>
<pre><code class="language-tsx">&lt;video ref={videoRef} className=&quot;video-js&quot; playsInline&gt;
    &lt;track label={&quot;자막&quot;} kind={&quot;captions&quot;} srcLang={&quot;kr&quot;} src={subtitle} default /&gt;
&lt;/video&gt;</code></pre>
<br>

<p>❷ <strong>Player 초기화 및 생성</strong></p>
<pre><code class="language-tsx">const player = (playerRef.current = videojs(videoElement as HTMLVideoElement, options, () =&gt; {
        onReady &amp;&amp; onReady(player);
                // player 실행시 동작시킬 부분
}));</code></pre>
<ol>
<li><p><code>videojs()</code>를 사용하여 player를 초기화 및 생성할 수 있다. 함수의 첫 번째 인자로 <code>video ****element</code>를, 두 번째 인자로 해당 <strong>*<em>video player의 *</em></strong><code>option 객체</code>를, 마지막 인자로 해당 player가 준비되었을 때 호출할 <code>callback 함수</code>를 작성한다.</p>
</li>
<li><p>위 코드는 변수 <code>player</code> 와 <code>player.current</code> 에 video player를 할당하여, player 변수로 직접video player를 조작하거나 player.current로 다른 곳에서도 동일한 player 인스턴스에 접근하기 위해 <strong>chained assignment</strong> 를 사용했다.</p>
</li>
<li><p>두번째 인자인 <strong><code>option 객체</code></strong>는 아래와 같은 형태로 커스텀 하여 사용할 수 있다.</p>
<ul>
<li><p>option custom 예시 코드</p>
<pre><code class="language-tsx">  {
      autoplay: true,                    // 자동 재생 설정
      controls: true,                    // 플레이어 컨트롤 표시 설정
      crossOrigin: &quot;anonymous&quot;,          // 교차 출처 리소스 공유 설정
      responsive: true,                  // 반응형 플레이어 설정
      fluid: true,                       // 컨테이너 크기에 맞게 플레이어 크기 조절
      playbackRates: [2, 1.5, 1.25, 1, 0.75, 0.5],  // 사용 가능한 재생 속도 옵션
      touchEnabled: true,                // 터치 이벤트 활성화 설정
      controlBar: {                      // 컨트롤 바 설정
        playToggle: true,                // 재생/일시정지 토글 버튼 활성화
        remainingTimeDisplay: true,      // 남은 시간 표시 활성화
        progressControl: true,           // 진행률 컨트롤 활성화
        pictureInPictureToggle: true,    // 화면 내 화면(PiP) 토글 버튼 활성화
        currentTimeDisplay: true,        // 현재 재생 시간 표시 활성화
        fullscreenToggle: !isIOS,        // 전체화면 토글 버튼 활성화 (iOS가 아닐 경우)
        qualitySelector: true,           // 화질 선택 컨트롤 활성화
      },
      plugins: {                         // 사용할 플러그인 설정
        // ...
      },
      poster: thumbnailUrl,              // 비디오 썸네일 URL
      sources: [{ src: videoUrl }],      // 비디오 소스 URL
  };

  **// 구체적인 옵션은 https://videojs.com/guides/options/ 에서 확인 가능 🔎**</code></pre>
</li>
</ul>
</li>
</ol>
<br>

<p>❸ <strong>메서드와 이벤트 리스너 사용</strong></p>
<p>player를 초기화 및 생성한 후에는 <strong>메서드</strong>를 통해 player를 조작할 수 있다.</p>
<pre><code class="language-tsx">player.play(); // 재생
player.puase(); // 정지</code></pre>
<p>혹은 <strong>이벤트 리스너</strong>를 연결하여 player를 조작할 수 있다.</p>
<pre><code class="language-tsx">player.on(&#39;play&#39;, function() {
  console.log(&#39;플레이어가 재생됨~&#39;);
});
// on 메서드를 사용하여 이벤트 리스너를 연결하여 &#39;play&#39; 이벤트 발생시 callback 실행</code></pre>
<br>
<br>

<h1 id="2-dom-조작">2. DOM 조작</h1>
<p>비디오 플레이어의 옵션을 변경해야 하는 경우 (ex: 컨트롤 바 요소 생성 및 추가, 삭제 등의 작업) </p>
<p><strong>DOM을 조작하여 player를 custom</strong> 할 수 있다.</p>
<p>DOM을 조작하는 방법은 다양하지만  크게 두 가지 방법으로 정리하겠다.</p>
<h2 id="❶--queryselector로-element-선택-→-element의-method-이용">❶  <strong>querySelector로 element 선택 → element의 method 이용</strong></h2>
<pre><code class="language-tsx">// 예시 - insertAdjacentHTML 메서드로 새로운 element 추가

const somebtn = player.el().querySelector(&quot;.somebtn&quot;);
const qualityWrapHTML = `
    &lt;div class=&quot;qulity-Wrap&quot;&gt;
        &lt;button&gt;
            화질 버튼
        &lt;/button&gt;
    &lt;/div&gt;
    `
somebtn.insertAdjacentHTML(&quot;beforeend&quot;, qualityWrapHTML);

위 코드는 특정 버튼 element를 querySelector를 사용하여 변수에 할당하고
html 태그를 문자열로 만들어서 insertAdjacentHTML 메서드를 사용하여 텍스트를 HTML 파싱하여 DOM에 추가한다.</code></pre>
<br>

<h2 id="❷--player의-instance-선택-→-instance의-method-이용">❷  <strong>player의 instance 선택 → instance의 method 이용</strong></h2>
<pre><code class="language-tsx">// 예시 - player의 controlBar 인스턴스에 접근하고 해당 인스턴스의 addChild 메서드로 element 추가

if (!player.controlBar.getChild(&quot;iosFullscreenCustomButton&quot;)) {
  player.controlBar.addChild(&quot;button&quot;, {
    name: &quot;iosFullscreenCustomButton&quot;,
    text: &quot;전체 화면&quot;,
    className: &quot;vjs-custom-button-IOS&quot;,
  });
}            

let iosFullscreenCustomButton: any = player.el().querySelector(&quot;.vjs-custom-button-IOS&quot;);                        
iosFullscreenCustomButton?.addEventListener(&quot;touchstart&quot;, function () {
    setIsFullscreenValue((prev) =&gt; !prev);
});

위 코드는 player의 contorlBar 인스턴스에 접근하여 `getChild` 메서드를 사용해서 &quot;iosFullscreenCustomButton&quot; 컴포넌트가 있는지 확인한다.
해당 컴포넌트가 없다면 `addChild` 메서드를 사용하여 새로운 element를 생성한다.
해당 element를 가지고 1번의 방법(querySelector와 element의 메서드 사용)을 이용해서 이벤트를 연결한다.</code></pre>
<p>아래는 DOM을 조작하여 <code>control-bar</code>를 custom하는 추가적인 예시이다.</p>
<ul>
<li>예시 코드</li>
</ul>
<pre><code class="language-tsx">// 1️⃣ **control bar의 배속 옵션의 텍스트 &#39;1x&#39;에서 &#39;1배&#39;로 수정**

// 1) querySelector로 조작할 element를 선택하여 할당한다.
const rateMenuButton = player.el().querySelector(&quot;.vjs-playback-rate .vjs-menu&quot;);

// 2) element의 innerText를 변경한다.
if (rateMenuButton) {
  var menuContentEl = rateMenuButton.querySelectorAll(&quot;.vjs-menu-item&quot;);
  menuContentEl.forEach(function (menuItem: any) {
    var textEl = menuItem.querySelector(&quot;.vjs-menu-item-text&quot;);
    if (textEl) {
      textEl.innerText = textEl.innerText.replace(&quot;x&quot;, &quot;배&quot;);
    }
  });
}


// 2️⃣ **control bar의 ios용 전체 화면 커스텀 버튼 생성**

// 1) player의 fullscreenToggle 인스턴스의 el() 메서드로 element를 할당하여 dispaly를 조작한다. 
if (isIOS) {
  // ios일 때 전체 화면 토글 버튼 숨김
  if (player.controlBar &amp;&amp; (player.controlBar as any).fullscreenToggle) {
    const fullscreenButton = (player.controlBar as any).fullscreenToggle.el();
    fullscreenButton.style.display = &quot;none&quot;;
  }

// 2) player의 controlbar 인스턴스의 getChild() 메서드로 컴포넌트 확인 -&gt; addChild() 메서드로 새로운 컴포넌트 생성 + 연결된 DOM element 생성
  // ios용 전체 화면 커스텀 버튼 생성
  if (!player.controlBar.getChild(&quot;iosFullscreenCustomButton&quot;)) {
    player.controlBar.addChild(&quot;button&quot;, {
      name: &quot;iosFullscreenCustomButton&quot;,
      text: &quot;전체 화면&quot;,
      className: &quot;vjs-custom-button-IOS&quot;,
    });
  }
}</code></pre>
<br>


<h3 id="🔎-player의-controlbar-인스턴스-사용에-대한-보충-설명">🔎 player의 controlBar 인스턴스 사용에 대한 보충 설명</h3>
<p>video.js에서 <code>videojs()</code> 함수를 사용해서 player 인스턴스를 생성한다.</p>
<p>video.js는  component-based architecture를 가지기 때문에 컴포넌트 클래스를 이용해서 컴포넌트 인스턴스를 생성하는 것이다. 즉, controlBar와 같은 하위 컴포넌트(video.js가 생성하는 컴포넌트)들이 모여서 video player를 만드는 것이고 각각의 하위 컴포넌트들은 결과적으로 컴포넌트 클래스의 인스턴스(클래스로 만들어낸 객체)이기 때문에 <strong>점 표기법</strong>을 ****통해 하위 컴포넌트로 접근할 수 있게 된다.</p>
<p>예를 들어, player 인스턴스가 controlBar 라는 하위 컴포넌트로 구성되어 있고 controlBar 컴포넌트가 fullscreenToggle 이라는 하위 컴포넌트로 구성되었다면<code>player.controlBar.fullscreenToggle</code> 과 같이 해당 하위 컴포넌트에 접근할 수 있는 것이다.</p>
<p>예시 코드 2️⃣ <strong>control bar의 ios용 전체 화면 커스텀 버튼 생성</strong> 을 보면, 위와 같은 이유로 인해 player.controlBar.fullscreenToggle에 접근하고 해당 인스턴스의 <code>el()</code> 메서드를 사용해서 특정 element에 접근하여 커스텀 할 수 있는 것이다.</p>
<br>
<br>

<h1 id="3-css-설정">3. CSS 설정</h1>
<p>video.js는 player에 대한 default style을 제공한다.</p>
<p><code>video.js/dist/video-js.css</code>를 import 하여 default style 적용이 가능하다.</p>
<p>하지만 default style이 아닌 custom style 적용이 필요한 경우가 생길 수 있다.</p>
<p>따라서 video.js의 player를 스타일링하는 방법을 크게 두 가지로 정리하겠다.</p>
<h2 id="❶-videojs가-제공하는-element의-classname-이용">❶ video.js가 제공하는 element의 classname 이용</h2>
<p>video.js를 사용해서 player를 생성하면, 특정 classname을 가진 element들이 player 인스턴스를 이루게 된다. 커스텀 할 element의 특정 classname을 이용해서 새로운 스타일을 overriding 하면 된다.</p>
<pre><code class="language-tsx">.vjs-control-bar {
    background-color: #333;
}

이 코드는 video.js player의 `.vjs-control-bar`라는 클래스를 가진 요소의
배경색을 지정하여 기존 스타일(video.js/dist/video-js.css)을 overriding 하여 스타일을 적용한다.</code></pre>
<p>이 방법은 기존 스타일 시트와 통합하여 사용 가능하기 때문에 간편하다.</p>
<br>

<h2 id="❷-dom-조작을-통한-style-property-이용">❷ DOM 조작을 통한 style property 이용</h2>
<p>player를 이루고 있는 요소 자체에 접근하여 style property를 이용해서 스타일링 한다.</p>
<pre><code class="language-tsx">const player = (playerRef.current = videojs(videoElement as HTMLVideoElement, options, () =&gt; {
    onReady &amp;&amp; onReady(player);

    // style property 사용
    let volumeButton = player.el().querySelector(&quot;.vjs-volume-button&quot;);
    volumeButton.style.order = &quot;999&quot;;
    volumeButton.style.background = &quot;url(&#39;/mobile_assets/settingbtn.svg&#39;) no-repeat center / contain&quot;;
};

이 코드는 querySelector를 통해 스타일링을 설정할 element를 변수에 할당하고
style property를 사용해서 동적으로 스타일링을 적용한다.</code></pre>
<p>이 방법은 JS 로직과 함께 사용 가능하기 때문에 동적으로 스타일링 적용이 필요할 때 유용하다.
하지만 이 방법을 사용할 때는 잠재적인 문제 예방을 위해, <strong>반드시 video player가 초기화 및 생성된 후에 스타일링</strong> 해야한다.</p>
<br>

<h2 id="❸-참고할-부분">❸ 참고할 부분</h2>
<p>두 가지 방법은 각각의 장점이 있어서 정적인 스타일링에서는 1번 방법을, 동적 혹은 js 로직이 필요한 부분에서는 2번 방법을 사용하면 된다.</p>
<p>하지만 <strong>player의 특정 element의 경우 1번(classname 방법)이 적용되지 않는 문제가 있다.</strong> </p>
<p>이 부분은 선택자 특정화(더 구체적인 선택자로 스타일 적용 점수 높임), <code>!important</code> 키워드 사용,  overriding할 style sheet의 import 순서 고려 등 다양한 방법을 시도했지만 적용되지 않았다.</p>
<p>결론적으로 Video.js가 run time에 JS를 사용해서 일부 스타일을 동적으로 적용하는 과정에서 스타일시트에 정의된 스타일이 재정의되었을 확률이 높다. 따라서 이런 경우에는 2번(DOM style property 방법)을 적용시켜 해결했다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[nextjs에서 window 객체 찾을 수 없는 에러]]></title>
            <link>https://velog.io/@doh_0112/nextjs%EC%97%90%EC%84%9C-window-%EA%B0%9D%EC%B2%B4-%EC%B0%BE%EC%9D%84-%EC%88%98-%EC%97%86%EB%8A%94-%EC%97%90%EB%9F%AC</link>
            <guid>https://velog.io/@doh_0112/nextjs%EC%97%90%EC%84%9C-window-%EA%B0%9D%EC%B2%B4-%EC%B0%BE%EC%9D%84-%EC%88%98-%EC%97%86%EB%8A%94-%EC%97%90%EB%9F%AC</guid>
            <pubDate>Sat, 12 Aug 2023 06:41:47 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>Server Error
ReferenceError: window is not defined
This error happened while generating the page. Any console logs will be displayed in the terminal window.
Source
util/video/player.ts (4:19) @ window
  2 | 
  3 | export const getVideoJsOptions = (videoUrl: string, thumbnailUrl: string, isIOS: boolean) =&gt; {
 4 | const isMobile = window.matchMedia(&quot;(max-width: 520px)&quot;).matches;
    |                 ^
  5 | return {
  6 |   autoplay: true,
  7 |   controls: true,</p>
</blockquote>
<p>  window 객체를 찾을 수 없는 에러 발생 
  window는 브라우저의 전역 객체로, 서버에서는 사용할 수 없습니다. Next.js와 같은 프레임워크에서는 기본적으로 서버 사이드 렌더링이나 정적 생성을 수행하기 때문에 이런 문제가 발생할 수 있습니다.</p>
<p>  해결 방법은</p>
<ol>
<li><p>조건부 사용해서 윈도우 객체 있는지 </p>
</li>
<li><p>useEffect로 컴포넌트 마운트 후에 window 접근하기 =&gt; 이거는 상황에 따라 불가능하기도 함, 함수안에서 useEffect 호출 못하니까</p>
<p>나는 함수 안에서 사용해야 하기 때문에 1번으로 해결함</p>
</li>
</ol>
<pre><code class="language-ts"> const isMobile = typeof window !== &quot;undefined&quot; &amp;&amp; window.matchMedia(&quot;(max-width: 520px)&quot;).matches;</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[router.push와 router.replace의 차이]]></title>
            <link>https://velog.io/@doh_0112/router.push%EC%99%80-router.replace%EC%9D%98-%EC%B0%A8%EC%9D%B4</link>
            <guid>https://velog.io/@doh_0112/router.push%EC%99%80-router.replace%EC%9D%98-%EC%B0%A8%EC%9D%B4</guid>
            <pubDate>Mon, 07 Aug 2023 10:23:01 GMT</pubDate>
            <description><![CDATA[<p>useRouter로 생성한 객체인 router를 사용할때 
browser history와 관련된 메서드인 push와 replace가 있다</p>
<p>일단 라우터 히스토리는 스택 구조로 되어있는데, push를 사용하면 새로운 route가 스택의 top으로 올라가고 replace를 사용하면 스택의 top을 완전히 그 route만으로 덮어씌운다(overwritting)</p>
<p>예를 들어 &#39;page/product&#39;에서 &#39;page/product/detail&#39;로 push를 사용해서 이동한 후에 뒤로가기를 누르면 &#39;page/product&#39;로 이동함
하지만 replace로 이동한 후 뒤로가기를 누르면 &#39;page&#39;로 이동함</p>
<p>그러니까 page를 기준으로 page/product 페이지에서 (replace 메서드를 사용한) 버튼을 눌러 page/product/detail로 이동한 상태에서 뒤로가기를 누르면 라우터 히스토리에는 page -&gt; page/product/detail로 덮어씌워졌기 때문에 이전 페이지는 page가 된다</p>
<p>따라서 유효하지 않은 접근을 한 유저가 뒤로가기 버튼을 눌러서 다시 유효하지 않은 페이지로 이동하려고 한다면 replace를 사용해서 그것을 차단하면 유용함</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[CS] 자료구조 - Array, LinkedList]]></title>
            <link>https://velog.io/@doh_0112/Array%EC%99%80LinkedList</link>
            <guid>https://velog.io/@doh_0112/Array%EC%99%80LinkedList</guid>
            <pubDate>Tue, 18 Jul 2023 16:26:06 GMT</pubDate>
            <description><![CDATA[<h1 id="0-array">0. Array</h1>
<p><img src="https://velog.velcdn.com/images/doh_0112/post/37ed5cc7-a40a-4bbf-9da9-62ec9bf3a52c/image.png" alt=""><em>출처:The Beginner&#39;s Guide to JS</em></p>
<p><strong>연관된 데이터</strong>를 사용하기 위한 자료구조 == 순차리스트
JS의 배열은 <strong>동적 배열</strong>(dynamic array)로 구현되어있어서 배열의 크기를 선언 시점에 고정하지 않고 동적으로 조정</p>
<h2 id="0-1-배열의-특징">0-1. 배열의 특징</h2>
<ol>
<li>대부분 언어에서 고정된 크기를 가지지만 JS에서는 동적으로 크기가 증감됨</li>
<li>원하는 원소의 Index를 알고 있다면 상수시간(O(1))로 원소 찾음</li>
<li>원소를 삭제하면 해당 index에 빈자리 생김</li>
</ol>
<h2 id="0-2-배열-생성">0-2. 배열 생성</h2>
<pre><code class="language-js">// 1. 빈 Array 생성 
let arr1 = [];

// 2. 미리 초기화된 Array 생성 
let arr2 = [1,2,3,4,5];

// 3. 많은 값 초기화시 fill 사용 
let arr3 = Array(10).fill(0);

//4. 특정 로직으로초기화시 from 사용 
let arr4 = Array.from({length : 10},(_,i)=&gt;I);</code></pre>
<h2 id="0-3-배열-요소-삭제와-추가">0-3. 배열 요소 삭제와 추가</h2>
<h3 id="-배열-요소-삭제">* 배열 요소 삭제</h3>
<p><img src="https://velog.velcdn.com/images/doh_0112/post/918d1fa3-7c8f-4b73-965e-5e01a0a1acda/image.png" alt=""><img src="https://velog.velcdn.com/images/doh_0112/post/378efa72-a197-4ea5-883f-0d299496713d/image.png" alt=""><img src="https://velog.velcdn.com/images/doh_0112/post/b755e5f3-8012-4852-bb62-b5833583cdb4/image.png" alt=""><em>출처:A to Z JavaScript</em></p>
<p>arr[8]의 요소를 arr[7]로 옮기면 배열 요소 삭제 마무리
전체 배열에서 요소 삭제 = 요소 삭제 -&gt; 한 칸씩 앞당김
즉, 배열이 길어질수록(데이터가 증가할수록) 앞 당겨야하는 데이터도 선형적으로 증가하기 때문에 <strong>O(n)의 시간복잡도</strong>를 가지게됨</p>
<h3 id="-배열-요소-추가">* 배열 요소 추가</h3>
<p><img src="https://velog.velcdn.com/images/doh_0112/post/8c578614-5694-4ddb-a788-19591dd14afc/image.png" alt=""><img src="https://velog.velcdn.com/images/doh_0112/post/0de3226d-6fb7-4e22-98fd-f41f17407d18/image.png" alt=""><img src="https://velog.velcdn.com/images/doh_0112/post/0e43d4d1-7dcd-44bc-9e18-37e3eeadba4b/image.png" alt=""><em>출처:A to Z JavaScript</em></p>
<p>arr[4]에 빈공간이 생겨서 값을 넣으면 배열 요소 추가 마무리
전체 배열에서 요소 추가 = 한칸씩 뒤로 당김 -&gt; 빈 인덱스에 요소 넣기
즉, 배열이 길어질수록(데이터가 증가할수록) 당겨야하는 데이터도 선형적으로 증가하기 때문에 <strong>O(n)의 시간복잡도</strong>를 가지게됨</p>
<pre><code class="language-js">Const arr = [1,2,3];

// 4를 배열 끝에 추가
arr.push(4); // O(1)
// push와 pop과 같은 메서드는 배열 요소를 이동시킬 필요가 없기 때문에 O(1)
// 위와 같은 이유로 요소를 이동시키는 shift, unshift는 O(n)

//3번 인덱스에 128 추가
Arr.splice(3,0,128); // O(n)

// 3번 인덱스 삭제
arr.splice(3,1);  // O(n)</code></pre>
<p>결론적으로
배열은 <strong>인덱스를 가지고 탐색하는 과정</strong>을 거친다면 <strong>굉장히 효율적</strong>(O(1))이지만
요소를 <strong>추가하고 삭제</strong>한다면 <strong>비효율적</strong>일 수 있다(O(n))</p>
<p>+) 배열의 탐색에 대해서 더 정리하자면,
데이터의 특정 위치를 알고 있다면 바로 접근해서 O(1)이 걸리지만, 모른다면 결국 모든 요소를 확인해야하기 때문에 이 경우에도 O(n)이 걸림.</p>
<p>또한 <code>push</code>,<code>pop</code>을 통해 끝 인덱스만을 변형할 경우 O(1)의 복잡도이지만, under the hood의 엔진 동작으로 인해 O(n)을 가짐
-&gt; 예를 들어, 배열의 크기가 매우 크거나 메모리가 많이 사용되고 있는 시스템에서 연속적으로 데이터를 저장할 충분한 공간이 없을때 JS 엔진이 모든 요소를 새로운 위치로 복사 해야함 따라서 배열의 크기에 비례하여 옮기는 데이터의 양도 선형적으로 증가하기 때문에 O(n)이 될 수 있음
-&gt; 이런 경우는 배열의 크기가 매우매우매우 커야한다고 함</p>
<p>하지만 정렬이 되어있거나, 정렬이 가능한 데이터들이라면 이진 탐색같은 알고리즘을 사용해서 배열의 탐색을 O(log(n))까지 줄일 수 있음
따라서 인덱싱을 통한 탐색에는 배열이라는 자료구조가 가장 효율적이라고 표현</p>
<ul>
<li>Under the hood란 ?<br>: 자동차의 엔진이나 기계장치가 보통 차체 아래(즉, &#39;hood&#39; 아래)에 숨겨져 있음을 뜻하는 비유에서 유래. 보통 사용자나 프로그래머에게는 보이지 않지만, 시스템이나 소프트웨어가 실제로 어떻게 작동하는지를 설명하는 데 사용되는 표현
저 설명에서는 push pop이 굉장히 간단한 메서드지만 메모리 할당, 요소의 이동, 가비지 컬렉션 등 많은 복잡한 과정이 뒤 따르는 부분이 있기에 under the hood 엔진 동작이라고 표현</li>
</ul>
<br>

<h1 id="1-linked-list">1. Linked List</h1>
<p>이전에 배열의 자료구조의 결론에서 요소를 추가하고 삭제하는 작업이 반복적으로 이루어져야 한다면 굉장히 비효율적이라는 것을 확인함</p>
<p>이에 따라서 요소의 추가 삭제 작업에 더 적합한 자료구조가 등장함
그것이 바로 Linked List(연결 리스트)!</p>
<p><img src="https://velog.velcdn.com/images/doh_0112/post/a86c6f9f-7e59-4431-b775-fcc479d1ab5f/image.png" alt=""></p>
<p>연결 리스트는 각각의 node들로 구성됨
가장 첫번째 node를 <code>Head</code>라고 정의하고, 각 node는 데이터를 가진(위 이미지에서는 영문자) <code>데이터 영역</code>과 다음 node를 가리키는 <code>포인터 영역</code>으로 구성됨</p>
<h2 id="1-1-linked-list의-특징">1-1. Linked List의 특징</h2>
<blockquote>
<ol>
<li>메모리가 허용하는 한 요소를 제한없이 추가</li>
<li>탐색은 O(n)이 소요되지만 추가 삭제는 O(1) 소요</li>
<li>Singly Linked List와 Doubly Linked List, Circular Linked List 존재</li>
</ol>
</blockquote>
<h2 id="1-2-array와-차이점">1-2. Array와 차이점</h2>
<ul>
<li><p>배열의 메모리
<img src="https://velog.velcdn.com/images/doh_0112/post/814c4467-ae02-46f2-bc6e-3373abf84e80/image.png" alt=""><em>출처: Matthew Chan Medium</em>
배열의 경우 저장된 요소의 값이 메모리 상에서 연속적으로 위치함
따라서 조회할때 linked List처럼 연결 노드를 찾아다니지 않아도 됨
하지만 요소를 추가하거나 삭제할 경우 차지하는 영역의 리사이징(요소 위치 당기던 작업)이 필요함 -&gt; 비효율적</p>
</li>
<li><p>Linked List의 메모리
<img src="https://velog.velcdn.com/images/doh_0112/post/82c97ef5-6342-4b63-a012-e02a2aa08c0c/image.png" alt=""><em>출처: Matthew Chan Medium</em>
Linked List의 경우, 연속적인 위치가 아닌 다음 노드의 위치를 가지고 추적하기에 요소를 조회할 때 더 많은 연산이 필요함
하지만 추가와 삭제 시, 리사이징을 거치지 않아도 되기에 효율적임</p>
</li>
</ul>
<h2 id="1-3-singly-linked-list">1-3. Singly Linked List</h2>
<p><img src="https://velog.velcdn.com/images/doh_0112/post/05b4df30-e368-4eb4-b5ef-20a5cc343112/image.png" alt="">
Head에서 Tail까지 단방향으로 이어지는 연결리스트. 가장 단순한 형태
포인터 영역이 Null이면 그 리스트의 가장 마지막 노드 = Tail 
Head는 해당 리스트의 가장 첫번째 node
Head Pointer는 Head를 참조하는 포인터</p>
<p>(지피티 왈 : 헤드 포인터는 헤드 노드를 가리키는 포인터이고, 헤드는 리스트의 첫 번째 노드입니다. 이 둘은 조금 다르지만, 실제로는 헤드 포인터를 통해 헤드 노드에 접근하므로 종종 혼용되어 사용됩니다.)</p>
<h2 id="signly-linked-list의-핵심-로직">Signly Linked List의 핵심 로직</h2>
<p><strong>1. 요소 찾기</strong>
<img src="https://velog.velcdn.com/images/doh_0112/post/7bc2e68b-89fd-4810-923d-92dc6b76bf1b/image.png" alt="">1. headPointer가 head 찾음
2. head의 데이터 영역을 살피고 찾는 값이 아니면 포인터 영역 참조해서 next Node로 이동
3. 원하는 데이터 찾을때까지 무한 반복
따라서 배열의 크기가 커질수록(데이터가 증가할수록) 참조해서 비교하는 데이터도 선형적으로 증가함 
-&gt; <strong>O(n)</strong></p>
<br>

<p><strong>2. 요소 추가</strong>
<img src="https://velog.velcdn.com/images/doh_0112/post/cbf8ab2c-43a6-4cb1-a599-9a6411356647/image.png" alt=""> <code>3</code>이라는 데이터를 가진 Node를 <code>2</code>데이터와 <code>4</code> 데이터 사이에 추가하고 싶음
<img src="https://velog.velcdn.com/images/doh_0112/post/a52837a2-5679-485b-8c08-00076f4548cb/image.png" alt="">1. 추가할 node의 포인터가 <code>4</code> 데이터를 가리키게 함
2. <code>2</code> 데이터를 가진 node의 포인터가 추가할 node를 가리키게함
-&gt; <strong>O(1)</strong></p>
<p>(하지만 <code>2</code>를 가진 데이터를 탐색하거나 <code>4</code>를 가진 데이터를 탐색한다면 다시 선형시간 소요. 따라서, 추가를 위한 탐색을 하지 않도록 주의하며 코드를 작성해야함)</p>
<br>

<p><strong>3. 요소 삭제</strong></p>
<p><img src="https://velog.velcdn.com/images/doh_0112/post/515a4d77-f4e3-4683-b3e2-5ec9a2680628/image.png" alt="">
<code>2</code> 데이터를 가진 node를 삭제하려면</p>
<ol>
<li>Prev node가 삭제하려는 node의 next node를 가리키게함</li>
<li><code>2</code> 데이터를 가진 node 메모리에서 삭제</li>
</ol>
<p>-&gt; <strong>O(1)</strong></p>
<br>

<h2 id="1-4-doubly-linked-list">1-4. Doubly Linked List</h2>
<p><img src="https://velog.velcdn.com/images/doh_0112/post/a6c6ca6e-1769-4d41-bf18-789cd70115dc/image.png" alt="">
<strong>양방향으로 이어지는 연결 리스트</strong> (이중 연결 리스트)
Signly 보다 자료구조의 크기가 더 큼</p>
<br>

<p><strong>1. 요소 추가</strong>
<img src="https://velog.velcdn.com/images/doh_0112/post/ac094d1f-bb60-4b59-b3fa-88aff8f6836a/image.png" alt=""></p>
<ol>
<li>추가할 node의 next node를 <code>4</code>데이터를 가진 node로 가리키게함</li>
<li><code>2</code>데이터를 가진 node의 next node를 추가할 node로 가리키게함</li>
<li><code>4</code>데이터를 가진 node의 prev node를 추가할 node로 가리키게함</li>
<li>추가할 node의 prev node를 <code>2</code>데이터를 가진 node로 가리키게함</li>
</ol>
<p>-&gt; O(1)</p>
<br>

<p><strong>2. 요소 삭제</strong>
<img src="https://velog.velcdn.com/images/doh_0112/post/58c122a5-51a5-468c-a955-e471087c2016/image.png" alt="">
<code>2</code>데이터와 <code>4</code> 데이터 사이의 <code>3</code>데이터를 가진 node 삭제하기</p>
<ol>
<li><code>2</code>데이터를 가진 node의 next node를 <code>4</code>데이터를 가진 node로 가리키게함</li>
<li><code>4</code>데이터를 가진 node의 prev node를 <code>2</code> 데이터를 가진 node로 가리키게함</li>
<li><code>3</code> 데이터 가진 node를 메모리에서 삭제</li>
</ol>
<p>-&gt; O(1)</p>
<br>

<h2 id="1-5circular-linked-list">1-5.Circular Linked List</h2>
<p><img src="https://velog.velcdn.com/images/doh_0112/post/8905a520-5e26-4417-8017-92ac1d75e19c/image.png" alt="">
단일 혹은 이중에서 <strong>Tail이 Head로 연결되는 리스트</strong>
메모리 아껴쓸 수 있음. 원형 큐 만들때도 사용</p>
<p>순회 시간: Circular Linked List는 순회할 때 특정 노드에서 시작하여 마지막 노드에 도달하면 다시 첫 번째 노드로 돌아가는 특성을 가지고 있습니다. 이로 인해 순회 연산이 효율적으로 수행될 수 있습니다. 만약 캐싱 시 데이터를 순회하며 접근해야 하는 경우, Circular Linked List를 사용하면 순회 시간을 줄일 수 있어 캐시 히트(hit) 확률을 높일 수 있음</p>
<p>+) 단일리스트를 코드로 정리</p>
<pre><code class="language-js">class Node {
    constuctor(value) {
        this.value = value;
        this.next = null;
    }
}

class SinglyLinkedList {
    constructor() {
        this.value = null;
        this.tail = null;
    }

    // 요소 찾기
    find(value) {
        let currNode = this.head;
        while (currNode.value !== value) {
            currNode = currNode.next;
        }
        return currNode;
    }


    // 요소 추가하기
    append(newValue) {
        const newNode = new Node(newValue);
        if (this.head === null) {
            this.head = newNode;
              this.tail = newNode;
        } else {
             this.tail.next = newNode;
            this.tail = newNode;
        }
    }

    // 요소 중간에 추가하기
    insert(node, newValue) {
        const newNode = new Node(newValue);
        newNode.next = node.next;
        node.next = newNode;
    }


    // 요소 삭제하기
    remove(value) {
        let prevNode = this.head;
        while (prevNode.next.value !== value) {
            prevNode = prevNode.next;
        }

        if (prevNode.next !== null) {
            prevNode.next = prevNode.next.next;
        }
}

const linkedList = new SignlyLinkedList();
linkedList.append(1);
linkedList.append(2);
linkedList.append(3);
linkedList.append(5);
console.log(linkedList.find(3)); // Node { value : 3, next : Node { value : 5, next :null } }

</code></pre>
<h1 id="참고-자료">참고 자료</h1>
<ul>
<li><a href="https://levelup.gitconnected.com/array-vs-linked-list-data-structure-c5c0ff405f16">Matthew Chan Medium</a></li>
<li><a href="https://www.javascripttutorial.net/javascript-array/">JSTutorial</a></li>
<li>A to Z JavaScript</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[CS] 자료구조의 종류와 시간 복잡도]]></title>
            <link>https://velog.io/@doh_0112/CS-%EC%9E%90%EB%A3%8C%EA%B5%AC%EC%A1%B0%EC%9D%98-%EC%A2%85%EB%A5%98</link>
            <guid>https://velog.io/@doh_0112/CS-%EC%9E%90%EB%A3%8C%EA%B5%AC%EC%A1%B0%EC%9D%98-%EC%A2%85%EB%A5%98</guid>
            <pubDate>Tue, 18 Jul 2023 15:20:52 GMT</pubDate>
            <description><![CDATA[<h1 id="자료구조의-종류">자료구조의 종류</h1>
<p><img src="https://velog.velcdn.com/images/doh_0112/post/cbaff023-9983-4efd-b825-91d1190a9b91/image.png" alt=""><em><code>사진출처 : A to Z : JavaScript</code></em></p>
<p>자료구조는 크게 3가지로 분류할 수 있음
단순 구조, 선형 구조, 비선형 구조
그 중에서도 선형과 비선형에 대해 간략하게 정리하겠음</p>
<ul>
<li>선형 구조 : 한 원소 뒤에 반드시 하나의 원소만 존재하는 구조 (자료가 선형으로 존재하는 구조) Array, Linked List, stack, queue 해당
<img src="https://velog.velcdn.com/images/doh_0112/post/cfa11dfa-8183-4b3c-b200-ef4e8dd48c90/image.png" alt=""><em><code>사진출처 : A to Z : JavaScript</code></em></li>
</ul>
<ul>
<li>비선형 구조 : 원소간 다대다 관계를 가지는 구조 (계층적 혹은 망형 구조) tree, graph 해당
<img src="https://velog.velcdn.com/images/doh_0112/post/90526f94-b511-4ae7-aaae-9e8dbc85e9f6/image.png" alt=""><em><code>사진출처 : A to Z : JavaScript</code></em></li>
</ul>
<br>

<h1 id="시간-복잡도">시간 복잡도</h1>
<p>프로그램의 성능을 완벽히 비교하려면 하드웨어의 성능부터 운영체제의 성능, 컴파일러 최적화, 입력 크기 등 고려해야 할 부분이 많음
따라서 정확히 성능을 비교하는 것은 불가능하지만 <strong>대략적인 성능을 비교하기 위해 Big O 표기법</strong>(시간복잡도를 표현하기 위한 방법으로 <strong>점근적 표기법</strong> 중 하나)을 만들어서 사용함</p>
<p><img src="https://velog.velcdn.com/images/doh_0112/post/7979b63e-24ae-4144-bf00-cc1f9e6af9c3/image.png" alt=""><em><code>사진출처 : Carnegie Mellon University</code></em></p>
<p>n을 기준으로 입력 데이터의 양에 따라 연산의 수가 얼만큼 증가하는가(=시간 복잡도)를 나타내는거임</p>
<p>O(1) &lt; O(logn) &lt; O(n) &lt; O(nlogn) &lt; On^2 &lt; O2^n</p>
<p>로그시간부터 코드를 정리하면 아래와 같음</p>
<pre><code class="language-js">// 로그 시간
// O(logn)
for (let i = 0; i &lt;= n; i *= 2) {}


// 선형시간
// O(n)
for (let i = 0; i &lt; n; i += 1) {}


// 선형지수시간
// O(nlogn)
for (let i = 0; i &lt; n; i += 1) {
  for (let j = 0; j &lt;= n; j *= 2) {}
}


// 2차시간
// On^2
for (let i = 0; i &lt; n; i += 1) {
  for (let j = 0; j &lt; n; j += 1) {}
}
</code></pre>
<p>위와 같이 for문을 가지고 코드로 정리해볼 수 있음
예시 코드는 없지만 지수시간(O2^n), 팩토리얼 시간(On!)은 코드상으로 가급적 사용하면 안됨</p>
<br>

<h2 id="📌-점근적-표기법"><strong>📌 점근적 표기법</strong></h2>
<p>알고리즘의 <strong>계산 복잡성</strong>을 비교한다고 가정했을 때</p>
<blockquote>
<p>A 알고리즘 계산 복잡성 :  16πn^2+23n
B 알고리즘 계산 복잡성 : 100000𝑛+10000</p>
</blockquote>
<p>어느 것이 더 효율적인 알고리즘인가? -&gt; 예측하기 어려움
예측에 대한 어려움을 해결하기 위해 우리가 알고있는 로그, 지수, 다항함수 등과의 비교로 표현 -&gt; <strong>계산 복잡성의 증가 양상을 단순화</strong>시킴
그게 점근적 표기법임 
총 다섯가지의 점근적 표기법이 있고, 그 중 big O 표기법을 가장 많이 사용함
이유는 최악의 경우(<em>입력에 대해 가장 나쁜 성능을 보여주는 경우</em>)에도 해당 알고리즘이 어떤 성능을 낼지 가늠해볼 수 있기 때문</p>
<p><img src="https://i.imgur.com/QmfDswm.png" alt="bigO표기법">
그래프를 해석하면, 𝑓(𝑛)이 𝑔(𝑛)에 한 없이 가까워질 수 있지만 그걸 넘을 수는 없음 쉽게 말해서 𝑔(𝑛)은 𝑓(𝑛)의 한계치임 ㅇㅇ
이걸 다른 말로 <strong>점근 상한</strong>(an asymptotic upper bound)이라함</p>
<p>위 그래프를 만족하는 𝑓(𝑛)를 𝑂(𝑔(𝑛))이라고 표시하는데
내 알고리즘 𝑓(𝑛)이 𝑂(𝑔(𝑛))에 속한다면, 𝑓(𝑛)의 계산복잡도는 최악의 경우라도 𝑔(𝑛)과 같거나 혹은 작게 되는 것을 알 수 있음
따라서 점근적 표기법을 통해 알고리즘의 계산 복잡성의 증가 양상을 단순화시킴으로써 알고리즘의 성능을 대략적으로 비교하는 데 도움을 주게됨~</p>
<h2 id="bigo-4가지-법칙">bigO 4가지 법칙</h2>
<p>이러한 점근 상한의 개념이 있기 때문에 bigO에서 4가지 법칙이 있음</p>
<blockquote>
<p><strong>1. 계수 법칙</strong>
상수 k가 0보다 클 때, 𝑓(𝑛) = 𝑂(𝑔(𝑛))이면** k𝑓(𝑛) = 𝑂(𝑔(𝑛))**</p>
</blockquote>
<pre><code class="language-js">// 두 개의 for문 모두 O(n)으로 표기
for (let i = 0; i &lt; n; i++) {}
...
for (let i = 0; i &lt; 5*n; i++) {}</code></pre>
<blockquote>
<p><strong>2. 합의 법칙</strong>
𝑓(𝑛) = 𝑂(h(𝑛)), 𝑔(𝑛) = 𝑂(p(𝑛))일 때, <strong>𝑓(𝑛)+𝑔(𝑛) = 𝑂(h(𝑛))+𝑂(p(𝑛))</strong></p>
</blockquote>
<pre><code class="language-js">// 두 for문을 합하여 O(n+m)으로 표기
for(let i = 0; i &lt; n; i++) {}
for(let i = 0; i &lt; 6*n; i++) {}
</code></pre>
<blockquote>
<p><strong>3. 곱의 법칙</strong>
𝑓(𝑛) = 𝑂(h(𝑛)), 𝑔(𝑛) = 𝑂(p(𝑛))일 때, <strong>𝑓(𝑛)<em>𝑔(𝑛) = 𝑂(h(𝑛))</em>𝑂(p(𝑛))</strong></p>
</blockquote>
<pre><code class="language-js">// 두 for문을 곱하여 O(n^2)으로 표기
for(let i = 0; i &lt; n; i++) {
    for(let j = 0; j &lt; 2*n; j++) {}
}
</code></pre>
<blockquote>
<p><strong>4. 다항 법칙</strong>
𝑓(𝑛)이 k차 다항식이면, <strong>𝑓(𝑛) = 𝑂(nᵏ)</strong></p>
</blockquote>
<pre><code class="language-js">// O(n^3)으로 표기
for(let i = 0; i &lt; n*n*n; i++) {}</code></pre>
<p>이렇게 big o 표기법의 4가지 법칙에 대해서 알아봄
여기서 기억해야할 핵심은 두 가지</p>
<ol>
<li>상수항 무시 (계수법칙 참고)</li>
<li>가장 큰 항 외에 무시</li>
</ol>
<pre><code class="language-js">for (let i = 0; i &lt; n; i++) {}

for (let i = 0; i &lt; n; i++) {
    for(let j = 0; j &lt; n; j++) {}
}
// 이 코드는 합의 법칙에 의해 O(n^2+n)이지만 작은 항은 무시하여
// O(n^2)라고 표기해도 됨</code></pre>
<p>위의 질문을 다시 한 번 가져와서 물어보면</p>
<blockquote>
<p>A 알고리즘 계산 복잡성 :  16πn^2+23n
B 알고리즘 계산 복잡성 : 100000𝑛+10000</p>
</blockquote>
<p>이제는 big O 표기법에 따라서 효율성을 예측할 수 있게 됨
A는 n^2이고 B는 n이니까
데이터 수가 증가함에 따라 A 알고리즘의 계산복잡도는 지수적으로, B 알고리즘은 선형적으로 증가하므로 B 알고리즘이 더 효율적이라 판단 가능!</p>
<br>

<h1 id="참고-자료">참고 자료</h1>
<ul>
<li><a href="https://ratsgo.github.io/data%20structure&amp;algorithm/2017/09/13/asymptotic/">ratsgo.github</a></li>
<li>A to Z JavaScript</li>
<li><a href="https://hanamon.kr/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-time-complexity-%EC%8B%9C%EA%B0%84-%EB%B3%B5%EC%9E%A1%EB%8F%84/">hanamon</a></li>
</ul>
<p>빅오 표기법 외에도 빅오메가, 빅세타에 대한 부분은 ratsgo.github에 잘 정리되어 있음</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[CS] 하드웨어와 CPU, 캐시메모리]]></title>
            <link>https://velog.io/@doh_0112/CS-%EC%BB%B4%ED%93%A8%ED%84%B0%EC%9D%98-%EA%B5%AC%EC%84%B1-%ED%95%98%EB%93%9C%EC%9B%A8%EC%96%B4</link>
            <guid>https://velog.io/@doh_0112/CS-%EC%BB%B4%ED%93%A8%ED%84%B0%EC%9D%98-%EA%B5%AC%EC%84%B1-%ED%95%98%EB%93%9C%EC%9B%A8%EC%96%B4</guid>
            <pubDate>Sat, 15 Jul 2023 06:07:57 GMT</pubDate>
            <description><![CDATA[<h1 id="0-컴퓨터의-구성">0. 컴퓨터의 구성</h1>
<p>컴퓨터는 하드웨어와 소프트웨어로 나눌 수 있음</p>
<ul>
<li>하드웨어 : 컴퓨터를 구성하는 물리적 장치</li>
<li>소프트웨어 : 하드웨어의 동작을 실행하는 명령어의 집합</li>
</ul>
<p>그 중에서도 하드웨어에 대해 정리해보겠음</p>
<h1 id="1-하드웨어의-구성">1. 하드웨어의 구성</h1>
<p>하드웨어는 크게 4가지의 부품으로 이루어진다고 볼 수 있음</p>
<ul>
<li>CPU (중앙처리장치)</li>
<li>메모리 (주기억장치)</li>
<li>보조기억장치</li>
<li>입출력장치</li>
</ul>
<h1 id="2-cpu중앙처리장치">2. CPU(중앙처리장치)</h1>
<p>CPU는 프로그램 명령어와 데이터를 읽어와 처리하고 명령어의 수행 순서를 제어함
크게 세 구성요소로 볼 수 있음</p>
<ul>
<li>ALU(산술논리연산장치) : 비교와 연산 담당</li>
<li>레지스터들 : cpu의 데이터 기억 담당</li>
<li>제어장치(CU) : 명령어 해석과 실행 담당</li>
</ul>
<p><img src="https://velog.velcdn.com/images/doh_0112/post/16e51d21-8ed9-4f50-9118-ec6f891307b3/image.png" alt=""><em>사진 출처 : 혼자 공부하는 컴퓨터구조</em></p>
<p>사진에서 볼 수 있듯이 메인보드 속 CPU에는 ALU와 레지스터들 그리고 제어장치로 이루어져 있음</p>
<h2 id="2-1-alu">2-1) ALU</h2>
<p><img src="https://velog.velcdn.com/images/doh_0112/post/f11a71e5-2369-45ea-a934-bd648baa45c6/image.png" alt=""><em>사진 출처 : 혼자 공부하는 컴퓨터구조</em></p>
<p>ALU는 레지스터들로부터 <strong>피연산자(연산의 대상)를 전달</strong> 받거나, <strong>제어장치로부터 제어신호를 입력받음</strong></p>
<p>제어신호를 받아 연산을 거치고 난 후, 그 <strong>결과값을 다시 레지스터로 전달</strong>하고 <strong>플래그 레지스터에 플래그를 내보냄</strong>
(플래그는 연산 결과에 대한 정보인데 0플래그 : 0인지 아닌지 / 부호플래그 : 음수인지 양수인지 등등을 전달해서 결과값에 대한 정보를 주는거임)</p>
<p>🙋🏻‍♀️ <strong>ALU는 왜 결과값을 메모리가 아닌 레지스터에 저장하는가?</strong>
CPU가 메모리에 접근하는 속도는 레지스터에 접근하는 속도보다 훨씬 느림
ALU가 연산할 때마다 결과를 메모리에 저장하면 CPU는 메모리에 자주 접근하게 되고, 결과적으로 프로그램 실행 속도를 늦추게 됨 ㅇㅇ 따라서 레지스터에 일시적으로 저장하는거임</p>
<br>

<h2 id="2-2-제어장치">2-2) 제어장치</h2>
<p>CPU의 제어장치는 메모리(주기억장치)에서 <strong>프로그램 명령어를 꺼내 해독</strong>하고, 그 결과에 따라 <strong>명령어 실행에 필요한 제어 신호</strong>를 기억장치, 연산장치, 입출력장치로 보냄</p>
<p><img src="https://velog.velcdn.com/images/doh_0112/post/6bca1ab3-8874-45a7-b112-38258cc8354c/image.png" alt=""><em>사진 출처 : 혼자 공부하는 컴퓨터구조</em></p>
<p>제어장치는 4가지 정보를 받아들이게 됨</p>
<ol>
<li>명령어 레지스터가 준 <strong>해석할 명령어</strong></li>
<li><strong>클럭</strong> : 컴퓨터의 모든 부품을 움직일 수 있게 하는 시간 단위</li>
<li>플래그 레지스터가 준 <strong>플래그</strong></li>
<li>CPU 외부에서 제어버스로 전달받은 <strong>제어신호</strong></li>
</ol>
<p>메모리에 어떤 명령어(연산 후 저장하라)와 데이터가 저장되어있다고 가정했을 때, 흐름을 간략하게 정리해보겠음
제어장치가 명령어 읽기 제어신호를 메모리에 보냄 -&gt; 메모리의 명령어를 명령어 레지스터에 저장함 -&gt; 제어장치가 명령어 해석함 -&gt; 제어장치가 명령어 실행에 필요한 데이터가 있다고 판단하여 다시 명령어 읽기 제어신호를 메모리에 보냄 -&gt; 메모리의 데이터를 메모리 버퍼 레지스터에 저장함 -&gt; ALU가 레지스터에 있는 데이터 가지고 연산하고 결과값을 레지스터에 저장함 -&gt; 제어신호가 명령어 해석에 따라(연산 후 저장하라) 이 결과값을 &#39;메모리 쓰기&#39; 제어신호와 함께 메모리로 전달 -&gt; 메모리가 결과값을 저장함</p>
<p>이 흐름을 쭉 읽어보면 왜 제어장치가 저 정보들을 받게 되는지 이해감</p>
<p>🙋🏻‍♀️ <strong>CPU에서 명령어를 해석하고 제어 신호를 보내는 것은 무엇인가요? 그것에 대해서 아는 만큼 설명해주세요.</strong>
명령어를 해석하고 제어신호를 보내는 것은 제어장치임
제어장치는 명령어 레지스터로부터 해석해야할 명령어를 받고, ALU가 저장한 플래그도 전달받아서 이를 참고하여 제어신호를 발생시킴
제어신호는 CPU 내부에 전달할 수도 있고 시스템 버스 중 하나인 제어버스를 통해 CPU 외부의 메모리나 입출력 장치에도 전달하여 메모리에 값을 쓰거나 읽을 수 있게 되는 것</p>
<br>

<h2 id="2-3-레지스터">2-3) 레지스터</h2>
<p>CPU에는 다양한 레지스터들이 있음
그 중에서도 주요 레지스터들에 대해 정리하겠음</p>
<ol>
<li><code>프로그램 카운터 (PC)</code> :메모리에서 읽어 들일 명령어의 주소 저장 레지스터 (다음에 수행할 명령어 주소 저장)</li>
<li><code>명령어 레지스터 (IR)</code> : 방금 메모리에서 읽어 들인 명령어를 저장하는 레지스터</li>
<li><code>메모리 주소 레지스터 (MAR)</code> : 메모리의 주소를 저장하는 레지스터</li>
<li><code>메모리 버퍼 레지스터 (MBR)</code> : 메모리와 주고받을 값(데이터와 명령어)을 저장하는 레지스터</li>
<li><code>범용 레지스터</code> : 디양하고 일반적인 상황에서 자유롭게 사용할 수 있는 레지스터</li>
<li><code>플래그 레지스터</code> : 연산 결과 또는 CPU 상태에 대한 부가적인 정보를 저장하는 레지스터</li>
<li>누산기 (AC) : 연산 결과 임시 저장하는 레지스터</li>
</ol>
<br>

<p><img src="https://velog.velcdn.com/images/doh_0112/post/757908c2-cf74-4a84-aa57-0ab346d59cc8/image.png" alt=""><img src="https://velog.velcdn.com/images/doh_0112/post/9799f259-54df-472d-acd6-46f27fc8724d/image.png" alt=""><img src="https://velog.velcdn.com/images/doh_0112/post/ee689897-1e27-4279-b7d7-5c915302e7ae/image.png" alt=""><img src="https://velog.velcdn.com/images/doh_0112/post/dde6d369-9a09-4d1a-a30d-515427c72e8f/image.png" alt=""><img src="https://velog.velcdn.com/images/doh_0112/post/843e7f4b-0e51-469b-a5c9-c544d0e0b04e/image.png" alt=""><img src="https://velog.velcdn.com/images/doh_0112/post/1d779411-4bd7-44f6-acad-027485dba370/image.png" alt=""><em>사진 출처 : 혼자 공부하는 컴퓨터구조</em>

제어장치는 명령어 레지스터의 명령어를 해석하고 제어 신호를 발생시킴
위 과정을 통해 명령어 사이클 중 인출 사이클이 완료됨
인출 이후에는 누산기(AC)에 메모리 버퍼 레지스터(MBR)의 값을 더해주는 실행 사이클이 진행됨</p>
<p>🙋🏻‍♀️ <strong>CPU의 명령 사이클에 대해서 설명해주세요. 딥하게 말고!</strong>
명령 사이클은 CPU가 하나의 명령어를 실행하는 데 필요한 전체 단계를 말함
첫번째 <strong>인출 단계</strong>에서 프로그램 카운터에 현재 실행해야 할 명령어의 주소가 저장되고, 메모리 주소 레지스터를 거쳐 메모리 버퍼 레지스터에 값이 저장되면서 PC가 증가합니다. 그 다음 메모리 버퍼 레지스터의 값이 명령어 레지스터에 저장됩니다.
그 다음 <strong>해독 단계</strong>에서 제어장치가 명령어를 해독하고 
<strong>실행 단계</strong>에서 메모리 버퍼 레지스터에 저장된 값을 가지고 ALU가 연산하여 
마지막** 저장 단계**에서 레지스터에 저장하거나 명령에 따라 메모리나 입출력 장치에 저장합니다.
이러한 단계를 거쳐 하나의 명령어가 실행되며, 프로그램 카운터(PC)가 증가함에 따라 다음 명령어를 인출하는 과정을 반복합니다.</p>
<h1 id="3-캐시-메모리">3. 캐시 메모리</h1>
<ul>
<li>캐시 메모리 : CPU 내부에 위치하며, <strong>주기억장치와 CPU 간의 데이터 전송 속도 차이를 극복하기 위해 사용되는 고속 메모리</strong>(주기억장치는 CPU에 비해 상대적으로 접근 속도가 느림). CPU의 성능을 향상시키고 전체 시스템의 응답 시간을 줄임</li>
</ul>
<h2 id="3-1-캐시-메모리의-계층-구조">3-1) 캐시 메모리의 계층 구조</h2>
<p>캐시 메모리는 3 level 계층 구조로 나뉨</p>
<ul>
<li>L1 : 용량이 작고, 매우 빠른 액세스 속도, CPU 코어에 바로 연결</li>
<li>L2 : L1 캐시보다 약간 느림, L1 캐시보다 용량이 큼, 공유 캐시로 사용되기도 함</li>
<li>L3 : L1, L2 캐시보다 더 큰 용량을 가지고 있으며, 일부 시스템에서만 사용, 여러 개의 CPU 코어 간에 데이터를 공유하는 데 사용</li>
</ul>
<h2 id="3-2-캐시-메모리-작동-원리">3-2) 캐시 메모리 작동 원리</h2>
<ul>
<li>시간 지역성의 원리(Time Locality):
시간 지역성은 CPU가 최근에 액세스한 데이터나 명령어에 다시 빠르게 액세스할 가능성이 높다는 개념. 시간 지역성을 활용해서 CPU가 최근에 액세스한 데이터를 캐시메모리에 저장 -&gt; 빠른 액세스 속도</li>
</ul>
<p>📌 예시) for나 while 같은 반복문에 사용하는 조건 변수는 반복문 안에서 시간 지역성의 원리를 기반으로 또 다시 접근할 가능성이 높기 때문에 캐시에 저장함</p>
<ul>
<li>공간 지역성의 원리(Spatial Locality) :
공간 지역성은 CPU가 특정 데이터나 명령어를 액세스할 때, 해당 데이터 주변의 데이터도 순차적으로 액세스될 가능성이 높다는 개념.
공간 지역성을 활용하여 캐시 메모리는 CPU가 요청한 데이터와 함께 근접한 데이터를 함께 저장하여 미리 가져옴 -&gt; 빠른 액세스 속도</li>
</ul>
<p>📌 예시) Arr[0], Arr[1]과 같은 연속 접근 시, 공간 지역성의 원리를 기반으로 근접한 데이터도 액세스 될 가능성이 높기 때문에 캐시에 저장함</p>
<h2 id="3-3-캐시-히트와-미스">3-3) 캐시 히트와 미스</h2>
<ul>
<li><code>캐시히트</code> : CPU가 요청한 데이터를 캐시에서 성공적으로 찾았을 때 발생하는 상황, 캐시 히트는 주로 시간 지역성의 원리에 의해 발생</li>
<li><code>캐시미스</code> : 캐시 미스는 CPU가 요청한 데이터를 캐시에서 찾지 못하고 주기억장치로부터 데이터를 가져와야 하는 상황, 주로 공간 지역성의 원리에 반하여 CPU가 요청한 데이터와 근처의 데이터를 함께 캐시에 저장하지 않았을 때 발생할 수 있음</li>
</ul>
<h3 id="캐시미스의-상황들">캐시미스의 상황들</h3>
<ol>
<li><p><code>Conflict Miss</code>:
충돌 미스는 가장 일반적인 캐시 미스 유형 중 하나
CPU가 캐시에서 데이터를 찾을 때, 요청한 데이터의 메모리 주소와 다른 데이터가 동일한 캐시 세트에 매핑되어 충돌이 발생하는 경우에 발생.</p>
</li>
<li><p><code>Cold Miss</code>:
CPU가 처음으로 데이터를 요청하거나, 이전에 캐시에 저장된 데이터가 초기화된 경우에 발생
캐시가 초기화되었거나, 이전에 액세스하지 않은 데이터를 가져와야 하므로 주기억장치에서 데이터를 가져오는 시간이 소요됨</p>
</li>
<li><p><code>Capacity Miss</code>:
용량 미스는 캐시의 용량이 요청된 데이터를 저장하기에 충분하지 않은 경우 발생
CPU가 캐시에 저장하려는 데이터의 크기가 캐시의 용량을 초과하여 캐시에 저장되지 못하는 경우에 발생하는 것으로 주로 큰 데이터 구조나 프로그램의 일부가 캐시에 저장되지 못하는 경우에 발생</p>
</li>
</ol>
<p>🙋🏻‍♀️ <strong>캐시미스가 뭔가요? 캐시미스에 대한 예시를 들어주세요</strong></p>
<p>참고 
<a href="https://taegyunwoo.github.io/interview/CS_CPU">https://taegyunwoo.github.io/interview/CS_CPU</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[리팩토링] 로그인과 토큰 저장]]></title>
            <link>https://velog.io/@doh_0112/%EB%A1%9C%EA%B7%B8%EC%9D%B8%EA%B3%BC-%ED%86%A0%ED%81%B0-%EB%A6%AC%ED%8C%A9%ED%86%A0%EB%A7%81</link>
            <guid>https://velog.io/@doh_0112/%EB%A1%9C%EA%B7%B8%EC%9D%B8%EA%B3%BC-%ED%86%A0%ED%81%B0-%EB%A6%AC%ED%8C%A9%ED%86%A0%EB%A7%81</guid>
            <pubDate>Wed, 12 Jul 2023 05:14:03 GMT</pubDate>
            <description><![CDATA[<h1 id="0-기존-코드의-문제점">0. 기존 코드의 문제점</h1>
<p>다님 서비스 프로젝트에서 로그인과 토큰 저장은 아래와 같이 사용함</p>
<blockquote>
<ol>
<li>로그인 시 서버로부터 액세스 토큰과 리프레쉬 토큰을 응답 헤더로 받음</li>
<li>클라이언트가 응답 헤더에서 토큰을 꺼내어 쿠키에 저장</li>
<li>인증 인가가 필요한 api 요청시 쿠키에서 꺼낸 액세스 토큰 사용</li>
<li>인증 인가 필요한 페이지 라우팅에서 액세스 토큰 검사하고(단순히 쿠키 존재 여부 검사) 존재하지 않는다면(=만료) 리프레쉬 토큰 가지고 액세스 재발급</li>
</ol>
</blockquote>
<p>코드 작성 이후 보안과 관련해서 문제점이 있다는 것을 알게됐음
일단 보안이 뚫리는 공격들에 대해서 간단하게 정리하자면</p>
<ul>
<li>XSS(Cross Site Scripting) 공격 
: 사용자로부터 입력을 받는 폼 등에서 악성 스크립트가 삽입되어 해당 스크립트가 사용자의 브라우저에서 실행되게 하는 공격
로그인시 사용자 이름이나 비밀번호 입력란에 악의적인 스크립트가 포함된 값을 입력하는 경우 악성 스크립트로 사용자 토큰 탈취</li>
<li>CSRF(Cross-site Request Forgery) 공격 
: 인증된 사용자의 권한을 이용하여 악의적인 요청을 보내는 공격</li>
</ul>
<p>일단 기존의 코드에서 쿠키에 토큰을 저장하는 부분에서 악성 스크립트를 이용해서 토큰을 탈취당할 수 있음을 인지하게 됨
따라서 쿠키에 저장하지 않고 사용할 수 있게 코드를 수정하려고 함</p>
<h1 id="1-계획">1. 계획</h1>
<ol>
<li>응답 헤더로 전달받았던 액세스 토큰을 payload로 받기</li>
<li>액세스 토큰을 따로 저장하지 않고 axios의 instance의 header로 설정</li>
<li>리프레쉬 토큰은 httpOnly 설정을 통해 서버에서만 관리</li>
<li>액세스 토큰 만료시 인터셉터를 사용해서 재발급</li>
</ol>
<p>이러한 수정 계획을 짰음
울팀 서버 개발자 영구님과 함께 시작~ 🙂</p>
<h1 id="2-시도들">2. 시도들</h1>
<h2 id="2-1-액세스-토큰-코드-변경">2-1. 액세스 토큰 코드 변경</h2>
<pre><code class="language-ts">
// 주요 코드 생략

// as-is
// 응답 헤더의 액세스 토큰과 리프레쉬 토큰을 쿠키에 저장함
const accessToken = response.headers.access_key;
const refreshToken = response.headers.refresh_key;
if (accessToken &amp;&amp; refreshToken) {
   setCookie(&quot;accessToken&quot;, accessToken, 1);
   setCookie(&quot;refreshToken&quot;, refreshToken, 14);
   }

// to-be
// 응답의 payload로 액세스 토큰을 받아 인스턴스 헤더에 설정
const {accessToken, ...} = response.data.data;
axiosInstance.defaults.headers.common.ACCESS_KEY = `${accessToken}`;</code></pre>
<p>먼저 액세스 토큰에 대한 코드를 변경했음
기존 코드에서는 응답 헤더에 있던 액세스 토큰과 리프레쉬 토큰을 쿠키에 저장했었음
하지만 header가 아닌 payload로 액세스 토큰을 전달받아서 바로 instance의 header로 설정함. 리프레쉬 토큰은 httpOnly 설정을 통해 서버에서만 관리하기 때문에 따로 코드를 작성하지 않음</p>
<h2 id="2-1의-문제점">2-1의 문제점</h2>
<p>이렇게 변경하고 로그인을 시도해서 기타 토큰이 필요한 요청들을 테스트해봤는데 정상적으로 기능했음. 하지만 새로고침 시 401 에러 발생</p>
<blockquote>
<p>원인은 <strong>웹 브라우저에서 페이지를 새로고침하면 JavaScript의 메모리가 초기화 되기 때문</strong> 
따라서 <strong>JavaScript로 실행되는 모든 코드와 변수, 객체 등의 상태가 초기화 =&gt; axios 인스턴스와 그 헤더 설정도 초기화 😇</strong></p>
</blockquote>
<h2 id="2-1의-해결방법">2-1의 해결방법</h2>
<p>새로고침 할 때 토큰을 새로 재발급 받으면 되지 않을까 해서
가장 최상위 컴포넌트인 App.tsx가 마운트 될때 토큰 재발급 함수를 사용하기로 결정함</p>
<pre><code class="language-ts">// APP.tsx

  // 새로고침 했을때 액세스 토큰 재발급
  useEffect(() =&gt; {
    refreshAccessToken();
  }, []);

// signUp.ts
export const refreshAccessToken = async () =&gt; {
  try {
    // 토큰 갱신 요청은 refreshInstance를 사용하여 보내기
    const response = await refreshInstance.get(&quot;/api Endpoint&quot;);
    const { accessToken } = response.data.data;
    axiosInstance.defaults.headers.common.ACCESS_KEY = `${accessToken}`;
    return accessToken;
  } catch (err: any) {
    console.log(&quot;여기서 에러&quot;, err);
    const errMessage = err.response.data.detail || err.message;
    return errMessage;
  }
};</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[특정 커밋 작업으로돌아가기]]></title>
            <link>https://velog.io/@doh_0112/%ED%8A%B9%EC%A0%95-%EC%BB%A4%EB%B0%8B-%EC%9E%91%EC%97%85%EC%9C%BC%EB%A1%9C%EB%8F%8C%EC%95%84%EA%B0%80%EA%B8%B0</link>
            <guid>https://velog.io/@doh_0112/%ED%8A%B9%EC%A0%95-%EC%BB%A4%EB%B0%8B-%EC%9E%91%EC%97%85%EC%9C%BC%EB%A1%9C%EB%8F%8C%EC%95%84%EA%B0%80%EA%B8%B0</guid>
            <pubDate>Tue, 27 Jun 2023 12:30:53 GMT</pubDate>
            <description><![CDATA[<p>팀 작업하다가 특정 커밋 시점으로 돌아가야 하는 상황발생</p>
<p>이때 나는 이렇게 했다</p>
<ol>
<li>내 원격 레포를 클론</li>
</ol>
<blockquote>
<pre><code>git clone &lt;repo 주소&gt;</code></pre></blockquote>
<pre><code>
2. 특정 커밋 시점으로 체크아웃

&gt; ```
git checkout &lt;hash&gt;</code></pre><p>나는 소스트리를 보고 특정 커밋의 해쉬 주소(소스트리에서는 커밋이라고 되어있는 열에 있는 값)를 가지고 체크아웃해서 돌아갔다!
다음에 참고할 일 생길까봐 기록</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[무한 스크롤 에러 : 더 이상 받아올 데이터가 없는데도 서버와 무한 통신]]></title>
            <link>https://velog.io/@doh_0112/%EB%AC%B4%ED%95%9C-%EC%8A%A4%ED%81%AC%EB%A1%A4-%EC%97%90%EB%9F%AC</link>
            <guid>https://velog.io/@doh_0112/%EB%AC%B4%ED%95%9C-%EC%8A%A4%ED%81%AC%EB%A1%A4-%EC%97%90%EB%9F%AC</guid>
            <pubDate>Wed, 07 Jun 2023 14:11:31 GMT</pubDate>
            <description><![CDATA[<h2 id="0-문제상황">0. 문제상황</h2>
<p>무한 스크롤은 동작하지만 가장 마지막 데이터에 스크롤이 교차됐을때 컴포넌트가 계속해서 리렌더링됨. 콘솔 테스트 문자열을 각각 추가해봤는데 정확히는** 가장 마지막 데이터가 로딩된 이후에 계속해서 서버에서 데이터를 받아오는 함수가 실행되어 콘솔에 테스트 문자열이 무한으로 출력**된다.</p>
<h2 id="1-문제인식">1. 문제인식</h2>
<pre><code class="language-tsx">  useEffect(() =&gt; {
    getAllPosts();
  }, [page]);</code></pre>
<p>일차적으로 이 코드로 인해서 page 값에 따라 getAllPosts 함수가 실행이 됨 이 함수는 서버와 통신해서 전체 게시글을 받는 함수다.
page 값은 옵저버 객체의 콜백함수에서 증가시키고 있는데 아래와 같다.</p>
<pre><code class="language-tsx">// 옵저버 객체 생성
  useEffect(() =&gt; {
    if (observer.current) observer.current.disconnect();
    observer.current = new IntersectionObserver(
      (entries) =&gt; {
        if (entries[0].isIntersecting &amp;&amp; entries[0].intersectionRatio &gt;= 1) {
          setPage((prevPage) =&gt; prevPage + 1);
        }
      },
      { threshold: 1 }
    );
    if (lastPostRef.current) {
      // 데이터가 불러와지기 전에 실행하면 안되니까 lastPostRef.current가 있을때로 조건 생성
      observer.current.observe(lastPostRef.current);
    }
  }, [allPosts]);</code></pre>
<p>관찰 중일때 page 값이 1 올라가게 작성되어있다.
이런 흐름으로 무한 함수 호출이 된거 같은데</p>
<ol>
<li>컴포넌트 렌더링시 <code>getAllPosts</code> 호출로 게시글 불러옴 </li>
<li>불러온 데이터의 가장 마지막 데이터(디비의 마지막 데이터가 아니라 size 파람을 기준으로 잘라서 가져온 데이터들 중 마지막)에 있는 ref를 기준으로 관찰을 시작</li>
<li>대상이 관찰되면(뷰표트에 잡히면) 콜백함수 실행 = page 값 1 증가</li>
<li>page 값 변경으로 인하여 <code>getAllPosts</code> 실행됨</li>
<li><code>getAllPosts</code> 실행 == 서버랑 통신해서 데이터 받아옴 </li>
<li>이 과정을 반복하다가 <strong>서버 db의 마지막 데이터까지 줬을때 끝나지 않고 또 모든 과정을 반복 반복 반복...</strong></li>
</ol>
<p>서버랑 통신하는 함수에서 더 줄 데이터가 없을 때(response.data의 length가 size 보다 작을 때) 콘솔에 <code>page</code>와 <code>더 이상 가져올 데이터 없음</code>을 추가했더니 아래와 같이 무한 page 값 증가와 함께 콘솔이 무한으로 찍힌다 ㅎㅎ</p>
<p><img src="https://velog.velcdn.com/images/doh_0112/post/bf64e2e0-7105-43d7-980d-90568895abf4/image.png" alt=""></p>
<h2 id="2-문제-해결">2. 문제 해결</h2>
<p>더 이상 받아올 데이터가 없을 때는 서버와 통신을 멈추면 된다!
라는 개념을 가지고 이 문제를 해결했다.</p>
<h3 id="1-데이터를-더-받아올지에-대한-state-생성">1. 데이터를 더 받아올지에 대한 state 생성</h3>
<pre><code class="language-tsx">  // 더 이상 불러올 데이터가 있는지 표시하는 상태
  const [hasMore, setHasMore] = useState(true);
</code></pre>
<h3 id="2-데이터-없을-때-조건-생성">2. 데이터 없을 때 조건 생성</h3>
<p>서버와 통신하는 함수인 mutateAllPosts 에서 아래와 같이 조건을 생성해줌</p>
<pre><code class="language-tsx">// 전체 게시글 조회 뮤테이션 함수
  const { mutate: mutateAllPosts } = useMutation(fetchAllPosts, {
    onSuccess: (response) =&gt; {
      if (response.statusCode === 200) {
        setAllPosts((prevPosts) =&gt; [...prevPosts, ...response.data]);

        // 더 이상 가져올 데이터 없음
        if (response.data.length &lt; size) {
          setHasMore(false);
        }
      }
    },
    onError: (error) =&gt; {
      console.log(error);
    },
  });</code></pre>
<p>그러니까 서버에서 받아온 데이터가 내가 정한 size(서버에 통신할 때 데이터를 몇 개 받아올지 지정하는 파람값) 개수보다 적으면 더 이상 받아올 데이터가 없다고 판단-&gt; hasMore 값을 false로 변경해준다.</p>
<h3 id="3-mutateallposts-실행시키는-함수에-조건-생성">3. mutateAllPosts 실행시키는 함수에 조건 생성</h3>
<p>mutateAllPosts를 실행시키는 함수에서 통신을 더 할지 말지를 결정하는 hasMore 값이 false일때는 더 이상 mutateAllPosts 함수를 실행시키지 않는 조건을 추가한다.</p>
<pre><code class="language-ts">  // 전체 게시글 불러오기
  const getAllPosts = () =&gt; {
    // 더 이상 불러올 데이터가 없다면 종료
    if (!hasMore) return;
    mutateAllPosts({ page, size });
  };
</code></pre>
<h2 id="해결">해결</h2>
<p><img src="https://velog.velcdn.com/images/doh_0112/post/035d6ced-ee82-42e2-87f7-6351884be8b9/image.png" alt="">
위와 같이 가장 마지막 데이터에 스크롤이 교차했을 때, 한 번의 콘솔만 출력되고 더 이상 서버에 데이터를 요청하는 함수가 실행되지 않는다! ㅎㅎ
서버에서 받아온 데이터가 더 이상 없을 때 요청을 멈추는 추가 로직을 생각하지 못해서 생긴 에러인 것 같다!</p>
]]></description>
        </item>
    </channel>
</rss>