ziglog

    Search by

    7월 2주차 기록

    July 9, 2022 • ☕️☕️☕️ 13 min read

    npm 용서못해…


    배워가기

    루틴 vs 코루틴

    • 루틴: 진입하면 반드시 반환까지 한 번에 실행됨
    • 코루틴: 진입한 뒤 중간에 반환하고 다시 그 위치부터 재시작 가능

    자바스크립트에서는 yield, await 같은 특정 키워드나 약속된 함수를 호출하는 부분을 기준으로 서브루틴을 나눈다.

    반면, Kotlin에서는 suspend 함수를 호출하는 위치를 기준으로 서브루틴을 나눈다.

    Next.js redirects

    Next.js를 사용할 때 next.config.js 파일에 다음과 같은 redirects 옵션으로 특정 경로에 대한 리다이렉션을 설정할 수 있다.

    Copy
    redirects: () => {
      return [
        {
          source: "/",
          destination: "/packages",
          permanent: true,
        },
      ];
    },

    Next.js useRouter

    Next.js의 useRouter 가 리턴하는 NextRouter 는 유용하게 사용할 수 있는 여러 메서드들과 값들이 담겨있어서 편하게 사용 가능하다.

    Copy
    export declare type BaseRouter = {
      route: string;
      pathname: string;
      query: ParsedUrlQuery;
      asPath: string;
      basePath: string;
      locale?: string;
      locales?: string[];
      defaultLocale?: string;
      domainLocales?: DomainLocale[];
      isLocaleDomain: boolean;
    };
    export declare type NextRouter = BaseRouter & Pick<Router, 'push' | 'replace' | 'reload' | 'back' | 'prefetch' | 'beforePopState' | 'events' | 'isFallback' | 'isReady' | 'isPreview'>;

    스냅샷 테스트

    jest render의 반환값을 json으로 바꿔서 스냅샷 테스트를 할 때, RangeError: Invalid string lengthError: Invalid string length 에러가 뜨는 경우가 있다.

    jest내부의 prettier-format에서 JSON.stringify 값 내에 엄청나게 긴 string을 넣으면 나는 오류다. 그런데 react-test-renderer로 스냅샷을 생성하면 정상 동작한다.

    그럼 jest의 render와 react-test-renderer가 같지 않다고 생각해볼 수 있다.

    스냅샷의 문자열이 워낙 길다보니 테스트 코드때문에 컴퓨팅 비용이 많이든다고 생각할 수 있는데, jest는 처음부터 성능을 염두에 두고 작성되었고 스냅샷 테스트도 그렇다고 한다. txt파일이고 사이즈도 작은편(300kb미만)이라서 빠르고 안정적이라고 한다.

    🐸 스냅샷 테스트는 TDD보다는 코드변경 이후 변경사항이 어떤것인지를 파악하는데 유리하다.

    Ref [https://jestjs.io/docs/snapshot-testing#:~:text=Jest%20has%20been,than%20300%20KB.](https://jestjs.io/docs/snapshot-testing#:~:text=Jest has been,than 300 KB.)

    jest의 import alias

    import alias를 사용하는 파일에서 테스트를 하기 위해서는 jest.config.js에서 해당 alias에 대한 모듈 경로를 맵핑 해주어야 한다.

    Copy
    // jest.config.js
    moduleNameMapper: {
      // import "@@module/models/..."
      "@@module/(.*)$": '<rootDir>/packages/my-module/src/$1'
    }

    Promise 함수의 argument 실행 순서

    Promise 함수에서는 argument가 먼저 판별되어야 함수가 실행된다.

    다음 코드를 살펴보자.

    Copy
    const d = async () => {
      try {
        const a = await Promise.all([b(), await c()]);
        console.trace(a, 5);
      } catch (e) {
        console.error({ e });
        console.error(6);
      }
    };
    
    const b = () =>
      new Promise((resolve, reject) => {
        console.trace(1);
        setTimeout(() => {
          console.trace(2);
          reject('b');
        }, 2000);
      });
    
    const c = () =>
      new Promise((resolve, reject) => {
        console.trace(3);
        setTimeout(() => {
          console.trace(4);
          reject('c');
        }, 1000);
      });
    
    d();

    Promise.all() 함수에서, argument가 먼저 판별되어야 함수가 실행된다.

    코드의 최상단 함수 d()의 호출에서, Promise.all()의 인자인 b()는 실행이 되었다. 그러나 await c()가 판별 중 에러가 발생하여 Promise.all()이 실행되지 못한다. 따라서 b()의 Rejection을 Promise.all()이 해결해주지 못한다.

    이 경우 14까지는 Warning(UnhandledPromiseRejectionWarning), 15부터는 Error로 처리되어 UnhandledPromiseRejection가 발생한다.

    위 코드는 결국 아래의 코드와 동일하게 동작한다. (위 코드에서의 Promise.all()은 실행되지 못했기 때문)

    Copy
    const d = async () => {
      try {
        b(); // ⬅️
        await c(); // ⬅️
    
        console.trace(a, 5);
      } catch (e) {
        console.error({ e });
        console.error(6);
      }
    };

    ++a/a++과 같은 연산의 우선순위, 비동기문맥/동기문맥이 어떻게 흘러가는지 알 수 있는 좋은 예제다!

    aws cli의 옵션

    aws climv, cp, rm 은 하나의 파일에 대해서만 적용 가능하다.

    여러 파일에 대해서 적용하고 싶다면 -exclude, -include, -recursive 옵션을 적용하면 된다.

    다만, -recursive 옵션을 사용하면 해당 path 내의 서브 디렉토리까지도 포함하게 되어서, flat한 디렉토리 구조가 아닌 경우에는 불필요한 파일이 필터링될 수 있다.

    Ref https://docs.aws.amazon.com/cli/latest/reference/s3/index.html#use-of-exclude-and-include-filters

    flex 정렬 주의사항

    css flex 정렬 시, 2개의 요소에 각각 정확한 50% 너비를 줄 때 flex: 1 값을 활용할 수 있다.

    하지만 하나의 요소에는 border-line이 그려져있고 하나의 요소에서는 border-line이 그려져있지 않을 때, border 크기에 의해서 두 요소가 정확하게 반반으로 너비가 정해지지 않는다.

    이때 border line을 box-shadow로 그려줌으로써, 정확하게 반반 너비로 나눠줄 수 있었다. 또는 flex: 50%으로 설정하면 된다.

    Next.js의 Custom Document

    Next.js의 Document를 수정하여 html, body, head 등 HTML 마크업을 업데이트할 수 있다. 서버에서만 렌더링되므로 이벤트 핸들러 등을 사용할 수는 없다.

    서버에서만 렌더링 되기 때문에 pages/_document.tsx<title> 태그를 추가할 수 없다.

    이때 <meta name=”viewport” …> 뷰포트 메타태그는 중복 제거를 할 수 없으므로 pages/_app.tsx에서 next/head로 처리한다.

    😮 실제 프로젝트에서는 favicon 관련 태그(link, meta)는 pages/_document.tsx 에, title 및 뷰포트 등은 pages/_app.tsx 에 넣어서 사용했다.

    Ref

    react-hook-form 이것저것

    • react-hook-form의 reset은 현재 value를 단순히 변경하는 게 아니라, useForm 내에 있는 defaultValue를 재설정하는 것이다.
    • useForm의 옵션인 shouldUnregister는 input이 unmount될 때 name에 대응하는 값을 react-hook-form의 데이터에서도 함께 제거한다. 단, defaultValue값이 변경되는 것이 아니라 onSubmit 함수에 전달되는 result에서 제거된다.

    I-prefix, yes or no

    🙂 YES

    • 이름짓기 편하다. (e.g. Button, IButton)
    • 에디터 인텔리센스로 자동완성하기 편하다.
      • 타입스크립트가 주는 개발편의성은 IDE의 기능에 굉장히 의존적인 편이다.
      • prefix를 붙임으로써 IDE에서 타입스크립트를 사용하기 더 편해질 수 있다. (개발 편의성이 올라간다.)
    • Interface만 I를 사용하는 것도 하나의 일관성이 될 수 있다.

    🙁 NO

    • 타입스크립트는 공식문서에서 헝가리안 표기를 권장하지 않고 있다.
    • 일관성을 파괴하는 네이밍 컨벤션
      • 하나의 프로젝트 (또는 어떤 다른 기준)에서 snake_case를 사용한다던가, camelCase를 사용한다던가 이런 표기법은 일관성이 가장 중요하다. 다른 변수나 함수 네이밍에는 헝가리식 표기법을 적용하지 않다가, 인터페이스에만 헝가리식 표기법을 적용하는 것은 잘못된 것이다.
    • 컨벤션의 목적과 사용되는 언어가 근본적으로 맞지 않는 문제
      • 자바스크립트와 같은 언어에서 변수 또는 함수에 자료형을 드러내기 위해서 사용하던 헝가리식 표기법을 구조적 타이핑을 기반으로 하는 타입스크립트에 적용하는 것은 좋지 않다.

    언어의 컨셉에 맞는 사용에 더 높은 가치를 둘지, 개발 편의성에 더 높은 가치를 둘지의 차이!

    filter(Boolean)

    Boolean을 iterator로 사용하여 배열을 믿을 수 있는 상태로 만들어주는 방법이다.

    false, 0, -0, 0n, "", null, undefined, NaN 을 제거할 수 있다.

    Copy
    let array = [false, 0, -0, 0n, "", null, undefined, NaN, { name: "zig" }];
    array.filter(Boolean) // [{ name: "zig" }]

    useEffect의 cleanup

    useEffect의 cleanup 함수는 다음 변경이 일어나 업데이트 되기 전 실행되기 때문에, 이를 이용하여 컴포넌트 첫 마운트 시는 제외하고 실행하는 것처럼 보이게(?) 만들 수 있다.

    Copy
    useEffect(() => () => snackbar.success({ description: '성공!' }), [_active] )
    1. useEffect 진입
    2. useEffect 로직 실행
    3. cleanup 함수 등록 (등록만 했기 때문에, 처음에는 아무 동작도 하지 않는다.)
    4. _active의 상태 변경
    5. 2번 째 useEffect 함수 실행
    6. useEffect 로직 실행 전 등록된 cleanup 함수 실행
    7. useEffect 로직 실행

    pnpm cli에서 패키지 선택

    pnpm cli에서 특정 패키지가 아니라 패키지 세트를 선택할 때는 따옴표가 없으면 못알아듣는다. 패키지 세트를 선택할 때 쓰는 건 패키지 명이 아니라 glob 패턴이라서 그런 것 같다.

    • pnpm --filter @projects/my-project cli build dev ✅ OK
    • pnpm --filter "@project/*" cli build dev ✅ OK
    • pnpm --filter @project/* cli build dev 🚨 Error

    Ref https://pnpm.io/ko/filtering#—filter-glob---filter-glob

    pre-commit-msg

    husky 라이브러리는 .git/hooks 폴더를 건드리지 않고도 git hook 스크립트를 제어 할수 있게 도와준다. (.git/hooks 폴더는 git으로 기록되지 않는다)

    Git hook 리스트 중에는 pre-commit-msg 가 있고, prefix 를 붙이는 것과 같이 커밋 메세지를 수정할 수 있다.

    쉘 스크립트 명령어를 이용하여 현재의 브랜치가 feature 브랜치에서 작업중이라면 브랜치 이름으로 부터 jira 이슈번호를 가져올수 있다. 그냥 커밋메세지를 커밋하면 자동으로 jira 이슈번호를 추가해준다.

    react 17의 새로운 JSX Transform

    react 17 버전부터 새로운 JSX Transform이 도입되었고 (대표적으로 import React from 'react'가 필요 없어졌다.), 이로 인해 react-17 이상 환경에서 사용될 때 babel에서 추가 설정이 필요로 해졌다.

    바로 babel 리액트 설정 중 runtime: automatic 옵션을 추가해줘야한다. 이 옵션은 새로운 JSX 형태를 transfile해주는 함수를 자동으로 import 해주는 역할을 해준다.

    iOS 프로젝트의 구성

    IOS 프로젝트는 xcode target들의 집합으로 이루어진다. target은 하나의 프로덕트이며, 하나의 프로젝트에서는 여러개의 target으로 이루어져있다.

    그래서 IOS 기기 빌드할때 필요한 권한들은 프로젝트 내의 target에 모두 각각 설정해주어야한다. target들은 일반적으로 프로젝트의 빌드 속성을 상속받지만, mac업데이트 후에 상속이 풀리는 경우가 있다.


    이것저것

    • html <a /> 태그의 다운로드 속성은 cross-origin requests를 지원하지 않는다. href의 이미지 파일 경로가 cross-origin이면 바로 다운로드 받지 않고 preview를 띄운다.

    • css revert - 현재 엘리먼트에 캐스캐이딩 된 속성을 style origin으로 되돌린다. 간단히 말해 부모속성으로 돌아가거나, user agent default 상태로 돌아간다.

    • 타입스크립트에서 객체의 value들만 뽑아서 타입으로 사용하는 방법

      Copy
      const PATH = {
        PACKAGES: "/packages",
        TEAMS: "/teams",
        STATISTICS: "/statistics",
      } as const;
      
      export type PathNames = typeof PATH[keyof typeof PATH];
    • test suite - 제스트에서 사용하는 테스트 관련 단위로, 하나 이상의 테스트가 더 큰 단위의 기능을 테스트할 때 이를 묶어 테스트 스위트로 묶을 수 있다.

    • img 태그는 기준선을 가지고 있지 않아서, 기본 vertical-align: baseline 으로 지정된 맥락에서 이미지의 아래쪽 모서리가 텍스트 기준선으로 가게된다.


    기타

    Mobx 라이브러리의 초기 컨셉

    MobX는 모든 파생(derivation)을 동기적으로 실행한다. 많은 라이브러리에서 반복적으로 떠오르는 문제는 ‘예측가능성(predictability)’이다. 비동기를 사용하는 많은 프레임워크가 코드를 두 번 실행하거나, 딜레이를 두고 실행한다면, 디버깅이 어려워졌다.

    Flux 패턴, 특히 Redux에서는 실행되는 함수를 추적함으로써 예측가능성의 문제를 타파하여 대중적으로 인기를 얻었다. Redux에서는 불변성을 유지하는 것이 중요하다. 반면 MobX는 조금 더 근본적인 방식의 접근을 취한다.

    1. 모든 mutation의 조합과 파생(derivation)은 딱 한번만 실행되어야 한다.
    2. 파생은 절대로 stale하지 않으며, 파생의 effect는 모든 observer에게 즉시 알려져야 한다.

    예를 들어보자.

    Copy
    const user = observable({ firstName: "Zig", lastName: "Song" })
    
    user.lastName = "Kim";
    sendLetterToUser(user);

    위 코드에서, sendLetterToUser(user)가 실행될 때 해당 함수는 user의 어떤 lastName을 가지게 될까? MobX를 사용한다면 답은 항상 "Kim"(업데이트된 버전)이 될 것이다. MobX는 모든 변경을 동기적으로 업데이트한다. 이는 모든 예측 불가능성을 타파할 뿐 아니라, 디버깅을 쉽게 만들어준다.

    mutation은 여러 개의 변화를 자동으로 수행하기 위해 트랜잭션(transaction)으로 그룹지어져야한다. 트랜잭션은 파생 값의 실행을 트래잭션의 마지막까지 미루지만, 여전히 이를 동기적으로 실행한다. 만약 트랜잭션이 끝나기 전에 계산된 값(computed value)을 사용하더라도, MobX는 그 변경의 업데이트된 값을 반환해준다. 이 트랜잭션을 자동으로 트리거해주는 액션(action)은 상태를 업데이트할 함수를 가리킨다. 모든 액션이 끝난 뒤에 reaction이 나타난다.

    action, state, computed value와 reaction 사이의 관계는 다음과 같다.

    01
    • State(Observable State)

      - 관찰 받고 있는 상태

      • 모델을 채우는 객체, 비객체, 원시, 참조의 그래프
      • 어플리케이션의 데이터 셀
      • 특정 부분이 바뀌면, MobX에서는 정확히 어떤 부분이 바뀌었는지 알 수 있음
      • 이 state의 변화는 reaction과 computations를 일으킴
    • Derivation(Computed values)

      - 파생 값(연산된 값)

      • Observable State의 변화에 따른 값
      • 기존의 상태값과 다른 연산된 값에 기반하여 만들어질 수 있는 값. 즉 특정값을 연산할 때에만 처리됨
      • 어플리케이션으로부터 자동으로 계산될 수 있는 모든 값
      • observable로부터 도출할 수 있으며, 값이 변경되면 자동으로 업데이트
      • 성능최적화를 위해 사용
    • Reactions

      - 반응

      • Observable State의 변화에 따른 부가적인 변화
      • 값이 바뀜에 따라 해야 할 일을 정하는 것을 의미
      • 파생 값과 비슷하지만 값을 생성하지 않는 함수
      • 대체로 I/O 와 관련된 작업
      • 적당할 때 자동으로 DOM이 업데이트 되거나 네트워크 요청을 하도록 만듬
      • when, autorun, reaction
    • Actions

      : 액션

      • Observable State가 사용자가 지정한 것을 포함한 모든 변경사항
      • 상태를 변경시키는 모든 것
      • MobX는 모든 사용자의 모든 사용자의 액션으로 발생하는 상태 변화들이 전부 자동으로 파생값(Derivation)과 리엑션(Reactions)으로 처리되도록 함

    리액션(reaction)보다는 계산된 값(computed value)를 사용하는 것이 좋다. 계산 과정에서 더욱 순수한 파생 값을 얻을 수 있으며, MobX에 의해 값이 추적되기 때문이다.

    계산된 값은 사이드이펙트를 발생시키지 않기 때문에, MobX는 계산된 값의 실행 순서를 안전하게 보장할 수 있다. 또 계산된 값은 자동으로 캐시된다. 특정 작업을 처리하는 것이 아니라, 의존하는 값이 바뀔 때 미리 값을 계산해놓고 조회할 때는 캐싱된 데이터를 사용한다.

    Ref

    삼가 IE의 명복을…

    이거 보러 경주 가고 싶다.

    Ref https://www.youtube.com/watch?v=aBLwAnubhec&feature=youtu.be

    올인원 자바스크립트 런타임, Bun

    JavascriptCore 기반 자바스크립트 런타임이 새롭게 공개되었다.

    로우 레벨 언어인 Zig로 작성되었으며, node와 deno보다 훨씬 빠른 성능을 보여준다고 한다.

    나는 이토록 느린데 자꾸만 뭐가 생긴다…

    Ref https://bun.sh/


    마무리

    하… 월요일부터 꼬여버렸던 npm이 주말인 지금까지도 풀리지 않는다. 바쁜 팀원 분들을 수 차례 붙잡고 도움 요청을 하고, 전체 채널에도 읍소(?)해봤으나 도대체 해결될 기미가 보이지 않는다. 😭 잠잘 때도 밥먹을 때도 갑갑한 마음에 정말 도라버려… 살려줘…

    우테코에서 친해졌던 동기가 우리팀에 새로 들어왔다. 내가 돌보미 역할을 했는데, 망할 npm 때문에 정신 팔려있느라 제대로 도와주지도 못하고 정신이 예민해져있던 것 같다. 빨리 해결하고 다시 정신 차려야지.

    그래도 오랜만에 놀토 팀원들을 만났다. 아주 오랜만에 만난 게 아닌데도 뭐 그렇게 할 얘기가 많은지 수다수다수다수다 폭풍수다 재밌게 떨고 왔당 ☺️ 거지같은 라이브러리는 나를 배신해도 친구들은 배신하지 않는다.

    해결했다! 일요일 밤이 다 가기전에 겨우 해결헀다. 정말 속 시원하다. 세상이 아름다워 보여…


    Relative Posts:

    7월 3주차 기록

    July 16, 2022

    7월 1주차 기록

    July 2, 2022

    zigsong

    지그의 개발 블로그

    RotateLinkImg-iconRotateLinkImg-iconRotateLinkImg-icon