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
max-age=0
과 동일하다.max-age / s-maxage
max-age=<seconds>
를 지정하면 해당 초만큼 캐시가 유효해진다.TypeScript는 선언된 함수의 타입보다 매개변수의 개수가 더 적은 함수도 할당이 될 수 있도록 설계되어 있다.
예를 들어, JavaScript Array의 내장 메서드인 forEach
의 콜백 함수에는 currentValue
, index
, array
총 3개의 인자를 넘겨주게 되어있다.
arr.forEach(callback(currentvalue[, index[, array]])[, thisArg])
TypeScript에서 forEach
메서드를 사용할 때, 항상 3가지 인자를 명시해야 하는 번거로움이 있어, 일부 인자를 생략해도 되도록 설계했다고 한다.
이는 TypeScript의 공변성(convariance) 과도 관계가 있다. (사실 이 블로그에서 공변성에 대한 주제를 몇 번이나 다뤘는데, 접할 때마다 헷갈리긴 한다…)
let array: Array<string | number> = [];
let stringArray: Array<string> = [];
array = stringArray; // ✅ OK
stringArray = array; // 🚨 Error
위 예제에서, array
에 stringArray
를 할당하는 것은 가능하다. 그러나 stringArray
에 array
를 할당하는 것은 불가하다.
string | number
는 string
을 포함하고 있으나, string
은 string | number
를 포함하지 않는다. string
이 string | number
의 서브타입이기 때문에, array
에 stringArray
를 할당하는 것만 가능하다.
이처럼 A
➡️ B
일 때, X<A>
➡️ X<B>
의 관계라면 X
는 공변 타입이다.
함수에서는 어떨까?
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
의 예제와는 정확히 반대로 동작한다. logNumber
에 log
를 할당할 수는 있지만, 반대로 log
에 logNumber
를 할당할 수 없다. number
가 sring | number
의 서브타입임에도 불구하고, Logger<string | number>
가 오히려 Logger<number>
의 서브타입이 되는 셈이다.
이처럼 A
➡️ B
일 때, X<B>
➡️ X<A>
의 관계라면 X
는 반공변 타입이다.
TypeScript에서 제공한 타입을 매개변수로 사용하는 함수 타입은 반변한다.
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 initializationuseState
에 직접적인 값 대신에 함수를 넘기는 것을 게으른 초기화(Lazy initial state) 라고 한다. 넘긴 함수는 넘기면 첫 렌더링 시에만 실행되고, 리렌더링 시에는 무시된다. 초기값이 복잡한 연산을 요할 때 사용하면 유용하다.
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
리액트에서 특정 컴포넌트 A를 서로 다른위치에 여러번 사용할 때 코드간 중복을 피하기위해 HOC 로 전환해서 사용할 때가 종종 있다.
이 때 A 의 props 타입 인터페이스를 명시적으로 정의하는 대신에 ComponentProps 를 사용하여 정의할 수 있다.
const HOC = (props: React.ComponentProps<typeof AtomicComponent>) => {
<AtomicComponent {...props} />;
};
함수형 컴포넌트(Functional Component) 와 함수 컴포넌트(Function Component) 중 무엇이 옳은 말일까?
React에서는 처음에 Functional Component라는 네이밍을 사용하다가 “함수형 컴포넌트를 사용하면 함수형 프로그래밍 방법으로 개발하는 것”이라는 오해의 소지가 생길 수 있는 여지가 생김에 따라 Functional Component란 네이밍이 1년 가량 유지되다가 Function Component로 이름이 바뀌게 되었다.
👩🏫 결론: 함수형 컴포넌트는 함수형 프로그래밍이라는 오해의 소지를 만들 수 있기 때문에 함수 컴포넌트 (Function Component) 라는 네이밍을 사용하는 것이 좋다.
Ref https://velog.io/@nsunny0908/함수-컴포넌트와-함수형-컴포넌트가-같은-말이라고-생각하시나요
animation-fill-mode
css animation의 animation-fill-mode
속성은 애니메이션의 시작 또는 끝 상태를 계속 유지하고자 할 때 사용한다.
예를 들어, 어떤 요소에 대하여 from transform(20, 20) to transform(50, 50)
으로 애니메이션을 주고 싶다고 하자.
animation-fill-mode
속성을 주지 않는다면 해당 요소는
transform(0, 0)
상태(20, 20)
으로 바뀜(50, 50)
으로 바뀜(0, 0)
으로 돌아옴위 순서로 바뀔 것이다.
animation-fill-mode
의 값에 따라 요소의 상태가 어떻게 변하는지 살펴보자.
animation-fill-mode: forwards
- 애니메이션의 마지막 상태를 유지할 수 있다. 즉 애니메이션이 끝난 후 마지막 transform(50, 50)
상태가 유지된다.animation-fill-mode: backwards
- 요소 즉시 애니메이션의 첫 상태를 적용할 수 있다. 즉 요소는 처음부터 transform(20, 20)
상태를 가지게 된다.animation-fill-mode: both
- 애니메이션의 첫 상태와 마지막 상태를 모두 유지한다.node에서는 기본으로 제공되는 모듈인 http(s)
를 활용해 GET 요청을 할 수 있다.
이때 콜백 함수의 param으로 요청의 response가 들어오고, 데이터는 여러 chunk 파일로 나뉘어져서 전송된다. (아마 전송되는 데이터가 세그먼트 단위로 나뉘어져서 전달되기 때문에 그런 것 같다.)
chunk 데이터는 toString('utf8')
을 통해 우리가 알아볼 수 있는 일반 문자열로 변환시킬 수 있다.
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의 권한 열거형에는 주로 0,1,2,4,8과 같은 2진수가 사용된다.
이는 비트 플래그를 효율적으로 활용하기 위한 방법이다.
메모리의 최소 크기 단위는 1byte(8bit) 이상이다. 8비트는 8가지 상태를 저장할 수 있기 때문에, 비트 플래그를 잘 활용하면 1바이트를 사용해서 1가지 상태만 저장하는 bool 자료형보다 훨씬 효율적으로 사용할 수 있다.
아래의 예시에서 바이트의 개별 비트를 비트 플래그라고 한다.
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 … 의 형태가 많이 사용한다.
서버의 응답헤더 set-cookie
값에 Domain
이 설정된 경우 해당 도메인을 대상으로 한 요청에만 쿠키가 전송된다.
set-cookie: foo=bar; Domain=localhost
하지만 쿠키에 별도로 설정을 하지 않는다면 크롬(samesite=lax
)을 제외한 브라우저들은 모든 HTTP 요청에 대해 쿠키를 전송하게 되는데, 이때 사용자가 접속한 페이지와 다른 도메인으로 전송하는 쿠키인 서드 파티 쿠키를 이용해서 csrf 와 같은 공격에 노출될 수 있다.
SameSite
쿠키는 서드파티 쿠키의 보안적 문제를 해결하기 위해 존재한다. 크로스사이트로 전송하는 요청의 경우 쿠키의 전송에 제한을 건다.
SameSite
에 들어갈 수 있는 구체적인 값은 다음과 같다.
None
- SameSite
를 설정하지 않았을 때와 같은 값이며, 크로스 사이트 요청의 경우에도 항상 전송된다. 즉 서드파티 쿠키도 전송된다.
SameSite = None
을 설정하려면 Secure(https가 적용된 요청에만 쿠키가 전송되는 옵션)를 추가해줘야한다.Strict
- 크로스 사이트 요청에는 전송되지 않고 동일 도메인일 경우에만 전송된다. (퍼스트 파티 쿠키일 때에만 전송)
Lax
- Strict
에 비해 상대적으로 완화된 정책으로 대체로 서드파티 쿠키는 전송되지 않지만 몇가지 예외적인 요청에는 전송된다.
Top Level Navigation은 <a>
앵커 태그를 클릭하거나, window.location.replace
등의 동작을 통해 자동으로 이뤄지는 이동을 의미한다. 302 리다이렉트를 이용한 이동도 포함한다.
<iframe>
이나 <img>
는 navigation이라 할 수 없으며, <iframe>
안에서의 페이지 이동도 역시 top level이라 할 수 없기 때문에 SameSite = Lax
일 때 쿠키가 전송되지 않는다.
…이런 걸 해내는 사람이 있다. 그 사람이 내 친구이자 동료라니
useToast
라는 custom hook을 만들고, 아래와 같은 클래스 컴포넌트가 있다고 하자.
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로 클래스 컴포넌트를 감싸는 형태다.
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
, isFetching
두 가지의 boolean 값이 있는데, 이 두 값에는 차이가 존재한다.
isLoading
- 사전에 데이터가 없을 때, 값을 불러올 당시에 true로 변경된다.isFetching
- 데이터 요청 작업이 있기만 하면, true로 변경된다.refetch()
- 이미 데이터가 있는 경우이기 때문에 query는 success
상태다. 따라서 refetch의 경우 isLoading
의 값은 true로 변경되지 않는다. stale-while-revalidate 의 원리다.얼마나 생소하면… 검색하면 MDN보다 블로그가 먼저 나온다~!!
운영체제 및 브라우저에 기본적으로 설정되어 있는 UI Control의 native appearance를 바꾸기 위한 속성이다.
div {
appearance: button;
-moz-appearance: button; /* Firefox */
-webkit-appearance: button; /* Safari and Chrome */
}
아래 케이스들에서 사용할 수 있다.
type="search"
필드의 둥근 테두리 값이나 reset 효과를 나타내는 버튼을 삭제하고 싶을 때select
필드의 기본 화살표 모양을 삭제하거나 대체할 때Ref https://developer.mozilla.org/en-US/docs/Web/CSS/appearance https://webdir.tistory.com/430
const conditional = true;
const value = conditional ? p.payload.value : p.value;
위 코드에서
property does not exist in type union
에러가 발생할 때는, 객체 프로퍼티의 소유 여부로 검사해주면 된다~!
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]
user-select: none
이 걸려있지 않은지 확인해보자.a[0]
대신 a[number]
를 사용해도 된다.Clipboard.writeText()
web API를 사용한다.navigator.clipboard.writeText(message)
m1 맥북에서는 고성능 모드를 켤 수 있다.
MeasureThat.net은, JavaScript 코드 성능을 온라인으로 측정할수 있는 벤치마크 툴이다. prototype 메소드 중에서 어떤 메소드가 효율적인지 빠르게 확인하기 위해서 사용할 수 있다.
ex) Array.prototype → indexOf vs includes vs some
원래 UI가 이랬나? 🤔 낯설다. 그리고 결정적으로, 재미가 없어졌다! 그냥 블로그 형식의 줄글들만… 🤮
구글에서 새로 개발한 wireit이라는 도구다. 로고가 힙하다. 세상이 발전하는구나. 근데… [와이레]라고 읽는 건가…? 와이라노… 와이라노…
아래 feature들을 자랑하고 싶다고 한다.
npm run
명령어를 사용할 수 있다.Ref https://github.com/google/wireit
오~ 왠지 구글에서 하는 건 다 간지나 보인다. 문화 사대주의
주말에 놀고 와서 써야징
놀고..와서.. 인생이 뜻대로 되는 일은 없다. 거의 없다! 3년 전에 두 번 다쳐서 반깁스까지 했던 발목을 기어코 또 다치고 말았다~! 언제 또 다치나 했드만 바로 이번에! 🤩 주말이라 병원도 못가고… 셀프로 붕대 툴툴 감고 쓰레빠 끌고 스터디 첫 모임 다녀왔다 ㅎㅎ 다치는 데 능숙해버린 스물 여섯살.
그리고 주말에 다시는 드라이브를 가지 않을 것 같다~! 1시간 반 거리를 3시간, 4시간 걸려서 왔당 ㅎㅎ 왕복 140km를 7시간 동안 달렸다고 하면 누가 믿으리오… 시위까지 겹쳐서 교통 통제 당하고 해탈해서 중간에 길도 잘못 들어서 울 뻔했지만 ‘나는 개짱쎈 어른이다’라고 맘을 다잡으며 다친 발을 이끌고 무사히 귀가했다… 남자친구는 그래도 왼발을 다쳐서 다행이라며 (운전은 오른발로 하니까) 🙄
벌써 까마득해져버렸지만, 공포의 새벽 배포가 있었다. 서비스를 하는 개발자의 숙명인가보다. 프론트 개발자는 조금 덜하긴 하겠지만, 그래도 정말 오랜만에 야식까지 먹고 뺨 때리며 대기탔다. 새벽에 다같이 잠긴 목소리로 대화하는 경험도 (가끔은) 추억일 것 같다. 건강 쪼렙이의 바이오리듬이 대붕괴될 것이라고 생각했으나, 대충 자고 다음날 꽤나 멀쩡했다. 꾸준한 운동으로 체력이 정말 나아진건가?
그치만 이런 긍정적 신호를 세상이 두고볼 리가 없지. 다시 발목 안녕~