ziglog

    Search by

    4월 4주차 기록

    April 24, 2022 • ☕️☕️☕️ 14 min read

    근데 이제 여름


    배워가기

    CSS list-style-position

    ::marker CSS 의사요소를 요소 내부에 둘지, 외부에 둘지 정할 수 있는 속성으로, display: list-item 속성이 있는 요소에 적용된다.

    Copy
    .element1 {
      list-style-position: inside;
    }
    
    .element2 {
      list-style-position: outside;
    }

    list-style-position 속성을 inside 으로 줄 경우 마커(list-style-type) 와 list-item 콘텐츠 사이 갭이 생기는데, 마커의 값이 Non-keyword(disc, circle, square 등)일 경우 간격을 직접 컨트롤 할 수 없다. 마커의 값이 keyword 인 경우 padding-inline-start 와 같은 속성으로 조절할 수 있다.

    Copy
    li {
      list-style: disc inside;
    
      &:before {
        content: "";
        margin-left: -0.5em; // 마커와 콘텐츠 사이 간격을 좁힘
      }
    }

    react-query

    react query 사용 시 SSR 서버에서 데이터 prefetch 후 컴포넌트에서 useQuery로 캐싱된 데이터를 가져올 때, 첫 로드 시에 저장된 캐시값이 없어 undefined가 리턴되는 경우가 있다. 이때 화면이 깜빡이는 현상이 발생한다.

    이는 SSR 서버에서 fetching된 데이터가 클라이언트에 값이 저장되어있지 않아서 발생하는 문제로, react-query에서 제안하는 2가지 해결 방법이 있다.

    첫 번째는 initialData를 활용하는 것이다.

    Copy
    export async function getServerSideProps() {
      const posts = await getPosts();
      return { props: { posts } };
    }
    function Posts(props) {
      const { data } = useQuery("posts", getPosts, { initialData: props.posts });
    }

    그러나 이 역시 마찬가지로 useQuery 초기 로드 시 data에 undefined가 리턴되어 해결되지 않는다.

    두 번째는 서버에서의 캐싱 쿼리를 유지하기 위해 dehydrate를 활용하는 방법이다.

    Copy
    export async function getServerSideProps() {
      const queryClient = new QueryClient();
      await queryClient.prefetchQuery("posts", getPosts);
      return {
        props: {
          dehydratedState: dehydrate(queryClient),
        },
      };
    }

    서버에서 prefetching한 정보가 클라이언트 초기 로드 시에도 캐시에 남아있게 되어, undefined가 반환되는 일 없이 올바른 data로 렌더링을 할 수 있다.

    중첩조건문 가드문으로 변경하기

    • 참인 경우와 거짓인 경우 모두 정상 동작으로 이어지는 형태라면 if/else 절을 사용한다.
      • if절과 else절에 똑같은 무게를 두어, 코드를 읽는 이에게 양 갈래가 똑같이 중요하다는 뜻을 전달한다.
    • 한쪽만 정상이라면 비정상 조건을 if에서 검사한 다음, 조건이 참이면(비정상이면) 함수에서 빠져나온다.
      • 이 형태를 흔히 가드문(gaurd clause)이라고 한다. 중첩 조건문을 가드문으로 바꾸는 핵심은 의도를 부각하는 데 있다.
      • 가드문은 “이건 이 함수의 핵심이 아니다. 이 일이 일어나면 무언가 조치를 취한 후 함수에서 빠져나온다“라고 이야기한다.

    Blob 객체

    Blob은 파일류의 불변하는 미가공 데이터를 나타낸다.

    사용자 시스템의 파일을 지원하기 위해 Blob 인터페이스를 상속해 기능을 확장한 것이 File 객체다. Blob에서 데이터를 읽는 방법 중 하나로 FileReader를 사용할 수 있다.

    File 객체의 실제 데이터에 접근하는 방법

    • Url로 접근하는 방법
      • createObjectURL() - 인자로 File 객체를 받으며, 해당 파일의 고유 URL 정보를 생성하고 반환한다.
      • revokeObjectURL() - 메모리 누수를 막기 위해 직접 해체해주어야 한다.
    • FileReader로 접근하는 방법
      • FileReader.readAsDataURL() - 파일을 읽고, result 속성에 파일을 나타내는 URL을 저장한다.
      • 비동기적으로 동작하기 때문에, 이벤트 핸들러를 등록해 사용한다.
        • FileReader.onload - 읽기 완료 성공 시 동작
        • FileReader.onloadend - 성공, 실패와 무관하게 읽기 완료 시 동작
        • FileReader.onerror - 읽는 도중 오류 발생 시 동작

    useImperativeHandle

    useImperativeHandleref를 사용할 때 부모 컴포넌트에 노출되는 인스턴스 값을 사용자화(customizes)한다. useImperativeHandle을 사용하여 자식 컴포넌트에서 특정 인스턴스를 만들어 부모 컴포넌트로 노출시킬 수 있다. forwardRef와 함께 사용한다.

    Copy
    function FancyInput(props, ref) {
      const inputRef = useRef();
    
      useImperativeHandle(ref, () => ({
        focus: () => {
          inputRef.current.focus();
        }
      }));
    
      return <input ref={inputRef} ... />;
    }
    FancyInput = forwardRef(FancyInput);

    위 예시에서 <FancyInput ref={inputRef} />를 렌더링한 부모 컴포넌트는 inputRef.current.focus()를 호출할 수 있다.

    Ref https://ko.reactjs.org/docs/hooks-reference.html#useimperativehandle

    TypeScript !

    TypeScript에서 !는 어설션 연산자(Non-null assertion operator), 또는 확정 할당 어설션(Definite Assignment Assertions)으로 사용된다.

    어설션 연산자는 피연산자가 null이 아니라고 컴파일러에게 전달, 일시적으로 null 제약조건을 완화한다.

    Copy
    function nonNullAssertionOperator(e: string | null): string {
      return e!;
    }

    확정 할당 어선셜은 이 변수에는 값이 무조건 할당되어 있다고 컴파일러에게 전달, 값이 없어도 사용할 수 있게 한다.

    Copy
    let notNull!: string;
    
    console.log(notNull);

    Ref https://velog.io/@kbm940526/Typescript-Non-null-assertion-operator-and-Definite-Assignment-Assertions

    useInfiniteQuery hook

    React Query의 useInfiniteQuery hook을 사용하면, page state를 따로 클라이언트 코드에서 관리할 필요가 없다.

    pageParams를 정의해놓으면, fetchNextPage()를 호출함으로써 API 호출 시 쿼리에 적절한 page 값을 삽입할 수 있다.

    검색기능 등 page를 0으로 초기화해야될 경우에도, query key의 의존성 배열에 검색어를 넣으면, 검색어가 변경될 때마다 page가 0으로 초기화되고 다시 카운팅되기 시작한다.

    prop으로 ref 넘겨주기?

    부모 컴포넌트에서 자식 컴포넌트로 ref를 넘길 때, custom prop으로 넘겨주는 방법

    Copy
    Component = ({ innerRef }) => <button ref={innerRef} />;

    그리고 forwardRef를 사용하는 방법이 있다.

    Copy
    Component = React.fowardRef((props, ref) => <button ref={ref} />);

    fowardRef는 부모 컴포넌트에서 input, form 등을 다루는 비제어 컴포넌트에 접근하기 위해 나온 것이다. 따라서 명시적으로 비제어 컴포넌트를 다룰 때에는 forwardRef를, 일반 제어 컴포넌트(함수 컴포넌트)를 다룰 때에는 prop으로 구분지어 다뤄주는 것이 좋다.

    DOMPurify

    XXS 공격을 방지하기 위해 HTML string에서 위험성 있는 코드를 제거하는 도구를 sanitizer라고한다.

    Copy
    DOMPurify.sanitizer(`<p>sample</p><script>alert('XSS공격')</script>`);

    위 코드의 실행 결과는 <p>sample</p>가 된다. 위험성 있는 script 태그를 삭제해준 것이다.

    innerHTML 대신 리액트의 dangerouslySetInnerHTML을 사용하고, DOMPurify.sanitizer를 거친 문자열을 넣으면 조금 더 안전하게 코드를 작성할 수 있다.

    서버사이드에서 사용하기 위한 isomorphic-dompurify라는 라이브러리도 있다. next를 사용한다면 서버에서 sanitizer를 한 문자열을 클라이언트에 전달해주는 방법도 좋다.

    Dialog vs Toast vs Snackbar

    • Dialog

      • 적은 양의 정보와 액션버튼으로 구성된다.
      • 이용자로부터 즉각적인 응답이 필요한 경우 적합하다.

    Dialog는 백드롭을 깔아서 다른 동작을 아예 막아버린다는 점이 다른 두 개, 특히 Snackbar와 다르다. 나만 보게 만드는 UX 패턴을 “모달리티“(modality)라고 한다.

    • Toast

      • 동작에 대한 간단한 피드백을 텍스트 형태로 제공하는 것이다.
      • 이용자를 방해하지 않고 메시지를 표시하고 시간이 지나면 자동으로 사라진다.
      • 간단하고 단순한 정보를 보여주는 데 적합하다.
    • Snackbar

      • 메시지를 통해 작업에 대한 간단한 피드백을 제공한다.
      • 이용자를 방해하지 않고, 시간이 지나면 자동으로 사라진다는 점에서 Toast와 비슷하다.
      • 단일 텍스트 액션을 통해 이용자와 상호작용을 할 수 있다.
      • 즉, Dialog보다 이용자에게 주는 영향은 적고, Toast보다는 더 커스텀하게 이용할 수 있는 것이 Snackbar이다.

    Dialog와 Toast 그 사이의 모든 경우에는 Snackbar를 이용하기 적합하다.

    Ref https://brunch.co.kr/@oemilk/91

    getServerSideProps의 props가 undefined일 때

    Next.js의 getServerSideProps에서 props 의 값으로 undefined가 있으면 JSON으로 직렬화되지 않아 에러가 발생할 수 있다.

    React Query의 prefetchInfiniteQuery를 사용할 경우 이전 페이지가 없기 때문에 pageParams는 0이 아닌 undefined로 던져진다. 이 영향으로 dehydrate(queryClient)의 결과물에 undefined가 포함되고, 이를 props로 넘기려 할 때 Next.js에서 undefined를 직렬화하지 못해 에러가 발생한다.

    이때는 stringify와 serialize 작업을 통해 undefined를 제거한 데이터를 props로 넘겨줘야 한다.

    Copy
    export const getServerSideProps: GetServerSideProps = async () => {
      const queryClient = new QueryClient();
      await queryClient.prefetchInfiniteQuery(...);
    
      return {
        props: {
          // pageParams 가 undefined로 정의될 때, undefined를 제거하기 위한 작업
          dehydratedState: JSON.parse(JSON.stringify(dehydrate(queryClient))),
        },
      }
    }

    event.nativeEvent.path

    event.nativeEvent.path로 이벤트 전파 순서를 확인할 수 있다. 이때 z-index도 이벤트 전파 순서에 영향을 준다.

    button > div > label > input(display: none)의 순서로 엘리먼트 계층이 구성되어 있을 때, button에서 발생한 이벤트가 div에 막혀 label까지 도달하지 못하는 경우가 있다. 이때 div보다 labelz-index를 더 높게하면 button의 이벤트가 label까지 도달한다.

    참고: interactive content 유저 인터랙션을 위해 만들어진 컨텐츠로, a, button, input 등이 이에 속한다.

    Ref https://html.spec.whatwg.org/#interactive-content

    npm ci vs npm install

    npm install이 실행되면 패키지를 설치할 때 package.json을 읽고 package-lock.json 파일도 업데이트한다. 이는 package.jsonpackage-lock.json을 수정할 수 있다는 것을 의미한다.

    반면에 npm cipackage-lock.json을 기반으로 패키지를 설치하며, 파일들을 수정하지 않는다. 만약에 node_modules가 존재하면 먼저 삭제한 뒤에 패키지를 설치한다.(ci 는 clean install의 줄임말이다.)

    크롬의 audio 자동플레이 정책

    크롬 뿐만 아니라 많은 웹브라우저들에는 비디오 및 오디오 자동 플레이 제한이 있다. 이는 사용자들이 인지 못한 채 소리가 나거나 동영상이 재생되는 경우를 막기 위함이다.

    audio가 자동플레이되려면 어떻게 해야 할까?

    • 유저 입장에서, 음소거 상태이거나 음량이 0이 아니어야 한다.
    • 유저가 확실히 의도를 가지고 재생을 시켜야 한다. (ex. 버튼 클릭, 탭)
    • 모바일에서는 홈화면에 추가, 데스크탑에서는 PWA가 설치되어야 한다.
    • 미디어 참여지수가 높아야 한다. (얼마나 사용자가 컨텐츠를 오래/자주 사용하는가의 비율)

    audio 자동플레이를 위해 공식 문서에서 권장되는 방법은 audio 재생 실패시 audio를 재생하는 버튼을 띄우는 것이다.

    ex) 주문접수페이지에서 소리 알람이 실패하면 새로운 주문이 왔습니다!와 같은 다이얼로그를 띄우고, 해당 다이얼로그에서 ‘확인’을 눌러야 소리가 재생되게끔 하는 방법이다.

    Ref https://developer.chrome.com/blog/autoplay/

    getDerivedStateFromProps

    getDerivedStateFromProps는 최초 마운트 시와 갱신 시 모두에서 render 메서드를 호출하기 직전에 호출된다. state를 갱신하기 위한 객체를 반환하거나, null을 반환하여 아무 것도 갱신하지 않을 수 있다. props 로 받아온 값을 state 에 넣어주고 싶을 때 사용한다.

    Copy
    static getDerivedStateFromProps(nextProps, prevState) {
      console.log("getDerivedStateFromProps");
      if (nextProps.color !== prevState.color) {
        return { color: nextProps.color };
      }
      return null;
    }

    그러나 state를 끌어오면 코드가 장황해지고, 이로 인하여 컴포넌트를 이해하기 어려워진다. React 팀에서 제안하는 대안들은 다음과 같다.

    • props 변화에 대응한 부수 효과를 발생시켜야 한다면 (예를 들어, 데이터 가져오기 또는 애니메이션), componentDidUpdate 생명주기를 대신해서 사용한다.
    • props가 변화했을 때에만 일부 데이터를 다시 계산 하고 싶다면, Memoization Helper를 대신해서 사용한다.
    • props가 변화할 때에 일부 state를 재설정 하고 싶다면, 완전 제어 컴포넌트 또는 key를 사용하는 완전 비제어 컴포넌트로 만들어서 사용한다.

    Refhttps://ko.reactjs.org/docs/react-component.html#static-getderivedstatefromprops https://react.vlpt.us/basic/25-lifecycle.html

    비밀번호 변경을 90일 주기로 요구하는 이유

    OWASP(The Open Web Application Security Project)가 처음 주장했던 내용으로, 옛날에 평균적으로 암호를 뚫는 데 90일정도 소요되었기 때문에 생긴 기준이다.

    그러나 최근에는 90일 기준이 전혀 도움이 되지 않는 구시대적 기준이라는 반대 진영도 있다. 해당 기준이 오늘날의 암호 추측 방해에 전혀 도움이 되지 않으며, 어차피 바꿔봤자 맨 뒤 1을 2로 바꿀 뿐이라는 것을 모두 알고 있기 때문이다!

    Ref https://www.sans.org/blog/time-for-password-expiration-to-die/

    리그레이션

    • 확인테스팅(Confirmation Testing)

      • (동의어: Re-Testing; 재테스팅)
      • 결함이 발견되고 수정된 후에 원래의 결함이 성공적으로 제거되었는지 확인하기 위해 다시 테스트 하는 것
    • 리그레이션 테스팅(Regression Testing)

      • 이미 테스트된 프로그램의 테스팅을 반복하는 것
      • 결함 수정 이후 변경의 결과로 도입되었거나 발견되지 않았던 또다른 결함을 발견할 수 있다.
      • 결함은 테스트 중인 소프트웨어에 존재하거나 다른 관련이 있는 또는 전혀 관련이 없는 소프트웨어 컴포넌트에 존재할 수 있다.
      • 소프트웨어 또는 환경이 변경되면 리그레이션 테스팅을 수행해야 한다.

    Ref https://cin-dy.tistory.com/33


    이것저것

    • a 태그의 href 속성은 hypertext reference의 약자다. ([에이치 레프]라고 읽는다.)

    • 무한스크롤로 구현된 페이지의 경우 추가된 내용만큼의 DOM 노드가 생기기 때문에, 노드가 생길수록 렌더링 시간도 선형적으로 증가하게 된다. 사용자가 보고 있는 부분에서 노출되고 있는 DOM 노드만 그려줌으로써 이런 경우를 해결할 수 있는데, 이를 ‘가상화’라고 한다. react-virtualized, react-window 등 가상화 라이브러리를 활용하면 무한 스크롤 시 발생하는 DOM 과부하를 방지할 수 있다.

    • useEffect의 cleanup 함수는 컴포넌트가 마운트 해제되는 때 뿐 아니라 컴포넌트가 렌더링이 실행될 때마다 실행된다. React가 다음 차례의 effect를 실행하기 전에 이전의 렌더링에서 파생된 effect를 정리하는 이유가 바로 이 때문이다. (Ref)

    • git switch c [브랜치명] - git checkout -b와 같이 새로운 브랜치를 만들고 해당 브랜치로 이동한다.

    • 스키마를 만든다는 건 어떤 알 수 없는 타입의 값과 대조를 해보겠다는 선언이다. 주로 런타임에 들어올 API 응답값과의 대조를 의미한다. assertion에 사용하지 않는다면 interface/type으로 선언하는 것이 좋다.

    • <input type="file"/>에서 파일 확장자 제한하는 방법

      • 이미지만 받기 "accept="image/*"
      • json만 받기 "accept="application/JSON"
    • @types/react 18ver에서 React.VFC가 deprecated되었다. 때문에 children이 필요하다면 VFC가 나오기 이전처럼 FC<PropsWithChildren<Props>>로 타입을 선언해줘야 한다.

    • .git/hooks/prepare-commit-msg로 커밋메세지를 원하는대로 작성할 수 있다. (Ref

    • 인덱스 시그니처를 사용할 때, key 값의 의미를 명확하게 담아내면 좋다.

      Copy
      // Bad
      { [key: string]: DisplayedProduct }
      
      // Good
      { [productId: string]: DisplayedProduct }
    • Node.js 18 버전에서는 fetchtest가 도입되었다.

    • PC의 mouseEnter(), mouseLeave() 이벤트에 대응되는 모바일 이벤트는 onTouchStart(), onTouchEnd()다.

    • JavaScript에서 객체가 비었는지 확인하는 방법

      Copy
      Object.keys(obj).length === 0 && obj.constructor === Object;

    기타

    React Hook Quiz

    여기서 풀어볼 수 있다. 역시 난 쪼렙이었다.

    Node.js 18

    다음 피쳐들이 새롭게 포함되었다! 원래 없었다니. ㅋㅋㅋ

    • fetch (experimental)
    • Web Streams API (experimental)
    • 기타 Global API
      • Blob
      • BroadcastChannel
    • Test runner module (experimental)

    새로운 API 말고도 향상된 부분들이 많다.

    • Toolchain and Compiler Upgrades
    • Build-time user-land snapshot (experimental)

    Ref https://nodejs.org/en/blog/announcements/v18-release-announce/


    마무리

    무사히 팀원 분들을 만났다! 모두 오랜만의 외출(?)에 차려입은 모습들을 보니 느낌이 새삼 달랐다. 주 1~2회 정도는 사무실 나가는 것도 좋을 것 같다. 그래서 집 와서 노트북 들고다닐 가방을 새로 장만했다. 사무실에 둘 키보드도 새로 샀다. (???)

    QA는 아직도 끝나지 않는다. QA해주시는 분들 정말 대단한 것 같다. 어떻게 그런 것까지 잡아내는지… 여기저기 급하게 고치면서 또 다른 버그들을 남겨두는 것 같아서 죄송하다 ㅠ 다음주에 무사히 배포가 나가면 좋겠다.

    주말에는 서울숲에 다녀왔다. 섬세이 테라리움이라는 체험전시도 갔다왔다. 와! 사람 진짜 많았다. 무슨 퇴근시간 강남만큼 많았다. 서울숲엔 튤립도 폈는데, 사실 슬슬 지고 있었다. 튤립은 원래 5월에 핀다는데, 진짜 기상이변이다.

    벌써 덥다. 막 아주 더운 건 아닌데, 이번 여름 큰일났구나, 느껴진다.


    Relative Posts:

    4월 5주차 기록

    April 29, 2022

    4월 3주차 기록

    April 15, 2022

    zigsong

    지그의 개발 블로그

    RotateLinkImg-iconRotateLinkImg-iconRotateLinkImg-icon