T-CREATOR

SolidJS 入門:最速で Hello World から始める

SolidJS 入門:最速で Hello World から始める

「SolidJS を試してみたいけど、何から始めればいいの?」そんな疑問にお答えします。

この記事では、SolidJS の基礎知識は最小限に留め、実際に手を動かしながら学習することに重点を置いています。30 分程度で Hello World から始めて、基本的なアプリケーションを作成できるようになることを目指しましょう。

プログラミングを始めたばかりの方でも安心してください。一歩ずつ丁寧に進めていきますので、焦らずに取り組んでみてください。

事前準備

必要な環境の確認

SolidJS の開発を始める前に、以下の環境が整っていることを確認しましょう。

必要なソフトウェア一覧:

#ソフトウェア推奨バージョン確認コマンド
1Node.js16.14.0 以上node --version
2Yarn1.22.0 以上yarn --version
3Git2.25.0 以上git --version

まず、ターミナル(Windows の場合はコマンドプロンプトまたは PowerShell)を開いて、以下のコマンドを実行してください。

bash# Node.jsのバージョン確認
node --version

# Yarnのバージョン確認
yarn --version

# Gitのバージョン確認
git --version

もし Node.js がインストールされていない場合は、Node.js 公式サイトからダウンロードしてインストールしてください。

Yarn がインストールされていない場合は、以下のコマンドでインストールできます:

bash# Yarnのインストール
npm install -g yarn

この時、以下のようなエラーが出る場合があります:

bashnpm ERR! code EACCES
npm ERR! syscall mkdir
npm ERR! path /usr/local/lib/node_modules/yarn
npm ERR! errno -13
npm ERR! Error: EACCES: permission denied

このエラーが発生した場合は、管理者権限で実行してください:

bash# macOS/Linuxの場合
sudo npm install -g yarn

# Windowsの場合(PowerShellを管理者権限で実行)
npm install -g yarn

エディタの設定とおすすめ拡張機能

SolidJS の開発には、Visual Studio Code(VS Code)を強くおすすめします。無料で使えて、SolidJS の開発に必要な機能が豊富に揃っています。

VS Code の必須拡張機能:

#拡張機能名機能
1TypeScript and JavaScriptTypeScript/JavaScript の言語サポート
2ES7+ React/Redux/React-NativeJSX の構文ハイライト
3Auto Rename TagHTML タグの自動リネーム
4Bracket Pair Colorizer括弧の色分け
5Prettier - Code formatterコードの自動整形

これらの拡張機能をインストールするには、VS Code の左側のサイドバーにある「拡張機能」アイコンをクリックし、上記の名前で検索してインストールしてください。

ステップ 1: プロジェクト作成

yarn create solid でプロジェクト生成

いよいよ SolidJS プロジェクトを作成しましょう。ターミナルを開いて、以下のコマンドを実行してください。

bash# SolidJSプロジェクトの作成
yarn create solid my-first-solid-app

# 作成したディレクトリに移動
cd my-first-solid-app

コマンドを実行すると、以下のような質問が表示されます:

perl? Which template would you like to use? (Use arrow keys)
❯ basic
  typescript
  tailwindcss
  bootstrap
  minimal

初心者の方は「basic」を選択してください。TypeScript に慣れている方は「typescript」を選択することもできます。

もし以下のようなエラーが表示された場合:

basherror An unexpected error occurred: "https://registry.yarnpkg.com/create-solid: Not found".

これは Yarn のキャッシュの問題です。以下のコマンドで解決できます:

bash# Yarnキャッシュをクリア
yarn cache clean

# 再度プロジェクト作成を実行
yarn create solid my-first-solid-app

生成されたファイル構造の理解

プロジェクトが正常に作成されたら、以下のような構造になっているはずです。

arduinomy-first-solid-app/
├── public/
│   ├── favicon.ico
│   └── index.html
├── src/
│   ├── App.jsx
│   ├── App.module.css
│   ├── index.jsx
│   └── logo.svg
├── .gitignore
├── package.json
├── vite.config.js
└── yarn.lock

