Solid 2.0이 비동기 처리, 조율된 상태, 스토어, 낙관적 상태, R3 기반 시그널을 통해 프런트엔드 UI의 가능성을 어떻게 확장하는지 살펴봅니다.
Solid 2.0 릴리스 후보가 이제 공개되었고, 이를 가장 잘 알릴 방법을 생각해 왔습니다. 이것은 단순히 “더 빠른 React”가 아니라, 다른 무언가입니다. 프런트엔드 UI로 가능한 일의 경계를 넓히는 무언가입니다.
오늘은 그 혁신들을 살펴보며 왜 주목할 가치가 있는지 보여드리고자 합니다.
비동기 처리는 Solid 2.0의 가장 핵심적인 기능입니다. 이는 앱 복잡성의 큰 원인이며, 2.0에서 Solid는 이를 정면으로 해결합니다. Solid 2.0 작업은 오래전에 시작되었고, Ryan은 이 문제를 훨씬 더 오래 고민해 왔습니다.
비동기 처리를 파생값으로 모델링한다는 최초의 징후는 Tanstack Query의 인기 상승이었습니다. 이는 결국 지금의 비동기 해결책으로 이어졌습니다. 예전에는 비동기 처리를 시스템 외부에 있는 것으로 여겼습니다. 예를 들어 useEffect에서 데이터를 가져오는 방식입니다.
Solid 2.0에서 비동기 처리는 그저 반응형 그래프의 일부입니다. 프로미스를 파생값에 전달하고 다른 값처럼 읽을 수 있습니다:
const user = createMemo(() => fetchUser(props.id));
// 일반 시그널처럼 읽습니다. 나머지는 경계가 처리합니다
return <h1>{user().name}</h1>;
그렇다면 왜 중요할까요?
if(loading) 가드는 과거의 일이 될 수 있습니다.이는 앞선 개념을 바탕으로 합니다. 비동기 처리가 그래프의 일부라면 이를 조율할 수 있다는 점도 자연스럽습니다. 버튼을 여러 번 눌렀는데 쓸모없는 중간 상태들 사이에서 UI가 깜빡이는 경험을 해본 적 있나요? 이것이 조율되지 않은 비동기 처리의 모습입니다. 네트워크 요청이 모두 서로 다른 시점에 도착해 어색한 경험을 만듭니다.
여기서 Solid는 React와 Svelte 모두에게서 영감을 받았습니다. Svelte는 명시적인 전환 API 없이 모든 비동기 처리를 조율할 수 없는 이유를 물었습니다. Solid는 여기에 React의 더 강한 보장을 적용했습니다.
이 조율이 마련되면 Solid는 과거의 값도 유지할 수 있고, Suspense 대체 화면으로 다시 끌려가는 일을 막을 수 있습니다.
그런데 모든 비동기 처리를 조율하면 보통 성능이 나빠집니다. 잠재적으로 세상의 두 버전을 동시에 유지하게 되기 때문입니다. Solid는 큰 손실을 피하기 위해 폐기 시스템으로 이 부분을 혁신했습니다.
이번에는 왜 중요할까요?
스토어는 Solid에 고유합니다. Svelte는 이를 하나의 $state 룬으로 통합했고, React에는 useState만 있습니다. Solid에서 이런 분리는 의도된 것이며, 저는 이를 아주 오랫동안 이해하지 못했습니다. 스토어는 객체나 배열을 시그널의 집합으로 표현하는 방법입니다. 상태의 한 리프를 업데이트하면 나머지 상태는 조용히 유지됩니다.
const [todos, setTodos] = createStore([...]);
// 할 일 3의 체크박스만 업데이트되며, 다른 어떤 것도 다시 실행되지 않습니다
setTodos(todos => {
todos[3].done = true
});
스토어는 가변 반응형이라고도 여겨지며, 대부분의 다른 프레임워크에서 찾을 수 있는 불변 반응형의 대응물입니다. 이것이 Solid 스토어 세터가 변경하는 produce 스타일 초안을 사용하는 이유입니다. 서버에서 데이터를 refresh하고 데이터를 reconcile할 때에도 행의 동일성을 유지할 수 있습니다. 서버가 새 JSON을 보냈다는 이유만으로 목록이 해체되고 다시 구축되지 않습니다.
이번 장점은 주로 성능에 기반하며, 이 글의 다른 모든 요소와 결합되어 효과가 커집니다. 스토어는 아래의 낙관적 상태 방식이 매우 깔끔해지는 이유입니다.
낙관적 상태는 제가 대체로 꽤 번거롭게 느껴 온 기능입니다. 이 때문에 React에서 useOptimistic를 실제로 많이 사용하지 않았습니다. Solid의 낙관적 상태는 그저 이해가 됩니다.
낙관적 상태는 원본 데이터 위에 놓이는 임시 오버레이로 취급됩니다. 전환이 지속되는 동안에만 유지됩니다. 되돌리기를 직접 처리할 걱정을 하지 않아도 된다는 점은 훌륭합니다.
Solid처럼 기본적으로 값이 유지되는 경우에는 보통 로딩 표시기를 보여주거나 미래가 이미 도래한 것처럼 보이게 하고 싶을 것입니다. Solid에서는 isPending과 createOptimistic로 둘 다 할 수 있습니다.
낙관적 상태를 스토어와 결합하면 전체 패턴은 거의 아무것도 아닌 수준으로 단순해집니다:
const [optimisticTodos, setOptimistic] = createOptimisticStore(todos);
const addTodo = action(async newTodo => {
setOptimistic(todos => todos.push(newTodo)); // 즉시 UI 반영
await saveTodo(newTodo); // 서버가 따라잡습니다
refresh(todos);
});
낙관적 상태는 일부 애플리케이션에 매우 중요하므로 분명한 사용자 경험상의 이점입니다. 한동안 만들어지던 인기 있는 Trello 데모들을 보세요. 사용자 경험상의 이점을 가능한 한 쉽게 달성하게 만드는 일은 분명 개발 경험상의 이점이기도 합니다. Trello 데모는 기본적으로 하나의 createOptimisticStore 프리미티브로 축소되었습니다.
R3는 Solid 코어 팀의 Milo가 만든 상당히 놀라운 프로젝트입니다. 그는 Alien Signals와 자신의 이전 작업에서 영감을 얻었습니다. 이것은 그 자체로 혁신적인 Solid 코어의 엔진입니다.
저는 이에 대한 전문가는 아니지만, 이 설계에는 많은 고민이 담겼다는 점만 알아두면 됩니다. 시그널은 일반적으로 푸시-풀-푸시 시스템이었지만, 이 새로운 구성은 주로 푸시 방식이며 이전 푸시-풀-푸시 시스템으로 되돌아가는 대안을 갖습니다. 제가 이해한 바로는 의존성의 높이 순서에 기반합니다.
이는 Solid 2의 성능을 이루는 토대이자 새로운 프로젝션 프리미티브의 기반입니다.
최근 메타프레임워크는 필수 요건으로 여겨져 왔습니다. Solid 2.0은 원하는 방식으로 자신만의 프레임워크를 만들 수 있는 프리미티브를 제공함으로써 그 사실에 도전합니다. 서버 함수가 필요하신가요? 불리언 값 하나만 활성화하면 됩니다. Solid의 저수준 접근 방식은 여기서 이점이 있습니다. 더 의견이 강한 조각들은 작성자인 여러분이 AI 에이전트를 사용해 조합하도록 남겨 두기 때문입니다. React 생태계에서 볼 수 있는 일부 분열을 피하기 위해 큰 진전을 이루었습니다. React와 Next 중 무엇을 선택해야 하는지는 더 이상 질문이 아닙니다.
Ryan은 RSC가 실제로 해결하려던 문제를 해결했는지 의문을 제기해 왔습니다. 결말을 미리 말하자면, 그가 해결책을 찾아냈을지도 모릅니다. 계속 지켜봐 주세요.
어떻게 생각하시나요? 이 혁신들 중 Solid를 시도해 볼 만큼 관심을 끄는 것이 있나요, 아니면 현재 상태에 만족하시나요?