在React Native中使用SQLite

4,641 阅读7分钟

开发人员使用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应用程序,并在阅读文章的过程中进行操作。

你会看到在仓库中有两个分支,startmain 。我们将从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 installpod 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.tsxToDoItem.tsx 文件的最终结果。

加载数据

为了在我们的应用程序中加载数据,我们将使用useEffectuseCallback 钩子。

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待办事项应用程序应该看起来像下面的图片。

React Native Sqlite Final Todo Application

结语

在本教程中,我们学习了如何将SQLite数据库与React Native应用程序连接,然后使用TypeScript创建了我们自己的应用程序。

我们使用了react-native-sqlite-storage ,一个连接器库,并完成了CRUD操作。然后,我们将这些操作与我们的React状态更新相结合,并考虑了简单的状态管理和持久化数据之间的区别。

我希望你喜欢这篇文章,不要忘记评论任何想法和改进。编码愉快!

The postUsing SQLite with React Native appeared first onLogRocket Blog.