<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Astro-JtFox</title><description>A modern blog template built with Astro and SolidJS. Fast performance, SEO optimized, and developer-friendly platform with integrated search functionality.</description><link>https://jtfox.dev/</link><language>en</language><item><title>SSR와 CSR에 대해</title><link>https://jtfox.dev/blog/2022-04-13/</link><guid isPermaLink="true">https://jtfox.dev/blog/2022-04-13/</guid><content:encoded>import { Image } from &quot;astro:assets&quot;;
import SSR from &quot;public/images/blog/2022-04-13/SSR.png&quot;;
import CSR from &quot;public/images/blog/2022-04-13/CSR.png&quot;;

# SSR (Server Side Rendering)

SSR은 서버에서 사용자에게 보여줄 페이지를 모두 구성하여 사용자에게 페이지를 보여주는 방식이다.  
쉽게 말해서 서버에서 렌더링할 준비가 다 되면 HTML을 브라우저에게 응답해준다.

&lt;Image src={SSR} alt=&quot;SSR&quot; /&gt;
출처:
https://medium.com/walmartglobaltech/the-benefits-of-server-side-rendering-over-client-side-rendering-5d07ff2cefe8

# CSR (Client Side Rednering)

CSR은 JavaScript가 다운로드 및 실행될 때까지 기다릴 필요 없이 브라우저가 서버에서 HTML 렌더링을 시작한다.

&lt;Image src={CSR} alt=&quot;CSR&quot; /&gt;
출처:
https://medium.com/walmartglobaltech/the-benefits-of-server-side-rendering-over-client-side-rendering-5d07ff2cefe8

# SSR vs CSR

- SSR의 경우는 서버에서 페이지를 모두 구성하여 사용자에게 보여주기 때문에 초기 페이지 구성이 느리지만 전체적으로 사용자에게 보여주는 콘텐츠 구성이 완료되는 시점은 빨라진다.

- CSR의 경우엔 클라이언트에서 페이지를 렌더링하기 때문에 빠른 속도로 초기 페이지를 볼 수 있지만 실제로 (React, Vue) 등이 실행이 완료되기 전까지 페이지와 상호작용할 수 없고, 서비스에 필요한 데이터를 클라이언트(브라우저)에서 추가로 요청하여 재구성해야 하기 때문에 전체적인 페이지 완료 시점은 SSR보다 느려진다.

- CPU의 처리량은 SSR이 CSR처리량보다 훨씬 적다.

- SSR을 사용하면 전체 페에지가 올바른 메타데이터로 컴파일 되어 프론트 엔드로 전송된다.  
  하지만, CSR를 사용하면 사이트의 콘텐츠가 JS를 통해 자동으로 생성된다.  
  어떤 페이지에서 다른 페이지로 이동할 때 메타데이터를 변경하는 것이 JS 실행에 의존하기 때문에 각 페이지에 대한 메타데이터를 설정하여 클라이언트에서 렌더링 되도록 해야 한다.  
  즉, SSR 방식이 CSR보다 SEO 이용이 수월하다.

## 어떤거로 개발해야 할까?

위에 글을 읽고 CSR와 SSR에 대해 알았다면 드는 생각은 하나다.  
`&quot;CSR의 빠른 초기 페이지 로드와 SSR 성능을 조합해서 쓰는 방법이 없을까?&quot;`

방법이 존재한다. Next.js라는 웹 프레임워크를 사용하는 것.  
해당 프레임워크는 Pre-rendering HTML 방식을 이용하며 SSR과 CSR 방식을 혼합하여 사용하고 있다.</content:encoded><category>Web</category><author>jt_fox</author></item><item><title>WebPack 그리고 babel</title><link>https://jtfox.dev/blog/2022-04-14/</link><guid isPermaLink="true">https://jtfox.dev/blog/2022-04-14/</guid><content:encoded>import { Image } from &quot;astro:assets&quot;;
import webpack from &quot;public/images/blog/2022-04-14/webpack.png&quot;;

React로 개발을 하다보면, Webpack 그리고 babel이란 단어를 심심치 않게 접하게 된다. 오늘은 Webpack과 babel의 개념에 대해서 알아보려고 한다.

## 웹팩 등장 전

아래와 같이 script를 바디에서 호출하는 식으로 사용했다.
이런 경우 문제점은 전역 스코프가 오염되어 예측할 수 없게 된다.

```js
  &lt;script type=&quot;module&quot; src=&quot;src/math.js&quot;&gt;&lt;/script&gt;
  &lt;script type=&quot;module&quot; src=&quot;src/app.js&quot;&gt;&lt;/script&gt;
```

## 모듈 스팩

### CommonJS

`CommonJS`는 자바스크립트를 사용하는 모든 환경에서 모듈을 하는 것이 목표로 export 키워드로 모듈을 만들고 require()함수로 불러 들이는 방식이다. NodeJs에서 이를 사용한다.

`math.js`:

```
exports function sum(a, b) {return a+b; }
```

`app.js`:

```
const sum = require(&apos;./math.js&apos;);
sum(1, 2); //3
```

### AMD

브라우저 처럼 비동기로 로딩되는 환경에서 모듈을 사용하는 목표이다.

### UMD

AMD기반으로 CommonJS 방식까지 지원하는 통합 형태이며, React나 바벨 웹팩을 이용했다면 친숙할 수 있는 형태이다.

`math.js`:

```
export function sum(a, b) {return a+b; }
```

`app.js`:

```
import * as math from &apos;./math.js&apos;
math.sum(1, 2); //3
```

## WebPack

---

Webpack 공식 Document의 정의는 `JavaScript 애플리케이션을 위한 정적 모듈 번들러` 으로 표기되어 있다.

여기서 말하는 정적 모듈 번들러가 뭘까?
먼저 여기서 말하는 모듈은 우리가 npm, yarn등으로 생성하는 node_modules와 Javascript, 이미지, Css 등을 패키징하여 재사용 가능하게 만드는 코드 덩어리이다. 그리고 번들링은 저 모듈들을 하나의 파일로 묶게 되는 것이다.

&lt;Image src={webpack} alt=&quot;webpack&quot; /&gt;
출처: https://webpack.js.org/

### webpack의 장점

1. 성능 최적화
   이렇게 모듈을 번들링하여 묶어서 얻는 장점이 무엇일까? 우리가 웹 페이지를 요청할 때 html, css, js 파일 등의 여러가지 파일을 요청하게 된다 이러한 요청을 WebPack을 이용해 요청의 수를 획기적으로 줄일 수 있다! 그러면 자원이나 성능에서 이점을 가져갈 수 있게 된다.

2. 브라우저의 모듈 지원
   위에 설명한 UMD이나 CommonJS를 지원하지 않는 브라우저도 있기 때문에 브라우저 무관하게 모듈을 사용할 때 웹팩을 이용해 사용할 수 있다.

### entry/output (엔트리/아웃풋)

`entry`: 모듈의 시작점을 지정한다. (모든 의존성이 시작되는 부분)
`output`: 번들링이 진행되고, 코드가 저장될 위치

### loader (로더)

웹팩에선 모든 파일을 모듈로 바라본다. 자바스크립트 뿐만 아니라 스타일시트, 이미지, 폰트까지도 모두 import 구문을 사용해 자바스크립트 코드 안으로 가져올 수 있다.

이것을 가능하게 해주는 것이 웹팩의 로더 이다. 로더는 타입 스크립트 같은 다른 언어를 자바스크립트 문법으로 변호나해 주거나 이미지를 data URL 형식의 문자열로 변환시킨다.

**자주 사용되는 로더**

- `css-loader`: css를 js로 변환하기 위한 로더

- `style-loader`: js로 처리된 css코드를 html에 주입시키는 역할을 하는 로더

- `file-loader`: 이미지 파일을 모듈 파일로 사용할 수 있게 하는 로더

- `url-loader`: 이미지 파일을 dataUrl 형식으로 변환시키는 로더

### plugin (플러그인)

플러그인은 로더가 파일 단위로 처리하는 반면 플러그인은 번들링된 결과물을 처리한다.

**자주 사용되는 플러그인**

- `BannerPlugin`: 빌드 시간, 정보, 버전 등을 맨 상단에 배너 형식으로 넣기 위한 용도

- `DefinePlugin`: 환경 의존적인 정보들을 관리하기 위한 플러그인

- `HtmlTemplatePlugin`: HTML의 파일을 빌드에 포함시킬 때 사용하는 플러그인

- `clean-webpack-plugin`: 빌드 시마다 기존의 output 폴더를 삭제하고 다시 생성해주는 플러그인

- `MiniCssExtractPlugin`: 스타일 코드만 뽑아서 별도의 CSS 파일로 만들어 파일을 분리하게 할 수 있다.

## webpack 설정해보기

### webpack-cli install / start

webpack과 cli 명령어로 실행할 수 있게 해주는
webpack-cli을 함께 설치해준다.

```powershell
npm install -D webpack webpack-cli
```

`webpack`을 실행하기 위해서는 필수적으로 3가지의 옵션을 설정해야한다.

- `mode`: &quot;development&quot;, &quot;production&quot;, &quot;none&quot; 세 가지가 있으며 실행하는 환경에 따라 지정해주면 된다.
- `entry`: 모듈의 시작점
- `output-path`: 번들링 된 파일이 만들어지는 위치

아래와 같이 실행하게되면 `dist`폴더에 main.js라는 번들링 된 파일이 생기게 된다.

```powershell
node_modules/.bin/webpack --mode development --entry ./src/app.js --output-path dist/main.js
```

번들링한 파일을 html 파일에서 로드해서 사용하면 된다. (type 지정하지 않아도 됨)

`index.html`:

```html
&lt;body&gt;
  &lt;script src=&quot;dist/main.js&quot;&gt;&lt;/script&gt;
&lt;/body&gt;
```

### webpack.config.js

매번 cli로 옵션을 설정하기 힘드니 webpack.config.js 파일을 생성 해 관리해주면 된다.

먼저 아래와 같이 package.json 파일에 script를 만들어주고

`package.json`:

```json
  &quot;build&quot;: &quot;webpack&quot;
```

웹팽 파일 설정은 아래와 같이 해준다.

`webpack.config.js`:

```js
const path = require(&quot;path&quot;);

module.exports = {
  mode: &quot;development&quot;,
  entry: {
    main: &quot;./src/app.js&quot;,
  },
  output: {
    path: path.resolve(&quot;./dist&quot;),
    // 여러개의 entry를 지정했을 때 동적으로 이름을 할당
    filename: &quot;[name].js&quot;,
  },
};
```

## babel

WebPack을 이용할 때 babel의 개념에 대해 알아 둘 필요가 있다.

ES6 클래스와 IMPORT문은 모든 브라우저에서 적용되진 않는다. 모든 브라우저에서 코드를 읽을 수 있도록 하려면 ES6의 기능을 ES5코드로 변환시켜주는 트랜스 파일링(작성한 코드를 다른 언어로 변환)이 필요하다.

바벨은 세 단계로 빌드를 진행한다.

1. Parsing - 코드를 토큰으로 하나씩 분해하여 추상 구문 트리(AST)로 변환한다..
2. Transforming - ES6 -&gt; ES5로 변환
3. Printing - 변경된 결과를 결과 출력

그리고 바벨은 보통 두 가지로 설정을 하게되는데 plugin과 preset이다.

### plugins

플러그인을 코드를 ES6을 ES5로 변경해주는 코드인데 예를들어
아래와 같이 ES5에서 지원하지 않는 arrow function을 ES5로 바꾸기 위해 `@babel/plugin-transform-arrow-functions` 플러그인을 설치해 설정파일에 넣어주면 된다.

```js
// ES6
const sum = (a, b) =&gt; {
  return a + b;
};

// ES5
var sum = function (a, b) {
  return a + b;
};
```

### preset

위에서처럼 플러그인을 하나 하나 넣기에는 무리가 있기 때문에 플러그인을 모아서 사용하는 경우가 바로 `preset`이다.

### polyfill

지원하지 않은 웹 브라우저 상의 기능을 구현하는 코드

(ES6 - Map, Promise 등)을 사용가능하게 구현이 누락된 새로운 기능을 메꿔주는 역할

쉽게말해서 ES6 에서 ES5로 변환되지 않는 것들을 추가적인 코드조각을 추가해서 해결하는 것이다.

대표적인 env preset에선 아래와 같이 설정할 수 있다.
`corejs`라는 polyfill을 사용한 것이다.

`babel.config.js`:

```js
module.exports = {
  presets: [
    [
      &quot;@babel/preset-env&quot;,
      {
        targets: {
          chrome: &quot;79&quot;,
          ie: &quot;11&quot;,
        },
        useBuiltIns: &quot;usage&quot;,
        corejs: {
          version: 2,
        },
      },
    ],
  ],
};
```

### WebPack 과 babel

WebPack이 모듈을 번들링할 때 Babel을 사용하여 트랜스 파일링 시킬 수 있다.

웹펙에선 babel을 loader로 사용된다.

1. 바벨 로더 / core-js 설치

```ps
npm i babel-loader core-js@2
```

2. 웹펙 파일 설정

사용법은 굉장히 간단한데 설치한 로더를 다른 로더 사용하듯이 추가해주면 된다.

exclude는 자바스크립트가 불러오는 node_module 내에 모듈까지도 바벨이 적용되지 않도록 방지하는 것이다.

`webpack.config.js`:

```js
module.exports = {
  mode: &quot;development&quot;,
  entry: {
    main: &quot;./app.js&quot;,
  },
  output: {
    path: path.resolve(&quot;./dist&quot;),
    filename: &quot;[name].js&quot;,
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        loader: &quot;babel-loader&quot;,
        exclude: /node_modules/,
      },
    ],
  },
};
```</content:encoded><category>Web</category><category>webpack</category><category>babel</category><author>jt_fox</author></item><item><title>DOM (Document Object Model) 과 브라우저 렌더링</title><link>https://jtfox.dev/blog/2022-04-18/</link><guid isPermaLink="true">https://jtfox.dev/blog/2022-04-18/</guid><content:encoded>import { Image } from &quot;astro:assets&quot;;
import myImage from &quot;public/images/blog/2022-04-18/DOM.png&quot;;

MDN에서의 정의는 `DOM은 웹 문서용 프로그래밍 인터페이스이다.`  
그리고 다음 문장이 DOM의 핵심이다.
`DOM은 문서의 구조화된 표현(structured representation)을 제공하며 프로그래밍 언어가 DOM 구조에 접근할 수 있는 방법을 제공한다.`

쉽게 말해서 우리가 아래 코드처럼 html에 있는 element 객체를 접근해 스타일을 변경하거나 내용을 변경할 수 있게 해주는 역할을 한다.

```javascript
document.querySelector(&quot;.css&quot;);
```

위에 글을 읽으면 이런 생각이 든다 그럼 DOM = HTML인가?
아니다! HTML은 단순히 텍스트로 구성되어 있고 DOM은 HTML 문서의 내용의 구조가 객체 모델로 변환되어 &quot;노드 트리&quot; 개체 구조로 표현된다.
아래는 그 예시다.

`HTML`

```html
&lt;html&gt;
  &lt;head&gt;
    &lt;title&gt;타이틀 내용&lt;/title&gt;
  &lt;/head&gt;
  &lt;body&gt;
    &lt;h1&gt;내용&lt;/h1&gt;
    &lt;h2&gt;내용&lt;/h2&gt;
  &lt;/body&gt;
&lt;/html&gt;
```

`DOM`
&lt;Image src={myImage} alt=&quot;DOM 노드트리&quot; /&gt;
(출처: 나)

## DOM과 HTML

### DOM의 수정

```javascript
var newitem = document.createElement(&quot;div&quot;);
document.body.appendChild(newitem);
```

위 코드처럼 자바스크립트로 DOM의 새로운 노드를 추가할 수 있다.
위 코드는 DOM을 업데이트하지만 HTML 문서의 내용을 변경하진 않는다.

### DOM은 렌더링 하지 않는다.

브라우저 뷰 포트에 보이는 것은 렌더 트리이다.
렌더 트리에선 오직 렌더링 되는 요소만 관련 있기 때문에 시각적으로 보이지 않는 요소는 제외된다.

```javascript
&lt;div style=&quot;display:none&quot;&gt;&lt;/div&gt;
```

위와 같은 코드에서 DOM은 `div` 요소를 포함하지만, 렌더 트리에선 포함하지 않는다.

# 브라우저 렌더링

브라우저는 사용자가 선택한 자원을 서버에 요청하고 브라우저에 표시한다.

브라우저가 서버로부터 페이지에 대한 자원을 받으면 화면을 그리기 전에 여러 단계를 거친다.

## 1. DOM 트리 구축을 위한 HTML 파싱

브라우저의 렌더링 엔진에서 HTML 문서를 파싱하고
DOM 노드로 변환하고, CSS 파일과 함께 스타일 요소 또한 CSSOM으로 변환된다.
`CSSOM: DOM과 관련된 스타일의 객체 표현 이 또한 노드 형식임`

## 2. 렌더트리 생성

DOM과 CSSOM의 조합이며, 페이지에 최종적으로 렌더링 될 내용을 나타내는 트리이다.

## 4. javascript 실행 (HTML 중간에 스크립트가 있다면 HTML 파싱이 중단된다.)

자바스크립트 파일들이 실행되며 만약 HTML을 파싱 중에 스크립트를 마주하게되면 스크립트를 실행 한 뒤 다시 파싱이 시작된다.

위와 같은 요소들을 `블록 리소스`라고 하는데 블록 리소스는 브라우저 로딩 단계 중 페인트 과정을
지연시킴으로 스크립트를 임포트하는 위치를 CSS는 `&lt;head&gt;` 태그 내에 js를 실행시키는 `&lt;script&gt;` 태그는 `&lt;body&gt;` 태그의 맨 하단에 위치시키는 것이 좋다.

```html
&lt;head&gt;
  &lt;link href=&quot;style.css&quot; rel=&quot;stylesheet&quot; /&gt;
  &lt;body&gt;
    &lt;script&gt;
    &lt;script&gt;
  &lt;/body&gt;
&lt;/head&gt;
```

## 3. 뷰포트 기반으로 렌더트리의 각 노드가 가지는 위치와 크기를 계산 (Layout/Reflow 단계)

레아이아웃은 뷰포트의 크기를 걸정하는 것으로 표시 영역 크기는 Html의 Head 테그의 정의되는 meta 태그에 의해 결정된다.
아래는 그 예이다.

```html
&lt;meta name=&quot;viewport&quot; content=&quot;width=device-width,initial-scale=1&quot; /&gt;
```

## 4. 계산한 위치/크기를 기반으로 화면에 그림 (Paint 단계)

마지막으로 렌더트리를 이용해 페이지의 UI를 그리게 된다.</content:encoded><category>Web</category><author>jt_fox</author></item><item><title>Remix로 Solidity 로컬 개발 환경 만들기</title><link>https://jtfox.dev/blog/2022-05-02/</link><guid isPermaLink="true">https://jtfox.dev/blog/2022-05-02/</guid><content:encoded>import { Image } from &quot;astro:assets&quot;;
import remix from &quot;public/images/blog/2022-05-02/remix.png&quot;;
import remix2 from &quot;public/images/blog/2022-05-02/remix2.png&quot;;
import remix3 from &quot;public/images/blog/2022-05-02/remix3.png&quot;;
import remix4 from &quot;public/images/blog/2022-05-02/remix4.png&quot;;

Solidity로 개발을 하다보니 리믹스에서 git을 사용하기 좀 껄끄러운 것도 있고, github로 소스를 관리하다보니 테스트할 때마다 Remix에 매번 복붙해서 배포하는 것도 귀찮아 찾아보니 로컬로 Remix에 WebSoket과 연결해 동기화 해 개발하는 방식이 있더라!

먼저, Solidity 파일이 있는 폴더로 이동해서

`npm으로 remix 설치`

```powershell
npm install @remix-project/remixd
```

그 후에 remix를 실행시켜주면 된다.  
`remix start`

```
remixd -s . —remix-ide https://remix.ethereum.org
```

성공했다면 아래 이미지와 같이 터미널에 나타난다.

&lt;Image src={remix} alt=&quot;remix&quot; /&gt;

아래는 나타나는 웹 소켓에 대한 포트 설명이다.

- 65520 Port - Remix IDE와 파일 시스템 공유하며, Remix IDE에서 Localhost로 로드할 수 있다.

- 65523 Port - slither라는 Contract 세부 정보를 사용자에게 제공하여 경고를 보고 소스를 수정할 수 있게 해주는 플러그인을 활성화하는 포트이다.

그리고 Remix로 이동해서 workspace를 아래 사진과 같이 선택해주면

&lt;Image src={remix2} alt=&quot;remix2&quot; /&gt;

요렇게 local에 있는 파일들이 Remix로 동기화 된다!

&lt;Image src={remix4} alt=&quot;remix4&quot; /&gt;

그리고 Deploy 탭으로 가서 확인해보면 아래와 같이 100ether씩 들어간 account가 10개 있다 이 계정들로 deploy해서 테스트를 해볼 수 있다~

&lt;Image src={remix3} alt=&quot;remix3&quot; /&gt;</content:encoded><category>BlockChain</category><category>Ethereum</category><category>Solidity</category><category>Remix</category><author>jt_fox</author></item><item><title>Recoil vs Redux</title><link>https://jtfox.dev/blog/2022-05-03/</link><guid isPermaLink="true">https://jtfox.dev/blog/2022-05-03/</guid><content:encoded>import { Image } from &quot;astro:assets&quot;;
import redux from &quot;public/images/blog/2022-05-03/redux.png&quot;;
import flux from &quot;public/images/blog/2022-05-03/flux.png&quot;;

# Recoil vs Redux

React는 보통 State 관리 도구로 Redux, MobX 등의 라이브러리를 사용했다. 근데 이번에 NextJs로 프로젝트를 시작하면서 상태관리를 어떤 라이브러리를 사용할까 하다가 페이스북에서 React.Js 전용 상태관리 라이브러리로 출시한 Recoil 선택하게 되었다!
그래서 이번엔 Redux와 Recoil을 비교해가며 왜 Recoil을 선택 했는지 알아보자

## Redux

&lt;Image src={redux} alt=&quot;redux&quot; /&gt;

### Flux

Redux는 Flux 디자인 패턴을 기반으로 제작되어있다.

&lt;Image src={flux} alt=&quot;flux&quot; /&gt;
위와 같이 `Dispatcher, Action, Store, View`로 이루어져 있으며, `단방향 데이트
흐름`을 가지고 있다. 모든 데이터는 디스패처를 통해 중앙 허브로 흐르며, View에서
상호작용이 일어나면 Action을 Dispatch을 통해 스토어로 전달하여 데이터를
변경하고, 상위 구성요소 트리에서 자신과 모든 하위 항목을 다시 렌더링하는
흐름이다.

그렇다고 `Redux == Flux는 아니다` 가장 큰 차이점은 Flux는 여러 개의 Store를 가지고 있지만, Redux는 단일 스토어만을 가지고 있다. 그리고 Redux는 Reducer라는 순수 함수를 여러개 두어 state를 변경한다.

## Recoil

먼저 Recoil을 처음 접하고 생각 든 것은 굉장히 `코드가 간결하고 간단하다`였다. Redux는 처음 싲가하려면 많은 보일러 플레이트과 코드를 작성해야하고, `비동기 데이터 처리`나 `캐시`와 같은 기능은 redux-saga 따위를 사용하는 불편함이 있었다.

Recoil은 이미 React에서 제공하는 Hook과 항상 유사한 형태이다. 그래서 많은 코드 양을 만들지 않고 사용할 수 있고, selector문을 통해서 비동기 데이터 흐름을 위한 솔루션도 제공한다.

### Atom

Recoil의 가장 기본 단위이다. 컴포넌트에서 Import하여 사용할 State라고 생각하면 될 것 같다.

atom 값을 변경하면 해당 값을 구독하고 있는 컴포넌트들이 모두 재렌더링 된다.

```tsx
export const titleState = atom&lt;strring&gt;({
  key: &quot;title_State&quot;,
  default: &quot;제곧네&quot;,
});
```

### useRecoilState, useRecoilValue, useSetRecoilState

- useRecoilState - atom 값을 굳고하여 업데이트할 수 있는 hook이며, useState와 동일하게 사용할 수 있다.
- useRecoilValue - atom 값만을 반환한다.
- useSetRecoilState - atom 값을 변경하는 setter 함수를 반환한다.

```tsx
import { useRecoilState } from &quot;recoil&quot;;
import { titleState } from &quot;./atomfile&quot;;
const [title, setTitle] = useRecoilState&lt;string&gt;(titleState);
const title = useRecoilValue&lt;string&gt;(titleState);
const setTitle = useSetRecoilState&lt;string&gt;(titleState);
```