各ファイルの役割を理解しましょう:

#ファイル/フォルダ役割
1public​/​静的ファイル(画像、HTML など)を格納
2src​/​SolidJS のソースコードを格納
3src​/​index.jsxアプリケーションのエントリーポイント
4src​/​App.jsxメインのアプリケーションコンポーネント
5package.jsonプロジェクトの依存関係と設定
6vite.config.jsVite(ビルドツール)の設定

開発サーバーの起動確認

まず、依存関係をインストールして開発サーバーを起動してみましょう。

bash# 依存関係のインストール
yarn install

# 開発サーバーの起動
yarn dev

成功すると、以下のような出力が表示されます:

arduino  VITE v4.4.5  ready in 500 ms

  ➜  Local:   http://localhost:3000/
  ➜  Network: use --host to expose
  ➜  press h + enter to show help

ブラウザで http:​/​​/​localhost:3000​/​ にアクセスしてください。SolidJS のデフォルトページが表示されれば成功です!

もし以下のようなエラーが表示された場合:

bashError: listen EADDRINUSE: address already in use :::3000

これは、ポート 3000 が既に使用されていることを意味します。以下のコマンドで別のポートを指定できます:

bash# ポート3001で起動
yarn dev --port 3001

ステップ 2: Hello World の実装

最小限のコンポーネント作成

それでは、独自の Hello World コンポーネントを作成してみましょう。src​/​App.jsx ファイルを開いて、以下のように書き換えてください。

jsx// src/App.jsx
function App() {
  return (
    <div>
      <h1>Hello, SolidJS World!</h1>
      <p>私の最初の SolidJS アプリケーションです。</p>
    </div>
  );
}

export default App;

ファイルを保存すると、ブラウザが自動的に更新され、新しい内容が表示されます。これが SolidJS のホットリロード機能です。

JSX の基本記法

SolidJS では JSX という構文を使用します。JSX は JavaScript の中に HTML のような記法を書くことができる構文です。

JSX の基本ルール:

jsx// 正しい書き方
function MyComponent() {
  return (
    <div>
      <h1>タイトル</h1>
      <p>段落です</p>
    </div>
  );
}

// 間違った書き方:複数の要素を返すことはできない
function BadComponent() {
  return (
    <h1>タイトル</h1>
    <p>段落です</p>  // エラー!
  );
}

もし上記の間違った書き方をすると、以下のようなエラーが表示されます:

javascriptSyntaxError: Adjacent JSX elements must be wrapped in an enclosing tag.

この場合は、要素を <div><> で囲む必要があります:

jsx// 解決方法1: divで囲む
function MyComponent() {
  return (
    <div>
      <h1>タイトル</h1>
      <p>段落です</p>
    </div>
  );
}

// 解決方法2: フラグメント(<>)で囲む
function MyComponent() {
  return (
    <>
      <h1>タイトル</h1>
      <p>段落です</p>
    </>
  );
}

ブラウザでの表示確認

作成したコンポーネントがブラウザで正しく表示されることを確認しましょう。さらに、開発者ツールの使い方も覚えておくと便利です。

ブラウザで F12 キー(またはマックの場合は Cmd + Option + I)を押して開発者ツールを開いてください。「Console」タブを確認し、エラーが表示されていないことを確認しましょう。

ステップ 3: 状態管理の基礎

createSignal の使い方

SolidJS では、状態管理に createSignal を使用します。これが SolidJS の核心的な機能の一つです。

まず、src​/​App.jsx を以下のように更新してください:

jsx// src/App.jsx
import { createSignal } from 'solid-js';

function App() {
  // 状態の作成
  const [count, setCount] = createSignal(0);

  return (
    <div>
      <h1>カウンター: {count()}</h1>
      <p>現在の値は {count()} です</p>
    </div>
  );
}

export default App;

重要なポイントは、count() というように関数として呼び出すことです。これを忘れると、以下のようなエラーになります:

