ziglog

    Search by

    6월 4주차 기록

    June 25, 2022 • ☕️☕️☕️ 13 min read

    싱나는 여름소풍


    배워가기

    React의 createElement()

    리액트에서 사용하는 JSX 문법은 트랜스파일러에 의해 createElement() 호출로 변환되며, createElement() 메서드는 오버라이딩된 여러 개의 메서드로 존재한다.

    Copy
    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를 포함하는 서브타입이 된다.

    Copy
    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 workspace

    npm에서 정의하는 workspaces란, 하나의 root package로 로컬 파일 시스템의 여러 개의 packages를 관리하기 위한 용어다.

    npm v7부터 workspaces를 지원하고 있다. npm install 명령어 자체에서 자동으로 패키지를 링크해주기 때문에, 하위 패키지들의 참조는 루트 node_modules로 이어져있다.

    타입스크립트의 타입 추론

    타입스크립트에서 유니온 타입으로 결합된 객체를 반복시킬 때 컴파일러는 해당 객체가 어떤 유형의 객체인지 알 수 없어 내부 타입을 추론할 수 없다.

    이때 타입 가드를 통해 범위를 좁히면 컴파일러에게 객체의 유형을 알려줄 수 있다.

    Copy
    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에서 시간 관련 메서드 테스트하기

    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-hooksact 메서드로 감싸줘야 한다.

    Ref https://davidwcai.medium.com/react-testing-library-and-the-not-wrapped-in-act-errors-491a5629193b

    Antd 디자인 철학 10가지 -

    1. 근접성 (Proximity)
    2. 정렬 (Alignment)
    3. 대조 (Contrast)
    4. 반복 (Repetition)
    5. 직관적으로 만들어라 (Make it Direct)
    6. 화면에 머물러라 (Stay on the Page)
    7. 가볍게 유지하라 (Keep it Lightweight)
    8. 가이드를 제공해라 (Provide an Invitation)
    9. 트랜지션을 사용하라 (Use Transition)

    Ref https://ant.design/docs/spec/proximity

    react-router-dom의 Outlet

    react-router-dom의 <Outlet />을 사용하면 공통으로 사용할 Layout 등을 정의할 수 있다.

    Copy
    // 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>
      );
    };

    타입스크립트 template literal의 제약

    typescript의 template literal type도 union type으로 정의되기 때문에, 너무 많은 경우의 수를 넣을 경우 에러가 발생한다. 타입이 약 40만개 정도로 정의될 때 에러가 발생하며, TS 버전이 높아질수록 더 많은 타입을 커버하고 있는 것으로 보인다.

    Copy
    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 사용 vs 반대

    이전에도 자주 본 글인데, 언어가 지원하는 문법을 어떻게 사용해야 하는지에 대한 선배 개발자 분들의 열띤 토론을 훔쳐볼 수 있는 영광스러운 기회(!)를 얻었다.

    우선 위 글에서의 주장과 같이 트리셰이킹의 문제를 방지하기 위해 enum 사용을 지양해야 한다는 입장은 다음과 같다.

    enum 의 존재 의의는 타입의 역할을 수행한다는 것과 리버스 룩업이 가능하다는 것인데, 타입은 레코드로도 충분히 표현할 수 있고, 많은 수의 용례는 ‘열거형’의 성질을 전혀 활용하지 않고 단순 딕셔너리 수준에서 끝나지 않나? 그래서 결국 const enum과 같은 문법이 나온 것이다.

    또 빌드 대상인 자바스크립트에 enum이 생기는 상황이 오지 않는 이상, 타입스크립트의 enum과 네이티브/바이트코드 컴파일 언어의 enum을 동일한 개념으로 생각하면 곤란하다.

    이에 대한 반대(?) 의견.

    enum은 프로그래밍 언어에서 사용되는 값의 집합을 나타내기 위한 선택지로서, 타입스크립트에서 지원하는 문법이다.

    enum의 사용이 타입스크립트 컴파일 시 트리셰이킹이 되지 않는다는 문제가 있어 Union Type이라는 선택지를 제공하고 있지만, 이게 맞는 것인지는 생각해봐야 한다. 의미론적으로 ‘열거(enum)’와 ‘조합(union)’은 다르기 때문이다.

    그러나 열거해야 하기 때문에 enum을 쓸 것이라면 Object.freeze() 또는 as const 문법을 사용할 수도 있다. 열거도 되고, 인덱스 접근도 되고, 사용법도 같기 때문이다. 물론 트리셰이킹이라는 개념과 그렇게 할 수밖에 없는 환경 자체가 에러지만, 그게 타입스크립트의 독자적인 문법이 아닌, enum과 같은 모든 프로그래밍 언어에서 쓰이는 개념이라면 사실 개발자 수준에서 이를 바꾸기보다 언어적인 지원에서 스펙트럼이 넓혀져야 하는게 맞다.

    그렇기에 정말 성능적으로 커다란 이슈가 없다면 enum을 써도 무방하다.

    결국에 시장 진입을 하는 사람들이 이 글에서 쓰이는 그러한 문제를 인지하고 const enumObject.freeze()를 쓰는 것은 문제다. 타입스크립트에서 enum을 지원한 이상, 생태계적으로 enum에 대한 문제는 잡힐 수 밖에 없다. 많은 사람들이 타입스크립트를 쓰고, 이 부분은 해결을 아에 못하는 부분은 아니라고 생각한다.

    그리고 어떤 근-본적인 이야기.

    언어, 컴파일러, 런타임의 삼권 분립 체제에서 개발 시점에 컴파일러, 런타임에서의 이슈를 가정하여 코딩하지 말라는 것은 모든 언어에서, 수십년간 공학적으로 쌓인 경험에서 나오는 룰이다. 마이크로 옵티마이즈는 그로 인한 성능 이점이 명확하고 클 때만 적용해야 한다.

    위 관점에서 바라보시는 enum의 트리셰이킹 논란은 다음과 같이 정리된다.

    최적화만 생각해서 올라가다 보면 enum 이슈 외에 아주 많은 성능 관련 이슈들이 있다. enum은 타입 중에 하나이고 그것이 자바스크립트에서 어떻게 동작하든지 상관없이 열거형 정보들을 처리하는 추상화된 타입이다. 그 나름의 역할이 있고 매우 유용하나 다른 타입들과는 다르게 역참조가 있어서 자바스크립트 코드까지 생성되는 것이 다른 타입들과 차이가 있다.

    이 이슈가 실제 서비스에서 성능적으로 크게 문제가 있다면 타입스크립트를 들여다보고 해결해서 타입스크립트 프로젝트에 백포트하는 것이 좋다고 생각합니다. 문제 해결은 본질적인 방법으로 접근해야지 코드 레벨의 추상화를 저해하는 방식은 추천하지 않는다!

    MobX 여러 버전 사용하기

    하나의 프로젝트에서 서로 다른 두 버전의 MobX를 사용하려면 아래의 configure({ isolateGlobalState: true }) 옵션을 설정해주어야 한다.

    위 옵션을 활성화하면, 동일한 환경에서 여러개의 MobX 인스턴스가 활성화 되어있는 경우, 서로의 전역상태를 격리한다. (두 인스턴스의 옵저버블이 따로 작동한다)

    옵션을 비활성화하면 ({ isolateGlobalState: false }), 두 인스턴스의 옵저버블이 함께 작동한다는 장점이 있지만 두 MobX의 버전이 일치해야 한다.

    AOS 허용 밀고당기기

    AOS에서 ‘이번만허용’은 앱을 껏다킬때마다 권한을 요청하겠다는 의미가 아니라, 일정시간동안 앱을 껐다 켜도 권한을 물어보지 않겠다는 의미다.

    반대로, ‘허용안함’은 never_ask_again의 의미로 다시는 해당권한을 물어보지 않는다. (설정을 다시 켜고 싶다면, 설정 > 어플리케이션에서 권한을 켜주도록 대응해야 한다.)

    validation vs verification

    • validation은 사용자 중심의 시스템 검증 과정
      • 최종적으로 만든 결과물이 잘 나왔는지 검증
      • 사용자에 대한 요구 사항을 충족하는가
    • verification은 개발자 중심의 시스템 검증 과정
      • 무언가를 만드는 과정을 잘 지켰는지를 검증
      • 스펙에 대한 요구사항을 충족하는가

    BFF (Backend for Frontend)

    • 기존 API 구조
      • 여러 플랫폼에서 동일하게 API 호출하는 문제
      • 앱에서 사용하지 않는 불필요한 데이터 포함
      • CORS 이슈, 엔드포인트 이슈 등
    • BFF 구조
      • 하나의 프론트엔드에 대해 하나의 BFF
      • BFF를 프론트엔드 요구사항에 맞게 구현 가능
        • 여러 플랫폼을 지원하지 않을 경우 BFF가 의미 없을 수도 있다.
      • BFF는 하나의 아키텍쳐일 뿐, 실제 구현체가 필요하다!
        • ex) graphql
        • 데이터를 브라우저 캐시 대신 라이브러리에서 제공해줌
        • 최적화되어있어 request 개수가 줄어듦
        • 모바일-웹 서로 다른 에이전트에서 동일한 API를 사용할 수 있다

    실제 비즈니스 로직의 구현응답 데이터를 클라이언트에서 요구되는 데이터로 파싱하는 두 가지 관점을 분리하여 복잡도를 낮추고, 필요한 작업에 집중하기 쉬워진다!

    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

    HTML role

    div 태그에 onClick과 같은 이벤트 핸들러를 붙이면 다음과 같은 린트 워닝이 뜬다.

    Copy
    Static HTML elements with event handlers require a role.

    아무래도 시멘틱한 HTML 설계에 위반돼서 그런가보다. 불가피하게 사용하는 경우에는 role="button" 등을 붙여주는 것이 좋다.

    Ref eslint-plugin-jsx-a11y/no-static-element-interactions.md at 287854abd066704e2a9964da597e7ab7f6f7e2ad · jsx-eslint/eslint-plugin-jsx-a11y


    이것저것

    • event.istrusted - 해당 이벤트가 사용자의 동작에 의해서 발생했는지(true), 스크립트에 의해 발생했는지(false) 알 수 있다

    • $0 - 브라우저에서 디버깅 시 현재 선택된 DOM을 바로 가져올 수 있다.

    • react hook을 테스트할 때는 @testing-library/react-hooksrenderHook 메서드를 통해 훅을 호출해야 한다.

      Copy
      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 선택자로 첫 글자에만 스타일을 지정할 수 있다.

      Copy
      p:first-letter {
        color: #ffffff;
      }
    • :where() pseudo-class로 여러 요소에 공통된 스타일을 적용할 수 있다.

      Copy
      /* where() 미사용 */
      .page div,
      .paget .title,
      .page #article {
        color: red;
      }
      
      /* where() 사용 */
      .page :where(div, .title, #article) {
        color: red;
      }
    • 이미지가 깨지는 현상을 방지하기 위해 화면에 보이는 것보다 2배 큰 사이즈를 원본으로 사용하면 좋다.


    기타

    오프라인으로 돌아온 FECONF 2022

    Ref https://2022.feconf.kr/

    ECMA 2022 승인

    오~ 이런게 승인되는 순간은 또 처음보는 것 같다. 2022년 6월 22일이라니. 일부러 날짜를 맞춘 건가 🤔

    ECMAScript 2022에서 새롭게 추가되는 feature들은 다음 블로그 글에서 더 자세히 볼 수 있다.

    몇 가지만 살펴보자.

    • class에 새로운 멤버 추가

      Copy
      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

      Copy
      // my-module.mjs
      const response = await fetch("https://example.com");
      const text = await response.text();
      console.log(text);

      이건 사실 이미 되는 줄 알고 있었지 모야…

    • at() 메서드

      Copy
      > ['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/


    마무리

    이번주는 아무래도래도… 전사행사가 있었다! 기대되는 듯 안되는 듯 시간은 슝 지나가고 금요일이 와버렸다.

    행사를 준비해주신 분들이 저어어엉말 고생하셨을 것 같다. 뭐 하나 허투루 하는 게 없는 배민… 귀엽고 아기자기 키치하고 섬세하고 웅장해지고 너무너무 재밌었고 알찼다.

    카더가든 씨 얼굴은 처음 본다. <나무> 노래 엄청 좋아해서 프뮤로도 해뒀었는데 ㅋㅋ 다른 노래들도 엄청 좋았다! 찾아들어봐야겠다. 입담이 유쾌한 아재라던데 정말 중간중간 멘트가 은은하게 웃겨서 재밌게 즐겼당. 카더가든 말처럼, ‘자기들끼리 진짜 잘 노는구나’가 딱 어울리는 말인 것 같다.

    사진은 어디 외부에 올리지 말라고 하셔서.. 🤔 나중에 회사 공식 유튜브에 올라오면 또 재미나게 추억을 즐길 수 있을 것 같다. 회사뽕 차던 하루 👍🥳


    Relative Posts:

    7월 1주차 기록

    July 2, 2022

    6월 3주차 기록

    June 18, 2022

    zigsong

    지그의 개발 블로그

    RotateLinkImg-iconRotateLinkImg-iconRotateLinkImg-icon