ziglog

    Search by

    1월 2주차 기록

    January 13, 2024 • ☕️☕️☕️ 13 min read

    배워가기


    jest에서 강제로 테스트 실패하게 하는 법

    fail() 함수를 호출하면 된다!

    Copy
    test('챗봇 정상 응답 시 도움이 되었는지 물어보는 응답을 반환한다,', async () => {
      customRender(<ChatContent onShowNewMessage={jest.fn()} onFocusChatInput={jest.fn()} />);
    
      userEvent.click(await screen.findByText('정산/부가세'));
    
      const categoryFaqList = undefined;
      if (!categoryFaqList) fail();});

    Fetch API의 Response 객체의 필드

    • bodyUsed - 응답에서 body가 사용되었는지 여부. response.json() (또는 .text(), .blob()) 를 호출하면 비로소 값이 true로 변경된다.
    • redirected - 응답이 redirect의 응답인지 여부
    • type - 응답의 타입(ex. basic, cors)

    Ref https://developer.mozilla.org/en-US/docs/Web/API/Response

    axios vs fetch API

    • axios는 프라미스를 한번만 이행하면 응답 데이터를 모두 얻을 수 있다
      • 상태: response.status
      • 헤더: response.headers
      • 본문: response.body
    • fetch는 두 개 프라미스가 이행되어야 객체를 모두 얻을 수 있다
      • 헤더를 모두 받으면 response 객체를 만든다
        • fetch() 함수가 반환한 Promise가 이행될 때
      • 브라우저가 바디를 모두 받으면 데이터를 만든다
        • 본문 조회 메소드가 반환한 Promise가 이행될 때

    Fetch API의 .json() 함수가 Promise를 반환하는 이유

    • 메인쓰레드를 블락하지 않기 위해서

    구현 세부사항을 테스트하는 것은 리팩토링에 취약하다

    • 거짓 음성, 거짓 양성
      • 거짓 음성(False Negative) - 실패해야 하는 상황에서 성공하면 잘못된 코드를 발견하지 못하고 나중에 버그로 발견됨
      • 거짓 양성(False Positive) - 운영 코드가 변경되었을 때 테스트가 실패하지 앟는 경우

    Ref Kent C Dodds.의 포스팅

    qs vs URLSearchParams

    qs

    자바스크립트로 URL의 query string을 파싱하기 위해 사용한다.

    문자열 형태의 쿼리 스트링을 객체의 형태로 변환할 때, qs의 parse() 함수를 사용한다.

    Copy
    const obj = qs.parse("mode=dark&active=true&nums=1&nums=2&nums=3");
    console.log(obj);
    // {
    //   mode: "dark",
    //   active: "true",
    //   nums: ["1", "2", "3"],
    // }

    반대로 자바스크립트 객체 형태의 쿼리 스트링을 문자열로 변환하고 싶을 때는 stringify() 함수를 사용한다.

    Copy
    const str = qs.stringify({
      mode: "dark",
      active: "true",
      nums: ["1", "2", "3"],
    });
    
    console.log(str); // "mode=dark&active=true&nums%5B0%5D=1&nums%5B1%5D=2&nums%5B2%5D=3"

    👩‍🏫 qs 라이브러리는 기본적으로 배열 값을 파라미터명[인덱스]=값의 형태로 변환한다.

    웹 표준 방식으로 동일한 파라미터명이 반복되게 하고 싶다면, 두 번째 인자로 arrayFormat 옵션의 값을 “repeat”로 주면 된다.

    Copy
    const str = qs.stringify(
      {
        mode: "dark",
        active: "true",
        nums: ["1", "2", "3"],
      },
      { arrayFormat: "repeat" }
    );
    
    // "mode=dark&active=true&nums=1&nums=2&nums=3"

    qs의 `stringify()“ 함수는 복잡한 형태의 객체를 다룰 수 있다.

    URLSearchParams

    웹 표준 API에서 제공하는 URLSearchParams를 사용하여 좀 더 안전하게 쿼리 스트링을 다룰 수 있게 되었다.

    URLSearchParams 객체의 생성자는 여러 형태의 값을 인자로 받을 수 있다.

    Copy
    new URLSearchParams([
      ["mode", "dark"],
      ["page", 1],
      ["draft", false],
      ["sort", "email"],
      ["sort", "date"],
    ]);

    쿼리 스트링을 문자열의 형태로 넘길 수도 있다.

    Copy
    new URLSearchParams("?mode=dark&page=1&draft=false&sort=email&sort=date");

    size 속성으로 쿼리 스트링에 얼마나 많은 매개변수가 들어있는지 알 수 있다.

    Copy
    const searchParams = new URLSearchParams("mode=dark&page=1&draft=false");
    searchParams.size; // 3

    쿼리 객체들을 문자열로 변환할 때는 toString()을 사용한다.

    Copy
    const searchParams = new URLSearchParams([
      ["mode", "dark"],
      ["page", 1],
      ["draft", false],
    ]);
    searchParams.toString(); // 'mode=dark&page=1&draft=false'

    append() 메서드를 이용하여 쿼리 스트링의 파라미터를 하나씩 추가할 수 있다.

    Copy
    const searchParams = new URLSearchParams();
    searchParams.append("mode", "dark");
    searchParams.append("page", 1);
    searchParams.toString(); // 'mode=dark&page=1'

    객체에 저장되어 있는 값을 읽을 때는 get()getAll() 메서드를 사용한다.

    Copy
    searchParams.get("mode"); // 'dark
    searchParams.getAll("mode"); // [ 'dark' ]
    searchParams.getAll("page"); // [ '1' ]

    URLSearchParams 객체에 저장되어 있는 파라미터는 for...of 문법을 사용하여 쉽게 순회할 수 있습니다.

    Copy
    for (const [key, value] of searchParams) {
      console.log(`${key}: ${value}`);
    }

    URL 객체의 search 속성에는 쿼리 스트링이 문자열로 저장되어 있고, searchParams 속성에는 쿼리 스트링이 URLSearchParams 객체로 저장되어 있다.

    Copy
    const url = new URL("https://example.org:8080/foo/bar?q=baz#bang");
    url.search; // '?q=baz'
    
    const searchParams = url.searchParams; // URLSearchParams {size: 1}
    searchParams.get("q"); // 'baz'
    
    searchParams.set("q", "updated");
    searchParams.append("r", 2);
    searchParams.append("r", false);
    
    url.toString(); // 'https://example.org:8080/foo/bar?q=updated&r=2&r=false#bang'

    함께 사용하면 유용하다! 😉

    Ref

    react component를 조건부로 wrapping하여 렌더링하는 방법

    Copy
    const ConditionalWrapper = ({ condition, wrapper, children }) =>
      condition ? wrapper(children) : children;
    Copy
    const ServiceCard = ({title, description, image, url}) => {
      return (
        <section>
          <ConditionalWrapper
            condition={url}
            wrapper={children => <a href={url}>{children}</a>}
          >
            <>
              <h2>{title}</h2>
              <p>{description}</p>
              <img src={image} alt={title} />
            </>
          </ConditionalWrapper>
        </section>
      )
    })

    Ref https://dev.to/dailydevtips1/conditional-wrapping-in-react-46o5

    changeset 패키지의 pre-release 기능

    • pre 모드 시작
      • pnpm changeset pre enter {tag}
    • pre 모드 종료
      • pnpm changeset pre exit

    Quality values

    쉼표로 구분된 목록에서 값의 우선순위를 표현하는 방법 (일부 HTTP 헤더 및 HTML에서 사용)

    활용 예시

    • Accept-Language: fr-CH, fr;q=0.9, en;q=0.8, de;q=0.7, *;q=0.5

    부연설명

    • 접미사 ';q=' 바로 뒤에 0에서 1사이의 값
    • 최대 3자리의 소수점 숫자
    • 가장 높은 값이 제일 높은 우선순위
      • 존재하지 않는 경우, 기본값은 1
    • ‘q-values’ 또는 ‘q-factors’라고도 부른다

    Safe Area

    Safe Area란, (모바일 기기에서) 화면 구성요소를 표현하기에 안전한 영역을 의미한다. ex. 상태바, 노치영역, (아이폰의) 하단 내비게이션 바 영역 등등

    안드로이드

    특별히 설정을 하지 않으면 대부분의 화면이 상태바 하단으로 잡히기 때문에 크게 신경쓸 일이 없다. 내비게이션 바는 애초에 화면 표시영역이 아니다.

    아이폰

    • 상/하단 모두 신경써야하는 경우가 있다.
    • 경우에 따라 iOS를 위한 (네이티브) UI 라이브러리는 표시영역과 상관없이 화면 구성요소 위치를 잡을 때 이를 고려해서 잡아주는 경우도 있다 (대표적으로, 네이버 지도 SDK에서 네이버 로고 아이콘 위치를 결정하는 로직)
    • 노치 디스플레이 여부를 파악하는 법 = 모델명으로 필터링한다
      • 노치 디스플레이(혹은 다이나믹 아일랜드)가 있는 아이폰은 무조건 홈버튼이 없어서, 이 여부로 하단 패딩을 줘도 된다.

    Safe Area 영역을 파악하는 파악하는 법 (웹에서)

    • CSS의 env(safe-area-inset-*)으로 수치를 알 수 있다.
      • fallback 값을 함께 설정할 수도 있다.

    아이폰 safe area inset의 대략적인 수치

    • 상단

      • 사각사각 디스플레이: 20 (상태바 영역정도)
      • 노치가 있는 경우: 44
      • 다이나믹 아일랜드: 59
    • 하단

      • 사각사각 디스플레이: 0
      • 나머지: 34

    이것저것 모음집


    • keydown 이벤트는 마우스클릭 뿐 아니라 tab할 때에도 트리거되기 때문에 조심해야 한다!
    • IME(Input Method Editor): 키보드 자판의 글쇠(키 하나하나)보다 더 많은 조합이 필요한 언어(ex 한글, 한자)의 경우 문자를 계산, 조합해서 입력해주는 시스템 소프트웨어 - ㅌ, ㅗ, ㅣ, ㄱ, ㅡ, ㄴ -> 퇴근 으로 만들어주는 역할을 한다.
    • storybook에 바로 vitest를 integrate 할 수 있는 addon(Ref)

    기타공유


    React Fiber

    React Fiber는 React 코어 알고리즘의 진행중인 작업이다. React Fiber의 목표는 애니메이션, 레이아웃, 제스처 등에서 사용성을 높이는 것이다. 그 주요 기능은 점진적 렌더링인데, 이는 렌더링 작업을 청크로 나누고 여러 프레임에 걸쳐 실행하는 것이다. 다른 주요 기능은 새로운 업데이트가 발생했을 때 작업을 일시중지, 중단 또는 재사용할 수 있게 하는 것이며, 서로 다른 타입의 업데이트 항목들에 대해 우선순위를 부여하고 새로운 동시성 모드를 지원하는 것이다.

    더 이상 ‘virtual DOM’이라는 표현은 쓰지 않는다. 근본적인 컨셉은 비슷하지만, 이를 구현하는 React의 기술 코어가 지난 3년 사이에 조금 바뀐 것 같다!

    Fiber는 컴포넌트의 입/출력 정보들을 가지고 있는 자바스크립트 객체다. 하나의 fiber는 스택 프레임에 해당하며, 동시에 Component의 인스턴스에 해당되기도 한다.

    Fiber에 해당하는 중요한 항목들

    • typekey - Fiber의 type과 key는 React 요소에 있어 그것과 같은 목적을 갖는다. (실제로 React 요소로부터 fiber가 생성될 때, 이 두 필드는 복사되어 전달된다). Fiber의 type은 그것이 상응하는 컴포넌트를 가리킨다. type과 함께, key는 fiber가 재사용될 수 있는지를 판별하기 위해 재조정 중에 사용된다.
    • childsibling - 이 필드들은 다른 fiber를 대상으로 하며, fiber의 재귀적 트리 구조를 가리킨다. 자식 fiber(a child fiber)는 컴포넌트의 render 함수가 반환하는 값에 해당한다. Sibling field는 render 함수가 여러개의 자식을 반환할 때 설명된다
    • return - return fiber는 현재 항목을 수행한 후 프로그램이 반환해야 하는 정보를 담고 있는 fiber다.
    • pendingPropsmemoizedProps - Fiber의 pendingProps는 함수의 실행 처음에 설정되고, memoizedProps는 실행 마지막에 설정된다. 들어오는 pendingProps가 memoizedProps와 같으면, 이걸 통해 fiber에게 이전 출력물이 재사용될 수 있음을 알리고, 불필요한 작업을 방지할 수 있다.
    • pendingWorkPriority - Fiber 작업의 우선 순위를 나타내는 숫자. ReactPriorityLevel (관련 설명 현재 삭제됨) 모듈은 다른 우선 순위 수준과 각각의 의미를 나열한다.
    • alternate
      • flush - fiber를 flush하는 것은 그 결과를 화면에 나타내는 것이다.
      • work-in-progress - 아직 완료되지 않은 fiber. 개념상으로 아직 반환되지 않은 스택 프레임을 의미한다.
    • output
      • host component - React 애플리케이션의 리프 노드. 렌더링 환경에 특정지어진다.
      • 모든 fiber는 결국 출력값을 갖지만 출력값은 host component를 통해 리프 노드에서만 생성된다.
      • 출력값은 rendering 환경에서 변경사항들을 flush 할 수 있게 렌더러에 전달되는 것이다. 출력물이 어떻게 생성되고 업데이트될지 결정하는 것은 렌더러의 역할이다.

    Ref

    특정 브라우저에 필요한 polyfill을 찾아주는 서비스

    Ref https://polyfill.io/

    타입스크립트를 지원하는 Tanstack Router

    react-router와 크게 다르지 않은 인터페이스를 제공하면서, 타입스크립트를 기본 지원하여 라우팅에도 타입 안정성을 제공한다.

    • RootRoute - root route를 생성한다.
    • Outlet - 잠재적으로 매칭될 자식 route들을 렌더하기 위해 사용된다. (현재 URL과 매칭될 경우)
    • Route - 자식 route들은 getParentRoute 메서드로 부모로부터 타입 정보와 런타임 정보를 받는다. 이를 통해 route 정의부의 타입 안정성을 보장하고, 순환 참조나 인스턴스화되지 않은 변수 등의 문제를 예방할 수 있다.
      Copy
      let rootRoute = new RootRoute()
      const indexRoute = new Route({ getParentRoute: () => rootRoute, path: '/' })
      const blogRoute = new Route({ getParentRoute: () => rootRoute, path: 'blog' })
      const postRoute = new Route({ getParentRoute: () => blogRoute, path: '$slug' })
    • 모든 route들이 생성되면, rootRoute.addChildren([...])route.addChildren([...])으로 route tree를 생성할 수 있다.
    • Router Type 등록 - 타입스크립트의 선언 병합을 사용하여 router의 타입을 재정의할 수 있다.
      Copy
      declare module '@tanstack/react-router' {
        interface Register {
          // This infers the type of our router and registers it across your entire project
          router: typeof router
        }
      }

    완성된 모습!

    Copy
    import { RootRoute, Route, Router } from '@tanstack/react-router'
    
    let rootRoute = new RootRoute()
    const indexRoute = new Route({ getParentRoute: () => rootRoute, path: '/' })
    const blogRoute = new Route({ getParentRoute: () => rootRoute, path: 'blog' })
    const postRoute = new Route({ getParentRoute: () => blogRoute, path: '$slug' })
    
    const routeTree = rootRoute.addChildren([
      indexRoute,
      blogRoute.addChildren([postRoute]),
    ])
    
    const router = new Router({ routeTree })
    
    declare module '@tanstack/react-router' {
      interface Register {
        router: typeof router
      }
    }

    Ref https://tanstack.com/router/v1/docs/overview

    2023 JavaScript Rising Stars

    • Bun이 정말 인기와 관심이 많구나!
    • Excalidraw, tldraw 등을 잘 활용하여 코드 아키텍처를 설명해보고 싶다.
    • Supabase 이름도 맘에 든다. 수년 전 잘 사용했던 Firebase만큼 기대된다.
    • Drizzle ORM도 이름 맘에 든다. Edge Runtime에서도 사용할 수 있다고?
    • HTMX은 문법 신기하면서도 호기심이 생긴다.

    Ref https://www.frontoverflow.com/magazine/3/2023%20JavaScript%20Rising%20Stars

    GCP, 타 플랫폼 마이그레이션 비용 제거

    타 플랫폼에도 압력이 될 수도 있다고(?)

    Ref https://cloud.google.com/blog/products/networking/eliminating-data-transfer-fees-when-migrating-off-google-cloud?hl=en

    中 50년 작동하는 동전 크기 원자력 전지

    중국 한 기업에서, 100mW 출력의 수명 50년짜리 원자력 전지를 동전보다 작은 크기로 소형화 및 모듈화 하는 데에 성공했다고 주장 및 특허 출원 신청 중. (주장이 사실이라면 기존의 어지간한 모바일 기기는 거뜬하고, 현대 인공위성이나 가전 등에도 무리없이 사용 가능)

    이르면 25년 제품화가 가능할 것이라고 주장.

    세기의 도박이라던 전지 혁명은 어떻게 될 것인가…

    Ref https://techrecipe.co.kr/posts/61854

    마무리


    12월 모든 약속이 1월로 밀려 완전 정신없는 한 주였다 🫠

    주말엔 찜질방 가서 힐링도 하고, 입주 두 달 차 대청소도 하고

    알차게 보내고 있는 1월이다


    Relative Posts:

    1월 3주차 기록

    January 19, 2024

    1월 첫주차 기록

    January 6, 2024

    zigsong

    지그의 개발 블로그

    RotateLinkImg-iconRotateLinkImg-iconRotateLinkImg-icon