javascriptTypeError: Cannot read properties of undefined (reading 'toString')

イベントハンドラーの実装

次に、ボタンをクリックしてカウンターを増やす機能を追加しましょう。

jsx// src/App.jsx
import { createSignal } from 'solid-js';

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

  // イベントハンドラー関数
  const increment = () => {
    setCount(count() + 1);
  };

  const decrement = () => {
    setCount(count() - 1);
  };

  return (
    <div>
      <h1>カウンター: {count()}</h1>
      <p>現在の値は {count()} です</p>
      <button onClick={increment}>+1</button>
      <button onClick={decrement}>-1</button>
    </div>
  );
}

export default App;

ボタンをクリックしてみてください。リアルタイムでカウンターの値が変更されることが確認できるはずです。

リアクティブな更新の体験

SolidJS のリアクティブシステムを体験してみましょう。状態が変更されると、自動的に画面が更新されます。

jsx// src/App.jsx
import { createSignal, createMemo } from 'solid-js';

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

  // 計算された値(メモ化)
  const doubleCount = createMemo(() => count() * 2);
  const isEven = createMemo(() => count() % 2 === 0);

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

  return (
    <div style={{ padding: '20px', font: 'Arial' }}>
      <h1>SolidJS カウンター</h1>

      <div style={{ margin: '20px 0' }}>
        <h2>現在の値: {count()}</h2>
        <p>2倍の値: {doubleCount()}</p>
        <p>偶数かどうか: {isEven() ? '偶数' : '奇数'}</p>
      </div>

      <div>
        <button
          onClick={increment}
          style={{ margin: '5px' }}
        >
          +1
        </button>
        <button
          onClick={decrement}
          style={{ margin: '5px' }}
        >
          -1
        </button>
        <button onClick={reset} style={{ margin: '5px' }}>
          リセット
        </button>
      </div>
    </div>
  );
}

export default App;

この例では、count の値が変更されると、doubleCountisEven も自動的に再計算されます。これが SolidJS のリアクティブシステムの威力です。

ステップ 4: コンポーネント間の連携

Props の受け渡し

複数のコンポーネントを作成し、データを受け渡してみましょう。新しいファイル src​/​Counter.jsx を作成してください。

jsx// src/Counter.jsx
function Counter(props) {
  return (
    <div
      style={{
        border: '1px solid #ccc',
        padding: '10px',
        margin: '10px',
      }}
    >
      <h3>{props.title}</h3>
      <p>現在の値: {props.value}</p>
      <button onClick={props.onIncrement}>+1</button>
      <button onClick={props.onDecrement}>-1</button>
    </div>
  );
}

export default Counter;

子コンポーネントの作成

次に、src​/​App.jsx を更新して、作成した Counter コンポーネントを使用しましょう。

jsx// src/App.jsx
import { createSignal } from 'solid-js';
import Counter from './Counter';

function App() {
  const [count1, setCount1] = createSignal(0);
  const [count2, setCount2] = createSignal(10);

  return (
    <div style={{ padding: '20px' }}>
      <h1>複数のカウンター</h1>

      <Counter
        title='カウンター1'
        value={count1()}
        onIncrement={() => setCount1(count1() + 1)}
        onDecrement={() => setCount1(count1() - 1)}
      />

      <Counter
        title='カウンター2'
        value={count2()}
        onIncrement={() => setCount2(count2() + 2)}
        onDecrement={() => setCount2(count2() - 2)}
      />

      <div style={{ marginTop: '20px' }}>
        <h3>合計: {count1() + count2()}</h3>
      </div>
    </div>
  );
}

export default App;

コンポーネントの再利用

同じ Counter コンポーネントを、異なる props で複数回使用していることがわかります。これがコンポーネントの再利用性の利点です。

もし props の名前を間違えると、以下のようなエラーが発生する可能性があります:

javascriptTypeError: Cannot read properties of undefined (reading 'title')

