资讯动态

AI从零搭建宠物生命周期管理App:React Native+SQLite全流程实战

发布时间:2026/10/14 6:42:27 来源:尧图企业网站定制
1. 项目缘起与整体设计思路1.1 为什么选宠物生命周期管理这个方向宠物生命周期管理这个选题乍看像是个小众赛道但实际拆开来看它覆盖的需求密度相当高。一只宠物从领养、日常喂养、疫苗驱虫、体检记录、行为训练到老年护理、临终关怀再到离世后的纪念处理每个阶段都有大量信息需要被记录和追踪。我身边养宠的朋友普遍存在一个痛点疫苗本丢了、驱虫时间记混了、体检报告散落在不同医院的公众号里、换粮之后的反应没有系统记录。这些碎片化的信息如果有一个统一的 App 来管理价值是实打实的。从技术实现的角度看这个选题还有一个隐性优势它的数据模型天然适合做生命周期状态机。宠物的每个阶段对应不同的数据字段、不同的提醒规则、不同的展示逻辑这正好能体现 AI 辅助搭建项目时对复杂业务逻辑的处理能力。如果只是做一个待办清单那太单薄了体现不出全流程搭建的价值。1.2 用 AI 从零搭建项目的核心思路这次搭建的核心思路是把 AI 当作一个能理解上下文的高级脚手架工具而不是帮你写几行代码的自动补全。这两者的区别很大。前者需要你在每个阶段给出足够清晰的约束条件让 AI 在约束内做决策后者你随便问一句它随便答一句最后拼起来的东西根本跑不通。我的做法是把整个项目拆成几个明确的阶段每个阶段只让 AI 聚焦解决一类问题。具体来说先让 AI 帮我梳理数据模型和页面结构确认整体骨架没问题再让它生成项目初始化和依赖配置然后是核心业务逻辑的实现最后是联调和边界情况处理。每个阶段我都会把上一阶段的产出作为上下文喂给它保证它不会失忆。这种分阶段推进的方式实测下来比一次性让 AI 生成整个项目要靠谱得多。一次性生成的问题在于AI 会在细节上自由发挥生成的代码看起来完整但模块之间的接口对不上改起来比重写还累。1.3 技术选型的考量技术栈这块我选的是React Native Expo TypeScript SQLite的组合。选 React Native 而不是 Flutter主要原因是 Expo 的生态对快速搭建更友好热重载体验好而且 AI 对 React Native 的代码生成质量普遍比 Flutter 稳定毕竟训练数据里 JS/TS 的占比更高。数据库用 SQLite 而不是接云端是因为宠物生命周期管理的数据大部分是个人记录本地存储完全够用还省去了后端搭建和账号体系的复杂度。如果后续要加多设备同步再引入云端同步层也不迟。TypeScript 是必须的因为 AI 生成的代码如果没有类型约束很容易出现字段名拼写不一致的问题有了 TS 的类型检查这类低级错误在编译阶段就能暴露出来。状态管理我用的是 Zustand没用 Redux。原因很简单这个项目的状态复杂度不高Redux 的样板代码太多AI 生成的时候容易在 action、reducer、selector 之间绕晕。Zustand 的 API 更简洁AI 理解起来也更直接。提示技术选型阶段一定要让 AI 明确知道你选了什么、为什么选这样它后续生成代码时才会保持一致性。如果你中途换技术栈之前生成的代码基本要重来。2. 数据模型与核心结构拆解2.1 宠物生命周期状态机的设计整个 App 的核心是围绕宠物的生命周期状态来组织的。我把宠物的生命阶段划分为五个状态幼年期、成年期、老年期、临终期、已离世。每个状态对应不同的数据采集重点和提醒策略。幼年期0-1 岁的重点是疫苗和驱虫需要高频提醒成年期1-7 岁重点是日常喂养和年度体检老年期7 岁以上需要增加慢性病监测和更频繁的体检临终期需要记录护理日志和用药情况已离世状态则转为纪念模式保留历史数据但关闭提醒。这个状态机不是让用户手动切换的而是根据宠物的出生日期自动计算同时允许用户手动覆盖。为什么要允许手动覆盖因为有些宠物的出生日期是估算的实际发育情况可能和年龄不完全对应用户需要有一定的调整空间。// 宠物生命周期状态定义 type LifeStage infant | adult | senior | hospice | passed; interface Pet { id: string; name: string; species: dog | cat | other; breed: string; birthDate: string; estimatedBirth: boolean; // 是否为估算日期 manualStageOverride?: LifeStage; avatar?: string; createdAt: string; updatedAt: string; } // 根据出生日期计算生命阶段 function calculateLifeStage(pet: Pet): LifeStage { if (pet.manualStageOverride) return pet.manualStageOverride; const ageInMonths getAgeInMonths(pet.birthDate); const seniorThreshold pet.species dog ? 84 : 96; // 狗7年猫8年 if (ageInMonths 12) return infant; if (ageInMonths seniorThreshold) return adult; return senior; }这里有个细节值得说猫和狗的老年期阈值不一样。狗一般 7 岁进入老年猫一般 8 岁。这个差异如果不处理用户会觉得 App 不够专业。AI 在生成这段逻辑的时候我特意在 prompt 里强调了物种差异它才把这个分支加进去。如果你不强调它默认会用一个统一阈值。2.2 事件记录的数据结构生命周期管理的核心是事件记录。我把事件分成几大类健康类疫苗、驱虫、体检、用药、手术、日常类喂食、换粮、洗澡、美容、行为类训练、社交、异常行为、纪念类生日、领养日、照片。每类事件共享一个基础结构但有不同的扩展字段。这种设计的好处是查询和展示逻辑可以统一处理同时保留各类事件的特殊性。interface BaseEvent { id: string; petId: string; type: EventType; title: string; date: string; notes?: string; attachments?: string[]; createdAt: string; } interface HealthEvent extends BaseEvent { type: health; category: vaccine | deworming | checkup | medication | surgery; nextDueDate?: string; // 下次到期时间 vetName?: string; cost?: number; } interface DailyEvent extends BaseEvent { type: daily; category: feeding | food_change | bath | grooming; foodBrand?: string; reaction?: string; // 换粮后的反应记录 }nextDueDate这个字段是提醒功能的关键。疫苗和驱虫都有周期性记录完当前这次之后系统需要自动计算出下次的时间并设置提醒。这个计算逻辑我让 AI 根据事件类型和宠物的生命阶段来生成不同阶段的提醒频率不一样。2.3 提醒规则的优先级设计提醒不是简单的时间到了就弹通知。我设计了一个优先级系统紧急疫苗过期、用药时间到、重要体检到期、驱虫到期、常规洗澡提醒、换粮提醒、低优先生日提醒、纪念日。优先级影响通知的展示方式和频率。紧急提醒会立即弹出并持续显示重要提醒每天推送一次常规提醒每周汇总推送低优先只在 App 内展示不推送系统通知。这个设计是为了避免通知轰炸用户如果被太多通知打扰很快就会关掉所有通知权限那提醒功能就废了。注意提醒规则的设计一定要考虑用户的接受度。我第一版做的时候所有提醒都走系统通知测试的时候一天弹了十几条自己都受不了。后来改成分级推送才合理。3. 项目初始化与核心模块实现3.1 项目脚手架搭建项目初始化这一步我直接让 AI 生成完整的初始化命令和配置文件。关键是要给它明确的版本约束不然它可能生成过时的配置。# 项目初始化命令 npx create-expo-applatest pet-lifecycle --template blank-typescript cd pet-lifecycle # 核心依赖安装 npx expo install expo-sqlite expo-notifications expo-image-picker npx expo install react-navigation/native react-navigation/bottom-tabs npx expo install react-native-screens react-native-safe-area-context npm install zustand date-fns依赖选择上有几个点要说明。expo-sqlite是 Expo 官方封装的 SQLite比第三方库稳定expo-notifications处理本地通知宠物提醒不需要服务端推送本地通知完全够用date-fns处理日期计算比 moment.js 轻量而且 AI 对 date-fns 的 API 生成准确率更高。目录结构我让 AI 按照功能模块来组织而不是按文件类型src/ db/ # 数据库层 schema.ts # 表结构定义 migrations.ts queries.ts # 查询封装 models/ # 数据模型 pet.ts event.ts reminder.ts screens/ # 页面 HomeScreen.tsx PetDetailScreen.tsx AddEventScreen.tsx ReminderScreen.tsx components/ # 通用组件 PetCard.tsx EventTimeline.tsx ReminderBadge.tsx stores/ # 状态管理 petStore.ts eventStore.ts utils/ # 工具函数 dateUtils.ts stageCalculator.ts这种组织方式的好处是每个模块的职责清晰AI 在后续生成代码时能准确找到该往哪个文件里放。如果按 components/screens/utils 这种类型分AI 经常会把业务逻辑写到组件文件里后期维护很痛苦。3.2 数据库层实现数据库这块是整个项目的地基我让 AI 先生成 schema确认表结构没问题之后再生成查询封装。// db/schema.ts export const CREATE_TABLES CREATE TABLE IF NOT EXISTS pets ( id TEXT PRIMARY KEY, name TEXT NOT NULL, species TEXT NOT NULL, breed TEXT, birth_date TEXT NOT NULL, estimated_birth INTEGER DEFAULT 0, manual_stage TEXT, avatar TEXT, created_at TEXT NOT NULL, updated_at TEXT NOT NULL ); CREATE TABLE IF NOT EXISTS events ( id TEXT PRIMARY KEY, pet_id TEXT NOT NULL, type TEXT NOT NULL, category TEXT NOT NULL, title TEXT NOT NULL, date TEXT NOT NULL, notes TEXT, attachments TEXT, next_due_date TEXT, metadata TEXT, created_at TEXT NOT NULL, FOREIGN KEY (pet_id) REFERENCES pets(id) ON DELETE CASCADE ); CREATE TABLE IF NOT EXISTS reminders ( id TEXT PRIMARY KEY, pet_id TEXT NOT NULL, event_id TEXT, title TEXT NOT NULL, due_date TEXT NOT NULL, priority TEXT NOT NULL, completed INTEGER DEFAULT 0, created_at TEXT NOT NULL, FOREIGN KEY (pet_id) REFERENCES pets(id) ON DELETE CASCADE ); CREATE INDEX IF NOT EXISTS idx_events_pet_date ON events(pet_id, date DESC); CREATE INDEX IF NOT EXISTS idx_reminders_due ON reminders(due_date, completed); ;这里有几个设计决策值得展开。metadata字段用 TEXT 存 JSON而不是给每类事件建独立的表。原因是事件类型可能会扩展如果每加一种类型就加一张表迁移成本太高。用 JSON 存扩展字段查询时在应用层解析灵活性更好。代价是无法用 SQL 直接查询 JSON 内的字段但这个项目的查询场景不需要那么复杂。索引的设计也有讲究。idx_events_pet_date是为了加速查某只宠物的所有事件按时间倒序这个最高频的查询。idx_reminders_due是为了加速查所有未完成的、即将到期的提醒。这两个索引覆盖了 90% 的查询场景。3.3 状态管理实现Zustand 的 store 设计我让 AI 按照一个领域一个 store的原则来组织。宠物和事件分开管理避免单个 store 过于臃肿。// stores/petStore.ts import { create } from zustand; import { Pet, LifeStage } from ../models/pet; import * as petQueries from ../db/queries/petQueries; interface PetState { pets: Pet[]; currentPet: Pet | null; loading: boolean; loadPets: () Promisevoid; addPet: (pet: OmitPet, id | createdAt | updatedAt) Promisevoid; updatePet: (id: string, updates: PartialPet) Promisevoid; deletePet: (id: string) Promisevoid; setCurrentPet: (pet: Pet | null) void; } export const usePetStore createPetState((set, get) ({ pets: [], currentPet: null, loading: false, loadPets: async () { set({ loading: true }); const pets await petQueries.getAllPets(); set({ pets, loading: false }); }, addPet: async (petData) { const newPet await petQueries.insertPet(petData); set({ pets: [...get().pets, newPet] }); }, updatePet: async (id, updates) { await petQueries.updatePet(id, updates); set({ pets: get().pets.map(p p.id id ? { ...p, ...updates } : p), currentPet: get().currentPet?.id id ? { ...get().currentPet!, ...updates } : get().currentPet }); }, deletePet: async (id) { await petQueries.deletePet(id); set({ pets: get().pets.filter(p p.id ! id), currentPet: get().currentPet?.id id ? null : get().currentPet }); }, setCurrentPet: (pet) set({ currentPet: pet }), }));这里有个细节updatePet的时候要同时更新pets列表和currentPet。如果只更新列表详情页的数据不会刷新如果只更新 currentPet列表页的数据会不一致。这种同一份数据存在多个地方的问题是 AI 生成代码时最容易漏掉的。我在 prompt 里明确要求它处理这个同步问题它才把两个地方都更新了。实操心得用 AI 生成状态管理代码时一定要让它明确列出哪些地方持有同一份数据然后检查它是否在所有地方都做了同步更新。这个坑我踩过不止一次。4. 核心功能实现与关键环节4.1 提醒引擎的实现提醒引擎是整个 App 最有技术含量的部分。它需要做三件事根据事件计算下次到期时间、生成提醒记录、调度本地通知。// utils/reminderEngine.ts import { addMonths, addYears, differenceInDays, parseISO } from date-fns; import * as Notifications from expo-notifications; interface ReminderRule { category: string; intervalMonths?: number; intervalYears?: number; advanceDays: number; // 提前多少天提醒 priority: urgent | important | normal | low; } const REMINDER_RULES: Recordstring, ReminderRule { vaccine: { category: vaccine, intervalYears: 1, advanceDays: 14, priority: urgent }, deworming: { category: deworming, intervalMonths: 3, advanceDays: 7, priority: important }, checkup: { category: checkup, intervalYears: 1, advanceDays: 30, priority: important }, medication: { category: medication, advanceDays: 0, priority: urgent }, bath: { category: bath, intervalMonths: 1, advanceDays: 3, priority: normal }, }; export async function scheduleReminder( petId: string, eventId: string, category: string, eventDate: string, petName: string ) { const rule REMINDER_RULES[category]; if (!rule) return; let nextDue: Date; const baseDate parseISO(eventDate); if (rule.intervalYears) { nextDue addYears(baseDate, rule.intervalYears); } else if (rule.intervalMonths) { nextDue addMonths(baseDate, rule.intervalMonths); } else { return; // 没有周期规则不需要提醒 } const notifyDate new Date(nextDue); notifyDate.setDate(notifyDate.getDate() - rule.advanceDays); // 如果提醒时间已经过了不调度 if (notifyDate new Date()) return; await Notifications.scheduleNotificationAsync({ content: { title: ${petName}的${getCategoryLabel(category)}提醒, body: 距离下次${getCategoryLabel(category)}还有${rule.advanceDays}天, data: { petId, eventId, category }, }, trigger: { date: notifyDate }, }); }这个引擎有几个关键设计点。第一提醒规则用配置表管理而不是硬编码在逻辑里。这样后续要调整某个类别的提醒提前天数改配置就行不用动逻辑。第二advanceDays的设计让用户有提前准备的时间疫苗提前 14 天提醒用户有时间预约驱虫提前 7 天因为驱虫药随时能买体检提前 30 天因为体检需要预约且可能要空腹准备。第三调度前检查notifyDate new Date()避免调度一个已经过去的时间。这个检查看起来简单但如果不做用户补录一条历史事件时系统会尝试调度一个过去的通知在某些平台上会直接报错。4.2 事件时间线的展示逻辑时间线展示是用户打开 App 后看到最多的界面。我让 AI 按照按日期分组、按类型着色、支持筛选的思路来实现。// components/EventTimeline.tsx import { format, isToday, isYesterday, parseISO } from date-fns; interface TimelineGroup { dateLabel: string; events: Event[]; } function groupEventsByDate(events: Event[]): TimelineGroup[] { const groups new Mapstring, Event[](); events.forEach(event { const date parseISO(event.date); let label: string; if (isToday(date)) { label 今天; } else if (isYesterday(date)) { label 昨天; } else { label format(date, yyyy年M月d日); } if (!groups.has(label)) { groups.set(label, []); } groups.get(label)!.push(event); }); return Array.from(groups.entries()).map(([dateLabel, events]) ({ dateLabel, events: events.sort((a, b) b.date.localeCompare(a.date)), })); }日期标签的处理有个细节今天和昨天用相对标签更早的用绝对日期。这个设计让用户对近期事件有更直观的感知。AI 一开始生成的是全部用绝对日期我要求它加上相对标签的处理它才补上了isToday和isYesterday的判断。类型着色我用的是一个映射表健康类用红色系、日常类用蓝色系、行为类用绿色系、纪念类用紫色系。颜色不直接写死而是定义在主题文件里方便后续调整。4.3 数据导出与备份功能宠物数据是用户长期积累的一旦丢失很难恢复。所以数据导出功能是必须的。我让 AI 实现了一个导出为 JSON 的功能同时支持导入恢复。// utils/dataExport.ts import * as FileSystem from expo-file-system; import * as Sharing from expo-sharing; export async function exportAllData(): Promisestring { const pets await petQueries.getAllPets(); const events await eventQueries.getAllEvents(); const reminders await reminderQueries.getAllReminders(); const exportData { version: 1.0, exportedAt: new Date().toISOString(), pets, events, reminders, }; const fileName pet-backup-${format(new Date(), yyyyMMdd-HHmmss)}.json; const filePath ${FileSystem.documentDirectory}${fileName}; await FileSystem.writeAsStringAsync( filePath, JSON.stringify(exportData, null, 2) ); return filePath; } export async function importData(filePath: string): Promisevoid { const content await FileSystem.readAsStringAsync(filePath); const data JSON.parse(content); if (data.version ! 1.0) { throw new Error(不支持的备份文件版本); } // 事务性导入失败则回滚 await db.withTransactionAsync(async () { for (const pet of data.pets) { await petQueries.insertPet(pet); } for (const event of data.events) { await eventQueries.insertEvent(event); } for (const reminder of data.reminders) { await reminderQueries.insertReminder(reminder); } }); }导入用事务包裹是关键。如果导入到一半失败没有事务的话数据库会处于半导入状态用户既没恢复成功原来的数据也可能被覆盖。有了事务失败自动回滚数据保持一致。注意导出文件里包含宠物的所有信息属于隐私数据。分享的时候要提醒用户注意接收方不要随便发到公开渠道。这个提示我在导出成功的弹窗里加了一句。5. 常见问题与排查技巧实录5.1 AI 生成代码的典型问题用 AI 从零搭建项目踩的坑和传统开发不太一样。我整理了几个高频问题问题现象根本原因解决方法模块间接口对不上AI 分次生成时上下文丢失每次生成前把相关模块的接口定义作为上下文喂给它字段名拼写不一致没有类型约束全程用 TypeScript开启严格模式状态更新不同步同一数据存在多处明确列出所有持有该数据的位置逐一检查依赖版本冲突AI 训练数据滞后用 expo install 而不是 npm install 装 Expo 相关依赖通知不触发权限未申请或调度时间已过检查权限状态调度前校验时间接口对不上这个问题最典型。比如 AI 先生成了petQueries.getAllPets()返回Pet[]后来生成 store 的时候又写成返回{ data: Pet[] }。这种不一致在运行时才会暴露排查起来很费时间。我的做法是在生成 store 之前把 queries 文件的函数签名复制到 prompt 里让 AI 严格按签名来调用。5.2 数据库迁移的处理项目迭代过程中表结构会变比如后来我给 events 表加了metadata字段。SQLite 不支持直接删列加列倒是可以。我让 AI 实现了一个简单的迁移机制// db/migrations.ts const MIGRATIONS [ { version: 1, sql: ALTER TABLE events ADD COLUMN metadata TEXT; }, { version: 2, sql: CREATE INDEX IF NOT EXISTS idx_events_type ON events(type); }, ]; export async function runMigrations(db: SQLiteDatabase) { const result await db.getFirstAsync{ user_version: number }( PRAGMA user_version ); const currentVersion result?.user_version ?? 0; for (const migration of MIGRATIONS) { if (migration.version currentVersion) { await db.execAsync(migration.sql); await db.execAsync(PRAGMA user_version ${migration.version}); } } }用PRAGMA user_version来记录当前版本每次启动时检查并执行未执行的迁移。这个机制简单但够用。关键是迁移脚本要幂等用IF NOT EXISTS之类的语法避免重复执行报错。5.3 性能优化的几个实测点宠物数据量大了之后时间线页面会变卡。我实测了几个优化点第一事件列表用FlatList而不是ScrollView并且设置keyExtractor和getItemLayout。getItemLayout对固定高度的列表项能大幅提升滚动性能因为省去了动态测量。第二数据库查询加LIMIT时间线默认只加载最近 50 条下拉再加载更多。一次性查全部事件在数据量上千之后会明显变慢。第三图片附件不要直接存 base64 到数据库存文件路径展示时用expo-image加载它自带缓存。base64 存数据库会让数据库文件迅速膨胀查询也变慢。第四提醒调度不要每次启动都重新调度所有提醒只调度新增的。已调度的提醒 ID 存下来避免重复调度。重复调度会导致同一个提醒弹多次通知。实操心得性能问题一定要在真机上测模拟器上跑得飞快不代表真机没问题。我一开始在模拟器上测时间线很流畅真机上数据量上来之后卡得明显后来加了getItemLayout才解决。5.4 通知权限的兼容处理通知权限在不同平台上的行为不一样。iOS 需要显式申请用户拒绝后要引导去设置里开启Android 13 以上也需要运行时申请。我让 AI 写了一个统一的权限处理函数export async function ensureNotificationPermission(): Promiseboolean { const { status: existingStatus } await Notifications.getPermissionsAsync(); if (existingStatus granted) return true; const { status } await Notifications.requestPermissionsAsync(); if (status ! granted) { Alert.alert( 需要通知权限, 提醒功能需要通知权限才能正常工作请在系统设置中开启, [ { text: 稍后, style: cancel }, { text: 去设置, onPress: () Linking.openSettings() }, ] ); return false; } return true; }这个函数在用户第一次添加提醒时调用而不是在 App 启动时就调用。启动时就申请权限用户还没理解 App 是干什么的大概率会拒绝。等到用户主动添加提醒时再申请同意率会高很多。这个细节是产品体验层面的AI 不会主动想到需要你在 prompt 里明确要求。6. 后续扩展方向与个人体会这个项目搭完之后我陆续加了一些扩展功能。比如宠物头像用 AI 生成卡通形象这个接一个图像生成接口就行比如多宠物对比视图把两只宠物的健康数据放在一起看比如体重曲线图用react-native-svg画折线图记录宠物的体重变化趋势。体重曲线这个功能特别实用。老年宠物如果体重持续下降往往是健康问题的早期信号。我让 AI 实现了一个简单的趋势检测如果连续三次记录体重下降超过 5%就在首页给出提示。这个逻辑不复杂但价值很高。数据同步这块我暂时没做因为本地存储对单用户场景够用。如果要做多设备同步我的思路是加一个同步层本地数据库作为主存储同步层负责把变更推送到云端。冲突解决用最后写入优先策略对个人记录类数据来说够用了。回过头看用 AI 从零搭建这个项目最大的体会是AI 擅长生成结构化的代码但不擅长做架构决策。数据模型怎么设计、状态怎么管理、模块怎么划分这些需要你自己想清楚然后明确告诉 AI。你给的约束越清晰AI 生成的代码质量越高。如果你只是说帮我做一个宠物管理 App它生成的东西大概率是能跑但没法维护的。另一个体会是分阶段验证比一次性生成重要得多。每完成一个模块就立刻跑起来验证发现问题马上修。如果等所有模块都生成完再一起测问题会交织在一起排查成本成倍增加。我一般是数据库层写完就先写个测试脚本验证增删改查确认没问题再往上写业务逻辑。最后分享一个小技巧让 AI 生成代码时要求它在关键函数上写注释说明这个函数解决什么问题、有什么边界条件。这些注释在后续排查问题时特别有用因为过几天你自己也会忘记当时的思路。而且写注释的过程会迫使 AI 更仔细地思考逻辑减少低级错误。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