Solid.js
솔리드
Fine-grained Reactivity 기반의 선언적 UI 라이브러리. Virtual DOM 없이 컴파일 타임 최적화로 React와 유사한 문법에서 뛰어난 성능을 제공합니다.
솔리드
Fine-grained Reactivity 기반의 선언적 UI 라이브러리. Virtual DOM 없이 컴파일 타임 최적화로 React와 유사한 문법에서 뛰어난 성능을 제공합니다.
Solid.js는 현대적인 선언적 UI를 구축하기 위한 반응형(Reactive) JavaScript 라이브러리입니다. React와 유사한 JSX 문법과 컴포넌트 기반 아키텍처를 제공하면서도, 근본적으로 다른 반응성 모델을 사용합니다. 컴포넌트가 한 번만 실행되고 이후에는 Signal(시그널)이라는 세밀한 반응성 단위가 필요한 부분만 정확히 업데이트하는 방식으로 동작합니다. 이로 인해 불필요한 리렌더링 없이 매우 효율적인 UI 업데이트가 가능합니다.
Solid.js는 Ryan Carniato가 2018년부터 개발을 시작하여 2021년에 정식 릴리스되었습니다. Carniato는 MarkoJS 코어 팀 멤버이자 현재 Netlify의 엔지니어로, 10년 이상 반응성 시스템을 연구해왔습니다. 그의 철학은 "React의 개발자 경험과 Knockout.js의 성능을 결합"하는 것이었고, Solid.js는 이 목표를 달성한 결과물입니다. 벤치마크에서 Solid.js는 지속적으로 Vanilla JavaScript에 근접하는 성능을 보여주며, 다른 프레임워크들을 압도합니다.
Fine-grained Reactivity는 Solid.js의 핵심 개념입니다. React의 Virtual DOM은 상태 변경 시 컴포넌트 트리 전체를 비교하여 변경사항을 찾지만, Solid.js는 이런 과정이 필요 없습니다. Signal이 변경되면 해당 Signal을 구독하는 DOM 노드만 직접 업데이트됩니다. 이것은 컴파일 타임에 의존성 그래프가 분석되어 가능한 것으로, 런타임 오버헤드가 거의 없습니다. createSignal, createEffect, createMemo가 이 반응성 시스템의 기본 빌딩 블록입니다.
실무에서 Solid.js의 장점은 성능과 번들 사이즈입니다. Solid.js의 번들 크기는 약 7KB(gzip)로 React(~40KB)보다 훨씬 작습니다. 또한 JS Framework Benchmark에서 항상 최상위권을 기록하며, 특히 대규모 리스트 렌더링과 빈번한 업데이트에서 탁월한 성능을 발휘합니다. SolidStart라는 메타 프레임워크를 통해 SSR, 라우팅, API 엔드포인트 등 풀스택 기능도 지원합니다. React 개발자라면 문법이 익숙하여 학습 곡선이 낮으면서도 성능 이점을 얻을 수 있습니다.
import { createSignal } from 'solid-js'; // Signal: Solid.js의 반응형 상태 // getter 함수와 setter 함수를 반환 (React와 다름!) function Counter() { // count는 함수! count()로 값을 읽음 const [count, setCount] = createSignal(0); // 컴포넌트 함수는 한 번만 실행됨 console.log('컴포넌트 생성 - 이 로그는 한 번만 출력!'); return ( <div> {/* count()를 호출해야 값을 읽을 수 있음 */} <p>카운트: {count()}</p> <button onClick={() => setCount(count() + 1)}> 증가 </button> </div> ); } // React와의 차이점 // React: const [count, setCount] = useState(0); // count는 값 // Solid: const [count, setCount] = createSignal(0); // count는 함수
import { createSignal, createEffect, createMemo } from 'solid-js'; function UserProfile() { const [firstName, setFirstName] = createSignal('김'); const [lastName, setLastName] = createSignal('철수'); // createMemo: 파생된 값을 캐싱 (React의 useMemo와 유사) // firstName이나 lastName이 변경될 때만 재계산 const fullName = createMemo(() => { console.log('fullName 계산 중...'); return `${firstName()} ${lastName()}`; }); // createEffect: 부수 효과 처리 (React의 useEffect와 유사) // 의존성 배열이 필요 없음 - 자동으로 추적! createEffect(() => { // fullName()을 사용하면 자동으로 의존성이 추적됨 console.log(`이름이 변경됨: ${fullName()}`); // 정리(cleanup) 함수 onCleanup(() => console.log('정리 작업 실행')); }); return ( <div> <input value={firstName()} onInput={(e) => setFirstName(e.target.value)} /> <input value={lastName()} onInput={(e) => setLastName(e.target.value)} /> <h2>안녕하세요, {fullName()}님!</h2> </div> ); }
import { createSignal, Show, For } from 'solid-js'; function TodoList() { const [todos, setTodos] = createSignal([ { id: 1, text: 'Solid.js 배우기', done: false }, { id: 2, text: '프로젝트에 적용하기', done: false } ]); const [showCompleted, setShowCompleted] = createSignal(true); const toggleTodo = (id) => { setTodos(todos().map(todo => todo.id === id ? { ...todo, done: !todo.done } : todo )); }; return ( <div> <label> <input type="checkbox" checked={showCompleted()} onChange={() => setShowCompleted(!showCompleted())} /> 완료된 항목 표시 </label> {/* For: 효율적인 리스트 렌더링 (map 대신 사용) */} <For each={todos()}> {(todo) => ( {/* Show: 조건부 렌더링 (삼항연산자 대신 권장) */} <Show when={showCompleted() || !todo.done}> <div style={{ 'text-decoration': todo.done ? 'line-through' : 'none' }} onClick={() => toggleTodo(todo.id)} > {todo.text} </div> </Show> )} </For> </div> ); }
solid-chartjs 같은 래퍼를 쓸 수 있어요. 핵심은 우리가 직접 만들어야 할 부분이 더 있을 수 있다는 거죠.
const { name } = user() 이렇게 하면 안 되는 거죠?
{user().name}처럼 직접 접근해야 해요. 아니면 createMemo로 파생 Signal을 만들거나요.
For 컴포넌트에서 콜백 함수로 아이템을 받는 거군요? 아이템 자체가 Signal이 아니라 콜백 안에서 접근해야 반응성이 유지되는 거죠?
const { name } = props 대신 props.name으로 접근하거나, splitProps() 유틸리티를 사용하세요.className 대신 class를 사용하고, 이벤트 핸들러는 onClick(대문자) 대신 onclick(소문자)도 지원합니다. style은 객체로 전달해야 합니다.createEffect 안에 배치하세요.{condition ? <A/> : <B/>} 대신 <Show> 컴포넌트 사용을 권장합니다. 삼항 연산자는 양쪽 모두 평가될 수 있어 의도치 않은 동작이 발생할 수 있습니다.