このエラーが発生した場合は、props の名前が正しいかどうか確認してください。

ステップ 5: 実用的な機能の追加

条件付きレンダリング

条件によって異なる内容を表示する機能を実装してみましょう。src​/​App.jsx を以下のように更新してください。

jsx// src/App.jsx
import { createSignal, Show } from 'solid-js';

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

  const increment = () => setCount(count() + 1);
  const decrement = () => setCount(count() - 1);
  const toggleDetails = () =>
    setShowDetails(!showDetails());

  return (
    <div style={{ padding: '20px', fontFamily: 'Arial' }}>
      <h1>条件付きレンダリング</h1>

      <div style={{ margin: '20px 0' }}>
        <h2>カウンター: {count()}</h2>

        {/* 条件付きレンダリング方法1: 三項演算子 */}
        <p>{count() > 0 ? '正の数です' : '0以下です'}</p>

        {/* 条件付きレンダリング方法2: Show コンポーネント */}
        <Show when={count() > 10}>
          <p style={{ color: 'red', fontWeight: 'bold' }}>
            カウンターが10を超えました!
          </p>
        </Show>

        <Show when={count() < 0}>
          <p style={{ color: 'blue' }}>
            カウンターがマイナスです
          </p>
        </Show>
      </div>

      <div style={{ margin: '20px 0' }}>
        <button
          onClick={increment}
          style={{ margin: '5px' }}
        >
          +1
        </button>
        <button
          onClick={decrement}
          style={{ margin: '5px' }}
        >
          -1
        </button>
        <button
          onClick={toggleDetails}
          style={{ margin: '5px' }}
        >
          {showDetails() ? '詳細を隠す' : '詳細を表示'}
        </button>
      </div>

      <Show when={showDetails()}>
        <div
          style={{
            border: '1px solid #ddd',
            padding: '15px',
          }}
        >
          <h3>詳細情報</h3>
          <p>現在の値: {count()}</p>
          <p>絶対値: {Math.abs(count())}</p>
          <p>2乗: {count() * count()}</p>
          <p>
            偶数: {count() % 2 === 0 ? 'はい' : 'いいえ'}
          </p>
        </div>
      </Show>
    </div>
  );
}

export default App;

リスト表示とループ処理

次に、リストを表示してループ処理を実装してみましょう。

jsx// src/App.jsx
import { createSignal, For } from 'solid-js';

function App() {
  const [items, setItems] = createSignal([
    { id: 1, name: 'リンゴ', price: 150 },
    { id: 2, name: 'バナナ', price: 100 },
    { id: 3, name: 'オレンジ', price: 200 },
  ]);

  const [newItemName, setNewItemName] = createSignal('');
  const [newItemPrice, setNewItemPrice] = createSignal('');

  const addItem = () => {
    const name = newItemName().trim();
    const price = parseInt(newItemPrice());

    if (name && price > 0) {
      const newItem = {
        id: Date.now(),
        name: name,
        price: price,
      };

      setItems([...items(), newItem]);
      setNewItemName('');
      setNewItemPrice('');
    }
  };

  const removeItem = (id) => {
    setItems(items().filter((item) => item.id !== id));
  };

  const totalPrice = () => {
    return items().reduce(
      (sum, item) => sum + item.price,
      0
    );
  };

  return (
    <div style={{ padding: '20px', fontFamily: 'Arial' }}>
      <h1>商品リスト</h1>

      {/* 新しいアイテムの追加 */}
      <div
        style={{
          marginBottom: '20px',
          border: '1px solid #ddd',
          padding: '15px',
        }}
      >
        <h3>新しい商品を追加</h3>
        <div style={{ marginBottom: '10px' }}>
          <input
            type='text'
            placeholder='商品名'
            value={newItemName()}
            onInput={(e) => setNewItemName(e.target.value)}
            style={{ marginRight: '10px', padding: '5px' }}
          />
          <input
            type='number'
            placeholder='価格'
            value={newItemPrice()}
            onInput={(e) => setNewItemPrice(e.target.value)}
            style={{ marginRight: '10px', padding: '5px' }}
          />
          <button
            onClick={addItem}
            style={{ padding: '5px 10px' }}
          >
            追加
          </button>
        </div>
      </div>

      {/* 商品リストの表示 */}
      <div>
        <h3>商品一覧(合計: {totalPrice()}円)</h3>
        <For each={items()}>
          {(item) => (
            <div
              style={{
                border: '1px solid #ccc',
                padding: '10px',
                margin: '5px 0',
                display: 'flex',
                justifyContent: 'space-between',
                alignItems: 'center',
              }}
            >
              <div>
                <strong>{item.name}</strong> - {item.price}
                円
              </div>
              <button
                onClick={() => removeItem(item.id)}
                style={{
                  backgroundColor: '#ff4444',
                  color: 'white',
                  border: 'none',
                  padding: '5px 10px',
                  cursor: 'pointer',
                }}
              >
                削除
              </button>
            </div>
          )}
        </For>
      </div>
    </div>
  );
}

