React Native离线级联选择器开发手记:当SQLite遇见小区房号选择

🚀 需求背景:离线时代的"数字寻址"挑战

产品大大甩给我一个看似寻常的级联选择需求------楼栋→单元→楼层→房号。这在常规Web开发中就像吃泡面一样简单,但这次的需求却加了"离线使用"这个魔鬼调料🌶️!

技术选型

技术侦探笔记:离线存储就像给APP装了个随身保险箱,要保证用户即使在地下车库没信号,也能丝滑选择房号。

可用的存储方案

选手 优势 致命弱点 适用场景
AsyncStorage 轻量如燕(6MB) 数据量一大就喘气 简单配置存储
SQLite 老牌劲旅,ACID全能选手 需要写SQL有点费脑细胞 复杂关系数据
MMKV 微信团队的闪电侠⚡ 不支持复杂查询 高频键值读写
Realm 对象存储界的贵公子 学习曲线陡峭 跨设备同步需求
WatermelonDB React Native亲儿子 文档就像迷宫 超大型数据集

决策依据

  1. 数据结构复杂度:地址数据存在层级关系,适合关系型存储
  2. 查询需求:需支持DISTINCT、JOIN等复杂操作
  3. 调试支持:Android Studio提供原生Database Inspector工具
  4. 社区生态:SQLite拥有成熟的开发者社区和工具链

最终选定SQLite方案,其ACID特性可确保数据一致性,且符合SQL-92标准便于长期维护。

💣 避坑指南:那些年我们追过的star陷阱

建议不要使用这个插件

github 一搜索,可能最先出现的是这个插件,我也不意外,选用这个插件,毕竟star比较多。于是遇到了各种兼容性问题,因为插件作者已经4年没有维护该项目,在最新的[email protected] 版本中,该插件已几乎无法使用。

建议使用的插件

这个插件作者在一直维护,虽然star少,但是我没有遇到什么坑,也感谢作者大佬的付出

编码过程

思维导图

graph TD A[用户打开APP] --> B{网络状态?} B -->|在线| C[从API获取最新数据] B -->|离线| D[读取本地SQLite] C --> E[数据同步到SQLite] D --> F[级联选择器初始化] E --> F F --> G[用户选择房号] G --> H[结束]

程序员的第一课:安装依赖

bash 复制代码
npm i -s @op-engineering/op-sqlite

创建数据库

因为我已经有一份数据库文件,所以我需要依据这个数据库文件创建我的数据库

  1. 将数据库文件放在项目目录下,推荐放在assets目录下
  1. 指定资源目录 在根目录下创建 react-native.config.js 文件
js 复制代码
//指定你的database路径
module.exports = {
  assets: ['./src/assets/database'],
};
  1. 链接你的资源
bash 复制代码
npx react-native-asset@latest

这时候资源就会同步到你的安卓目录下

  1. 执行创建方法
ts 复制代码
//database.ts
import {DB, moveAssetsDatabase, open} from '@op-engineering/op-sqlite';

let db: DB | undefined;

export const init = async () => {
  const moved = await moveAssetsDatabase({filename: 'trash_classification.db'});
  if (!moved) {
    throw new Error('Could not move assets database');
  }
};

执行init方法后,数据库就创建成功了

  1. 查看你的数据库
  • 使用android studio

打开tools->app Inspection -> Database Inspector 就能看到你的数据库

  • 使用其他工具

如果你没有指定其他存储路径的话,database文件一般会存在在默认的项目目录下。

你可以使用方法获取文件地址

ts 复制代码
export const getDBPath = () => {
  console.log(getDB().getDbPath());
};

也可以直接打开手机的文件目录查找数据库文件导出,然后用三方工具查看

通常在 data/data/com.xxx/database目录下

比如我导出后使用DB Browser for SQLite查看就是这样的

同步数据库

基于我们app的需求我需要让app一打开就同步数据库

tsx 复制代码
//LoadDatabse.tsx
//...其他实现

  /** 更新数据库 */
  const {loading} = useRequest(services.main.initDatabase, {
    retryCount: -1,
    retryInterval: 3000,
    onError(err) {
      setError(true);
      console.log('数据库同步失败!', err.message);
    },
    /** 完成时的回调 */
    onSuccess(data) {
      if (data.code === 200) {
        syncDatabase(data.data).then(success => {
          if (success) {
            navigation.replace('Home');
          } else {
            setError(true);
          }
        });
      }
    },
  });

};

export default LoadDatabase;

主要为同步数据库,如果同步失败就间隔3s继续同步, 同时提供用户直接放弃同步前往主页的入口,同步成功就直接前往主页

