yalc - 로컬 Node 모듈을 다른 프로젝트에서 바로 테스트. npm link보다 문제 적음 (파일 복사 방식) — symlink 대신 publish 될 파일만 전역 저장소로 복사하고, 앱은 그걸 .yalc 로 끌어온다.1

npm i -g yalc

# 패키지에서
yalc publish          # ~/.yalc에 저장
yalc push             # = 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


Footnotes

  1. yalc README: “When you run yalc publish in the package directory, it grabs only files that should be published to NPM and puts them in a special global store (located, for example, in ~/.yalc).” / “yalc publish --push will publish your package to the store and propagate all changes to existing yalc package installations …” / “yalc push - is a use shortcut command for push operation …” ↩

#503

MDIR 스타일 인터페이스 개발 - SQLite DB를 탐색하는 도구

  1. Neovim Telescope 순수 Neovim 솔루션. :StoryBrowse, :StorySearch
  2. VS Code 확장 Activity Bar + Tree View + Quick Pick
  3. Bun Single-file Executable --compile로 DB + 웹서버 + 프론트엔드 단일 파일 (~100MB)
#500

TypeScript 중첩 객체 타입 부분 수정 — 바깥에서 K 를 Omit 으로 빼고, 같은 키를 매핑 타입으로 다시 붙이면서 안쪽만 바꾼다.1

interface O {
  actions: { a: string; b: number }
}

// 중첩 속성 Optional로 변경
type MakeNestedOptional<T, K extends keyof T, OK extends keyof T[K]> = Omit<
  T,
  K
> & {
  [P in K]: Omit<T[K], OK> & Partial<Pick<T[K], OK>>
}
type Result = MakeNestedOptional<O, 'actions', 'b'> // { actions: { a: string; b?: number } }

// 중첩 속성 타입 오버라이드
type OverrideNested<T, K extends keyof T, Override> = Omit<T, K> & {
  [P in K]: Omit<T[K], keyof Override> & Override
}
type Result2 = OverrideNested<O, 'actions', { b: boolean }> // { actions: { a: string; b: boolean } }

한두 군데만 쓸 거면 그냥 손으로 타입 작성이 더 명확함. 유틸리티 타입은 반복 사용할 때만 가치.

// 라이브러리 객체 변이 주의
// ❌ info.actions.onDownload = undefined
// ✅ const modifiedInfo = { ...info, actions: { ...info.actions, onDownload: undefined } }

Footnotes

  1. Utility Types — Omit<Type, Keys> | TypeScript: “Constructs a type by picking all properties from Type and then removing Keys (string literal or union of string literals). The opposite of Pick.” ↩

#499

증기 계란 조리기: 계란이 많을수록 물을 적게 넣는 이유

계란 1개 (완숙): 117ml
계란 6개 (완숙): 96ml

증기 조리 방식은 물의 양 = 조리 시간. 계란이 많으면:

  • 서로 열을 공유/유지 (“집단 난방” 효과)
  • 증기가 좁은 공간에 집중되어 효율 ↑
  • 열 손실 표면적이 상대적으로 ↓

물에 삶는 것과 달리, 증기 조리는 배치 밀도에 따라 열 효율이 크게 달라짐.

#498

styled-components에서 &-header 같은 BEM 스타일 자식 선택자를 HTML에서 참조하는 방법? 없다. & 는 컴포넌트에 붙은 생성 클래스라 &-header 는 해시 뒤에 -header 가 붙은 클래스가 된다.1 대신 styled 컴포넌트마다 고정 클래스가 있어서 ${Header} 로 선택자에 끼울 수 있다.2

// ❌ 해시된 클래스명과 매칭 안됨
const Container = styled.div`
  &-header { color: red; }
`

// ✅ 방법 1: 일반 클래스 선택자
const Container = styled.div`
  .header { color: red; }
`
<Container><div className="header">Header</div></Container>

// ✅ 방법 2: Styled 컴포넌트 변수로 선언 (추천)
const Header = styled.div`color: red;`
const Container = styled.div`
  ${Header} { margin-bottom: 20px; }
`

Footnotes

  1. Basics | styled-components: “& a single ampersand refers to all instances of the component …” ↩

  2. Advanced Usage — Referring to other components | styled-components: “Whenever a component is created or wrapped by the styled() factory function, it is also assigned a stable CSS class for use in targeting.” ↩

#497

React Children API - 가능하지만 비추천

암묵적 의존성, 타입 안전성 부족, 매 렌더 트리 순회, 예측 불가능

재귀 순회

function traverseReactNode(children: ReactNode, callback, typeToMatch?) {
  Children.forEach(children, (child) => {
    if (!isValidElement(child)) return
    if (child.type === Fragment) {
      traverseReactNode(child.props.children, callback, typeToMatch)
      return
    }
    if (child.type === typeToMatch) callback(child)
    if (child.props?.children) {
      traverseReactNode(child.props.children, callback, typeToMatch)
    }
  })
}

동적 래핑

const renderChildren = (children) => {
  const elements = React.Children.toArray(children)
  const hasLink = elements.some(
    (el) => React.isValidElement(el) && el.props.url
  )
  return hasLink ? children : <ul>{children}</ul>
}
// toArray는 string, number도 포함 → isValidElement 체크 필수

대안: Compound Component

// ❌ 마법처럼 동작 (예측 불가)
<Tabs>{/* 어디에 넣든 Tab 찾아줌 */}</Tabs>

// ✅ Compound Component
<Tabs.Root>
  <Tabs.List>
    <Tabs.Trigger value="a">A</Tabs.Trigger>
  </Tabs.List>
  <Tabs.Content value="a">Content</Tabs.Content>
