十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

expo-sqlite 完全指南:在 Expo 与 React Native 中使用 SQLite 构建跨平台本地数据库

expo-sqlite 完全指南:在 Expo 与 React Native 中使用 SQLite 构建跨平台本地数据库 expo-sqlite 完全指南在 Expo 与 React Native 中使用 SQLite 构建跨平台本地数据库【免费下载链接】expoAn open-source framework for making universal native apps with React. Expo runs on Android, iOS, and the web.项目地址: https://gitcode.com/GitHub_Trending/ex/expo本指南以 packages/expo-sqlite/README.md 为主体结合仓库内expo-sqlite模块的 TypeScript 源码、配置插件与脚本系统讲解如何在 Expo 托管项目和裸 React Native 项目中安装、配置并使用 SQLite。读完本文你将掌握数据库的打开与关闭、异步/同步两套查询 API、预编译语句、事务、Bun 风格模板字符串查询、React Hooks 集成以及 SQLCipher、FTS 全文检索、向量扩展等进阶能力。模块概览什么是 expo-sqliteexpo-sqlite是 Expo SDK 中用于提供 SQLite 数据库访问能力的官方模块其package.json中的描述为Provides access to a database using SQLite. The database is persisted across restarts of your app.基于 SQLite 提供数据库访问数据库在应用重启后依然持久保存。该模块以 SQLitehttps://www.sqlite.org/为底层引擎可在 Android、iOS 与 Web 三个平台运行是 Expo 生态中实现本地持久化、离线缓存、全文检索等场景的核心组件。模块的公共入口定义在 packages/expo-sqlite/src/index.ts它统一导出了以下四大部分SQLiteDatabase数据库连接对象及其查询方法SQLiteDatabase.tsSQLiteSession基于 SQLite session 扩展sqlite3session_*的变更会话能力SQLiteSession.tsSQLiteStatement预编译语句prepared statement及其执行结果SQLiteStatement.tsSQLiteTaggedQueryBun 风格的 SQL 模板字符串查询 APISQLiteTaggedQuery.tshooksReact 集成包括SQLiteProvider与useSQLiteContexthooks.tsx。此外模块还额外导出了Storage/AsyncStorage子路径见 Storage.ts可以用 SQLite 实现一个兼容react-native-async-storage/async-storage的键值存储。安装与平台配置在托管managedExpo 项目中安装对于托管 Expo 项目安装方式与 SDK 中的其他模块一致在项目根目录执行以下命令由 Expo CLI 自动选择与当前 SDK 匹配的版本npx expo install expo-sqlite在裸bareReact Native 项目中安装在裸 React Native 项目中需要先确保已经按照 Expo 模块安装流程配置好expo包然后再添加依赖npx expo install expo-sqliteAndroid 配置无需额外设置。仓库的 Android 原生代码android/通过 CMakeandroid/CMakeLists.txt直接构建绑定层安装 npm 包后即可使用。iOS 配置安装 npm 包后需要运行 CocoaPods 安装npx pod-installiOS 侧的模块声明在 ios/ExpoSQLite.podspec执行pod-install后 CocoaPods 会把原生代码链接进工程。通过 config plugin 开启编译期特性expo-sqlite附带一个 config pluginplugin/src/withSQLite.ts支持在app.json/app.config.js中声明编译期构建选项。它会把选项分别写入 Android 的gradle.propertiesexpo.sqlite.*键和 iOS 的 Podfile 属性属性类型说明customBuildFlagsstring传给 SQLite 编译的自定义构建标志enableFTSboolean是否启用 FTS 全文检索扩展SQLite FTS3/FTS5useSQLCipherboolean是否使用 SQLCipher加密版 SQLite替换内置 SQLitewithSQLiteVecExtensionboolean是否内置sqlite-vec向量扩展供loadExtensionAsync加载useLibSQLboolean已废弃libSQL 支持已移除此属性不再有任何效果构建始终使用 SQLite顶层属性对 Android 与 iOS 同时生效也可以通过android/ios字段做平台级覆盖。以启用 SQLCipher 为例// app.json { expo: { plugins: [ [expo-sqlite, { useSQLCipher: true }] ] } }从 withSQLite.ts 的源码可以看到若配置中仍然传入useLibSQL插件不会静默忽略而是通过WarningAggregator输出平台级警告提示该属性已废弃防止开发者误以为构建仍在用 libSQL。打开与关闭数据库打开数据库模块提供了异步与同步两种打开方式均定义在 SQLiteDatabase.ts 中import { openDatabaseAsync, openDatabaseSync } from expo-sqlite; // 异步打开推荐在 UI 线程使用 const db await openDatabaseAsync(myDatabase.db); // 同步打开适合初始化场景但重任务会阻塞 JS 线程 const dbSync openDatabaseSync(myDatabase.db);databaseName数据库文件名数据库文件默认存放在defaultDatabaseDirectory由原生层导出的默认目录下directory可选参数指定数据库文件所在目录该参数在 Web 平台不受支持options打开选项即SQLiteOpenOptions。SQLiteOpenOptions定义在 NativeDatabase.ts包含三个字段字段默认值说明enableChangeListenerfalse是否启用sqlite3_update_hook()开启后可通过addDatabaseChangeListener订阅onDatabaseChange变更事件useNewConnectionfalse即使存在同名数据库连接缓存也强制创建新连接finalizeUnusedStatementsBeforeClosingtrue关闭数据库时自动 finalize 未关闭的语句内部使用标记为hidden从 openDatabaseAsync 的源码可以看到完整的打开流程先通过createDatabasePath拼接路径 → 调用原生层ensureDatabasePathExistsAsync确保目录存在 → 构造NativeDatabase并initAsync初始化 → 包装成SQLiteDatabase返回当useNewConnection ! true时还会把数据库注册到 DevTools 客户端便于在开发者工具中浏览数据。关闭与删除数据库// 关闭数据库关闭后连接资源被释放 await db.closeAsync(); db.closeSync(); // 删除数据库文件同时支持异步/同步 await deleteDatabaseAsync(myDatabase.db); deleteDatabaseSync(myDatabase.db);注意从源码看closeAsync/closeSync在useNewConnection ! true时会同时调用unregisterDatabaseForDevToolsAsync从 DevTools 注销该数据库避免误操作已关闭的实例。序列化、反序列化与备份模块直接封装了 SQLite 的在线备份backup与序列化serialize/deserialize能力import { serializeDatabaseAsync, // 实际为 db.serializeAsync() deserializeDatabaseAsync, backupDatabaseAsync, } from expo-sqlite; // 将数据库序列化为 Uint8Array默认序列化 main 数据库 const data: Uint8Array await db.serializeAsync(); // 从二进制数据反序列化为内存数据库 const memoryDb await deserializeDatabaseAsync(data); // 数据库间在线备份默认备份 main 到 main await backupDatabaseAsync({ sourceDatabase: db, destDatabase: backupDb, });这些方法分别对应 SQLite 的sqlite3_serialize/sqlite3_deserialize/sqlite3_backup_*系列 C 接口源码注释中给出了官方文档链接非常适合做数据库快照、迁移备份等场景。执行 SQL异步 API 与同步 APISQLiteDatabase为每个操作同时提供*Async与*Sync两种形态。同步 API 底层直接调用 JSI 原生同步函数文档与源码均反复强调同步方式执行重任务会阻塞 JavaScript 线程、影响性能因此生产环境应优先使用异步 API同步 API 更适合初始化或轻量查询。批量执行 execexecAsync(source)/execSync(source)直接执行字符串中的一条或多条SQL 语句以分号分隔适合建表、初始化脚本await db.execAsync( PRAGMA journal_mode WAL; CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY NOT NULL, name TEXT NOT NULL); );注意源码注释明确提示queries are not escaped for you即exec不会为你转义任何内容拼接用户输入构造查询时务必自行处理避免 SQL 注入。便捷查询方法shorthand模块为最常见的预编译 执行 收尾三步操作提供了四个便捷包装方法见 SQLiteDatabase.ts内部会自动完成prepareAsync → executeAsync → finalizeAsync// 执行写操作返回 { lastInsertRowId, changes } const result await db.runAsync(INSERT INTO users (name) VALUES (?), Alice); console.log(result.lastInsertRowId, result.changes); // 取第一行无结果返回 null const user await db.getFirstAsync{ id: number; name: string }( SELECT * FROM users WHERE id ?, 1 ); // 取全部行 const users await db.getAllAsync{ id: number; name: string }(SELECT * FROM users); // 逐行迭代返回 AsyncIterableIterator支持 for await...of for await (const row of db.getEachAsync{ id: number; name: string }(SELECT * FROM users)) { console.log(row.name); }参数绑定数组、变参与命名参数绑定参数的类型定义在 NativeStatement.tsSQLiteBindValue string | number | null | boolean | Uint8Array | ArrayBuffer支持三类传参形式// 1) 匿名参数 数组 db.getAllAsync(SELECT * FROM test WHERE intValue ? AND name ?, [1, Hello]); // 2) 匿名参数 变参 db.getAllAsync(SELECT * FROM test WHERE intValue ? AND name ?, 1, Hello); // 3) 命名参数 对象支持 :VVV / VVV / $VVV 三种前缀 db.getAllAsync(SELECT * FROM test WHERE intValue $intValue AND name $name, { $intValue: 1, $name: Hello });源码注释特别建议优先使用$VVV形式因为 JavaScript 允许$直接出现在标识符中而无需转义。Blob 类型的绑定值Uint8Array/ArrayBuffer会由原生层单独处理为二进制参数。预编译语句Prepared Statement当同一语句需要反复执行如循环插入、频繁查询时应显式使用预编译语句以获得更好的性能与安全性// 预编译 const statement await db.prepareAsync(INSERT INTO users (name, age) VALUES (?, ?)); try { // 重复执行仅替换绑定参数 const r1 await statement.executeAsync(Alice, 30); const r2 await statement.executeAsync(Bob, 25); console.log(r1.lastInsertRowId, r1.changes); } finally { // 手动 finalize避免资源泄漏 await statement.finalizeAsync(); }SQLiteStatement的核心方法见 SQLiteStatement.tsexecuteAsyncT(params)/executeSyncT(params)执行语句返回SQLiteExecuteAsyncResultT/SQLiteExecuteSyncResultTgetColumnNamesAsync()获取结果集列名finalizeAsync()调用sqlite3_finalize()释放语句。文档强调虽然关闭数据库时会自动 finalize 遗留的孤儿语句但最佳实践是用完后立即手动 finalize并用try...finally保证出错时也能释放。执行结果对象执行结果同时实现了迭代器接口并且带有写操作的元数据lastInsertRowId对应sqlite3_last_insert_rowid()最近一次插入的 rowidchanges对应sqlite3_changes()受影响的行数getFirstAsync()/getAllAsync()取首行 / 全部行resetAsync()调用sqlite3_reset()重置游标。// 既返回写入元数据又支持迭代RETURNING 语句 const result await statement.executeAsync{ name: string }(John Doe, 101); console.log(lastInsertRowId:, result.lastInsertRowId); console.log(changes:, result.changes); for await (const row of result) { console.log(name:, row.name); }一个值得注意的实现细节getFirstAsync/getAllAsync要求游标处于初始状态如果已经迭代过必须先调用resetAsync()再取行否则会抛出明确错误见 SQLiteStatement.ts。另外由于 Hermes 不支持Symbol.asyncIterator源码使用Object.defineProperties把lastInsertRowId、changes、getFirstAsync等方法挂载到 AsyncGenerator 上以模拟AsyncIterableIterator接口。Bun 风格模板字符串查询Tagged Query APISQLiteTaggedQuery提供了一种完全不同的查询写法直接await一条模板字符串查询底层自动使用预编译语句防注入无需手动 prepare/finalize。该 API 的灵感来自 Bun 的 SQL 接口源码注释中明确说明入口是db.sql属性// 直接 awaitSELECT 返回对象数组 const users await db.sqlUserSELECT * FROM users WHERE age ${21}; // 取首行 const user await db.sqlUserSELECT * FROM users WHERE id ${userId}.first(); // 取值为数组形式Bun 风格 const rows await db.sqlSELECT name, age FROM users.values(); // → [[Alice, 30], [Bob, 25]] // INSERT/UPDATE/DELETE 返回 SQLiteRunResult const result await db.sqlINSERT INTO users (name, age) VALUES (${name}, ${age}) as SQLiteRunResult; console.log(Inserted row:, result.lastInsertRowId); // 流式迭代 for await (const user of db.sqlUserSELECT * FROM users.each()) { console.log(user.name); } // 同步变体 const all db.sqlUserSELECT * FROM users WHERE age ${21}.allSync(); const one db.sqlUserSELECT * FROM users WHERE id ${userId}.firstSync();该 API 的实现细节在 SQLiteTaggedQuery.ts构造函数把模板字符串片段用?连接成 SQL 源串值则收集为参数数组通过parseSQLQueryqueryUtils.ts解析 SQL判断该语句是否可能返回行SELECT、PRAGMA、WITH、EXPLAIN或带RETURNING子句——可以返回行时走getAllAsync否则走runAsync返回写操作元数据提供.first()、.values()、.each()及同步版.firstSync()、.valuesSync()、.eachSync()、.allSync()等结果形态变换方法。事务处理SQLiteDatabase内置了两级事务 API普通事务 withTransactionAsyncawait db.withTransactionAsync(async () { await db.execAsync(UPDATE test SET name aaa); // ...其他查询 });内部实现SQLiteDatabase.ts非常简单直观执行BEGIN→ 运行task()→ 成功COMMIT任何异常则ROLLBACK后重新抛出。但源码注释明确指出该事务不是排他的可能被其他并发异步查询打断无法保证await顺序时查询的执行次序。排他事务 withExclusiveTransactionAsyncawait db.withExclusiveTransactionAsync(async (txn) { await txn.execAsync(UPDATE test SET name aaa); });排他事务在新连接上运行一旦升级为写事务其他并发写查询将以database is locked报错终止。事务内所有查询必须通过回调参数txn对象执行txn拥有与SQLiteDatabase相同的接口。其实现基于Transaction内部类SQLiteDatabase.tsTransaction.createAsync会用{ ...db.options, useNewConnection: true }打开一条新连接事务结束无论成败后关闭该连接。注意withExclusiveTransactionAsync不支持 Web 平台在 Web 上调用会直接抛出withExclusiveTransactionAsync is not supported on web错误源码中有平台判断。同步版withTransactionSync同样存在但重任务会阻塞 JS 线程。React 集成SQLiteProvider 与 useSQLiteContext对于使用函数组件与 Hooks 的应用模块提供了开箱即用的 Context 集成hooks.tsx。SQLiteProvider在应用根部用SQLiteProvider包裹子组件即可通过useSQLiteContext()拿到同一个数据库实例import { SQLiteProvider, useSQLiteContext } from expo-sqlite; export default function App() { return ( SQLiteProvider databaseNametest.db onInit{migrateDbIfNeeded} Main / /SQLiteProvider ); } async function migrateDbIfNeeded(db) { await db.execAsync(CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT)); } function Main() { const db useSQLiteContext(); return View{/* ... */}/View; }SQLiteProviderProps支持的配置项属性说明databaseName要打开的数据库文件名必填directory数据库文件所在目录默认defaultDatabaseDirectoryoptions透传给openDatabaseAsync的SQLiteOpenOptionsassetSource从打包资源导入数据库文件{ assetId: require(./assets/db.db), forceOverwrite?: boolean }forceOverwrite默认falseonInit打开数据库后、渲染 children 前的初始化回调适合执行迁移脚本onError打开失败的错误处理默认直接 rethrowuseSuspense是否启用React.Suspense集成默认false从源码看有两个值得注意的行为useSuspense与onError互斥同时传入会抛出Cannot use onError with useSuspense, use error boundaries insteadSuspense 模式下的错误应交给 Error Boundary 处理SQLiteProvider使用memo包裹并通过deepEqual对options、assetSource做深比较只有相关 props 真正变化时才会重开数据库。assetSource的实现importDatabaseFromAssetAsynchooks.tsx会先通过Asset.fromModule(...).downloadAsync()下载打包资源再把本地文件导入数据库目录forceOverwrite为false时若文件已存在则不覆盖。useSQLiteContextexport function Main() { const db useSQLiteContext(); console.log(sqlite version, db.getFirstSync(SELECT sqlite_version())); return View /; }useSQLiteContext只能在SQLiteProvider内部使用否则抛出useSQLiteContext must be used within a SQLiteProvider。Suspense 模式的用法是在外层包Suspense fallback{TextLoading.../Text}。键值存储SQLiteStorage 与 AsyncStorage除了关系型查询模块还内置了一个基于 SQLite 的键值存储SQLiteStorageStorage.ts并默认导出一个名为AsyncStorage的实例作为react-native-async-storage/async-storage的即插即用替代品import AsyncStorage from expo-sqlite/kv-store; // AsyncStorage API await AsyncStorage.setItem(user, JSON.stringify({ id: 1 })); const value await AsyncStorage.getItem(user); await AsyncStorage.removeItem(user); await AsyncStorage.getAllKeys(); await AsyncStorage.clear(); // 还有 mergeItem / multiGet / multiSet / multiRemove / multiMerge 等AsyncStorage实例使用名为ExpoSQLiteStorage的数据库文件底层是一张storage (key TEXT PRIMARY KEY NOT NULL, value TEXT)表。存储同样提供同步版方法getItemSync/setItemSync等。实现细节包括使用await-lock串行化异步访问setItem支持传入更新函数(prevValue) nextValue并通过排他事务保证读改写原子性multiSet/multiRemove/multiMerge均在withExclusiveTransactionAsync内批量执行数据库使用PRAGMA user_version做版本号迁移当前DATABASE_VERSION 1。默认实例是单例new SQLiteStorage(ExpoSQLiteStorage)如需独立存储可自行new SQLiteStorage(myStore.db)。变更监听与扩展加载数据库变更监听打开数据库时传入enableChangeListener: true即可订阅表级变更事件import { openDatabaseAsync, addDatabaseChangeListener } from expo-sqlite; const db await openDatabaseAsync(myDatabase.db, { enableChangeListener: true }); const subscription addDatabaseChangeListener((event) { console.log(event.databaseName, event.databaseFilePath, event.tableName, event.rowId); }); // 不再需要时取消订阅 subscription.remove();事件载荷DatabaseChangeEventSQLiteDatabase.ts包含databaseName默认mainATTACH DATABASE时为对应库名、databaseFilePath、tableName与rowId。底层调用sqlite3_update_hook()通过ExpoSQLite.addListener(onDatabaseChange, listener)分发。加载 SQLite 扩展loadExtensionAsync/loadExtensionSync用于加载 SQLite 扩展Android/iOS/macOS/tvOS// 加载内置的 sqlite-vec 扩展需先在 config plugin 中启用 withSQLiteVecExtension const extension SQLite.bundledExtensions[sqlite-vec]; await db.loadExtensionAsync(extension.libPath, extension.entryPoint); // 也可以加载自定义扩展库 await db.loadExtensionAsync(/path/to/extension);bundledExtensions常量SQLiteDatabase.ts暴露了原生层预编译的内置扩展信息。若不传entryPoint将按sqlite3_load_extension的默认规则推断入口点。更新内置 SQLite3 与 SQLCipher 源码对于需要自定义内置 SQLite 版本的维护者仓库在 packages/expo-sqlite/scripts/ 下提供了两个辅助脚本README 中给出了完整用法# 1) 进入模块目录需先克隆 expo/expo 仓库 cd packages/expo-sqlite # 2) 下载并构建 sqlite3.[ch] # 例如使用 sqlite 3.45.3 与 sqlcipher 4.6.0 ./scripts/prepare_sqlite.ts vendor/sqlite3 3.45.3 ./scripts/prepare_sqlite.ts vendor/sqlcipher 4.6.0 --sqlcipher # 3) 替换 sqlite3 符号防止与 iOS 系统自带 sqlite3 冲突 ./scripts/replace_symbols.ts vendor/sqlite3 ./scripts/replace_symbols.ts vendor/sqlcipherprepare_sqlite.ts负责从上游下载对应版本源码并构建replace_symbols.ts负责符号重命名——这一步在 iOS 上尤为关键因为系统自带的libsqlite3与内置 SQLite 同时存在时符号冲突会导致链接或运行时问题。下载的源码会放置在vendor/目录并在构建时Android 的 CMake 与 iOS 的 Podspec被编译进模块。Web 平台支持模块对 Web 平台也有完整的实现源码目录 web/ 内嵌了wa-sqliteWASM 版 SQLite及其wa-sqlite.wasm二进制、VFS文件系统抽象层实现、SyncSerializer与WorkerChannel等基础设施TS 侧入口为 ExpoSQLite.web.ts。需要留意平台差异directory参数在 Web 上不受支持withExclusiveTransactionAsync在 Web 上不可用同步 APIexecSync、getAllSync等在 Web 上同样会阻塞执行环境重任务应避免。总结与最佳实践围绕 packages/expo-sqlite/README.md 及源码可以沉淀出如下实践建议安装托管项目执行npx expo install expo-sqlite裸项目安装后再运行npx pod-install完成 iOS 链接Android 无需额外配置。API 选择默认使用异步 APIopenDatabaseAsync/getAllAsync/runAsync等仅在初始化等轻量场景使用同步 API避免阻塞 JS 线程。安全查询所有带用户输入的 SQL 一律走参数绑定?或$name不要拼接字符串传给execAsync。资源管理显式预编译语句用完即finalizeAsync()配合try...finally数据库用完closeAsync()。事务需要原子性且无并发写时用withTransactionAsync对写次序敏感、需要排他保证时用withExclusiveTransactionAsyncWeb 除外。React 集成用SQLiteProvideruseSQLiteContext管理全局连接迁移逻辑放在onInit中。进阶能力加密选 SQLCipher全文检索开enableFTS向量检索开withSQLiteVecExtension并配合loadExtensionAsync备份迁移用serialize/deserialize/backup系列方法。【免费下载链接】expoAn open-source framework for making universal native apps with React. Expo runs on Android, iOS, and the web.项目地址: https://gitcode.com/GitHub_Trending/ex/expo创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表