@value b from "./b.module.css";
.root {
color: aquamarine;
}
.root :global(.b) {
text-decoration: line-through;
}
@value 로 가져온 이름은 선언 값뿐 아니라 셀렉터 안에서도 치환된다1 — 다른 모듈의 클래스 b 를 가져오면 :global(.b) 가 그 모듈의 해시된 클래스(._b_15lvz_1)가 된다. @value 줄이 없으면 문자 그대로 .b 라 아무것도 안 잡힌다.
Footnotes
-
icss-utils
replaceSymbols.js—postcss-modules-values가 모은 정의를 이 함수로 치환하는데, 선언(decl)뿐 아니라 규칙의 셀렉터(rule.selector)도 돈다. ↩
URL() 생성자는 던지고 canParse() 는 false, parse() 는 null — 같은 파싱인데 실패를 알리는 방식만 다르다1
URL.parse(url)메서드는 주어진 URL에 따라 새 URL 객체를 생성- 두 번째 파라미터 base는 상대 URL을 해석하기 위한 기준 URL로 사용되며, 이를 통해 URL의 경로가 올바르게 조정됨
- URL 객체나 다른 문자열을 파라미터로 사용할 수 있으며, 내부에서 문자열로 변환됨
describe('null 입력에 대한 URL API 동작 테스트', () => {
test('new URL(null)은 TypeError를 발생시킨다', () => {
expect(() => new URL(null)).toThrow(TypeError)
})
test('URL.canParse(null)은 false를 반환한다', () => {
expect(URL.canParse(null)).toBe(false)
})
test('URL.parse(null)은 null을 반환한다', () => {
expect(URL.parse(null)).toBe(null)
})
})
Footnotes
-
URL: parse() static method - MDN: “This is an alternative to using the URL() constructor to construct a
URLwithin a try…catch block, or using canParse() to check the parameters and returningnullif the method returnsfalse.” ↩
가상 키보드와 fixed
- https://saricden.com/how-to-make-fixed-elements-respect-the-virtual-keyboard-on-ios
- https://sophia-dev.io/blog/watching-keyboard-status-with-web-apis
- https://ishadeed.com/article/virtual-keyboard-api/
- https://velog.io/@th_velog/%EC%9B%B9%EB%B7%B0-Fixed-%EA%B0%80%EC%83%81-%ED%82%A4%EB%B3%B4%EB%93%9C-VisualViewport-%EC%82%AC%EC%9A%A9
- https://jooonho.dev/web/2023-01-09-webview-issue/
모달과 스크롤 잠금
- https://pqina.nl/blog/how-to-prevent-scrolling-the-page-on-ios-safari/
- https://github.com/willmcpo/body-scroll-lock
- https://www.developerway.com/posts/hard-react-questions-and-modal-dialog
- https://zenn.dev/lclco/articles/f5b20817a15b9a
- https://zenn.dev/nihashi/articles/4db670877ee5fe
sticky
- https://ryanmulligan.dev/blog/sticky-header-scroll-shadow/
- https://www.taniarascia.com/horizontal-scroll-fixed-headers-table/
- https://www.paigeniedringhaus.com/blog/use-css-grid-to-make-a-fixed-sidebar-with-scrollable-main-body
- https://utilitybend.com/blog/is-the-sticky-thing-stuck-is-the-snappy-item-snapped-a-look-at-state-queries-in-css
- https://css-tricks.com/how-to-make-a-scroll-to-select-form-control/
- https://mmazzarolo.com/blog/2024-08-13-async-chunk-preloading-on-load/
- https://mmazzarolo.com/blog/2024-07-29-data-preloading-script/
- https://martinfowler.com/articles/data-fetch-spa.html
- https://jakelazaroff.com/words/whats-a-single-page-app/
- https://jakelazaroff.com/words/building-a-single-page-app-with-htmx/
새 버전 배포 후 다시 불러오기 — 지속적인 폴링에 의존하지 않고 SPA를 업데이트하고 다시 로드하기 위한 다양한 기술과 전략에 대해 설명합니다. CloudFront와 같은 콘텐츠 전송 네트워크(CDN)를 API 프록시로 활용하여 SPA를 무효화하고, 새 콘텐츠를 사용할 수 있을 때 사용자가 애플리케이션을 새로 고치라는 메시지를 구현하고, 캐시 버스팅 기술과 서비스 워커를 사용하는 등의 방법을 살펴봅니다.
- Using a CloudFront API Proxy to Invalidate a Single-Page Application Without Polling
- Getting clients to reload your latest single page application version - Codemzy’s Blog
- https://twitter.com/housecor/status/1669722918392156162?s=20
- https://github.com/coryhouse/vite-pwa-app
- https://vite-pwa-org.netlify.app/
취소 가능한 fetch 요청을 생성하는 createCancelableFetch 함수 구현 — signal 을 넘긴 fetch 는 abort() 가 불리면 AbortError 로 reject 된다.1
/**
* 취소 가능한 fetch 요청
*
* @param url - 요청 URL
* @param options - fetch 옵션
* @returns {run, cancel} - run: 요청 실행 함수, cancel: 요청 취소 함수
*
* @example
*
* ```ts
* const { run, cancel } = createCancelableFetch('/api/data')
*
* run()
* .then((data) => console.log('Fetched data:', data))
* .catch((err) => console.error('Error or canceled:', err))
*
* cancel()
* ```
*/
function createCancelableFetch(url: string, options: RequestInit = {}) {
const abortController = new AbortController()
const run = async () => {
const response = await fetch(url, {
...options,
signal: abortController.signal,
})
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`)
}
return response.json()
}
const cancel = () => abortController.abort()
return {
run,
cancel,
}
}
AbortController를 사용하여 요청을 중단할 수 있는 기능 제공run함수로fetch요청을 실행하고,cancel함수로 요청을 취소할 수 있음- 요청 실패 시 오류를 처리하고 예외를 발생시킴
Footnotes
-
Window: fetch() - MDN: “The request was aborted due to a call to the
AbortControllerabort()method.” (AbortError항목) ↩
field-sizing를 사용하면 콘텐츠를 기반으로 크기 조절을 사용 설정하는 데 CSS 한 줄이 필요합니다. 이 콘텐츠 기반 크기 조절 스타일은textarea외에도 다른 요소에도 적용됩니다.
- https://github.com/arthurfiorette/proposal-safe-assignment-operator
- https://github.com/arthurfiorette/tuple-it
비동기 호출의 결과와 오류를 튜플 형식으로 반환
const [error, data] = await tuple(someAsyncFunction())
// 성공: [null, 결과값]
// 실패: [error] 또는 [new TupleItError(error)]
오류 처리 간소화 - 단일 체크로 오류 관리 가능 (Error 가 아닌 값으로 reject 돼도 TupleItError 로 감싸서 if (error) 하나로 갈린다)1
Footnotes
-
tuple-it
index.js: “Wrapping into TupleItError avoids the need to checkif (error !== undefined)in favor of a simplerif (error)” ↩
Event 인터페이스는 DOM에서 발생하는 이벤트를 나타냅니다.
- https://www.robinwieruch.de/react-event-bubbling-capturing/ 1
- https://cssence.com/2024/form-label-event-bubbling/ 2
- JavaScript eventing deep dive3
- Monitor Events and Function Calls via Console4
Footnotes
-
React 로 보는 버블링·캡처링 입문. 이벤트는 문서에서 타깃까지 내려가고(캡처) 다시 올라온다(버블) — 바깥 요소 핸들러가 같이 불리는 이유.
stopPropagation()으로 올라가는 걸 끊고,target대currentTarget을 구분하며, 캡처 단계에서 가로채려면onClickCapture나addEventListener의 세 번째 인자true를 쓴다. ↩ -
<label>을 클릭하면 버블링이 두 번 일어난다 — 라벨에서 조상까지 올라간 뒤, 브라우저가 라벨된 요소(for로 이어진<input>등)를 대신 클릭해 거기서 다시 올라간다. 라벨 안에 input 을 중첩하면 라벨 핸들러가 두 번 불리고, 막으려면stopPropagation도 두 번 필요할 수 있다. ↩ -
이벤트 ↩
-
monitorEvents를 활용한 디버깅 ↩
💡
useStateObject는 React의useState를 확장한 가벼운 래퍼로, 객체 상태 관리를 간편하게 할 수 있도록 설계되었습니다.
export type StateObject<T extends object> = T & {
set: React.Dispatch<React.SetStateAction<T>>
setItem: <K extends keyof T>(key: K, value: T[K]) => void
merge: (newState: Partial<T>) => void
reset: () => void
}
그렇다면 Map과 Set도 시도해보기 — 같은 Map 을 고쳐서 넘기면 Object.is 로 같은 값이라 React 가 갱신을 무시하므로, 매번 복사본을 만든다.1
function useStateMap<K, V>(init: Iterable<[K, V]> = []) {
const [map, setMap] = useState(new Map<K, V>(init))
const update = useCallback(
(updater: (currentMap: Map<K, V>) => void) => {
setMap((prev) => {
const newMap = new Map(prev)
updater(newMap)
return newMap
})
},
[setMap]
)
return {
map,
set: (key: K, value: V) => update((m) => m.set(key, value)),
delete: (key: K) => update((m) => m.delete(key)),
clear: () => setMap(new Map()),
has: (key: K) => map.has(key),
get: (key: K) => map.get(key),
entries: () => Array.from(map.entries()),
size: map.size,
}
}
function useStateSet<T>(init: Iterable<T> = []) {
const [set, setSet] = useState(new Set<T>(init))
const update = useCallback(
(updater: (currentSet: Set<T>) => void) => {
setSet((prev) => {
const newSet = new Set(prev)
updater(newSet)
return newSet
})
},
[setSet]
)
return {
set,
add: (value: T) => update((s) => s.add(value)),
delete: (value: T) => update((s) => s.delete(value)),
has: (value: T) => set.has(value),
clear: () => setSet(new Set()),
entries: () => Array.from(set),
size: set.size,
}
}
Footnotes
-
useState - React: “React will ignore your update if the next state is equal to the previous state, as determined by an
Object.iscomparison. This usually happens when you change an object or an array in state directly:” ↩
로컬·테스트 서버에서 크롬 인증서 경고를 강제로 통과한다.
언제: 이 연결은 비공개로 설정되지 않았습니다 화면에 NET::ERR_CERT_AUTHORITY_INVALID·NET::ERR_CERT_COMMON_NAME_INVALID 가 뜨고, 고급 → 안전하지 않음(계속)이 없거나 매번 누르기 번거로울 때.
절차
- 경고 화면 아무 곳이나 클릭한다 — 페이지에 포커스를 준다
- 입력창 없이 그대로
thisisunsafe를 타이핑한다
입력창이 없는 게 정상이다. 경고 페이지의 keypress 핸들러가 한 글자씩 받아 BYPASS_SEQUENCE 와 대조하고, 다 맞으면 CMD_PROCEED 를 보낸다 — “안전하지 않음(계속)“을 누른 것과 같은 명령이다.
확인: 마지막 글자를 치는 순간 넘어간다. 안 넘어가면 포커스가 주소창에 있는 것이다 — 1번부터.