ziglog

    Search by

    3월 3주차 기록

    March 19, 2022 • ☕️☕️☕️ 17 min read

    매일매일 어디론가


    배워가기

    Groovy

    docker와 함께 Jenkins 환경설정 및 플러그인설치 등 수동작업을 자동화하는 데 Groovy 파일을 사용할 수 있다. Groovy 파일은 Java를 발전시킨 객체지향 프로그래밍 언어로, 젠킨스에서 파이프라인을 구성하기 위한 파이프라인 문법 작성에 Groovy를 사용한다고 한다.

    Ref https://blog.voidmainvoid.net/104

    HTML <form>의 유효성 검증 방식

    HTML <form> 태그의 유효성 검증은 아래 순서로 이루어진다.

    1. submit 버튼을 클릭한다.
    2. 검증을 통과하지 못한 부분에 경고가 나타난다.
    3. 고치면 경고가 사라진다.(실시간 유효성 검증)
    4. 다시 잘못 입력해도 submit 버튼을 누르기 전까지는 실시간 검증이 되지 않는다.

    이는 react-hook-form의 UX 정책과 유사한 방식이다. 하지만 실시간 검증을 계속 할 경우 성능상 이슈가 생길 수 있다.

    exhaustiveness checking

    TypeScript에서 타입 체크할 때 모든 케이스에 대해 철저히 검사하는 것으로, 절대 나타나지 않을 경우에 대해 never를 사용한다.

    Copy
    const exhaustiveCheck = (param: never): never => {
      throw new Error("type error!");
    };

    조건문(if-else 또는 switch)으로 타입 가드를 쭉 작성하다가, 마지막 else 또는 default문에 위 함수를 넣어주면 된다. 모든 경우에 대해서 개발자가 확인했음을 보장하는 것이다.

    타입 가드를 철저히 작성하지 않았다면 위 함수에서 TypeScript 컴파일 에러가 발생하게 된다. 따라서 철저한 타입 체크 및 런타임 에러를 방지할 수 있다.

    Copy
    type Fruit = "banana" | "orange" | "mango";
    
    function makeDessert(fruit: Fruit) {
      switch (fruit) {
        case "banana":
          return "Banana Shake";
        case "orange":
          return "Orange Juice";
      }
      exhaustiveCheck(fruit); // 🚫 ERROR! `mango` is not assignable to type `never`
    }

    위 예제에서는 파라미터에 mango가 들어오는 경우 조건문에서 처리해주지 못해 에러가 발생했다. mango의 케이스를 추가해 컴파일 에러를 제거한다.

    Copy
    type Fruit = "banana" | "orange" | "mango";
    
    function makeDessert(fruit: Fruit) {
      switch (fruit) {
        case "banana":
          return "Banana Shake";
        case "orange":
          return "Orange Juice";
        case "mango":
          return "Mango Smoothie";
      }
      exhaustiveCheck(fruit); // ✅ no error, all values handled.
    }

    Ref https://dev.to/babak/exhaustive-type-checking-with-typescript-4l3f

    유용한 <picture> 태그

    <picture> 태그는 <source> 요소와 <img> 요소를 포함하여 화면/디바이스 별로 서로 다른 대체 이미지를 제공할 수 있게 해준다.

    이때, picture 태그로 다양한 DPI와 이미지 확장자를 처리할 때에 반드시 하나의 img 태그를 자식으로 포함해야 한다.

    Copy
    <picture>
      <source srcset="/media/cc0-images/surfer-240-200.jpg"
              media="(min-width: 800px)">
      <img src="/media/cc0-images/painted-hand-298-332.jpg" alt="" />
    </picture>

    수많은 srcset 중에서 현재 렌더중인 src는 <img> 태그의 currentSrc 프로퍼티로 확인 가능하다

    많은 경우 <picture> 태그에 display: contents 를, <source> 태그에는 display: none을 적용한다. display: contents 는 자신의 box를 만들지 않는다. 따라서 <picture>의 래퍼 컴포넌트를 만들 때에 <picture>display: contents를 적용하고 forwardRef로 ref를 <img> 태그에 주면, 사용하는 측에서는 img 태그를 사용하는 것과 똑같이 사용할 수 있다.

    <source> 태그는 렌더링될 게 없으나 default로 display: inline 이 되어있어 불필요하게 렌더트리에 포함된다. 따라서 깔끔하게 display: none으로 보이지 않게끔 적용한다. 이는 picture의 동작에는 영향을 미치지 않는다.

    CSS remedy 에서도 위 속성들을 사용하는 코드를 볼 수 있다.

    Ref https://developer.mozilla.org/en-US/docs/Web/CSS/display#box https://developer.mozilla.org/en-US/docs/Web/HTML/Element/picture

    optional한 타입의 추론

    optional한 타입은 undefined | type 으로 추론되기 때문에, 특정한 type만을 가지는 변수에 optional 타입을 직접 삽입할 수 없다. 이 때 undefined가 되는 상황을 안다면 default 값을 설정을 하여 값이 undefined인 경우를 배제시킬 수 있다.

    Copy
    interface props = {
      inRange?: boolean
    }
    // isFirstDayOfMonth는 boolean 타입의 값을 받는다
    const testFunc = ({ inRange }) => {
      function returnBoolean(): boolean {}
    
      // 🚨 타입 에러
      return <Day isFirstDayOfMonth={inRange && returnBoolean()} />
    }
    
    /*
    inRange 타입이 undefined | boolean으로 추론되기 때문에
    inRange && returnBoolean()의 추론타입은 undefined | boolean이 된다.
    결과적으로 Boolean(inRange && return Boolean()) 과 같이 캐스팅 작업이 필요해지게 된다.
    
    하지만 위의 경우는 inRange가 props로 넘어오지 않는 상황의 값이 false인 경우와 동일하다.
    그러므로 inRange의 default 값을 false로 지정해줌으로써 타입이 undefined인 경우를 배제시킬 수 있다.
    */
    
    const testFunc = ({ inRange = false }) => {
      function returnBoolean(): boolean {}
    
      // ✅ 정상 작동
      return <Day isFirstDayOfMonth ={inRange && returnBoolean()} />
    }

    함수 컴포넌트에 Generic 사용하기

    함수 컴포넌트도 함수기 때문에 Generic한 component를 만들 수 있다. props에 Generic Type을 사용해야 할 때 유용하다.

    Copy
    interface Props<T> {
      renderItem: (item: T) => React.ReactNode;
      keyExtractor: (item: T) => string;
      data: T[];
    }
    
    const GenericComponent = <T extends unknown>({
      data,
      renderItem,
      keyExtractor
    }) (props: Props<T>) => {
      return (
        // ...
      )
    }

    Ref https://medium.com/edonec/creating-a-generic-component-with-react-typescript-2c17f8c4386e

    <iframe> 태그와 <object> 태그

    두 태그 모두 웹페이지(html) 내에서 다른 웹페이지(html)나 멀티미디어 객체를 보여줄 수 있는 태그다.

    <iframe> 태그는 종종 볼 수 있지만, <object> 태그는 낯설 수도 있다. 아래와 같이 사용한다.

    Copy
    <object
      type="application/pdf"
      data="/media/examples/In-CC0.pdf"
      width="250"
      height="200"
    ></object>

    💡 HTML5에서 표준화된 <embed>라는 태그도 존재한다. html이 아닌 외부 요소들을 통합하는 데 사용한다. 주로 svg나 정적인 데이터를 표시한다.

    Copy
    <embed type="video/webm"
      src="/media/cc0-videos/flower.mp4"
      width="250"
      height="200">

    말줄임표 CSS 구현방법

    • 1줄 제한

      Copy
      .selector {
        text-overflow: ellipsis; /* 끝 처리를 ... 으로 한다. */
        white-space: nowrap; /* 줄바꿈을 하지 않는다. */
        overflow-x: hidden; /* ...뒤에 나머지 내용을 숨긴다. */
      }
    • n줄 제한

      Copy
      .selector {
        display: -webkit-box; /* 해당 영역을 box 형태로 관리한다. */
        -webkit-box-orient: vertical; /* webkit box 내의 정렬을 수직으로 한다. */
        -webkit-line-clamp: 1; /* 영역 내 컨텐츠의 최대 라인수를 결정한다. */
        text-overflow: ellipsis; /* 끝 처리를 ... 으로 한다. */
        overflow-x: hidden; /* ...뒤에 나머지 내용을 숨긴다. */
      }

    (정)규식이형 이것저것

    • 정규식의 기본은 3연산이다. - 반복, 집합, 선택

    • 정규식에서 그룹 연산자는 모든 연산자보다 우선 순위가 높다.

    • 정규식을 파싱할 때 서브패턴 ()를 사용하면 정규식 엔진은 서브패턴을 하나의 정규식으로 인식한다. 즉, (ab)|(cd)(정규식|정규식)으로 정의되어 정규식의 성능을 저하시키는 원인이 될 수 있다.

    • JavasSript에서 전방 일치는 match(), 부분 일치는 search()를 사용한다.

      • 대부분의 언어에서는 부분 일치와 완전 일치 메서드를 제공한다.
      • 부분 일치와 완전 일치 메서드를 활용하면 전방 일치와 후방 일치를 구현할 수 있다.
    • 정규식에서는 위치도 문자와 동등하다.

      • "abcd" === "위치a위치b위치c위치d위치"

    overflow-wrap vs word-break

    CSS overflow-wrap(word-wrap) 속성은 어떤 문자가 내용 칸 밖으로 넘치지 않게 브라우저가 단어 마디 안에 줄을 바꿔야 할 것인지를 정할 때 사용한다. overflow-wrap: break-word로 설정하면, 한 줄에서 단어가 길어질 때 임의의 지점에서 줄을 바꾼다.

    기존에는 word-wrap이라는 이름으로 사용됐지만, overflow-wrap으로 바뀌었고, word-wrap은 동의어로 계속 사용되고 있다.

    word-break 속성은 텍스트가 자신의 콘텐츠 박스 밖으로 오버플로할 때 줄을 바꿀지 지정한다. 즉 각 글자를 띄어쓰기 단위를 고려해서 줄바꿈할 것인지, 어디서든 끊게끔 할 것인지 결정할 수 있다.

    word-break: break-word라면 실제 overflow-wrap 속성에 상관하지 않고, word-break: normaloverflow-wrap: anywhere를 설정한 것과 같은 효과를 낸다.

    Ref https://developer.mozilla.org/ko/docs/Web/CSS/overflow-wrap https://developer.mozilla.org/ko/docs/Web/CSS/word-break

    ZWSP: Zero Width Space

    별다줄…인 말이 있다. ZWSP. 두번 다시 떠올리기도 힘들 단어다. ‘Zero Width Space’ 즉 ‘폭 없는 공백’은 공백이 아닌 공백 문자를 가리킨다. 유니코드 문자셋 U+200B에 해당한다. 일반 사용자에게서는 직접 사용할 상황이 없는 기능이지만, 윈도우 XP 이상의 메모장에서는 우클릭을 눌러 나오는 메뉴에서 유니코드 제어 문자 삽입을 통해 이 문자를 직접 추가할 수 있다. (대표적으로 ZWJ, ZWNJ)

    드래그해서 이 문자를 선택할 수 있고 복사, 붙여넣기 등의 작업도 정상적으로 가능하다. 여기서 문제가 발생할 수 있다! 유저가 다른 곳에서 어떤 문자열을 드래그해서 붙여넣기 했는데, 우리가 만든 플랫폼에서 form validation 등에 의해 보이지 않는 문자에 대한 검증이 이뤄져 오류인 것처럼 보이는 상황을 만들어낸다.

    폭 없는 문자라는 이름에서 알 수 있듯이 아무리 많이 이 글자를 추가하더라도 문장의 길이가 늘어나지 않기 때문에 육안으로는 얼마나 이 글자가 추가되어 있는지 식별할 방법이 없다. 미리 유니코드 문자셋 /u200b를 사용하여 제거해주도록 하자!

    Ref https://unicode-table.com/kr/200B/

    TypeScript

    Copy
    const numbers = [1, undefined, 2, undefined, 3];

    위와 같은 배열이 있을 때, numbers의 타입은 (number | undefined)[]로 추론된다.

    filter() 메서드를 통해 배열의 원소 중 falsy값 즉 undefined를 제거해보자.

    Copy
    const realNumbers = numbers.filter(Boolean);
    console.log(realNumbers); // [1, 2, 3]

    그런데 realNumbers의 타입은? number[]가 아니라 여전히 (number | undefined)[]다. 😫 생각해보면, filter()를 통해서 falsy값을 제거했다고 한들 filter()에서 실제로 뭘 했는지는 TypeScript가 알 길이 없다.

    그래서, 타입을 올바르게 추론하게끔 하기 위해 아래와 같이 타입 가드를 작성해서 해결했다. TypeScript에 기본 유틸리티 타입으로 존재하는 NonNullable<Type>은, Type에서 nullundefined를 제거한 타입을 반환한다.

    Copy
    function nonNullable<T>(value: T): value is NonNullable<T> {
      return value !== null && value !== undefined;
    }
    
    [1, 2, 0, null].filter(nonNullable); // number[]

    Ref https://stackoverflow.com/questions/47632622/typescript-and-filter-boolean

    -ms-high-contrast

    디스플레이가 high contrast mode, 즉 고해상도 모드에서 동작하고 있는지를 나타낸다.

    -ms-high-contrast: active라면 시스템이 색상과 관계없이 고대비 모드로 설정되었을 때 후속 스타일링 규칙이 적용됨을 나타낸다. 글자 등이 겹쳐 보일 때 스타일링을 다르게 주어 사용자가 컨텐츠를 올바르게 인지할 수 있게끔 해준다.

    Copy
    @media screen and (-ms-high-contrast: active) {
      /* 모든 고대비 모드에 대한 스타일 규칙입니다 */
    }

    Ref https://developer.mozilla.org/en-US/docs/Web/CSS/@media/-ms-high-contrast

    <img> 태그의 display

    <img> 태그의 display 기본값은 inline이다. 기본적으로 display 는 inline이나, 기본 크기는 내장한 이미지의 고유 크기로 정해지므로 마치 inline-block처럼 보인다.

    <img> 태그는 ‘대체 요소’로, 문서 스타일과는 별개의 스타일이 적용되어 있을 수 있다.

    대체 요소 CSS의 대체 요소(replaced element)란 자신의 표현 결과가 CSS의 범위를 벗어나는 요소로서, CSS 서식 모델과는 분리된 외부 객체인 요소다. 간단히 말해서, 대체 요소는 자신의 콘텐츠가 현재 문서 스타일의 영향을 받지 않는 요소라고 할 수 있다.

    예를 들면, <img> 태그에 아무 설정도 하지 않았지만 알 수 없는 margin이 적용되어 있을 때가 있다. 이는 <img /> 태그의 display 속성이 inline이라 기본적으로 갖고 있는 white-space, line-height 등의 속성이 적용되어 있기 때문이다. display: block을 적용하면 콘텐츠가 차지하는 영역을 올바르게 조정할 수 있다.

    Ref https://developer.mozilla.org/ko/docs/Web/CSS/Replaced_element https://stackoverflow.com/questions/20788232/remove-unexplainable-img-margin-space-css


    이것저것

    • git checkout <브랜치명> <파일명> 으로 해당 브랜치의 파일으로 변경할 수 있다.

    • react-scripts 를 실행시킬 때 DISABLE_ESLINT_PLUGIN=true 로 주고 실행시키면 webpack 내 eslint plugin을 off 할 수 있다.

    • 크롬 개발자 패널에서 런타임 퍼포먼스 측정방법을 제공하고 있다.

    • React의 useMemo는 1) 참조 동일성 2) 계산이 복잡한 작업 에 사용하는 것이 좋다. (Ref)

    • 정규식의 수량자는 syntax sugar다! (ex. {a|aa|aaa} ➡️ a{1,3}`)

    • eslintConfig에서 직접 prettier룰을 넣어 설정할 수 있다.

      Copy
      "prettier/prettier": ["error", {
        "semi": false,
        "singleQuote": true,
        "trailingComma": "none",
        "printWidth": 80
      }],
    • vscode 설정에서 .ts 파일에 대해서만 prettier formatter를 off할 수 있다.

      Copy
      {
        "editor.defaultFormatter": "esbenp.prettier-vscode",
        "editor.formatOnSave": true,
        "[typescript]": {
          "editor.formatOnSave": false, // ⬅️ 여기만 off
          "editor.codeActionsOnSave": {
            "source.fixAll.eslint": true
          }
        },
        "[typescriptreact]": {
          "editor.formatOnSave": false, // ⬅️ 여기만 off
          "editor.codeActionsOnSave": {
            "source.fixAll.eslint": true
          }
        },
        // ...
      }
    • svg 파일을 클라이언트 디렉토리에 저장하지 않고, 문자열만으로 직접 만들어서 상용할 수 있다. createIcon 함수를 만들어두고, svg의 값들을 넘겨서 만들어서 사용한다.

    • addEventListener 메서드의 옵션 중 once라는 옵션은 수신기가 최대 한번만 동작해야함을 나타내는 boolean 값이다. 해당 옵션을 true로 지정할 경우, 수신기가 발동한 후에 스스로를 대상으로부터 제거하게 된다.

    • 부모의 max-height 값이 선언되어있더라도, 자식은 부모의 height 값을 기준으로 삼지 못한다. 부모가 max-height: 80%, height: auto로 선언되었다면 자식은 부모로부터 height 기준을 알지 못하게 되고, height: auto에 의해 내용물의 크기를 기준으로 삼는다.

    • Next.js는 빌드 시 env가 있으면 process.env.~ 부분을 해당 값으로 교체하고, 없으면 .next에 그 구문을 그대로 남겨둔다. next start 런타임에서 주입될 env들은 이렇게 사용할 수 있다.

    • react-hook-form 라이브러리를 사용하면 reValidateMode 옵션을 활용해서 onChange일 경우, onBlur일 경우, onSubmit일 경우에 각각 유효성 검사를 할 수 있게끔 설정할 수 있다.

    • JavaScript includes()는 객체 비교 시 참조값으로 비교하기 때문에, 객체의 배열에서 검사 시 includes() 대신 find()filter() 메서드를 사용하는 것이 좋다

    • <table> 태그 사용 시, <tr> 태그에는 border를 적용할 수 없다. 그렇다고 <td> 태그에 border를 넣으면 각 셀에 맞춰 끊긴 border가 그려진다. 이때는 <table> 태그에 border-collapse: collapse를 적용한 후 <td> 태그에 border를 넣어야 한다.


    기타

    공변과 반변

    이전에 정리한 글에서 TypeScript 함수 타입 정의에서의 공변과 반변을 다뤘었는데, 이번에 새롭게 알게 된 좋은 아티클이 있다.

    Ref https://seob.dev/posts/공변성이란-무엇인가/ https://sorto.me/posts/2021-03-16—variance

    React 18의 IE 지원 종료

    이렇게 세상이 바뀐다…

    Ref https://reactjs.org/blog/2022/03/08/react-18-upgrade-guide.html#dropping-support-for-internet-explorer

    Node 모듈의 malware

    Vue CLI와 Unity Hub에서 쓰는 node-ipc의 개발자가 우크라이나 전쟁을 반대한다는 이유로 패키지 dependency에 ‘peacenotwar’라는 malware를 심어뒀다고 한다. 바탕화면에 임의로 텍스트 파일 생성하고, 사용자 IP가 러시아/벨라루스 지역이면 시스템의 모든 파일 내용을 하트 이모지로 변경 (지금은 제거)한다.

    엄청난 역따봉이 달리고 있는데, 신기하다… 오픈소스 커뮤니티 세상…

    Ref https://github.com/RIAEvangelist/node-ipc/issues/233

    JavaScript에서 Type 문법 사용하기 (Proposal)

    Stage 0에 등록된 내용으로, JavaScript에서 Type 문법을 사용할 수 있게 해주자는 것이다. TypeScript는 결국 JavaScript의 superset이기 때문에 코드상으로 다른 문제는 일으키지 않을 것이며, 타이핑을 comment와 같은 역할을 할 수 있게끔 만들 수 있다.

    Copy
    /**
     * @param a {number}
     * @param b {number}
     */
    function add(a, b) {
      return a + b;
    }

    위와 같은 식으로, JSDoc에 타입을 사용할 수 있게끔 제안하는 것 같다.

    JavaScript에서 이런 방식으로 타입을 사용할 수 있게 된다면, TypeScript의 컴파일(트랜스파일) 단계가 필요하지 않아서 실행 시간을 단축시킬 수 있다는 장점을 이야기하고 있다.

    Ref https://devblogs.microsoft.com/typescript/a-proposal-for-type-syntax-in-javascript/ https://github.com/giltayar/proposal-types-as-comments/

    크롬 99 Release

    무엇보다 벌써 크롬이 99버전이나 됐다는 게 놀랍다… 시도때도 없이 나타나는 크롬 업데이트 버튼이 이를 보여주는 것이었을까.

    크롬 99 업데이트에서 달라지는 것은 다음과 같다.

    • 웹소켓 요청 쓰로틀링
    • 새로운 Reporting API (Application 탭)
    • 요소가 보일 때까지/클릭할 수 있을 때까지 기다림 (Recorder 탭)
    • 더 나은 콘솔 스타일링/포맷팅/필터링
    • 소스맵 개선
    • 자동 다크 테마 업데이트
    • 터치하기 쉬운 color picker와 split pane
    • 기타 다양한 기능들
      • cookie 편집
      • Shift + Tab으로 이전 명령어 선택하기
      • CORS preflight 요청 리포트 (Issues 탭)
      • User-Agent Client Hints 리포트 (Issues 탭)

    Ref https://developer.chrome.com/blog/new-in-devtools-99/

    프론트엔드 개발자가 알면 좋은 11가지 툴

    그중에서도 가장 첫 번째에 소개된 Responsively은 정말 멋진 것 같다! 이 툴을 써보고 싶어서 빨리 반응형을 작업해보고 싶다(?)

    Ref https://blog.devgenius.io/11-amazing-frontend-development-tools-that-you-should-use-in-2022-b12c64caa346

    사망한 개발팀 살리기

    Ref https://pfdev.medium.com/사망한-개발팀-살리기-6f2d22a53242

    10배 이상 뛰어난 개발자가 되는 법

    1. 사용하는 도구에 대한 연구를 한다.
    2. 도움을 요청한다.
    3. 비즈니스 가치를 제공한다.

    Ref https://yozm.wishket.com/magazine/detail/1373/


    마무리

    이번주 정말정말 업무 외적으로 일이 정말 많았다. 용케 다 해온 게 대견스럽다. 결국 주말에 하루 기절했지만… 날씨도 갑자기 쌀쌀해지고 비까지 온다. 그래도 이 봄비가 지나가니 정말 봄이 올 것 같다.

    배민X오늘의집에서 이벤트를 해서 지난주 급 뽐뿌가 왔던 모션데스크와 그 폭풍으로 인한 각종 가구들을 쇼핑했다! 이렇게 매달 flex해버려도 되는 걸까.. 싶지만 뭐 오래 쓸 거니까 ㅎㅎ

    배민 사내 신춘문예에서 아깝게(!) 2등을 했다. 근데 후보까지 갈 줄도 몰랐는데.. 너무 이미지 이상한 사람 되는 거 아닌가 모르겠다 🤷‍♀️


    Relative Posts:

    3월 4주차 기록

    March 25, 2022

    3월 2주차 기록

    March 12, 2022

    zigsong

    지그의 개발 블로그

    RotateLinkImg-iconRotateLinkImg-iconRotateLinkImg-icon