본문으로 건너뛰기

· 약 11분
Hyunmo Ahn

React를 사용하다 보면 function을 component props로 넘길 때 re-render를 피하기 위해 reference 관리에 주의해야 한다. 대부분 useCallback을 사용해서 불필요한 re-render를 피하게 되지만 좋은 대안을 발견하게 되어 이 글을 쓰게 되었다.

영감을 받은 코드는 radix-ui/primitivesuseCallbackRef 이며 이 글에서는 useCallbackRef를 쓰는 케이스와 동작을 이야기 할 예정이다.

아래의 코드는 앞으로 설명 할 useCallbackRef 코드이다. 과연 어떤 상황에서 사용 할 것 같은가?

// useCallbackRef.js
import { useRef, useEffect, useMemo } from 'react';

export function useCallbackRef(callback) {
const callbackRef = useRef(callback);

useEffect(() => {
callbackRef.current = callback;
});

return useMemo(() => ((...args) => callbackRef.current?.(...args)), []);
}

· 약 40분
Hyunmo Ahn

Intro

현재 참여하고 있는 프로젝트에서 typescript와 i18next을 사용하는데, i18n JSON 파일의 type check를 강하게 적용했던 경험을 정리하고자 한다.

About I18n

먼저 i18n을 어떤식으로 활용하고 있었는지 소개하자면 다음과 같다.

i18n은 같은 화면에서 여러 언어로 된 Text를 보여주기 위해 사용하는 것으로, 하나의 key, 여러개의 value를 가지고 언어에 맞게 보여주는 것을 의미한다.

const i18nJSON = {
'simple-example': 'This is example text',
'values-example': 'I need to show {0} value',
'line-break-example': 'Hello. \n I am FE developer'
} as const

첫번째로 위와 같은 Key-Value 형태의 객체가 존재한다. 객체는 JSON이 될 수도 있고, typescript의 객체가 될 수도 있다.

import tFunction from 'utils';

tFunction('simple-example') // This is example text
tFunction('values-example', [15]) // I need to show 15 value
tFunction('line-break-example') // Hello <br /> I am FE developer

두번째로 tFunction를 사용해서 i18n key를 집어 넣어서 key에 맞는 string을 가져오며, 케이스에 따라서는 values-example과 같이 변수를 넣어 Text별로 맞는 value를 포함한 string을 리턴하는 경우도 있다. 마지막 line-break-example에서는 \n 줄바꿈 문자를 인식하여 <br/> 태그로 변환해서 React에서 줄바꿈을 할 수 있도록 사용하고 있다.

주의

본 article은 i18n을 적용하기 위한 방법으로 tFunction이라는 함수를 가져다 쓴다. JS로직이 아닌, Type 레벨에서의 내용을 주로 다루기 때문에 실제 내부 로직이 어떻게 되는지는 다루지 않을 예정이다. i18next.t와 같은 함수와 같은 역할을 한다고 보면 될 것이다.

How about I18n return type

여기서, tFunction에서 return 된 값이 어떤 값으로 나오는지 알 수 있다. 그렇다면 각 return값의 type은 어떻게 될까?

import tFunction from 'utils';

tFunction('simple-example')
// This is example text
// string
tFunction('values-example', [15])
// I need to show 15 value
// string
tFunction('line-break-example')
// Hello <br /> I am FE developer
// ReactElement

L3, L6는 string 타입이라고 보아도 무방하다. 하지만 L9에서는 i18n text에 \n가 있기 때문에 ReactElement가 리턴된다.

노트

ReactElement로 리턴하지 않고, string을 리턴하면서 줄바꿈을 지원하기 위해서는 dangerouslySetInnerHTML를 사용해야한다. 하지만 여러 한계가 있으므로 string 대신에 ReactElement로 리턴한다.

만약 values-example의 변수로 15가 아니라 a tag와 같은 JSX를 넣는다면, values-example의 리턴 타입 또한 달라진다.

import tFunction from 'utils';

tFunction('values-example', [15])
// I need to show 15 value
// string

tFunction('values-example', [<a href="/about">more</a>])
// I need to show <a href="/about">more</a> value
// ReactElement

