Node.js에서 윈도우즈 외부 경로 파일 읽기
const path = require('path')
const fs = require('fs').promises
// 슬래시 사용 (Node.js가 자동 변환)
// ('C:/Users/username/Documents/file.txt')
// 또는 백슬래시 이스케이프
// ('C:\\Users\\username\\Documents\\file.txt')
// 권장: path.join 사용 (크로스 플랫폼)
path.join(process.env.USERPROFILE, 'Documents', 'file.txt')
// path.resolve로 절대 경로 생성
path.resolve('../../Documents/file.txt')
사용자 입력으로 경로 받을 때는 path.normalize()로 디렉토리 트래버설 공격 방지.
Date.getDay() - 요일 반환 (0=일요일, 6=토요일)
new Date().getDay() // 0~6
new Date('2024-12-25').getDay() // 3 (수요일)
new Date(2024, 11, 25).getDay() // 월은 0부터 시작
const days = ['일', '월', '화', '수', '목', '금', '토']
days[new Date().getDay()] // 오늘 요일
검색어 필터링 UI → Debounce (300-500ms)
타이핑 완료 후 검색이 더 자연스러움. “자바스크립트” 입력 시 매 글자마다 검색하면 비효율적.
const handleSearch = debounce((term) => performSearch(term), 300)
input.addEventListener('input', (e) => handleSearch(e.target.value))
Throttle은 실시간 검색 결과를 보여주면서 요청 제한할 때 사용 (1초마다 최대 1회 등).
TypeScript 제네릭 기본값 - <T extends Type = DefaultValue>
type SelectProps<T extends 'single' | 'multiple' = 'multiple'> = {
mode: T
value: T extends 'single' ? string : string[]
}
const a: SelectProps = { mode: 'multiple', value: ['a', 'b'] } // 기본값 사용
const b: SelectProps<'single'> = { mode: 'single', value: 'a' } // 명시적 지정
// 여러 매개변수에 각각 기본값
type ApiResponse<
TData = any,
TStatus extends 'loading' | 'success' | 'error' = 'loading'
> = {
data: TStatus extends 'success' ? TData : null
status: TStatus
}
가장 자주 사용되는 케이스를 기본값으로 설정하면 제네릭을 항상 명시하는 번거로움 줄어듦.
Vitest 모킹: vi.mocked() vs vi.hoisted()
vi.mocked()는 타입만 제공, 실제 모킹 구현체는 별도로 필요.
// ❌ mockImplementation이 undefined
const mockUseSize = vi.mocked(useSize)
// ✅ vi.hoisted() 사용 (추천)
const mockUseSize = vi.hoisted(() => vi.fn())
vi.mock('ahooks', () => ({ useSize: mockUseSize }))
beforeEach(() => {
mockUseSize.mockImplementation(() => ({ width: 100, height: 20 }))
})
DOM 속성 모킹 (scrollWidth/clientWidth):
beforeEach(() => {
Object.defineProperty(HTMLElement.prototype, 'scrollWidth', {
configurable: true,
get() {
return 150
},
})
})
afterEach(() => {
// 원래 속성 복원
})
DOM 측정이 복잡하면 Playwright/Cypress로 통합 테스트 고려.
ResizeObserver Mock (Vitest, ES Module 환경)
vi.hoisted()로 모킹 함수 미리 선언 후 vi.mock()에서 사용:
import { vi } from 'vitest'
const { mockResizeObserver, MockResizeObserver } = vi.hoisted(() => {
let observers: { callback: ResizeObserverCallback; observer: any }[] = []
const MockResizeObserver = vi.fn().mockImplementation((callback) => {
const observer = {
observe: vi.fn(),
unobserve: vi.fn(),
disconnect: vi.fn(),
}
observers.push({ callback, observer })
return observer
})
return {
MockResizeObserver,
mockResizeObserver: {
triggerResize: (entries: ResizeObserverEntry[], index = 0) => {
observers[index]?.callback(entries, observers[index].observer)
},
reset: () => {
observers = []
},
},
}
})
vi.mock('resize-observer-polyfill', () => ({ default: MockResizeObserver }))
// 테스트에서 사용
beforeEach(() => mockResizeObserver.reset())
it('should handle resize', () => {
const mockEntry = {
target: document.createElement('div'),
contentRect: { width: 100 },
}
mockResizeObserver.triggerResize([mockEntry])
})
핵심: ES Module에서는 vi.hoisted() 필수. 여러 observer는 배열로 추적.
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))
: []
캘린더 이벤트 겹침 처리 알고리즘
겹치는 이벤트들을 나란히 배치하기 위한 레인 할당:
interface Event {
id: string
start: number
end: number
}
interface PositionedEvent extends Event {
lane: number
totalLanes: number
}
function calculatePositions(events: Event[]): PositionedEvent[] {
// 1. 시작 시간 순 정렬
const sorted = [...events].sort((a, b) => a.start - b.start || a.end - b.end)
// 2. 겹침 그룹 찾기 + 레인 할당
const result: PositionedEvent[] = []
let group: Event[] = []
let groupEnd = 0
sorted.forEach((event) => {
if (group.length === 0 || event.start < groupEnd) {
group.push(event)
groupEnd = Math.max(groupEnd, event.end)
} else {
processGroup(group, result)
group = [event]
groupEnd = event.end
}
})
if (group.length) processGroup(group, result)
return result
}
function processGroup(group: Event[], result: PositionedEvent[]) {
const activeLanes: number[] = []
group.forEach((event) => {
// 사용 가능한 가장 낮은 레인 찾기
let lane = activeLanes.findIndex((end) => end <= event.start)
if (lane === -1) lane = activeLanes.length
activeLanes[lane] = event.end
result.push({ ...event, lane, totalLanes: 0 })
})
// totalLanes 업데이트
const maxLane =
Math.max(...result.slice(-group.length).map((e) => e.lane)) + 1
result.slice(-group.length).forEach((e) => (e.totalLanes = maxLane))
}
// CSS 적용: left = lane/totalLanes * 100%, width = 100%/totalLanes