export default App;

フォーム入力の処理

フォームの入力処理について、より詳しく学んでみましょう。

jsx// src/App.jsx
import { createSignal } from 'solid-js';

function App() {
  const [formData, setFormData] = createSignal({
    name: '',
    email: '',
    age: '',
    gender: '',
    interests: [],
  });

  const [errors, setErrors] = createSignal({});

  const handleInputChange = (field, value) => {
    setFormData({
      ...formData(),
      [field]: value,
    });

    // エラーをクリア
    if (errors()[field]) {
      setErrors({
        ...errors(),
        [field]: undefined,
      });
    }
  };

  const handleCheckboxChange = (interest, checked) => {
    const currentInterests = formData().interests;
    let newInterests;

    if (checked) {
      newInterests = [...currentInterests, interest];
    } else {
      newInterests = currentInterests.filter(
        (i) => i !== interest
      );
    }

    setFormData({
      ...formData(),
      interests: newInterests,
    });
  };

  const validateForm = () => {
    const newErrors = {};
    const data = formData();

    if (!data.name.trim()) {
      newErrors.name = '名前は必須です';
    }

    if (!data.email.trim()) {
      newErrors.email = 'メールアドレスは必須です';
    } else if (!/\S+@\S+\.\S+/.test(data.email)) {
      newErrors.email =
        'メールアドレスの形式が正しくありません';
    }

    if (!data.age || data.age < 0 || data.age > 150) {
      newErrors.age = '年齢は0-150の範囲で入力してください';
    }

    setErrors(newErrors);
    return Object.keys(newErrors).length === 0;
  };

  const handleSubmit = (e) => {
    e.preventDefault();

    if (validateForm()) {
      console.log('フォームデータ:', formData());
      alert('フォームが正常に送信されました!');
    }
  };

  return (
    <div
      style={{
        padding: '20px',
        fontFamily: 'Arial',
        maxWidth: '600px',
      }}
    >
      <h1>ユーザー登録フォーム</h1>

      <form onSubmit={handleSubmit}>
        {/* 名前 */}
        <div style={{ marginBottom: '15px' }}>
          <label
            style={{
              display: 'block',
              marginBottom: '5px',
            }}
          >
            名前 <span style={{ color: 'red' }}>*</span>
          </label>
          <input
            type='text'
            value={formData().name}
            onInput={(e) =>
              handleInputChange('name', e.target.value)
            }
            style={{
              width: '100%',
              padding: '8px',
              border: errors().name
                ? '1px solid red'
                : '1px solid #ccc',
            }}
          />
          {errors().name && (
            <div
              style={{
                color: 'red',
                fontSize: '12px',
                marginTop: '5px',
              }}
            >
              {errors().name}
            </div>
          )}
        </div>

        {/* メールアドレス */}
        <div style={{ marginBottom: '15px' }}>
          <label
            style={{
              display: 'block',
              marginBottom: '5px',
            }}
          >
            メールアドレス{' '}
            <span style={{ color: 'red' }}>*</span>
          </label>
          <input
            type='email'
            value={formData().email}
            onInput={(e) =>
              handleInputChange('email', e.target.value)
            }
            style={{
              width: '100%',
              padding: '8px',
              border: errors().email
                ? '1px solid red'
                : '1px solid #ccc',
            }}
          />
          {errors().email && (
            <div
              style={{
                color: 'red',
                fontSize: '12px',
                marginTop: '5px',
              }}
            >
              {errors().email}
            </div>
          )}
        </div>

        {/* 年齢 */}
        <div style={{ marginBottom: '15px' }}>
          <label
            style={{
              display: 'block',
              marginBottom: '5px',
            }}
          >
            年齢
          </label>
          <input
            type='number'
            value={formData().age}
            onInput={(e) =>
              handleInputChange(
                'age',
                parseInt(e.target.value) || 0
              )
            }
            style={{
              width: '100%',
              padding: '8px',
              border: errors().age
                ? '1px solid red'
                : '1px solid #ccc',
            }}
          />
          {errors().age && (
            <div
              style={{
                color: 'red',
                fontSize: '12px',
                marginTop: '5px',
              }}
            >
              {errors().age}
            </div>
          )}
        </div>

        {/* 性別 */}
        <div style={{ marginBottom: '15px' }}>
          <label
            style={{
              display: 'block',
              marginBottom: '5px',
            }}
          >
            性別
          </label>
          <div>
            <label style={{ marginRight: '15px' }}>
              <input
                type='radio'
                name='gender'
                value='male'
                checked={formData().gender === 'male'}
                onChange={(e) =>
                  handleInputChange(
                    'gender',
                    e.target.value
                  )
                }
              />
              男性
            </label>
            <label style={{ marginRight: '15px' }}>
              <input
                type='radio'
                name='gender'
                value='female'
                checked={formData().gender === 'female'}
                onChange={(e) =>
                  handleInputChange(
                    'gender',
                    e.target.value
                  )
                }
              />
              女性
            </label>
            <label>
              <input
                type='radio'
                name='gender'
                value='other'
                checked={formData().gender === 'other'}
                onChange={(e) =>
                  handleInputChange(
                    'gender',
                    e.target.value
                  )
                }
              />
              その他
            </label>
          </div>
        </div>

        {/* 興味のある分野 */}
        <div style={{ marginBottom: '15px' }}>
          <label
            style={{
              display: 'block',
              marginBottom: '5px',
            }}
          >
            興味のある分野(複数選択可)
          </label>
          <div>
            {[
              'プログラミング',
              'デザイン',
              'マーケティング',
              'データ分析',
            ].map((interest) => (
              <label
                key={interest}
                style={{
                  display: 'block',
                  marginBottom: '5px',
                }}
              >
                <input
                  type='checkbox'
                  checked={formData().interests.includes(
                    interest
                  )}
                  onChange={(e) =>
                    handleCheckboxChange(
                      interest,
                      e.target.checked
                    )
                  }
                />
                {interest}
              </label>
            ))}
          </div>
        </div>

        <button
          type='submit'
          style={{
            backgroundColor: '#007bff',
            color: 'white',
            padding: '10px 20px',
            border: 'none',
            cursor: 'pointer',
            fontSize: '16px',
          }}
        >
          送信
        </button>
      </form>

      {/* デバッグ用: 現在のフォームデータを表示 */}
      <div
        style={{
          marginTop: '30px',
          padding: '15px',
          backgroundColor: '#f5f5f5',
        }}
      >
        <h3>現在のフォームデータ</h3>
        <pre>{JSON.stringify(formData(), null, 2)}</pre>
      </div>
    </div>
  );
}

