본문으로 건너뛰기
AI 도우미
🤖
안녕하세요! 프로그래밍 질문, 코드 설명, 블로그 글 검색 등을 도와드릴 수 있어요. 무엇이 궁금하신가요?
Enter를 누르거나 버튼을 클릭하여 전송 • Shift+Enter로 줄바꿈
Previous
Next
SolidJS 완전 가이드 | React보다 빠른 반응형 UI 프레임워크

SolidJS 완전 가이드 | React보다 빠른 반응형 UI 프레임워크

SolidJS 완전 가이드 | React보다 빠른 반응형 UI 프레임워크

이 글의 핵심

React와 문법은 유사하지만 2배 빠른 SolidJS. Virtual DOM 없이 진짜 반응성(Fine-grained Reactivity)으로 작동하며, 번들 크기는 React의 1/3입니다. JSX를 그대로 사용할 수 있어 마이그레이션이 쉽습니다.

이 글의 핵심

SolidJS는 React보다 2배 빠르고 가벼운 프론트엔드 프레임워크입니다. Virtual DOM 없이 진짜 반응성(Fine-grained Reactivity)으로 작동하며, JSX를 그대로 사용할 수 있어 React 개발자에게 친숙합니다. 번들 크기는 React의 1/3입니다.

목차

SolidJS란?

SolidJS는 2018년 Ryan Carniato가 개발한 반응형 UI 프레임워크입니다.

🚀 핵심 특징

1. Virtual DOM 없음

React:
컴포넌트 렌더링 → Virtual DOM 생성 → Diffing → 실제 DOM 업데이트

SolidJS:
Signal 변경 → 실제 DOM 직접 업데이트
→ 2배 빠름

2. Fine-grained Reactivity

// 상태가 변경되면 정확히 그 부분만 업데이트
const [count, setCount] = createSignal(0);

// count()가 사용된 곳만 재실행
<div>{count()}</div>  // 이 부분만 업데이트

3. JSX 그대로 사용

// React와 거의 동일한 문법
function App() {
  const [count, setCount] = createSignal(0);
  
  return (
    <div>
      <p>Count: {count()}</p>
      <button onClick={() => setCount(count() + 1)}>
        Increment
      </button>
    </div>
  );
}

4. 번들 크기

React + ReactDOM: 45KB (gzip)
SolidJS: 13KB (gzip)
→ 1/3 크기

SolidJS 시작하기

프로젝트 생성

# Vite 템플릿
npm create vite@latest my-solid-app -- --template solid
cd my-solid-app
npm install
npm run dev

# 또는 Degit
npx degit solidjs/templates/js my-app
cd my-app
npm install
npm run dev

# TypeScript 템플릿
npx degit solidjs/templates/ts my-app

프로젝트 구조

my-solid-app/
├── src/
│   ├── App.tsx
│   ├── index.tsx
│   └── index.css
├── index.html
├── package.json
└── vite.config.ts

반응성 (Reactivity)

createSignal (State)

import { createSignal } from 'solid-js';

function Counter() {
  // Signal 생성
  const [count, setCount] = createSignal(0);
  const [name, setName] = createSignal('Alice');

  // Getter 함수로 값 읽기
  console.log(count()); // 0
  console.log(name());  // 'Alice'

  // Setter 함수로 값 변경
  setCount(10);
  setName('Bob');

  // 함수형 업데이트
  setCount(c => c + 1);

  return (
    <div>
      <p>Count: {count()}</p>
      <p>Name: {name()}</p>
      <button onClick={() => setCount(count() + 1)}>
        Increment
      </button>
    </div>
  );
}

createEffect (Side Effects)

import { createSignal, createEffect } from 'solid-js';

function App() {
  const [count, setCount] = createSignal(0);

  // count()가 변경될 때마다 실행
  createEffect(() => {
    console.log('Count changed:', count());
  });

  // 정리 함수 (cleanup)
  createEffect(() => {
    const timer = setInterval(() => {
      console.log('Tick');
    }, 1000);

    // 컴포넌트 언마운트 시 실행
    return () => clearInterval(timer);
  });

  return <button onClick={() => setCount(count() + 1)}>Count: {count()}</button>;
}

createMemo (Computed Values)

import { createSignal, createMemo } from 'solid-js';

