ziglog

    Search by

    2월 4주차 기록

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

    첫 월급 flex~


    배워가기

    React.cloneElement()

    Copy
    React.cloneElement(element, [config], [...children]);

    React.cloneElement()에 대한 공식문서 설명을 아래와 같다.

    element를 기준으로 새로운 React 엘리먼트를 복사하고 반환합니다. config는 key 와 ref 그리고 모든 새로운 props를 포함합니다. 새로운 엘리먼트에는 원본 엘리먼트가 가졌던 props가 새로운 props와 얕게 합쳐진 뒤 주어집니다. 새로운 자식들은 기존의 자식들을 대체합니다. config에 key와 ref가 없다면 원본 엘리먼트의 key와 ref는 그대로 유지됩니다.

    그냥 children을 렌더링하는 것과 대체 어떤 차이가 있는지 궁금했었는데, 코드 사례를 보고 나니 조금 이해가 되었다!

    inGroup이라는 이름의 prop을 받는 Button 컴포넌트가 있다고 치자. (이때 inGroup = true일 경우 레이아웃이 달라진다.)

    Copy
    export const Button = ({ inGroup, ... }: Props) => {
      return (
        <button>
          {inGroup ? (
            <span>Group Button</span>
          ) : (
            <span>Solo Button</span>
          )}
        </button>
      )
    }

    이때 ButtonGroup이라는 상위 컴포넌트 안에 렌더링하는 Button 컴포넌트의 경우 inGroup 프로퍼티를 반드시 true로 강제하고 싶다면, 아래와 같이 작성할 수 있다.

    Copy
    export const ButtonGroup = ({ direction, children, align }: Props) => {
      // 렌더링하는 children에 대해서 inGroup props를 강제해줌
      const childProps = {
        inGroup: true,
      };
    
      return (
        <div>
          {React.Children.map(children, (child, i) => {
            return <>{React.cloneElement(child, childProps)}</>;
          })}
        </div>
      );
    };

    주로 위와 같이 동일한 React element를 반복해서 렌더링할 때, 그리고 그때 렌더링되는 자식 요소들이 특정한 prop을 가져야 할 때 사용할 수 있다.

    TypeScript Omit vs Exclude

    • Omit<Type, Keys> - Type의 프로퍼티에서 Keys를 제거한다.
    • Exclude<UnionType, ExcludedMembers> - UnionType에서 ExcludedMembers에 할당할 수 있는 모든 유니언 집합을 제외한 타입을 반환한다.

    얼핏 보면 Omit에서 베이스가 되는 타입은 하나의 명시적인 타입(Type)이고, Exclude에서 베이스가 되는 타입은 어떤 교차 타입(UnionType)이라는 차이처럼 보인다. 둘의 차이를 좀 더 명확히 알아보자.

    OmitPick에 대응되는 유틸리티 타입으로, 베이스가 되는 객체 타입에서 특정한 프로퍼티들을 제거한다.

    Copy
    Omit<{ a: string, b: string }, 'a'> === { b: string }

    Exclude는 유니언 타입을 베이스로, 그 유니언의 특정한 조합을 제거한다.

    Copy
    Exclude<string | number, string> === number

    Exclude의 타입 시그니처는 아래와 같다.

    Copy
    type Exclude<T, U> = T extends U ? never : T;

    TUextends한다면, never 타입을 반환할 것이다.

    같은 타입을 제공했을 때 OmitExclude가 반환하는 타입이 달라지는 경우가 생긴다.

    Copy
    type T0 = Omit<{ a: string; b: string }, "a">; // 1️⃣
    type T1 = Exclude<{ a: string; b: string }, "a">; // 2️⃣
    type T2 = Omit<string | number, string>; // 3️⃣
    type T3 = Exclude<string | number, string>; // 4️⃣

    1️⃣ a가 제거되어 { b: string } 타입을 반환한다. 2️⃣ a{ a: string, b: string }extends하지 않기 때문에, { a: string, b: string } 타입을 반환한다. 3️⃣ string | number에서 모든 string key를 제거하기 때문에 (결국 모든 key를 제거하게 된다.) {} 타입이 반환된다. 4️⃣ stringstringextends하므로 제거되고, number 타입을 반환한다.

    Ref https://stackoverflow.com/questions/56916532/difference-b-w-only-exclude-and-omit-pick-exclude-typescript

    딥링크

    웹에서 http 혹은 https로 시작하는 주소로 특정 서비스의 웹페이지로 이동할 수 있듯이, 모바일 앱에서 특정 주소 혹은 값을 입력하여 앱 내부의 특정 주소로 이동하는 방법이다. 웹에서 바로 모바일 앱으로 이동할 때 이 모바일 딥링크를 사용한다.

    앱은 youtube://처럼 주로 애플리케이션의 이름이 프로토콜이 되는 경우가 일반적이다. 이러한 방식을 URI 스킴 방식이라고 하는데, 스킴값이 서로 중복되는 문제가 발생한다. 이를 근본적으로 해결하기 위해 유니버셜 링크(iOS)와 앱링크(Android)가 탄생했다. 유니버셜 링크는 애플에서 만든 앱에서만, 앱링크는 구글에서 만든 앱만 지원하기 때문에 원활한 광고 운영을 위해서는 URI 스킴 방식을 적절히 활용해야 한다.

    Ref https://help.dfinery.io/hc/ko/articles/360039757433-딥링크-Deeplink-URI스킴-유니버셜-링크-앱링크-구분과-이해

    배열의 원소 타입을 추론하는 코드

    Copy
    /** 배열의 원소 타입을 추론한다.
     * ex. ElementOf<string[]> === string
     */
    export type ElementOf<ArrayType extends readonly unknown[]> =
      ArrayType extends readonly (infer ElementType)[] ? ElementType : never;

    infernever 키워드를 이런 경우 사용할 수 있다. 또, 배열 원소 타입을 추론할 때 원소는 추론하고자 하는 배열 타입에 readonly를 붙여야 한다.

    Type Predicate

    Type Predicate를 활용하면 효과적으로 타입을 좁힐 수 있다.

    Copy
    function isFish(pet: Fish | Bird): pet is Fish {
      return (pet as Fish).swim !== undefined;
    }

    이렇게 사용자 정의 타입 가드를 만들어 사용하면 as 키워드로 타입 단언을 사용하지 않아도 타입 추론을 잘할 수 있다.

    git submodule

    submodule이란 git 저장소 안에 다른 git 저장소를 디렉토리로 분리해 넣는 것이다. 다른 독립된 git 저장소를 clone 해서 내 git 저장소 안에 포함할 수 있으며 각 저장소의 커밋은 독립적으로 관리한다.

    코어 로직들을 별도의 레포로 구성하고 이를 submodule로 가져와서 사용할 수 있다, 이때 submodule 레포를 수정한 후 이를 import 해와서 쓰는 경우, merge 순서가 중요하다(submodule 레포를 먼저 merge해야 한다.)

    Ref https://git-scm.com/book/ko/v2/Git-도구-서브모듈

    Perl

    Perl은 High Level에서 동작하는 동적 타입 언어이며, 쉘 스크립트에서 기원했기 때문에 시스템 관리에 사용하기 좋은 언어이다. 특히 텍스트 데이터 처리에 용이하다.

    정규 표현식과 Perl의 패턴 매치 연산자를 이용하여 특정 문자열에 대해 replace 동작도 수행할 수 있다. 예를 들어 s/(^|-)./uc($&)/ge;s/-//g 는 kebab-case를 PascalCase로 변환한다.

    • s/A/B/g : 모든 A를 B로 변환한다
    • s/A/B/ge : 모든 A를 B로 변환하는데 eval을 wrapping한다
    • (^|-). : 문자열의 시작 부분 혹은 “ 가 앞에 있는 모든 문자열의 경우 (1번째 그룹)
    • uc($&) : 대문자로 변환한다
    • ; : 식의 끝. 이후에 이어서 작성하면 새로운 식 적용 가능 (세미콜론 역할)
    • s/-//g : 모든 “를 빈 문자로 변환한다

    모노레포 빌드 도구

    모노레포 빌드를 도와주는 대표적인 도구로 lerna가 있으며, 이밖에도 Nx, Turborepo 등 모노레포 빌드를 돕기 위한 다양한 툴들이 있다. 해당 툴들은 아래와 같은 기능들을 제공한다.

    • 로컬 작업 오케스트레이션
    • 변화된 패키지(와 그로부터 영향을 받는 패키지)를 감지
    • 컴퓨테이션 캐싱
    • 패키지간 의존성 시각화

    이러한 툴들을 사용하지 않고 직접 yml 파일을 수정 할 수도 있다. yml 파일의 rules를 수정하여 해당 패키지에 변화가 있을 때에만 script를 실행하도록 설정할 수도 있ek.

    Copy
    rules:
     - if: 'BRANCH_NAME'
        changes:
        - packages/common/**/*
    script: yarn build

    하지만 이 경우에 변화된 패키지를 의존하는 패키지들은 변화 감지가 되지 않아 공통 패키지의 변경사항(공용 컴포넌트의 스타일이 변경되는 등)이 다른 패키지에 반영되지 않을 수 있다.

    DDL vs DML vs DCL

    • DDL(Data Define Language, 데이터 정의어)
      • 예) Create, Alter, Drop, Truncate
      • 데이터베이스를 정의하는 언어
      • 데이터를 생성, 수정, 삭제하는 등의 데이터 전체 골격을 결정
    • DML(Data Manipulation Language, 데이터 조작어)
      • 예) Select, Insert, Update, Delete
      • 데이터베이스 사용자가 데이터를 실질적으로 처리하는데 사용
      • 데이터베이스에 입력된 레코드를 조회, 수정, 삭제
    • DCL(Data Control Language, 데이터 제어어)
      • 예) Grant, Revoke, Commit, Rollback
      • 데이터의 보안, 무결성 등을 정의하는 언어
      • 데이터베이스에 접근하거나 객체에 권한을 줄 때 사용

    Superstruct

    TypeScript를 사용하며 무려 런타임에 데이터를 검증할 수 있도록 설계된 라이브러리다! 인터페이스를 보다 쉽게 정의하고, 그 인터페이스에 대한 JavaScript 데이터 검증을 할 수 있다. API로 받아온 임의의 값을 필요할 때, 런타임에서 데이터의 구조를 검증할 때 유용하다.

    object, number, string 등 함수로 타입을 정의하고 assert 함수로 검증을 실행하며, 실패 시 Error를 throw한다

    Ref https://docs.superstructjs.org/

    정규표현식의 LookaheadLookbehind

    • X(?=Y) (Positive Lookahead) : 본래 패턴에 맞는 곳을 우선 찾고, Lookaround 조건이 맞으면 매칭한다.
    • X(?!Y) (Negative Lookahead) : Positive Lookahead와 다르게 조건에 충족하지 않으면 매칭된다.
    • (?<=Y)X (Positive Lookbehind) : Positive Lookahead와 반대로 조건 Y에 충족하는지 먼저 체크하고 맞는 패턴 X를 찾는다.
    • (?<!Y)X (Negative Lookbehind) : 조건 Y에 충족하지 않는지 체크하고 맞는 패턴 X를 찾는다.

    Lookaround는 조건을 겹칠 필요가 있을 때 유용하게 사용할 수 있다. 예를 들어, 숫자와 영문자와 특수문자 3종류가 모두 포함된 문자열을 찾아야 할 때, 즉 3가지 조건에 충족하는 문자열을 매칭할 때 다음과 같이 작성할 수 있었다. 특수문자의 경우 일일이 모든 경우의 수를 입력해주어야 하기 때문에 정규식이 좀 길어질 수밖에 없었다.

    Copy
    /^(?=.*[A-Za-z])(?=.*[0-9])(?=.*[!@#$%^&*()_+\-=\[\]\{\}\;\'\,\.\/\<\>\?\~\`])[A-Za-z0-9!@#$%^&*()_+\-=\[\]\{\}\;\'\,\.\/\<\>\?\~\`]{0,}$/;

    Ref https://ko.javascript.info/regexp-lookahead-lookbehind

    TypeScript의 객체 키 추론 방식

    TypeScript는 Record<Key, Value> 타입의 객체에 대해 Object.keys()Object.entries()의 키타입이 무조건 Key[]가 아니라 string[]을 추론하게 된다.

    Object.entries에서 key를 string 타입으로 지정하는 이유는 여기서 찾을 수 있다. Object.entries를 순회할 대상이 되는 객체가 특정 객체를 확장한 타입의 객체일 때, key의 타입 추론에 실패하기 때문이다.

    Ref https://www.reddit.com/r/typescript/comments/rc1wwd/objectentries_on_record_type_is_incorrect_and/

    lock 파일의 중요성

    package.json에 명시되어있지 않은 라이브러리어도 lock 파일에 포함되어 있으면 설치된다. 하지만 그러면 버전 관리를 하기 힘들 것이다.

    ex) emoji-regex 라이브러리를 통해 이모지 정규식을 사용하는데, 이모지를 제대로 잡아내지 못하길래 보니 emoji-regex 라이브러리가 package.json 파일에 명시되어 있지 않았고 lock 파일에만 존재했다. 버전이 낮아서 제대로 잡아내지 못했던 것이다.

    💡 lock 파일을 정리하는 법

    • yarn v2 : yarn dedupe 명령어를 통해 중복 dependency를 정리할 수 있다. 번들 사이즈도 줄여주는 효과가 있다.
    • yarn v1 : yarn dedupe 명령어는 v2부터 지원한다. v1에서는 yarn-deduplicate 라이브러리가 지원한다

    setTimeout vs setInterval

    setTimeoutsetInterval의 차이는 콜백함수를 실행시키는 간격이다.

    Copy
    setInterval(fn, 1000);
    
    setTimeout(function cbFn() {
      fn();
      setTimeout(cbFn, 1000);
    }, 1000);

    위 코드에서 setInterval은 1초가 지날 때마다 콜백함수를 바로바로 호출한다. 콜백함수의 종료는 신경쓰지 않고, 그냥 냅다 실행만 시키고 시간은 흘러간다.

    반면 setTimeoutsetTimeout이 재귀 호출되기 전에 fn함수가 실행 및 종료가 된 후에 setTimeout을 다시 호출한다. 즉, 함수가 종료된 후에 다시 1초를 기다리기 때문에 정확히 1초 간격으로 진행된다.

    그렇기 때문에 잘못 사용(ex. 잘못된 위치에서 interval을 clear)하면 더이상 요청하지 않아도 되는 요청을 엄청 많이 실행하게 될 수도 있다.

    Copy
    let count = 0;
    
    let counter = setInterval(() => {
      count++;
    
      if (count > 5) {
        if (await dialog('다이얼로그 확인')) {
          //...
        }
        clearInterval(counter); // dialog 지옥이 펼쳐짐!
      }
    }, 200);

    Ref https://velog.io/@proshy/JS일정-간격으로-함수를-호출할-때-setTimeout-vs-setInterval

    dot notation

    antd 라이브러리 일부 컴포넌트의 네이밍은 같은 결의 컴포넌트를 응집하여 정의한다.

    Copy
    Typography.Text;
    Typography.Title;
    Typography.Paragraph;

    notation의 최상단 컴포넌트는 하위 컴포넌트들이 공유할 state와 update logic을 추상적으로 제공하고, 하위 컴포넌트에서는 공유되는 값을 이용해서 원하는 대로 렌더링 제어를 할 수 있게되는 구조로 이해할 수 있다.

    Compound Components

    Kent C. Dodds가 제시한.. 건진 모르겠지만 작성한 포스팅에서 등장하는 개념이다.

    HTML에서 제공하는 기본 태그인 <select><option>은, 암묵적으로 로직을 공유하는 부분이 있는 컴포넌트의 관계로 생각해볼 수 있다. Dodds는 이를 활용하여 같은 context를 공유하는 Toggle 컴포넌트와 Toggle.On, Toggle.Off, Toggle.Button 컴포넌트의 예시를 제공하고 있다.

    중재자 패턴 (Mediator Pattern)

    객체들이 서로 직접 통신하지 않고 중재자를 통해서 통신하도록 구성하는 패턴으로, 각 객체 간의 의존성을 줄여 결합도를 감소시킬 수 있다.

    채팅을 할 때 사람들끼리 모두 직접적으로 연결되어 있는 것이 아니라, 채팅방이라는 매개체를 통해서 통신하는 구조와 같다. (M:N → M:1)


    이것저것

    • stale-while-revalidation 캐시전략 - API 요청에 대해 캐시된 데이터가 있는 경우 캐시 데이터를 사용하여 사용자에게 화면을 제공하고, 후에 도착한 최신 API응답을 사용해서 화면을 최신화하는 캐시 전략

    • Recoil과 jotai의 데이터 관리법은 다르다. Recoil은 문자열 키값, jotai는 object reference를 통해 데이터를 관리한다.

    • 도메인 계층은 뒤에서부터 순차적으로 탐색을 진행하기 때문에 AWS Route 53에서 도메인 확인을 할 때에도 devbaemin.in 처럼 뒤에서부터 검색해야한다.

    • Optimistic UI(낙관적인 UI)란, 특정 요청이 성공할 것이라 가정하고 먼저 그 요청의 결과를 보여주는 방식이다. 페이스북의 좋아요 버튼을 누르면 바로 좋아요 누르기에 성공한 것처럼 UI에 표시되지만, 실제 네트워크 탭을 보면 요청과 완료는 그보다 늦게 진행되는 것이 그 예시다. 반대로 비관적 업데이트는 서버의 응답을 기다리고 처리하는 방식이다.

    • blue-green 배포란, blue(원본), green(대체) 두 버전 모두 실행 상태를 유지시켜 놓고 blue에 배포가 진행되면 green으로 잠시 사용자 트래픽을 이전해두는 방식의 무중단 배포 아키텍쳐다.

    • 심볼릭 링크란, 링크를 연결하여 원본 파일을 직접 사용하는 것과 같은 효과를 내는 링크이다.

    • package.json에 패키지 의존성을 추가할 때, ^(캐럿)을 붙임으로써 자동 업데이트의 위험성은 존재하지 않는지 고려해봐야 한다. 또, yarn에서는 패키지 버전에 ^을 붙여도 자동 업데이트를 하지 않는다.

    • outline-offset - outline과 요소의 border 영역 사이의 여백을 정의할 수 있는 속성이다.

    • Next.js(10 버전 이상)에서는 이미지 lazy loading이 자동으로 적용되어 있다.

    • core-js는 폴리필 라이브러리로 ES 최신 문법에 대한 폴리필을 지원한다. 단독으로 사용할 수도 있지만, babel이나 swc에 통합되어 있기 때문에 자연스럽게 같이 사용할 수 있다.

    • 개발자를 농락하는 이모지의 재미있는 사실

      Copy
      const test = "해피👨‍👨‍👧‍👦뉴이어";
      test.slice(0, 4); // '해피👨'
      test.slice(0, 7); // '해피👨‍👨'

      이모지를 포함한 문자열을 원하는 길이로 자를 때는 반드시 주의하자! (이모지를 제거 후에 순수한 문자열을 자르는 것도 좋은 방법이다.)

    • optionalDependencies - npm install --no-optional 명령어로 실행하며, optionalDependencies에 추가한 디펜던시는 설치되지 않는다.

    • 네이밍에 대한 컨벤션을 강제할 수 있는 lint rule이 있다.

    • CSS shadow 모아보기

    Ref https://kdydesign.github.io/2020/08/25/mono-repo-lerna/ https://www.typescriptlang.org/docs/handbook/advanced-types.html https://docs.npmjs.com/cli/v6/configuring-npm/package-json#optionaldependencies


    기타

    요번주는 읎당 ㅎㅎ 이번주에 읽은 & 본 것들을 모아보자.


    마무리

    첫 월급을 받았다! 많이 주는 줄 알았지만 진짜 많이 줘서… 이래도 되나 싶고… 그만큼 또 열심히 해야겠당. ‘나에게 주는 선물’ 중독자는 이틀동안 2만 보 가까이 걸으며 알차게 플렉스를 즐기고 앓아눕진 않았다. 고작 지난 2주 운동한 게 효과가 있나보다 ㅎㅎ 운동 더 열심히 해서 짱 쎄져야겠다고 다짐했다.


    Relative Posts:

    3월 첫주차 기록

    March 5, 2022

    2월 3주차 기록

    February 19, 2022

    zigsong

    지그의 개발 블로그

    RotateLinkImg-iconRotateLinkImg-iconRotateLinkImg-icon