ziglog

    Search by

    3월 2주차 기록

    March 12, 2022 • ☕️☕️☕️ 14 min read

    유캔두 100일 종료!


    배워가기

    2개 이상의 modal이 떴을 때 순서대로 닫기

    document.activeElement를 통해 현재 focus 된 element 요소를 가져올 수 있다. modal이 띄워진 상태에서 새로운 modal을 띄울 때, 가장 최근에 focus 된 element (기존에 띄워진 modal의 자식 element)를 저장해두고, 새로운 modal을 focus한다.

    그리고 focus된 새로운 modal을 닫을 때, 이전에 저장해둔 element를 다시 focus 함으로써 focusing 대상이 첫번째 modal의 자식이 될 수 있게 구현한다. (실제 이벤트 동작을 위해서는 이벤트 버블링을 이용한다.)

    위와 같은 방식으로 띄워진 modal의 순서에 맞춰 focusing이 가능해졌다. react-component dialog가 위와 같은 방식으로 구현되어 있다.

    TypeScript에서 Discriminated union을 구조 분해 할당하면?

    TS 4.6 이전 버전에서는 Discriminated union(Tagged union)을 구조 분해 할당할 경우 TypeScript가 문맥을 잃어 태그에 따른 나머지 필드에 대한 타입을 잡아주지 못했지만, 이제는 잡아줄 수 있다.

    Copy
    type Action = { tag: "a"; payload: number } | { tag: "b"; payload: string };
    
    type A = {
      tag: "a";
      payload: number;
    };
    
    type B = {
      tag: "b";
      payload: string;
    };
    
    type AorB = A | B; // tagged union
    
    function typeCheck(param: AorB) {
      const { tag, payload } = param;
    
      if (tag === "a") {
        // v4.6 : payload는 number로 추론됨
        // v4.5 : payload는 number | string으로 추론됨
        console.log(payload.toFixed()); // v4.5에서만 error
      } else {
        // v4.6 : payload는 string으로 추론됨
        // v4.5 : payload는 number | string으로 추론됨
        console.log(payload.toLocaleUpperCase()); // v4.5에서만 error
      }
    }
    
    // v4.5에서 error가 나지 않으려면
    function typeCheckAgain(param: AorB) {
      if (param.tag === "a") {
        console.log(param.payload.toFixed());
      } else {
        console.log(param.payload.toLocaleUpperCase());
      }
    }

    추가적으로, 이전에는 jsx 코드를 컴파일할 경우 의미 없는 void 0 이라는 코드가 붙어서 컴파일되었지만, 이제는 이 부분이 제거되었다. 그래서 번들 사이즈가 향상되었다.

    Ref https://devblogs.microsoft.com/typescript/announcing-typescript-4-6/#removed-unnecessary-arguments-in-react-jsx

    react-router v6에서 달라지는 것

    React-Router v6부터 Routes 이하에는 RouteReact.Fragment만 들어갈 수 있다.

    Routes 이하의 컴포넌트에 제약을 걸어, 복잡하지 않고 효과적인 경로 분기 처리를 위함이다.

    Copy
    // 가능
    <Routes>
      <Route path={...} element={<Foo/>}/>
      <>
        <Route path={...} element={<Bar/>}/>
      </>
    </Routes>
    Copy
    // 불가능
    <Routes>
      {routes.map(({ path, element }) => (
        <RouteWrapper key={path} path={path} element={element}/>
      )}
    </Routes>
    // ...
    function RouteWrapper(info: RouteInfo): React.ReactNode {
      return <Route {...info} />
    }

    Art Direction

    Art direction은 각기 다른 디스플레이 사이즈에 맞추어 보여지는 이미지를 제공하고자 하는 것이다. 데스크톱 화면에서는 전체 풍경을 보여 주는 가로 이미지를 보여주고 모바일 화면에서는 좀 더 편하게 볼 수 있게 세로 이미지를 제공한다.

    미디어 쿼리를 통해 웹페이지 가로모드 / 세로모드에 맞춰 css를 작성할 수 있다.

    • @media (orientation: portrait) 세로모드
    • @media (orientation: landscape) 가로모드

    Ref https://studiomeal.com/archives/1068

    hooks & presentational pattern

    리액트에서 비즈니스 로직과 뷰를 분리하기 위해 custom hook을 사용할 수 있다. 이를 hooks & presentational pattern 이라고 한다.

    많은 경우 custom hook은 반복되는 로직을 줄이는 목적으로 사용을 하는데, 컴포넌트 서비스 로직의 대부분을 custom hook에 넣고 컴포넌트에서 사용되는 데이터들만 따로 반환해 줌으로써 view와 logic을 분리할 수 있다.

    하지만 잘못 사용하게 되면, 어떤 내용으로 코드가 동작하는지 로직들이 훅에 가려져서 코드 파악이 어려워 지니, 당장 몰라도 되는 디테일만 custom hook으로 빼내고, 코드 파악에 핵심이 되는 요소들은 컴포넌트에서 직접 다루는 연습이 필요하다.

    Ref https://felixgerschau.com/react-hooks-separation-of-concerns/ https://vallista.kr/Component-분리의-미학/

    dangerouslySetInnerHTML

    리액트에서 innerHTML 대신 string 형태의 HTML 문자열을 렌더링하기 위한 속성이다.

    innerHTML은 XSS 공격에 취약하기 때문에 위험하다는 걸 상기시키는 역할을 한다. <div dangerouslySetInnerHTML={ {__html: ${stringHTML}} }>와 같은 방식으로 작성한다.

    dangerouslySetInnerHTML을 사용하면 리액트는 해당 요소의 컨텐츠가 동적이라는 것을 알고, 해당 노드의 자식에 대해서는 가상 DOM을 비교하는 과정을 수행하지 않는다.

    유저가 많은 양의 텍스트를 입력하여 해당 내용을 보여주고자 할 때와 같은 경우, 데이터에 html 태그가 표함되어 있는 경우 등에서 사용할 수 있다.

    예를 들어,

    Copy
    const App = () => {
      const data = "lorem <b>ipsum</b>";
    
      return <div>{data}</div>;
    };
    
    export default App;

    위와 같은 경우 <b> 태그는 html 태그로 인식되지 않고 <b>ipsum</b>가 문자열 그대로 화면에 출력될 것이다. 이럴 때 dangerouslySetInnerHTML을 사용하여 <b> 태그를 올바르게 인식하여 html을 출력하게 해준다.

    Copy
    const App = () => {
      const data = "lorem <b>ipsum</b>";
    
      return <div dangerouslySetInnerHTML={{ __html: data }} />;
    };
    
    export default App;

    Ref https://ko.reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml https://blog.logrocket.com/using-dangerouslysetinnerhtml-in-a-react-application/

    ReactNode vs ReactElement vs JSX.Element의 타입

    ReactNodeReactElement이자 & JavaScript의 원시타입이다. 가장 넓은 타입을 가지고 있고, 클래스 컴포넌트에서 render() 메소드의 반환 값으로 사용된다.

    Copy
    type ReactText = string | number;
    type ReactChild = ReactElement | ReactText;
    interface ReactNodeArray extends Array<ReactNode> {}
    type ReactFragment = {} | ReactNodeArray;
    
    type ReactNode =
      | ReactChild
      | ReactFragment
      | ReactPortal
      | boolean
      | null
      | undefined;

    ReactElementtypeprops를 가지는 타입으로 propstype의 타입을 지정할 수 있다. 함수형 컴포넌트의 반환 값으로 사용된다.

    Copy
    interface ReactElement<
      P = any,
      T extends string | JSXElementConstructor<any> =
        | string
        | JSXElementConstructor<any>
    > {
      type: T;
      props: P;
      key: Key | null;
    }

    JSX.ElementReactElement의 제네릭 타입에 any가 지정된 타입이다. JSX.Element는 글로벌 네임스페이스로 정의되어있어 각 라이브러리에서 지정하는 방식으로 설정할 수 있다.

    Copy
    // React에 지정된 JSX.Element
    declare global {
      namespace JSX {
        interface Element extends React.ReactElement<any, any> { }

    toPrecision()

    toPrecision() 메서드는 Number 객체를 지정된 정밀도로 나타내는 문자열을 반환한다. 인자로는 유효 자릿수를 지정하는 정수를 넣는다. 아무것도 넣지 않으면 값을 그대로 반환한다.

    Copy
    function precise(x) {
      return x.toPrecision(4);
    }
    
    console.log(precise(123.456));
    // expected output: "123.5"
    
    console.log(precise(0.004));
    // expected output: "0.004000"
    
    console.log(precise(1.23e5));
    // expected output: "1.230e+5"

    123.455.toPrecision()과 같이 number 값을 그대로 사용할 수는 없다.

    Copy
    const num = 123.456;
    num.toPrecision();

    유의할 점은, 숫자가 아닌 문자열을 반환한다는 것이다! 그리고, 유효자릿수가 작아 큰 자리 단위수를 반환하면 e+ 형태의 지수로 값을 표현한다.

    Copy
    let num = 927;
    let n = num.toPrecision(1);
    
    console.log(n); // '9e+2'

    Ref https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Number/toPrecision


    이것저것

    • Array.prototype.every()은 빈 배열 순회 시 true를, Array.prototype.some()은 빈 배열 순회 시 false를 반환한다. every()는 ‘어긋나는 게 있는지 검사’하고, some은 ‘충족하는 게 있는지 검사’한다고 생각하면 편하다.
    • Git Submodule은 A 저장소 하위에 B 원격 저장소를 두고 관리하는 것으로, B 저장소 내용을 업데이트(커밋)하더라도 A 저장소에 기록이 남지 않는다.
    • CSS font-style의 기본값은 none이 아니라 normal이다.
    • monorepo를 사용할 때, 에디터의 워크스페이스에 따라 eslint 룰이 적용될 수도, 안될 수도 있다. 내가 올리는 코드가 다른 팀원들과 같은 lint 룰을 적용받고 있는지 체크하자.
    • shell 코딩 시 유용한 툴 zx
    • git remote prune origin 명령어를 사용하여 local에 존재하는 유효하지 않은 ref를 제거할 수 있다
    • Next.js는 src/pages 하위에 존재하는 모든 .tsx 파일을 자동으로 직접 접근이 가능한 라우트로 취급한다. 따라서 도메인 컴포넌트(페이지가 아닌 다른 컴포넌트)는 다른 디렉토리에 옮겨야한다 (ex. src/common/pages)
    • Artifact라는 용어는 Deploy를 위해 최종적으로 관리되는 산출물이라는 뜻으로 사용되어진다.
    • a 태그의 href 속성에는 파일이 존재하는 경로를 지정하고, download 속성에는 파일명을 집어넣어 클릭 메소드를 호출해서 원하는 파일을 다운로드 받을 수 있게 구현할 수 있다.
    • .code-workspace 파일을 이용해 monorepo에서 multi-root workspace를 구현할 수 있다. (Ref)
    • enumObject.keys()의 인자로 들어갈 수 있다.
    • React에서 렌더링하는 JSX에 넣는 key를 통해 렌더링을 컨트롤할 수 있다. element에 심어둔 key값을 바꿀 때 컴포넌트를 리렌더링시킬 수 있다. (Ref)
    • CSS의 only-of-type은 같은 유형의 형제가 없을 때 적용된다.

    기타

    TypeScript로 구현한 SOLID 원칙

    1. Single Responsibility Principle (SRP; 단일 책임 원칙)
    2. Open-Close Principle (OCP; 개방-폐쇄 원칙)
    3. Liskov Substitution Principle (LSP; 리스코프 치환 원칙)
    4. Interface Segregation Principle (ISP; 인터페이스 분리 원칙)
    5. Dependency Inversion Principle (DIP; 의존관계 역전 원칙)

    여태 본 SOLID 원칙에 대한 설명 예제 중에 가장 이해가 잘 된다! 🤓

    Ref https://blog.bitsrc.io/solid-principles-in-typescript-153e6923ffdb

    조건부 로직 테스트코드에서 사용하지 않기

    테스트 코드는 작동 원리를 쉽게 이해할 수 있게끔 작성해야 하며, 주석이 필요 없을 정도로 간단해야 한다. 조건부 (if~else) 테스트 로직은 테스트를 실제보다 더 복잡하게 만드는 요인 중 하나다.

    조건부 로직을 테스트에서 사용한 경우에 어떻게 개선할 수 있을까?

    첫 번째, 테스트 실행마다 결과가 달라지는 경우다. 매번 테스트를 수행할 때마다 테스트 환경이 변경되는 경우 하나의 테스트 코드가 수많은 케이스를 고려해야만 한다.

    이런 경우 테스트 환경을 직접 제어하는 코드를 작성한다. 여러 케이스를 하나의 테스트로 검증하고 싶다면 Parameterized Test를 작성하면 된다.

    두 번째, 테스트에서 프로덕션 로직을 사용한 경우다. 하나의 메소드에서 모든 입력값에 대한 결과를 검증하기 위해서 테스트 내부에 로직을 넣게 된다. 이렇게 작성된 테스트는 프로덕션 로직에 변경이 있을 때마다 테스트 코드도 함께 변경해야만 한다.

    이러한 SUT(System Under Test)를 테스트하기 위해서는 미리 계산된 값을 사용할 수 있다. 또는 첫 번째 경우와 마찬가지로 Parameterized Test를 사용할 수 있다.

    세 번째, 중복 코드를 리팩토링하는 과정에서 발생한다. 테스트 코드에서 중복되는 코드를 제거하고자 검증 부분만 조건문으로 작성하게 된다면 테스트 코드를 이해하기 더욱 어려워진다.

    이 경우, 리팩토링을 하더라도 2가지의 전제조건을 가져가면 좋다.

    • 해당 테스트가 무엇을 하는지 테스트 코드만으로 확인이 가능해야 한다.
    • 조건문을 최대한 피한다.

    관련 코드는 링크에서 다시 확인해보자!

    Ref https://jojoldu.tistory.com/642?fbclid=IwAR39pHWihFnYuudqUr5ScRVFpFhKDeNX0nLZiz4Xs_AvwmzmCe51TsqkgIA

    Hype Driven Development - 설레발 주도 개발

    팀에서 핫한 최신 기술만을 좇다가는 어떻게 되는지 보여주고 있다. 기술의 등장 배경이나 원리, 사용 시 장단점을 제대로 파악해보지도 않은 채 그저 트렌드라는 이유로, 작성법이 fancy해보인다는 이유로 여러 라이브러리/프레임워크 등을 써왔던 지난 날들을 반성하게 해준다…

    결정하기 전에 연구하고 테스트하기 가 좋은 방법이라는 것은 알지만, 실제 바쁘게 업무를 하면서 신기술에 대한 연구와 테스트를 진행하는 것도 많은 경우 현실적으로 어려운 상황이라는 것도 안다. 😵

    Ref https://lazygyu.net/blog/hype_driven_development

    기능별로 폴더 구조 나누기

    리액트에서 디렉토리 구조를 나누는 것은 정말 귀찮은 일이다. 하나의 best practice라는 게 존재하지도 않고 (구글에 ‘react folder structure best practice’를 검색하면 수많은 글들이 나온다…), 도메인마다, 또는 팀의 스타일에 따라서 달라지는 법이다.

    이 아티클에서는 기능별로 폴더의 구조를 나누는 방법을 소개하고 있다. 파일의 ‘타입’이 아닌 ‘기능(feature)’를 중심으로 폴더 구조를 나누는 것이다.

    Copy
    └── src/
      ├── components/
      ├── contexts/
      └── hooks/

    위와 같은 폴더 구조는 ‘나는 리액트 앱이다’라고 강력하게 얘기하고 있는 반면,

    Copy
    └── src/
      ├── features/
      │   ├── todos/
      │   ├── projects/
      │   ├── ui/
      │   └── users/
      └── pages/
        ├── create-project.js
        ├── create-todo.js
        ├── index.js
        ├── login.js
        ├── privacy.js
        ├── project.js
        ├── signup.js
        └── terms.js

    위 폴더 구조를 보고 우리는 이 앱이 ‘프로젝트 관리 툴’이라는 것을 알 수 있다.

    아래는, 덧붙여 소개하고 있는 Best Practices들의 내용이다.

    • 절대 경로 import를 사용한다.
    • index.js를 public API로 사용한다. 폴더 안의 파일들을 묶어 한 번에 export하는 역할을 한다.
    • 파일과 폴더 이름에 kebab-case를 사용한다. MacOS가 case-insensitive하기 때문에 발생할 수 있는 문제를 예방하기 위함이라는데, 개인적으로는 잘 모르겠다 🤔

    Ref https://profy.dev/article/react-folder-structure

    페이스북 개발자의 성과 만들기

    글의 요약은 다음과 같다.

    • 데이터 기반의 명확한 목표
    • 작업자가 스스로 할 일을 결정하는 능동적인 업무
    • A/B 테스트 위주의 개발
    • 데이터 기반의 성과 측정
    • 연예인 매니저같은 매니저
    • 체계적이고 합리적인 성과 관리 및 평가

    페이스북을 비롯한 실리콘 밸리의 이름난 기업들, FAANG 아니 MAANG… 실제로 이렇게 일하고 있는지 궁금하다! 한번 찾아가 보고 싶다. 자칫하면 국내의 그 어떤 기업들보다도 성과 중심적인 치열한 분위기가 되지 않을까 싶기도 하고.

    아래 문장이 기억에 남는다.

    개발자의 성과에는 책임이 있더라도 성장에는 책임이 있지 않습니다.

    그리고, 개발자로서 증명해보일 수 있는 성과를 만들어내기 위해 구체적으로 생각해볼 지표들. 즉 ‘프로젝트 영향력’을 잊지 않고 잘 챙겨야겠다.

    프로젝트 영향력은 그 명칭으로 짐작하실 수 있듯 프로젝트에 공헌한 성과들입니다. “AA 를 유지 보수했음.” 혹은 “BB 파트를 맡아 스프린트를 N 개 진행했음” 이런 것은 성과로 증명할 수 없는 내용입니다.

    • 내가 한 작업으로 인해 클릭률 혹은 방문율이 몇 퍼센트 올랐는지
    • 내가 한 작업으로 KPI를 어떻게 얼마나 달성했는지
    • 어떤 중요한 아이디어를 냈고 그것이 어떻게 반영됐는지
    • 퍼포먼스를 어떻게 얼마나 향상시켰는지(이게 제일 중요함)
    • 기타 프로젝트 자체에 기여한 성과

    Ref https://blog.shiren.dev/2022-03-07/?fbclid=IwAR0XXlVfXZZedoGLRnyfrDrdS4IOVZDIXDKgBIm6kDpBvmFGvZGKwtbE0Mk

    v8n

    메서드 체이닝 형식으로 유효성 검사를 할 수 있는 라이브러리다. custom한 rule을 만들어서 적용할 수도 있다. v8n의 풀네임은 당연하게도 ‘validation’이다.

    아래와 같이 작성한다. 상당히 fancy하다! (하지만 설레발 주도 개발의 위험성을 잊어서는 안될 것이다…)

    Copy
    v8n().string().first("H").last("o").test("Hello"); //true

    Ref https://imbrn.github.io/v8n/#what-s-v8n


    마무리

    대통령이 바뀌었다. 아직 취임을 안했으니 바뀐 건 아니지만… 앞으로의 5년이 어떻게 될지… 어떻게든 되겠지…

    작년 12월 1일에 시작했던 유캔두도 끝났다! 진짜로 내가 100일 연속 인증을 채웠다. 유캔두 주최자였던 웨지에게 기프티콘을 선물로 받았다. ㅋㅋㅋ 그리고 포코가 지정한 다음 유캔두 공동 주최자가 되었다. 😱 열흘 쉬고, 다시 시작!!!

    01

    대선일과 주말에 쉬는 동안 운전 연수를 받았더니 갑자기 자신감이 생겼다. 면허 딴지 4년 만에 처음 운전대를 잡아보는데, 시내도로와 고속도로도 타고, 꽉꽉 막힌 강남대로와 올림픽대로를 지나 양평까지 다녀왔다. 셀프 주유도 해봤다. 다음 달부터 쏘카 빌려서 드라이브 다녀야지 😎

    회사에서 맡은 티켓은 끝날 듯 끝나지 않고 있다. 수많은 코드리뷰를 받고, QA에서 잘못된 부분들을 검수받고 나니 아직 갈 길이 먼듯 싶다. 운전은 “초보인데 어쩌라고” 마인드로 했지만, 회사에서는 ‘신입이니까 그럴 수 있어’로 살 수만은 없다. 그런 시간은 아아아주 짧게 가지고, 신입이어도 능력을 낼 수 있는 팀원으로 거듭나기 위해 분발해야겠다. 너무 스트레스는 받지 말구 🤩


    Relative Posts:

    3월 3주차 기록

    March 19, 2022

    3월 첫주차 기록

    March 5, 2022

    zigsong

    지그의 개발 블로그

    RotateLinkImg-iconRotateLinkImg-iconRotateLinkImg-icon