TypeORM を使って、 expo-sqlite
を操作できます。
スマホアプリ上のデータの保存を SQL を操作するように取り扱えるので、非常に便利です。
必要なライブラリをインストールする
npm install expo-sqlite typeorm reflect-metadata
tsconfig.json の修正
{
"extends": "expo/tsconfig.base",
"compilerOptions": {
"experimentalDecorators": true,
"strictPropertyInitialization": false,
"emitDecoratorMetadata": true,
"strict": true
}
}
データベース用のディレクトリとファイルを作成
mkdir -p lib/databases/{entities,repositories}
touch lib/databases/typeorm.config.ts
typeorm.config.ts の設定
import { DataSource } from 'typeorm';
export const AppDataSource = new DataSource({
type: 'expo',
driver: require('expo-sqlite'),
database: 'dev-tabmemo',
logging: [],
synchronize: true,
entities: [],
});
export async function initialize() {
if (!AppDataSource.isInitialized) {
await AppDataSource.initialize();
}
return AppDataSource.isInitialized;
}
database
にはデータベース名を設定します。
開発中はテーブルが色々と呼ばれるので、接頭辞として dev
をつけて、リリース前に外してます。
データソースとの接続状態を Recoil で管理する
データベースに接続されていないうちに TypeORM から SQL を投げると、エラーが出ます。
データベースにちゃんと接続できているかを状態として管理します。
Recoil の導入については以下の記事のとおりです。
Recoil を導入後、以下のファイルを作成します。
touch lib/states/atoms/DataSourceReady.ts
DataSourceReady.ts
は以下のようにします。
import { atom } from 'recoil';
export const DataSourceReady = atom({
key: 'dataSourceState',
default: false,
});
最後に App.tsx
を以下のように書きます。
import { initialize } from './lib/databases/typeorm.config';
import { DataSourceReady } from './lib/states/atoms/DataSourceReady';
import { StatusBar } from 'expo-status-bar';
import { useEffect } from 'react';
import { StyleSheet, Text, View } from 'react-native';
import { RecoilRoot, useSetRecoilState } from 'recoil';
export default function App() {
return (
<RecoilRoot>
<DatabaseInitializer />
<View style={styles.container}>
<Text>Open up App.tsx to start working on your app!</Text>
<StatusBar style="auto" />
</View>
</RecoilRoot>
);
}
function DatabaseInitializer() {
const setDataSourceReady = useSetRecoilState(DataSourceReady);
useEffect(() => {
async function initializeDatabase() {
try {
const initializeResult = await initialize();
if (initializeResult) {
setDataSourceReady(true);
}
console.log('[INFO]DataSource initialized');
} catch (err) {
console.log(err);
console.log('[ERROR]DataSource failed to initialize');
setDataSourceReady(false);
}
}
initializeDatabase();
}, []);
return null;
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
});
<DatabaseInitializer />
を上の方に書いて、他のコンポーネントが呼び出される前に呼ぶようにしてください。そうでないと、
EntityMetadataNotFoundError: No metadata for "Tab" was found
というエラーが出てしまう可能性がありmす。 Entity を読み込む前にテーブルにアクセスしてはいけない、ということです。
この状態で、 npx expo start --tunnel
を実行して、シミュレーターを立ち上げると、コンソールに以下のように表示されます。
LOG [INFO]DataSource initialized
DataSourceReady の使い方
データを取得する際は以下のようにカスタムフックを作成し、 dataSourceReady
が true
であることを確認してからデータを fetch すると、事故りにくくなると思います。
export const useFetchTasks = () => {
const [taskItems, setTaskItems] = useState<Task[]>([]);
const dataSourceReady = useRecoilValue(DataSourceReady);
const taskReload = useRecoilValue(TaskReload);
useEffect(() => {
if (dataSourceReady) {
const fetch = async () => {
const tasks = await getTasks();
setTaskItems(tasks);
};
fetch();
}
}, [dataSourceReady, taskReload]);
return { taskItems, setTaskItems };
};
typeorm [EntityMetadataNotFoundError: No metadata for was found.] の原因
typeorm [EntityMetadataNotFoundError: No metadata for was found.]
の原因はだいたい2つです。
一つは、 typeorm.config.ts
の entities
に Entity を設定し忘れていること。
import { Memo, Tab } from './entities/Tab';
import { DataSource } from 'typeorm';
export const AppDataSource = new DataSource({
type: 'expo',
driver: require('expo-sqlite'),
database: 'dev-tabmemo',
logging: [],
synchronize: true,
entities: [Tab, Memo],
});
export async function initialize() {
if (!AppDataSource.isInitialized) {
await AppDataSource.initialize();
}
return AppDataSource.isInitialized;
}
entities
に忘れずに、使うエンティティを設定しましょう。
もう1つは、データベースの初期化が完了する前にクエリを投げてしまうこと。これに関しては、 Recoil などでデータベースの状態を監視して、初期化が完了した後にクエリを投げるようにすれば良いです。
const [tabs, setTabs] = useState<Tab[]>([]);
const dataSourceReady = useRecoilValue(DataSourceReady);
const [reloadTabState, setReloadTabState] = useRecoilState(ReloadTabState);
const fetchTabs = async () => {
const tabs = await getTabs();
setTabs(tabs);
};
useEffect(() => {
if (dataSourceReady) {
fetchTabs().then();
}
}, [reloadTabState, dataSourceReady]);