然后实现syncDatabase函数

ts 复制代码
//database.tsx
import {DB, moveAssetsDatabase, open} from '@op-engineering/op-sqlite';

let db: DB | undefined;

/** 避免重复链接数据库  */
export const getDB = () => {
  if (!db) {
    db = open({name: 'trash_classification.db'});
  }
  return db;
};

/** 插入站点数据 */
export const insertStation = async (station: API.StationDTO) => {
  console.log(station);
  const result = await getDB().execute(
    'INSERT INTO device_station (id,hazardous_bins_quantity,kitchen_bins_quantity, other_bins_quantity, recyclable_bins_quantity,residential_quarters_id, station_code, station_name, description, status, deletion_flag, create_time, update_time, creator) VALUES (?,?,?,?,?, ?, ?, ?, ?, ?, ?, ?, ?, ?)',
    [
      station.id,
      station.otherBinsQuantity,
      station.hazardousBinsQuantity,
      station.kitchenBinsQuantity,
      station.recyclableBinsQuantity,
      station.residentialQuartersId,
      station.stationCode,
      station.stationName,
      station.description,
      station.status,
      station.deletionFlag,
      station.createTime,
      station.updateTime,
      station.creator,
    ],
  );
  return result;
};

//... 其他插入数据实现


export const syncDatabase = async (data: API.DatabaseDTO) => {
  const newDb = getDB();
  try {
    await newDb.transaction(async tx => {
      // 先来个大扫除
      await tx.execute('DELETE FROM device_station');
      // ...其他表清理

      // 新数据入住仪式
      await insertStation(data.station);
      await insertCommunity(data.community);
      
      // 楼栋房间的俄罗斯套娃
      for (const building of data.buildingList) {
        await insertBuilding(building);
        for (const room of building.roomList) {
          await insertRoom(room);
        }
      }
    });
    return true;
  } catch (error) {
    console.error('数据库同步失败:', error);
    return false;
  }
};

到这里数据库就同步完成了!

查询数据库

有了数据库自然要查询。我们继续补充database.ts 文件,添加查询语句

ts 复制代码
//database.ts


/** 获取所有楼栋 */
export const getBuildings = async () => {
  try {
    // 去重查询
    const result = await getDB().execute(
      'SELECT DISTINCT building_number as id, building_number as name FROM building_info   WHERE deleted = 0 ORDER BY sort',
    );
    return result.rows as Array<{id: string; name: string}>;
  } catch (error) {
    console.error('Get buildings failed:', error);
    return [];
  }
};
//... 其他查询实现

核心实现:级联选择的舞蹈编排

tsx 复制代码
//Login.tsx
// 选择逻辑就像跳格子游戏
const handleItemSelect = async (item: {id: string; name: string}) => {
  let newPath = [...selectedPath];
  
  switch(currentLevel) {
    case 'building':
      newPath = [{...item, type: 'building'}];
      currentItems = await getUnits(item.id); // 召唤单元列表
      break;
      
    case 'unit':
      newPath = [selectedPath[0], {...item, type: 'unit'}];
      currentItems = await getFloors(item.id); // 召唤楼层列表
      break;
      
    // ...其他case就像俄罗斯套娃
  }

  setSelectedPath(newPath);
  setCurrentItems(currentItems);
};

感谢阅读

如果对你有帮助,希望能给个赞!,你的点赞是我继续更新的动力!

相关推荐
爱嘿嘿的小黑9 分钟前
docker 常用命令
前端
dangfulin11 分钟前
CSS——变换、过度与动画
前端·css
南屿欣风22 分钟前
解决 Gin Web 应用中 Air 热部署无效的问题
前端·gin
猿大师办公助手25 分钟前
Web网页内嵌福昕OFD版式办公套件实现在线预览编辑PDF、OFD文档
前端·pdf·word
幼儿园技术家1 小时前
什么是RESTful 或 GraphQL?
前端
echola_mendes2 小时前
LangChain 结构化输出:用 Pydantic + PydanticOutputParser 驯服 LLM 的“自由发挥”
服务器·前端·数据库·ai·langchain
拉不动的猪2 小时前
刷刷题46(常见的三种js继承类型及其优缺点)
前端·javascript·面试
关注我:程序猿之塞伯坦2 小时前
JavaScript 性能优化实战:突破瓶颈,打造极致 Web 体验
开发语言·前端·javascript
兰德里的折磨5502 小时前
对于后端已经实现逻辑了,而前端还没有设置显示的改造
前端·vue.js·elementui
hikktn2 小时前
【开源宝藏】30天学会CSS - DAY9 第九课 牛顿摆动量守恒动画
前端·css·开源