function App() {
  const [count, setCount] = createSignal(0);

  // count()가 변경될 때만 재계산 (캐싱)
  const doubled = createMemo(() => count() * 2);
  const squared = createMemo(() => count() ** 2);

  console.log(doubled()); // 0
  console.log(squared()); // 0

  return (
    <div>
      <p>Count: {count()}</p>
      <p>Doubled: {doubled()}</p>
      <p>Squared: {squared()}</p>
      <button onClick={() => setCount(count() + 1)}>Increment</button>
    </div>
  );
}

컴포넌트

함수 컴포넌트

// 컴포넌트는 한 번만 실행됨 (중요!)
function Greeting(props) {
  console.log('Component created'); // 한 번만 출력

  return <h1>Hello, {props.name}!</h1>;
}

// 사용
<Greeting name="Alice" />

Props

interface ButtonProps {
  label: string;
  onClick: () => void;
  disabled?: boolean;
}

function Button(props: ButtonProps) {
  // props는 반응형 프록시
  return (
    <button 
      onClick={props.onClick}
      disabled={props.disabled}
    >
      {props.label}
    </button>
  );
}

// 사용
<Button label="Click me" onClick={() => console.log('Clicked')} />

Children

import { JSX } from 'solid-js';

interface CardProps {
  title: string;
  children: JSX.Element;
}

function Card(props: CardProps) {
  return (
    <div class="card">
      <h2>{props.title}</h2>
      <div class="content">
        {props.children}
      </div>
    </div>
  );
}

// 사용
<Card title="Welcome">
  <p>This is the content</p>
</Card>

조건부 렌더링

Show 컴포넌트

import { Show } from 'solid-js';

function App() {
  const [loggedIn, setLoggedIn] = createSignal(false);

  return (
    <div>
      <Show
        when={loggedIn()}
        fallback={<p>Please log in</p>}
      >
        <p>Welcome back!</p>
      </Show>
      
      <button onClick={() => setLoggedIn(!loggedIn())}>
        Toggle
      </button>
    </div>
  );
}

Switch/Match

import { Switch, Match } from 'solid-js';

function App() {
  const [status, setStatus] = createSignal<'loading' | 'success' | 'error'>('loading');

  return (
    <Switch>
      <Match when={status() === 'loading'}>
        <p>Loading...</p>
      </Match>
      <Match when={status() === 'success'}>
        <p>Success!</p>
      </Match>
      <Match when={status() === 'error'}>
        <p>Error occurred</p>
      </Match>
    </Switch>
  );
}

리스트 렌더링

For 컴포넌트

import { For } from 'solid-js';

function TodoList() {
  const [todos, setTodos] = createSignal([
    { id: 1, text: 'Learn SolidJS', completed: false },
    { id: 2, text: 'Build an app', completed: false },
  ]);

  return (
    <ul>
      <For each={todos()}>
        {(todo, index) => (
          <li>
            <input
              type="checkbox"
              checked={todo.completed}
              onChange={(e) => {
                setTodos(todos => {
                  const newTodos = [...todos];
                  newTodos[index()].completed = e.target.checked;
                  return newTodos;
                });
              }}
            />
            {todo.text}
          </li>
        )}
      </For>
    </ul>
  );
}

Index 컴포넌트 (Key 기반)

import { Index } from 'solid-js';

// Index는 각 항목이 변경되지 않고 순서만 바뀔 때 최적화됨
function App() {
  const [items, setItems] = createSignal(['A', 'B', 'C']);

  return (
    <ul>
      <Index each={items()}>
        {(item, index) => (
          <li>{index + 1}: {item()}</li>
        )}
      </Index>
    </ul>
  );
}

이벤트 처리

function App() {
  const [value, setValue] = createSignal('');

  const handleClick = (e: MouseEvent) => {
    console.log('Clicked!', e);
  };

  const handleInput = (e: InputEvent) => {
    setValue((e.target as HTMLInputElement).value);
  };

  const handleSubmit = (e: Event) => {
    e.preventDefault();
    console.log('Submitted:', value());
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="text"
        value={value()}
        onInput={handleInput}
        placeholder="Type something..."
      />
      <button type="submit" onClick={handleClick}>
        Submit
      </button>
    </form>
  );
}

Stores (복잡한 상태)

createStore

import { createStore } from 'solid-js/store';

