June 25, 2022 • ☕️☕️☕️ 13 min read
싱나는 여름소풍
createElement()
리액트에서 사용하는 JSX 문법은 트랜스파일러에 의해 createElement()
호출로 변환되며, createElement()
메서드는 오버라이딩된 여러 개의 메서드로 존재한다.
declare namespace React {
// ...
function createElement(
type: "input",
props?: InputHTMLAttributes<HTMLInputElement> & ClassAttributes<HTMLInputElement> | null,
...children: ReactNode[]): DetailedReactHTMLElement<InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>;
function createElement<P extends HTMLAttributes<T>, T extends HTMLElement>(
type: keyof ReactHTML,
props?: ClassAttributes<T> & P | null,
...children: ReactNode[]): DetailedReactHTMLElement<P, T>;
function createElement<P extends SVGAttributes<T>, T extends SVGElement>(
type: keyof ReactSVG,
props?: ClassAttributes<T> & P | null,
...children: ReactNode[]): ReactSVGElement;
function createElement<P extends DOMAttributes<T>, T extends Element>(
type: string,
props?: ClassAttributes<T> & P | null,
...children: ReactNode[]): DOMElement<P, T>;
// ...
}
이때, 모든 createElement()
오버라이딩 메서드 반환 타입의 뿌리가 되는 것이 React.ReactElement
타입이다. ReactElement
는 리액트 컴포넌트를 JSON 형태로 표현해놓은 것으로, createElement()
가 반환하는 객체들의 슈퍼타입(supertype)이다.
클래스 컴포넌트의 render()
메서드가 반환하는 ReactNode
역시 ReactElement
를 포함하는 서브타입이 된다.
interface ReactElement<
P = any,
T extends string | JSXElementConstructor<any> =
| string
| JSXElementConstructor<any>
> {
type: T;
props: P;
key: Key | null;
}
Ref https://simsimjae.tistory.com/426
npm에서 정의하는 workspaces란, 하나의 root package로 로컬 파일 시스템의 여러 개의 packages를 관리하기 위한 용어다.
npm v7부터 workspaces를 지원하고 있다. npm install
명령어 자체에서 자동으로 패키지를 링크해주기 때문에, 하위 패키지들의 참조는 루트 node_modules
로 이어져있다.
타입스크립트에서 유니온 타입으로 결합된 객체를 반복시킬 때 컴파일러는 해당 객체가 어떤 유형의 객체인지 알 수 없어 내부 타입을 추론할 수 없다.
이때 타입 가드를 통해 범위를 좁히면 컴파일러에게 객체의 유형을 알려줄 수 있다.
interface a {
id: string;
}
interface b {
name: string;
}
type AunionB = a | b;
const arr: AunionB[] = [
{
id: "foo",
name: "bar",
},
];
const isA = (item: a | b): item is a => {
return (item as a).id !== undefined;
};
const isB = (item: a | b): item is b => {
return (item as b).name !== undefined;
};
for (const item of arr) {
console.log(item.id); // Property 'id' does not exist on type 'AunionB'
console.log(item.name); // Property 'name' does not exist on type 'AunionB'
isA(item) && console.log(item.id); // OK
isB(item) && console.log(item.name); // OK
}
jest에서 시간 관련 메서드(setTimeout
, setInterval
등)를 테스트하고 싶을 때, 물리적인 시간을 사용하지 않고 mock 타이머를 통해 테스트할 수 있다.
테스트코드 상단에 jest.useFakeTimers()
를 선언한 후 다음 메서드들을 적절하게 사용한다.
jest.spyOn(global, 'setTimeout');
- global에는 window와 같은 글로벌 객체를 넣는다. setTimeout
함수에 대해 mocking 하여, toHaveBeenCalled
와 같은 테스트가 가능하다.jest.runAllTimers();
- 모든 타이머를 실행시킨 다음의 시점으로 바로 이동한다.jest.runOnlyPendingTimers()
- 현재 대기중인 타이머에 대해서 그 타이머를 실행시킨 다음의 시점으로 바로 이동한다.jest.advanceTimersByTime(msToRun)
: 타이머를 지정한 millisecond만큼 실행시킨 다음의 시점으로 바로 이동한다.단, 리액트 컴포넌트 안에서 시간 관련 메서드를 쓰게 되어 렌더링과 관련이 있다면, fake timer를 @testing-library/react-hooks
의 act
메서드로 감싸줘야 한다.
Ref https://davidwcai.medium.com/react-testing-library-and-the-not-wrapped-in-act-errors-491a5629193b
Ref https://ant.design/docs/spec/proximity
Outlet
react-router-dom의 <Outlet />
을 사용하면 공통으로 사용할 Layout 등을 정의할 수 있다.
// Layout.tsx
import { Outlet } from "react-router-dom";
const Layout = () => {
return (
<Layout>
<Outlet />
</Layout>
);
};
// App.tsx
const App = () => {
return (
<Routes>
<Route element={<Layout />}>
<Route path="/" element={<Main />} />
<Route path="/dashboard" element={<Dashboard />} />
</Route>
</Routes>
);
};
typescript의 template literal type도 union type으로 정의되기 때문에, 너무 많은 경우의 수를 넣을 경우 에러가 발생한다. 타입이 약 40만개 정도로 정의될 때 에러가 발생하며, TS 버전이 높아질수록 더 많은 타입을 커버하고 있는 것으로 보인다.
type Digit = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9;
type Chunk = `${Digit}${Digit}${Digit}${Digit}`;
type PhoneNumberType = `010-${Chunk}-${Chunk}`; // 🚨 Error : `Expression produces a union type that is too complex to represent.`
이전에도 자주 본 글인데, 언어가 지원하는 문법을 어떻게 사용해야 하는지에 대한 선배 개발자 분들의 열띤 토론을 훔쳐볼 수 있는 영광스러운 기회(!)를 얻었다.
우선 위 글에서의 주장과 같이 트리셰이킹의 문제를 방지하기 위해 enum 사용을 지양해야 한다는 입장은 다음과 같다.
enum 의 존재 의의는 타입의 역할을 수행한다는 것과 리버스 룩업이 가능하다는 것인데, 타입은 레코드로도 충분히 표현할 수 있고, 많은 수의 용례는 ‘열거형’의 성질을 전혀 활용하지 않고 단순 딕셔너리 수준에서 끝나지 않나? 그래서 결국 const enum
과 같은 문법이 나온 것이다.
또 빌드 대상인 자바스크립트에 enum이 생기는 상황이 오지 않는 이상, 타입스크립트의 enum과 네이티브/바이트코드 컴파일 언어의 enum을 동일한 개념으로 생각하면 곤란하다.
이에 대한 반대(?) 의견.
enum은 프로그래밍 언어에서 사용되는 값의 집합을 나타내기 위한 선택지로서, 타입스크립트에서 지원하는 문법이다.
enum의 사용이 타입스크립트 컴파일 시 트리셰이킹이 되지 않는다는 문제가 있어 Union Type이라는 선택지를 제공하고 있지만, 이게 맞는 것인지는 생각해봐야 한다. 의미론적으로 ‘열거(enum)’와 ‘조합(union)’은 다르기 때문이다.
그러나 열거해야 하기 때문에 enum을 쓸 것이라면 Object.freeze()
또는 as const
문법을 사용할 수도 있다. 열거도 되고, 인덱스 접근도 되고, 사용법도 같기 때문이다.
물론 트리셰이킹이라는 개념과 그렇게 할 수밖에 없는 환경 자체가 에러지만, 그게 타입스크립트의 독자적인 문법이 아닌, enum과 같은 모든 프로그래밍 언어에서 쓰이는 개념이라면 사실 개발자 수준에서 이를 바꾸기보다 언어적인 지원에서 스펙트럼이 넓혀져야 하는게 맞다.
그렇기에 정말 성능적으로 커다란 이슈가 없다면 enum을 써도 무방하다.
결국에 시장 진입을 하는 사람들이 이 글에서 쓰이는 그러한 문제를 인지하고 const enum
나 Object.freeze()
를 쓰는 것은 문제다. 타입스크립트에서 enum을 지원한 이상, 생태계적으로 enum에 대한 문제는 잡힐 수 밖에 없다. 많은 사람들이 타입스크립트를 쓰고, 이 부분은 해결을 아에 못하는 부분은 아니라고 생각한다.
그리고 어떤 근-본적인 이야기.
언어, 컴파일러, 런타임의 삼권 분립 체제에서 개발 시점에 컴파일러, 런타임에서의 이슈를 가정하여 코딩하지 말라는 것은 모든 언어에서, 수십년간 공학적으로 쌓인 경험에서 나오는 룰이다. 마이크로 옵티마이즈는 그로 인한 성능 이점이 명확하고 클 때만 적용해야 한다.
위 관점에서 바라보시는 enum의 트리셰이킹 논란은 다음과 같이 정리된다.
최적화만 생각해서 올라가다 보면 enum 이슈 외에 아주 많은 성능 관련 이슈들이 있다. enum은 타입 중에 하나이고 그것이 자바스크립트에서 어떻게 동작하든지 상관없이 열거형 정보들을 처리하는 추상화된 타입이다. 그 나름의 역할이 있고 매우 유용하나 다른 타입들과는 다르게 역참조가 있어서 자바스크립트 코드까지 생성되는 것이 다른 타입들과 차이가 있다.
이 이슈가 실제 서비스에서 성능적으로 크게 문제가 있다면 타입스크립트를 들여다보고 해결해서 타입스크립트 프로젝트에 백포트하는 것이 좋다고 생각합니다. 문제 해결은 본질적인 방법으로 접근해야지 코드 레벨의 추상화를 저해하는 방식은 추천하지 않는다!
하나의 프로젝트에서 서로 다른 두 버전의 MobX를 사용하려면 아래의 configure({ isolateGlobalState: true })
옵션을 설정해주어야 한다.
위 옵션을 활성화하면, 동일한 환경에서 여러개의 MobX 인스턴스가 활성화 되어있는 경우, 서로의 전역상태를 격리한다. (두 인스턴스의 옵저버블이 따로 작동한다)
옵션을 비활성화하면 ({ isolateGlobalState: false }
), 두 인스턴스의 옵저버블이 함께 작동한다는 장점이 있지만 두 MobX의 버전이 일치해야 한다.
AOS에서 ‘이번만허용’은 앱을 껏다킬때마다 권한을 요청하겠다는 의미가 아니라, 일정시간동안 앱을 껐다 켜도 권한을 물어보지 않겠다는 의미다.
반대로, ‘허용안함’은 never_ask_again의 의미로 다시는 해당권한을 물어보지 않는다. (설정을 다시 켜고 싶다면, 설정 > 어플리케이션에서 권한을 켜주도록 대응해야 한다.)
→ 실제 비즈니스 로직의 구현과 응답 데이터를 클라이언트에서 요구되는 데이터로 파싱하는 두 가지 관점을 분리하여 복잡도를 낮추고, 필요한 작업에 집중하기 쉬워진다!
Ref 카카오페이지는 BFF(Backend For Frontend)를 어떻게 적용했을까? BFF(Backend for Frontend) 란?
createBrowserHistory
react-router-dom의 <BrowserRouter>
는 history
객체를 자동으로 생성한다. 이때 <BrowserRouter>
컴포넌트는 <Router>
컴포넌트를 렌더링할때 props 로 history
객체를 전달하는데, 이 객체는 history
패키지의 createBrowserHistory()
함수를 호출함으로써 생성된다.
Ref react-router 및 history React Router: Declarative Routing for React
div
태그에 onClick
과 같은 이벤트 핸들러를 붙이면 다음과 같은 린트 워닝이 뜬다.
Static HTML elements with event handlers require a role.
아무래도 시멘틱한 HTML 설계에 위반돼서 그런가보다. 불가피하게 사용하는 경우에는 role="button"
등을 붙여주는 것이 좋다.
event.istrusted
- 해당 이벤트가 사용자의 동작에 의해서 발생했는지(true), 스크립트에 의해 발생했는지(false) 알 수 있다
$0
- 브라우저에서 디버깅 시 현재 선택된 DOM을 바로 가져올 수 있다.
react hook을 테스트할 때는 @testing-library/react-hooks
의 renderHook
메서드를 통해 훅을 호출해야 한다.
const { result } = renderHook(() => useMyHook(params));`
jest config에서 testEnvironment
을 jsdom 으로 설정할 수 있는데, 이는 가상의 브라우저 환경에서 테스트를 돌리겠다는 뜻이다. testEnvironment
을 node로 설정하면 노드 환경에서 돌리게 된다.
Fastlane - IOS 빌드, 배포 자동화를 위해 사용되는 툴
mobX 전역 상태 타입이 optional이면 상태의 변화가 일어나더라도 구독하는 컴포넌트가 업데이트 되지 않을 수 있다. Optional보다는 Nullable로 사용하자.
삼성인터넷은 기기가 다크모드이면 자기 마음대로 색상을 변경시킨다. prefers-color-scheme
과 메타태그도 동작을 안하며, window.matchMedia('(prefers-color-scheme:light)').matches
로 다크모드 감지가 안된다. 🤯
iOS에서 deeplink url의 한글 파라미터를 따로 인코딩해주지 않으면 제대로 동작하지 않는다.
iOS 사파리에서는 딥링크 혹은 앱스토어로 리다이랙트 시킬 때 자체적인 팝업을 띄워 한번 더 확인한다. 이때 window.blur
가 발생한다.
:first-letter
선택자로 첫 글자에만 스타일을 지정할 수 있다.
p:first-letter {
color: #ffffff;
}
:where()
pseudo-class로 여러 요소에 공통된 스타일을 적용할 수 있다.
/* where() 미사용 */
.page div,
.paget .title,
.page #article {
color: red;
}
/* where() 사용 */
.page :where(div, .title, #article) {
color: red;
}
이미지가 깨지는 현상을 방지하기 위해 화면에 보이는 것보다 2배 큰 사이즈를 원본으로 사용하면 좋다.
오~ 이런게 승인되는 순간은 또 처음보는 것 같다. 2022년 6월 22일이라니. 일부러 날짜를 맞춘 건가 🤔
ECMAScript 2022에서 새롭게 추가되는 feature들은 다음 블로그 글에서 더 자세히 볼 수 있다.
몇 가지만 살펴보자.
class에 새로운 멤버 추가
class MyClass {
instancePublicField = 1;
static staticPublicField = 2;
#instancePrivateField = 3;
static #staticPrivateField = 4;
#nonStaticPrivateMethod() {}
get #nonStaticPrivateAccessor() {}
set #nonStaticPrivateAccessor(value) {}
static #staticPrivateMethod() {}
static get #staticPrivateAccessor() {}
static set #staticPrivateAccessor(value) {}
static {
// Static initialization block
}
}
자바스크립트가 점점 타입스크립트스러워지는 것 같다. 이러다 자바스크립트는 아예 없어질 수도…? (나쁘지 않음)
Top-level await
// my-module.mjs
const response = await fetch("https://example.com");
const text = await response.text();
console.log(text);
이건 사실 이미 되는 줄 알고 있었지 모야…
at()
메서드
> ['a', 'b', 'c'].at(0)
'a'
> ['a', 'b', 'c'].at(-1)
'c'
이런 게 나올지도 모른다고 했었는데, 정말 된다니! 이제서야 나오다니… 알고리즘적으로 많이 잡아먹지 않는 건지도 궁금하다. 배열의 원소가 만약 1억 개라면..? 😵
Ref https://www.ecma-international.org/news/ecma-international-approves-new-standards-6/
이번주는 아무래도래도… 전사행사가 있었다! 기대되는 듯 안되는 듯 시간은 슝 지나가고 금요일이 와버렸다.
행사를 준비해주신 분들이 저어어엉말 고생하셨을 것 같다. 뭐 하나 허투루 하는 게 없는 배민… 귀엽고 아기자기 키치하고 섬세하고 웅장해지고 너무너무 재밌었고 알찼다.
카더가든 씨 얼굴은 처음 본다. <나무> 노래 엄청 좋아해서 프뮤로도 해뒀었는데 ㅋㅋ 다른 노래들도 엄청 좋았다! 찾아들어봐야겠다. 입담이 유쾌한 아재라던데 정말 중간중간 멘트가 은은하게 웃겨서 재밌게 즐겼당. 카더가든 말처럼, ‘자기들끼리 진짜 잘 노는구나’가 딱 어울리는 말인 것 같다.
사진은 어디 외부에 올리지 말라고 하셔서.. 🤔 나중에 회사 공식 유튜브에 올라오면 또 재미나게 추억을 즐길 수 있을 것 같다. 회사뽕 차던 하루 👍🥳