April 24, 2022 • ☕️☕️☕️ 14 min read
근데 이제 여름
::marker CSS 의사요소를 요소 내부에 둘지, 외부에 둘지 정할 수 있는 속성으로, display: list-item 속성이 있는 요소에 적용된다.
.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
와 같은 속성으로 조절할 수 있다.
li {
list-style: disc inside;
&:before {
content: "";
margin-left: -0.5em; // 마커와 콘텐츠 사이 간격을 좁힘
}
}
react query 사용 시 SSR 서버에서 데이터 prefetch 후 컴포넌트에서 useQuery
로 캐싱된 데이터를 가져올 때, 첫 로드 시에 저장된 캐시값이 없어 undefined
가 리턴되는 경우가 있다. 이때 화면이 깜빡이는 현상이 발생한다.
이는 SSR 서버에서 fetching된 데이터가 클라이언트에 값이 저장되어있지 않아서 발생하는 문제로, react-query에서 제안하는 2가지 해결 방법이 있다.
첫 번째는 initialData
를 활용하는 것이다.
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
를 활용하는 방법이다.
export async function getServerSideProps() {
const queryClient = new QueryClient();
await queryClient.prefetchQuery("posts", getPosts);
return {
props: {
dehydratedState: dehydrate(queryClient),
},
};
}
서버에서 prefetching한 정보가 클라이언트 초기 로드 시에도 캐시에 남아있게 되어, undefined
가 반환되는 일 없이 올바른 data로 렌더링을 할 수 있다.
Blob
은 파일류의 불변하는 미가공 데이터를 나타낸다.
사용자 시스템의 파일을 지원하기 위해 Blob
인터페이스를 상속해 기능을 확장한 것이 File
객체다. Blob
에서 데이터를 읽는 방법 중 하나로 FileReader
를 사용할 수 있다.
createObjectURL()
- 인자로 File
객체를 받으며, 해당 파일의 고유 URL 정보를 생성하고 반환한다.revokeObjectURL()
- 메모리 누수를 막기 위해 직접 해체해주어야 한다.FileReader.readAsDataURL()
- 파일을 읽고, result
속성에 파일을 나타내는 URL을 저장한다.FileReader.onload
- 읽기 완료 성공 시 동작FileReader.onloadend
- 성공, 실패와 무관하게 읽기 완료 시 동작FileReader.onerror
- 읽는 도중 오류 발생 시 동작useImperativeHandle
useImperativeHandle
은 ref
를 사용할 때 부모 컴포넌트에 노출되는 인스턴스 값을 사용자화(customizes)한다. useImperativeHandle
을 사용하여 자식 컴포넌트에서 특정 인스턴스를 만들어 부모 컴포넌트로 노출시킬 수 있다. forwardRef
와 함께 사용한다.
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에서 !
는 어설션 연산자(Non-null assertion operator), 또는 확정 할당 어설션(Definite Assignment Assertions)으로 사용된다.
어설션 연산자는 피연산자가 null이 아니라고 컴파일러에게 전달, 일시적으로 null 제약조건을 완화한다.
function nonNullAssertionOperator(e: string | null): string {
return e!;
}
확정 할당 어선셜은 이 변수에는 값이 무조건 할당되어 있다고 컴파일러에게 전달, 값이 없어도 사용할 수 있게 한다.
let notNull!: string;
console.log(notNull);
React Query의 useInfiniteQuery
hook을 사용하면, page state를 따로 클라이언트 코드에서 관리할 필요가 없다.
pageParams
를 정의해놓으면, fetchNextPage()
를 호출함으로써 API 호출 시 쿼리에 적절한 page 값을 삽입할 수 있다.
검색기능 등 page를 0으로 초기화해야될 경우에도, query key의 의존성 배열에 검색어를 넣으면, 검색어가 변경될 때마다 page가 0으로 초기화되고 다시 카운팅되기 시작한다.
부모 컴포넌트에서 자식 컴포넌트로 ref를 넘길 때, custom prop으로 넘겨주는 방법
Component = ({ innerRef }) => <button ref={innerRef} />;
그리고 forwardRef
를 사용하는 방법이 있다.
Component = React.fowardRef((props, ref) => <button ref={ref} />);
fowardRef
는 부모 컴포넌트에서 input
, form
등을 다루는 비제어 컴포넌트에 접근하기 위해 나온 것이다. 따라서 명시적으로 비제어 컴포넌트를 다룰 때에는 forwardRef
를, 일반 제어 컴포넌트(함수 컴포넌트)를 다룰 때에는 prop으로 구분지어 다뤄주는 것이 좋다.
XXS 공격을 방지하기 위해 HTML string에서 위험성 있는 코드를 제거하는 도구를 sanitizer라고한다.
DOMPurify.sanitizer(`<p>sample</p><script>alert('XSS공격')</script>`);
위 코드의 실행 결과는 <p>sample</p>
가 된다. 위험성 있는 script
태그를 삭제해준 것이다.
innerHTML
대신 리액트의 dangerouslySetInnerHTML
을 사용하고, DOMPurify.sanitizer
를 거친 문자열을 넣으면 조금 더 안전하게 코드를 작성할 수 있다.
서버사이드에서 사용하기 위한 isomorphic-dompurify
라는 라이브러리도 있다. next를 사용한다면 서버에서 sanitizer
를 한 문자열을 클라이언트에 전달해주는 방법도 좋다.
Dialog
Dialog는 백드롭을 깔아서 다른 동작을 아예 막아버린다는 점이 다른 두 개, 특히 Snackbar와 다르다. 나만 보게 만드는 UX 패턴을 “모달리티“(modality)라고 한다.
Toast
Snackbar
Dialog와 Toast 그 사이의 모든 경우에는 Snackbar를 이용하기 적합하다.
Ref https://brunch.co.kr/@oemilk/91
Next.js의 getServerSideProps에서 props 의 값으로 undefined가 있으면 JSON으로 직렬화되지 않아 에러가 발생할 수 있다.
React Query의 prefetchInfiniteQuery
를 사용할 경우 이전 페이지가 없기 때문에 pageParams
는 0이 아닌 undefined로 던져진다. 이 영향으로 dehydrate(queryClient)의 결과물에 undefined가 포함되고, 이를 props로 넘기려 할 때 Next.js에서 undefined를 직렬화하지 못해 에러가 발생한다.
이때는 stringify와 serialize 작업을 통해 undefined를 제거한 데이터를 props로 넘겨줘야 한다.
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
로 이벤트 전파 순서를 확인할 수 있다. 이때 z-index
도 이벤트 전파 순서에 영향을 준다.
button
> div
> label
> input(display: none)
의 순서로 엘리먼트 계층이 구성되어 있을 때, button
에서 발생한 이벤트가 div
에 막혀 label
까지 도달하지 못하는 경우가 있다. 이때 div
보다 label
의 z-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.json
과 package-lock.json
을 수정할 수 있다는 것을 의미한다.
반면에 npm ci
는 package-lock.json
을 기반으로 패키지를 설치하며, 파일들을 수정하지 않는다. 만약에 node_modules
가 존재하면 먼저 삭제한 뒤에 패키지를 설치한다.(ci
는 clean install의 줄임말이다.)
크롬 뿐만 아니라 많은 웹브라우저들에는 비디오 및 오디오 자동 플레이 제한이 있다. 이는 사용자들이 인지 못한 채 소리가 나거나 동영상이 재생되는 경우를 막기 위함이다.
audio가 자동플레이되려면 어떻게 해야 할까?
audio 자동플레이를 위해 공식 문서에서 권장되는 방법은 audio 재생 실패시 audio를 재생하는 버튼을 띄우는 것이다.
ex) 주문접수페이지에서 소리 알람이 실패하면 새로운 주문이 왔습니다!
와 같은 다이얼로그를 띄우고, 해당 다이얼로그에서 ‘확인’을 눌러야 소리가 재생되게끔 하는 방법이다.
Ref https://developer.chrome.com/blog/autoplay/
getDerivedStateFromProps
는 최초 마운트 시와 갱신 시 모두에서 render
메서드를 호출하기 직전에 호출된다. state를 갱신하기 위한 객체를 반환하거나, null을 반환하여 아무 것도 갱신하지 않을 수 있다. props 로 받아온 값을 state 에 넣어주고 싶을 때 사용한다.
static getDerivedStateFromProps(nextProps, prevState) {
console.log("getDerivedStateFromProps");
if (nextProps.color !== prevState.color) {
return { color: nextProps.color };
}
return null;
}
그러나 state를 끌어오면 코드가 장황해지고, 이로 인하여 컴포넌트를 이해하기 어려워진다. React 팀에서 제안하는 대안들은 다음과 같다.
componentDidUpdate
생명주기를 대신해서 사용한다.Refhttps://ko.reactjs.org/docs/react-component.html#static-getderivedstatefromprops https://react.vlpt.us/basic/25-lifecycle.html
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)
리그레이션 테스팅(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/*"
"accept="application/JSON"
@types/react
18ver에서 React.VFC
가 deprecated되었다. 때문에 children
이 필요하다면 VFC가 나오기 이전처럼 FC<PropsWithChildren<Props>>
로 타입을 선언해줘야 한다.
.git/hooks/prepare-commit-msg
로 커밋메세지를 원하는대로 작성할 수 있다. (Ref
인덱스 시그니처를 사용할 때, key 값의 의미를 명확하게 담아내면 좋다.
// Bad
{ [key: string]: DisplayedProduct }
// Good
{ [productId: string]: DisplayedProduct }
Node.js 18 버전에서는 fetch
와 test
가 도입되었다.
PC의 mouseEnter()
, mouseLeave()
이벤트에 대응되는 모바일 이벤트는 onTouchStart()
, onTouchEnd()
다.
JavaScript에서 객체가 비었는지 확인하는 방법
Object.keys(obj).length === 0 && obj.constructor === Object;
여기서 풀어볼 수 있다. 역시 난 쪼렙이었다.
다음 피쳐들이 새롭게 포함되었다! 원래 없었다니. ㅋㅋㅋ
새로운 API 말고도 향상된 부분들이 많다.
Ref https://nodejs.org/en/blog/announcements/v18-release-announce/
무사히 팀원 분들을 만났다! 모두 오랜만의 외출(?)에 차려입은 모습들을 보니 느낌이 새삼 달랐다. 주 1~2회 정도는 사무실 나가는 것도 좋을 것 같다. 그래서 집 와서 노트북 들고다닐 가방을 새로 장만했다. 사무실에 둘 키보드도 새로 샀다. (???)
QA는 아직도 끝나지 않는다. QA해주시는 분들 정말 대단한 것 같다. 어떻게 그런 것까지 잡아내는지… 여기저기 급하게 고치면서 또 다른 버그들을 남겨두는 것 같아서 죄송하다 ㅠ 다음주에 무사히 배포가 나가면 좋겠다.
주말에는 서울숲에 다녀왔다. 섬세이 테라리움이라는 체험전시도 갔다왔다. 와! 사람 진짜 많았다. 무슨 퇴근시간 강남만큼 많았다. 서울숲엔 튤립도 폈는데, 사실 슬슬 지고 있었다. 튤립은 원래 5월에 핀다는데, 진짜 기상이변이다.
벌써 덥다. 막 아주 더운 건 아닌데, 이번 여름 큰일났구나, 느껴진다.