function TodoApp() {
  const [todos, setTodos] = createStore([
    { id: 1, text: 'Learn SolidJS', completed: false },
    { id: 2, text: 'Build an app', completed: false },
  ]);

  // 특정 항목만 업데이트 (효율적!)
  const toggleTodo = (id: number) => {
    setTodos(
      todo => todo.id === id,
      'completed',
      completed => !completed
    );
  };

  // 항목 추가
  const addTodo = (text: string) => {
    setTodos(todos.length, {
      id: Date.now(),
      text,
      completed: false,
    });
  };

  return (
    <ul>
      <For each={todos}>
        {(todo) => (
          <li>
            <input
              type="checkbox"
              checked={todo.completed}
              onChange={() => toggleTodo(todo.id)}
            />
            {todo.text}
          </li>
        )}
      </For>
    </ul>
  );
}

Context API

import { createContext, useContext } from 'solid-js';

// Context 생성
const ThemeContext = createContext();

// Provider
function ThemeProvider(props) {
  const [theme, setTheme] = createSignal('light');

  const value = {
    theme,
    toggleTheme: () => setTheme(theme() === 'light' ? 'dark' : 'light'),
  };

  return (
    <ThemeContext.Provider value={value}>
      {props.children}
    </ThemeContext.Provider>
  );
}

// Consumer (Hook)
function useTheme() {
  const context = useContext(ThemeContext);
  if (!context) {
    throw new Error('useTheme must be used within ThemeProvider');
  }
  return context;
}

// 사용
function Button() {
  const { theme, toggleTheme } = useTheme();

  return (
    <button 
      class={theme()}
      onClick={toggleTheme}
    >
      Current theme: {theme()}
    </button>
  );
}

function App() {
  return (
    <ThemeProvider>
      <Button />
    </ThemeProvider>
  );
}

Solid Router

npm install @solidjs/router
// App.tsx
import { Router, Route, Routes, A } from '@solidjs/router';
import { lazy } from 'solid-js';

const Home = lazy(() => import('./pages/Home'));
const About = lazy(() => import('./pages/About'));
const User = lazy(() => import('./pages/User'));

function App() {
  return (
    <Router>
      <nav>
        <A href="/">Home</A>
        <A href="/about">About</A>
        <A href="/user/123">User</A>
      </nav>

      <Routes>
        <Route path="/" component={Home} />
        <Route path="/about" component={About} />
        <Route path="/user/:id" component={User} />
      </Routes>
    </Router>
  );
}
// pages/User.tsx
import { useParams } from '@solidjs/router';

function User() {
  const params = useParams();

  return <h1>User ID: {params.id}</h1>;
}

SolidJS vs React

기능SolidJSReact
렌더링실제 DOMVirtual DOM
반응성Fine-grainedRe-render 전체
성능⚡⚡ 2배 빠름⚡ 빠름
번들 크기13KB45KB
문법JSX (거의 동일)JSX
학습 곡선🟡 중간🟡 중간
생태계🌱 성장 중🌳 성숙
서버 컴포넌트✅ SolidStart✅ Next.js

실전 프로젝트: Counter 앱

// App.tsx
import { createSignal, createEffect, Show } from 'solid-js';

function App() {
  const [count, setCount] = createSignal(0);
  const [history, setHistory] = createSignal<number[]>([]);

  // count가 변경될 때마다 history에 추가
  createEffect(() => {
    setHistory([...history(), count()]);
  });

  const increment = () => setCount(c => c + 1);
  const decrement = () => setCount(c => c - 1);
  const reset = () => setCount(0);

  return (
    <div class="app">
      <h1>Counter: {count()}</h1>

      <div class="buttons">
        <button onClick={decrement}>-</button>
        <button onClick={reset}>Reset</button>
        <button onClick={increment}>+</button>
      </div>

      <Show when={history().length > 1}>
        <div class="history">
          <h2>History</h2>
          <ul>
            <For each={history()}>
              {(value, index) => (
                <li>Step {index() + 1}: {value}</li>
              )}
            </For>
          </ul>
        </div>
      </Show>
    </div>
  );
}

export default App;

핵심 정리

SolidJS의 장점

  1. 압도적인 성능: React보다 2배 빠름
  2. 작은 번들 크기: React의 1/3 (13KB)
  3. 진짜 반응성: Virtual DOM 없이 직접 업데이트
  4. 익숙한 문법: JSX 그대로 사용
  5. 쉬운 마이그레이션: React 개발자에게 친숙

🚀 다음 단계


시작하기: npm create vite@latest my-app -- --template solid로 5분 만에 프로젝트를 시작하고, React보다 2배 빠른 성능을 경험하세요! 🚀