### Selector

atom에 의존하는 동적인 데이터 만들 수 있게 해준다. get 함수를 통해 반환하며, set 함수 또한 사용할 수 있다. selector에 영향을 주는 atom들의 값을 변경할 수도 있다는 것이다!

```tsx
const fontSizeState = atom&lt;int&gt;({
  key: &quot;fontSizeState&quot;,
  default: 14,
});

const fontSizeLabelingState = selector({
  key: &quot;fontSizeLabelingState&quot;,
  get: ({ get }) =&gt; {
    const fontsize = get(fontSizeState);
    return `${fontsize}px`;
  },
});
```

## 결론

1. Recoil이 Redux보다 사용하기 굉장히 간단하다.
2. 순수 함수를 사용해 비동기 테이터를 쿼리할 수 있다.
3. UseState와 동일하게 hook을 사용해 store를 컨트롤할 수 있다. Dispatcher 와 action 등의 코딩을 할 필요가 없기때문에 깔끔한 코드를 구성할 수 있다.
4. 보일러 플레이트가 없다.
5. Recoil은 아직 베터라 Redux는 확립되어 있기 때문에 안정성 면에선 Redux가 우위에 있다.

내가 생각했을 때 지금 Recoil을 사용하지 않을 이유는 없다고 생각한다. 하지만, 대규모 프로젝트에선 안정성이 중요하기 때문에 위험을 감수하며 사용할진 잘 모르겠다.</content:encoded><category>React</category><category>Recoil</category><category>Redux</category><category>State</category><author>jt_fox</author></item><item><title>Smart Contract으로 ERC-20 토큰 만들기</title><link>https://jtfox.dev/blog/2022-05-10/</link><guid isPermaLink="true">https://jtfox.dev/blog/2022-05-10/</guid><content:encoded>import { Image } from &quot;astro:assets&quot;;
import erc1 from &quot;public/images/blog/2022-05-10/erc1.png&quot;;
import erc2 from &quot;public/images/blog/2022-05-10/erc2.png&quot;;
import erc3 from &quot;public/images/blog/2022-05-10/erc3.png&quot;;
import erc4 from &quot;public/images/blog/2022-05-10/erc4.png&quot;;
import erc5 from &quot;public/images/blog/2022-05-10/erc5.png&quot;;

# ERC-20

ERC-20(Ethereum Request for Commnet 20)은 EIP(Ethereum Improvement Propasls)에서 관리하는 공식 프로토콜 중 하나로 Ethereum네트워크에서 유통 가능한 토큰의 표준이다. 요약하자면, 우리가 흔히 부르는 암호화폐 또는 코인이 여기에 해당된다.

# Contract 작성 전에

제일 먼저 이전에 포스팅 한 [Remix로 Solidity 로컬 개발 환경 만들기](/blog/2022-05-02/)을 읽고 해당 개발환경을 갖추고 오길 바란다. 이 글은 해당 환경에서 진행된다.

그다음으론 `OpenZepplin`을 이용해 ERC20 SmartContract을 구현할 것이다.  
그러니 OpenZepplin을 npm을 통해 다운받아주자

```powershell
npm install @openzeppelin/cli
```

그 다음으론 rinkeby 테스트 넷에서 배포 후 테스트 해볼 것이기 때문에 rinkeby faucet 사이트에 들어가서 test ether를 받는다.  
이 사이트에 들어가서 본인이 사용하는 지갑의 주소를 넣어주면 된다. (참고로 잘 안준다... 열심히 노가다 해야된다.)

- https://faucet.rinkeby.io/
- https://faucets.chain.link/rinkeby
- https://rinkebyfaucet.com/

자 이제 준비는 다 됐다! Smart Contract를 작성해보자

# ERC20 토큰 소스 분석

배포하는 토큰을 잘 사용하기 위해선 ERC20의 소스를 살펴볼 필요가 있다.

`ERC20.sol`

```solidity
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/ERC20.sol)

pragma solidity ^0.8.0;

import &quot;./IERC20.sol&quot;;
import &quot;./extensions/IERC20Metadata.sol&quot;;
import &quot;../../utils/Context.sol&quot;;

contract ERC20 is Context, IERC20, IERC20Metadata {
    mapping(address =&gt; uint256) private _balances;

    mapping(address =&gt; mapping(address =&gt; uint256)) private _allowances;

    uint256 private _totalSupply;

    string private _name;
    string private _symbol;

    constructor(string memory name_, string memory symbol_) {
        _name = name_;
        _symbol = symbol_;
    }

    function name() public view virtual override returns (string memory) {
        return _name;
    }

    function symbol() public view virtual override returns (string memory) {
        return _symbol;
    }

    function decimals() public view virtual override returns (uint8) {
        return 18;
    }

    function totalSupply() public view virtual override returns (uint256) {
        return _totalSupply;
    }

    function balanceOf(address account) public view virtual override returns (uint256) {
        return _balances[account];
    }

    function transfer(address to, uint256 amount) public virtual override returns (bool) {
        address owner = _msgSender();
        _transfer(owner, to, amount);
        return true;
    }

    function allowance(address owner, address spender) public view virtual override returns (uint256) {
        return _allowances[owner][spender];
    }

    function approve(address spender, uint256 amount) public virtual override returns (bool) {
        address owner = _msgSender();
        _approve(owner, spender, amount);
        return true;
    }

    function transferFrom(
        address from,
        address to,
        uint256 amount
    ) public virtual override returns (bool) {
        address spender = _msgSender();
        _spendAllowance(from, spender, amount);
        _transfer(from, to, amount);
        return true;
    }

    function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {
        address owner = _msgSender();
        _approve(owner, spender, allowance(owner, spender) + addedValue);
        return true;
    }

    function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {
        address owner = _msgSender();
        uint256 currentAllowance = allowance(owner, spender);
        require(currentAllowance &gt;= subtractedValue, &quot;ERC20: decreased allowance below zero&quot;);
        unchecked {
            _approve(owner, spender, currentAllowance - subtractedValue);
        }

        return true;
    }

    function _transfer(
        address from,
        address to,
        uint256 amount
    ) internal virtual {
        require(from != address(0), &quot;ERC20: transfer from the zero address&quot;);
        require(to != address(0), &quot;ERC20: transfer to the zero address&quot;);

        _beforeTokenTransfer(from, to, amount);

        uint256 fromBalance = _balances[from];
        require(fromBalance &gt;= amount, &quot;ERC20: transfer amount exceeds balance&quot;);
        unchecked {
            _balances[from] = fromBalance - amount;
        }
        _balances[to] += amount;

        emit Transfer(from, to, amount);

        _afterTokenTransfer(from, to, amount);
    }

    function _mint(address account, uint256 amount) internal virtual {
        require(account != address(0), &quot;ERC20: mint to the zero address&quot;);

        _beforeTokenTransfer(address(0), account, amount);

        _totalSupply += amount;
        _balances[account] += amount;
        emit Transfer(address(0), account, amount);

        _afterTokenTransfer(address(0), account, amount);
    }
    function _burn(address account, uint256 amount) internal virtual {
        require(account != address(0), &quot;ERC20: burn from the zero address&quot;);

        _beforeTokenTransfer(account, address(0), amount);

        uint256 accountBalance = _balances[account];
        require(accountBalance &gt;= amount, &quot;ERC20: burn amount exceeds balance&quot;);
        unchecked {
            _balances[account] = accountBalance - amount;
        }
        _totalSupply -= amount;

        emit Transfer(account, address(0), amount);

        _afterTokenTransfer(account, address(0), amount);
    }

    function _approve(
        address owner,
        address spender,
        uint256 amount
    ) internal virtual {
        require(owner != address(0), &quot;ERC20: approve from the zero address&quot;);
        require(spender != address(0), &quot;ERC20: approve to the zero address&quot;);

        _allowances[owner][spender] = amount;
        emit Approval(owner, spender, amount);
    }

    function _spendAllowance(
        address owner,
        address spender,
        uint256 amount
    ) internal virtual {
        uint256 currentAllowance = allowance(owner, spender);
        if (currentAllowance != type(uint256).max) {
            require(currentAllowance &gt;= amount, &quot;ERC20: insufficient allowance&quot;);
            unchecked {
                _approve(owner, spender, currentAllowance - amount);
            }
        }
    }

    function _beforeTokenTransfer(
        address from,
        address to,
        uint256 amount
    ) internal virtual {}

    function _afterTokenTransfer(
        address from,
        address to,
        uint256 amount
    ) internal virtual {}
}
```

함수를 하나씩 알아보자! 먼저 IERC20, IERC20metadata, Context 을 상속받고 있으며
IERC20 인터페이스를 기반으로 만들어져 있다.

## 변수

| 이름          | 설명                                                                                    |
| ------------- | --------------------------------------------------------------------------------------- |
| \_balances    | 키값으로 주어지는 address 기준으로 해당 토큰 보유량을 반환하기 위한 매핑 변수이다.      |
| \_allowances  | 키값으로 주어지는 다른 주소가 spender에게 approve한 토큰의 수를 확인하는 매핑 변수이다. |
| \_totalSupply | 총 발행량                                                                               |
| \_name        | 토큰명                                                                                  |
| \_symbol      | 토큰 심볼 (ex) ETH)                                                                     |

## Return 함수

### name

토큰 이름을 반환한다.

### decimal

기본값은 18이다. 이더리움은 단 wei이다 1Ether가 되기 위한 wei의 개수가 10^18이라
기본값이 18로 되어있다.

Gwei로 가스 비용을 표현하는데 0.00000~~1 이라 말하는거보다 1gwei이다 10gwei이다 이런식으로 환산해서 말하고 표현한다.

보통 배포할 때 사용되며 필자는 deploy시에 (10 \*\* decimal)을 곱해 코드를 단순하게 사용한다. (송금 및 토큰 스왑시에도 사용할 수 있는 개념이다)

### totalSupply

인자로 받은 주소의 토큰 보유량을 반환한다.

### allowance

spender에게 승인해준 amount 값을 반환한다.

## Active 함수

### Approve

spender에게 amount만큼의 토큰을 사용할 수 있게 승인해주는 함수다.  
보통 spneder는 SmartContact 주소가 들어가게된다.
SmartContract에서 토큰을 사용하는 계약을 실행하기 전에 해주지 않으면 토큰을 송금할 수 없기 때문에 애꿎은 가스비만 날라가고 실행되지 않을 수 있다 꼭 실행해 줘야 한다.

### transfer

to 파라미터로 값의 주소로 amount 만큼의 송금을 실행한다.

### transferFrom

from에서 to로 amount의 송금을 실행하는 함수이다.  
이 함수는 SmartContract에서 발생시키기 때문에 실행 전에 from 주소에 대해 송금하는 amount 이상의 Approve를 받아야한다.

---

