ziglog

    Search by

    6월 1주차 기록

    June 4, 2022 • ☕️☕️ 12 min read

    꿈에 그리던 제주도 코딩(?)


    배워가기

    이미지 로딩이 실패했을 때

    이미지 로딩이 실패 했을 때 대체 이미지를 띄우려면 onError 핸들러에서 currentTarget.src에 대체 이미지를 넣어줄 수 있다.

    Copy
    <img src={originImg} onError={handleImgError} />;
    
    const handleImgError = (e) => {
      e.target.src = defaultImg;
    };

    타입스크립트 함수에서 void를 리턴하면

    Copy
    function sayHi(): void {
      console.log("Hi!");
    }
    
    let speech: void = sayHi();
    console.log(speech); // undefined

    일반적으로 void는 함수가 아무것도 리턴하지 않을 때 사용한다. 그러나 함수의 실제 리턴 값은 undefined일 것이다.

    타입스크립트 문서에는 다음과 같이 설명되어 있다.

    Contextual typing with a return type of void does not force functions to not return something.

    리턴 타입으로 void를 명시한 함수에게 무언가를 리턴하도록 강제하지 않을 뿐, 실제로는 무언가 값을 리턴해도 상관없다. 따라서 함수의 리턴 타입을 void로 선언한 후 number, string[] 등의 값을 리턴한다고 해서 에러가 나지 않는다.

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

    사실 이는 타입체킹을 하지 않는 것이 아니라, 문자열을 반환해도 void를 반환하는 것과 다름없기 때문이다.

    Copy
    declare const a: () => string;
    declare const b: () => void;
    function doSomething(): void {
      a();
      b();
    }

    일반적으로는 string extends void는 유효하지 않지만, 함수 반환 타입에 있어서는 stringvoid보다 넓은 타입, 즉 서브타입이라고 해도 무방하다. 공변과 반변에서, 반환 타입은 공변함을 알 수 있다.

    아래와 같은 함수 타입을 정의했을 때,

    Copy
    type F<T> = () => T;

    함수 ab는 각각 다음처럼 표현할 수 있다.

    Copy
    type A = F<string>;
    type B = F<void>;

    stringvoid보다 넓은 타입이기 때문에, b의 자리에 a를 넣는 것이 가능하다.

    Copy
    declare let a: A;
    declare let b: B;
    
    b = a;

    그러면 아래와 같은 코드가 가능하다.

    Copy
    let b: () => void = () => "dd";

    그러나 리터럴 함수 정의문이 void 타입을 리턴한다면 해당 함수는 절대로 아무것도 리턴해서는 안 된다.

    Copy
    function f2(): void {
      return true; // 🚨 Type 'boolean' is not assignable to type 'void'
    }
    
    const f3 = function (): void {
      return true; // 🚨 Type 'boolean' is not assignable to type 'void'
    };

    이는 그저 특이 케이스라고 하는데, 이럴 거면 그냥 void 타입은 아무것도 리턴하게 하지 말지. 괜히 헷갈리게.. 🤨

    HTTP 상태코드 401 vs 403

    • 401 Unauthorized
      • 인증(Authentication) 거부 또는 인가(Authorization) 거부
      • 클라이언트가 credential 정보를 보내지 않았거나 유효하지 않은 credential을 보낸 경우
      • 예시
        • 액세스토큰을 보내지 않음 => 로그인 안됨.
        • 액세스토큰을 보냈는데 만료됨 => 로그인 만료됨.
        • 스펙의 “If the reqeust already included Authorization credentials, then the 401 response indeicates that authorization has been refused for those credentials.“에 해당
    • 403 Forbidden
      • 인가(Authorization) 거부
      • 클라이언트의 credential이 유효한데, 해당 자원에 접근할 권한이 없는 경우
      • 예시
        • 로그인은 되었는데 관리자 페이지 접근권한은 없음.

    Uniform Resource Identifier(URI)

    URI는 인터넷에 있는 자원을 나타내는 유일한 주소

    • encodeURIComponent - URI의 특정한 문자를 UTF-8로 인코딩한다.
    • decodeURIComponent - encodeURIComponent, 또는 비슷한 방식으로 만들어진 URIComponent를 해독한다.

    react-redux v8

    22년 4월 17일에 react-redux v8이 릴리즈되었다.

    타입스크립트에서 useSelectorstate 인자의 기본타입이 DefaultRootState였는데 unknown 으로 바뀌었다.

    기존에는 useSelector를 사용할 때 useSelector((state: RootState) => state.someReducer);와 같이 매번 RootState 써주기 번거로워 아래와 같이 작성 후,

    Copy
    declare module "react-redux" {
      interface DefaultRootState extends RootState {}
    }

    useSelector(state => state.someReducer);로 사용했었는데 이제 이런 코드는 유효하지 않게 되었다!

    이유는 수많은 코드에서 라이브러리의 타입을 덮어씌우는 행위가 프로젝트와 Redux 라이브러리 간의 타입을 간섭하고 오염시킨다고 판단한 것 같다.

    위 방법은 막혔지만, 아래처럼 useSelector 자체를 선언해버리면 된다.

    Copy
    declare module "react-redux" {
      export declare const useSelector: <T>(
        selector: (state: RootState) => T,
        equalityFn?: EqualityFn<T>
      ) => T;
    }

    UTC, ISO 8601, RFC 3339

    • UTC
      • 협정 세계시(Coordinated Universal Time) 라 하며 1972년 1월 1일 부터 시행된 국제 표준시.
      • 영어권은 CUT, 프랑스어권은 TUC(Temps Universel Coordonne) 를 제안했는데 두 언어 모두 C, T, U 로 구성되어 있는것을 착안하여 UTC 를 약어로 결정했다.
      • UTC는 그레고리력의 표기를 따른다.
      • UTC+0은 그리니치 표준시를 토대로 한다. 그리니치시는 런던에 소재한 그리니치 천문대를 기준으로 하는 경도를 사용한다. (그리치니 천문대의 경도 = 0.00)
    • ISO 8601
      • 국제표준화기구(ISO) 에서 지정한 날짜와 시간의 표기에 관한 국제 표준 규격.
      • YYYY-MM-DDThh:mm:ss.sssZ - T를 기준으로 왼쪽은 날짜, 오른쪽은 시간이며 Z는 zone offset 을 의미한다. (zone offset: UTC의 시차)
      • 한국을 기준으로 UTC 시간대를 표기하면 2022-05-30T14:57:34.630Z
      • UTC 외의 시간대에서는 2022-05-30T05:57:34+09:00
      • 위 두 시간은 동일
      • RFC 3339
      • ISO 8601 을 인터넷 프로토콜로 어떻게 다룰 것인지를 규정한 RFC.
      • ISO 8601 와의 차이점 중 하나는 T의 생략을 허용하지 않는 대신에 날짜와 시간 사이에 공백을 허용한다.
      • 예) 2022-05-30 05:57:34+09:00

    console 가지고 놀기

    console.log()%c(치환 문자)를 사용하면 스타일을 설정할수 있다.

    01

    console.log() 에 복잡한 객체를 출력할 때는 객체를 깊은 복사(Deep Copy) 해주어야 정확한 값이 출력된다. ([object object]로 출력되는 경우)

    Copy
    console.log(JSON.parse(JSON.stringify(obj)));

    객체는 참조형 데이터이기 때문에 console.log를 사용한 시점의 객체값을 보장할 수 없다. JSON 문자열로 변환(stringify)했다가 다시 객체로 변환(parse) 해줌으로써 객체에 대한 참조를 없앨수 있다.

    console.table() 을 사용하면 배열이나 JSON 형태의 값을 key-value 형태의 테이블 형식으로 보여준다.

    02

    d.ts 파일 포함한 모듈 배포하기

    tsconfig.json에서 일반적으로 typeRootsnode_modules/@types를 설정해놓기 때문에, .d.ts 파일을 포함한 모듈은 @types/~~로 배포해야 한다.

    빌드된 모듈을 배포할 수도 있다. output: { ... libraryTarget: 'umd' ... }을 명시해주면, umd 로 패키지를 번들할 수 있다.

    이때 모듈을 빌드된 상태로 배포를 하면, 트리셰이킹이 되지 않을 수 있다. 모듈을 사용하는 프로젝트의 번들 크기가 커질수 있으니, react와 같이 큰 라이브러리는 번들 대상에서 제외시켜주고 peerDependencies에 넣어주는 것이 좋다.

    Semantic Versioning

    Semantic Versioning은 버전을 표시할 때 자주 사용되는 규약 (npm, node 모두 이 규약을 따름)으로, MAJOR, MINOR, PATCH로 구분된다.

    • MAJOR - 인터페이스가 변경되는 등의 하위버전과 호환성이 깨 질만한 큰 변경사항이 있는 버전
    • MINOR - 하위버전과 호환(backwards-compatible)이 되지만, 기능이 추가된 버전
    • PATCH - 하위버전의 버그를 수정한 버전

    Dependencies에 기재된 버전에 붙은 ^(캐럿) 기호의 의미: MINOR나 PATCH 버전은 하위호환성이 보장되므로 업데이트를 한다.

    • 1.0.2: >=1.0.2 <2.0
    • 1.0: >=1.0.0 < 2.0
    • 1: >=1.0.0 < 2.0

    userEvent.click()

    userEvent.click() 을 사용할 때 해당 엘리먼트의 상위 엘리먼트에서 pointer-events: none 속성을 가지고 있다면 throw 에러를 발생시킨다.

    Copy
    <div className="App">
      <h2>RTL Test Sample</h2>
      <div style={{ pointerEvents: "none" }}>
        <div>{count}</div>
        <div style={{ pointerEvents: "auto" }}></div>
      </div>
    </div>
    
    <button onClick={() => setCount(count + 1)}>Click Me!</button>
    
    // userEvent.click() => throw Error("Unable to perform pointer interaction as the element inherits `pointer-events: none`")

    이 때 skipPointerEventsCheck 옵션을 활성화시켜 엘리먼트를 클릭할 수 있다.

    Copy
    userEvent.click(elem, undefined, { skipPointerEventsCheck: true });

    :first-child 와 :first-of-type

    일반적으로 :first-child는 자식의 첫번째 요소를 의미하고, :first-of-typediv:fist-of-type 과 같이 어떤 태그의 첫번째 요소를 뜻한다.

    태그 없이 :first-of-type 을 사용했을 때는, 모든 엘리먼트의 첫번째 요소들을 의미한다. 즉 기본 선택자 없이 :first-of-type을 사용하면 전체 선택자(*)가 암시된다.

    Copy
    const DSSample: React.FC<SampleProps> = (props) => {
      return (
        <Article>
          <h1>h1</h1>
          <p>Paragraph 1.</p>
          <h1>h1</h1>
          <p>Paragraph 2.</p>
          <p>Paragraph 3.</p>
          <div>hh</div>
        </Article>
      );
    };
    export const Sample = DSSample;
    
    const Article = styled.article`
      & :first-of-type {
        color: red;
      }
    `;
    03

    무중단 배포

    운영중인 서비스를 중단하지 않고 신규 소프트웨어를 배포하는 기술이다. 무중단 배포의 핵심은 로드밸런서를 통해 연결된 서로 다른 인스턴스에 트래픽을 제어해 배포하는 것이다.

    무중단 배포는 크게 세 종류로 나뉜다.

    • 롤링 배포

      • 사용중인 인스턴스 내에서 새 버전을 점진적으로 교체하는 가장 기본적인 무중단 배포 방식
      • 배포가 진행되는 동안 구버전과 신버전이 공존할 수 있다. (호환성 문제 발생 가능성)
      1. 서비스 중인 인스턴스 하나를 로드밸런서에서 라우팅 하지 않게 닫는다.
      2. 새 버전 인스턴스를 실행, 다시 라우팅 되도록 열어준다.
      3. 라우팅 닫았던 old 인스턴스를 중지한다.
      4. 1번으로 가서 반복
    • 블루-그린 배포

      • 블루 = 구버전 / 그린 = 신버전
      • 실제 운영환경에서 신버전을 미리 테스트할 수 있다.
      • 시스템 자원이 두배가 필요하며 신버전에 대한 테스트가 전제되어야 한다.
      1. 구버전과 동일한 운영환경으로 신버전 인스턴스를 준비한다.
      2. 로드밸런서를 통해 신버전으로 모든 트래픽을 한번에 전환한다.
    • 카나리 배포

      • 블루-그린 배포 방식과 거의 유사
      • 트래픽을 한번에 넘기는 것이 아닌 10%, 20%, 80%, 100% 등 점점 트래픽을 늘려가면서 적용한다.
      • 소수의 유저에게만 배포 + 테스트를 해보면서 점차 많은 유저들에게 배포할 수 있다.

    이것저것

    • @testing-library/react를 사용하는 경우 screen.debug()를 통해 현재 렌더되고 있는 JSDOM을 console로 확인할 수 있다.
    • 테스트는 코드의 단위를 검증하는게 아니라 동작의 단위, 즉 문제 영역에 의미가 있는 것, 이상적으로는 비지니스 담당자가 유용하다고 인식할 수 있는 것을 검증해야 한다.
    • VSC Jest 익스텐션은 Jest Config 설정에 따라 실제 테스트 결과와 다른 값을 반환할 수도 있다. 따라서 Jest Config에 따라 Jest Extension도 커스텀 해서 사용해야 하는 경우가 존재한다.
    • 하나의 VirtualDOM에서 mobX 컨택스트를 multiple 하게 사용하려면 configure({ isolateGlobalState: true }) 설정이 필요하다.

    기타

    크롬 개발자 도구의 Recorder 패널

    와! 정말 fancy하다. recording 버튼을 클릭하여 사용자 플로우를 그대로 녹화하고, 재생하고, 수정과 측정까지 가능하다. 구글에서 제공하는 coffee-cart 사이트에서 체험 가능하다. 기록 결과를 JSON이나 Puppeteer로 export도 가능하다고 한다!

    04

    Ref https://developers.google.com/codelabs/devtools-recorder#0

    달라지는 CSS

    아직도 CSS 잘 모르는데 이것저것 또 추가되고 바뀌고~! State of CSS 2022를 살펴보면 된다.

    킹콜라스 선생님의 영상에서 간략하게 살펴볼 수도 있다.

    상당히 fancy하고 그동안 왜 안 해줬나 싶었던 기능들이 포함됐다! :has라니.. 유용하게 사용할 수 있을 것 같다. @scope도 유사한 기능을 보여주고 있다. 그리고 css에도 중복 코드를 줄이기 위해 @nest 가 등장했다. 상당히 어색하지만 혁신적이다.


    마무리

    지난주 엠티를 다녀오자마자 제주도 여행을 갔다. 사전투표하고 갔당. ㅎㅎ 개발자 남자친구와 함께 노트북까지 챙겨서 갔고, 남자친구가 일 좀 해야 한다고 했는데, 정작 남친 말고 내가 일했다 😭 바다가 보이는 카페에서 미팅도 참석하고, 방에 돌아와서 코드도 짰다. 그렇게 아주아주 나쁘진 않았다. 놀러가서도 일하는 개발자가 멋져보이는 아직 1년차 개발자… 하지만 나는 멋지지 않았다. 배포 전까지 요구사항이 조금씩 바뀌는데 그 점을 고려하지 못하고 휴가쓰고 갔다온 것 같다. 아직 이런 회사 업무와의 밸런스 조절이 어려운 것 같다.

    그래도 평화롭게 (운전도 무사히 하고 ✌️) 잘 다녀왔더니 팀명이 바뀌어 있었다! 알고는 있었지만 뚝딱 바뀌어버리니 괜히 어색. 이름도 엄청 길당. fancy한 축약어가 나왔으면 좋겠다. 정말정말로 모든 팀원분들, 실장님까지 참석하신 프론트엔드 송별회 겸 회식에서 늦게까지 신나게 놀다왔다. 다들 알고보면 정말 재밌는 분들이다 내가 제일 조용할 지도 모른다 🤫


    Relative Posts:

    6월 2주차 기록

    June 11, 2022

    5월 4주차 기록

    May 28, 2022

    zigsong

    지그의 개발 블로그

    RotateLinkImg-iconRotateLinkImg-iconRotateLinkImg-icon