export default App;

よくあるエラーと解決法

初心者が遭遇しやすい問題

SolidJS を学習する際によく遭遇するエラーとその解決法をまとめました。

エラー 1: Signal を関数として呼び出していない

bashTypeError: Cannot read properties of undefined (reading 'toString')

原因: createSignal で作成した状態を関数として呼び出していない

jsx// ❌ 間違い
const [count, setCount] = createSignal(0);
return <div>{count}</div>;

// ✅ 正しい
const [count, setCount] = createSignal(0);
return <div>{count()}</div>;

エラー 2: JSX 要素の不正な構造

bashSyntaxError: Adjacent JSX elements must be wrapped in an enclosing tag.

原因: 複数の JSX 要素を返す際に、ラッパー要素で囲んでいない

jsx// ❌ 間違い
function MyComponent() {
  return (
    <h1>タイトル</h1>
    <p>内容</p>
  );
}

// ✅ 正しい
function MyComponent() {
  return (
    <div>
      <h1>タイトル</h1>
      <p>内容</p>
    </div>
  );
}

エラー 3: Import 文の間違い

bashModule not found: Error: Can't resolve './Counter'

原因: ファイルの拡張子を省略しているか、パスが間違っている

jsx// ❌ 間違い(拡張子がない場合)
import Counter from './Counter';

