ziglog

    Search by

    4월 5주차 기록

    April 29, 2022 • ☕️☕️☕️ 15 min read

    공포의 배포 주간


    배워가기

    캐시의 생명주기

    브라우저는 요청을 보내는 URL에 직접 리소스를 요청하기 전에, 메모리에 동일한 URL에 해당하는 리소스가 캐시되어있는지 먼저 확인한다. 이때, 배포 시 요청할 파일 뒤에 해시값을 붙여 넣으면 최초 실행시 해당 해시값과 일치하는 캐시를 찾지못해 서버에 다시 리소스 요청을 보내게 된다.

    ex) 처음 버전에서 .../a.png?v=1 파일을 요청하다가 새로운 버전에서 .../a.png?v=2로 요청한다면 해당 URL에 해당하는 파일이 캐싱되어있지 않다고 판단하여 서버에 다시 리소스를 요청한다.

    메모리에 리소스가 있으면 캐시의 유효성을 확인한다. 캐시가 유효하다면, 서버에 추가 요청을 보내지 않고 해당 리소스를 사용한다. 그렇기 때문에 서버에서 CDN Invalidation 등의 작업이 있더라도 유효한 캐시를 지우기는 어렵다.

    다만 캐시의 유효기간이 지났다고 해서 캐시가 완전히 사라지는 것은 아니고, 서버에 조건부 요청을 통해 캐시가 여전히 유효한지 재검증을 수행할 수 있다. 재검증 결과 브라우저가 갖고 있는 캐시가 유효하다면 서버는 304요청 을 내려준다. 해당 응답은 HTTP 본문을 포함하지 않기 때문에 매우 빠르게 내려받을 수 있다. 재검증 결과 캐시가 유요하지 않으면 서버는 적합한 상태코드와 함께 본문을 내려준다.

    no-cache vs no-store

    • no-cache
      • 캐시를 저장은 하지만, 사용할 때마다 서버에 재검증 요청을 보낸다.
      • max-age=0과 동일하다.
    • no-store
      • 캐시를 저장조차 하지 않는다.
      • 캐시를 해서는 안되는 리소스일 때 사용한다.

    max-age / s-maxage

    • max-age
      • Cache-Control의 헤더 값으로 max-age=<seconds>를 지정하면 해당 초만큼 캐시가 유효해진다.
      • Expires 헤더로 캐시 만료 시간을 정확히 지정할 수도 있다.
    • s-maxage
      • CDN같은 중간 서버에만 적용되는 max-age 값을 설정하기 위해 사용된다.

    TypeScript의 타입 공변성

    TypeScript는 선언된 함수의 타입보다 매개변수의 개수가 더 적은 함수도 할당이 될 수 있도록 설계되어 있다.

    예를 들어, JavaScript Array의 내장 메서드인 forEach의 콜백 함수에는 currentValue, index, array 총 3개의 인자를 넘겨주게 되어있다.

    Copy
    arr.forEach(callback(currentvalue[, index[, array]])[, thisArg])

    TypeScript에서 forEach 메서드를 사용할 때, 항상 3가지 인자를 명시해야 하는 번거로움이 있어, 일부 인자를 생략해도 되도록 설계했다고 한다.

    이는 TypeScript의 공변성(convariance) 과도 관계가 있다. (사실 이 블로그에서 공변성에 대한 주제를 몇 번이나 다뤘는데, 접할 때마다 헷갈리긴 한다…)

    Copy
    let array: Array<string | number> = [];
    let stringArray: Array<string> = [];
    array = stringArray; // ✅ OK
    stringArray = array; // 🚨 Error

    위 예제에서, arraystringArray를 할당하는 것은 가능하다. 그러나 stringArrayarray를 할당하는 것은 불가하다.

    string | numberstring을 포함하고 있으나, stringstring | number를 포함하지 않는다. stringstring | number의 서브타입이기 때문에, arraystringArray를 할당하는 것만 가능하다.

    이처럼 A ➡️ B일 때, X<A> ➡️ X<B>의 관계라면 X공변 타입이다.

    함수에서는 어떨까?

    Copy
    type Logger<T> = (param: T) => void;
    let log: Logger<string | number> = (param) => {
      console.log(param);
    };
    let logNumber: Logger<number> = (param) => {
      console.log(param);
    };
    log = logNumber; // 🚨 Error
    logNumber = log; // ✅ OK

    위 예제는 Array의 예제와는 정확히 반대로 동작한다. logNumberlog를 할당할 수는 있지만, 반대로 loglogNumber를 할당할 수 없다. numbersring | number의 서브타입임에도 불구하고, Logger<string | number>가 오히려 Logger<number>의 서브타입이 되는 셈이다.

    이처럼 A ➡️ B일 때, X<B> ➡️ X<A>의 관계라면 X반공변 타입이다.

    TypeScript에서 제공한 타입을 매개변수로 사용하는 함수 타입은 반변한다.

    Copy
    type NumberParser<T> = (v: T) => number;

    따라서 앞서 설명했던 것처럼, forEach의 콜백에 모든 인자를 넣지 않도 정상적으로 동작한다.

    Ref https://seob.dev/posts/공변성이란-무엇인가/ https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach

    useState lazy initialization

    useState에 직접적인 값 대신에 함수를 넘기는 것을 게으른 초기화(Lazy initial state) 라고 한다. 넘긴 함수는 넘기면 첫 렌더링 시에만 실행되고, 리렌더링 시에는 무시된다. 초기값이 복잡한 연산을 요할 때 사용하면 유용하다.

    Copy
    const [state, setState] = useState(() => {
      const initialState = someExpensiveComputation(props);
      return initialState;
    });

    Ref https://reactjs.org/docs/hooks-reference.html#lazy-initial-state https://yceffort.kr/2020/10/IIFE-on-use-state-of-react

    React ComponentProps

    리액트에서 특정 컴포넌트 A를 서로 다른위치에 여러번 사용할 때 코드간 중복을 피하기위해 HOC 로 전환해서 사용할 때가 종종 있다.

    이 때 A 의 props 타입 인터페이스를 명시적으로 정의하는 대신에 ComponentProps 를 사용하여 정의할 수 있다.

    Copy
    const HOC = (props: React.ComponentProps<typeof AtomicComponent>) => {
      <AtomicComponent {...props} />;
    };

    함수형 컴포넌트 vs 함수 컴포넌트

    함수형 컴포넌트(Functional Component)함수 컴포넌트(Function Component) 중 무엇이 옳은 말일까?

    React에서는 처음에 Functional Component라는 네이밍을 사용하다가 “함수형 컴포넌트를 사용하면 함수형 프로그래밍 방법으로 개발하는 것”이라는 오해의 소지가 생길 수 있는 여지가 생김에 따라 Functional Component란 네이밍이 1년 가량 유지되다가 Function Component로 이름이 바뀌게 되었다.

    👩‍🏫 결론: 함수형 컴포넌트는 함수형 프로그래밍이라는 오해의 소지를 만들 수 있기 때문에 함수 컴포넌트 (Function Component) 라는 네이밍을 사용하는 것이 좋다.

    Ref https://velog.io/@nsunny0908/함수-컴포넌트와-함수형-컴포넌트가-같은-말이라고-생각하시나요

    CSS animation-fill-mode

    css animation의 animation-fill-mode 속성은 애니메이션의 시작 또는 끝 상태를 계속 유지하고자 할 때 사용한다.

    예를 들어, 어떤 요소에 대하여 from transform(20, 20) to transform(50, 50) 으로 애니메이션을 주고 싶다고 하자.

    animation-fill-mode 속성을 주지 않는다면 해당 요소는

    1. transform(0, 0) 상태
    2. 깜빡거리면서 (20, 20)으로 바뀜
    3. (50, 50)으로 바뀜
    4. 다시 (0, 0)으로 돌아옴

    위 순서로 바뀔 것이다.

    animation-fill-mode의 값에 따라 요소의 상태가 어떻게 변하는지 살펴보자.

    • animation-fill-mode: forwards - 애니메이션의 마지막 상태를 유지할 수 있다. 즉 애니메이션이 끝난 후 마지막 transform(50, 50) 상태가 유지된다.
    • animation-fill-mode: backwards - 요소 즉시 애니메이션의 첫 상태를 적용할 수 있다. 즉 요소는 처음부터 transform(20, 20) 상태를 가지게 된다.
    • animation-fill-mode: both - 애니메이션의 첫 상태와 마지막 상태를 모두 유지한다.

    node.js의 http(s) 기능

    node에서는 기본으로 제공되는 모듈인 http(s)를 활용해 GET 요청을 할 수 있다.

    이때 콜백 함수의 param으로 요청의 response가 들어오고, 데이터는 여러 chunk 파일로 나뉘어져서 전송된다. (아마 전송되는 데이터가 세그먼트 단위로 나뉘어져서 전달되기 때문에 그런 것 같다.)

    chunk 데이터는 toString('utf8')을 통해 우리가 알아볼 수 있는 일반 문자열로 변환시킬 수 있다.

    Copy
    https.get(url, (res) => {
      let fullChunk = '';
    
      // 청크 데이터가 여러 번에 걸쳐서 들어온다
      res.on('data', (chunk) => {
        fullChunk += chunk;
      })
    
      res.on('end', () => {
        const data = fullChunk.toString('utf8');
        const jsonData = JSON.parse(data);
      })

    enum 권한 열거형의 형태

    enum의 권한 열거형에는 주로 0,1,2,4,8과 같은 2진수가 사용된다.

    이는 비트 플래그를 효율적으로 활용하기 위한 방법이다.

    메모리의 최소 크기 단위는 1byte(8bit) 이상이다. 8비트는 8가지 상태를 저장할 수 있기 때문에, 비트 플래그를 잘 활용하면 1바이트를 사용해서 1가지 상태만 저장하는 bool 자료형보다 훨씬 효율적으로 사용할 수 있다.

    아래의 예시에서 바이트의 개별 비트를 비트 플래그라고 한다.

    Copy
    enum Permissions {
      None   = 0,
      Read   = 1,
      Write  = 2,
      Delete = 4
    }
    Permissions.Read   === 1 === 00000001
    Permissions.Write  === 2 === 00000010
    Permissions.Delete === 4 === 00000100
    
    ReadAndWritePermission = Permissions.Read | Permissions.Write
    
    ReadAndWritePermission === 00000011 (read & write 권한)

    위처럼 권한과 관련해서 1byte로 다양한 경우의 수를 처리할 수 있기 때문에 enum의 값에는 0,1,2,4,8 … 의 형태가 많이 사용한다.

    쿠키와 SameSite

    서버의 응답헤더 set-cookie 값에 Domain이 설정된 경우 해당 도메인을 대상으로 한 요청에만 쿠키가 전송된다.

    Copy
    set-cookie: foo=bar; Domain=localhost

    하지만 쿠키에 별도로 설정을 하지 않는다면 크롬(samesite=lax)을 제외한 브라우저들은 모든 HTTP 요청에 대해 쿠키를 전송하게 되는데, 이때 사용자가 접속한 페이지와 다른 도메인으로 전송하는 쿠키인 서드 파티 쿠키를 이용해서 csrf 와 같은 공격에 노출될 수 있다.

    SameSite 쿠키는 서드파티 쿠키의 보안적 문제를 해결하기 위해 존재한다. 크로스사이트로 전송하는 요청의 경우 쿠키의 전송에 제한을 건다.

    SameSite에 들어갈 수 있는 구체적인 값은 다음과 같다.

    • None - SameSite 를 설정하지 않았을 때와 같은 값이며, 크로스 사이트 요청의 경우에도 항상 전송된다. 즉 서드파티 쿠키도 전송된다.

      • 크롬의 경우 SameSite = None을 설정하려면 Secure(https가 적용된 요청에만 쿠키가 전송되는 옵션)를 추가해줘야한다.
    • Strict - 크로스 사이트 요청에는 전송되지 않고 동일 도메인일 경우에만 전송된다. (퍼스트 파티 쿠키일 때에만 전송)

    • Lax - Strict에 비해 상대적으로 완화된 정책으로 대체로 서드파티 쿠키는 전송되지 않지만 몇가지 예외적인 요청에는 전송된다.

    Top Level Navigation

    Top Level Navigation은 <a> 앵커 태그를 클릭하거나, window.location.replace 등의 동작을 통해 자동으로 이뤄지는 이동을 의미한다. 302 리다이렉트를 이용한 이동도 포함한다.

    <iframe>이나 <img>는 navigation이라 할 수 없으며, <iframe> 안에서의 페이지 이동도 역시 top level이라 할 수 없기 때문에 SameSite = Lax일 때 쿠키가 전송되지 않는다.

    클래스 컴포넌트에 custom hook 적용하기

    …이런 걸 해내는 사람이 있다. 그 사람이 내 친구이자 동료라니

    useToast라는 custom hook을 만들고, 아래와 같은 클래스 컴포넌트가 있다고 하자.

    Copy
    export type Props = ReturnType<typeof useToast>;
    
    class ToastProvider extends React.Component<React.PropsWithChildren<Props>> {
      render() {
        const { children, title, text, theme, isVisible } = this.props;
    
        return (
          <>
            <ToastBase
              title={title}
              text={text}
              theme={theme}
              isVisible={isVisible}
            />
            {children}
          </>
        );
      }
    }

    여기서 wrapper HoC로 클래스 컴포넌트를 감싸는 형태다.

    Copy
    const withHasMounted = (
      Comp: React.ComponentClass<React.PropsWithChildren<Props>>
    ) => {
      return ({ ...props }) => {
        return <Comp {...useToast()} {...props} />;
      };
    };
    
    export default withHasMounted(ToastProvider);

    withHasMounted 함수는 리턴값으로 함수 컴포넌트를 가지는 함수를 반환한다. 클래스 컴포넌트에서 직접 hook을 사용할 수 없으므로, HoC에서 hook을 사용하고, hook의 반환 결과를 클래스 컴포넌트에 넘겨주는 것이다.

    생각만 해봤지만… 직접 만든 것을 보니 괜찮은 것 같기도… 👀

    Ref https://zereight.tistory.com/1175

    react-query의 isLoading vs isFetching

    react-query에는 isLoading, isFetching 두 가지의 boolean 값이 있는데, 이 두 값에는 차이가 존재한다.

    • isLoading - 사전에 데이터가 없을 때, 값을 불러올 당시에 true로 변경된다.
    • isFetching - 데이터 요청 작업이 있기만 하면, true로 변경된다.
    • refetch() - 이미 데이터가 있는 경우이기 때문에 query는 success 상태다. 따라서 refetch의 경우 isLoading의 값은 true로 변경되지 않는다. stale-while-revalidate 의 원리다.

    CSS appearance 속성

    얼마나 생소하면… 검색하면 MDN보다 블로그가 먼저 나온다~!!

    운영체제 및 브라우저에 기본적으로 설정되어 있는 UI Control의 native appearance를 바꾸기 위한 속성이다.

    Copy
    div {
      appearance: button;
      -moz-appearance: button; /* Firefox */
      -webkit-appearance: button; /* Safari and Chrome */
    }

    아래 케이스들에서 사용할 수 있다.

    • iOS의 폼 요소들에 부여되어 있는 둥근 테두리값이나 그림자 효과를 제거할 때
    • webkit 계열의 브라우저의 type="search" 필드의 둥근 테두리 값이나 reset 효과를 나타내는 버튼을 삭제하고 싶을 때
    • select 필드의 기본 화살표 모양을 삭제하거나 대체할 때

    Ref https://developer.mozilla.org/en-US/docs/Web/CSS/appearance https://webdir.tistory.com/430

    🚨 property does not exist in type union

    Copy
    const conditional = true;
    const value = conditional ? p.payload.value : p.value;

    위 코드에서

    property does not exist in type union 에러가 발생할 때는, 객체 프로퍼티의 소유 여부로 검사해주면 된다~!

    Copy
    const value = "payload" in p ? p.payload.value : p.value;

    모바일 디바이스 픽셀은 어려워

    픽셀에는 물리 픽셀논리 픽셀 이 있다.

    모바일 디바이스는 물리적 해상도와 논리적 해상도가 다르다. 이는 모바일 프로덕트를 벡터 기반 프로그램으로 사용해야 하는 근거가 된다.

    🌻 물리적(Physical) 해상도 물리 픽셀 = 디바이스 픽셀로, 단말이 실제로 표현할 수 있는 물리적인 화소 기본 단위를 가리킨다.

    예) 사진 기본 크기

    🌻 논리적(Logical) 해상도 논리 픽셀 = CSS 픽셀로, 디바이스 픽셀과 무관하게 HTML/CSS에서 논리적으로 표현할 수 있는 화소 기본 단위를 가리킨다.

    예) 사진 출력 크기

    프론트엔드 개발자의 고충을 덜기 위해, 디바이스의 픽셀을 확인할 수 있는 사이트가 있다. (그러나 실제 고충이 덜어진 건진 모르겠다.)

    사실 아티클을 읽어봐도 무슨 말인지 잘 모르겠다. 역시 그때그때 대응법이 최고다.

    Ref https://velog.io/@productuidev/%ED%95%B4%EC%83%81%EB%8F%84]


    이것저것

    • parameter vs argument
      • parameter: 함수를 정의할 때 전달 받을 매개변수
      • argument: 함수를 호출할 때 입력하는 전달인자
    • node.js 의 경우 버전에 따른 성능차도 존재하기 때문에 해당 결과를 맹목적으로 신뢰할 수는 없다. 단일 내장함수, prototype 메소드 뿐 아니라 종종 쓰이는 로직에 대해(배열 중복체크 등) 코드를 모두 작성하고 커밋하기 전에 한번은 비슷한 로직과의 퍼포먼스는 어떤지 확인하는 것이 나중에 같은 상황에서의 효율적인 코드 선택이 가능하다.
    • 드래그가 안된다면 상위 컴포넌트 스타일에 user-select: none이 걸려있지 않은지 확인해보자.
    • 배열 타입의 요소 타입을 알아올 때 a[0] 대신 a[number]를 사용해도 된다.
    • 클립보드에 복사하기 기능
      • Clipboard.writeText() web API를 사용한다.
      • ex) navigator.clipboard.writeText(message)
    • SAP 시스템이란, 비즈니스 프로세스를 위한 포괄적인 솔루션으로, 회계 및 재무 결산을 위한 용도로 사용되는 시스템이다.
    • ad-hoc 테스트란 비공식적이고, 자의적이고 임의적인 테스트를 의미한다. 실제 업무에서 설계한 TC를 모두 마친 후, 말그대로 예상 결과를 정의하지 않고 테스트를 하다보면 소프트웨어의 특성상 결함이 발생할 수도 있다. (Ref)

    기타

    m1 맥북의 고성능 모드

    m1 맥북에서는 고성능 모드를 켤 수 있다.

    MeasureThat.net

    MeasureThat.net은, JavaScript 코드 성능을 온라인으로 측정할수 있는 벤치마크 툴이다. prototype 메소드 중에서 어떤 메소드가 효율적인지 빠르게 확인하기 위해서 사용할 수 있다.

    ex) Array.prototype → indexOf vs includes vs some

    State of frontend 2022

    원래 UI가 이랬나? 🤔 낯설다. 그리고 결정적으로, 재미가 없어졌다! 그냥 블로그 형식의 줄글들만… 🤮

    Ref https://tsh.io/state-of-frontend/?utm_campaign=SOFE2022&utm_content=205764693&utm_medium=social&utm_source=twitter&hss_channel=tw-1672399308

    npm을 효율적으로 사용하기 위한

    구글에서 새로 개발한 wireit이라는 도구다. 로고가 힙하다. 세상이 발전하는구나. 근데… [와이레]라고 읽는 건가…? 와이라노… 와이라노…

    아래 feature들을 자랑하고 싶다고 한다.

    • 기존에 알고 있는 npm run 명령어를 사용할 수 있다.
    • npm scripts들 사이의 의존성을 자동으로 병렬적으로 실행해준다.
    • 모든 script를 watch하며, 변화가 발생했을 때 지속적으로 재실행(re-run)한다.
    • 이미 최신 상태의 scripts는 스킵한다.
    • 로컬과 github에서 모두 cache 결과를 확인할 수 있다 (무료로!)
    • 단일 패키지, npm 워크스페이스, 그리고 다른 monorepo들과 함께 잘 동작한다.

    Ref https://github.com/google/wireit

    Google I/O

    오~ 왠지 구글에서 하는 건 다 간지나 보인다. 문화 사대주의

    Ref https://io.google/2022/


    마무리

    주말에 놀고 와서 써야징

    놀고..와서.. 인생이 뜻대로 되는 일은 없다. 거의 없다! 3년 전에 두 번 다쳐서 반깁스까지 했던 발목을 기어코 또 다치고 말았다~! 언제 또 다치나 했드만 바로 이번에! 🤩 주말이라 병원도 못가고… 셀프로 붕대 툴툴 감고 쓰레빠 끌고 스터디 첫 모임 다녀왔다 ㅎㅎ 다치는 데 능숙해버린 스물 여섯살.

    그리고 주말에 다시는 드라이브를 가지 않을 것 같다~! 1시간 반 거리를 3시간, 4시간 걸려서 왔당 ㅎㅎ 왕복 140km를 7시간 동안 달렸다고 하면 누가 믿으리오… 시위까지 겹쳐서 교통 통제 당하고 해탈해서 중간에 길도 잘못 들어서 울 뻔했지만 ‘나는 개짱쎈 어른이다’라고 맘을 다잡으며 다친 발을 이끌고 무사히 귀가했다… 남자친구는 그래도 왼발을 다쳐서 다행이라며 (운전은 오른발로 하니까) 🙄

    벌써 까마득해져버렸지만, 공포의 새벽 배포가 있었다. 서비스를 하는 개발자의 숙명인가보다. 프론트 개발자는 조금 덜하긴 하겠지만, 그래도 정말 오랜만에 야식까지 먹고 뺨 때리며 대기탔다. 새벽에 다같이 잠긴 목소리로 대화하는 경험도 (가끔은) 추억일 것 같다. 건강 쪼렙이의 바이오리듬이 대붕괴될 것이라고 생각했으나, 대충 자고 다음날 꽤나 멀쩡했다. 꾸준한 운동으로 체력이 정말 나아진건가?

    그치만 이런 긍정적 신호를 세상이 두고볼 리가 없지. 다시 발목 안녕~


    Relative Posts:

    5월 1주차 기록

    May 7, 2022

    4월 4주차 기록

    April 24, 2022

    zigsong

    지그의 개발 블로그

    RotateLinkImg-iconRotateLinkImg-iconRotateLinkImg-icon