위와 같이 같은 values-example I18n key를 사용하더라도 values로 어떤 값이 오느냐에 따라서 타입이 달라져야한다. a tag 또한 <br/>과 마찬가지로 string이 아니라 ReactElement인 컴포넌트로 리턴해야 하기 때문이다.

What is matter?

자, i18n Text 타입이 상황에 따라 string, ReactElement로 달라진다는 것을 알았다. 그렇다면 어떤 것이 문제일까?

tFunction은 위에서 이야기 한 것과 같이 똑똑하게 타입을 추론해주지 않는다는 점이다.

여기서 type이 적절히 추론되지 않는다면 어떤 문제가 있을까?

<input 
type='input'
placeholder={tFunction('line-break-example')} // type error
/>

여러가지 경우가 있겠지만, 대표적으로는 HTML tag를 사용할 때 string으로 정의 된 attribute에 string이 아닌 i18n 값이 포함 될 수 있다. 이러면, placeholder에 xlt text 대신 [object Object]가 보이게 된다.

물론, placeholder에 a tag가 들어가거나 \n 같은 줄바꿈이 들어가는 케이스가 명백히 이상하다. 하지만 i18n key를 잘못 사용하는 경우도 있고, i18n text가 잘못 등록 된 경우도 있을 수 있다.

즉, typescript를 사용하는데 타입이 제대로 추론 되지 않는 다는 점에 있어서 위와 같은 문제점이 있고 또 다른 문제를 야기 할 수 있다.

이번 article에서는 i18n text의 format 별로 타입을 정의하는 방법에 대해서 이야기 해 볼 예정이다. i18n을 예로 들었지만 기본적으로는 typescript Template Literal Types과 관련된 이야기이다.

사전지식
  • typescript 4.1+ 에 대한 전반적인 지식
  • Template Literal Types에 대한 관심
  • i18n system 사용 경험 (optional)

· 약 34분
Hyunmo Ahn

최근 프로젝트에 OAS-generator를 도입해서 사용하고 있다. 사용하기로 결정할 때 고민이나 확인이 필요한 점이 많았고, 적용 이후 좋은 점도 많은 것 같아서 OAS-Generator 사용 경험에 대해 글을 쓰려한다.

아마 OAS Generator가 무엇인지 궁금한 분들, 알고는 있지만 도입에 고민이 되시는 분들, 이미 사용하고 있지만 잘 사용하고 있는지 망설여지는 분들이 이 글을 읽고 좋은 모티브나 경험을 배워갔으면 좋겠다.

이 글에서 말하고 있는 건 다음과 같다.

  • OAS-generator 가 어떤 것인지.
  • OAS-generator 를 사용했을 때의 장단점.
  • OAS-generator 를 어떻게 사용해야하는지.
    • 설정
    • 커스텀 템플릿
  • 최적화
사전지식
  • Rest API로 Front-end 개발을 진행해 본 경험
  • Mustache 문법을 읽을 수 있는 지식 (Optional)
    • 글을 이해하는데는 필요없지만 실제로 사용한다면 꼭 알아야한다.

· 약 45분
Hyunmo Ahn

이 글은 기본적으로 immer에 대해서 알아보는 시간을 가진다. 만약 immer를 잘 모르는 분들은 아래 챕터를 먼저 읽어보는 것을 권장한다.

무엇이 궁금할까?

Question

Q1. immer는 객체 mutable하게 바꾸는 방식을 어떻게 immutable한 방식으로 바꾸어주고 있을까?

immer는 mutable하게 변경하는 객체 built-in method를 사용하더라도 immutable하게 데이터를 반환해주는 기능을 한다. 이 기능이 내부적으로 어떤 방식으로 이루어지는지 알아본다.

아래 코드 예시는 immer 공식 문서에 존재하는 basic example을 가져온 것이다.

import produce from 'immer';

const baseState = [
{
title: "Learn TypeScript",
done: true,
},
{
title: "Try Immer",
done: false,
},
]

const nextState = produce(baseState, (draft) => {
draft.push({ title: "Tweet about It" });
draft[1].done = true;
})

