ziglog

    Search by

    5월 3주차 기록

    May 21, 2022 • ☕️☕️☕️☕️ 19 min read

    썽난 부채질


    배워가기

    React.StrictMode의 은밀한 소행

    버튼을 누르면 counter의 값이 하나씩 증가하는 리액트 코드를 작성한다고 해보자. 리액트의 useState로 counter의 값을 설정하고, 리렌더링될 때마다 ref.current의 값도 1씩 증가하게끔 한다.

    Copy
    import { useState, useRef } from "react";
    
    export default function App() {
      const ref = useRef(0);
      const [counter, setCounter] = useState(0);
    
      ref.current += 1;
      console.log("rerender", counter, ref.current);
    
      const increase = () => setCounter((v) => v + 1);
    
      return (
        <div className="App">
          {counter} <br />
          renderCount: {ref.current}
          <button onClick={increase}>+1</button>
        </div>
      );
    }

    그러나 코드를 실제로 실행해보면, ref.current가 2씩 증가한다. 여러 케이스가 있을 수 있겠지만, 리액트가 제공하는 StrictMode 때문일 수도 있다.

    StrictMode는 애플리케이션 내의 잠재적인 문제를 알아내기 위한 도구이다. Fragment와 같이 UI를 렌더링하지 않으며, 자손들에 대한 부가적인 검사와 경고를 활성화한다. 개발 단에서만 활성화되며, 프로덕션 빌드 시에는 동작하지 않는다.

    Copy
    import React from "react";
    
    function App() {
      return (
        <div>
          <Header />
          <React.StrictMode>
            <div>
              <ComponentOne />
              <ComponentTwo />
            </div>
          </React.StrictMode>
          <Footer />
        </div>
      );
    }

    StrictdMode는 자동으로 사이드이펙트를 알아내진 않지만, 사이드이펙트를 약간 더 결정론적으로 발견하여 개발자에게 알려준다. 이를 위해 다음 함수들을 고의적으로 두 번 실행한다.

    • 클래스 컴포넌트의 constructor, render, 그리고 shouldComponentUpdate메서드
    • 클래스 컴포넌트의 (static) getDerivedStateFromProps 메서드
    • 함수 컴포넌트 바디
    • state를 update하는 함수 (setState)
    • useState, useMemo, useReducer에 전달된 함수들

    StrictMode는 여러 가지 경고도 날리지만, 함수들을 두 번 실행시켜 비교분석하기도 하는 것이다.

    이 때문에 useReducerreducer를 순수하게 유지하는게 중요하다. 그렇지 않다면, 아래와 같은 문제가 발생한다.

    Copy
    const [state, dispatch] = useReducer(reduce, {})
    // ...
    dispatch({ ... }) // 🚨 실행 스택에서 2번씩 실행된다.

    쓰레드에서 Dan abramov는 이것은 StrictMode의 의도적인 작용이라고 설명하며, 함수를 사이드 이펙트 없이 순수(pure)하게 유지해서 함수가 두 번씩 실행되어도 애플리케이션의 로직에 문제가 없도록 해야 한다고 한다.

    Ref https://ko.reactjs.org/docs/strict-mode.html https://reactjs.org/docs/strict-mode.html#detecting-unexpected-side-effects https://stackoverflow.com/questions/50819162/why-is-my-function-being-called-twice-in-react

    yarn workspace의 hoisting

    yarn workspace는 패키지 구조를 설정하기 위한 방법이다. 여러 개의 패키지를 yarn install만 실행하면 단숨에 설치해준다.

    IDE에서 여러 프로젝트들이 모여 있는 공간을 작업 공간, 즉 워크스페이스라고 부른다. yarn의 workspace도 마찬가지다.

    yarn workspace를 사용하는 방법은 아래와 같다. 저장소의 루트에 package.json을 만들고 아래와 같이 작성한다.

    Copy
    {
      "private": true,
      "workspaces": ["workspace-a", "workspace-b"]
    }

    yarn은 이제 저장소를 워크스페이스로 인식하게 된다.

    • 각각의 프로젝트는 마치 npm 패키지의 로컬 사본처럼 저장소 루트의 node_modules에 저장된다.
    • 각각의 프로젝트별로 node_modules, yarn.lock 등의 파일이 생성되는 대신에, 저장소 루트에 하나만 생성된다.

    workspace-aworkspace-b라는 이름으로 각각 하위폴더를 생성하고, 각 폴더 아래 package.json을 만들어준다.

    Copy
    // workspace-a/package.json:
    {
      "name": "workspace-a",
      "version": "1.0.0",
    
      "dependencies": {
        "cross-env": "5.0.5"
      }
    }
    // workspace-b/package.json:
    {
      "name": "workspace-b",
      "version": "1.0.0",
    
      "dependencies": {
        "cross-env": "5.0.5",
        "workspace-a": "1.0.0"
      }
    }

    이제 yarn은 워크스페이스 내의 패키지와 프로젝트를 통합 관리한다.

    프로젝트의 루트에서 yarn install을 실행하면 워크스페이스 전체에 대해서 패키지를 설치하고, yarn.lock 파일을 갱신한다. 이제 아마도 아래와 같은 파일 계층 구조를 갖게 될 것이다.

    Copy
    /package.json
    /yarn.lock
    
    /node_modules
    /node_modules/cross-env
    /node_modules/workspace-a ('/workspace-a'라는 이름으로 symlink 연결된다.)
    
    /workspace-a/package.json
    /workspace-b/package.json

    workspace-b의 파일에서 필요로 하는 workspace-a는 npm에 공개된 패키지 대신 현재 프로젝트 안에서 정확한 패키지를 찾아다 쓸 것이다. 그리고 workspace-aworkspace-b에서 모두 사용되는 cross-env 패키지는 프로젝트의 루트에 놓이게 된다.

    이처럼 yarn workspace는 여러 프로젝트에서 공통 사용하는 유틸리티를 별도의 공유 프로젝트에 작성하고, 참조해서 사용한다.

    yarn workspace에서 워크스페이스 디펜던시는 파일시스템의 계층 구조에서 상위 계층으로 hoist 된다.

    대부분의 패키지 매니저는 모든 의존성 모듈들을 가져다 평탄화(flatten)하기 위해 각자만의 hoisting 방식을 취한다. 단독 프로젝트에서, 디펜던시 트리는 다음과 같이 줄어든다.

    01

    호이스팅으로 A@1.0B@1.0의 중복을 제거할 수 있다. 많은 모듈 크롤러/로더/번들러들은 프로젝트 루트의 node_modules부터 탐색을 시작하여 모듈을 효율적으로 배치한다.

    모노레포 프로젝트는 파일의 계층 구조가 node_modules로 연결되어 있지 않다. 그런 프로젝트에서, 모듈은 여러 곳에 산재된다.

    02

    yarn workspace는 자식 프로젝트/패키지들의 모듈들을 부모 프로젝트의 node_modules로 hoisting하여 모듈을 공유한다.

    이로써 프로젝트의 루트 node_modules에서 모든 모듈에 접근할 수 있다. 하지만 우리는 종종 로컬 프로젝트의 각 패키지를 빌드하기도 한다. 이때 모듈이 해당 로컬 프로젝트의 node_modules에 없을 수도 있다. (모든 크롤러가 symlink를 탐색하는 것도 아니다.)

    이로 인해 자식 프로젝트에서 빌드할 때는 때로 ‘module not found’ 에러에 마주하기도 한다.

    • can’t find module “B@2.0” from project root “monorepo” (symlink를 따라갈 수 없다.)
    • can’t find module “A@1.0” from “package-1” (monorepo의 모듈 트리를 알 수 없다.)

    이는 호이스팅 프로세스가 완전히 표준화되지 않아서 보장되지 않는 문제라고 한다. 🤨

    이런 문제에 봉착했을 때는 nohoist 옵션을 사용하면 된다고 한다. 여기저기 한계점이 하나도 없는 기술은 아직 없나보다. 🤷‍♀️

    중첩 구조의 package.json이 존재하는 프로젝트가 있을 때, 최상위 package.json에서 yarn install을 실행하면 각자의 node_modules에 모든 의존성을 설치한다. 이때 하위 package.json의 의존성을 상위로 hoist 하려면 yarn install --production 옵션을 넣어줘야 한다.

    다만 yarn github 이슈를 살펴보면, yarn classic(yarn workspace)은 호이스팅을 완전히 보장하지 않고, 이 문제를 yarn berry에서 해결했다고 한다. (v1에서의 문제v2에서 PnP로 해결)

    호이스팅 시에 하위 모든 package.json의 의존성에 버전 충돌이 있다면 yarn 내부에서 정해둔 규칙에 따라 하나의 버전만 상위로 hoist되는데, 그 기준은 공개되어 있지 않다.

    예를 들어 project A의 package.json 하위에 있는 project B, C, D의 package.json에 각각 16, 17, 18 버전의 react에 의존성이 있다면, 셋 중 어떤 버전이 호이스팅될 지 알 수 없으며, 경우에 따라 전혀 다른 버전이 호이스팅될 수도 있다.

    구체적인 내부 로직은 공개되어 있지 않기 때문에 모노레포에서 사용하기에는 위험성이 있다.

    pnpm은 install 시 하위에서 사용하는 모든 버전의 모듈을 상위에 위치시키고, 하위에서는 symbolic link를 통해 상위의 모듈에 접근한다.

    패키지매니저만 봤을 때 yarn보다 pnpm이 모노레포에 더 적합할 수도 있겠다.

    Ref https://classic.yarnpkg.com/lang/en/docs/workspaces/ https://classic.yarnpkg.com/blog/2018/02/15/nohoist/ https://musma.github.io/2019/04/02/yarn-workspaces.html

    인덱스 시그니처 있다 없으니까

    인덱스 시그니처가 없는 ‘타입’은 인덱스 Record 타입에 할당할 수 있지만, 인덱스 시그니처가 없는 ‘인터페이스’는 할당할 수 없다.

    Copy
    interface I {
      foo: string;
    }
    declare const i: I;
    
    type T = { foo: string };
    declare const t: T;
    
    type R = Record<string, unknown>;
    
    const ra: R = i; // 🚨 Error
    // Type 'I' is not assignable to type 'R'.
    // Index signature for type 'string' is missing in type 'I'
    const rb: R = t; // ✅ OK

    변화무쌍한 git log

    Copy
    git log [options]
    • -online: 하나의 커밋을 커밋 체크섬과 커밋 메시지만으로 한 줄로만 표현한다.
    • -graph: log를 그래프로 표현한다.
    • -decorate: 커밋 옆에 브랜치를 표시한다.
    • -all: 모든 브랜치를 표시한다. (all이 아니라면 현재 브랜치만 표시)

    peerDependencies

    peerDependencies는 ‘이 패키지는 패키지 A의 x.x.x버전에 의존성이 있다’고 명시하는 역할을 한다. 즉, 내가 만든 패키지가 다른 패키지와 함께 사용될 때, 내 패키지가 외부 패키지 의존성의 어떤 버전과 호환되는지 나타낸다.

    peerdependencies에 특정 패키지의 버전을 입력한다고 해서, install 시 해당 패키지가 자동으로 설치되지는 않는다. 만약 해당 패키지를 사용하는 상위 패키지도 패키지 A에 의존하며, 그 버전이 다르다면 install 시 충돌이 일어나고 이후에는 패키지 매니저에 따라 다르게 처리된다. (ex. 패키지 A가 패키지 B, C@1.0에 의존하고 패키지 B가 패키지 C@2.0에 의존하는 경우)

    peerdependencies가 명시되어 있지 않다면 npmyarn classic은 ‘flat’한 node_modules 구조를 선택한다. (모든 의존성이 node_modules의 1-depth에 설치된다.) 그렇기 때문에 내부적으로 경합 후에 C 패키지의 단 하나의 버전을 node_modules에 설치한다.

    이 경우 최종적으로 어떤 버전이 설치될 지 보장할 수 없다. 충돌이 일어난 버전 중 하나일 수도 있고, 전혀 다른 버전일 수도 있다.

    따라서 peerdependencies 에 특정 버전을 지정했더라도, 그 버전이 node_modules에 있을 것이라고 보장할 수 없다. (설치는 되더라도, 버전은 보장할 수 없다)

    peerdependencies가 명시되어 있다면 각 패키지 매니저는 다음과 같이 동작한다.

    • npm3 - 피어 종속성을 자동 설치한다.
    • npm4 ~ npm6 - 경고 메세지를 보여준다.
    • npm7 - 설치 자체를 막는다.
    • yarn classic - 경고 메세지만 보여주고 설치를 진행한다.

    내가 작성한 패키지에서 peerdependencies 에 버전을 명시했더라도, 외부 패키지가 peerdependencies 를 작성했을 것이라고 보장할 수 없기 때문에, 패키지 매니저가 peerdependencies를 참고해서 버전을 결정하기 어렵다.

    yarn classicpeerdependencies를 배포할 때 외에 강제하지 않기 때문에, 모노레포의 경우 배포하지 않고 그 내부에서 의존성으로 연결되어 있어서 peerdependencies 를 강제할 수 없다.

    non-flat 구조의 패키지 매니저

    non-flat node_modules 구조를 가진 패키지 매니저(pnpm, yarn berry)는 flat한 구조를 가진 패키지 매니저 (npm, yarn classic 등)보다 peerdependencies를 좀 더 적극적으로 사용할 수 있다.

    non-flat 패키지 매니저는 install 시 하위 패키지에서 의존하는 모든 버전의 패키지를 최상위 node_modules에 모두 설치한다. 최상위 node_modules 에는 랜덤한 버전 대신 모든 버전이 있기 때문에, peerdependencies의 명시된 버전을 명확하게 가져올 수 있다.

    peerdependencies모든 버전의 교집합을 지정하면 문제가 발생하지 않는다.

    • ex) 패키지A의 의존성 버전이 >1.0.0 / 2.0.0 이라면 peerdependency 는 2.0.0
    • ex) 만약 1.0.0 / 2.0.0 이라면, 애초에 교집합을 만들 수 없으므로 의존성 버전을 수정할 필요가 있다.

    위의 이유를 종합해 보았을 때, 패키지를 배포하지 않고 하나의 레포 안에서 서로 의존하면서 구성하는 모노레포의 경우 non-flat한 node_modules 구조가 더 적합하다.

    Ref https://pnpm.io/ko/how-peers-are-resolved

    Braze - User Lifecycle Orchestration 플랫폼

    Braze(브레이즈)는 User Lifecycle Orchestration 플랫폼이다. 이 플랫폼을 사용하게 되면 사용자에게 전파하는 여러 메시징 수단을 브레이즈로 통합해 관리할 수 있고, 가장 최적화된 시간에 최적화된 고객에게 등록한 이벤트나 메시지를 전달할 수 있다.

    자주 사용하는 기능으로, A/B 테스트, 고객에 따른 개인화 메시지, 개인이 가장 많이 사용하는 시간에 푸쉬메시지 전달 등이 있다. ex) 우리가 배민앱에서 노티를 받는 것들은 마케터가 고객군을 체크하고, 가장 적절한 시간에 브레이즈를 이용해서 전달하는 것이다.

    브레이즈에 커스텀 마크업 메시지 전달 기능이 있다. 해당 기능을 이용하게 되면 브레이즈에 마크업 코드를 올리고, 앱에서 해당 기능을 지원하면 브레이즈를 통해 원하는 페이지에서 마크업 요소를 자유롭게 출력시킬 수 있다. (브레이즈에서 앱에 SDK를 제공하고, 브레이즈 서버를 거쳐, 웹 데이터를 전달받는 형태)

    이런 기능을 이용해, 쿠팡 메인 홈에 들어갔을때 출력되는 dialog 같은 요소를 웹으로 구현할 수 있다.

    증분 빌드

    빌드 시에 변경사항이 있는 입력값만 빌드하고 그 외에는 기존의 결과값을 재사용하는 빌드 방식이다. 입력, 출력이 1:1로 매핑되어 있어야 한다. 1:1 매핑이 존재하는 경우 해당 출력 항목의 타임스탬프와 모든 입력 항목의 타임스탬프를 비교하지만. 1:1 매핑이 없는 출력 파일은 모든 입력 파일과 비교되기 때문이다.

    🤔 증분 vs 캐싱

    • 캐싱은 일시적인 것이고 증분은 영구적일수 있다.
    • 캐싱은 메모리 영역에 가깝고, 증분은 그 자체가 파일일 수 있다. cf) 빌드 과정에서 캐싱을? Enabling the build cache (experimental) | Rush

    자세한 내용은 incremental build를 찾아보자.

    Ref https://docs.microsoft.com/ko-kr/visualstudio/msbuild/incremental-builds?view=vs-2022 https://en.wikipedia.org/wiki/Incremental_build_model

    craco로 babel-loader 오버라이드하기

    cracoCRA Config Override의 약자를 딴 툴로, eject 없이 cra의 설정을 오버라이드 할 수 있게 해주는 도구다. 모노레포 환경에서 babel-loader의 적용범위를 프로젝트 외부까지 확장시킬 때 사용할 수 있다.

    CRA 웹팩 설정을 확인해보기 위해 eject 을 해보면, 웹팩 모듈 중 babel-loader 가 선언된 부분의 include 범위는 /src 이다. (여기서 확인)

    따라서 /src 범위 바깥에 있는 jsx, tsx 파일을 import 해서 사용하려고 해도 babel 이 실행되지 않기 때문에 (코드가 트랜스파일링되지 않는다) 리액트에서 오류를 내뱉는다.

    하지만 babel-loaderincludes 옵션 값을 /src 외 우리가 필요한 경로까지 확장해준다면 jsx, tsx 파일을 정상적으로 랜더링할 수 있게 된다.

    CRA 앱을 craco 로 전환한 뒤 babel-loader 를 아래와 같이 오버라이드 할 수 있다. (getLoader(), loaderByName()과 같은 유틸함수는 웹팩 설정을 쉽게 도와준다.)

    Copy
    // craco.config.js
    const { getLoader, loaderByName } = require("@craco/craco");
    
    module.exports = {
      webpack: {
        alias: {},
        plugins: [],
        configure: (webpackConfig, { env, paths }) => {
          const { isFound, match } = getLoader(
            webpackConfig,
            loaderByName("babel-loader")
          );
          if (isFound) {
            // <https://webpack.js.org/configuration/module/#ruleinclude> 참고
            match.loader.include = ... // 여기서 __dirname/src 외에 필요한 경로를 추가
          }
          return webpackConfig;
        }
      }
    };
    Copy
    // apps/cra-project
    import { Button } from '@packages/ui'
    ...
    // 변경 전
    <Button>버튼</Button> // 🚨 SyntaxError: Unexpected token ...
    
    // 변경 후
    <Button>버튼</Button> // ✅ 정상적으로 랜더링

    캡슐화

    상태와 행동을 하나의 객체 안에 모아 캡슐화를 하면 변경에 대한 영향을 최소화할 수 있다. 외부에서 알 필요 없는 구현 세부사항을 안정적인 인터페이스 뒤로 숨긴다. 객체지향의 유지보수성을 높이는 방법 중 하나는 변경될 수 있는 어떤 것이라도 캡슐화를 하는 것이다.객체가 수행할 책임이 아니라 내부 상태에 포커스를 맞출 경우 캡슐화를 위반할 가능성이 높아진다.

    👩‍🏫 getter, setter는 객체 내부 상태를 캡슐화하지 못한다.

    리액트를 사용할 때도, 클라이언트 단의 코드와 서버와 통신하는 도메인의 레이어를 나누면 외부(서버)의 변경에 유연하게 대처할 수 있다.

    base64 vs multipart

    base64

    • binary를 6바이트 단위의 ASCII 캐릭터로 인코딩 (radix-64 표기법)
    • 패딩값 (문자열의 ==)에 의한 오버헤드가 발생할 수 있다.
    • 그냥 parameter로 받으면 된다.
    • IE9 이하에서는 파일 인코딩 불가능하다.
    • 전체 데이터를 변환하고 서버에 보내기 때문에 큰 용량의 파일에는 부적합하다.
    • serializable하다.
    Copy
    // 암호화(Encode)
    btoa("12345"); // MTIzDNU=
    
    // 복호화(Decode)
    atob("MTIzDNU="); // 12345

    한글 데이터의 경우 아래와 같이 사용한다.

    Copy
    // 암호화(Encode)
    btoa(encodeURIComponent("한글")); // JUVEJTk1JTlDJUVBJUI4JTgw
    
    // 복호화(Decode)
    decodeURIComponent(atob("JUVEJTk1JTlDJUVBJUI4JTgw")); // 한글

    multipart/form-data

    • binary 형식의 데이터
    • ajax로는 전송 불가하다.
    • http protocol (MIME type)의 하나
    • 브라우저에서 서버로 HTML Form의 내용을 전송 시 사용한다.
    • 큰 용량의 파일에 적합하다.

    MIME type

    • Multipurpose Internet Mail Extension의 약자.
    • 클라이언트에게 전송된 문서의 다양성을 알려주기 위한 메커니즘으로, 여러 형태의 파일 전송에 사용한다. ex) text/plain, text/html, image/jpeg, image/png, audio/mpeg, audio/ogg http 프로토콜의 Content-type 헤더에 바로 이 MIME type이 들어간다.

    멀티파트 타입 일반적으로 다른 MIME 타입들을 지닌 개별적인 파트들로 나누어지는 문서의 카테고리를 가리킨다. 즉 이 타입은 합성된 문서를 나타내는 방법이다. ex) multipart/form-data, multipart/byteranges

    enum vs union type

    컴파일 후에 enum의 용량이 더 크다.

    그리고, enum은 덕 타이핑이 안 된다

    Copy
    type COLOR_TYPE = "RED" | "BLUE" | "YELLOW";
    
    enum COLORS_ENUM {
      RED = "RED",
      BLUE = "BLUE",
      YELLOW = "YELLOW",
    }
    
    const red: COLORS_ENUM = "RED"; // 🚨 Error

    opacity vs visibility vs display

    • display: none은 요소가 렌더링됐을 때 영역을 차지하지 않는다.
    • 브라우저는 display: none이나 visibility: hidden 을 사용하는 요소의 이벤트에 응답하지 않는다. visibility: hiddenopacity: 0, pointer-events: none 과 동일하게 동작한다.
    • 접근성 측면에서, 오직 opacity: 0 요소만 탭 순서에 따라 접근이 가능하며, 요소의 컨텐츠를 스크린리더로 읽을 수 있다.
    • display: none 이나 opacity: 0을 적용하는 것은 자식 요소들에 영향을 준다. 반면 visibility: hidden 은 자식 요소들의 visibility에 영향을 주지 않는다.
    • 요소의 크기를 측정하고 싶다면, 절대 display: none 을 쓰면 안 된다.

    Ref display: none vs opacity: 0 vs visibility: hidden - HTML DOM

    타입스크립트가 null 또는 undefined를 걸러내게끔 하는 방법

    다음 예제처럼 optional 연산자 ?를 쓰는 대신,

    Copy
    if (!MEDIAN_PRICE[shop.categoryCode]) {
      return null;
    }
    
    return {
      <span className={styles.accent}>{MEDIAN_PRICE[shop.categoryCode]?.toLocaleString()}원
    }

    아래 예제처럼 별도의 변수로 분리한다.

    Copy
    const medianPrice = MEDIAN_PRICE[shop.categoryCode]; // ✅
    
    if (!medianPrice) {
      return null;
    }
    
    return {
      <span className={styles.accent}>{medianPrice.toLocaleString()}</span>;
    }

    UTM

    사용자가 어떤 경로를 통해 애플리케이션으로 유입되는지 파악할 때 사용할 수 있다.

    Urchin Tracking Module의 약자로, GA에서 애플리케이션 사용자의 유입을 분석할 때 사용한다. UTM 코드는 링크에 해당하는 웹사이트 주소 뒤에 붙게 되며, GA에 해당 링크의 정보를 전달하는 역할을 수행한다.

    Copy
    https://airbridge.io?utm_source=google&utm_medium=blog&utm_campaign=july_launch&utm_content=WhatisUTM

    👩‍🏫 이때 source, medium은 필수값이다.

    UTM은 아래와 같은 정보를 수집한다.

    1. 해당 유입이 어디로부터 발생하였는가?
    2. 해당 유입이 어떻게 발생하였는가? (→ utm_medium)
    3. 해당 유입이 왜 발생하였는가?

    Ref https://blog.ab180.co/posts/utm-code-olbareuge-sayonghagi

    as unknown as [type]

    Copy
    this._timer = setTimeout(() => this._send(), 100) as number;
    // 🚨 Conversion of type 'Timeout' to type 'number' may be a mistake
    // because neither type sufficiently overlaps with the other.
    // If this was intentional, convert the expression to 'unknown' first.

    에러 메시지를 살펴보면, 타입을 한번 풀어준 다음에 써줘야 할 것 같다.

    아래와 같이 작성하여 해결한다.

    Copy
    this._timer = setTimeout(() => this._send(), 100) as unknown as number;

    이때 any 가 아닌 unknown 을 썼기 때문에, 반드시 개발자가 type narrowing을 직접 해줘야 한다!

    String.charCodeAt() & String.fromCharCode()

    String.charCodeAt()은 이름에서도 알 수 있듯이 파라미터로 주어진 인덱스에 대한 UTF-16 코드를 알아낸다. String.fromCharCode()는 물론 그 반대.

    Copy
    const text = "HELLO WORLD";
    const code = text.charCodeAt(0); // 72
    String.fromCharCode(code); // 'H'

    Ref https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/String/charCodeAt https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/String/fromCharCode


    이것저것

    • 체리픽을 하면 커밋 번호가 새롭게 생성된다.

    • 웹뷰에서 파라미터를 넘겨주려면 encodeURIComponent를 활용해서 url을 인코딩한 주소를 열어야 한다.

    • yml / yaml - JSON의 단점을 보완하는 superset으로, JSON의 완전상위호환이다. 주석/ 불필요한 따옴표 제거/ 타입명시 가능 등 좀 더 간결하고 안전한 데이터 표현이 가능하다.

    • <img /><iframe /> 태그에는 loading이라는 속성이 있다.

    • create-react-app 의 최신 버전은 major 5인데, 여기서 리액트 18 버전을 사용한다

    • regexp.test()의 파라미터의 타입은 string이기 때문에, 자바스크립트에서는 undefined를 넣어도 결과가 나오지만 타입스크립트에서는 에러로 취급한다.

      Copy
      // 타입스크립트
      /ab+c/i.test(undefined); // 🚨 Argument of type 'undefined' is not assignable to parameter of type 'string'.
    • flow content

      • <body /> 태그 안에서 바로 사용할 수 있는 태그들을 가리킨다.
      • <li />는 반드시 ul/ol/menu 이하에서 사용되어야 하기 때문에 flow content가 아니다
    • 웹 브라우저에도 page lifecycle이 있다. (Page Lifecycle API - Chrome Developers)


    기타

    Mock Service Worker는 프론트엔드에서 모델을 설계하고, 더미 데이터를 운영하는데 있어 큰 도움이 될 수 있는 라이브러리다.

    앞으로의 테스트 생태계는 Mock Service Worker로 대체 될 수 있을까? 링크의 깃헙 페이지 하위의 mswjs/data를 이용하면 데이터를 손쉽게 모델링할 수도 있다. 백엔드는 잘 모르지만, primaryKey, nullable 등의 옵션을 보아하니 거의 SQL의 여러 기능들을 가져온 것 같다?

    프로젝트의 상황에 따라 백엔드가 제공되지 않은 상황에서 다음과 같은 기능을 사용한다면 충분히 도움이 될 수 있을 것이다. 또 서버가 제대로 모델링이 안되었을 경우, 프론트엔드에서 선제적 모델링이 가능해질 시기가 올 수도?

    Ref https://github.com/mswjs


    마무리

    또 바쁜 일주일이 후다닥 지났다. 사실 별로 안 더운데, 일에 집중하고 있을 때는 왜 더울까 🤔 벌써 5월 말에 접어들고 있으니 더울 만도 하다. 평일 동안 부채질만 하며 살다가, 주말에 드뎌 에어컨 청소를 했다. 하지만 틀진 않을 것이다 (??)

    다행히 더 더워지기 전에 깁스도 풀었다. 이제 운동도 다시 살살 하고, 다음주에 열심히 놀아야지!


    Relative Posts:

    5월 4주차 기록

    May 28, 2022

    5월 2주차 기록

    May 14, 2022

    zigsong

    지그의 개발 블로그

    RotateLinkImg-iconRotateLinkImg-iconRotateLinkImg-icon