ziglog

    Search by

    5월 4주차 기록

    May 28, 2022 • ☕️☕️☕️ 15 min read

    오랜만에 프론트 정기밋업을 했다!


    배워가기

    mswjs

    mock service worker API를 사용해서 network 요청을 중간에 가로채서 대신 응답할 수 있다. 따라서 API url을 mock 전용으로 따로 구분할 필요 없으며, 서버 개발이 완료 됐거나 production 모드일 때 msw 기능을 끄면 이후 로직을 변경할 필요가 없다.

    (단, 조건은 msw로 제공하는 응답 스키마가 서버와 차이가 없어야 한다.)

    msw는 클라이언트에서 mock 서버처럼 동작하여, 클라이언트/서버 개발이 병렬로 진행될 때 서버의 데이터 스키마를 무작정 기다리지 않고 클라이언트에서 모델링을 진행할 수 있다.

    리소스 로드 최적화

    • preload
      • 크롬 기준 DevTools Priority: High
      • 현재 페이지에서 사용되는 리소스에 적용하기 적합하다. (히어로이미지, 웹폰트 등)
      • as 속성을 제대로 명시하지 않으면 브라우저가 두번 fetch 해온다.
      • 모던 브라우저는 리소스 우선순위 적용을 알아서 잘 하기도 하고 사용자의 대역폭을 고려했을 때 선택적, 전략적으로 preload 해야한다.
      • 모든 모던 브라우저에서 지원된다.
    • prefetch
      • 크롬 기준 DevTools Priority: Lowest
      • 미래에 사용되는 리소스에 적용하기 적합하다. (다른 뷰나 페이지로 내비게이팅)
      • 우선순위가 낮아 현재 페이지에 사용될 리소스에 적용하면 초기 렌더링 속도가 오히려 느려진다.

    Ref https://web.dev/i18n/en/preload-critical-assets/ https://medium.com/reloading/preload-prefetch-and-priorities-in-chrome-776165961bbf

    크롬의 4가지 캐시

    첫째, HTTP 캐시가 있다. HTTP 통신에 Cache-Control 헤더를 사용했을 때 캐시된다.

    둘째, 메모리 캐시가 있다. RAM 메모리에 저장되는 캐시로, 빠르지만 지속적이지 않다. max-age와 같은 유효한 Cache-Control이 없으면 이곳에 캐시된다. 크롬 개발자 도구의 Network 탭에서 Size 컬럼에 memory cache로 표시된다.

    01

    셋째, 서비스워커 캐시가 있다. 서비스 워커는 웹 응용 프로그램, 브라우저, 그리고 네트워크 사이의 프록시 서버 역할을 한다. 서비스 워커는 연관된 웹 페이지/사이트를 통제하여 탐색과 리소스 요청을 가로채 수정하고, 리소스를 굉장히 세부적으로 캐싱할 수 있다. 이를 통해 웹 앱이 어떤 상황에서 어떻게 동작해야 하는지 완벽하게 바꿀 수 있다. 서비스워커는 fetch 이벤트의 중간자 역할로 사용할 수 있다. 이 경우 서비스워커는 HTTP를 통해 정보를 요청하는 대신 가지고 있는 캐시에서 자료를 전달한다. 캐시가 삭제되지 않는 한 브라우저는 인터넷 연결 없이도 정보를 보여줄 수 있다. window.navigatorserviceWorker 필드로 접근하여 서비스워커 캐시를 설정할 수 있다.

    마지막으로, 푸시 캐시가 있다. 이에 대한 정보는 많이 나와있지 않은데, 서버 푸쉬 기능을 제공하는 HTTP2를 사용했을 때 HTTP2 세션에 저장되는 캐시인 것 같다. 정확한 명칭은 ‘unclaimed push streams container’라고 한다.

    Ref https://developer.mozilla.org/ko/docs/Web/API/Service_Worker_API https://stackoverflow.com/questions/54959244/where-is-chrome-push-cache-stored-physically

    parseInt(float) vs Number

    1. 'A1.23' (숫자 혹은 +-가 아닌 문자로 시작할 때)

      Copy
      parseInt("A1.23"); // NaN
      Number("A1.23"); // NaN
    2. 123A (중간에 문자가 나올 때)

      Copy
      parseInt("123A"); // 123
      Number("123A"); // NaN
    3. 1.23 (소수점이 있을 때)

      Copy
      parseInt("-1.24"); // -1
      parseFloat("-1.24"); // -1.24
      Number("-1.24"); // -1.24
    4. 1.23A (소수점과 함께 문자가 있을 때)

      Copy
      parseInt("1.23A"); // 1
      parseFloat("1.23A"); // 1.23
      Number("1.23A"); // NaN

    결론: Number는 처음부터 똑바로 된 숫자를 넣어줘야 한다.

    eslint

    eslint는 린트 대상 파일과 가까운 eslintrc 파일의 구성을 우선시한다. 그 후에 디렉토리 상위를 탐색하면서 eslintrc 파일에 root: true가 있거나, 최상단 루트디렉토리에 도달할 때 까지 발견한 모든 eslintrc 파일을 병합한다.

    root: true는 eslint 구성을 어디까지 병합할 지 조절할 때 사용한다.

    eslintrc 파일과 eslintConfig 필드가 있는 package.json이 동일한 디렉토리에 있다면 eslintrc 파일의 구성이 적용되며, package.json의 구성은 무시된다. (합쳐지지 않는다.)

    시각 보정

    글자 디자인 시 발생하는 착시 현상들을 방지하고 글자의 시각적 결함을 찾아내어 글자의 디자인을 세밀하게 조정하는 작업을 말한다. 시각보정은 글자의 가독성과 판독성에 지대한 영향을 끼치기 때문에, 폰트디자인에 있어 가장 핵심이라고 할 수 있다.

    간단하게 말하자면, 실제 수치상으로는 맞는데 사람의 시각에서는 이상하여 보정하는 작업이다.

    02

    성능 지표

    • TTFB (Time To First Byte) : 페이지를 요청했을 때 서버에서 데이터의 첫 번째 바이트가 도착하는 시점을 나타낸다. TTFB는 주로 서버 성능과 직결된다.
    • FCP(First Contentful Paint) : 첫 요소가 로드 될 때까지 시간
    • FMP(First Meaningful Paint) : 사용자에게 의미있는 첫 요소가 로드 될 때까지 걸리는 시간
    • LCP(Largest Contentful Paint) : 주요 콘텐츠가 로드 될 때까지 걸리는 시간
      • FCP의 경우 로딩바가 첫요소가 되는 경우가 많아 제외. FMP 또한 정확하지 않다고 판단하여 LCP를 기준으로 로딩 속도를 측정한다.
      • 구글 기존 2.5미만이면 좋음, 4.0미만이면 개선이 필요함, 4초 이상이면 형편없음으로 분류한다.
      • 화면에서 가장 큰 요소가 계속 변경되면 LCP 속도는 늦어진다.
    • 그 외에도 FID(First Input Delay), CLS(Cumulative Layout Shift) 등이 있다.
      • FID(상호 작용력) : 사용자의 행동에 대해 실제로 이벤트 핸들러가 반응하기까지 걸리는 시간이다. 100ms미만 좋음. 300ms미만 개선 필요.
      • CLS(시각적 안정성) : 시작 위치에서 레이아웃이 얼마나 변화했는지에 대한 측정이다. 0.1미만 좋음. 0.25미만 개선 필요.
    • TTI(Time to Interactive) : 자바스크립트의 초기 실행이 완료되어서 사용자가 직접 행동을 취할 수 있는 순간이다.
      • TTI는 FID보다 늦다.
    • TBT (Total Blocking Time) : TBT는 주 스레드가 input 응답을 막을 정도로 오래 차단 되었을때 FCP와 TTI 사이의 총 시간을 나타낸다. 긴 작업의 차단 시간은 50ms를 초과하는 시간으로 계산한다. 즉 task의 작업 시간이 250ms이면 TBT는 200ms이다.

    path.join vs path.resolve

    path.join은 주어진 경로 segment 를 모두 결합한 다음, 결과 경로를 정규화 한다.

    path.resolve는 절대 경로가 생성될 때까지 오른쪽에서 왼쪽으로 경로를 정규화 한다. 이때 경로 인자를 탐색하는 도중 / 을 만나면 절대경로로 인식해서 나머지 경로인자들을 무시한다. 절대경로를 찾지 못할 경우 워킹디렉토리(루트폴더) 가 자동으로 앞에 연결된다.

    cf) path.resolve() === __dirname 은 실행중인 파일이 현재 작업 디렉토리에 있을 때에만 성립한다.

    Copy
    const path = require("path");
    
    console.log(path.resolve()); // /home/runner/6pev2q2j72v
    console.log(path.resolve("a", "b", "c")); // -> /home/runner/6pev2q2j72v/a/b/c
    console.log(path.resolve("/a", "/b", "c")); // -> /b/c
    console.log(path.resolve("/a", "b", "c")); // -> /a/b/c
    console.log(path.resolve(__dirname, "/a")); // -> /a
    
    console.log(path.join("/a", "/b", "/c")); // -> /a/b/c
    console.log(path.join("/a", "/b", "c")); // -> /a/b/c
    console.log(path.join("/a", "b", "c")); // -> /a/b/c
    console.log(path.join("a", "b", "c")); // -> a/b/c

    @testing-library/user-event

    @testing-library/user-eventfireEvent 의 기반으로 빌드된 패키지지만, 사용자 상호작용과 더 유사한 여러 메서드들을 제공한다. fireEvent.change 는 단순히 input의 하나의 변경 이벤트를 트리거하지만 type 호출은 문자마다 keyDown, keyPress, keyUp 이벤트들을 트리거 한다. 즉, userEvent가 실제 사용자 상호작용과 유사하다.

    언젠가 @testing-library/dom에 포함될수도 있다고 한다.

    strict: false

    tsconfig에서 "strict": falsestrictNullChecks 도 false가 되어서 undefined, null 타입을 무시한다. strict 모드에 속하는 세부 옵션들은 다음과 같다.

    서로 다른 타입의 배열 유니온 타입

    서로 다른 타입의 배열 유니온 타입의 경우 몇 가지 메소드를 사용할 수 없는 경우가 있다.

    예를 들면,

    Copy
    type A = number[] | string[];

    A 타입의 배열에는 filter, reduce, every, find 등의 메소드를 사용할 수 없다. 그러나 forEach, map, some 등은 사용 가능하다.

    이유는 각 메소드 콜백함수의 파라미터 타입이 number인지, string인지 알 수 없기 때문이다. 각 메소드의 인터페이스를 까보면 위에서 사용할 수 없는 메소드들은 모두 제네릭을 사용하고 있다.

    extends의 또 다른 사용

    type을 정의할 때 extends 키워드는 단순히 상속만을 위해 사용하지는 않는다.

    Copy
    type A = string | number;
    type B = string;
    type C = B extends A ? boolean : never;

    위처럼 삼항연산자와 함께 사용하면 조건부 타입 정의가 가능하다. BA에 할당 가능한 타입이라면, 타입 Cboolean이고 아니라면 never가 된다.

    window.navigator.userAgent

    window.navigator.userAgent를 파싱하여 브라우져 버젼, OS 버젼 등을 확인할 수 있다.

    • Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.5005.61 Safari/537.36 : 맥북 크롬
    • Mozilla/5.0 (iPhone; CPU iPhone OS 13_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 : IE 못지않은 극악의 iOS 13.4

    TypeScript never

    TypeScript의 never는 return type에 지정해도 당장의 타입체킹에 별다른 영향을 미치지 못 한다. 그러나 never가 함수의 파라미터에 사용되는 경우 타입을 체킹할 수 있다.

    Copy
    function fn(input: never) {}
    
    // 오직 `never` 만 받는다.
    declare let myNever: never;
    fn(myNever); // ✅
    
    // 아무 값이나 전달하거나 아무 값도 전달하지 않으면 타입 에러 발생
    fn(); // ❌ 인자 'input'에 아무 값도 주어지지 않음
    fn(1); // ❌ 'number' 타입은 'never' 타입에 할당할 수 없음
    fn("foo"); // ❌ 'string' 타입은 'never' 타입에 할당할 수 없음
    
    // `any`도 통과할 수 없다.
    declare let myAny: any;
    fn(myAny); // ❌ 'any' 타입은 'never' 타입에 할당할 수 없음

    return type이 void인 경우는 관찰대상이 아니다. 따라서 return 값에 어떤 타입이 와도 타입체킹을 하지 않는다.

    Copy
    const foo: () => void = () => {
      return "bar"; // ✅ OK
    };

    Ref https://ui.toast.com/weekly-pick/ko_20220323

    react-query의 cacheTime vs staleTime

    cacheTime은 쿼리 인스턴스가 unmount된 후 메모리에 데이터를 캐싱해두는 시간

    • 기본 300초
    • 이 시간이 지나면 캐시데이터를 refetch한다.
    • 쿼리 인스턴스가 마운트 될 때 캐시데이터가 있으면 첫 value로 사용하지만, fetch는 이루어진다.

    staleTime은 최초의 쿼리 인스턴스가 마운트 된 이후부터 데이터를 구식이라고 판단하는 시점이다.

    • 기본 0초
    • 최초 fetch 이후에 staleTime 시간이 지나면 해당 데이터는 구식이라고 판단되어 refetch한다.
    • 쿼리 인스턴스가 마운트 될 때 staleTime이 남아있다면 fetch가 이루어지지 않는다.

    componentDidMount vs render

    리액트 클래스 컴포넌트 쓰는 사람 나야나

    render() 메서드는 클래스 컴포넌트에서 반드시 구현돼야하는 유일한 메서드로, JSX를 렌더링한다.

    componentDidMount() 는 모든 요소가 정상적으로 렌더링되었을 때 실행된다. (render() 메서드 이후에 실행된다.) 구체적으로는 컴포넌트가 마운트된 직후, 즉 트리에 삽입된 직후에 호출된다. DOM 노드가 있어야 하는 초기화 작업은 이 메서드에서 이루어지면 된다. 외부에서 데이터를 불러와야 한다면, 네트워크 요청을 보내기 적절한 위치이다.

    constructor()는 React 컴포넌트의 생성자로, 해당 컴포넌트가 마운트되기 전에 호출된다.

    Ref React Lifecycle Methods Render And ComponentDidMount

    git rebase 똑바로 하기

    1. git fetch
    2. git rebase origin/master
    3. git add .
    4. git rebase –continue

    다른 옵션들

    • git rebase –abort
    • git push origin HEAD -f
    • git push –force-with-lease origin HEAD

    Ref https://www.w3docs.com/snippets/git/how-to-rebase-git-branch.html

    JavaScript array 요리조리 주물주물

    object의 boolean 값으로 sorting하기

    Copy
    const arr = [
      { id: 1, bool: true },
      { id: 2, bool: false },
      { id: 3, bool: false },
      { id: 4, bool: true },
    ];
    
    // ✅ true values first
    const trueFirst = arr.sort((a, b) => Number(b.bool) - Number(a.bool));
    
    console.log(trueFirst);
    // [ { id: 1, bool: true }, { id: 4, bool: true }, { id: 2, bool: false }, { id: 3, bool: false } ]

    아래와 같이 해보자!

    Copy
    const newArray = array.map(
      ({ dropAttr1, dropAttr2, ...keepAttrs }) => keepAttrs
    );
    const arr = [
      { id: 1, name: "a", grade: 30 },
      { id: 2, name: "b", grade: 40 },
      { id: 3, name: "c", grade: 50 },
      { id: 4, name: "d", grade: 60 },
    ];
    
    const newArr = arr.map(({ id, ...keepAttrs }) => keepAttrs);
    console.log(newArr);
    // [
    //   { name: 'a', grade: 30 },
    //   { name: 'b', grade: 40 },
    //   { name: 'c', grade: 50 },
    //   { name: 'd', grade: 60 }
    // ]

    Ref https://bobbyhadz.com/blog/javascript-sort-array-of-objects-by-boolean-property https://stackoverflow.com/questions/18133635/remove-property-for-all-objects-in-array


    이것저것

    • package lock 파일을 삭제 및 변경하지 않도록 유의하자. lock 파일을 지웠다가 다시 패키지를 설치하면서 @latest 옵션으로 패키지가 설치되는 경우가 있는데, 이 때 패키지의 major 버전이 바뀌면서 예기치 못한 새로운 에러가 발생할 수 있다.

    • test를 작성할 때 queryBy 는 실패할 게 예상되는 상황에서 사용하는 게 좋다. (Ref)

    • 자바스크립트 Promise에서 resolve()를 호출해도 뒷 문장이 실행된다. 뒷 문장을 실행하고 싶지 않다면 return resolve()를 해주자.

    • gif > mp4 변환 같은 이미지 프로세싱은 CPU 점유율이 매우 큰 편이다.

    • 여러 개의 요소들이 묶여있는 그룹에서 어떤 요소의 width에 의해서 텍스트가 줄바꿈이 되면서 찌부되는 경우가 있는데, 이런 경우 white-space: nowrap으로 줄바꿈되지 않도록 방지하여 찌부되는 것을 막을 수 있다.

    • block 요소 두개 중 하나는 border line이 그려져있고, 하나는 그려져있지 않을 때, flex로 정렬되더라도 두 개의 width가 다르게 나온다. 이럴 경우 flex: 50% 를 주어서 두 개의 요소 길이를 같게 만들 수 있다.

    • onClick등의 이벤트 핸들러 안에서 에러를 throw하면 리액트 컴포넌트의 라이프사이클 밖에서 throw 된 에러이기 때문에 ErrorBoundary에 걸리지 않는다.

    • 컴포넌트 테스트를 작성할 때 테스트 케이스가 많아진다면 해당 컴포넌트가 너무 많은 역할을 하고 있는건 아닌지 의심해보자.

    • tel: 링크를 활용하면 휴대전화에서 전화번호를 자동입력할 수 있다.

      Copy
      <a href="tel:+49.157.0156">+49 157 0156</a>
    • css only-of-type - 같은 유형의 형제가 없을 때 사용한다.

      Copy
      /* Selects each <p>, but only if it is the */
      /* only <p> element inside its parent */
      p:only-of-type {
        background-color: lime;
      }
    • nexus를 이용하여 사설 npm package를 만들 수 있다. (Ref)


    기타

    Next.js도 중첩 레이아웃 할래

    Vercel의 Next.js의 RFC에서 Remix처럼 중첩 레이아웃을 지원하겠다고 한다!

    Ref https://nextjs.org/blog/layouts-rfc

    자바스크립트 v8 엔진의 가비지 컬렉션 동작 방식

    C나 C++과 같은 언어는 가비지 컬렉터(GC)를 이용하여 수동으로 쓸모없는 메모리 영역을 해제할 수 있다. 하지만 자바스크립트는 그런 능력이 없다! (자바스크립트는 대체 뭘 할 수 있는 건지!)

    정말로 자바스크립트의 V8 엔진은 대체 뭘 하는 걸까. V8은 힙 영역을 New/Old space으로 나누어, GC에 의해 Old space으로 옮겨진 객체는 오래된 것으로 판단하여 제거된다.

    구체적으로는, 마이너 GC(Scavenger)와 메이저 GC로 나뉘어 사용하지 않는 객체들을 메모리에서 제거한다. 마이너 GC는 오래된 객체를 New space에서 Old space로 옮긴다. 메이저 GC는 Mark-Sweep-Compact 알고리즘과 Tri-color 알고리즘을 사용하여 Old space에 있는 객체들을 마킹 후에 제거한다.

    전통적인 마이너 GC와 메이저 GC의 가비지 컬렉션이 수행할 때 프로그램이 멈추게 되며, 이 시간이 길어질수록 렌더링이 지연되어 UX 경험을 떨어뜨린다. 이에 최신 GC에는 여러 기술들이 추가되었다.

    • Parallel - 기존에는 메인 쓰레드 혼자 하던 일을 헬퍼 쓰레드들과 균등하게 나누어 일을 한다.
    • Incremental - 메인 쓰레드가 적은 양의 작업을 간헐적으로 처리한다.
    • Concurrent - 메인 쓰레드는 더 이상 가비지 컬렉션을 하지 않고, 헬퍼 쓰레드들이 수행한다.
    • Idle-time GC - 개발자는 GC에 직접 접근할 수 없다. 하지만 v8은 크롬과 같은 embedder에게 가비지 컬렉션을 유발할 수 있는 메커니즘을 제공한다. 크롬은 프로그램이 쉬는 free나 idle time을 알 수 있기 때문, 애니메이션 프레임 렌더링 작업이 16ms보다 빨리 끝난다면 크롬은 다음 프레임 작업 전까지 가비지 컬렉션을 유발한다.

    Ref https://fe-developers.kakaoent.com/2022/220519-garbage-collection/


    마무리

    오랜만에 프론트 정기밋업을 했다! 재밌었던 마수타와 여러 세션 발표가 있었다. 언젠가 (오랜 시간이 지난 후에…) 능력치가 쌓이면 발표해보고 싶다.

    그리고 우테코 프론트 3기 엠티를 다녀왔다. 25명 전참이라니… 도른 사람들. (한 명은 입실렌티 갔다 늦게 왔당 ㅋㅋㅋ)

    03

    Relative Posts:

    6월 1주차 기록

    June 4, 2022

    5월 3주차 기록

    May 21, 2022

    zigsong

    지그의 개발 블로그

    RotateLinkImg-iconRotateLinkImg-iconRotateLinkImg-icon