</Tabs.Root>

React 팀도 2021년부터 Children API 사용 권장하지 않음: “Using Children is uncommon and can lead to fragile code”1

역사적 배경: 2013년엔 Context API도 없었음. “선언형”이라면서 Children API로 명령형 트리 순회 제공하는 이중성.


Footnotes

  1. Children – React: “Using Children is uncommon and can lead to fragile code.” ↩

#496

React 19 Concurrent 훅 - useTransition, useOptimistic vs React Query

useTransition

const [isPending, startTransition] = useTransition()

function handleFilter(value: string) {
  startTransition(async () => {
    const data = await fetchData(value)
    startTransition(() => setResults(data)) // await 뒤 set 은 다시 감싼다
  })
}
  • startTransition으로 감싼 state 업데이트는 **비긴급(non-urgent)**으로 처리
  • 급한 업데이트(타이핑, 클릭 피드백)를 먼저 처리하고, transition 작업은 뒤로 미룸
  • isPending으로 로딩 상태 확인, 기존 UI 유지하면서 백그라운드에서 새 UI 준비
  • await 뒤의 set 은 transition 으로 안 잡힌다 — 한 번 더 startTransition 으로 감싼다1

useOptimistic

const [optimisticItems, addOptimistic] = useOptimistic(
  items,
  (current, newItem) => [...current, newItem]
)

function handleAdd(item: Item) {
  startTransition(async () => {
    addOptimistic(item) // 즉시 UI 반영
    await saveToServer(item) // Action 이 끝나면 items 로 돌아간다
  })
}
  • 서버 응답 전에 UI 먼저 업데이트, 실패 시 자동 복구 — 낙관적 값은 Action 이 진행 중일 때만 보이고 끝나면 items 가 보인다. 그래서 성공하면 items 를 갱신해야 남는다2
  • setter 는 Action(startTransition·form action) 안에서 불러야 한다. 밖에서 부르면 경고와 함께 바로 되돌아간다3
  • 좋아요 버튼, 장바구니 추가 같은 인터랙션에 적합

현실: Query가 이미 너무 편함

const { data, isPending } = useQuery({
  queryKey: ['items', filter],
  queryFn: () => fetchItems(filter),
})

useMutation({
  mutationFn: addItem,
  onMutate: async (newItem) => {
    const previous = queryClient.getQueryData(['items'])
    queryClient.setQueryData(['items'], (old) => [...old, newItem])
    return { previous }
  },
  onError: (err, _, context) => {
    queryClient.setQueryData(['items'], context.previous)
  },
})

캐싱, 리페치, devtools, stale-while-revalidate까지 한 방에 해결. 팀에서 이미 쓰고 있으면 “굳이?” 됨.

useTransition이 의미 있는 지점 - 무거운 클라이언트 연산, Next.js Server Actions 조합

<form action={(formData) => {
  startTransition(async () => {
    await createItem(formData)
    router.refresh()
  })
}}>

Footnotes

  1. useTransition – React: “Any async calls that are awaited in the action will be included in the Transition, but currently require wrapping any set functions after the await in an additional startTransition …” ↩

  2. useOptimistic – React: “Optimistic state only renders while an Action is in progress, otherwise value is rendered.” ↩

  3. useOptimistic – React: “When you call the setter outside an Action, the optimistic state will briefly appear and then immediately revert back to the original value.” ↩

#495

Serverless 환경에서 SQLite 사용 불가 → Turso 도입

Vercel 같은 플랫폼에서 SQLite 공식 미지원. 읽기 전용 데이터 파일도 최근 환경에서 에러 발생.

Turso - LibSQL 기반 SQLite-compatible serverless DB. 기존 쿼리 그대로 사용 가능.

JSON 기반 대안:

  • AlaSQL - SQL 문법으로 JSON 쿼리
  • JSONata - 선언적 JSON 질의
#492

llms.txt는 웹사이트나 애플리케이션이 자신이 사용하는 LLM(대규모 언어 모델) 및 관련 설정에 대해 명시적으로 문서화할 수 있는 포맷이다.

아래 링크들은 llms.txt 포맷이 실제로 어떻게 사용되고 있는지 참고한 자료들. 각 사이트는 자신들의 문서를 llms.txt에 구조적으로 명시하고 있다.


#491

visibilitychange 는 탭 전환·최소화·앱 전환처럼 보임 상태가 바뀔 때 document 에서 발생한다.1

const isVisible = document.visibilityState === 'visible'
const isHidden = document.visibilityState === 'hidden'

document.addEventListener('visibilitychange', onChange)

시스템이 잠들면 visibilitychange 가 안 올 때가 있다 — hidden 상태일 때만 입력으로 복귀를 잡는다.2

if (document.visibilityState === 'hidden') {
  document.addEventListener('mouseenter', setVisible)
  document.addEventListener('keydown', setVisible)
}

const [isVisible, setIsVisible] = useState(true)

useEffect(() => {
  const onChange = () => {
    const newState = document.visibilityState !== 'hidden'

    if (newState !== isVisible) {
      setIsVisible(newState)
    }
  }

  // addEventListener

  return () => {
    // removeEventListener
  }
}, [isVisible])

Footnotes

  1. Document: visibilitychange event - MDN: “The visibilitychange event is fired at the document when its visibility status changes — for example, when the user switches browser tabs, navigates to a new page, minimizes or closes the browser, or on mobile, switches to a different app.” ↩

  2. use-visibility-state/src/index.tsx: “When the system goes to sleep, sometimes the visibilitychange event is not fired. In that case, we can use mouseenter or keydown to detect when the window is focused.” ↩

#490
33 중 7페이지