ziglog

    Search by

    2월 2주차 기록

    February 12, 2022 • ☕️☕️☕️ 14 min read

    빠름 빠름 빠름


    배워가기

    이제 코드 구조를 어느 정도 이해하고 있는 것 같다. 사내에서 쓰는 약어들도 귀동냥으로 하나 둘 익혀가고. 세부적인 코드는 물론 보안사항일 것이기에 올릴 수 없겠지만, 앞으로 코딩하면서 마주하는 문제들, 그리고 그를 해결한 스토리들을 올리지 않을까 싶다.

    사내 프론트엔드 팀원분들이 공유하는 TIL에서 유용한 내용들을 줍줍해서 정리하려고 한다. (나는 안 쓰는 주제에) 사실 매일매일 하는 건 내가 지키지 못할 것이라고 생각해서, 그리고 한번 지키지 못하면 또 울적해서 계속 안할 것이기에 내가 지킬 수 있는 ‘주 1회’ 공부 내용 업로드나 열심히 하려고 한다.

    React.VFC vs React.FC

    다른 분들과 마찬가지로, children prop을 필수적으로 포함하고 있는지 여부에 따라서 달라진다고만 생각했다. 그리고 children prop이 필요하지 않은 컴포넌트들도 많기 때문에, 굳이 컴포넌트의 타이핑을 해주지 않는 편을 택했다. 어쨌든, 여러 문서()에서도 React.FC와 같은 타이핑을 지양하고 있으니까… 게다가 React.VFC는 deprecated된다는 이야기가 나온지 꽤 된 것 같다.

    새롭게 알게된 내용들이 있다. React에서 구현된 Component들과 Element들은 모두 -Component, -Element와 같은 네이밍을 사용하지만, 처리하는 부분에서 서로 다른 경우가 있다. 하지만 말단의 인터페이스는 동일하다.

    React.FC가 갖고 있는 children prop은 어떻게 보면 필요한 것일 수도 있다. 결과적으로 컴포넌트는 children이 모두 존재하기 때문이다! (JavaScript 런타임에서도 변수 슬롯이 적용되지 않았을 뿐, 변수로는 존재한다.)

    그러나 문제는 TypeScript 사용 시 발생한다. TypeScript의 구조적 타이핑의 관점에서 children은 존재하면 안 된다. TypeScript의 타입 체킹은 런타임과 분리되어 있기 때문이다. 어떤 프로퍼티를 갖고 있는가에 따라 타입이 정의되는 TypeScript의 성질상 React.FC와 class component, pure component를 동일한 수준으로 취급할 수 없게 되었다. 그래서 이들을 호환시키는 atomic component의 입장에서 React.FC가 가진 children 탓에 복합적인 프로그래밍이 어려워지게 됐다.

    그래서 children을 사용하지 않는 React.VFC가 나오게 되었으나, 궁극적으로 atomic component에서 이를 제대로 활용하지 못해서 여전히 골칫거리로 남게 된다.

    React.FCReact.VFC라는 프레임워크를 최대한 활용하는 방법에는 장단점이 있다. 장점은 프레임워크가 달라지면 TypeScript나 트랜스파일러 등 다양한 도구에서 문법적 오류를 발견하여 에러를 내준다는 것이다. (그래서 쉽게 고칠 수 있다.) 단점은 프레임워크가 달라지면 무조건 그에 따라 변경이 필수적이라는 것이다.

    가장 중요한 것은, 런타임과 타입체커 영역에 대한 분리다. 타입 체커 영역에서 불필요한 것을 왜 사용해야 하는지 논의가 이뤄질 수 있다.

    Ref https://medium.com/@martin_hotell/10-typescript-pro-tips-patterns-with-or-without-react-5799488d6680#78b9

    tsconfig

    tsconfigcompilerOptionslib에 ‘esnext’만 적어준다고 해서 es6부터 최신 문법이 다 커버되지는 않는다. 필요한 문법을 lib에 각각 넣어줘야 한다.

    Ref https://www.typescriptlang.org/tsconfig#lib

    useRef

    컴포넌트 밖에서 선언한 변수와 useRef의 다른점은 리액트에서 메모리를 관리하는가 아닌가에 따른 차이가 있다. useRef로 만들어진 변수는 리액트가 파괴되면 메모리상에서 함께 파괴된다. 다만 전역 변수는 브라우저가 종료되야 파괴된다.

    메모리 오너쉽이 리액트에 있다는 것은 리액트의 렌더링 관점에서 무언가 접근할 수 있다는 것이고, 한 번만 렌더링된다는 관점에서는 리액트에서 DOM의 변경과 무관하게 static 변수(혹은 매직넘버)를 넣을 수 있다는 것이다.

    또 리액트에서 메모리를 관리한다는 것은, useCallback이나 useMemo처럼 어딘가 메모리를 가용한다는 개념이니 성능적 향상을 위해 쓸 수도 있다.

    charles

    charles은 앱과 인터넷 사이에 위치하는 web proxy 툴이다. 모든 네트워킹 요청은 Charles를 통해서 지나가고 해당 내용을 기록한다. 또한 네트워크 요청과 응답값을 살펴보는 것이 가능하고 심지어 응답 값을 변경하여 클라이언트에 적용할 수 있다.

    charles proxy 프로그램을 이용하면 특정 Origin에 대한 요청을 rewrite 할 수 있다. 실제와 매우 유사한 구동환경에서 개발 및 테스트할 수 있다. 앱 내에서 웹뷰 디버깅 시 실제 앱 코드를 수정하지 않고 로컬에서 띄운 웹을 볼 때 유용하다.

    charles의 Map Local 기능을 사용하면 응답 데이터를 마음대로 바꿀 수 있어서 다양한 상황에 따른 디버깅에 굉장히 유용하다.

    + USB로 모바일 디바이스를 연결했을 때 charles가 제대로 동작하게 하려면 PC와 동일한 와이파이를 사용하고, 그 와이파이에 프록시 세팅을 해줘야 한다.

    Ref https://www.detroitlabs.com/blog/2018/05/01/how-to-set-up-charles-proxy-for-an-ios-simulator/ https://charlesdocsy.com/2020/05/12/rewrite-redirect-url/

    @ts-ignore 옵션

    Ref https://365kim.tistory.com/200

    DI in TS (아티클)

    MUI

    MUI에는 Material Icons를 React Component로 제공하는 @mui/icons-material이라는 패키지가 있다.

    svg 파일들이 일일이 React Component 파일의 형태로 되어있는 것이 아니라, svg 파일들을 읽어들이고 SvgIcon 컴포넌트로 매핑하여 빌드된다.

    아이콘마다 각각 컴포넌트를 import해서 가져올 수 있다.

    Copy
    import AccessAlarmIcon from "@mui/icons-material/AccessAlarm";
    import ThreeDRotation from "@mui/icons-material/ThreeDRotation";

    Ref https://github.com/mui/material-ui/blob/master/packages/mui-icons-material/builder.js

    interface vs 추상 클래스

    • 추상클래스
      • 오직 클래스만 정의할 수 있다.
      • JavaScript 런타임 코드를 생성한다.
      • 다양한 기능을 제공하는만큼 더 무겁다. (접근제한자 선언, 생성자 등)
    • interface
      • 런타임 코드를 생성하지 않고, 컴파일 타임에만 존재한다.
      • 추상클래스에 비해 더 가볍다.

    여러 클래스에서 공유하는 구현이라면 추상 클래스를 사용하고, 클래스의 타입을 정의하고자 한다면 인터페이스를 쓰자.

    html/bodyheight: 100% | 100vh 는 지양하자

    • 유동적으로 변해야 할 본문의 사이즈를 한정짓는 것이기 때문에 의미적으로 어색하다.
    • 플랫폼별 스크롤링 최적 기법에 따라 스크롤링 문제가 발생할 수 있다.
    • 전체 컨텐츠가 명시적으로 100% 내에 위치해야 하는 경우가 아니라면 지양하는 것이 좋다.
    • 화면에 비해 컨텐츠가 부족해 의도적 공백이 필요한 경우라면 min-height: 100vh 사용을 고려해보자.

    Ref Do not put height 100% on html, body in 2020

    ErrorBoundary가 클래스 컴포넌트로만 가능한 이유

    ErrorBoundarygetDerivedStateFromErrorcomponentDidCatch 생명주기 메소드를 기반으로 구현할 수 있는데, 생명주기 메소드는 클래스 컴포넌트에서만 사용할 수 있기 때문이다.

    그렇다면, 생명주기 메서드를 기반으로 구현해야 하는 이유는 무엇일까?

    이것저것

    • craco같은 CRA 서드파티는 리액트 버전이 올라감에 따라 지원이 안 되는 경우가 있기 때문에 주의해야 한다.

    • git merge --no-ff: fast-forward의 관계더라도 merge commit을 만드는 옵션

    • forceConsistentCasingInFileNames: 같은 파일에 대한 일관되지 않은 참조를 허용하지 않을지 여부에 대한 설정이다. CRA + TypeScript로 프로젝트 생성 시 자동으로 true로 설정된다.

    • mobX의 setter를 활용했을 때, makeAutoObservableaction으로 할당해주기 때문에 runInAction 없이 상태변경을 할 수 있지만, 추가 코드량이 증가하기 때문에 runInAction을 활용하는 것이 좋을 수도 있다.

    • npm v7 부터는 yarn.lock을 버전 지침으로 활용한다. package-lock.json을 함께 운용하는 이유는, npm이 버전 관리를 위해 남기는 데이터가 yarn의 것보다 더 많기 때문이다.

    • react/jsx-curly-brace-presence: JSX props나 children에 대해 { "text" }와 같은 불필요한 중괄호를 쓰지 않도록 막아주는 린트 룰

    • react/no-array-index-key: 리액트 컴포넌트 리스트의 key로 index를 사용하는 것을 방지해주는 린트 룰

    • html/body에서의 overflow: hidden 은 모든 기기에서 유효하지는 않다. (모바일 기기에서 터치로 스크롤이 되는 등)

    • 프록시 세팅을 할 때 로컬 IP 주소를 입력해야하는데, 통신사 중 특히 KT는 IP를 수시로 줬다 뺏기 때문에 만약 배민 앱 접속 시 503이 뜬다면 일단 IP 주소가 바뀌진 않았는지를 먼저 확인해보자.

    • axios 에서 쿼리 파라미터를 편하게 쓰기 위해 qs 라이브러리를 활용할 수 있다.

    Copy
    axios.defaults.paramsSerializer = (params) => {
      return qs.stringify(params);
    };
    • css width 속성에는 max-contentmin-content도 있다.

    • react 제어 컴포넌트에서 유효성 검사 등의 이유로 value를 바꿔주면 cursor가 맨 뒤로 이동한다. 이 경우 input value 중간으로 커서를 이동시켜 값을 삭제/추가할 때 cursor가 계속 맨 뒤로 이동해 정상적으로 값을 수정할 수 없는 버그가 발생한다. ref를 통해 이전값과 현재값을 비교하고, setSelectionRange API를 사용해 커서 위치를 잡아줄 수 있다.

    • aos/ios/로컬 개발 환경 등에 따라 폰트가 다르게 적용될 수 있다.

      • 폰트에도 여백이 존재하고, 영문/한글이 섞이는지에 따라 수직 align이 조금 달라질 수도 있다. 이는 폰트별로 다 달라서 환경별로 의도한 디자인과 다른 결과물을 볼 수도 있다.
      • styled-component 문제일 수 있다. 로컬 환경과 배포된 환경에서 개발자 도구로 확인해보면 css 위치가 달라진다.
    • webpack plugin의 동작 구조는 각 동작 파트마다 callback을 등록하여 각 단계별 처리를 하는 구조이다.

    • util 함수와 같이 여러 부분에서 공통적으로 사용되는 코드는 추상화 정도가 높아야 한다. 도메인-free한 공통함수를 작성하자.

    • 무한스크롤, lottie 등에서 memory leak이 발생하지 않는지 확인하자.

    • props로 전달받는 on- 핸들러의 경우, 옵셔널로 하는 습관을 들이는 것이 좋다. 특정 비즈니스 코드에서는 핸들러의 존재 여부가 명확하지만, 조금 더 공통 컴포넌트를 작성하다보면 on- 핸들러의 경우 반드시 전달하지 않는 경우가 많아서 이를 예상하고 공통 컴포넌트를 작성하는 편을 권장한다.

    • id 프로퍼티를 가진 객체의 배열을 id 프로퍼티의 값을 키로 하는 객체로 변경하는 함수를 Object.fromEntries를 사용하여 만들 수 있다. 기존 코드에서 계속해서 배열에서 요소롤 find 하는게 시간복잡도와 가독성 측면에서 좋지 않다고 판단하여서 하였는데, 모든 요소를 순회하는 메서드의 경우, find를 줄여서 얻을 부하 감소와 Object.fromEntries를 사용에 따른 부하 증가를 면밀히 따져보아야 한다. 배열의 크기가 굉장히 크고 실제로 대부분 배열의 앞부분에 찾고자 하는 값이 있을 경우에는 Object.fromEntries에 따른 성능 감소가 더 클 수도 있다.

    • package.json에서 private: true이면 패키지 배포가 refuse된다.

    • 웹뷰가 focus되지 않고 있다면 JavaScript로 focus를 하더라도 해당 이벤트가 발생하지 않는다. 해당 이벤트들은 그대로 쌓여 있다가 웹뷰가 focus되면 그제서야 focus 이벤트가 발생하면서 onfocus 핸들러가 호출된다.

    • 새로운 웹뷰 또는 새탭에서 브라우저 실행할 때 현재 웹이 active가 되지 않아서 focus()와 같은 API를 호출해도 onfocus 콜백함수가 실행되지 않는다.

    • CRA로 build를 하면 index.html 내 빌드 파일명이 바뀌기 때문에 캐시 무효화는 /* 로 모든 파일을 무효화할 필요 없이 /index.html만 무효화 하면 된다.

    • 폰트에 중앙 정렬 속성을 모두 줬는데도 중앙 정렬이 제대로 되어 있지 않다면 해당 폰트 자체에 위 아래 여백이 다르게 들어가있는 건 아닌지 확인해보자.

    • styled-components를 사용하면 로컬 개발 환경에서는 head 태그 안에 주입해주는데, 빌드를 하고 나면(prod 환경) 별도의 JS 번들로 변환된다. 이때 따옴표가 누락되기도 하고 쌍따옴표가 되기도 하고 띄어쓰기가 사라지기도 한다.

    • 크롬 개발자 도구의 Elements 탭 하단에 ‘Computed’ 탭에 들어가서 스크롤을 맨 아래로 내려보면 ‘Rendered Fonts’를 볼 수 있는데, 여기서 해당 컴포넌트에 있는 글자들 중 몇 글자가 어떤 폰트로 렌더링되었는지를 볼 수 있다. Roboto하고 (8 glyphs)라고 되어있으면 8글자가 Roboto 폰트로 렌더링됐다는 뜻.

    • 이모지는 문자열에서 length 2를 가지며, 이모지에 대한 정규식은 라이브러리로 나와있다.

    • onfocus의 event 객체 타입은 전역에 선언되어있는 Focus이벤트이다. React에서 FocusEvent 타입을 가져와서 사용하려할 때 충돌이 발생한다.

    • 아이폰X 부터 상하단의 둥근 영역이 전부 디스플레이에 포함되면서, 해당 영역으로 인해 컨텐츠가 잘리는 것을 방지하기 위해 safe-area를 별도로 두고 있다. 이 safe-area 밖의 공백 영역을 위해 아이폰X 에서 사용 가능한 CSS 속성인 env() 속성과 4개의 변수가 존재한다. (iOS 11.2 이전에는 constant, 그 이후에는 env)

    • Visual Viewport API를 사용하면 가상 키보드의 활성화 여부를 감지할 수 있다. 가상 키보드가 올라가고 내려가는 것은 window.visualViewport 에서 발생하는 resize 이벤트로 감지할 수 있다.


    기타

    CSS line-height

    CSS에서 동일한 font-size를 지정해도, font-family에 따라 글자의 높이가 서로 다르게 렌더링되는 경험이 있을 것이다. line-height의 값을 1로 정하는 것이 왜 옳지 않은지 설명한다.

    Ref https://mygumi.tistory.com/366 https://wit.nts-corp.com/2017/09/25/4903

    조금만 신경써서 초기 렌더링 빠르게 하기

    JavaScript와 HTML, CSS 정적 리소스들을 활용하여 웹 애플리케이션을 구성하는 스택인 JAM Stack 구조에서 성능을 최적화시킬 수 있는 방법을 소개한다.

    토스에서는 SSG(Static Site Generation)를 사용하고 있다. SSG는 앱을 빌드하는 시점에 미리 그려두고 이를 서빙하는 방식으로, JAM Stack에서 정적 리소스를 생성하는 용도로 사용한다. 컴파일 단계에서 미리 그릴 수 있는 부분을 최대한 그려서 사용자에게 도달하는 최초 index.html을 구성한다. Next.js 프레임워크를 사용하여 SSG를 활용하고 있다.

    비동기를 제어할 때는 React의 Suspense를 사용하는데, Next.js와 함께 사용 시 Suspense는 서버사이드 렌더링을 지원하지 않기 때문에(알파 버전이 공개되어 있는 React18에서는 지원할 예정이다.) Suspense를 한번 감싸서 사용해주고 있다.

    Copy
    import { useState, useEffect, Suspense as ReactSuspense } from "react";
    
    export function Suspense({
      fallback,
      children,
    }: ComponentProps<typeof ReactSuspense>) {
      const [mounted, setMounted] = useState(false);
    
      useEffect(() => {
        setMounted(true);
      }, []);
    
      if (isMounted) {
        return <ReactSuspense fallback={fallback}>{children}</ReactSuspense>;
      }
      return <>{fallback}</>;
    }

    Suspense로 제어하고 있는 컴포넌트를 SSG로 빌드하면 fallback이 렌더링된다. 이때 컴포넌트와 API를 가깝게 배치시켜, 데이터가 필요한 곳에서 가장 가까운 곳에서 API를 호출해주도록 한다. 즉, 컴포넌트를 작게 쪼개 각각을 Suspense로 감싸준다.

    Copy
    function UserPage() {
      return (
        <Layout>
          <h1>사용자 정보</h1>
          <dl>
            <dt>이름</dt>
            <Suspense fallback={<dd>Loading</dd>}>
              <UserName />
            </Suspense>
          </dl>
          <h2>사용자 상세 정보</h2>
          <Suspense fallback={<dd>Loading</dd>}>
            <UserDetailInfo />
          </Suspense>
        </Layout>
      );
    }

    UserNameUserDetailInfo 컴포넌트를 각각 Suspense로 감쌌기 때문에 UserPage에는 먼저 데이터가 준비된 컴포넌트부터 렌더링이 시작된다. 사용자는 더 이상 멍하니 흰 화면을 보고 있지 않아도 된다.

    Ref https://toss.tech/article/faster-initial-rendering?fbclid=IwAR04X-6TSyHL4R385tFwtOuQYLUZceGcbFWo8isPowjQtxShaJlNipN0hcs

    css-in-js 라이브러리 코드로 파악해보는 css-in-js의 이모저모

    CSS-in-JS 방식의 하나인 emotion 라이브러리를 사용할 경우 dev와 prod에서 확인할 수 있는 스타일 태그의 형태가 다르다. prod에서는 스타일 태그들이 비어있다. dev에서는 DOM을 수정하는 방식을 선택하는 반면, prod에서는 CSSOM을 수정하는 방식을 선택하기 때문이다.

    이는 runtime CSS-in-JS라는 emotion의 특성과 겹치는데, styled-component도 동일하다. prod에서도 dev와 같이 style 태그를 직접 추가한다면, 컴포넌트에서 런타임에 스타일을 수정할 때마다 style 태그가 추가되고, 그러면 DOM 트리를 다시 그리고 또 CSSOM 트리를 구축하는 과정이 반복되어 렌더링이 차단되기 때문이다.

    emotion은 DOM 트리는 수정하지 않고 CSSOM을 수정하는 방식을 선택하여 DOM 트리 파싱에 드는 시간을 줄이는 방식을 선택한 것이다. (styled-component도 마찬가지)

    🤔 JavaScript로 조작하는 UI 스타일링들은 JavaScript 런타임과 연관이 생기고, 성능 관련 문제들을 유발시킨다. 그렇다면 아예 runtime이 아닌 라이브러리를 쓰면 되지 않을까?

    linaria라는 zero runtime css-in-js 라이브러리가 있기는 하다. linaria는 프로젝트 빌드 시에 CSS 파일에 스타일을 추출하고 CSS 파일을 로드하는 방식으로 동작한다. babel plugin과 webpack loader를 사용해서 빌드 시에 별도의 CSS 파일을 생성하고, 이 파일 안에서 prop이나 state 등에 의한 값들을 CSS 변수로 정의하고 CSS 변수의 값을 변경시킴으로써 동적 스타일링을 구현한다.

    CSS와 JavaScript가 별도의 파일에 있기 때문에 CSS와 JavaScript를 병렬로 로드할 수 있고, CSS 구문 분석과 같은 추가 작업이 런타임에 수행될 필요가 없기 때문에 런타임 성능이 향상된다. 하지만 CSS 변수를 사용한 방식인 만큼 브라우저 호환성에 문제가 있다.

    🤔 atomic css는 또 뭘까?

    facebook… 아니 메타에서는 빌드 타임에 CSS를 생성해 atomic css를 JavaScript적인 방법으로 활용할 수 있는 stylex 라이브러리를 개발하여 CSS 파일 크기를 80% 줄이면서 최적화를 극대화했다.

    atomic css가 갖는 특징은 다음과 같다.

    • tailwind와 같이 원자 단위로 css를 작성
    • css를 변수처럼 선언하고 해당 스타일이 필요한 HTML tag 혹은 컴포넌트가 className 등을 통해 스타일을 가져다 쓰는 방식
    • 사용하는 컴포넌트가 많아지고 UI복잡도가 올라가면 장점은 더 극대화

    Ref https://ideveloper2.dev/blog/2022-01-25—emotion으로-파악해보는-css-in-js의-이모저모/


    마무리

    마침 지금 듣고 있는 아이유의 unlucky에서 ‘울고 싶을지 몰라~’하는 가사가 지나갔다. 진짜 울고싶다 😫 진짜 unlucky한 인생이다. 일적으로도, 공부도 취미도 아무 문제들이 없었는데 그냥 또 불안해하던 타이밍에서 진짜 최악의 일이 터졌다. 근데 자꾸 최악이라는 표현을 쓰니까 별 거 아닌데도 최악이라고 규정지어버리는 것 같다. 누군가한테 옮은 말버릇인 것 같은데 쓰지 말아야겠다. 자꾸 스스로를 학대하면서 침잠하지 말고 벗어날 생각을 해야지 지은아.

    좋은 일이 없었나. 아, 회사 사무실이 있는 롯데타워에 갔다. 38층까지 있는 사무실 건물의 38층이라니! 정말 처음 보는 뷰였다. 고소공포증이 있는 줄은 전혀 몰랐는데, 좀 무서웠다. 고작 하루였지만 팀원 분도 뵙고, 투어도 하고, 사무실이라는 공간에서 일하니 더 집중도 되는 것 같고 좋았당. 가끔씩 또 가고 싶다!

    01

    Relative Posts:

    2월 3주차 기록

    February 19, 2022

    2월 첫주차 기록

    February 5, 2022

    zigsong

    지그의 개발 블로그

    RotateLinkImg-iconRotateLinkImg-iconRotateLinkImg-icon