dayjs로 특정 날짜가 포함된 주의 모든 날짜 가져오기
import dayjs from 'dayjs'
import isoWeek from 'dayjs/plugin/isoWeek'
dayjs.extend(isoWeek)
// startOf('week') → 일요일 시작
// startOf('isoWeek') → 월요일 시작 (캘린더 UI에 주로 사용)
function getWeekDays(date, iso = true) {
const start = dayjs(date).startOf(iso ? 'isoWeek' : 'week')
return Array.from({ length: 7 }, (_, i) =>
start.add(i, 'day').format('YYYY-MM-DD')
)
}
getWeekDays('2025-10-28') // ['2025-10-27', ..., '2025-11-02']
JavaScript 부동소수점 오차 - 0.1 + 0.2 = 0.30000000000000004
IEEE 754 64비트 부동소수점 표준. 0.1은 이진법으로 무한 반복(0.0001100110011...)이라 근사치 저장됨.
// 해결책
;(1.1 * 10 + 0.1 * 10) / 10 // 정수 변환
parseFloat((1.1 + 0.1).toFixed(1)) // toFixed
Math.abs(1.1 + 0.1 - 1.2) < Number.EPSILON // 비교 시
// 정밀 계산: decimal.js, big.js, bignumber.js
JS만의 문제 아님. Python, Java, C++ 등 IEEE 754 사용하는 모든 언어에서 동일.
Drizzle ORM에서 IN 절 사용
import { inArray } from 'drizzle-orm'
const authorIds = [1, 2, 3]
// SELECT * FROM authors WHERE id IN (1, 2, 3)
const authors = await db
.select()
.from(schema.authors)
.where(inArray(schema.authors.id, authorIds))
// 빈 배열 처리
const result =
authorIds.length > 0
? await db
.select()
.from(schema.authors)
.where(inArray(schema.authors.id, authorIds))
: []
캘린더 이벤트 겹침 처리 알고리즘
RSC에서 날짜 처리: 쿠키 기반 타임존
문제: Date 객체 직렬화, 서버/클라이언트 타임존 불일치, 하이드레이션 FOUT
해결: 서버에서 타임존 적용해서 렌더링
// middleware.js - 타임존 감지
export function middleware(request) {
const timezone =
request.cookies.get('user-timezone')?.value ||
request.geo?.timezone || // Vercel
'UTC'
const response = NextResponse.next()
response.headers.set('x-user-timezone', timezone)
return response
}
// 서버 컴포넌트
const getUserTimezone = cache(() => {
return headers().get('x-user-timezone') || 'UTC'
})
// 클라이언트 - 타임존 자동 감지 후 쿠키 저장
useEffect(() => {
const tz = Intl.DateTimeFormat().resolvedOptions().timeZone
document.cookie = `user-timezone=${tz}; path=/; max-age=31536000`
}, [])
첫 방문은 인프라 추정값 사용, 이후 정확한 타임존 적용. FOUT 없음.
대안: useSyncExternalStore (client component 전용)
'use client'
const timezoneStore = {
getSnapshot: () => Intl.DateTimeFormat().resolvedOptions().timeZone,
getServerSnapshot: () => 'UTC',
subscribe: () => () => {},
}
function useTimezone() {
return useSyncExternalStore(
timezoneStore.subscribe,
timezoneStore.getSnapshot,
timezoneStore.getServerSnapshot
)
}
서버: UTC → 클라이언트: 실제 타임존. 하이드레이션 에러 없음, 대신 FOUT 발생.
대용량 리스트에서 selected 아이템 조회 최적화
O(n×m) → O(n+m)로 개선: Map으로 인덱싱
// 기존: 매번 find (느림)
selected.map((key) => items.find((item) => item.key === key))
// 개선: Map 인덱싱 (빠름)
const itemsMap = new Map(items.map((item) => [item.key, item]))
selected.map((key) => itemsMap.get(key)).filter(Boolean)
React에서 백그라운드 인덱싱:
function useItemsIndex(items: Item[]) {
const [map, setMap] = useState(new Map())
const [ready, setReady] = useState(false)
useEffect(() => {
// 청크 단위로 처리하여 UI 블로킹 방지
const newMap = new Map(items.map((item) => [item.key, item]))
setMap(newMap)
setReady(true)
}, [items])
return { map, ready }
}
10만개 이상이면 Web Worker 고려.
Tumblr 테마 Vue → Web Components 마이그레이션
NPF 데이터는 <script type="application/json" data-npf>에 저장. 컴포넌트에서 closest로 포스트 컨테이너 찾아서 참조 (React Context 패턴과 유사).
<div id="{PostID}" data-type="{PostType}">
{block:Text}
<script type="application/json" data-npf>
{NPF}
</script>
<tumblr-npf-media></tumblr-npf-media>
<tumblr-npf-text></tumblr-npf-text>
{/block:Text}
</div>
// 데이터 참조
const post = this.closest('[id][data-type]')
const npf = JSON.parse(post.querySelector('script[data-npf]').textContent)
// 렌더링 - createElement 사용 (Tumblr 템플릿 ${} 충돌 회피)
const img = document.createElement('img')
img.src = imageURL
this.appendChild(img)
제약: {NPF}는 Text 포스트에서만 사용 가능. Photo는 기존 Tumblr 변수 사용.
React에서 iframe 내부에 컴포넌트 렌더링
// react-frame-component 사용 (권장)
import Frame from 'react-frame-component'
;<Frame head={<style>{`body { margin: 0; }`}</style>}>
<MyComponent />
</Frame>
// 직접 구현: createPortal + contentDocument
function IframeRenderer({ children }) {
const iframeRef = useRef<HTMLIFrameElement>(null)
const [mountNode, setMountNode] = useState<HTMLElement | null>(null)
useEffect(() => {
const iframe = iframeRef.current
const handleLoad = () => setMountNode(iframe?.contentDocument?.body ?? null)
iframe?.addEventListener('load', handleLoad)
if (iframe?.contentDocument?.readyState === 'complete') handleLoad()
return () => iframe?.removeEventListener('load', handleLoad)
}, [])
return (
<>
<iframe ref={iframeRef} />
{mountNode && createPortal(children, mountNode)}
</>
)
}
iframe 내부는 부모 CSS 미적용 (스타일 별도 주입 필요), 이벤트 버블링 안 됨. 단순 스타일 격리 목적이면 Shadow DOM 고려.
Jest setupFiles vs setupFilesAfterEnv - 실행 시점이 다르다.
- setupFiles: 테스트 프레임워크 설치 전. Jest 전역 객체 없음. 환경 변수, 폴리필용.
- setupFilesAfterEnv: 테스트 프레임워크 설치 후.
jest.setTimeout(), 커스텀 matcher, 전역beforeEach용.
process.env는 setupFiles에서. 모듈이 import 시점에 환경 변수를 읽기 때문에 setupFilesAfterEnv에서 설정하면 이미 늦다.
// jest.config.js
{ setupFiles: ['./jest.env.js'], setupFilesAfterEnv: ['./jest.setup.js'] }
// jest.env.js - 환경 변수
process.env.API_URL = 'http://test-api.example.com'
// jest.setup.js - Jest API 활용
import '@testing-library/jest-dom'
beforeEach(() => { jest.clearAllMocks() })
yalc - 로컬 Node 모듈을 다른 프로젝트에서 바로 테스트. npm link보다 문제 적음 (파일 복사 방식).
npm i -g yalc
# 패키지에서
yalc publish # ~/.yalc에 저장
yalc push # 연결된 모든 프로젝트에 반영
yalc publish --push # 둘 다
# 앱에서
yalc add my-module
yalc remove my-module && npm install # 정리
watch 모드: "dev": "tsup src/index.ts --watch --onSuccess 'yalc push'"
.gitignore: .yalc, yalc.lock