开发人员使用SQLite(一种C语言库)作为移动应用程序的数据存储是非常常见的。SQLite对离线应用程序特别有用,许多平台都包括对SQLite的支持,使其可以直接安装。
在这篇文章中,我们将在React Native应用中使用SQLite,建立一个简单的待办事项列表应用,向我们展示所有的CRUD操作是如何工作的。考虑到TypeScript的代码质量和可维护性等优势,我们还将使用它。
先决条件。
- 对React和React Native的基本了解
- 熟悉TypeScript
开始工作
我们将创建一个待办事项列表应用程序,其中包括以下内容。
- 完成按钮:清除已完成的项目
- 添加ToDo按钮:添加新项目
- 两个
useState:一个用于保存待办事项列表,一个用于跟踪新的待办事项 - 应用程序组件:处理用户事件,如添加和删除待办事项清单项目
- 哑巴组件:显示待办事项列表项目
注意,我们将使用一组功能组件和几个新的钩子API来实现状态管理。
设置React Native和TypeScript
我们将首先使用TypeScript创建一个React Native应用程序。
npx react-native init MyApp --template react-native-template-typescript
你可以 克隆React应用程序,并在阅读文章的过程中进行操作。
你会看到在仓库中有两个分支,start 和main 。我们将从start 分支开始。
引入SQLite
让我们把SQLite引入我们的应用程序。为了与SQLite连接,我们将使用 [react-native-sqlite-storage](https://www.npmjs.com/package/react-native-sqlite-storage)库。
要安装SQLite,在你的终端运行以下代码。
npm install --save react-native-sqlite-storage
安装React Native包
iOS
如果你使用的是iOS,运行下面的命令来安装必要的React Native包。
cd ios && pod install && cd ..
如果你运行的是React Native 0.59或更低版本,你有两个选择来安装React Native包,这取决于你是否使用CocoaPods。
使用CocoaPods。
如果你正在运行CocoaPods,将下面的代码添加到你的podfile中。
pod 'React', :path => '../node_modules/react-native'
pod 'react-native-sqlite-storage', :path => '../node_modules/react-native-sqlite-storage'
运行pod install 或pod update 。
没有CocoaPods
如果你没有运行CocoaPods,你必须使用react-native link 。如果你遇到任何错误,你将不得不从Xcode中打开项目并手动添加依赖性。更多的细节,请参考库的文档。
安卓系统
如果你在React Native .60或以上版本中使用你设备的SQLite,你不需要采取任何额外的步骤。
然而,如果你使用的是与react-native-sqlite-storage 库捆绑的SQLite,你可以在你的react-native.config.js 文件中添加下面的代码。
module.exports = {
...,
dependencies: {
...,
"react-native-sqlite-storage": {
platforms: {
android: {
sourceDir:
"../node_modules/react-native-sqlite-storage/platforms/android-native",
packageImportPath: "import io.liteglue.SQLitePluginPackage;",
packageInstance: "new SQLitePluginPackage()"
}
}
}
...
}
...
};
如果你运行的是旧版本的React Native,你必须手动更新Gradle 文件。关于完整的配置,请参考库的文档。
实现一个数据存储服务
现在,我们已经准备好实现一个数据存储服务了。我们将引入一个新的.ts 文件,称为db-service.ts ,在这里我们可以添加我们所有的db 操作。首先,让我们创建一个方法来获取一个db 连接。
由于我们使用的是TypeScript,我们可以安装@types/react-native-sqlite-storage ,以使用包含的类型。如果你坚持使用JavaScript,你不需要安装这个库。
使用下面的代码添加db 的连接方法。
import {openDatabase} from 'react-native-sqlite-storage';
export const getDBConnection = async () => {
return openDatabase({name: 'todo-data.db', location: 'default'});
};
如果我们启动应用程序时,一个表还不存在,我们需要创建一个。运行下面的代码来添加另一个方法。
export const createTable = async (db: SQLiteDatabase) => {
// create table if not exists
const query = `CREATE TABLE IF NOT EXISTS ${tableName}(
value TEXT NOT NULL
);`;
await db.executeSql(query);
};
由于我们在库中使用基于承诺的API,所以将下面的代码添加到我们的db-service.ts 文件中很重要。
enablePromise(true);
接下来,我们将添加方法来保存、删除和获取我们的待办事项。添加这些方法后,我们的db 服务文件将看起来像下面的代码块。
import { enablePromise, openDatabase, SQLiteDatabase } from 'react-native-sqlite-storage';
import { ToDoItem } from '../models';
const tableName = 'todoData';
enablePromise(true);
export const getDBConnection = async () => {
return openDatabase({ name: 'todo-data.db', location: 'default' });
};
export const createTable = async (db: SQLiteDatabase) => {
// create table if not exists
const query = `CREATE TABLE IF NOT EXISTS ${tableName}(
value TEXT NOT NULL
);`;
await db.executeSql(query);
};
export const getTodoItems = async (db: SQLiteDatabase): Promise<ToDoItem[]> => {
try {
const todoItems: ToDoItem[] = [];
const results = await db.executeSql(`SELECT rowid as id,value FROM ${tableName}`);
results.forEach(result => {
for (let index = 0; index < result.rows.length; index++) {
todoItems.push(result.rows.item(index))
}
});
return todoItems;
} catch (error) {
console.error(error);
throw Error('Failed to get todoItems !!!');
}
};
export const saveTodoItems = async (db: SQLiteDatabase, todoItems: ToDoItem[]) => {
const insertQuery =
`INSERT OR REPLACE INTO ${tableName}(rowid, value) values` +
todoItems.map(i => `(${i.id}, '${i.value}')`).join(',');
return db.executeSql(insertQuery);
};
export const deleteTodoItem = async (db: SQLiteDatabase, id: number) => {
const deleteQuery = `DELETE from ${tableName} where rowid = ${id}`;
await db.executeSql(deleteQuery);
};
export const deleteTable = async (db: SQLiteDatabase) => {
const query = `drop table ${tableName}`;
await db.executeSql(query);
};
我们已经添加了一个deleteTable 方法,这在我们开发应用程序时将很有用。稍后,我们将为用户添加一个功能,使用deleteTable 方法清除所有的数据。
我们可以使用rowid ,这是SQLite自带的,作为主键。我们已经更新了我们的待办事项,使其具有一个ID和一个值,而不是一个简单的字符串,这样我们就可以轻松地删除项目。
接下来,我们将为我们的ToDoItem 类型添加一个模型。在.NET中另一个名为models 的文件夹中添加以下代码:index.ts 。
export type ToDoItem = {
id: number;
value: string;
};
使用db 服务
我们要在App.tsx 中使用我们的db 服务。遵循这四个步骤。
1.)更新ToDoItem 组件和App 组件,以使用新的ToDoItem 类型
2.)从SQLite加载数据
3.)保存数据到db
4.)更新其中已删除的项目。db
首先,让我们完成对db 的设置,然后我们来看看App.tsx 和ToDoItem.tsx 文件的最终结果。
加载数据
为了在我们的应用程序中加载数据,我们将使用useEffect 和useCallback 钩子。
const loadDataCallback = useCallback(async () => {
try {
const initTodos = [{ id: 0, value: 'go to shop' }, { id: 1, value: 'eat at least a one healthy foods' }, { id: 2, value: 'Do some exercises' }];
const db = await getDBConnection();
await createTable(db);
const storedTodoItems = await getTodoItems(db);
if (storedTodoItems.length) {
setTodos(storedTodoItems);
} else {
await saveTodoItems(db, initTodos);
setTodos(initTodos);
}
} catch (error) {
console.error(error);
}
}, []);
useEffect(() => {
loadDataCallback();
}, [loadDataCallback]);
在上面的代码片断中,我们正在从db 。如果我们存储了任何待办事项,我们就用这些项目初始化应用程序。如果没有,我们会将初始值持久化到db ,并使用这些数据初始化应用程序。
添加一个项目
要添加一个待办事项,请运行以下代码。
const addTodo = async () => {
if (!newTodo.trim()) return;
try {
const newTodos = [...todos, {
id: todos.reduce((acc, cur) => {
if (cur.id > acc.id) return cur;
return acc;
}).id + 1, value: newTodo
}];
setTodos(newTodos);
const db = await getDBConnection();
await saveTodoItems(db, newTodos);
setNewTodo('');
} catch (error) {
console.error(error);
}
};
删除一个项目
最后,运行下面的代码来删除一个待办事项。
const deleteItem = async (id: number) => {
try {
const db = await getDBConnection();
await deleteTodoItem(db, id);
todos.splice(id, 1);
setTodos(todos.slice(0));
} catch (error) {
console.error(error);
}
};
我们最终的App.tsx 文件应该看起来像下面的代码。
/**
* Sample React Native App
* https://github.com/facebook/react-native
*
* Generated with the TypeScript template
* https://github.com/react-native-community/react-native-template-typescript
*
* @format
*/
import React, { useCallback, useEffect, useState } from 'react';
import {
Button,
SafeAreaView,
ScrollView,
StatusBar,
StyleSheet,
Text,
TextInput,
useColorScheme,
View,
} from 'react-native';
import { ToDoItemComponent } from './components/ToDoItem';
import { ToDoItem } from './models';
import { getDBConnection, getTodoItems, saveTodoItems, createTable, clearTable, deleteTodoItem } from './services/db-service';
const App = () => {
const isDarkMode = useColorScheme() === 'dark';
const [todos, setTodos] = useState<ToDoItem[]>([]);
const [newTodo, setNewTodo] = useState('');
const loadDataCallback = useCallback(async () => {
try {
const initTodos = [{ id: 0, value: 'go to shop' }, { id: 1, value: 'eat at least a one healthy foods' }, { id: 2, value: 'Do some exercises' }];
const db = await getDBConnection();
await createTable(db);
const storedTodoItems = await getTodoItems(db);
if (storedTodoItems.length) {
setTodos(storedTodoItems);
} else {
await saveTodoItems(db, initTodos);
setTodos(initTodos);
}
} catch (error) {
console.error(error);
}
}, []);
useEffect(() => {
loadDataCallback();
}, [loadDataCallback]);
const addTodo = async () => {
if (!newTodo.trim()) return;
try {
const newTodos = [...todos, {
id: todos.length ? todos.reduce((acc, cur) => {
if (cur.id > acc.id) return cur;
return acc;
}).id + 1 : 0, value: newTodo
}];
setTodos(newTodos);
const db = await getDBConnection();
await saveTodoItems(db, newTodos);
setNewTodo('');
} catch (error) {
console.error(error);
}
};
const deleteItem = async (id: number) => {
try {
const db = await getDBConnection();
await deleteTodoItem(db, id);
todos.splice(id, 1);
setTodos(todos.slice(0));
} catch (error) {
console.error(error);
}
};
return (
<SafeAreaView>
<StatusBar barStyle={isDarkMode ? 'light-content' : 'dark-content'} />
<ScrollView
contentInsetAdjustmentBehavior="automatic">
<View style={[styles.appTitleView]}>
<Text style={styles.appTitleText}> ToDo Application </Text>
</View>
<View>
{todos.map((todo) => (
<ToDoItemComponent key={todo.id} todo={todo} deleteItem={deleteItem} />
))}
</View>
<View style={styles.textInputContainer}>
<TextInput style={styles.textInput} value={newTodo} onChangeText={text => setNewTodo(text)} />
<Button
onPress={addTodo}
title="Add ToDo"
color="#841584"
accessibilityLabel="add todo item"
/>
</View>
</ScrollView>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
appTitleView: {
marginTop: 20,
justifyContent: 'center',
flexDirection: 'row',
},
appTitleText: {
fontSize: 24,
fontWeight: '800'
},
textInputContainer: {
marginTop: 30,
marginLeft: 20,
marginRight: 20,
borderRadius: 10,
borderColor: 'black',
borderWidth: 1,
justifyContent: 'flex-end'
},
textInput: {
borderWidth: 1,
borderRadius: 5,
height: 30,
margin: 10,
backgroundColor: 'pink'
},
});
export default App;
最后,我们最终的ToDoItem.tsx 文件应该看起来像下面的代码块。
import React from 'react';
import {
Button,
StyleSheet,
Text,
View,
} from 'react-native';
import { ToDoItem } from '../models';
export const ToDoItemComponent: React.FC<{
todo: ToDoItem;
deleteItem: Function;
}> = ({ todo: {id, value}, deleteItem }) => {
return (
<View style={styles.todoContainer}>
<View style={styles.todoTextContainer}>
<Text
style={styles.sectionTitle}>
{value}
</Text>
</View>
<Button
onPress={() => deleteItem(id)}
title="done"
color="#841584"
accessibilityLabel="add todo item"
/>
</View>
);
};
const styles = StyleSheet.create({
todoContainer: {
marginTop: 10,
paddingHorizontal: 24,
backgroundColor: 'deepskyblue',
marginLeft: 20,
marginRight: 20,
borderRadius: 10,
borderColor: 'black',
borderWidth: 1,
},
todoTextContainer: {
justifyContent: 'center',
flexDirection: 'row',
},
sectionTitle: {
fontSize: 20,
fontWeight: '400',
}
});
这就是你的成果!我们完成的React Native待办事项应用程序应该看起来像下面的图片。

结语
在本教程中,我们学习了如何将SQLite数据库与React Native应用程序连接,然后使用TypeScript创建了我们自己的应用程序。
我们使用了react-native-sqlite-storage ,一个连接器库,并完成了CRUD操作。然后,我们将这些操作与我们的React状态更新相结合,并考虑了简单的状态管理和持久化数据之间的区别。
我希望你喜欢这篇文章,不要忘记评论任何想法和改进。编码愉快!
The postUsing SQLite with React Native appeared first onLogRocket Blog.