// ✅ 正しい
import Counter from './Counter.jsx';

エラー 4: 依存関係の問題

bashError: Cannot resolve dependency tree
npm ERR! ERESOLVE unable to resolve dependency tree

解決法:

bash# node_modulesを削除
rm -rf node_modules yarn.lock

# 依存関係を再インストール
yarn install

# または、キャッシュをクリア
yarn cache clean
yarn install

エラー 5: ポートの競合

bashError: listen EADDRINUSE: address already in use :::3000

解決法:

bash# 別のポートを使用
yarn dev --port 3001

# または、プロセスを強制終了
# macOS/Linux
lsof -ti:3000 | xargs kill -9

# Windows
netstat -ano | findstr :3000
taskkill /PID <PID番号> /F

デバッグのコツ

1. ブラウザの開発者ツールを活用する

  • F12 キーまたは Cmd + Option + I で開発者ツールを開く
  • Console タブでエラーメッセージを確認
  • Network タブでファイルの読み込み状況を確認

2. コンソールでの値の確認

jsx// 状態の値を確認
const [count, setCount] = createSignal(0);

// コンソールに出力
console.log('現在のカウント:', count());

// 関数内でのデバッグ
const handleClick = () => {
  console.log('ボタンがクリックされました');
  setCount(count() + 1);
  console.log('新しいカウント:', count());
};

3. 段階的な実装

問題が発生した場合は、以下の手順で段階的に確認してください:

  1. 最小限のコードで動作を確認
  2. 一つずつ機能を追加
  3. 各段階でテスト
  4. エラーが発生した箇所を特定

4. 公式ドキュメントとコミュニティの活用

まとめ

お疲れさまでした!この記事を通じて、SolidJS の基本的な使い方を学ぶことができました。

習得できた内容:

#内容重要度
1開発環境のセットアップ★★★
2プロジェクトの作成★★★
3基本的なコンポーネント★★★
4状態管理(createSignal)★★★
5イベントハンドリング★★★
6コンポーネント間連携★★
7条件付きレンダリング★★
8リスト表示とループ★★
9フォーム処理★★
10エラーハンドリング

次のステップ:

  1. ルーティング: SolidJS Router を使用してページ遷移を実装
  2. 状態管理: より複雑な状態管理ライブラリの学習
  3. API 連携: 外部 API との通信処理
  4. スタイリング: CSS-in-JS や Tailwind CSS の活用
  5. テスト: Jest や Vitest を使用したテスト作成
  6. デプロイ: Vercel や Netlify への本番環境デプロイ

SolidJS は学習しやすく、パフォーマンスも優秀なフレームワークです。ぜひ実際のプロジェクトでも活用してみてください。

何か問題が発生した場合は、この記事のエラー解決法を参考にしながら、一歩ずつ解決していきましょう。プログラミングは試行錯誤の連続ですが、それが上達への近道です。

関連リンク