console.log(baseState === nextState) // false
console.log(nextState)
/*
[
{
title: "Learn TypeScript",
done: true,
},
{
title: "Learn TypeScript",
done: true,
},
{
title: "Tweet about It",
},
]
*/
Question

Q2. immer는 어떻게 structural sharing을 사용하는걸까?

*structural sharing: 객체를 copy할 때 변경되지 않은 객체는 reference를 동일하게 사용하는 방식.

객체를 immutable하게 업데이트 한다는 것은 기존 객체를 새로운 객체로 복사한다는 것이다. 즉, 복사에 비용이 발생한다. immer는 객체를 복사할 때, 변경되지 않은 reference는 재사용하는 structural sharing 방식을 사용해서 객체를 복사한다. immer에서는 어떤방식을 사용해서 structural sharing을 사용하고 있는지 알아본다.

Question

Q3. immer에서는 produce함수 내에서 draft를 직접 업데이트하는 방식이 아니라 return을 통해서 데이터를 업데이트하는 경우가 있는데, 이런 경우에 로직이 다른지?

immer를 사용할 때, 위에서 제시한 mutable한 객체 변경 방식이 아닌, 새로운 객체를 리턴하는 경우가 있다. 이는 immer와는 무관하게 immutable하게 Javascript에서 객체를 반환해주는 방식과 동일하다. immer에서는 이러한 방식을 공식적으로 허용하고 있고 두 방식, mutable하게 객체를 변경하는 방식과 immutable하게 객체를 변경시키는 방식 모두 혼용해서 쓰는 개발자도 많을 것 이다. 이러한 방식 차이는 immer에서 어떤 로직차이를 발생시키는지 알아본다.

// mutable method
const nextState = produce(baseState, (draft) => {
draft.push({ title: "Tweet about It" });
draft[1].done = true;
})

// immutable method
const nextState = produce(baseState, (draft) => {
return {
...baseState,
{ ...baseState[1], done: true },
{ title: "Tweet about It" },
}
})
사전지식
  • immer 혹은 redux-toolkit을 사용해 본 경험
  • Proxy에 대한 이해 (optional)

· 약 42분
Hyunmo Ahn

목적

redux를 이용해서 웹 어플리케이션 개발을 진행해 본 사람이라면 redux-devtools를 이용해서 time-travel 디버깅을 사용해본 경험이 있을 것이다.

redux-devtools가 어떤 것인지 헷갈리시는 분이 있다면 아래 영상을 참고하기 바란다.

만약 redux-devtools를 사용해 본 경험이 없다면 이번 article을 이해하기 어려울 수 있다.

redux-devtools는 redux를 사용한 web application의 redux정보(action, reducer, state)를 기록하고, 특정 시점의 reducer로 rollback하고, 특정 action을 없었던 일로 할 수 있다. 하지만, redux-devtools를 사용하지 않고 web application 내부에 비슷한 동작을 구현을 하려 한다면 간단한 일이 아니다. 예를들어, A버튼을 눌렀을 때 이제껏 발생시켰던 action을 없었던 일로 한다거나, Submit버튼을 누르기 전 이탈 한다면 해당 페이지에서 발생시켰던 모든 action을 rollback한다거나 하는 로직말이다.

redux-devtools는 버튼으로 손쉽게 제공하는 동작을 직접 로직을 구현한다고 하면 방법을 모르겠다. redux-devtools는 어떻게 이러한 일들을 가능하게 할까?

이 article에서는

  • redux-devtools 에서 호출되는 action과 reducer를 logging하는 방법.
  • redux-devtools 에서 특정 action이 dispatch 된 시점으로 jump하는 방법.
  • redux-devtools 에서 특정 action이 동작하지 않은 것 처럼 skip시키는 방법.

에 대해서 확인해 볼 것 이다.

사전지식
Caution
  • 이 문서는 browser extension에 대한 내용은 다루지 않습니다.
  • redux-devtools의 core에 대해서 다루고 있으며 browser extension에 대한 지식이 없어도 이해가 가능합니다.
  • redux-devtools-extensions와 관련하여 browser extension에 대한 내용을 원하는 분들은 이 글에서 다루는 내용과 맞지 않을 수 있습니다.