1
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

ReactからSQLite公式のsqlite3 WASMを使ってみた

Last updated at Posted at 2023-03-19

React v18から sqlite3 WASM(SQLite公式のWebAssembly実装)を使ってみました。

  • 外部のWeb APIからデータを取得するのに、JSONPlaceholdertodos API を使用します。
    そのデータ内容は下図の通りです。

image.png

アプリ実行イメージ

先にアプリ実行イメージを示しておきます。

  • まず「データ取得」ボタンを押すと、todos API を呼んで、レスポンスデータをsqlite3 WASMに保存します。
  • データ取得後に「表示」ボタンを押すと、sqlite3 WASMにクエリーを発行して、id列とtitle列をブラウザ画面に表示します。

image.png

参考サイト(感謝します)

ライブラリのダウンロード

  • 本記事執筆時点で、npmやyarnでインストールできません。
  • 公式ページ の「WebAssembly & JavaScript」からダウンロードします。
  • 本記事では sqlite-wasm-3410100.zip をダウンロードし、プロジェクト配下のsrcディレクトリの下に解凍しました。

ソースコード

todoのデータ型

TypeScriptですので、必要な型を定義します。

src/types/todo.ts

export type TodoType = {
    userId: number;
    id: number;
    title: string;
    completed: boolean;
};

データ表示コンポーネント

「表示」ボタンが押されたときに、todoのid列と title列を表示するコンポーネントです。

src/Todo.tsx

import { FC } from "react"
import { TodoType } from "./types/todo"

type Props = {
    todo: TodoType;
};

export const Todo: FC<Props> = (props) => {
    const { todo } = props;
    return <p>{`(${todo.id}) ${todo.title}`}</p>;
}

主たる処理を行うコンポーネント

  • 最初に呼ばれる initDb()で、dbオブジェクトとテーブルを作成します。
  • 「データ取得」ボタンが押された時に呼ばれる onClickGetTodos()で、外部の todos API を呼んで、レスポンスデータをsqlite3にINSERTします。
  • 「表示」ボタンが押された時に呼ばれる onClickPrintTodos()で、sqlite3にSELECT文を発行して、結果データをブラウザ画面に表示します。
src/App.tsx

import sqlite3InitModule from "./sqlite-wasm-3410100/jswasm/sqlite3";
import { useState, useEffect, useRef } from "react";
import axios from "axios";
import { TodoType } from "./types/todo";
import { Todo } from "./Todo";

const createTableStmt = `
CREATE TABLE todos (
    id INTEGER PRIMARY KEY,
    user_id INTEGER NOT NULL,
    title TEXT NOT NULL,
    completed INT NOT NULL
) STRICT`;

const insertStmt = `
INSERT INTO todos (
    id,
    user_id,
    title,
    completed
)
VALUES (
    $id,
    $user_id,
    $title,
    $completed)`;

const selectStmt = `
SELECT
    id,
    user_id AS userId,
    title,
    completed
FROM
    todos`;

function App() {
    const db = useRef<any | null>(null);
    const [status, setMessage] = useState<string>("");
    const [todos, setTodos] = useState<Array<TodoType>>([]);
    const [disableButton, setDisableButton] = useState<boolean>(false);

    useEffect(() => {
        (async() => {
            await initDb()
        })();
    }, []);

    const initDb = async () => {
        try {
            const sqlite3 = await sqlite3InitModule();
            const oo = sqlite3.oo1;
            db.current = new oo.DB("/foo.sqlite3", "ct");
            db.current.exec(createTableStmt);
        }
        catch (err) {
            setMessage((err as Error).toString());
        }
    };

    const onClickGetTodos = async () => {
        try {
            setDisableButton(true);
            setTodos([]);

            const res = await axios.get<Array<TodoType>>("https://jsonplaceholder.typicode.com/todos");
            setMessage(`status=${res.status}`);
            res.data.map((rec) => {
                db.current.exec({
                    sql: insertStmt,
                    bind: {
                        $id: rec.id,
                        $user_id: rec.userId,
                        $title: rec.title,
                        $completed: rec.completed
                    }
                });
            });
        }
        catch (err) {
            setMessage((err as Error).toString());
        }
        finally {
            setDisableButton(false);
        }
    };

    const onClickPrintTodos = () => {
        try {
            setDisableButton(true);
    
            let resultRows: Array<TodoType> = [];
            db.current.exec({
                sql: selectStmt,
                rowMode: "object",
                resultRows: resultRows,
                callback: function(row: TodoType) {
                    // デバッグ用
                    console.log(`id=${row.id}, userId=${row.userId}, completed=${row.completed}, title=${row.title}`);
                }
            });
            setTodos(resultRows);
        }
        catch (err) {
            setMessage((err as Error).toString());
        }
        finally {
            setDisableButton(false);
        }
    };

    return (
        <>
            <button disabled={disableButton} onClick={onClickGetTodos}>データ取得</button>
            <p>{status}</p>
            <button disabled={disableButton} onClick={onClickPrintTodos}>表示</button>
            {todos.map((todo) => {
                return <Todo key={todo.id} todo={todo} />;
            })}
        </>
    );
}

export default App;

用途

ページネーションに対応していない外部APIを使う時に、一旦全データをsqlite3に保存しておいて、Reactでページネーション処理するのに使えそうかな?と思いました(まだ試していません)。

1
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?