SolidJS 2.0은 컴포넌트가 프로미스(promise)를 다른 반응형 값(reactive value)처럼 다룰 수 있게 해주는 네이티브 비동기 데이터 모델을 탑재했습니다. 이는 React 개발자들이 Suspense와 훅(hooks)을 사용할 때 겪는 연쇄적인 리렌더링(re-render cascade) 없이 수행됩니다. 이 변화가 중요한 이유는 데이터가 새로고침되는 동안 UI를 화면에 유지하고, 보일러플레이트(boilerplate)를 줄이며, React 팀이 데이터 페칭(data-fetching) 코드를 Solid로 옮길 수 있는 구체적인 경로를 제공하기 때문입니다.
Solid의 비동기 모델이 다르게 느껴지는 이유
React에서 데이터가 필요한 컴포넌트는 보통 프로미스 형태의 객체를 반환하는 훅(주로 커스텀 훅)을 호출한 다음, 프로미스가 해결(resolve)되는 동안 폴백(fallback)을 보여주기 위해 UI를 <Suspense>로 감쌉니다. 프로미스가 완료될 때마다 React는 리렌더링을 예약합니다. 만약 동일한 컴포넌트가 나중에 데이터를 다시 가져오면(refetch), 이미 보이는 콘텐츠 위에 폴백이 깜빡이며 나타날 수 있습니다.
Solid는 이 방식을 완전히 뒤집었습니다. 프로미스는 단순히 반응형 그래프(reactive graph)가 관찰하는 하나의 값일 뿐입니다. 연산(computation)이 해당 값을 읽을 때, 그래프는 프로미스가 해결될 때까지 해당 읽기 작업을 일시 중지하지만, 나머지 UI는 렌더링된 상태를 유지합니다. 컴포넌트가 처음 마운트될 때는 <Loading> 경계(boundary)가 폴백을 표시할 수 있지만, 그 이후에는 새로운 데이터가 도착할 때까지 기존 DOM을 건드리지 않고 리페치(refetch)를 수행합니다. 컴포넌트 내부에서 명시적인 await를 사용하거나, createResource를 쓰거나, 수동으로 상태를 전환할 필요가 없습니다.
핵심 프리미티브(primitives)
<Loading>경계 – React의<Suspense>를 대체합니다. 초기 로드 시에만 폴백을 보여줍니다. 첫 번째 페인트(paint) 이후에는 대기 중인 읽기 작업이 UI를 교체하지 않으며, 새로운 값이 해결될 때까지 이전 콘텐츠가 유지됩니다. 특정 리페치 시 스피너를 강제로 표시하려면on프롭(prop)을 전달하세요.<Reveal>컴포넌트 – 여러<Loading>경계가 함께 나타나는 방식을 제어합니다. 다음 중 선택할 수 있습니다:- Sequential: DOM 순서에 따라 경계가 하나씩 차례대로 나타납니다.
- Together: 모든 데이터가 준비되면 한꺼번에 나타납니다.
- Natural: 각 데이터가 도착하는 즉시 해당 경계가 나타납니다.
isPending시그널(signal) – 특정 읽기 작업이 진행 중인 동안true를 반환합니다. 메인 콘텐츠를 유지하면서 얇은 로딩 바나 미세한 애니메이션을 겹쳐서 보여주는 데 사용하며, 이를 통해 "stale-while-revalidate" 방식을 별도 구현 없이 사용할 수 있습니다.action제너레이터 – React의 가변 상태(mutable-state) 업데이트를 대체합니다.action(function*…)를 사용하면 UI를 낙관적으로(optimistically) 업데이트하고, 서버 응답을 기다리기 위해yield한 다음, 프로미스가 해결되면 결과를 조정(reconcile)할 수 있습니다. UI는 즉각적으로 느껴지며, 조정 단계가 프리미티브에 내장되어 있습니다.
React 개념과의 매핑
| 기능 | React 방식 | Solid 2.0 방식 |
|---|---|---|
| 데이터 페칭 | use() (실험적) 또는 서드파티 훅; 결과가 <Suspense>로 감싸짐 |
프로미스를 반환하는 createMemo(또는 유사한 기능); JSX에서 직접 읽음 |
| 로딩 UI | <Suspense>는 매 리페치마다 폴백을 다시 트리거할 수 있음 |
<Loading>은 첫 로드 시에만 작동; 리페치 시 기존 UI 유지 |
| 새로고침 | UI 업데이트를 지연시키기 위한 useTransition |
isPending은 UI 교체 없이 대기 중인 읽기 작업을 알림 |
| 뮤테이션 | useState/useReducer + 비동기 호출, 종종 커스텀 액션으로 감싸짐 |
내장된 낙관적 처리를 갖춘 action(function*…) |
실질적인 결과로, Solid는 React가 별개의 훅으로 취급하는 기능들을 핵심 반응형 엔진(core reactivity engine)에 구축합니다.
단계별 마이그레이션 가이드
- 데이터 소스 식별 – React에서는 보통
const data = useMyFetch(url)와 같은 형태를 사용했을 것입니다. Solid에서는 이를 promise를 반환하는 memo로 교체하세요:const data = createMemo(() => fetch(url).then(r => r.json())). - 최상위 컴포넌트 감싸기 – promise가 해결되기 전에 컴포넌트가 렌더링된다면,
<Loading fallback={<Spinner/>}>…</Loading>로 감싸세요. fallback은 첫 마운트 시에만 나타납니다. - 재요청(refetch) 시마다 나타나는 스피너 교체 – 이전에 loading 플래그를 토글했다면, 이제는
isPending(data)를 읽으세요. 해당 boolean 값을 사용하여 기존 UI가 화면에 유지되는 동안 미세한 인디케이터를 렌더링하세요. - 낙관적 업데이트(optimistic updates) 변환 –
setState(prev => ({...prev, optimisticValue}))를 호출한 뒤 비동기 호출을 이어가는 방식이었다면, 다음과 같이 다시 작성하세요:const update = action(function* (newValue) { state = newValue; const server = yield fetch(...); state = reconcile(server); });. generator는 서버가 응답할 때까지 제어권을 양보(yield)하며, 응답 후에는 반응형 그래프(reactive graph)를 자동으로 업데이트합니다. - 여러 비동기 요소 처리 – 필요에 따라
<Loading>경계를 중첩하고,<Reveal>래퍼를 추가하여 요소들이 동시에 나타날지 아니면 순차적으로 나타날지 결정하세요. 이는 React 개발자들이 복잡한 상태 체크를 통해 여러<Suspense>컴포넌트를 교차로 배치하던 패턴을 대체합니다. - 흐름 테스트 – Solid는 promise가 해결될 때 리렌더링되지 않으므로, UI 업데이트가 예상한 대로 발생하는지 확인하세요. 반응형 그래프가 변경 사항을 자동으로 전파하므로, 추가적인
useEffect호출이 필요하지 않습니다.
아직 변동 중인 사항
Solid 2.0의 async API는 현재 베타 버전입니다. <Loading>이나 action 같은 이름은 안정적인 릴리스 전에 변경될 수 있으며, 문서 또한 계속 업데이트되고 있습니다. promise를 반응형 값(reactive values)으로 취급한다는 핵심 아이디어는 변하지 않지만, 라이브러리가 안정화됨에 따라 초기 사용자들은 약간의 breaking change를 예상해야 합니다.
수혜 대상
- 데이터 페칭이 많은 React 팀 – 외부 상태 관리 라이브러리에 대한 필요성이 줄어들고 내장된 stale-while-revalidate 패턴을 사용할 수 있어, 번들 크기를 줄이고 코드베이스를 단순화할 수 있습니다.
- 성능 중심의 앱 – 매 페칭마다 전체 컴포넌트를 리렌더링하는 것을 피함으로써, Solid는 특히 저사양 기기에서 더 매끄러운 시각적 업데이트를 제공합니다.
- "스피너 깜빡임(flash of spinner)"에 지친 개발자 –
<Loading>경계의 "첫 로드 시에만 작동"하는 동작은 이미 화면에 보이는 콘텐츠 위에 스피너가 깜빡거리는 흔한 불편함을 제거합니다.
잠재적인 단점
- 베타 상태 – API가 안정화될 때까지, 장기 프로젝트의 경우 나중에 마이그레이션 작업에 필요한 리소스를 미리 고려해야 할 수도 있습니다.
향후 주목할 점
<Loading>이나 action의 명칭 변경 여부를 공식 변경 로그(changelog)에서 계속 확인하세요.
결론: SolidJS 2.0은 promise를 일급 반응형 값(first-class reactive values)으로 취급할 수 있게 하여, 데이터가 새로고침되는 동안 UI를 안정적으로 유지하고 React 스타일의 수많은 훅(hooks)을 사용할 필요를 없애줍니다. 리렌더링 중심 모델에서 벗어날 준비가 된 팀에게 마이그레이션 경로는 명확하며, 성능상의 이점은 실질적이고, 유일한 실제 리스크는 일반적인 베타 단계의 불확실성뿐입니다.