**일단 여기까지가 ERC-20의 표준 인터페이스를 구현한 함수들이다.(name,decimal은 제외  
아래로는 Openzepplin에서 제공하거나 확장 가능한 virtual 함수들이다. 간단하게 짚고 넘어가려한다.**

---

### increaseAllowance (확장x)

spender의 allowance값을 증가시킨다.

### decreaseAllowance (확장x)

spender의 allowance값을 감소시킨다.

### \_mint

토큰을 발행하고, 계정에 토큰을 할당한다.

### \_beforeTokenTransfer

토큰 송금 실행 전에 실행되는 함수 (Openzepplin) 확장 함수에서만 사용됨

### \_afterTokenTransfer

토큰 송금 실행 후에 실행되는 함수 (Openzepplin) 확장 함수에서만 사용됨

몇개의 함수가 조금 더 있지만 ERC-20 인터페이스에서 제공하는 함수와 기능을 조금 확장한 것이라 지금 설명한 함수들을 참고해 코드를 살펴보면 충분히 알 수 있는 정도의 확장이다.

# Contract 작성

`MyToken.sol`

```solidity
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.7;

import &quot;@openzeppelin/contracts/token/ERC20/ERC20.sol&quot;;

contract MyToken is ERC20 {
    address public _adminAddress;

    constructor(string memory _name, string memory _symbol, uint _initSupply) ERC20(_name, _symbol){
        _adminAddress = msg.sender;
        _mint(msg.sender, _initSupply * (10 ** uint256(decimals())));
    }
}
```

소스를 살펴보면 굉장히 간단한 것을 볼 수 있다.

\_adminAddress는 송금을 위한 관리자 지갑을 따로 변수로 저장하여 사용하며,\_mint함수를 통해 토큰을 발행한 뒤 관리자 계정에 할당하였다.  
그리고 주요기능은 ERC20토큰을 이용해 사용할 것이며 추후에 Swap, Staking 등의 기능을 추가할 생각이다.

자 이제 배포를 해보자!  
난 아래와 같이 생성자 파라미터를 구성했다. (개인 마음대로 변경해도 상관없다!)

&lt;Image src={erc1} alt=&quot;Deploy&quot; /&gt;

그런 뒤에 Transet 버튼을 누르면

아래와 같이 배포 계약이 실행된다.

&lt;Image src={erc2} alt=&quot;MetaMask&quot; /&gt;

그리고 배포 한 뒤에 아래의 `토큰 가져오기`를 클릭하고 배포한 Contract주소를 넣고 가져오기를 실행하면

&lt;Image src={erc5} alt=&quot;Token&quot; /&gt;

아까 배포시에 설정했던 이름의 토큰이 발행된걸 확인할 수 있다!

또한, Deploy 후 Remix에서 파라미터를 넣어서 함수들을 테스트해볼 수 있다.

&lt;Image src={erc3} alt=&quot;Parameter&quot; /&gt;

EtherScan에 들어가 Contract에 대해 확인이 가능하다.

&lt;Image src={erc4} alt=&quot;Etherscan&quot; /&gt;</content:encoded><category>BlockChain</category><category>Solidity</category><category>Ethereum</category><category>SmartContract</category><author>jt_fox</author></item><item><title>블록체인 SideChain</title><link>https://jtfox.dev/blog/2022-05-17/</link><guid isPermaLink="true">https://jtfox.dev/blog/2022-05-17/</guid><content:encoded>import { Image } from &quot;astro:assets&quot;;
import sidechain from &quot;public/images/blog/2022-05-17/sidechain.jpg&quot;;

# SideChain

먼저, 사이드 체인이 나오게 된 배경은 비트코인의 여러가지 한계점 때문에 나오게 되었다.

- 느린 처리 속도 - 약 10분의 블록 생성 시간으로 인해 자신의 거래가 블록체인에 기록되어 있는지 확인하려면 몇 초에서 느리면 수십분까지 기다리게 된다.

- 하나의 거래방식 - 비트코인 네트워크에선 오직 비트코인 만을 거래할 수 있다.

- 공개 방식 - 비트코인은 퍼블릭 블록체인으로 모든 거래가 공개되어 기밀성을 지킬 수 없다.

이 외에도 다른 문제들도 가지고 있지만 사이드 체인과 관련해서는 이 두 가지만 생각하고 얘기해도 될 것 같다.

사이드 체인은 양항뱡 페그를 이용해 서로 다른 블록체인을 연결하는 별도의 블록체인 네트워크이다.

블록체인끼리 서로 상호 운용할 수 있게 된다는 것 이다.

---

## Side Chain작동 원리

1. 어떤 유저가 Main Chain에서 자신의 코인을 특정 address(동결 주소)로 송금하게 되면 그 코인에 대해서 권한을 잃게되며, 그 코인만큼의 자산을 이용할 수 없게된다.

2. 일정 시간이 지나 체인이 검증되면 같은 양의 대체코인 (다시 비트코인으로 바꿀 수도 있으니 비트코인이라 봐도 무방하다)이 Side Chain에서 발행되게 된다.

3. Side Chain에서 생성된 코인으로 다른 블록체인 코인이나 다른 대체코인과 거래할 수 있게 된다.

## Side Chain의 확장성

위에서 얘기한 Side Chain의 작동 원리는 코인의 거래만을 얘기했지만, Side Chain을 이용해 기존의 코인들에 단점을 대체할 수 있다.

사이드 체인에 스마트 컨트랙트를 구축해 비트코인을 이더리움 네트워크에서 사용할 수 있다던가, 수수료를 적게 만드는 등 Side Chain으로 인해 기존의 코인의 단점들을 보안 해나갈 수 있다.</content:encoded><category>BlockChain</category><category>SideChain</category><author>jt_fox</author></item><item><title>블록체인의 채굴</title><link>https://jtfox.dev/blog/2022-05-25/</link><guid isPermaLink="true">https://jtfox.dev/blog/2022-05-25/</guid><content:encoded>import { Image } from &quot;astro:assets&quot;;
import pow from &quot;public/images/blog/2022-05-25/pow.jpg&quot;;

## 블록체인의 채굴

블록체인에서의 `&apos;채굴&apos;` 이라는 단어는 사실 우리 사회적으로 통용되는 `귀금속의 &apos;채굴&apos;`과 비슷한 부분이 있지만, 엄연히 따지면 다르다.  
블록체인의 채굴의 주된 목적은 보상금이나 새로운 코인의 생성이 아니다.

코인은 그저 블록체인에서 채굴 프로세스에 대한 인센티브일 뿐이다. 블록 생성의 보상으로 채굴자에게 주는 코인은 채굴자들이 네트워크 보안을 지켜주고, 동시에 통화 공급을 실행하게 하는 동기를 부여한다. 하지만, 오늘 날 많은 사람들이 블록체인 채굴의 목적 자체가 코인을 목적으로 잘못 판단하고 있다.

채굴은 분산화된 클리어링 하우스의 주요 프로세스로, 채굴에 의해 거래를 검증 및 승인한다.  
채굴은 비트코인 체제의 `보안을 강화시켜` 주고 중앙 `권력기관 없이도 네트워크 전역에서 합의`가 이루어질 수 있도록 해준다.

짧게 말한다면 `비트코인의 보안이 분산화 되는 매커니즘`라고 정리할 수 있다.

채굴일어나고 블록체인에 적용되는 과정은 아래와 같다.

1. 검증된 비트코인 거래들이 거래풀에 쌓이게 된다.
2. 채굴자들이 경쟁하여 가장먼저 Pow을 성공한 사람이 블록 생성자가 된다.
3. 블록이 생성되고 거래풀에 쌓인 거래들이 추가(승인)되어 블록체인 내에 추가된다.
4. 채굴자는 블록을 생성하면서 블록 내에 포함되는 거래들에 대한 수수료와 새로운 코인을 생성하며 코인에 대한 인센티브를 받게된다.

## 거래 풀 (Memory pool)

비트코인 네트워크 상에 있는 거의 대부분의 노드들이 거래 풀이라 불리는 미승인 거래로 이루어진 임시 목록을 보관하고 있다.
노드들은 이풀을 이용해서 네트워크에는 알려졌지만, 블록체인에 아직 포함되지 않은 거래들을 추적할 수 있게 된다.
새로운 블록이 생성될 시에 수수료가 많은 것으로 우선순위를 정해 블록체인 내에 포함되게 된다.

## 작업증명 (Pow, Proof of Work)

&lt;Image src={pow} alt=&quot;pow&quot; /&gt;

채굴을 하기 위해서는 암호화 해시 알고리즘을 기반으로 하는 어려운 수학 문제를 풀어야 된다.  
이 수학 문제에 대한 해답을 작업증명 이라고 한다.

암호 해시 알고리즘은 입력값으로 출력값을 유추하는 것이 사실상 불가능하기 때문에 특정 목표에 일치하는 해시결과를 얻기 위해서는 무작위로 해싱하여 결과가 우연히 나타날 때까지 입력값을 임의로 수정해 가는 수 밖에 없다.
이 임의로 수정하는 변수로 사용되는 숫자 값을 `난스(nonce)`라고 한다.

목표값이 0으로 시작되는 해시라고 가정해보자 16진수(0부터 F)까지 16개의 진수 중 하나로 가능성으로 얘기한다면, 한다면 평균 16회 정도의 해싱으로 결과를 만들어 낼 수 있다고 볼 수 있다.

이렇게 목표값이 부여한 난이도로부터 성공하기 위해 필요한 작업량을 추정할 수 있다. 입력값 자체는 목표값 아래의 결과를 내기 위해서 행해져야 하는 작업의 특정량에 대한 `증명`이 된다. 그래서 `작업증명`이라는 것이다.

## 비트코인의 공급량

비트코인의 통화 공급은 채굴을 통해서 이루어지며, 약 4년마다(정확히 210,000 블록)마다 절반으로 줄어든다.
2009년 50비트코인으로 시작됐으며, 2022년 현재로선 6.25 비트코인으로 줄어들었다.
이 계산으론 2140년이 되면 비트코인의 총량인 2099만 9999.98 비트코인이 발행된다.

한정된 양을 한도로 통화가 점점 줄어들면서 발행되게 되면 자연스럽게 통화를 발행하면서 생기는 인플레이션은 방지가 된다.  
하지만, 해당 통화는 디플레이션을 야기할 수 도 있다. 하지만, 일반 통화는 수요의 붕괴로 인해 디플레이션이 이러나게 되지만, 비트코인은 예상 가능한 제공 공급량에 의해 발생하는 디플레이션이기 때문에 비트코인 전문가들은 디플레이션 자체가 나쁜 것은 아니라 평 하고있다.</content:encoded><category>BlockChain</category><category>BitCoin</category><category>POW</category><category>POS</category><author>jt_fox</author></item><item><title>데이터 바인딩 (Data Binding)</title><link>https://jtfox.dev/blog/2022-04-19/</link><guid isPermaLink="true">https://jtfox.dev/blog/2022-04-19/</guid><content:encoded>데이터 바인딩은 뷰 요소 혹은 사용자 인터페이스를 채우는 데이터를 연결하는 프로세스이다. 즉, Model과 View의 연결을 생성한다.
쉽게 생각한다면, UI 구성요소에 데이터를 동기화 시키는 방법이라 생각하면 될거같다.

데이터 바인딩은 2가지 유형으로 분류할 수 있는데 `단방향 바인딩`과 `양방향 바인딩`이 존재한다.

## 단방향 바인딩 (One-Way Binding)

1. 단방향 바인딩에서 데이터 흐름은 `모델 -&gt; 뷰` 혹은 `뷰 -&gt; 모델` 하나의 방향이다

2. `모델 -&gt; 뷰` 형식의 단방향 바인딩에서 데이터가 변경되었을 경우에 바인딩 되어있는 구성요소의 값이 자동으로 업데이트되지만, 구성요소에서 값을 변경하여도 바인딩 된 데이터의 값이 변경되지 않는다.

3. UI에서 변경 내용을 모니터할 필요가 없는 경우에 단방향 바인딩을 사용하면 불필요한 리소스 확보를 줄일 수 있다.

## 양방향 바인딩 (Two-Way Binding)

1. 양방향 바인딩에서 데이터 흐름은 `모델 &lt;-&gt; 뷰` 양방향이다.

2. View에 대한 모든 변경 사항은 Model로 전파되고, Model에 대한 모든 변경 사항 또한 View에 반영된다.

---

## 웹 프레임워크들의 바인딩

웹 프레임워크 3대장의 바인딩은 어떨까?

### Angular

Angualr는 단방향, 양방향 바인딩 모두를 지원한다.

### React

React는 단방향 데이터 바인딩을 지원하며 둘 중 하나 조건을 따를 수 있다.

- Componet to View: Component 데이터의 모든 변경사항은 View에 반영 됨
- View to Component: View 데이터의 모든 변경사항은 Component에 반영 됨

### Vue

Vue는 단방향, 양방향 바인딩 모두를 지원한다.</content:encoded><category>CS</category><category>Web</category><author>jt_fox</author></item><item><title>JavaScript 코드의 실행과정</title><link>https://jtfox.dev/blog/2022-04-20/</link><guid isPermaLink="true">https://jtfox.dev/blog/2022-04-20/</guid><content:encoded># JavaScript의 실행과정

1. 처음 브라우저에서 서버에서 클라이언트 코드를 요청하여 HTML 코드를 읽으며 `&lt;script&gt;` 태그를 이용한 JavaSript(편의상 JS) 코드와
   `OnClick`과 같은 JS 코드가 포함된 속성들을 브라우저의 JS 엔진으로 보낸다.

2. JS는 스크립트를 실행하기 위해 실행 컨텍스트(Execution Context)를 생성한다.

3. JS는 단일 스레드의 특성 상 Execution Stack을 쌓게 된다. GEC(Global Execution Context)가 가장 먼저 쌓이고 그 후로 FEC(Funtion Execution Context)들이 쌓이게 된다.

4. Stack에 쌓인 실행 컨텍스트들이 실행 되고 Pop처리가 되고,
   실행된 컨텍스트는 JS엔진에서 활성 실행 컨텍스트가 된다.

---

## Execution Context (EC)

실행 과정들을 과정을 정확히 이해하기 위해서는 Execution Context에 대한 설명이 필요하다.

EC는 scope, hoisting, this, closure 등의 동작 원리를 담고 있는 자바스크립트의 핵심 원리이다.

Execution Context는 세 가지 종류가 있다.

- Global Execution Context

- Function Execution Context

- Eval Execution Context (하지만 보안 문제로 인해 eval 코드를 절대 사용하지 말 것으로 권고하며, 설명에서도 넘어가도록 한다.)

## Global Execution Context (GEC)

3번 과정에서 Stack을 쌓게될 때 가장 먼저 GEC를 쌓게 된다. 모든 JS 파일은 하나의 GEC만이 존재할 수 있으며, `함수 내부에 없는 전역으로 사용할 수 있는 JS 코드`가 실행되는 컨텍스트이다.

## Function Execution Context (FEC)

함수가 호출될 때 마다 JS 엔진이 GEC 내에서 FEC를 생성한다. 함수마다 고유한 FEC를 만들게 되며, 여러개의 FEC가 존재할 수 있다.

---

## Execution Context의 생성 과정

EC는 다음과 같은 과정으로 생성된다.

## 생성 단계

EC는 생성 단계를 거치며 `Lexical Environment component`와 `VariableEnvrionment component`를 생성하게 된다.

## Leximal Envrionment (LE)

LE는 변수와 함수와 같은 실체 객체의 매핑을 보유하는 Component이다.

예를 들어 아래와 같은 코드가 있다면

```javascript
var a = 20;
var b = 40;

function example() {
  console.log(&quot;test&quot;);
}
```

LE는 아래와 같은 형태로 만들어진다.

```javascript
LE = {
  a: 20,
  b: 40,
  example: &lt;ref, to example function&gt;
}
```

또한, LE는 3가지의 구성요소를 가지고 있는데

**Environment Record**

변수 및 함수 선언이 LE 내부에 저장되는 곳이다.  
`Declartive Environment Record (변수 및 함수 선언을 저장하는 레코드)`  
`Object Environment Record (브라우저의 창 객체를 저장하는 레코드)`  
이렇게 두가지의 레코드 구조를 가지고 있다.

**Reference to the outer environment**

이름에서 말하 듯이 외부 환경에 대한 참조이며, 외부 환경 내부의 변수를 찾을 수 있게한다.

**This Binidng**

우리가 익히 알고있는 this 속성이다.  
GEC에선 이건 통상 (window)를 뜻한다. 아래는 GEC의 this 호출과 FEC의 this 호출의 차이다.

```javascript
const FEC = {
  m: 100,
  n: 50,
  calc: function () {
    console.log(this.m - this.n);
  },
};

FEC.calc();
// 50이 콘솔에 적히며, calc은 FEC 객체를 참조하기 때문에 this 함수로 FEC 내의 m과n을 참조하게 된다.

const GEC = FEC.calc;
GEC();
// NaN이 출력되며 여기서 calc에서 this는 전역으로 적힌 m과 n을 찾게 되는데 선언된 변수가 없기 때문에 NaN이 출력되게 된다.
```

## Variable Environment (VE)

위에서 정의한 Leximal Environment에서 기술한 구성요소를 모두 가지고 있으며, LE와의 차이점은 LE는 `함수 선언과 변수(let, Const)` 바인딩을 저장하고, VE는 `Var` 형식의 바인딩만을 저장하는데 사용된다.

## 실행 단계

이 단계에서는 모든 변수에 대한 할당이 완료되고, 코드가 최종적으로 실행된다.

아래와 같은 JS가 작성되었을 때

```javascript
let a = 1;
const b = 2;
var c;

function multiple(e, f) {
  var g = 20;
  return e * f * g;
}

c = multiply(20, 30);
```

GEC의 실행단계 까지 과정은 이렇다.

`생성 단계`

```javascript
  GEC = {
    LexicalEnvrionment:{
      EnvironmentRecord: {
        Type: &quot;Object&quot;,
        a: uninitalized,
        b: uninitalized
        multiply: &lt;func&gt;
      },
      outer: null,
      ThisBinding: &lt;Global Object&gt;
    },

    VariableEnvironment:{
      EnvironmentRecord: {
        Type: &quot;Object&quot;,
        // Identifier bindings go here
        c: undefined,
      },
      outer: &lt;null&gt;,
      ThisBinding: &lt;Global Object&gt;
    }
  };
```

`실행 단계`

```javascript
GEC = {
  LexicalEnvironment: {
      EnvironmentRecord: {
        Type: &quot;Object&quot;,
        // Identifier bindings go here
        a: 20,
        b: 30,
        multiply: &lt; func &gt;
      }
      outer: &lt;null&gt;,
      ThisBinding: &lt;Global Object&gt;
    },
  VariableEnvironment: {
      EnvironmentRecord: {
        Type: &quot;Object&quot;,
        // Identifier bindings go here
        c: undefined,
      }
      outer: &lt;null&gt;,
      ThisBinding: &lt;Global Object&gt;
    }
  }

```

아래는 multiply 함수가 실행되며 만들어진 FEC의 실행과정이다.

`생성 과정`

```javascript
FunctionExectionContext = {
  LexicalEnvironment: {
      EnvironmentRecord: {
        Type: &quot;Declarative&quot;,
        // Identifier bindings go here
        Arguments: {0: 20, 1: 30, length: 2},
      },
      outer: &lt;GlobalLexicalEnvironment&gt;,
      ThisBinding: &lt;Global Object or undefined&gt;,
    },
  VariableEnvironment: {
      EnvironmentRecord: {
        Type: &quot;Declarative&quot;,
        // Identifier bindings go here
        g: undefined
      },
      outer: &lt;GlobalLexicalEnvironment&gt;,
      ThisBinding: &lt;Global Object or undefined&gt;
    }
}
```

`실행 과정`

```javascript
FunctionExectionContext = {
  LexicalEnvironment: {
      EnvironmentRecord: {
        Type: &quot;Declarative&quot;,
        // Identifier bindings go here
        Arguments: {0: 20, 1: 30, length: 2},
      },
      outer: &lt;GlobalLexicalEnvironment&gt;,
      ThisBinding: &lt;Global Object or undefined&gt;,
    },
  VariableEnvironment: {
      EnvironmentRecord: {
        Type: &quot;Declarative&quot;,
        // Identifier bindings go here
        g: 20
      },
      outer: &lt;GlobalLexicalEnvironment&gt;,
      ThisBinding: &lt;Global Object or undefined&gt;
    }
}
```

함수가 완료된 후의 반환 값은 내부에 저장되게 된다. 따라서 GEC가 업데이트 된다.

## 끝 맺으며

오늘은 Javascript의 내부적인 동작 과정에 대해 기술했다.  
 모든 개념을 알아둘 필요는 없을 것 같지만, 전체적인 개념을 충분히 이해하면 Closer, Hoisting(let,const 등장 이후로 쓸일은 없지만...), Scope 등의 개념을 이해하기 훨씬 수월할 것 같다.</content:encoded><category>JavaScript</category><author>jt_fox</author></item><item><title>IPFS</title><link>https://jtfox.dev/blog/2022-06-03/</link><guid isPermaLink="true">https://jtfox.dev/blog/2022-06-03/</guid><content:encoded>import { Image } from &quot;astro:assets&quot;;
import ipfs from &quot;public/images/blog/2022-06-03/ipfs.jpeg&quot;;

# 여담

BlockReview라는 프로젝트를 진행하면서 리뷰를 NFT화 하는 Contract를 만들었는데 NFT를 저장할 때 통상 IPFS라는 시스템에 저장하게 되더라 일반 URL 저장과 무엇이 다른지 한 번 정리해보려고한다.

# IPFS

공식 문서에서는 `IPFS는 파일, 웹 사이트, 응용 프로그램 및 데이터를 저장하고 액세스 하기 위한 분산 시스템`이란다.

먼저 IPFS의 특징을 알아보자,

1. P2P 분산 파일 시스템 (탈중앙화)
2. IPFS 노드에 참여하게되면 FileCoin이란 인센티브를 제공한다.
3. HTTP는 URL을 통해 접근하지만, IPFS는 내용으로 접근하게된다.

## P2P 분산 파일 시스템 (탈 중앙화)

예를 들어, [Aard](https://en.wikipedia.org/wiki/Aardvark)를 위키피디아 주소로 접근할 수 있다.
이것을 ipfs를 통해 접근하게되면, [IPFS](https://ipfs.io/ipfs/QmXoypizjW3WknFiJnKLwHCnL72vedxjQkDDP1mXWo6uco/wiki/Aardvark.html)로 접근하게되면, 전 세계 IPFS 노드에게 요청을하여 페이지를 공유한다.

이러한 방식을 가져가며 얻는 이익은 아래와 같다.

- `서버가 공격을 받거나, 서버에 이상이 생겨도` 다른 곳에서 동일한 웹 페이지를 얻을 수 있게 된다.

- `콘텐츠를 검열하기 쉽지 않다.` IPFS의 파일은 요청을 받는 곳을 예상하기 힘들기 때문에 행동을 제약하기 쉽지않다. 토렌트와 같은 불법 다운로드도 위와 같은 이유로 잡아내기 쉽지 않다.

- `서버가 굉장히 멀리있어도, 가까이 있는 노드가 있다면 속도를 높여 빨리 가져올 수 있다`. 요즘은 이것을 일반 서비스에선 CDN으로 해결한다

## 인센티브 (FileCoin)

IFPS의 클라이언트는 특정 수준의 중복성 및 가용성으로 데이터를 저장하기 위해 비용을 지불하고, 스토리지 제공자는 데이터를 지속적으로 저장하고, 마치 비트코인의 pow 처럼 데이터에 대한 증명을 함으로써 FileCoin을 얻게된다.

## 콘텐츠 주소 지장

IPFS는 HTTP 계층에서 `콘텐츠 주소 지정`을 사용한다. 파일의 내용을 가져와서 암호화 해시를 적용해 주소를 만들게 된다 그래서 `https://ipfs.io/ipfs/QmXoypizjW3WknFiJnKLwHCnL72vedxjQkDDP1mXWo6uco/wiki/Aardvark.html` 같은 형태의 주소가 만들어진다.</content:encoded><category>BlockChain</category><category>IPFS</category><category>Web3</category><author>jt_fox</author></item><item><title>NextJs 웹 어플리케이션 최적화 (트리셰이킹)</title><link>https://jtfox.dev/blog/2022-07-04/</link><guid isPermaLink="true">https://jtfox.dev/blog/2022-07-04/</guid><content:encoded>import { Image } from &quot;astro:assets&quot;;
import treeshaking1 from &quot;public/images/blog/2022-07-04/treeshaking1.png&quot;;
import treeshaking2 from &quot;public/images/blog/2022-07-04/treeshaking2.png&quot;;
import treeshaking3 from &quot;public/images/blog/2022-07-04/treeshaking3.png&quot;;
import treeshaking4 from &quot;public/images/blog/2022-07-04/treeshaking4.png&quot;;

# Tree Shaking

Tree Shaking은 `실제로 사용하는 모듈`만 로딩하게 하여 번들의 사이즈를 줄여
`빌드 시간`과 `렌더링 시간`감소를 기대할 수 있다.

## bundle-analyzer을 통해 번들 크기 시각화

- `@next/bundle/analyzer`

NextJs에선 위의 패키지를 통해 빌드 프로세스에서 번들링 된 패키지 크기를 시각화 하여 볼 수 있다.

- `webpack-bundle-analyzer`

NextJS가 아닌 Webpack을 집적 구성해 프로젝트를 빌드할 때는 위의 패키지를 사용해 동일한 결과를 가질 수 있다.

## 번들 사이즈 측정

먼저 `@next/bundle-analyzer`를 사용하기 위해서는 `next.config.js` 파일에 bundle analyzer를 intergrate 해주어야 한다.

`nextconfig.js`

```js
const withBundleAnalyzer = require(&quot;@next/bundle-analyzer&quot;)({
  enabled: process.env.ANALYZE === &quot;true&quot;
});

module.exports = withBundleAnalyzer(nextConfig);
```

실행은 아래와 같이 package.json 파일에 스크립트로 설정하거나 .env 파일로 설정하여 실행하면 된다.
`package.json`

```json
&quot;analyze&quot; : &quot;cross-env ANALYZE=true next build&quot;
```

위와 같이 실행 후에는 빌드 결과물의 .next 폴더 아래에 analyze 폴더에서 확인할 수 있다.
&lt;Image src={treeshaking4} alt=&quot;bundle&quot; /&gt;

그리고 해당 파일을 누르면 아래와 같은 결과들을 확인할 수 있다.

&lt;Image src={treeshaking2} alt=&quot;bundle&quot; /&gt;
&lt;Image src={treeshaking1} alt=&quot;bundle&quot; /&gt;

&gt; 사이즈를 표시하는 속성으로 stat과 parsed, Gzipped을 확인할 수 있다. stat은 축소와 같은 변환 이전의 파일의 &apos;입력&apos;크기 이고,
&gt; parsed는 파일의 &apos;출력&apos; 크기 이며, Webpack이 트리셰이킹을 마친 상태의 크기다. gzip은 압축을 통해 구문 분석된 번들/모듈을 실행하는 크기이다. 우리는 `parsed size`만을 보면된다.

## 작업 진행

가장 큰 번들의 크기가 `aws-sdk`의 `2.69MB`이다.  
S3를 사용하기 위한 모듈이였는데 EC2, utils 등의 여러 모듈도 함께 빌드되어 가장 큰 용량을 차지하는 것으로 보인다.
`각자 사용하는 모듈이 다르고 모듈마다 트리셰이킹 방식도 다르기 때문에 이번 포스팅에선 가장 큰 패키지 하나만 진행하려고한다.`

`변경 전`

```js
import { config, S3 } from &quot;aws-sdk&quot;;

const region = &quot;ap-northeast-2&quot;;
const bucket = &quot;blockjobsawsbucket&quot;;

config.update({
  region: region,
  accessKeyId: process.env.NEXT_PUBLIC_AWS_ACCESS_ID,
  secretAccessKey: process.env.NEXT_PUBLIC_AWS_ACCESS_KEY
});

const handleFileInput = async (e: ChangeEvent&lt;HTMLInputElement&gt;) =&gt; {
  const file = e.target.files?.[0];

  const upload = new S3.ManagedUpload({
    params: {
      Bucket: bucket, // 버킷 이름
      Key: id + &quot;.png&quot;, // 유저 아이디 혹은 enterpriseid
      Body: file // 파일 객체
    }
  });

  const promise = upload.promise();
  promise.then(
    function () {
      // 종료를 상위 컴포넌트에 callback
      uploadComplete();
    },
    function (err) {
      // 이미지 업로드 실패
      console.log(err);
    }
  );
};
```

&gt; `aws-sdk` 패키지를 제거한 후 S3 업로드에 필요한 `@aws-sdk/client-s3` `@aws-sdk/lib-storage` 패키지를 다운받아
&gt; 해당 패키지에 맞게 코드를 수정했다.

`변경 후`

```js
import { S3Client } from &quot;@aws-sdk/client-s3&quot;;
import { Upload } from &quot;@aws-sdk/lib-storage&quot;;

export const useS3 = () =&gt; {
  const region = &quot;ap-northeast-2&quot;;
  const bucket = &quot;blockjobsawsbucket&quot;;
  const fileBaseUrl = `https://${bucket}.s3.${region}.amazonaws.com/`;

  const handleFileInput = async ({
    id,
    uploadComplete,
    e
  }: ProfileUpload_props) =&gt; {
    const file = e.target.files?.[0];

    const s3 = new S3Client({
      region: region,
      credentials: {
        accessKeyId: process.env.NEXT_PUBLIC_AWS_ACCESS_ID ?? &quot;&quot;,
        secretAccessKey: process.env.NEXT_PUBLIC_AWS_ACCESS_KEY ?? &quot;&quot;
      }
    });

    try {
      const mulitpartUpload = new Upload({
        client: s3,
        params: {
          Bucket: bucket, // 버킷 이름
          Key: id + &quot;.png&quot;, // 유저 아이디 혹은 enterpriseid
          Body: file // 파일 객체
        }
      });

      await mulitpartUpload.done();
      await uploadComplete(id);
    } catch (e) {
      console.log(e);
    }
  };

  return { handleFileInput, fileBaseUrl };
};
```

## 작업 결과

위와같이 작업을 진행한 결과

- Parsed Szie: `2.69MB` -&gt; `373.61KB` (-86%)

&lt;Image src={treeshaking3} alt=&quot;bundle&quot; /&gt;

사실 위와 같은 결과는 나오기 쉽지 않고 (사실 나오면 안된다. 큰 모듈을 통째로 사용하는 것을 지양해야 한다.) 10% ~ 20%의 감소정도를 기대해 볼 수 있는 것 같다. (실무 프로젝트에서도 최대가 15% 정도 였다...)</content:encoded><category>Web</category><category>NextJS</category><category>Webpack</category><category>TreeShaking</category><category>최적화</category><author>jt_fox</author></item><item><title>프론트 엔드에서의 렌더링 최적화</title><link>https://jtfox.dev/blog/2022-07-28/</link><guid isPermaLink="true">https://jtfox.dev/blog/2022-07-28/</guid><content:encoded>import { Image } from &quot;astro:assets&quot;;
import sprite from &quot;public/images/blog/2022-07-28/sprite.png&quot;;

## 1. 트리쉐이킹

[NextJs 웹 어플리케이션 최적화 (트리셰이킹)](/blog/2022-07-04/) 포스팅에서 설명처럼 사용하지 않는 모듈들을 제거하여 `실제로 사용하는 모듈`만을 남겨 파일의 크기를 줄이는 방식이다.

## 2. 블록 리소스 최적하

[DOM (Document Object Model) 과 브라우저 렌더링](/blog/2022-04-18/) 이 포스팅에서 설명했듯이 HTML 파싱을 차단하는 블록 리소스를 태그의 위치를 적절히 설정하여 최적하 하는 방법이다.
페인팅을 빠르게 하고 로딩 속도를 개선할 수 있다.

## 3. 이미지 스프라이트 / 지연로딩

### 이미지 스프라이트

이미지 스프라이트는 여러 개의 이미지를 하나로 합쳐서 관리하는 기법이다.
이미지가 많을 경우 웹 브라우저는 서버에 해당 이미지의 수만큼 요청해야만 하기 때문에 웹 페이지의 로딩 시간이 오래 걸릴 수 밖에 없다.

&lt;Image src={sprite} alt=&quot;sprite&quot; /&gt;

그래서 위와 같이 한 이미지에 여러 이미지를 배치하고, 포지션별로 이미지를 구분해 한번의 로딩만으로 여러 이미지를 사용하는 기법이다.

### 이미지 지연로딩 (Lazy Loading)

여러 이미지가 보이는 페이지에서, 사용자 화면에 보이는 이미지만을 로드하고, 아직 보지 못하는 이미지(스크롤 되지 않은 이미지)들은 바로 요청하지 않는 기법

## 4. 강제 동기 레이아웃 및 스래싱 피하기

### 강제 동기 레이아웃 지양하기

일반적으로 javascript가 실행된 다음 스타일 계산, 레이아웃이 실행된다. 그러나 javascript를 사용해 브라우저가 레이아웃을 더 일찍 수행할 수 있도록 할 수 있다.

스타일을 변경한 후 offsetHeight, offsetTop과 같은 계산된 값을 속성으로 읽으면 강제로 동기 레이아웃이 실행된다.

```js
// not good
function getBoxHeight() {
  box.classList.add(&quot;getbox&quot;);

  console.log(box.offsetHeight);
}

// good
function getBoxHeight() {
  console.log(box.offsetHeight);

  box.classList.add(&quot;getbox&quot;);
}
```

위와 같은 경우 높이 가져오기 위해 레이아웃을 실행한 후 가져와야 한다. 이거는 불필요하고 잠재적으로 비용이 많이 드는 작업이다. 항상 스타일을 읽는 것을 일괄 처리한 후에 수행해야 한다.

### 레이아웃 스래싱

많은 레이아웃을 연속적으로 빠르게 수행하는 것을 레이아웃 스래싱이라고 한다.

```js
function resizeAllParagraphsToMatchBlockWidth() {
  for (var i = 0; i &lt; paragraphs.length; i++) {
    paragraphs[i].style.width = box.offsetWidth + &quot;px&quot;;
  }
}
```

위에 소스는 루프의 각 반복에서 스타일 값을 읽고 너비를 업데이트하게 되면 매번 다음 반복에서 전에 요청된 스타일로 변경되어야 하기 때문에 레이아웃에 계속되서 실행된다.

```js
let width = box.offsetWidth;
function resizeAllParagraphsToMatchBlockWidth() {
  for (var i = 0; i &lt; paragraphs.length; i++) {
    paragraphs[i].style.width = width + &quot;px&quot;;
  }
}
```

위에 소스처럼 한번의 값을 읽어 변수에 할당하여 사용하는 것이 좋다.</content:encoded><category>Web</category><category>렌더링 최적화</category><category>최적화</category><category>성능</category><author>jt_fox</author></item><item><title>React v18 변경 사항 톺아보기</title><link>https://jtfox.dev/blog/2022-07-29/</link><guid isPermaLink="true">https://jtfox.dev/blog/2022-07-29/</guid><content:encoded>import { Image } from &quot;astro:assets&quot;;
import syncRendering from &quot;public/images/blog/2022-07-29/syncRendering.png&quot;;
import concurrent from &quot;public/images/blog/2022-07-29/concurrent.png&quot;;

# React v18

React에서 중요시하는 동시성에 관련한 사항들과 Suspense가 주요 업데이트 사항인것 같다.

## Automatic Batching (자동 일괄 처리)

### batching 처리란?

동일한 클릭 이벤트 내에 두 개의 상태 업데이트가 있는 경우 React는 항상 이를 하나의 재렌더링으로 일괄 처리한다.
다음 코드를 실행하면 클릭할 때마다 상태를 두 번 설정하더라도 React는 단일 렌더링만 수행하는 것을 볼 수 있다.
불필요한 재렌더링을 줄여 성능을 확보하는 렌더링 방식이였다.

```jsx
function App() {
  const [count, setCount] = useState(0);
  const [flag, setFlag] = useState(false);

  function handleClick() {
    setCount((c) =&gt; c + 1); // 리렌더링이 일어나지않음
    setFlag((f) =&gt; !f); // 리렌더링이 일어나지않음
    // React에선 한번의 렌더링으로 일괄처리
  }

  return (
    &lt;div&gt;
      &lt;button onClick={handleClick}&gt;Next&lt;/button&gt;
      &lt;h1 style={{ color: flag ? &quot;blue&quot; : &quot;black&quot; }}&gt;{count}&lt;/h1&gt;
    &lt;/div&gt;
  );
}
```

하지만 React에서 일괄 처리는 일관성이 없었다. 예를들어 다른곳에서 데이터를 불러와 State를 업데이트하는 경우엔 각자 독립적인 렌더링이 일어났다.

```jsx
function App() {
  const [count, setCount] = useState(0);
  const [flag, setFlag] = useState(false);

  function handleClick() {
    fetchSomething().then(() =&gt; {
      // 콜백 이밴트는 이벤트가 종료됐다고 판단하여 일괄처리가 되지 않음
      setCount((c) =&gt; c + 1); // 리렌더링
      setFlag((f) =&gt; !f); // 리렌더링
    });
  }

  return (
    &lt;div&gt;
      &lt;button onClick={handleClick}&gt;Next&lt;/button&gt;
      &lt;h1 style={{ color: flag ? &quot;blue&quot; : &quot;black&quot; }}&gt;{count}&lt;/h1&gt;
    &lt;/div&gt;
  );
}
```

위와 같이 react 이벤트 핸들러가 아닌 Promise, setTimeout, 기본 이벤트 핸들러 등에서는 React에서 일괄처리를 할 수 없었다.

### Automatic Batching (자동 일괄 처리)

React v18에서는 createRoot 내에 모든 업데이트는 출처와 상관없이 자동으로 일괄 처리하게끔 변경 되었다.

아까와 같은 코드로 예를 들자면 아래와 같고 batching을 차단하고 싶으면 `flushSync` API를 사용하면 된다.

```jsx
function App() {
  // React 18 버전 이후
  import { flushSync } from &quot;react-dom&quot;;

  const [count, setCount] = useState(0);
  const [flag, setFlag] = useState(false);

  function handleClick() {
    fetchSomething().then(() =&gt; {
      flushSync(() =&gt; {
        setCount((prev) =&gt; prev + 1);
      });
      // 리렌더링
      setCount((c) =&gt; c + 1);
      setFlag((f) =&gt; !f);
      // 그 후로 한 번의 리렌더링
    });
  }

  return (
    &lt;div&gt;
      &lt;button onClick={handleClick}&gt;Next&lt;/button&gt;
      &lt;h1 style={{ color: flag ? &quot;blue&quot; : &quot;black&quot; }}&gt;{count}&lt;/h1&gt;
    &lt;/div&gt;
  );
}
```

적용하기 위해선 새로 추가된 `react-dom/client` API인 `createroot` 태그로 루트를 생성해야한다.

```jsx
// React v17
import * as ReactDOMClient from &quot;react-dom/client&quot;;

function App() {
  return (
    &lt;div&gt;
      &lt;h1&gt;Hello World&lt;/h1&gt;
    &lt;/div&gt;
  );
}

const rootElement = document.getElementById(&quot;root&quot;);

ReactDOMClient.render(&lt;App /&gt;, rootElement, () =&gt; console.log(&quot;renderered&quot;));
```

위와 같은 방식에서 아래와 같은 방식으로 변경할 수 있다.

```jsx
import * as ReactDOMClient from &quot;react-dom/client&quot;;

function App({ callback }) {
  // Callback will be called when the div is first created.
  return (
    &lt;div ref={callback}&gt;
      &lt;h1&gt;Hello World&lt;/h1&gt;
    &lt;/div&gt;
  );
}

const rootElement = document.getElementById(&quot;root&quot;);

const root = ReactDOMClient.createRoot(rootElement);
root.render(&lt;App callback={() =&gt; console.log(&quot;renderered&quot;)} /&gt;);
```

## ConCurrent Feature (동시성 기능)

### 동시성

리엑트는 항상 동시성을 추구하고 있다.

그러나 자바스크립트는 싱글 스레드기반 언어로 여러 작업을 동시에 처리할 수 없다.  
그래서 ConCurrent Mode를 사용해 동시에 작업이 처리되는 것처럼 기능들을 확대하고 있었다.

1. 여러 작업을 작은 단위로 나눈 후 작업들 간의 우선순위를 정한다.
2. 정해진 우선순위에 따라 작업을 수행한다.

즉 실제로는 동시에 작업이 실행되지 않지만 사용자 입장에서는 작업 간 전환이 매우 빨라 동시에 작업이 진행되는 걸로 보인다.

React v18 에서는 동시성을 기능으로 제공하기 위해서 긴급 업데이트와 긴급하지 않은 업데이트를 구분할 수 있는 개념을 추가했다.

### Transitions

`Urgent updates` 는 입력, 클릭, 누르기 등과 같은 직접적인 상호 작용을 반영 (ex. input 입력)
`Transition updates` 는 UI를 한 보기에서 다른 보기로 전환 (ex. 검색 필터 변경)

아래와 같이 startTransition을 사용해 타이핑, 클릭, 스크롤 등에서 `쓰로틀링`이나 `디바운싱`같은 처리 없이도 렌더링이 완료되기 전에 변경된 최신 결과만을 보여줄 수 있다.

```jsx
import { startTransition } from &quot;react&quot;;

// 긴급: 입력한 내용 표시
setInputValue(input);

// 내부 상태 업데이트를 Transition으로 후순위로 넘김
startTransition(() =&gt; {
  // 쿼리 결과 표시
  setSearchQuery(input);
});
```

startTransition에 래핑된 업데이트는 긴급하지 않은 것으로 처리되며 클릭이나 키 누름과 같은 더 긴급한 업데이트가 들어오는 경우 중단된다.

전환이 사용자에 의해 중단되면(예: 여러 문자를 연속으로 입력) React는 다음을 throw하고, 완료되지 않은 오래된 렌더링 작업을 제거하고 최신 업데이트만 렌더링된다.

transition은 hook 형태와 API 형태가 있는데 사용용도는 다음과 같다.

- useTransition: 보류 상태를 추적하는 값을 포함하여 전환을 시작하는 훅
- startTransition: 후크를 사용할 수 없을 때 트랜지션을 시작하는 방식

쉽게말해 useTransition은 지금 순위가 낮은 업데이트가 보류중인지 여부를 알려주는 값을 포함한 훅이다.

`useTransition 사용법`

```jsx
import { useTransition } from &quot;react&quot;;

function App() {
  // 지연 시간을 초기화 할 수 있다.
  const [isPending, startTranstion] = useTransition({ timeoutMs: 1000 });

  function updateSearchQuery(e) {
    useTransition(() =&gt; {
      // 쿼리 결과 표시
      setSearchQuery(e.event.value);
    });
  }

  return &lt;div id=&quot;app&quot;&gt;{isPending &amp;&amp; &lt;p&gt;Current Updating...&lt;/p&gt;}&lt;/div&gt;;
}
```

### useDeferredValue

위에 useTranstion과 동일하다고 볼 수 있는 기능이다.
긴급하지 않은 부분이 다시 렌더링하는 것을 연기할 수 있으며, 어찌 보면 디바운싱과 같지만 고정된 시간 지연이 없어서 React는 첫 번째 렌더링을 진행한 후에 지연된 렌더링을 시도한다.

useTrnasaction과 가장 큰 차이는 useTransaction은 상태를 업데이트하는 코드를 래핑하는 반면에 useDeferredValue는 상태를 업데이트하면서 영향 받는 값들을 래핑한다.

아래와 같이 사용할 수 있다.

```jsx
function ProductList({ products }) {
  const deferredProducts = useDeferredValue(products);
  return (
    &lt;ul&gt;
      {deferredProducts.map((product) =&gt; (
        &lt;li&gt;{product}&lt;/li&gt;
      ))}
    &lt;/ul&gt;
  );
}
```

### useSyncExternalStore

아래에 훅이 나온 배경을 설명하기 전에 낯설 수 있는 단어를 먼저 설명하려고 한다.

**Extarnal Store**  
외부 저장소는 subscribe할 수 있는 모든 것이다. (Redux, 전역 변수, 모듈 범위 변수, DOM 상태 등)

**Internal Store**
내부 저장소에는 props, context, useState, useReducer가 포함된다.

React v18 부터는 동시성 렌더링을 반영하면서 `Tearing`이란 문제가 일어나기 시작했다.  
Tearing은 시각적인 불일치를 나타내며, UI가 동일한 상태에 대해서 여러 값을 표시하는 것을 의미한다.

React v18부터 렌더링을 중지하기 때문에 일시 정지 사이에 업데이트는 렌더링에 사용되는 데이터와 관련된 변경사항을 가져올 수 있다.

동기 렌더링에서는 아래와 같이 UI는 일관성을 유지할 수 있었다.
&lt;Image src={syncRendering} alt=&quot;syncRendering&quot; /&gt;

아래에 동시 렌더링 시에는 처음에 파란색이였다가. React에서 외부 스토어가 변경되어 빨간색으로 렌더링을 계속하면서 Tearing을 유발한다.
&lt;Image src={concurrent} alt=&quot;concurrent&quot; /&gt;

위와 같은 이유로 useExternalStore hook을 사용해 스토어 내에 데이터를 올바르게 가져올 수 있도록 한다.

useSyncExternal 훅은 두 가지 기능을 사용할 수 있다.

- subscribe - 콜백 함수를 등록하는 함수
- getSnapshot - subscribe 된 값이 마지막 렌더링 이후 변경되었는지 렌더링 되었는지 확인하고, 문자열이나 숫자같은 변경할 수 없는 값이거나 캐싱된 객체인지 확인하여 immutable한 값이 반환된다.

```jsx
import {useSyncExternalStore} from &apos;react&apos;;

  or

// Backwards compatible shim
import {useSyncExternalStore} from &apos;use-sync-external-store/shim&apos;;

//Basic usage. getSnapshot must return a cached/memoized result
useSyncExternalStore(
  subscribe: (callback) =&gt; Unsubscribe
  getSnapshot: () =&gt; State
) =&gt; State

// Selecting a specific field using an inline getSnapshot
const selectedField = useSyncExternalStore(store.subscribe, () =&gt; store.getSnapshot().selectedField);
```

`getSnapshot`

```jsx
import { useSyncExternalStoreWithSelector } from &quot;use-sync-external-store/with-selector&quot;;

const selection = useSyncExternalStoreWithSelector(
  store.subscribe,
  store.getSnapshot,
  getServerSnapshot,
  selector,
  isEqual
);
```

## Suspense Features

React v16.6 버전부터 Suspense로 로드 상태를 명시적으로 지정할 수 있었다.
하지만 React.lazy를 이용한 분할 코드였고, 서버에서 렌더링할 때에는 사용할 수 없었다.

그래서 React v18부터는 Suspense를 확장하여 비동기 작업(로드 코드, 데이터, 이미지 등)을 처리할 수 있도록 변경되었다.

아래와 같은 코드로 사용할 수 있으며 작업중에는 fallback 파라미터로 넘기는 엘리먼트가 표시된다.

```jsx
&lt;div&gt;
  {showComments &amp;&amp; (
    &lt;Suspense fallback={&lt;Spinner /&gt;}&gt;
      &lt;Panel&gt;
        &lt;Comments /&gt;
      &lt;/Panel&gt;
    &lt;/Suspense&gt;
  )}
&lt;/div&gt;
```

## useId

useId는 클라이언트와 서버측에서 모두 고유한 id를 생성하는데 사용할 수 있으며
아래와 같은 형태로 사용할 수 있다.

```jsx
function Checkbox() {
  const id = useId();
  return (
    &lt;&gt;
      &lt;label htmlFor={id}&gt;Do you like React?&lt;/label&gt;
      &lt;input id={id} type=&quot;checkbox&quot; name=&quot;react&quot; /&gt;
    &lt;/&gt;
  );
}
```

## useInsertionEffect

`useEffect`와 동일하지만 DOM이 변경 후 그리고 레이아웃 전에 동기적으로 실행되는 훅이다. 레이아웃을 읽기 전에 스타일을 DOM에 삽입하려면 해당 훅을 사용하면 된다.
레이아웃 전이기 때문에 ref로 엘리먼트에 액세스할 수 없다.

`useInsertEffect`는 CSS-in-JS 렌더링 도중 스타일을 삽입할 때 생기는 성능 문제를 해결하는 용도이다.

```jsx
function useCSS(rule) {
  useInsertionEffect(() =&gt; {
    if (!isInserted.has(rule)) {
      isInserted.add(rule);
      document.head.appendChild(getStyleForRule(rule));
    }
  });
  return rule;
}
function App() {
  let className = useCSS(rule);
  return &lt;div className={className} /&gt;;
}
```</content:encoded><category>Web</category><category>React</category><category>v18</category><category>동시성</category><author>jt_fox</author></item><item><title>프론트엔드 개발자 취업 후기</title><link>https://jtfox.dev/blog/2022-08-29/</link><guid isPermaLink="true">https://jtfox.dev/blog/2022-08-29/</guid><content:encoded>import { Image } from &quot;astro:assets&quot;;
import 일상1 from &quot;public/images/blog/2022-08-29/일상1.png&quot;;

# 시작 전에

취준 기간 동안 누군가 취직한 후기를 많이 보면서 동기부여도 되고 팁도 얻었습니다.
저 또한 누군가에게 도움이 되고 즐거운 시간이 됐으면 해서 작성하는 글입니다.

## 회사 리스트업

이번달 2022-08-19일자로 취업 성공을 했고 전문연구요원으로 편입하여 지금 회사에서 근무하면서 적응하는 중이다.

먼저 나는 석사 전문연구요원으로 편입해야 했고, 프론트엔드 경력으로 취직해야했기 때문에
회사를 리스트업했다.

그리고 결과와 진행상황을 노션으로 아래처럼 정리했다.

&lt;Image src={일상1} alt=&quot;지원회사&quot; /&gt;

&gt; 꽤나 처참하다? 물론 이거 말고 더 넣었음

## 취업 준비

나는 취직을 올해 6월 부터 준비하고 7~8월을 목표로 취업 계획했다.
먼저 나는 3개의 키워드로 준비했는데 아래와 같다.

1. 포트폴리오
2. 코딩테스트
3. 면접

## 포트폴리오

먼저 포트폴리오는 자신이 지원하는 분야에 대해 내가 이만큼~~ 할 수있어! 라고 말한다고 생각하면 된다.  
내가 포폴을 다 숙지하고 옳게 대답할 수 있다면 무기가 될 수 있고, 그 반대라면 면접관에게 나를 향해 휘두룰 수 있는 무기를 쥐어 주는 셈이다.

깃허브에 소스를 하나하나 까보는 면접관도 있었고, 소스를 아예 보지 않고 오는 면접관도 있었지만,
본인의 포트폴리오에 사용하는 기술이나 소스코드를 하나하나 모두는 아니여도 전반적으로 무조건 숙지하고 있어야 한다. (본인이 짜는 코드나 라이브러리를 이해 못하는 사람은 조금 더 학습을을 해보고 취준을 하는 것을 추천한다.)

내가 포폴에서 가장 많이 준비했던 것은 기술을 사용한 `타당한 이유` 였다.
프론트엔드는 굉장히 초기 세팅의 자율성이 많기 때문에 (빌드 도구만 해도 WebPack, Rollup, Vite 등) 이런 여러 라이브러리를 선택하는 `이유`가 분명해야 한다.

또한 ReadMe를 성의 있게 만들자 면접관들은 굉장히 많은 이력서를 읽어야 하고, 평균적으로 하나의 이력서에 30초~1분으로 첫 인상을 파악한다고 한다.
면접관의 입장에서 성의 없어보이는 포트폴리오는 빠르게 넘어가기 마련이다.

## 코딩테스트

코딩테스트는 대부분의 회사는 실행한다. 나는 이전까진 코테 준비를 딱히 하지 않았고 6월 부터 하루에 `한 시간 ~ 두 시간` 정도 백준 문제집에 삼성 기출 문제를 풀었다.

&gt; `동빈 나 - 이것이 코딩 테스트다.` 책이랑 같이 유튜브를 시청해서 초반 개념을 잡았다.

요즘은 구현 문제가 많이 나오기도하고, BFS와 DFS 그리고 DP등 여러 알고리즘을 준비할 수 있는 좋은 가이드북인 것 같다.

그리고 대부분의 프론트엔드 코테는 공채 아니면 javascript로 하기 때문에 python으로 한다면 포팅하는 방식으로 풀던가 javascript로 하는 연습이 필요하다.

&gt; 사실 나도 코테 잘 못해서 많이 떨어졌다 ㅠㅠ 코테 잘하는 분들 보면 존경스러움,,

## 면접

보통 면접은 1차 기술면접 2차 인성면접으로 나누어서 보게된다.

기술면접은 ESNext, ReactJs, NextJs, 빌드 도구(webpack), css, html 정도를 준비했다.
그리고 코테 보는 곳은 코테코드에 대해 물어보는 곳도 있었다.

사실 프론트엔드 기술면접 자료는 너무 많아서 링크를 남긴다.
거의 아래 두 개로 모두 커버된 것 같다.

캡틴판교님 - https://joshua1988.github.io/web-development/interview/frontend-questions/
github - https://github.com/baeharam/Must-Know-About-Frontend

인성 면접도 사실 유튜브나 구글에 너무 많이 나와있어서 그걸 보고 공부했다.
말이 꼬이지않게 혼자 나불나불 연습했다.

면접 후 꼭 `복기`해라 진짜 중요하다 이게 인성면접이든 기술면접이든 정말 중요하다.
내가 부족한게 무엇인지 알 수 있고 대답을 잘했다 생각한 것도 보완할게 나온다.

## 최종 합격 후

온보딩 전에 보통 오퍼레터가 오고 협의를 하게된다. 연봉이 마음에 들면 바로 온보딩 하면되고, 안들면 카운터 오퍼를 보내면 된다.

## 마지막으로

취준하시는 분들 많이 고생하시고 힘든 걸 알고있다. 그래도 열심히 꾸준히 준비하면 좋은 결과가 있을거라 믿는다!</content:encoded><category>후기</category><category>일상</category><author>jt_fox</author></item><item><title>React에 StoryBook 셋업하기!</title><link>https://jtfox.dev/blog/2022-09-01/</link><guid isPermaLink="true">https://jtfox.dev/blog/2022-09-01/</guid><content:encoded>## Storybook install

storybook 설치

```ps
npm i @storybook/react
```

storybook init

```ps
npx storybook init
```

## Storybook setting

`stories/main.js`

```js
module.exports = {
  addons: [&quot;@storybook/addon-essentials&quot;],
  babel: async (options) =&gt; ({
    ...options,
  }),
  framework: &quot;@storybook/react&quot;,
  stories: [&quot;../src/**/*.stories.@(js|mdx)&quot;],
  webpackFinal: async (config, { configType }) =&gt; {
    return config;
  },
};
```

위에는 storybook에서 제공하는 main.js configuartion 코드이다.

- addons - 스토리북에서 사용하는 bable로 치면 plugin같이 서드파티 혹은 다른 라이브러리와 상호작용하기 위한 목록

- babel - storybook build 시에 babel 구성

- webpackFinal - storybook build 시에 webpack 구성

- framework - 로딩 및 빌드 프로세스를 돕기 위한 프레임워크 구성

- stories - 스토리 파일의 위치

`button.stories.tsx`

```tsx
export default {
  title: &quot;button&quot;,
  component: Button,
  argTypes: {
    label: {
      description: &quot;overwritten description&quot;,
      table: {
        type: {
          summary: &quot;something short&quot;,
          detail: &quot;something really really long&quot;,
        },
      },
      control: {
        type: &quot;text&quot;,
      },
    },
  },
};

export const Default = (props: BunttonProps) =&gt; {
  return &lt;Button {...props}&gt;Example&lt;/Button&gt;;
};

Default.storyName = &quot;Default&quot;;
Default.args = {
  color: &quot;primary&quot;,
};
```

위와 같이 스토리를 생성한 컴포넌트로 기반으로 작성할 수 있으며, args로 초기 argument들을 정의할 수 있고,
argTypes를 통해 storybook 내에서 설정한 control을 사용해 props 값을 변경할 수 있다.</content:encoded><category>Web</category><category>React</category><category>Testing</category><category>StoryBook</category><author>jt_fox</author></item><item><title>RxJS (feat.반응형 프로그래밍)</title><link>https://jtfox.dev/blog/2022-11-14/</link><guid isPermaLink="true">https://jtfox.dev/blog/2022-11-14/</guid><content:encoded># RxJs

## 개요

RxJs의 용도는?

```jsx
Think of RxJS as Lodash for events.
(RxJs를 이벤트용 Lodash로 생각하라)
```

처음에 RxJs의 공식 문서를 살펴보면 이게 정확히 무슨 용도로 쓰이고 어떤 효용성이 있는지 파악하기 힘들다.

작성자도 같은 생각이었는지 위와 같이 설명한다.

내가 생각하는 RxJs의 용도는 간단히 말하면 `비동기 코드와 시간 관련된 코드를 쉽고 간결하게 작성하게 도와주는 것` 이다.

만약에 아래조건으로 자동완성을 처리를 해야된다고 생각해보자

&gt; 1. 타이핑 할 때마다 서버에 데이터를 받아서 보여주세요
&gt; 2. 너무 잦은 요청은 서버에 부하를 줄 수 있으니 타이핑 간격이 좁으면 대기하다가 입력이 늦어지면 그 때 요청해주세요
&gt; 3. 같은 내용일 때는 요청하지 마세요
&gt; 4. 일정 시간 동안 응답이 없으면 3회 재시도하고 그래도 응답이 없으면 에러메시지를 출력해주세요
&gt; 5. 데이터는 캐시로 보관하여 먼저 보여주고 요청이 완료되면 변경된 데이터를 표시해주세요

위와 같은 조건을 Async/Await 혹은 Promise를 이용한다고 생각해보자 굉장히 복잡하고 어려운 코드를 만들게 될거다.

RxJS를 이용했을 때 코드는 아래와 같다.

```jsx
let autoCompleteRef;
const inputElement = document.createElement(&quot;input&quot;);
const request = (data: any) =&gt; fromFetch(&quot;url&quot;);
const input$ = fromEvent &lt; HTMLInputElement &gt; (inputElement, &quot;input&quot;); // Event -&gt; Observable

input$
  .pipe(
    map((event) =&gt; event.value),
    distinctUntilChanged(), // 3 - 중복되는 요청 막기
    debounceTime(500), // 2 - 연속되는 타이핑 제어
    mergeMap((text) =&gt;
      request(text).pipe(
        map((res) =&gt; res.text),
        tap((value) =&gt; (autoCompleteRef = value)), // autoCompleteElement 처리
        timeout(7000), // 4 - 7초동안 서버의 응답이 없으면 timeout 처리
        retry(3), // 3번 재시도
        catchError((e) =&gt; e)
      )
    )
  )
  .subscribe();
```

코드를 보면 떠오르는게 있다! 마치 Array가 떠오르지 않나? 그래서 많은 사람들이 rxjs를 비동기 처리를 Array처럼 처리 할 수 있게 해주는 라이브러리라고 칭하기도 한다.

저 처리들을 모두 async/await 혹은 Promise로 처리한다고 생각해보자 저거보다 코드를 더 작성해야할 것이고 가독성도 좋지 않을 것이고, subscribe와 같은 observable 처리는 또 따로 해줘야 할 것이다. 물론 부가적인 기능이 더 있을 수 있지만 주로 이런 처리를 위해 RxJs를 사용한다.

## EveryThing is a Stream

Stream이란 말은 프론트엔드 개발자에게 굉장히 친숙하다. stream은 실시간으로 계속 변경되는 데이터를 Chunk 단위로 받아 처리하는 것을 뜻한다.
예를 들면 Netflix와 같은 OTT 동영상 데이터를 받아 처리하는 것이나 화면 상에 움직이는 마우스 좌표 사용자가 입력하는 키보드 값 등 거의 모든 것을 Stream으로 표현할 수 있다.

## 반응형 프로그래밍 (Pull, Push)

반응형 프로그래밍은 생각보다 우리 주변에서 쉽게 접할 수 있다.

가장 쉬운 예로는 Excel이 있다! 가령 A1+B1 이라는 수식을 C1에 적어둔다면 A1과 B1이 변경될 때마다 C1이 알아서 반응하여 숫자를 변경한다.

그리고 Javascript에서 addEventListener 또한 반응형이다! event를 등록하고, event가 발생할 때 반응하여 여러 작용을 하는 방식이다.

반응형 프로그래밍은 Push 방식의 데이터 패러다임을 가지고 있다.

### Pull

Pull은 Promise가 제일 대표적이다. 사용자가 필요할 때마다 데이터를 요청해서 사용하는 방식이다. 레거시 데이터나 중복 요청이 있을 수 있다.

### Push

Push는 Subscribe된 값이 변경될 때마다 전파된 데이터를 받아 항상 최신의 데이터를 유지할 수 있다.

# Observable (생성자)

Observable은 RxJS에서 가장 중심이 되는 역할을 한다. `observer` 을 인자로 받고 함수를 반환하며, Subscribe할 수 있는 역할이다.

Observable은 Promise와 같이 하나의 객체로 생각해야되며, Observable은 기본적으로 일반 function과 같이 동기적으로 작동한다.

하지만 함수와 다른점은 `반환` 에 있다.

비동기 적 반환과, 동기식으로 여러 개의 값을 반환할 수 있다.

```jsx
// 일반 함수
function foo() {
  return 42;
  return 100; // 죽은 코드이며 실행할 수 없는 코드다
}

import { Observable } from &quot;rxjs&quot;;
// Observable

const foo = new Observable((subscribe) =&gt; {
  console.log(&quot;test&quot;);
  subscribe.next(&quot;1&quot;); // 이런식으로 여러 값을 리턴할 수 있다.
  subscribe.next(&quot;10&quot;);
  subscribe.next(&quot;10&quot;);
  interval(3000, () =&gt; {
    subscribe.next(&quot;result&quot;); // 비동기 반환
  });
});

foo.subscribe((x) =&gt; {
  console.log(x);
});
```

`output`

```jsx
&quot;test&quot;
&quot;1&quot;
&quot;10&quot;
&quot;10&quot;
3초 뒤 ..
&quot;result&quot;
```

## subscribe

생성된 observable에 대한 subscribe 를 정의 할 수 있고, 전달될 데이터의 콜백과 다를게 없다.

`Subscription` 이라는 객체를 반환하며 이 객체는 오직 unsubscribe를 처리하는 용도로만 사용된다.

## Observable 실행

### next

실제 데이터를 `Subscription`에게 전달하는 함수.

### Error

예외가 발생했을 때 Subscription에게 전달하는 함수 Observable Execution 동안 한 번의 실행만 가능

### Complete

실행이 다 완료되었을 때 전달하는 함수 Error와 마찬가지로 한 번의 실행만 가능하며 둘 중에 하나만 있을 수 있다.

## Observable 실행 중지

observable.subscribe() 실행시 Subscription이 반환된다. 그 반환된 객체로 unsubscribe 처리를 해주면 된다.

```jsx
subscription.unsubscribe();
```

# Observer (소비자)

Observer은 Observable에서 전달되는 값을 유형별로 처리하는 콜백 세트이다.

next, error, complete 과 같이 Observable에서 실행하는 함수와 같다.

# Operator (연산자)

Operator는 함수를 실행하고 Observable을 반환하는 함수이다.

연산자는 두 가지 종류가 있다.

### Pipe 가능한 연산자

해당 연산자는 Observable에 파이프할 수 있는 종류이다. 여기에는 filter, mergeMap이 포함된다. 그리고 구독 로직이 첫 번 째 Observable을 기반으로하는 새로운 Observable을 반환한다.

### Pipe

Pipe는 Rxjs 5.5부터 도입된 개념이고, RxJs의 범위가 커지면서 Method방식은 Tree-Shaking에 불리하여 Pipe같은 Operator 함수로 분리하면 import를 한 만큼만 번들링이 되기 때문에 번들링의 크기를 줄일 수 있게 된다.

만약에 클릭이 3번 이상 일어났을 때 어떠한 동작을 한다고 생각하면 Dot Chain 방식에서는 아래와같은 코드를 사용할 수 있다.

```jsx
import { Observable } from &apos;rxjs&apos;

const obs = fromEvent(window,&quot;click&quot;).bufferTime(250).filter(click =&gt; click.length === 3).subscribe(~~)
```

Pipe 연산자로 그대로 옮긴 다면 다음과 같다.

```jsx
const pipe$ = fromEvent(window, &quot;click&quot;)
  .pipe(
    bufferTime(2500),
    filter((click) =&gt; click.length &gt;= 3)
  )
  .subscribe((value) =&gt; {
    console.log(`value:${value}`);
  });
```

### Create Operator (생선 연산자)

미리 정의된 몇 가지 일반적인 동작을 사용하거나 다른 Observable을 결합하여 Observable을 생성하는데 사용할 수 있는 함수들이다.

예를 들어 `Interval`이 있습니다.

```jsx
// Observable 생성
const observable = interval(1000);
```

더 많은 것들은 공식문서를… Event도 있고 데이터끼리 join을 해서 Observable을 생성하는 것도 있습니다.

## Cold Observable

Observable 내에서 데이터를 생산하는 경우를 일컫는 말이다.

Observable은 기본적으로 무언가가 구독할 때만 값을 실행하게 되는데 각 구독자에 대해서 새로운 실행을 시작하므로 데이터가 공유 되지 안는다.

코드로 예를 든다면 아래와 같다.

```jsx
import * as Rx from &quot;rxjs&quot;

const observable = of(0,1).pipe(
	next(Math.random());
)

observable.subscribe((dt) =&gt; {
	console.log(dt); // 0.235314123324
});

observable.subscribe((dt) =&gt; {
	console.log(dt); // 0.712341231124
});
```

구독자에 대해서 새로운 실행을 수행하기 때문에 구독자가 받는 데이터는 다르게 되는 동작이다.

`Cold` 방식은 애니메이션이나 계속해서 함수가 생성되어 변화를 감지해야하는 것들이 어울린다.

## Hot Observable

`Hot` 방식은 Cold와는 반대로 Observable 외부에서 데이터를 받는 방식이다.

코드로 예를 든다면 아래와 같다.

```jsx
import * as Rx from &quot;rxjs&quot;

const random = Math.random();
const observable = of(0,1).pipe(
	next(random);
)

observable.subscribe((dt) =&gt; {
	console.log(dt); // 0.235314123324
});

observable.subscribe((dt) =&gt; {
	console.log(dt); // 0.235314123324
});
```

구독자가 있든 없든 데이터가 생성되기때문에 구독자가 없다면 쓸 때없는 데이터에 메모리를 사용하는 경우가 될 수 있다.

`Hot` 방식은 API를 호출하거나 상태관리를 할 때 어울리는 방식이다.

### SubJect

Subject는 Observable과 Observer 역할 모두 수행할 수 있으며, subscribe할 수 있으며 동시에 pipe도 가능하다.

Subject로 Hot Observable을 사용할 수 있다.

Subject의 기능은 여러 Observer에 멀티캐스트할 수 있다.

코드로 예를 든다면 아래와 같다.

```jsx
const subject = new Subject();
subject.subscribe((val) =&gt; console.log(val)); // 0.123123
subject.subscribe((val) =&gt; console.log(val)); // 0.123123

subject.next(Math.random());
```

### UseObservable

```tsx
import { useEffect, useMemo, useRef, useState } from &quot;react&quot;;
import { BehaviorSubject, combineLatest, from, map } from &quot;rxjs&quot;;

const useObservedValue = (observable: any) =&gt; {
  const [value, setValue] = useState();

  const subject = useRef(new BehaviorSubject(observable));

  useEffect(() =&gt; {
    subject.current.next(value);

    return () =&gt; {
      subject.current.unsubscribe();
    };
  }, [value]);

  return useMemo(() =&gt; subject.current.asObservable(), [subject]);
};

const test = () =&gt; {
  const [title, setTitle] = useState&lt;string&gt;(&quot;&quot;);
  const myObservaed = useObservedValue(&quot;value&quot;);

  myObservaed.subscribe((value) =&gt; {
    console.log(value);
  });
};
```

## API 호출 시 유용한 함수

Promise 값이나 iterator을 가진 값은 From 메소드로 Observable 형식으로 변환시킬 수 있다.

### ForkJoin

ForkJoin에 모든 Observable이 완료되는 것을 기다린 후 해당 Observable의 마지막값들을 return한다.

Subject에서 여러 API를 호출한 뒤에 하나의 결과로 모아볼 수 있는 API이다.

`예제 코드`

```jsx
import { forkJoin, of, timer } from &quot;rxjs&quot;;

const observable = forkJoin({
  foo: of(1, 2, 3, 4),
  bar: Promise.resolve(8),
  baz: timer(4000),
});

observable.subscribe({
  next: (value) =&gt; console.log(value),
  complete: () =&gt; console.log(&quot;This is how it ends!&quot;),
});

// Logs:
// { foo: 4, bar: 8, baz: 0 } after 4 seconds
// &apos;This is how it ends!&apos; immediately after
```

### MergeMap

ForkJoin과는 다르게 Observable 중 하나가 완료될 때 마다 ID를 사용하고 반응이 가능한 API

`예제코드`

```jsx
import { of, mergeMap, interval, map } from &quot;rxjs&quot;;

const letters = of(&quot;a&quot;, &quot;b&quot;, &quot;c&quot;);
const result = letters.pipe(
  mergeMap((x) =&gt; interval(1000).pipe(map((i) =&gt; x + i)))
);

result.subscribe((x) =&gt; console.log(x));

// Logs:
// a1
// b1
// c1
// a2
// b2
// c2
// a3
// b3
// c3
```

### CombineLatest

Observables 배열에 전달된 모든 Observables의 값을 결합한다.

### @akanass/rx-http-request

API를 호출하여 바로 Observable을 반환하여 Subscribe에게 전달할 수 있는 라이브러리이다.</content:encoded><category>Web</category><category>JavaScript</category><category>RxJS</category><category>비동기 프로그래밍</category><category>반응형 프로그래밍</category><author>jt_fox</author></item><item><title>Atomic Design Pattern</title><link>https://jtfox.dev/blog/2022-11-15/</link><guid isPermaLink="true">https://jtfox.dev/blog/2022-11-15/</guid><content:encoded>import { Image } from &quot;astro:assets&quot;;
import atoms from &quot;public/images/blog/2022-11-15/atoms.png&quot;;
import molecules from &quot;public/images/blog/2022-11-15/molecules.png&quot;;
import organisms from &quot;public/images/blog/2022-11-15/organism.png&quot;;
import template from &quot;public/images/blog/2022-11-15/template.png&quot;;
import page from &quot;public/images/blog/2022-11-15/page.png&quot;;
import atomic from &quot;public/images/blog/2022-11-15/atomic.png&quot;;

# Atomic Design Pattern

우리가 학창시절에 배우는 화학에 영감을 얻어 만들어진 디자인 패턴이다.
고등학교 과정을 받으면 다 아는 정도에 화학 지식이면 충분하기 때문에 걱정할 필요는 없다...!

&gt; 원자가 어떠한 물질을 이루는 가장 작은 단위이고, 원자가 결합해 분자를 형성한다 그리고 분자들이 모여 유기체를 만든다.

위와 같은 개념으로 컴포넌트를 구성할 수 있게 도와주는 정신적인 모델이다. 선형적으로 개발할 필요는 없으며, 단지 5단계로 이루어져 있는 각각의 인터페이스가 계층적으로 역할을 할 수 있게 개발하면 된다.

[bradfrost](https://atomicdesign.bradfrost.com/) 라는 개발자가 만들었고 본인이 잘 정리한 사이트도 있다!

&lt;Image src={atomic} alt=&quot;atomic&quot; /&gt;
기본적으로 5 단계로 이루어져 있고 순서는 `원자 - 분자 - 유기체 - 템플릿 -
페이지` 이다.

이제 하나씩 어떤 역할을 하는지 훑어보겠다.

## Atom (원자)

&lt;Image src={atoms} alt=&quot;atoms&quot; /&gt;

Atom은 더 이상 분해될 수 없는 객체로 구성하게 된다.
`Button, Input, Card, Label` 등의 기능을 중단시키지 않는 이상 분해될 수 없는 객체, 보통은 HtmlElement을 Wrappring 해서 많이 구성한다.

디자인 패턴 맥락에서 Atom은 기본 스타일을 보여주며, 재사용이 굉장히 용이하다.
Atom은 무조건 Molecules로만 결합되는 것이 아닌 Molecules과 함께 Organisms을 구성할 수도 있으며 여러 단위들과의 조합이 가능하다.

## Molecules (분자)

&lt;Image src={molecules} alt=&quot;molecules&quot; /&gt;

Molecules은 Atom들이 결합된 그룹이다. 예를 들어 물 분자와, 과산화소수의 분자가 동일한 원자구조를 가지고 있지만 다른 특성을가지고 기능하는 것을 보면 알 수 있겠지만 같은 Atom으로 생성한 컴포넌트를 이용해 Molecules를 형성해도 고유한 형태로 만들어 낼 수 있다.

위에 이미지 같이 SearchForm을 만들게되면, SRP(단일 책임 원칙)을 준수해 한 가지 일을 수행하는 재사용과 테스트하기 좋은 mocules를 만들 수 있다.

## Organisms (유기체)

&lt;Image src={organisms} alt=&quot;organisms&quot; /&gt;

위에 원자와 분자와는 다르게 조금 복잡한 구조이다. 각각이 다른 분자들을 조합해서 유기체를 만들 수도 있고, 반복되는 분자들을 만들어서 유기체를 구성할 수도 있다.

보통 위에 이미지처럼 하나의 섹션을 보통 담당하며, 재사용성은 매우 떨어진다.

## Template

&lt;Image src={template} alt=&quot;template&quot; /&gt;

여기서부턴 화학 비유가 끝나게 된다. (`bradfost의 말로는 화학 비유로 너무 멀리 나아가면 미친사람 취급을 당할 수 있다고 한다`)

위에 만든 구성요소들을 레이아웃에 배치하고, 기본 콘텐츠 구조를 생성한다. 골격이 만드는 페이지라고 생각하면 편하다.

## Page

&lt;Image src={page} alt=&quot;page&quot; /&gt;

Page는 가장 구체적인 데이터들을 다루는 부분이다. Template이 구성된 곳에 실제 데이터를 넣고, 기본 콘텐츠 구조와 디자인이 동적특성을 잘 반영하는지 테스트할 수 있다.

&gt; 나는 보통 Organism부터는 재사용이 힘들다고 생각하고 Context를 사용해 구성하기도 한다. Organism과 Mocules에 대한 구분은 프로젝트 특성이나 사람마다 다른 것 같아서 본인이 재사용성을 높이고, 다른 단계들과 잘 융합될 수 있는 작업을하면 될 것 같다.</content:encoded><category>Web</category><category>디자인패턴</category><category>Atomic Design</category><author>jt_fox</author></item><item><title>React (webpack) + electron 환경 다국어 적용기 (i18next)</title><link>https://jtfox.dev/blog/2024-01-22/</link><guid isPermaLink="true">https://jtfox.dev/blog/2024-01-22/</guid><content:encoded>import { Image } from &quot;astro:assets&quot;;
import myImage from &quot;public/images/blog/2024-01-22/post36_1.png&quot;;

## 들어가며

최근 회사 프로젝트에 다국어 지원을 적용했다.

프로젝트는 React로 개발된 애플리케이션을 webpack으로 번들링하고, 이를 electron으로 실행하여 번들링된 파일을 watch하는 구조다.

일반적인 웹 환경에서는 소셜 공유 및 검색 결과 노출을 소셜 공유 및 검색 결과 노출을 위한 Open Graph 태그 등의 메타 태그를 쉽게 커스터마이징할 수 있습니다.위한 Open Graph 태그 등의 메타 태그를 쉽게 커스터마이징할 수 있습니다.[i18next-browser-languagedetector](https://www.npmjs.com/package/i18next-browser-languagedetector)를 사용하여 브라우저의 언어를 감지할 수 있지만, electron 환경에서는 OS의 언어 설정을 직접 감지해야 한다. 이 글에서는 electron 환경에서 OS의 언어를 감지하고 i18next에 적용하는 방법을 소개한다.

## react-i18next

먼저 React 환경에 아래와 같이 i18next를 설치한다.

```bash
npm i --save-dev react-i18next
```

다음으로 src 폴더 내에 locales 폴더를 생성하고 아래와 같이 구성한다.

&lt;Image src={myImage} alt=&quot;folders&quot; /&gt;

- kr: 한국어 가이드 파일
- en: 영어 가이드 파일

`src/locales/kr/common.json`

```json
{
  &quot;create&quot;: &quot;생성&quot;,
  &quot;delete&quot;: &quot;삭제&quot;
}
```

`src/locales/en/common.json`

```json
{
  &quot;create&quot;: &quot;Create&quot;,
  &quot;delete&quot;: &quot;Delete&quot;
}
```

이번 프로젝트에서는 페이지별로 다국어 키를 중복해서 사용했다.

이유는 공통화를 하게 되면 매번 key값을 찾으러 해매야 하는 것과 공통화할 항목들이 모호해지는 상황을 겪게 된다.

물론 파일이 커지는 부작용과 중복되는 키가 보기 좋지 않는 단점이 있겠지만 빠른 작업을 필요로 했기 때문에 직관적으로 페이지별로 키를 구분했다.

다국어 파일은 **JSON 형식으로 작성하는 것을 추천한다.** [i18n-ally](https://github.com/lokalise/i18n-ally)는 React 파일 내에서 키 값을 매핑하고 수정할 수 있게 해주는 편리한 도구인데, JSON은 수정을 지원하지만 TypeScript는 extension 내에서 수정이 불가능하고 실시간으로 키 값을 인식하지 못하는 문제가 있다.

그리고 모든 언어 파일의 키 값은 동일하게 유지해야 한다.

이제 locales 폴더의 index.ts 파일에 i18next 설정을 추가한다.

`src/locales/index.ts`

```typescript
import i18n from &quot;i18next&quot;;
import { initReactI18next } from &quot;react-i18next&quot;;
import * as localeEn from &quot;./en&quot;;
import * as localeKr from &quot;./kr&quot;;

export const kr = { ...localeKr };
export const en = { ...localeEn };

i18n.use(initReactI18next).init({
  resources: { en, kr },
  fallbackLng: [&quot;en&quot;, &quot;kr&quot;],
  interpolation: { escapeValue: false },
});

export default i18n;
```

이제 다국어 설정이 완료되었다. 아래와 같이 직접 모듈을 사용하거나

```typescript
i18n.t(&quot;common:created&quot;);
```

아래와 같이 라이브러리에서 제공하는 hook을 사용해도 된다.

파일명(예: common)을 지정하지 않아도 해당 키가 있다면 다른 파일에서 찾아 매핑한다.

```typescript
import { useTranslation } from &quot;react-i18next&quot;;
const { t } = useTranslation();

t(&quot;common:created&quot;);
// 이렇게도 사용 가능
t(&quot;created&quot;);
```

## electron Language Detector

자 이제 서론에서 얘기했던 electron에서 detector를 설정할 때다.

우리 환경은 React와 electron이 ipc통신을 사용하고 있어서 아래와 같이 컨트롤러를 만들었다.

`electron/src/controllers`

```typescript
import { app, BrowserWindow, ipcMain } from &quot;electron&quot;;

export default (window: BrowserWindow) =&gt; {
  ipcMain.handle(&quot;getLocale&quot;, async (event, args) =&gt; {
    try {
      // 현재 사용자 컴퓨터의 locale을 가져온다.
      const userLocale = app.getLocale();
      return userLocale;
    } catch (error) {
      console.error(error);
      return &quot;en&quot;;
    }
  });
};
```

이제 React에서 이 handle을 호출하는 Detector 모듈을 만든다.

`languageDetector.ts`

```typescript
import { electron } from &quot;~/utils/electron&quot;;
import { ModuleType } from &quot;i18next&quot;;

interface LanguageDetector {
  init: () =&gt; void;
  async: boolean;
  type: ModuleType;
  detect: () =&gt; string;
  cacheUserLanguage: () =&gt; void;
}

const languageDetector: LanguageDetector = {
  type: &quot;languageDetector&quot;,
  async: true,
  init: () =&gt; {},
  // i18next 내부에서 languageDetector로 정의된 모듈의 detect함수를 실행시켜 반환값을 현재 언어로 설정한다.
  detect: () =&gt;
    electron.ipcRenderer.invoke(&quot;getLocale&quot;, undefined).then((result: any) =&gt; {
      return result;
    }),
  // user의 language를 cache할 조건이 있다면 여기에 정의
  cacheUserLanguage: () =&gt; {},
};

export default languageDetector;
```

마지막으로 i18next에 detector를 추가한다.

`src/locales/index.ts`

```typescript
import i18n from &quot;i18next&quot;;
import { initReactI18next } from &quot;react-i18next&quot;;
import * as localeEn from &quot;./en&quot;;
import * as localeKr from &quot;./kr&quot;;
import languageDetector from &quot;~/utils/languageDetector&quot;;

export const kr = { ...localeKr };
export const en = { ...localeEn };

i18n
  .use(languageDetector)
  .use(initReactI18next)
  .init({
    resources: { en, kr },
    fallbackLng: [&quot;en&quot;],
    interpolation: { escapeValue: false },
  });

export default i18n;
```

## 마치며

이상으로 i18next 기본 설정과 electron + React 환경에서 다국어를 설정하는 detector 구현 방법을 살펴봤다.

일반적인 웹 환경과 달리, electron 환경에서는 OS의 locale을 직접 감지해야 하는 경우가 있다. 이 글이 비슷한 상황에서 도움이 되었으면 한다.</content:encoded><category>React</category><category>Electron</category><category>i18next</category><category>다국어</category><author>jt_fox</author></item><item><title>Astro 블로그 마이그레이션 후기</title><link>https://jtfox.dev/blog/2025-05-12/</link><guid isPermaLink="true">https://jtfox.dev/blog/2025-05-12/</guid><content:encoded>최근에 Github의 Jeklly 테마 기반으로 운영하던 블로그를 Astro로 만든 블로그로 마이그레이션 했다.

Jeklly의 가이드에 따라 제작하고 작성하다 보니 커스텀에 대한 필요성과, 방치하게 되면 의존성 있는 패키지들이 지원되지 않는 경우도 생기다 보니 변경을 결심하게 됐다.

기존의 포스팅들은 지우고 새롭게 블로그를 꾸밀까 고민했지만 Web과 FrontEnd에 대해서 본격적으로 공부하기 시작했을 때의 모습들을 기억하고 싶어 그대로 가져오게 되었다.

처음엔 private 코드로 개인만 사용하는 블로그를 만들 생각이였지만, 그래도 만드는김에..! 라는 생각과 나와 같이 미니멀하고 성능이 괜찮은 블로그를 쉽게 만들기를
원하는 사람이 있지 않을까라는 기대에 [astro-fox](https://github.com/LimChaeJune/astro-fox)라는 blog template으로 만들게 되었다.

다음 기능들을 고려해서 새로 만들게 되었다.

## 좋은 점

1. 다양한 웹 라이브러리 (SolidJS 사용) 지원하기 때문에 커스텀이 용이하다.
2. markdown, mdx 형식을 지원하고 Astro의 Image 컴포넌트를 사용해 이미지 최적화가 수월하다.
3. SSG을 기본으로 하며, 필요시 부분적 하이드레이션(Partial Hydration)을 통해 동적인 요소를 추가할 수 있어 초기 로딩 속도와 페이지 전환 속도가 매우 빠른 편이다.
4. OpenGraph 커스텀이 가능하다.
5. sitemap, rss 등 검색 엔진 최적화에 필요한 기능들을 지원한다.

## 아쉬운 점

1. 배포와 도메인을 Vercel, Netlify 등에서 직접 관리해야 한다.
2. 공식적인 댓글 기능을 지원하지 않는다.
3. 공식적인 검색 (Agolia, PageFind) 또한 없다.

현재까지론 단점보다는 장점이 더 많아 보이는 블로그 이주로 보인다.</content:encoded><category>Blog</category><category>Astro</category><author>jt_fox</author></item><item><title>4년차 프론트엔드 개발자의 2025년 회고</title><link>https://jtfox.dev/blog/2026-01-05/</link><guid isPermaLink="true">https://jtfox.dev/blog/2026-01-05/</guid><content:encoded>2026년의 첫 블로그 글을 2025년의 회고로 작성하게 되었다.

2025년을 단어로 축약하자면 &apos;고난&apos;과 &apos;달성&apos;이 아닐까 싶다.

3년간 어려운 순간들도 많았지만 병역특례를 무사히 종료했고,
끝나지 않을거 같던 고통의 치아 고문도 무사히 완료되어서 만족스러운 치아 배열을 가지게 되었다.

또, 4개월 가량 이직을 준비했고, 47개의 회사에 지원했고, 9개의 코딩테스트와 과제를 거치고, 5번의 면접 끝에
현재 이직한 회사에 입사하게 되었고, 그 과정에서 여러가지를 느끼고 성장할 수 있었다.

이직과정에서 느낀 것은 시장이 정말 안 좋고 AI로 인해 원하는 인재의 기준이 많이 높아졌음을 느낄 수 있었다.
내가 겪은 시행착오들과 약간의 팁들은 이직기로 포스팅할 예정이다.

## 올해의 로그

```
- 이직🙂
- 병역특례 복무만료
- 교정 끝 (2년 6개월)
- 오른팔 철심 제거 수술 (3월에 함 지금은 완치)\
- 연말 브아솔 콘서트
- 오픈소스 2회 기여(간략하긴 하다.)

- 대외활동: FEConf Lightning Talk 연사, Prography 2026 운영진 합류, Profit Hackathon 스태프 참여
- 책: 순전한 기독교, 이토록 사소한 것들, 쓸만한 인간, 눈물을 마시는 새, 바다가 들리는 편의점
- 주식: AI 인프라 섹터 투자 시작, SCHD 전량 매도 (절반의 성공)
- 이사: 서초(월세) -&gt; 안양(전세)
- 여행: 강릉, 제주도, 추천, 경주, 원주
```

## 사람과의 관계

2025년은 사람들과 많은 약속 자리를 가지려고 노력한 한 해였다.
이유는 사실 간단하다. 내가 친하고 편안하게 생각하는 사람들과 더 많은 시간을 보내고 싶었다.

사람에게 연락을 한다는 것은 내가 그 사람을 생각한다는 말도 된다.
그렇게 생각하니 이따금 연락을 주는 분들에게 너무 감사한게 아닌가 싶다. 따라서 나도 연락이 뜸했던 분들에게도 연락을 드리고, 안부를 물었다.
여러 사람을 만나면서 커피챗도 하고, 상담도 받다보니 정말 세상이 좁고 같은 바운더리에 있는 사람들은 다시 만날 수 밖에 없다는 것을 깨달은 시간들이었다.

작년에 처음으로 행복이란 것이 무엇인지, 내가 행복하기 위해선 어떤 일들을 해야하는지 고민해보니
당장은 내가 좋아하는 사람들과 시간을 보내는 것이 큰 부분이라고 생각이 되었다.

## 기술

`AI, AI, AI...`라고 말할 수 밖에 없다. 거의 모든 개발자들이 AI 기술에 대한 관심도가 높아졌다.
그래서 각자 나름의 파이프라인을 만들기도, 프롬프트 작성법을 공유하기도 한다.
그런 정보들도 너무나 많이 쏟아지고, 이것들을 선별하기도 어려울 뿐더러 모델의 버전이 변경되면 그 정보들이 무용지물이 되는 경우도 많다.

AI를 이렇게 많이 사용할 수록 인간의 고유한 가치 예를 들어 &apos;협업&apos;이나 &apos;소통&apos; 등이 중요하다고 느껴진다.
하지만 흥선대원군이 될 수 없지 않은가 나만의 AI 사용법을 정리하고, 비용을 투자해서 어느정도의 효율성을 높이는 작업을 할 수 밖에 없다.

FE의 기술은 React 19와 NextJS에서 일어난 보안 사건들을 보면 FE와 BE에 경계가 섞이면서 나는 부작용들이 나오고 있다.
RSC/Server Actions 등을 쓰면 사실상 FE가 아니라 서버 앱이 되어버린다.
여기서 우리가 고민해야할 지점은 격리의 레벨을 어떻게 하고 FE에 경계를 어디까지 산정할 것인가 고민해야되는 시점이라고 생각한다.

FE의 패러다임이 바뀔 수도 있다고 생각한다.

## 주식

나는 ETF를 적립식 매수하면서 꽤나 안정적인 수익을 내고있었다. 특히 SCHD로 받는 분기 배당금이 꽤나 쏠쏠했다.
그런데 올해 8월 즈음에 주식 섹터를 공부하고 회사 동료의 주식 전망 이야기를 들어보니 내 나이에 너무 소극적인 투자를 한 게 아닌가 고민하게 되었다.
물론 다른 사람의 말을 듣고 내가 공부 안 한 종목을 살 생각은 없었고, 여러 섹터에 대한 공부 끝에 SCHD를 전량 매도하고
조금 더 공격적인 투자로 방향을 잡았고 현재 꽤나 순항중이다.

물론 헷지를 위한 ETF, 금, 코인 등 여러 자산을 분산하고 있고 개별주의 비중을 조금 늘린 것이라고 생각한다.
단기적 주가 조정에 흔들리지 않고 장기적으로 그 기업의 펀더멘탈을 분석하고 미래에 이룰 수 있는 일들을 상상하며 투자하고 있다.
투자자로서 새로운 마음가짐도 있고, 큰 리스크에 부담도 있지만 찾아온 해에는 더욱 주식 공부와 종목 분석에 매진해서 나름의 성과를 내는 것도 목표이다.

## 여행

애인과 정말 많은 곳을 여행했다. 매년 해외로 한번 씩 나갔는데 이번에는 제주도를 가고 여러 국내 여행지를 돌아다녔다.
혼자서는 그렇게도 심심하던 여행이 애인과 가면 그렇게나 즐거울 수 없다. 시시콜콜해도 즐겁고 간단한 간식을 함께 먹어도 즐거웠다.
또, 내가 이직을 준비한다고 조금 예민하고 더 잘해주지 못해서 미안한 마음도 있다. (이 글을 볼지 모르겠지만 항상 미안하고 고맙다.)

최근 여행을 가면서 느낀 점은 여행지에서 주는 특별함도 있지만 함께 간 사람과의 추억이 쌓이는게 정말 큰 즐거움으로 쌓이는거 같다.
가족과 함께 여행을 가서 다투는 것도 어느 순간에는 그립고 추억이 될거라고 생각하며 앞으로도 더욱 많은 추억을 쌓아야지. 그럼 앞으로도 열심히 돈 벌어야겠지?

## 좋은 개발자란?

AI가 나오기 전에는 `좋은 개발자 = 기술적으로 뛰어난 개발자`라고 생각했다. 그런데 이제 많은 지식과 인사이트를 AI에 의존할 수 있고,
단순 작업을 직접 하는 개발자는 거의 없을거라고 생각한다.
이제 4년차 개발자로서 시니어를 향해가는데 노선을 잘 정해야 된다고 생각이 되는 지점이다.
좋은 개발자를 판단하는 기준은 앞으로 더욱 넓어질거라 생각된다.

`협업을 잘하는 개발자`, `기술을 많이 아는 개발자`, `지식을 공유하는 개발자` 등 나만의 특장점을 가져야 한다고 생각하고 앞으로는
`기술적으로 뛰어나지만 같이 일하기 힘든사람`은 절대 좋은 개발자 취급을 못 받을거라고 생각한다.
2026년에는 여러 사람과 생각을 공유하고 다방면으로 지식을 넓이는 쪽으로 방향을 잡아보려고 한다.

## 독서

진짜 지지리도 안 읽었다. 바쁘다는 핑계로 참작이 안되는게 잘 때 유튜브 쇼츠를 거의 중독자 수준으로 봐서
수면 시에 읽었던 책 양이 그대로 증발했다. 이제 수면의 질을 위해서라도 수면은 책과 함께하는게 새해 목표이다.

2026년에는 내 자아를 찾는 책과 종교적, 철학적 책을 읽으며 생각을 정리해보려고한다.

## 이사

[이사](/log/2025-05-01-record)에 대해서는 월간 기록에 나름 자세히 썼기 때문에 생략..

## 이 글을 보시는 분들께

누군가에겐 힘들었고, 누군가에겐 행복했던 한 해가 지나갔습니다. 때로는 절망스럽고 힘든 인생이긴 해도 여러분 나름의 행복을 찾으시길 바랍니다.
힘든 과정에도 여러 행복들이 있고 주변에 작은 부분들에 행복해하는 사람이 되시길 바랍니다.

그리고 어느 책에서 봤는데 복을 많이 받으라는 사람 보다는 복을 많이 주는 사람이 되겠다고 이야기 하는 사람이 더 매력적으로 보이더라구요? (내가 복주머니가 된 기분이랄까?)

**2025년 모두 고생 많으셨고 새해에는 더욱 복 많이 주는 채준이 되겠습니다.**</content:encoded><category>회고</category><category>2025</category><category>일상</category><author>jt_fox</author></item><item><title>&apos;내&apos; 코드와의 이별</title><link>https://jtfox.dev/blog/2026-02-09/</link><guid isPermaLink="true">https://jtfox.dev/blog/2026-02-09/</guid><content:encoded>오늘 회사에서 개발을 하면서 갑자기 가슴이 쿵쾅거리기 시작했다.
이유는 즉슨 오늘 단 한줄의 코드도 치지 않고 개발을 하고 있다는 사실을 깨닳았을 때이다.

여러 기능을 만들고, 동시에 리팩토링을 진행하고, 웹 개발을 진행하며 모바일의 CI/CD를 구성하는 등
이전에는 하나에 몇 일, 몇 주씩 걸리던 일들을 한 순간에 이뤄내고 있었다.
이 지점에서 소름이 돋았다. 내가 지금 &quot;내 의사대로 움직이고 있는 걸까?&quot;

그 질문은 단순한 감상이 아니었다.
마치 아주 오래 알고 지내던 무언가와 이별 직전에 던지는 마지막 확인 같았다.

## 작성자에서 선택자로

나는 분명 결정을 하고 있었고,
무엇을 만들지, 어떤 구조로 갈지, 무엇을 버릴지까지 판단하고 있었다.
하지만 그 판단의 결과물은 더 이상 &apos;내 손&apos;에서 나오지 않았다.

AI는 내가 생각한 방향을 순식간에 코드로 만들어냈고, 나는 그 코드를 읽고, 고치고, 다시 방향을 제시했다.
손은 움직이지 않았지만, 일은 전보다 훨씬 빠르게 앞으로 나아갔다.

예전의 개발은 명확했다. 코드를 치는 만큼, 제품이 만들어지는 만큼 나였다.
에러를 만나면 나의 안일함이었고, 구조가 어지러우면 나의 사고가 정리되지 않았다는 증거였다.

하지만 지금은 다르다. AI는 내가 미처 생각하지 못한 구현을 제안하고, 찾지 못했던 오류를 손쉽게 찾아낸다.
나는 더 이상 `&apos;작성자&apos;가 아니라 &apos;선택자&apos;` 가 되어가고 있었다.

## 나에게 묻는 질문

그래서 질문을 바꾸어 보았다.

&quot;이 코드가 내 코드인가?&quot; 가 아니라 &quot;이 선택이 내 선택인가?&quot;

어쩌면 우리가 집착해왔던 &apos;내 코드&apos;라는 개념은 개발자의 본질이 아니라 시대가 요구하던 역할이었을지도 모른다.

과거에는 (비교적 최근까지) 아래와 같은 것들로 실력을 판단했다.

- 빨리 작성하는가

- 더 많은 개발적 지식이 있는가 (디자인 패턴, 아키텍처)

- 함께 일할 수 있는 좋은 코드를 만드는가

하지만 AI 시대의 개발은 점점 이렇게 변하고 있다.

- AI로 생성된 결과물 중 옳은 것을 고를 수 있는 분별력

- 이 코드가 왜 존재해야 하는지 설명할 수 있는 능력

- 선택이 불러올 파장을 파악하는 능력

이제는 손의 숙련도보다 사고의 정렬이 더 중요해진다.

## 정체성의 변화

현재 느끼는 감정은 &apos;일자리를 빼앗길지도 모른다&apos;는 공포보다는 정체성이 바뀌고 있다는 신호에 가까웠다.

나는 더 이상 &quot;코드를 잘 치는 사람&quot;으로 나를 설명할 수 없게 되었고

이런 질문을 스스로에게 던지게 된다.

- 기능의 동작을 보장할 수 있는가?

- 이 구조를 내가 이해하고 누군가에게 설명할 수 있는가?

- 이 결정의 책임을 내가 질 수 있는가?

## 처음으로 되돌아가서

그리고 다시 처음의 질문으로 돌아온다.

내가 지금 하고 있는 선택은,
정말 &apos;내 의사&apos;일까?

손을 내려놓는 대신 판단을 떠맡는 시대.

나는 아직 그 시대의 답을 모르지만,
적어도 이 질문을 붙잡고 있다는 사실만은 기록하고 싶었다.

## 선택의 의미

그 순간부터 불안의 결이 조금 달라졌다. 
이건 기술에 대한 두려움이 아니었다. 의사와 관계로 만들어질 개발자라는 직업의 격변에 대한 불안이었다.

장폴 사르트르는 인간을 이렇게 말한다. &apos;우리는 선택의 결과이며, 그 선택으로 스스로를 정의한다.&apos;

앞으로 중요한 것은 &quot;만드는 것&quot;이 아니라 &quot;결정하고 책임지는 것&quot;이 된다.

AI가 코드를 작성하고, 나는 고개를 끄덕이며 승인했다. 그 선택은 빠르고 합리적이었고, 아무런 문제도 없어 보였다.

사르트르식으로 말하면, 그 순간 나는 이미 선택하고 있었다. 선택하지 않은 척하면서.</content:encoded><category>AI</category><category>철학</category><author>jt_fox</author></item><item><title>개발자가 주식하는법</title><link>https://jtfox.dev/blog/2026-06-05/</link><guid isPermaLink="true">https://jtfox.dev/blog/2026-06-05/</guid><content:encoded>## 투자를 시작한 계끼

15살 정도 되었을 때 성남 한적한 빌라에서 살던 소년은 생각했다.
`나는 커서 무엇이 될까?` 그 아이는 커서 예쌍치도 못하게 개발자가되어 먹고 살고있다.
지금 나의 모습이 과연 15살 소년이 바라던 미래였을까?.
아마 아닐 것이다. 그 때에는 경제 개념이 잡히기 전이었고, 현재 나이가 굉장히 많은 나이라고 생각했다.
그래서 이 나이쯤 되면 막연하게 부자가 되어있지 않을까? 라는 

25살의 청년은 현실을 깨닳고 투자를 시작했다. 공격적이진 않았지만, 꾸준하게 예금 이상의 수익을 낼 수 있는 구조로 말이다. 하지만, 그 것에</content:encoded><category>주식</category><category>투자</category><author>jt_fox</author></item><item><title>May Log (2025)</title><link>https://jtfox.dev/log/2025-06-03-record/</link><guid isPermaLink="true">https://jtfox.dev/log/2025-06-03-record/</guid><content:encoded>import { Image } from &quot;astro:assets&quot;;
import jeju1 from &quot;public/images/log/2025-06-03/roymay.jpg&quot;;
import jeju2 from &quot;public/images/log/2025-06-03/udong.jpg&quot;;
import jeju3 from &quot;public/images/log/2025-06-03/ocean.jpg&quot;;
import jeju4 from &quot;public/images/log/2025-06-03/hanlim.jpg&quot;;

## 제주도

여자친구와 2주년을 기념으로 함께 제주도 여행을 가게 되었다.
처음으로 함께 가는 제주도 여행이었고 가장 큰 계획은 사진관에서 사진 찍기, 한옥에서 숙박하기 였다.

&gt; 이 글에 나오는 모든 곳은 나와 관련이 없으며, 직접 즐기고 온 기억을 남기고 공유하고 싶어서 작성하는 것이다.

### 사진관

`소랑 사진관`은 인스타에서 다양한 배경을 찾아보다가 발견한 사진관인데, 배경이 가장 다양하고
가격도 합리적이어서 선택했고, 가서 사진촬영할 때 사진사 분이 리드를 잘 해 주셔서 결과물이 만족스럽게 나왔다.

### 한옥

외관은 한옥이고 내부는 깔끔한 한옥을 찾아 예약했는데 주변 조경도 이쁘게 꾸며 놓았고,
바베큐도 가능한 펜션 느낌이였다. 가격대비 무난했다.

### 음식

가장 기억에 남았던 음식점은 가정식 중식을 판매하는 &quot;로이앤 메이&quot;라는 음식점이었다.
일상에서 접하기 힘든 색다른 음식들이 많이 나오고 여기 마파두부는 진짜 포장해서 집에 가져가고
싶을 정도로 맛있었다. 두부에 무슨짓을 하신 건지 진짜 너무 부드럽고 맛있었다,,

&lt;Image src={jeju1} alt=&quot;로이앤 메이&quot; /&gt;

### 유동커피

집에 가는 비행기를 타기 전에 남은 시간이 있어 커피를 마시러간 곳이다. 딱히 엄청 찾아서 간 곳은
아니었는데 생각보다 컨셉이 확실하고(소금공장을 카페로 개조한 것이이라 한다.) 커피 맛도 준수했다 커피를
탄광모양의 얼음으로 줘서 녹여먹는 재미도 있었다.

&lt;Image src={jeju2} alt=&quot;유동커피&quot; /&gt;

### 한림공원

협재 쪽에서 점심을 먹고 갈 곳을 찾다가 한림공원을 가게 되었는데 의외로 즐길 거리가 참 많았다.
처음보는 식물들도 굉장히 많았고 유채꽃 밭에서 사진도 많이 찍었다.
그리고 공작새를 이렇게 가까이서 보는 것은 처음이었다.
또한 파충류관에는는 귀여운 거북이와 도마뱀들이 있어서 눈이 즐거웠다. (여자친구는 싫어함...)

&lt;Image src={jeju4} alt=&quot;한림공원&quot; /&gt;
&lt;Image src={jeju3} alt=&quot;바다&quot; /&gt;

## 도둑맞은 집중력

유명한 책인데 이제야 읽기 시작했다.
주변에 지인 모두 이 책을 읽으면서도 (집중력을) 도둑 맞았다고 얘기를 많이 들었는데 나는 과연 그러지 않을 수 있을지...
평소 `장동선의 궁금한 뇌` 채널 영상을 즐겨보는데 얼마 전에 집중력에 대한 영상 또한 재밌게 본 기억이 있어서 읽기 시작했다.

뭔가 머리에 너무 많은 생각이 들어서 그런지 집중력이 하락하는게 느껴지기도 하고,
독서에 대한 열정도 떨어지는 것 같아 이 책으로 이런 문제들에 대해서 다시 한번 생각해보고자 한다.

## 주차 등록

안양에서 강남으로 출퇴근한지 어느새새 1달 정도 되었는데 결국 대중교통의 고통에 벗어나고자
주차 등록을 해버렸다... 당장에 금액적인 부담은 물론 있겠지만 이로 인해
시간을 절약하여 더욱 의미있는 곳에 사용하고자 비용을 감수하고 등록하게 되었다.</content:encoded><author>jt_fox</author></item><item><title>지식은 흐른다.</title><link>https://jtfox.dev/log/2025-06-12-record/</link><guid isPermaLink="true">https://jtfox.dev/log/2025-06-12-record/</guid><content:encoded>최근 유튜브에서 흑백요리사로 유명해진 안성재 쉐프의 말을 듣고 크게 공감한 구절이 있었다.

&gt; **지식은 흘러야 한다**

평소 안성재 쉐프의 프로다움을 느끼고 배울 점이 많다고 생각했는데, 이 말이 특히 와닿았다.

내 경력이 길지는 않지만, 학생 시절 프리랜서로 일하면서 종종 목격했던 광경이 있다.
나이 많고 경력 있는 시니어 개발자들이 정보나 지식을 알려주는 걸 꺼려하고,
본인만이 아는 지식인 양 어필하는 경우들이었다. (비방목적의 글이 아니다)

지금처럼 정보의 바다에서 원하는 정보를 골라 학습하고, LLM으로 편하게 원하는 답을 찾고, 오픈소스를 통한 학습이 자연스러워지기 전까지는 상황이 달랐다.
과거엔 번역본이 많지 않은 책과 외국 개발 사이트(StackOverflow, 블로그) 정도가 주요 학습 수단이었을 것이다.

이런 환경에서 개발자들은 크게 두 부류로 나뉘었을 거라고 생각한다.

**첫 번째**: 지식을 얻고 공유하는 사람들  
**두 번째**: 지식을 얻고 혼자만의 것으로 간직하는 사람들

지식을 공유하려면 학습 과정에서 응용하고 해석하는 단계를 거쳐야 한다. 후자에게는 이런 과정이 전혀 없었을 것이다.

지식을 흘려보내지 못하면 자연스럽게 고이게 된다. 자신이 알고 있는 지식만으로 상황을 해결하려 하고, 
그 지식으로 우월감을 느끼던 개발자들은
현재 시점에서 **아무도 쓰지 않는 전자사전을 들고 있는 것**과 다름없다.

반대로 당시에 지식을 공유하고 설파했던 개발자들은 지금도 새로운 기술을 받아들이며 많은 사람들에게 가치를 전달하고 있을 것이다.

---

이는 비단 개발자만의 이야기가 아니다.
모든 분야에서 지식은 고이지 않게 흘려보내고, 새로운 강물을 받아들여 넓은 바다 같은 사람이 되어야 한다고 생각한다.

결국 **지식의 독점이 아닌 순환**은 개인과 전체를 발전시키는 동력이 된다.</content:encoded><author>jt_fox</author></item><item><title>June Log (2025)</title><link>https://jtfox.dev/log/2025-07-09-record/</link><guid isPermaLink="true">https://jtfox.dev/log/2025-07-09-record/</guid><content:encoded>import { Image } from &quot;astro:assets&quot;;
import mintlife1 from &quot;public/images/log/2025-07-09/mintlife1.png&quot;;
import mintlife2 from &quot;public/images/log/2025-07-09/mintlife2.png&quot;;

조금 뒤늦게 6월을 기록하게 되었다.
같은 팀의 FE 개발자 분이 출산휴가를 가게 되어 정신없이 보낸 한달이었다.

### 철학

최근 `너진똑`이라는 유튜버의 니체 영상을 출퇴근 시간에 반복해서 듣고있다.
니체라는 사람은 `망치의 철학자`로 불리며 쇼펜하우어, 소크라테스, 플라톤 등 여러 철학자들에 대해
비판하는걸 듣게 되면서 다른 철학자들의 이야기도 궁금해서 요즘 최대 관심사는 철학에 대한 얘기이다.

사실 철학에 대해서 설명하기엔 지식의 깊이가 너무 얕아 추후에 블로그 글로 주기적으로 글을 써볼 생각이다.

철학은 본질적으론 &quot;지혜를 배우는 학문&quot; &quot;슬기를 탐구하는 학문&quot; 이라고 얘기한다.
많은 철학자들은 인간에 대한 본질, 세상을 살아가는 방법에 대해 이야기한다.
하지만 대부분의 유명한 철학자들은 이미 세상을 떠난지 오래 되었다. 그로 인해 시대적 상황과 나라와 환경 등이 오늘날과 달라 그 사람들의 말을 그대로 받아들이게 되면
이해가 가지 않거나 오해로 받아들이게 되는 경우가 많아 이 부분을 경계하면서 철학자들의 말을 생각해보는게 중요하다. (이 과정에서 저자의 의도를 왜곡하면 안된다.)

철학에 관심을 가지게 되는 이유를 생각해보면, 명확한 계기라 말하긴 힘들지만 전문연이 끝나게 되면서
앞으로의 커리어와 내가 개발자, 그리고 한 사람으로서 어떻게 성장하고 살아가야 할지에 대한 &quot;진리&quot;가 필요하다는 생각이 들었다.
그러면서 사람이 종교를 가지게 되는 이유, 종교가 사람에게 주는 영향, 사람을 대하는 방식, 일을 대하는 방식 등에 대해 고찰하다보니 자연스레 철학을 접하고 있는 것 같다.

나만의 &apos;진리&apos;를 찾는 &apos;항해&apos;를 시작했다. 어떤 섬에 도착할진 모르겠지만 닻이 닿는 곳으로 멈추지 않고 나아가야 한다.

### FEConf

5월에 신청한 FEConf Lightning Talk의 연사로 선정되었다.(사실 5월 말에 소식을 받게 되었는데 늦게 작성,,,)
작년에 참여한 FEConf에서 새로운 개념들을 접하고 여러 연사자 분들의 발표를 듣다보니 내년엔 나도 저 자리에서 발표를 하고싶다는 생각이 들었다.

스피커로 신청할까도 고민했지만 아직 긴 발표 경험이 없어 이번엔 짧은 발표를 하는 것으로 정했다.
회사 외에 불특정 다수에게 발표하는 것은 처음이라 긴장되기도하고 부담이 되기도 한다.

하지만 발표 또한 경험이 쌓일수록 실력이 늘고 자신감도 생긴다고 생각 해 앞으로도 기회가 생긴다면 적극적으로 참여해 봐야할 것 같다.

### mac

얼마전 mac mini(m4 24GB 528GB)를 구매하게 되었다.
이유는 집에서 윈도우 피씨만 있고, 예전에 사용하던 인텔 맥북은 수명을 다했는지 배터리 성능이 저하되고, 오른쪽 포트가 안되는 현상이 있어 맥 환경을 집에서 사용하기 위해 구매했다.

졸업자도 대학원 할인이 가능해서 학생 할인을 사서 구매했는데 굉장히 만족하면서 쓰고있다.
벤치를 측정한건 아니지만 체감되는 성능은 윈도우 피씨에 비해 2배는 좋아진 것 같다.

또 obsidian을 icloud로 동기화해서 사용 중인데 윈도우 피씨에선 동기화가 잘 안되는 버그가 있었다.
mac으로 넘어온 후로는 굉장히 쾌적하게 사용하고 있다.

아쉬운건 usb-a 포트가 없어서 허브랑 연결해서 사용하면 선 정리가 깔끔하게 안된다.
Mac Mini용 Dock을 사자니 중국산이 많고 블루투스 성능이 떨어진다는 후기가 많아 현재는 심미를 포기하고 사용하게 될 것 같다.

하지만 컴팩트하고 귀여운 디자인은 역대급으로 잘 뽑은거 같다.

### 페스티벌과 공간의 의미

뷰티풀 민트라이프라는 페스티벌에 다녀왔다. 낮에는 너무 뜨거워서 양산을 쓰고 있었지만 해가 진 이후로는 시원하게 즐길 수 있었다.
이렇게 야외에서 공연을 보는건 처음이었는데 공연장에서 보는 것과는 큰 차이가 있었다.

야외 공간이 주는 많은 관객의 뜨거운 열기와 자유로움 여러가지 팝업들과 이벤트로 중간중간 쉬는 시간에도 관객들이 지루하지 않게 해주는 컨텐츠가 많았다.
돗자리 깔고 편하게 음식을 먹으면서 내가 좋아하는 가수들의 노래를 듣는게 조금은 비현실 적이기도 하면서 너무 큰 추억이 되었다.

이번에 페스티벌을 다녀오면서 사람에게 공간이 끼치는 영향이 굉장히 크다는걸 깨달았다.
콘서트장은 조명을 무대쪽에만 집중하고, 관객은 무대를 둘러싸는 돔형태로 이루어져있어 온전히 무대를 집중할 수 밖에 없다.

어두운 공연장과 다르게 밝은 야외는 사람들의 행동, 분위기를 더욱 밀접하게 느낄 수 있게 되어 나도 쉽게 동조되기도 하고
공연장 말고도 시야의 범위가 넓고, 타인과의 거리가 있어 분위기에서 빠르게 빠져나와 옆 사람과 이야기를 나누면서 음식을 먹기도 좋았다.

이런걸 보면 회사, 집 모든 공간들이 주는 의미가 굉장히 크다는걸 느끼게 된다 최근 `유현준` 교수님의 `공간이 만든 공간`을 읽게 되면서,
내가 지내는 공간과 새로 맞이하는 공간의 의미에 대해서 생각을 많이하게 되는 것 같다.

&lt;Image src={mintlife1} alt=&quot;mintlife1&quot; /&gt;
&lt;Image src={mintlife2} alt=&quot;mintlife2&quot; /&gt;</content:encoded><author>jt_fox</author></item><item><title>July Log (2025)</title><link>https://jtfox.dev/log/2025-08-22-record/</link><guid isPermaLink="true">https://jtfox.dev/log/2025-08-22-record/</guid><content:encoded>굉장히 늦은 7월 기록을 시작한다..
나의 나태함과 게으름에 질려버리는 요즘이다.
해야할 것들은 많고, 책임은 무거워지는데 8월의 변덕스러운 날씨와 같이 감정이 요동치는 것 같다.

### FEConf 발표 준비

8월 23일(작성 날짜 기준으로 명일)에 Lightning Talk에 연사자로 참여한다.

회사에서 경험했던 일화를 바탕으로 발표를 구성했고, 그 당시에 내가 느꼈던 감정이 흐려질 정도로 시간이 흘렀다.
이런 떠나간 감정들을 기억하기 위해서는 글을 꾸준히 쓰는 수 밖에 없는 것 같다.
그래도 발표 자료를 만들면서 그 당시에 작성한 문서들과 작업 내역들을 보니 어느정도 기억이 되살아나서 나름 재밌게 얘기할 수 있는 자료를 만든 것 같다.

이렇게 불특정 다수에게 발표하는게 처음이라 긴장이 되기도 하지만 내 경험을 전달하고 공감받는 것에 대한 기대감도 충만해진다.
9월에 만족스러운 발표 후기를 작성할 수 있기를 바래본다.

### 교정

무려 2년 6개월만에 교정이 끝났다! 🥳

교정 때문에 입병도 많이 나게 되고, 충치도 많이 생기고, 진짜 눈물나게 힘들었다.. 🥲
하지만 앞에 힘듬들이 상쇄될 만큼 애프터 사진을 보니 굉장히 만족스러운 치료였던것 같다.
치아가 고르고 이쁘게 나는건(나도 나는건 이쁘게 났는데 돌출입이...) 큰 복중 하나라는데 옛날 선조들에 말에 굉장히 공감하는 바이다...

앞으로 유지장치를 최소 5년간 써야겠지만 일단 교정기와 비교했을 때는 굉장히 편해졌고, 밥 먹을 때 입 안이 깔끔한 느낌은 진짜... 감격스러울 정도이다.

교정과정에서 충치가 좀 생겨서 아직 치과를 몇 번 더 방문해야겠지만, 끝이 났다는 거에 너무 행복하다ㅠ

### 순전한 기독교

거의 반 년동안 다른 책과 함께 읽은 것 같다.. &quot;나니아 연대기&quot;와 논리적인 변증가로 유명한 C.S.루이스에 저서이다.
특이하게도 기독교 -&gt; 불신론자 -&gt; 기독교로의 복귀를 한 인물이다.

첫 번째 장에서는 내가 들어본 것중에 가장 논리적인 기독교의 근거를 제시하게 된다.
초등학생 시절 교회를 가서 목사님에게 하나님이 진짜 있느냐고, 증거가 있냐고 물었던 기억이 난다.
그 때 목사님은 초자연적인 대답을 해주셨고, 그 당시 나는 아무 흥미를 느끼지 못하고 교회를 안가게 되었다.

그런데 이 책을 읽으면서 그 때의 질문들이 다시 떠올랐다. 하나님은 어떠한 형태로 존재하는거? 그리고 우리 내면에 도덕은 어디로부터 왔는가? 라는
질문에 대답을 실제 사람의 행동을 기반한 논증으로 제시해 주는게 그게 굉장히 흥미로웠다.

그리고 두 번째 장과 세 번째 장은 기독교에 대한 오해와 및 교리와 관련되 내용들이 있었는데 그건 기독교인 아닌 사람도
공감하면서 읽고, 나의 삶의 행동들을 되돌아보는 시간을 가질 수 있을 것 같다.

마지막 네 번째 장은 하나님을 믿고 있고, 성경의 배경을 이해하는 것을 가정하에 풀어낸 이야기라 사실 이해가 잘 가지 않았다.

다음은 불교에 관련된 책을 한번 읽어볼까.

### 눈물을 마시는 새

예전에 책으로 좀 읽다가 말은 눈물을 마시는 새가 `오디오 북`으로 나왔다는 얘기를 듣고 출퇴근 시간에 틈틈히 들었다.
결론은 한달 사이에 완청?이라 해야할까 모두 들었다.

진짜 이 글이 2002년에 연재 되었다는게 믿기지 않을 정도로 세련된 문장들과 탄탄한 스토리와 너무나 깊은 서사를 가진 캐릭터들 까지..
나는 눈마새 덕후가 되어버린거 같다...
온 세상 사람들에게 눈마새 얼른 보라고 니르고싶어진다

정말 한국의 반지의 제왕이라고 불려도 손색이 없을 정도로 (참고로 나는 반지의 제왕 덕후임) 스토리가 방대하고 허점이 거의 없다고 느껴진다.
그리고 종족 간의 개성과 약점 그리고 왕을 바라보는 관점과 눈물을 마시는 새에 대한 이야기도 너무나 특색있고, 한국적인 맛도 많이 가미되어있다.

여러분 눈마새 얼른 보세요.</content:encoded><author>jt_fox</author></item><item><title>August Log (2025)</title><link>https://jtfox.dev/log/2025-09-03-record/</link><guid isPermaLink="true">https://jtfox.dev/log/2025-09-03-record/</guid><content:encoded>import { Image } from &quot;astro:assets&quot;;
import feconf_1 from &quot;/public/images/log/2025-09-03/feconf_1.jpg&quot;;
import feconf_2 from &quot;/public/images/log/2025-09-03/feconf_2.jpg&quot;;
import feconf_3 from &quot;/public/images/log/2025-09-03/feconf_3.jpg&quot;;

드디어 조금은 선선해지는 9월에 8월 기록을 시작한다.
벌려둔 일들이 하나씩 정리되고, 새로운 일들을 시작하는 달이다.

## FEConf 2025 발표

8월 24일에 Lightning Talk에서 `1인 주니어 개발자의 생존기`라는 제목으로 발표를 했다.

작년에 원형 테이블에 10~15명 정도 앉아서 발표를 듣고 질의응답을 하는 형식이었는데, 올해는 일반 Speaker 분들의 발표처럼
강단 같은곳에 올라가서 100명 정도의 청중을 앞에 두고 발표를 하게 되었다.

생각보다 큰 발표장에 긴장이 많이 되고 식은땀이 나기도 했지만, 다행히도 발표를 시작하니 생각보다 집중이 되어서 그런지 전달하고 싶은 바를 잘 전달할 수 있었다.
작년에 참여자로 처음갔던 컨퍼런스에 스피커로 참여한게 감회가 새롭기도하고, 여러 사람들이 내 이야기를 들어주는 것에 대한 희열도 느꼈다.

### 네트워킹 존

발표 후에는 네트워킹 존이 있어서 내 발표를 들어주신 분들과 다른 연사자 분들과 이야기를 나눌 기회가 있었는데 주니어 개발자 분들의 비중이 많고,
커리어에 대한 고민과, 이직을 생각하시는 분들의 이야기를 많이 나눌 수 있었다.

근데 확실히 시장이 좋진 않은지 모두 이직에 어려움을 토로하셨고, 그에 대한 해결책을 같이 고민하면서 나는 이직을 어떻게 준비해야할지 다시 한번 생각해보게 되었다.
평소에 링크드인을 적극적으로 하진 않았는데 네트워킹 존에서 자연스럽게 링크드인을 교환하고, 후기에 대한 첫 글도 작성했는데 생각보다 많은 분들이 관심을 가져주셔서 감사했다.

&lt;div className=&quot;grid grid-cols-2 gap-4&quot;&gt;
  &lt;Image src={feconf_1} alt=&quot;FEConf 발표&quot; className=&quot;my-4 rounded-lg&quot; /&gt;
  &lt;Image src={feconf_2} alt=&quot;FEConf 발표&quot; className=&quot;my-4 rounded-lg&quot; /&gt;
&lt;/div&gt;

### 토스 프라이빗 파티

컨퍼런스 세션이 모두 끝난 뒤에 토스에서 연사자들과 일부 참여자를 추첨으로 뽑아서 프라이빗 파티를 주최했다.
평소 글이나 유튜브에서 보던 분들을 실제로 만나서 이야기를 나눌 수 있는 기회는 너무 좋았고 재밌었다.
토스의 리더 분들이 토론하는 것도 실시간으로 듣고, 조끼리 나눠서 자유롭게 토론하는 분위기도 즐겁고 다른 분들의 넓은 생각을 들을 수 있는 기회였다.

식당이 돌잔치를 많이 하는 곳이라 그런지 생각보다 음식이 잡다하게 나오긴 했지만 맛은 있었다 ㅋㅋㅋ

&lt;Image src={feconf_3} alt=&quot;FEConf 발표&quot; className=&quot;my-4 rounded-lg&quot; /&gt;

## 전문연구요원 복무만료

2022년 8월부터 시작했던 전문연구요원 생활이 끝나게 되었다.
시작할 때는 복무가 만료되면 굉장히 큰 변곡점이 올 것 같았는데 생각보다 내 생활은 크게 바뀌지 않았다.

하지만 마음가짐이 조금은 달라진 것 같다.

회사를 다니고 있지만, 내가 군 복무를 하고 있고, 다른 곳으로 가는데 자유롭지 못한다는 생각이 있었는데,
이제는 그런 제약이 없어진 만큼 더욱 적극적으로 미래를 설계하고, 행동할 수 있는 자유가 생긴 것 같다.

앞으로 어떻게 삶을 꾸려나갈지에 대한 두려움도 있고, 걱정도 있지만 또 다른 삶을 내가 만들어 나갈수 있다는 기대도 생기는 요즘이다.</content:encoded><author>jt_fox</author></item><item><title>September Log (2025)</title><link>https://jtfox.dev/log/2025-10-13-record/</link><guid isPermaLink="true">https://jtfox.dev/log/2025-10-13-record/</guid><content:encoded>꽤나 시린 바람이 불기 시작하는 10월에 9월의 기록을 시작한다.
9월은 새로운 도전을 많이 하며, 실패도 많이 겪게 되는 달이었다.
좋은 개발자는 무엇이고 함께 일하고 싶은 사람은 어떤 사람인지에 대해 고민하고, 내가 그런 사람인지 파악할 수 있는 달이었다.

# 나란 사람.

자세히 말 할 순 없지만 9월에 상당히 많은 테스트와 커피챗을 하게 되었다. (거의 주마다 1~2회 한 것 같다.)
그 과정에서 다른 사람이 바라보는 나의 모습과 내가 바라고 지향하는 모습의 괴리가 있다는 것을 파악했다.
내가 장점으로 생각했던 부분들보다 다른 부분들을 제 3자는 더욱 강점이라 생각하고, 내 원하는 지향점은 3자가 원하는 나와 다르다고 생각이 들었다.
커리어에 대해서 고민이 되는 9월 이었다.

# 이처럼 사소한 것들 - 클레이 키건

최근에 읽게 된 책이다. 시놉시스를 간단히 말하자면,
펄롱은 석탄을 팔아가며, 다섯 딸과 아내와 함께 살아간다.
그는 크리스마스를 앞둔 겨울 날 수녀원에 석탄을 배달하러 가 뜻 밖의 진실을 마주하게 된다.

실제로 있었던 `막달레나 수녀원 사건`이라는 수녀원에서 일어난 인권 유린 사건을 모티브로 한 소설이다.

이 책을 보고 가장 먼저 생각이 든 것들은 &apos;저런 해방감과 희열을 나도 느낄 수 있을까?&apos;였다.
펄롱의 마지막 행동으로 인해 자신의 아이가 세상에 나왔을 때보다. 아내와 사랑에 빠졌을 때보다 더욱 큰 해방감과 희열을 느끼게 된다.

우리는 살아가면서 많은 `옳은 선택`을 마주보게 된다. 하지만 그 옳은 선택은 나에게 또 나의 가족에게 긍정적인 결과를 가져다 줄 확률이 묻는다면,
그것은 오히려 반작용을 주는 경우가 더 많다고 생각한다. `옳은 선택`을 하게 되며 책임 지어야할 일들, 소위 감당해야할 무게가 너무 무겁기 때문이다.

하지만 펄롱은 우리가 생각하는 `옳은 선택`을 하며, 그 때 희열감과 해방감을 느낀다.
왜 그런걸까? 펄롱은 본인이 옳다 생각하는 것을 행 함으로서 그로 인해 많은 책임과 고민이 생겼지만, 그 책임과 고민은 펄롱에게는 사소한 것들이 되었다.
인간은 본인의 생각을 관철하고 그것을 행동을 이루고 성공했을 때의 희열감과 도파민은 다른 것에 비할 것이 없다고 생각한다.
펄롱은 아마 위와 같은 이유로 인해 희열감과 해방감을 느낀게 아닐까 생각한다.

펄롱은 사소한 것들이 모여 좋은 삶을 꾸미게 되었다.

어머니가 하녀로 일했지만 고용주 미시즈 윌슨이 그를 아끼고 어머니를 돌봐주었다는 것.
사랑하는 아내를 만나고, 아끼는 딸을 얻은 것. 석탄을 팔아 가족을 먹여 살리 수 있는 것

우리의 삶도 다르지 않다고 생각한다. 순간의 작은 선택을 하는 것, 불의를 묵인하는 것, 옳은 것을 행하는것
이러한 사소한것들이 모여 커다란 우리의 삶이 이어진다 생각한다.</content:encoded><author>jt_fox</author></item><item><title>October Log (2025)</title><link>https://jtfox.dev/log/2025-11-10-record/</link><guid isPermaLink="true">https://jtfox.dev/log/2025-11-10-record/</guid><content:encoded>몇년 만에 굉장히 긴 연휴가 있었던 10월을 되돌아봤다. 
많은 일들을 정리하기도 하고 새로운 시작을 하기 위한 준비를 하기도 한 달이었다.
몇 달간 회고를 이어오고 있는데 이 사실을 잊어버린채 살아가기도 한다. 이번 달도 마찬가지다.

## 어쩔수가 없다

나는 박찬욱의 복수 3부작 중 올드보이를 최애 영화로 뽑을 정도로 박찬욱 감독에 대한 애정과 신뢰가 크다. 과연 그 기대감을 충족시켜주는 영화였다.
극에 처음 대사로 &quot;모두 다 이뤘어&quot;라고 이병헌이 말하지만 그 문장이 다음엔 &quot;모두 다 잃었어&quot;로 들리게 된다.
정말 어쩔 수가 없었을까? 주인공의 선택도, 그 가족의 선택도 모두 어쩔 수 없지 않았다. 그건 모두 본인들이 선택했지만, 타인에게 그것을 이해하기 바란다는 작은 죄책감의 표현이라고 생각된다.
또한, 나오는 중심 인물들은 대부분 본인의 직업에 종속되어있다 마치 본인이 그 직업을 대변하기라도 하는 것처럼, 과연 그것이 현명한 삶의 태도인지는 의문이 든다.
결국 주인공은 마지막 순간에 모든 것을 되찾고 밝은 미래를 꿈꾸지만, 가족 내에서는 작은 어둠이 만들어졌다. 그것은 과연 옳은 거래였을까?

## 좌절

10월의 연휴 시작은 굉장히 좌절스러운 한 주였다. 준비했던 모든 것들이 무산되고, 그로 인해 스스로에 대한 회의감이 들었다.
그동안 준비한 것에 대한 자부심도, 잘 해낼거라는 믿음 모두 사라지고, 무기력한 상태로 연휴를 시작했다.
3년여 만에 할머니댁에 방문해 인사를 드리고, 고모네 가족과 함께 시간을 보내면서 마음을 추스르기도 하고, 그 시간을 온전히 즐기면서 보냈다.

불과 며칠 전 나는 좌절했었다. 하지만 시간이 지나니 금새 좌절이 분노가 되고, 분노가 동기가 되었다.
이런 좌절을 여러번 겪으면 무뎌지게 될까? 아니면 더욱 무기력해질까? 궁금하기도 하고, 두렵기도 하다.

## 프로그라피

10기 까지 운영된 이름이 친숙한 IT 커뮤니티인 프로그라피에 FE 멘토로 합류하게되었다.
11기 부터는 &quot;수익화를 위한 프로젝트&quot;로 방향이 바뀌면서 기존에 기수와는 다르게 운영될 예정이다.
일전에 지인들에게 멘토 역할을 한 적은 있지만, 규모가 있는 커뮤니티에 멘토로 참여하는 것은 처음이라 기대가 크게 된다.

커뮤니티가 교육의 역할을 하진 않지만, 멘토로서는 어느정도 지식 과 경험을 전달하고, 멘티들이 올바른 길로 나아가도록 돕는 역할을 해야한다고 생각한다.
나 또한 멘토 역할을 하며 멘티 분들과 함께 성장할 수 있는 기회라고 생각이 된다.</content:encoded><author>jt_fox</author></item><item><title>November Log (2025)</title><link>https://jtfox.dev/log/2025-12-03-record/</link><guid isPermaLink="true">https://jtfox.dev/log/2025-12-03-record/</guid><content:encoded>백예린의 `November Song`을 듣기 시작하는 11월의 기록을 시장한다.
나는 겨울이 되면 항상 붕어빵을 찾게된다. 물론 맛 때문에 찾기도 하지만 찬 바람을 맡으며 붕어빵을 만들어주는걸 기다리는 그 느낌 또한 찾는 것 같다.
겨울이 실감나게.

## 쓸만한 인간

최근에 청룡영화상에서 박정민이 굉장히 화제가 되어 밀리에서 그의 책인 &quot;쓸만한 인간&quot;이 올라오게 되었다.
그가 배우가 되기를 결심한 시점부터 책을 출판하는 시점까지의 이야기와 생각들을 담은 에세이 성격의 책이다.
멋스러운 문장들이 있진 않아도 충분히 좋은 글을 쓰고, 약간의 가벼운 유머들로 책의 분위기를 이끌어가게 된다.

그가 배우라는 직업을 바라보는 시선, 사람에게 느끼는 감정에 대한 시선, 강아지를 바라보는 시선 등이 있으며
이 과정들로 인해 그가 형성되고 어느정도 자아가 만들어지는 것을 보게 되었다.
이처럼 우리가 살아가는데 작지만 많은 경험들을 하면서 그로인해 내가 만들어지는데 그 과정을 사유하며 내가 어떤 사람인지 다시 생각해보는 좋은 책이었다.

## 주식

11월은 나스닥이 굉장히 좋지 않았다. 미국 정부 셧다운부터, 내가 투자하는 부분의 의심과 매도량 증가, 유동성 부족 등의 여러가지 이유로 인해 주가가 많이 하락했다.
나는 주식을 투자할 때 단기적으로 투자하지않고, 펀더멘탈과 상상하는 바를 이룰거 같은 기업에게 투자한다.
그런데 항상 이런 하락장에는 많은 네러티브와 루머들이 생기고 주주의 마음을 흔들게 된다.

하지만, 내가 꿈꾸는 미래를 믿고 올곧게 투자하면 언젠가 좋은 결과가 생기지 않을까? 라고 바래본다.

## 판교

요즘에 판교에 갈 기회가 많아 자주 가게 된다. 판교는 특이하게도 높은 건물이 많지 않고 큰 광장과 같은 공간이 많다.
또한, IT회사들이 밀집되어 있어 구경하는 맛 또한 있다. (개발자라서 그런지도?)

본가가 성남이지만 놀랍게도 판교에서 근무한 적은 한번도 없었다. 그래서 그런지 이곳을 올때마다 항상 설레고 왠지 모를 두근거림 생긴다.
개발자에게 판교는 많은 기회들이 보이기도 하고, 많은 좌절이 보이기도 한다. 그 사이에서 나도 기회를 노리고, 좌절도 겪어보고 싶은 마음이 아닐까 생각한다.

내가 근무하고 있는 강남을 비하할 마음은 없지만 판교에 비하면 빌딩의 높이가 높고 촘촘해서 사람들이 걷거나 산책할 곳이 많이 없다.
옴스 테드의 말처럼 공원 및 산책로(다소 의역)이 없으면 그 자리에 정신병원이 필요하다는 말과 같이,
사람은 자연과 가까이서 해방감을 느낄 공간이 있는게 정신건강에 굉장히 영향을 끼친다는 것을 강남에 와서야 느끼고있다...
다음 직장은 어디가 될 진 모르겠지만 꼭 산책로가 있는 곳이길...

마지막 말로는 쓸만한 인간에 자주 나온 문장으로 마무리 하고싶다.

&quot;결국 모두 다 잘 될거니까&quot;</content:encoded><author>jt_fox</author></item><item><title>December Log (2025)</title><link>https://jtfox.dev/log/2026-01-04-record/</link><guid isPermaLink="true">https://jtfox.dev/log/2026-01-04-record/</guid><content:encoded>새해를 맞이하고 첫 주말에 2025년의 마지막 기록을 한다.

영하 10도에 육박하는 기온에 세상의 모든 것이 얼고 우리집 수도관도 얼고있다.

이직에 성공했고, 내일 첫 출근을 앞두고 있다 그 덕에 2025년의 12월은 굉장히 가벼운 마음으로 보낸것 같다.

이 글을 보시는 모두 새해복 많이 받으셨으면 좋겠다. 기원하는 일이 모두 이루어지기를.

## 퇴사

3년 4개월 가량 몸 담았던 회사인 `세이지`에서 퇴사하게 되었다.
전문연구요원으로 입사하게 되면서 군 생활과 함께 첫 번째 정규직 개발자로 활동한 회사라 정도 많이들고 소중한 기억이 많이 쌓이게 된 회사였다.
너무 좋은 사람들을 많이 알게되고, 개발자로서의 롤 모델이 생기기도 했고, 군생활을 무사히 마치게 도와준 나에게 너무나 고마운 회사다.

퇴사하는 이유는 여러가지였다. 면접에서 설명하는 합리적인 이유 외로 개인적으로 생각하는 이유였다.

첫 번째, 이곳에서의 경험이 확장되기 어렵다고 생각했다. 이건 회사의 잘못도 나의 잘못도 아니다. AI VISION으로 이차전지 결함을 검출하는 제품 특성상 FE의 영향력보단
AI와 서버 개발자의 영향이 클 수 밖에 없고, 이 제품 영역에서 많은 경험을 쌓기 어려웠다.

두 번째, 회사와 나의 시간이 너무 지나버렸다. 3년이 짧다고 할 수 있지만, 사회초년생인 나에겐 긴 시간으로 느껴졌고, 그 시간동안 회사와 나는 서로 너무 편해져버렸다.
다시 긴장하면서 집중할 수 있는 일터에 가고 싶은 생각이 들엇다.

세 번째, 조금은 지쳤다. 2년차 때 같은 팀의 FE 분들이 모두 나가게 되면서 혼자 제품을 도맡았고, 퇴사 전까지도 제품 하나를 책임지고 있었다. 물론 이것이 나쁘다 생각은 안하지만
그 과정에서 여러 관계들과 피로함을 느끼기도 했고, 한 제품을 3년 동안 유지보수하니 매너리즘도 많이 느끼게 되었다.

퇴사하는 이유를 이야기하면 당연히 아쉬운 소리를 할 수 밖에 없지만, 굉장히 장점이 많은 회사라고 생각한다.
구성원들이 조금이라도 즐겁게 일할 수 있게 해주고, 여러 협업 관계를 개선하고자하는 의지가 보이기 때문에 앞으로도 좋은 회사로 유지될 것이라고 생각한다.

마지막으로 회사를 나서는 길은 아쉽지도, 기쁘지도 않았다 그저 나의 미래와 회사의 미래를 응원할 뿐이다.
아무도 보지 않을 수 있는 자그마한 소회지만, 잊을 수 없는 기억이 되어서 고마웠다 세이지!

## 이직

12월에 여러 인터뷰들이 있었고, 그 결과가 모두 12월에 발표 되었다.
그 중에서 현실적인 조건과 내가 추구하는 방향과 일치된다고 생각하는 회사로 이직하게 되었다.

좋은 기회를 준 회사들에게 모두 감사하고, 8월부터 치열하게 준비한 결실을 맺게 되어서 너무 기쁘고 어려움을 겪으면서 다른 취준생 혹은 이직을 준비하시는 분들의 겨울이
부디 빠르게 따뜻해지기를 바란다.

(준비 과정에 대한 글은 블로그 글로 따로 포스팅 할 생각이다.)

신입 개발자로 입사했을 때 &apos;나&apos;와 현재 이직을 하는 &apos;나&apos;는 많이 다르다고 생각한다.
그 곳에서 어떤 성장과 기여를 할 수 있을지 궁금하기도 하고, 새로운 연을 맺게 되는 것에 대한 두려움과 기대 모두 생긴다.

글을 쓰는 이 시점으로 내일 첫 출근하게 된다.

20대의 마지막에 시작되는 회사의 생활은 &apos;전문연구요원&apos;이라는 울타리도 &apos;신입&apos;이라는 울타리도 없이
온전히 나로서 그려가는 개발자 생활의 첫 걸음을 내딛어 보려고한다.

## 브라운 아이드 소울 콘서트

7년만에 브아솔이 콘서트를 열게 되었고, 평소 나얼과 브아솔의 노래를 즐기는 나로서는 관람할 수 밖에 없었던 기회였다.

첫 공에 가게 되었는데 40대 후반인 나얼의 목 컨디션은 조금 불안하긴 했지만, 여전한 클래스의 퍼포먼스를 보여주었고
콘서트 구성에 대한 아쉬운 마음도 있긴 했지만, 브아솔 멤버가 모여서 노래를 부르는 현장 자체가 나에겐 너무 감동이었다.

사람의 전성기는 생각보다 빠르게 지나간다. 그런 사람의 실력이 떨어지는 것에 집중하는 것이 아니라 그 사람이 변한 과정과
전성기를 존중하는 자세를 가져야한다고 생각한다. 누구든 매일 젊을 수는 없기 때문에.</content:encoded><author>jt_fox</author></item><item><title>April Log (2025)</title><link>https://jtfox.dev/log/2025-05-01-record/</link><guid isPermaLink="true">https://jtfox.dev/log/2025-05-01-record/</guid><content:encoded>## 1. 이사

2년간 살았던 서초를 떠나 새로운 보금자리를 찾아 이사했다. 가장 큰 이유는 살던 집에 `비가 샌다`는 **치명적인 문제** 때문이었다. 또한 새벽에 쓰레기차, 오토바이 소음 때문에 잠을 설치는 경우가 있었다. 때문에 다음 집은 빌라를 피하고 싶었다. 계약 만료와 함께 집주인이 월세를 5% 인상한 것도 이사를 결심한 이유 중 하나였다.

서초의 한가롭고 조용한 분위기를 좋아했던 나로서는 아쉽긴 했지만, 정신을 차리고 이사할 집의 **필수 조건**을 정하고 여러 선택지를 꼼꼼히 살폈다.

### 필수 조건

- 편도 출퇴근 시간 1시간 내외
- 현재 월세 대비 20% 이내 인상폭
- 현재 집과 비슷한 평수 (약 7평)
- 오피스텔 또는 아파트
- 주차 공간 확보

### 계약 형태: 월세 vs 전세

가장 먼저 `월세`와 `전세` 중 어떤 계약 형태를 선택할지 결정해야 했다. 계약 `형태에 따라` 찾아볼 매물이 달라지기 때문이었다.

#### 월세 장점

- 1년 단위 계약 가능
- 목돈 부담 적음
- 전세보다 매물 다양

#### 월세 단점

- 강남역 인근 월세 시세 높음 (5-8평 원룸 기준, 기본 80~100만 원)
- 고정 지출 부담

#### 전세 장점

- 최소 2년 계약 (계약갱신청구권 사용 시 +2년, 총 4년 거주 가능)
- 비슷한 대출 이자 부담 시 월세보다 나은 주거 환경 기대

#### 전세 단점

- 목돈 묶임
- **전세 사기 위험** (보증보험 가입으로 일부 해소 가능하나 추가 비용 발생)
- 상대적으로 적은 매물

각각의 장단점을 비교해보니, 기존 집의 열악한 환경이 떠올라 좋은 컨디션의 집을 선택할 수 있는는 *전세*를 우선으로 고려하게 되었다.

### 지역

처음에는 직장이 있는 `강남역` 주변을 알아봤지만, 예산 내에서 만족스러운 집을 찾기 어려웠다. 결국 출퇴근 1시간 거리의 경기도까지 범위를 넓혔다. 2호선 라인의 신림과 낙성대도 잠시 고민했지만, 대부분 빌라 매물이라 제외했다.

1.  **안양 (평촌)**

    - 소형 아파트 매물 다수
    - 예산에 적합한 가격대
    - 대부분 아파트 단지 주차난 심각
    - 좌석버스 이용 시 출퇴근 1시간 내외

2.  **성남 (미금)**
    - 본가와 가까움
    - 강남 대비 10~20% 저렴한 시세
    - 주로 오피스텔 매물
    - 신분당선 이용 시 출퇴근 30분

3일간 직접 발품을 팔며 다음 사항들을 중점적으로 확인했다.

1.  `세대당 주차 대수`
2.  `리모델링 여부`
3.  `누수 및 결로 확인`
4.  `해충 발생 여부`

고심 끝에 안양 평촌의 소형 아파트를 선택했고, 4월에 이사하여 현재는 만족스럽게 지내고 있다.

평촌은 학원가와 학교가 많아 늦은 밤에도 비교적 밝고 안전하며, 아파트 단지 위주라 동네가 조용하고 깨끗한 점이 마음에 들었다. 반면 미금은 상가와 오피스텔이 밀집해 다소 번잡하고, 저녁 시간에는 취객들로 소란스러울 때가 있었다.

계약 후 전세 대출, 보증보험 가입, 이사, 입주 청소까지 정신없는 시간을 보냈지만, 지금 생각하면 빠르게 이사한게 잘한 선택으로 생각된된다.

다만 이번 이사를 준비하며 이사 청소, 이사업체 선정, 가구 배송 등 여러 과정에서 불쾌한 경험도 있었다.
업체들이 일방적으로 일정을 변경하거나 부당한 추가 요금을 요구하는 경우가 있었고, 현금 결제만 고집하며 암묵적으로 탈세를 조장하는 분위기도 너무 싫었다.

오히려 집을 구하고 계약하는 과정보다 이런 부수적인 일들에서 더 큰 스트레스를 받아, 앞으로 4년간은 이사 생각을 접을 것 같다.

~~하지만 벌써 출퇴근이 너무 힘들어 강남역 월주차를 구할까 생각중이다...~~</content:encoded><author>jt_fox</author></item><item><title>January Log (2026)</title><link>https://jtfox.dev/log/2026-02-06-record/</link><guid isPermaLink="true">https://jtfox.dev/log/2026-02-06-record/</guid><content:encoded>입춘이 지났지만 아직도 살이 떨리는 추위가 지속되는 2월경에 올해 첫 달의 기록을 한다.


## 입사
새로운 회사에 출근하게 되었다.
떨림과 긴장 설렘이라는 감정을 담고 출근을 했다. 첫 날에는 함께 입사한 동기?들과 함께 OT를 들었다.
회사에서 일하는 방법, 보안 프로그램, 결재 방법 등 일반적인 설명과 함께 사옥 투어를 하게 되었다.
처음 다니는 큰 사옥에 들뜬 마음이 불쑥불쑥 들었고, 일원이 된다는 설레는 마음이 들었다.


## 첫 인상
사람들은 생각보다 첫 인상을 짙게 기억한다. 나 또한 그렇다. 그래서 나는 이직하는 회사에 가면
적어도 수습기간은 회사에 많은 비중을 두려고 한다.
거기서 예상을 상회할 만큼의 성과가 나온다면, 그 사람에 대한 기대치와 신뢰를 빠른 시간에 쌓을 수 있는 기회가 될 것이다.

사람들은 왜 첫 인상을 쉽게 잊지 못할까?
첫 번째로는 `초두 효과`가 있다. Solomon Asch라는 심리학자가 1946년에 한 사람을 묘사하는 형용사를 목록을 순서만 바꿔서 읽어주고, (`긍정적 단어 &lt;-&gt; 부정적 단어`) 그 사람을 평가하는 방식이 뇌의 먼저 제시된 정보가 나중에 알게된 정보보다 평가 형성에 강력한 미치는 것을 확인했다.

두 번째로는 `인지적 경제성`이 있다. 이건 나도 없다고 할 수 없는 부분인데 사람은 한번 형성한 생각을 다시 바꾸기 싫어한다. 그에 대한 에너지가 소모가 아깝기 때문이다.

그 외로도 많은 과학적/감정적 이유가 있게지만, 나는 이 두 가지가 내 입장에서 가장 크게 작용한다고 생각한다.
그래서 앞으로 어떤 회사를 가든 초반에 집중하여 좋은 퍼포먼스를 보여주고 그 후에는 작은 시간을 투자함에도 그 퍼포먼스를 상회할 수 있도록 노력하려고 한다.

## 업무
새로운 회사에서는 Claude Team을 제공해준다. 체감상 Max와 사용량이 비슷한 느낌이다. (실제로는 모르겠음)
이 덕분에 레거시를 빠르게 파악할 수 있었고, 여러 제품에 대해서도 맥락을 파악하기 정말 편했다.
이제는 코딩을 하는게 어떻게 보면 개발자의 업무가 아니게 되는것 같기도 하다. 
의사를 결정하고, 기술적 검토를 하고 그에 대한 선택을 책임 지는 방향으로 개발자의 방향성이 변모하고 있다고 생각이 든다.
앞으로의 격변에 살아남기 위해 어떤 방향으로 걸음을 옮길지 고민되는 때다.

회사를 옮기면서 너무 정신 없었던 한달이었다..</content:encoded><author>jt_fox</author></item><item><title>February Log (2026)</title><link>https://jtfox.dev/log/2026-03-01-record/</link><guid isPermaLink="true">https://jtfox.dev/log/2026-03-01-record/</guid><content:encoded>드디어 무거웠던 외투를 벗고 가벼운 발걸음으로 맞이한 3월에 2월의 기록을 한다.

## 새로운 환경
새로운 회사에서는 당연하게도 직전 회사와는 많이 체계가 다르다. 스타트업보다 안정적인 시스템과 운영을 위하다보니
많은 결재 서류를 요구하고 그를 기다리는 시간이 꽤나 소모된다. 또한 서버가 여러 서버로 이중화되고 나뉘어있는데 이에 대한 권한을
타팀에서 관리하다보니 그에 대한 결재 권한 처리도 꽤나 복잡하고 번거로웠다.

하지만 위와 같은 시스템이 피(라고하긴 그렇지만 사고)로 쓰여졌을거라고 생각이 들며 회사의 법도에 반항할 생각은 없다.
또한, 팀의 구조와 동료의 군상도 많이 변화하게 되었다. 스타트업은 직무를 넘어서 일을 함께 고민하고, 조금은 가볍고 친밀한 느낌이 많았다면, 이곳은 조금 딱딱하고 위계도 꽤나 분명한 것으로 보인다.

물론 이것에 장단점은 모두 있고, 그에 따른 적응을 해 나가는것도 업무라고 생각한다. 새로운 환경에 적응하는건 매번 노력과 시간이 들지만 이 또한 나의 양식이리라..


## 킹키부츠
너무 보고싶었던 뮤지컬인 킹키부츠를 보고 왔다.

전체적인 줄거리는 어떻게 보면 일반적이지만, 거기서 나오는 노래들과 연기가 너무 신나게 시간을 보낼 수 있게 해줬다.
조금 어렸을 때에는 오히려 뮤지컬이 유치하다고 느꼈었다. `왜 갑자기 대화하는데 노래를 하지?` `무슨 말을 하는지 하나도 모르겠다.`라는 생각이 주로 들었던 것 같다.

하지만 오히려 나이를 먹다보니, 사람이 감정과 마음을 표현하는데에는 많은 방법이 있다고 생각한다. 글, 노래, 연기 등 또 그것들이 결과가 아닌 과정으로 보이기 시작했다. 어떻게 보면 그 과정들이 나에게 공감할 요소가 많아졌다고도 생각한다.

대부분의 뮤지컬이 고난이 있다. 그것을 극복하기도 실패하기도 한다. 어렸을 때에는 그런 것들을 겪고 볼 기회가 많지 않았지만 현재는 내가 겪기도, 내 친한 사람들이 겪기도 하고 그것들이 무대를 보고 떠오르고 감동으로 이어지는 것 같다.
조금 잡설이 많았는데 아무튼 `강홍석 배우`가 하는 롤라 역할은 지존이시다..

## AI
이젠 말하기도 지겨운 단어다 모든 곳에서 AI를 잘 활용 하는 법에 대해서 이야기 하고 있고, 그것들이 하루, 한 주 걸러서 필요없는 정보들이 되어가기도 한다.
그래서 최근 AI를 적극적으로 쓰면서 드는 생각을 글로 정리했다. [『내』 코드와의 이별](/blog/2026-02-09)</content:encoded><author>jt_fox</author></item><item><title>March Log (2026)</title><link>https://jtfox.dev/log/2026-04-01-record/</link><guid isPermaLink="true">https://jtfox.dev/log/2026-04-01-record/</guid><content:encoded>하늘이 분홍빛으로 뒤덮이는 4월의 봄에 3월의 기록을 시작한다.

## 프로젝트 헤일메리
영화 `마션`의 원작 작가로 유명한 `앤디 위어`의 원작 소설을 모티브한 영화를 관람하고 왔다.
최근 영화관에 `왕과 사는 남자`라는 영화가 관을 뒤덮은 상태인데, 사실 내용이 그려지는 영화라 굳이 보지 않고 있었다. (이미 역사를 통해 스포당하기도 했고...) 그런데 나와 같이 볼만한 영화를 못찾던 사람들의 SF명작이 떴다는 리뷰를 달기 시작하여 보게되었다.

사실 SF는 워낙 명작이 많기도하고, 보기 어려운 영화도 많아서 조금은 긴장상태로 관람을 시작하였다.
하지만, 예상과 다르게 단 하나도 어렵지 않았고, `마션`에서 나오는 특유의 유머코드와 인간이 아닌 다른 생명처를 넣어 함께 문제를 해결하는 과정에 대한 이야기를 정말 즐겁게 봤다.

작중에서 인간은 다른 인간에 대해서 희생을 강요하고, 거부하면 무력 또한 감행한다. 하지만 처음 보는 생명체는 인간을 위해 목숨을 걸기도 하며, 그 주인공 또한 인간을 위해선 희생하긴 어려웠던 사람이었지만, 처음 보는 생명체를 위해서는 본인의 희생을 감수하기도 한다.

이를 보고 든 생각은 `오래된 관계`가 무조건 적으로 중요하진 않다는 생각이었다. 짧은 시간이어도 내가 고난을 겪을때 함께 있어 주는 사람, 서로를 위해 희생을 감수해줄 수 있는 관계 그런 것들이 단순히 관계를 오래 지속한 관계보다 더 소중할 수 있는 생각이 들었다.

아무튼 간만에 본 명작이었다. 흘러가는 시간이 아까울정도로 즐거웠다.

## 프로그라피 모집
3월엔 프로그라피 인원 모집을 위한 면접이 이루어졌다.
많은 사람들에 대해서 면접을 진행하였고, 여러 사람들과 이야기를 나누는 시간을 가졌다.

아쉬운 점이 있던 사람들은 공통점이 있었다.

첫 번째로는 본인이 어떤 생각을 하는지 다른 사람에게 명확히 전달을 못하는 것이었다. 이는 어느정도 훈련으로 해결이 되는것이다. 글로써는 본인의 생각을 잘 정리해놓고 정작 면접에서 본인이 하고싶은 말을 잘 못하는게 너무 안타까웠다.

두 번째로는 본인이 했던 말이 번복되는 것이었다. 이것은 아무래도 본인의 생각이 아닌 것을 이야기 했던가 상황을 타계하기 위해 급조해서 말한 경우에 이런 경우가 많이 생기는 것 같았다. 차라리 모르거나 고민을 못해 본것을 솔직히 말하고, 그에 대한 생각을 천천히 전개하는 것이 더 좋을 것이다.

물론 그 분들이 이 글을 보실일은 없겠지만, 이건 비단 프로그라피와 같은 면접이 아니라 대화에서도 통용되는 말이라고 생각해 누구든 이것을 잘 지키고 대화하고 있는지 생각해보면 좋을 것 같다.

## 킥복싱
나는 평소에 UFC를 굉장히 즐겨본다. 그리고 중학생 때 무에타이를 하기도 했었다.
팔을 다치고 나서 예전처럼 고중량 헬스를 하기 어려운 상황이라 조금 지루하던 참에 회사 근처에 있는 킥복싱장을 등록하게 되었다.
기능성 운동을 전혀하지 않았어서 체력이 안좋고, 유연성이 많이 떨어진게 느껴저 시작을하게 되었고, 앞으로 UFC를 볼 때 선수들이 어떤 움직임을 보이고 행동하는지 더 자세히 관찰하고 분석하며 재밌게 볼 수 있을 것 같다.

근데 진짜 학생때랑 체력이 차원이 다르다고 생각한다. 정말 많이 힘들다.</content:encoded><author>jt_fox</author></item><item><title>April Log (2026)</title><link>https://jtfox.dev/log/2026-05-04-record/</link><guid isPermaLink="true">https://jtfox.dev/log/2026-05-04-record/</guid><content:encoded>따뜻한 바람이 지나가고 뜨거운 태양이 비치는 5월 4월의 기록을 시작한다.

## 수습 평가
벌써 입사한지 3개월을 지나 수습 평가 결과가 나오게 되었다.
다행히 평가에서 거의 최고점을 받으며 면수습을 하게 되었다.
1월에 썼던 전략을 최대한 수행하려고 했고, Claude는 그런 나에게 날개를 달아주었다.
여러 도메인의 맥락을 빠르게 흡수하고, 빠르게 대응하는 모습과 개선점을 찾아 팀 차원에서 여러 해결법을 제시하고, 도입한 것에 대한 긍정적인 평가가 아닐까 싶다. 나름 기분이 좋다.

## 프로그라피 정규
4월부터 본격적인 정규 세션이 시작되었다.

2주마다 하루씩 세션을 진행하는데 이게 생각보다 쉽지 않더라, 여러 사람을 통제하기도 해야하고, 경력이 있거나, 본인의 자아가 비대한 사람들은 다른 참가자들에게 옳지 않은 영향을 주지 않게 제재 또한 해야한다.
이런 것들을 아무리 부드럽게 진행한다고 해도 감정적인 노동이 꽤나 있는 것 같다.

또한, 이 사람들에게 이 프로그램이 정말 효용성이 있고, 뜻 깊은 6개월이 되도록 만들어야 하는 부담도 꽤나 있지만, 내가 시작한 것이니깐 올해 참가자 분들이 원하는 바를 이루고 나가실 수 있도록 최대한 도울 생각이다.

## 레인보우 맨션
2월에 보기 시작한 책을 이제야 완독했다.
이 책은 우주공학에 뛰어든 사업가들의 이야기를 담은 책이다. 내가 투자하고 있는 회사의 오너도 이곳에서 서술되며 여러 우주회사의 설립 과정과 어떤 방향으로 회사의 미래를 그리는지 소설처럼 볼 수 있는 재밌는 책이었다.
우주공학에 대한 어려운 설명보다는 그 당시에 우주산업이 어떻게 돌아가고, 우주회사의 오너들의 선택들이 현재 어떻게 작용했는지를 이야기한다.
심장을 뛰게 하는 대목도, 여러 실패 아쉬워하게 되는 대목도 있었고, 현재 우주산업의 전반적인 흐름을 알 수 있는 아주 좋은 책이라고 생각한다.</content:encoded><author>jt_fox</author></item><item><title>May Log (2026)</title><link>https://jtfox.dev/log/2026-06-02-record/</link><guid isPermaLink="true">https://jtfox.dev/log/2026-06-02-record/</guid><content:encoded>바삐 오가던 바람이 지나가고 태양의 뜨거운 햇살이 비치는 6월에 5월 기록을 시작한다.

## 몰입
최근에 직무관련해서 새로운 기회가 생겨서 도전을 했었다.
많은 일을 병렬적으로 하는 것이 아닌 한 가지를 집중해서 할 수 있는 시간이었고, 그로 인해 결과가 내 뜻대로 되진 않았지만, 그 과정에 몰입하는 것을 즐겼다.

현대인은 모두가 느끼겠지만 점점 더 집중하기 어려운 환경이 되는 것 같다.
주변에 많은 전자기기와 자리에 누우면 스쳐가는 오늘의 일과 내일의 할 일들이 떠오른다.
또한, 몰입은 너무나도 주변 환경에 취약해서 몰입 상태에 돌입했다가도 금방 빠져나오게 된다.
이를 극복하기 위해선 목적을 위해 몰입하는 연습과 환경을 만드는 수 밖에 없다고 생각한다.

## 대만 여행

5월엔 애인과 함께 대만 여행을 다녀왔다. 5월에 가서 조금은 선선할 줄 알았지만 38도에 자외선이 진짜 따갑더라
그래도 우육면, 버블티 등 내가 좋아하는 음식이 넘치는 곳이라 즐겁게 먹고 많이 걸어다니면서 투어도 많이했다.
대만의 특징으로 느낀 점이 몇 가지 있다.


* 주변 사람들이 대만 사람이 굉장히 친절하다는 이야기가 많았는데 내 생각은 조금 다르다 굉장히 일반적인 동아시아 시민들 같았다. 특별하게 친절하거나(물론 불친절하지도 않았지만) 배려가 넘친다고 느끼진 않았다. (오히려 일본 사람들이 더 친절한 듯...)

* 중귝의 향기가 느껴진다 장제스가 중국에서 나와서 첫 총통이 된만큼 대륙의 스케일을 느낄 수 있었다. 인 상깊었던 곳이 장제스를 기리는 `중정 기념당` 이었는데, 양쪽에 궁이나 가운데 사당도 크기가 굉장하고, 계단도 굉장히 높고 거대했다.

* `오토바이가 많지만 질서가 있다.` 베트남 필리핀과 같은 곳은 오토바이가 신호에 따르지않고 막무가내로 움직이는게 기본이다. 추가로 사람이 지나가는데도 막 지나친다.. 하지만 대만은 규칙과 법칙이 굉장히 잘 지켜지는 나라라고 느껴졌다. 이런 곳에서 시민 의식이 나오는 것 같기도 하다.

* 산악 지역이 굉장히 많다. 한국과 비슷하게 언덕이 높고 산악지형이 많고, 실제로 산악에 거주하는 사람도 아직 만힝 있는 것 같다. (한국의 경우 거의 존재하지 않으니)

* 높은 고지의 생산되는 차가 참 맛있다. 이렇게 더운 나라에서 어떻게 차를 계쏙 끓여먹을 생각을 했는지는 몰라도 우롱차 블랙티 모두 굉장히 고소하고 맛있다. 나도 마트에서 우롱차를 사와서 몇주째 회사에서 먹고있다.

* 더워서 사람이 죽는다던데 왜 죽는지 알겠다 내가 갔을때 온도가 38도 정도였는데 습함이 진짜 처음 겪어보는 습함이라 너무 짜증나고 죽겠더라 진짜, 당분간 이렇게 너무 더운 여행지는 안가고싶어졌다.


5월은 뭔가 많은 일이 있기보다는 큰 일들이 몇 가지 있었던 것 같다.
이제 날이 많이 더워질 텐데 벌써 다가오는 태양이 무섭다.</content:encoded><author>jt_fox</author></item></channel></rss>