1. 为什么我选择用AI全流程搭建一个宠物生命周期管理App
先说结论:这个项目从空目录到可运行的原型,我全程没有手写超过200行代码,绝大部分逻辑、组件、数据模型甚至测试用例都是通过对话式AI编程工具生成的。整个过程大概花了两个周末的碎片时间,最终跑通了一个能记录宠物从领养、疫苗、驱虫、体检到离世全过程的移动端应用。
宠物生命周期管理这个需求,其实是我身边好几个养宠朋友的真实痛点。他们手机里散落着各种备忘录、相册、微信收藏,疫苗本拍完就忘,驱虫时间靠脑补,体检报告找不到,等到宠物生病了才手忙脚乱翻记录。市面上虽然有一些宠物管理App,但要么功能太重,要么数据不在自己手里,要么就是广告满天飞。所以我想做一个轻量的、数据本地优先的、能覆盖宠物完整生命周期的工具。
这个项目适合谁看?如果你是有一定编程基础但没时间从零写样板代码的开发者,或者你想了解AI编程工具在真实项目里到底能扛多少活,再或者你自己养宠、想动手做一个趁手的小工具,那这篇内容应该能给你不少参考。我会把整个搭建流程、关键决策、踩过的坑、以及AI在哪些环节特别靠谱、哪些环节必须人工兜底,全部摊开来讲。
核心关键词我先埋在这里:Claude Code、AI全流程开发、宠物生命周期管理、从零搭建、移动端应用。后面所有内容都围绕这几个词展开,不跑题。
2. 项目整体设计与技术选型思路
2.1 需求拆解:宠物生命周期到底要管什么
我先把“宠物生命周期”这个模糊概念拆成了可落地的功能模块。一只宠物从进入家庭到离开,核心事件节点其实就那么几类:
- 基础档案:品种、性别、生日、领养日期、绝育状态、芯片号、体重曲线
- 健康记录:疫苗(种类、批次、接种日期、下次到期)、驱虫(体内/体外、药品名、日期)、体检(医院、项目、报告图片)、过敏史、慢性病
- 日常事件:喂食记录、体重测量、洗澡美容、行为异常备注
- 提醒系统:疫苗到期提醒、驱虫周期提醒、体检建议提醒、用药提醒
- 纪念模块:照片时间线、重要时刻标记、离世纪念页
这五块里,提醒系统是刚需中的刚需,也是我朋友们抱怨最多的点。所以第一版我把重心放在档案+健康记录+提醒这三块,纪念模块做了个最简版。
2.2 技术栈选择:为什么是这套组合
技术选型我纠结了大概半天,最后定下来的是:
| 层级 | 选型 | 理由 |
|---|---|---|
| 客户端 | React Native + Expo | 跨平台,Expo能省掉大量原生配置,AI生成代码后能直接扫码预览 |
| 状态管理 | Zustand | 比Redux轻太多,AI生成不容易出错,样板代码少 |
| 本地存储 | SQLite (expo-sqlite) | 数据本地优先,结构化查询方便,离线可用 |
| 导航 | Expo Router | 文件路由,AI对文件结构的理解比配置式路由更准 |
| UI组件 | 自建 + 少量社区库 | 避免重型UI库,减少AI生成时的版本冲突 |
| 提醒 | expo-notifications | 本地通知,不依赖服务端 |
这里重点说两个决策逻辑。第一,为什么不用Flutter?因为我实测下来,Claude Code对React Native生态的代码生成准确率明显更高,尤其是Expo相关的API,它给出的代码基本能直接跑,Flutter的Dart代码偶尔会出现包版本对不上的情况。第二,为什么坚持本地优先?宠物数据涉及隐私,而且用户不一定随时有网,本地SQLite是最稳妥的方案,后续要同步再说。
2.3 AI全流程开发的边界在哪里
在动手之前,我给AI编程工具划了个能力边界,这个边界是我踩过坑之后总结的:
AI特别擅长的:生成CRUD代码、写SQL建表语句、生成表单组件、写工具函数、生成测试用例、解释报错、重构重复代码、生成类型定义。
AI需要人工兜底的:涉及具体业务规则的判断(比如驱虫周期到底怎么算)、UI的最终视觉调整、第三方服务的真实配置、数据迁移逻辑、性能瓶颈的定位。
AI基本帮不上忙的:产品决策、用户真实反馈的解读、上架审核相关的合规问题。
把这个边界想清楚之后,整个开发流程就顺了——我把80%的样板工作交给AI,自己专注在业务规则和最终验收上。
3. 从零搭建的核心环节与实操要点
3.1 项目初始化:让AI生成可运行骨架
第一步是创建Expo项目。我没有手动敲命令,而是直接让Claude Code帮我生成初始化命令和目录结构。我给的指令大概是这样的:
帮我创建一个基于Expo的React Native项目,使用TypeScript, 路由用Expo Router,状态管理用Zustand,本地数据库用expo-sqlite。 项目名暂定PetLife,需要包含基础的目录结构: app/ 放路由页面,components/ 放通用组件, db/ 放数据库相关,stores/ 放状态管理, utils/ 放工具函数,types/ 放类型定义。它返回的命令是:
npx create-expo-app@latest PetLife --template blank-typescript cd PetLife npx expo install expo-router expo-sqlite expo-notifications zustand然后它自动帮我改了package.json的入口,创建了app/_layout.tsx和app/index.tsx。这里有个细节要注意:Expo Router的版本和Expo SDK版本强相关,AI有时候会给出不匹配的版本号。我的做法是先用npx expo install让Expo自己解析兼容版本,而不是直接npm install。
注意:每次让AI生成依赖安装命令后,优先用
npx expo install而不是npm install,前者会自动选择与当前SDK兼容的版本,能避免大量“包版本冲突”的报错。
初始化完成后,我跑了一次npx expo start,确认能正常启动,再进入下一步。这个“先跑通再开发”的习惯非常重要,不然后面出了问题你分不清是初始化的问题还是业务代码的问题。
3.2 数据模型设计:AI辅助建表与类型定义
数据模型是整个App的地基。我先把需求用自然语言描述给AI,让它生成SQLite建表语句和对应的TypeScript类型。
我给的描述是:需要pet表存宠物基础信息,health_record表存健康记录(疫苗、驱虫、体检用type字段区分),reminder表存提醒,weight_log表存体重记录,photo表存照片关联。
AI生成的建表语句核心部分:
CREATE TABLE IF NOT EXISTS pet ( id TEXT PRIMARY KEY, name TEXT NOT NULL, species TEXT NOT NULL, breed TEXT, gender TEXT, birthday TEXT, adopt_date TEXT, neutered INTEGER DEFAULT 0, chip_no TEXT, avatar TEXT, created_at TEXT DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE IF NOT EXISTS health_record ( id TEXT PRIMARY KEY, pet_id TEXT NOT NULL, type TEXT NOT NULL, title TEXT, record_date TEXT NOT NULL, next_date TEXT, hospital TEXT, notes TEXT, FOREIGN KEY (pet_id) REFERENCES pet(id) ON DELETE CASCADE );这里我做了个关键调整:AI一开始把疫苗、驱虫、体检分成了三张表,我改成了统一用health_record加type字段。理由是这三类记录的结构高度相似(都有日期、下次日期、机构、备注),分表会导致查询和UI组件重复度极高。合并之后,一个列表组件就能渲染所有健康记录,维护成本大幅下降。
对应的TypeScript类型AI也一并生成了,我让它把type字段做成联合类型:
export type HealthRecordType = 'vaccine' | 'deworm' | 'checkup' | 'medication'; export interface HealthRecord { id: string; petId: string; type: HealthRecordType; title: string; recordDate: string; nextDate?: string; hospital?: string; notes?: string; }实操心得:让AI生成类型定义时,明确要求它使用联合类型而不是
string,这样在写业务代码时编辑器能给出准确的自动补全和类型检查,能提前拦掉一大批拼写错误。
3.3 数据库封装层:一次生成,多处复用
SQLite的原生API用起来比较啰嗦,我让AI帮我封装了一层通用的数据库操作。我给的指令是:封装一个db工具,提供initDB、insert、update、delete、queryAll、queryOne这几个方法,支持泛型,自动处理JSON字段的序列化。
AI生成的封装大概长这样:
import * as SQLite from 'expo-sqlite'; const db = SQLite.openDatabaseSync('petlife.db'); export function initDB() { db.execSync(`CREATE TABLE IF NOT EXISTS pet (...)`); db.execSync(`CREATE TABLE IF NOT EXISTS health_record (...)`); // 其他表 } export function queryAll<T>(sql: string, params: any[] = []): T[] { return db.getAllSync<T>(sql, params); } export function insert(table: string, data: Record<string, any>) { const keys = Object.keys(data); const placeholders = keys.map(() => '?').join(','); const sql = `INSERT INTO ${table} (${keys.join(',')}) VALUES (${placeholders})`; return db.runSync(sql, Object.values(data)); }这个封装层是整个项目里复用率最高的代码,后面所有业务模块都基于它。我在这里加了一个AI没想到的细节:对undefined值的处理。原生SQLite绑定参数时如果传undefined会报错,所以我在insert和update里加了一层过滤,把undefined转成null。这个坑我是实际跑的时候才发现的,AI生成的初版没考虑这个边界。
3.4 提醒系统:业务规则必须人工定义
提醒系统是AI帮不上太多忙的地方,因为“疫苗什么时候该打”“驱虫周期多久”这些是业务规则,不是代码问题。我的做法是自己先把规则定清楚,再让AI实现。
我定的规则是这样的:
- 疫苗:记录时填写
next_date,到期前7天、3天、当天各提醒一次 - 驱虫:体内每3个月,体外每月,根据上次记录日期自动推算下次日期
- 体检:每年一次,根据上次体检日期推算
- 用药:按用户填写的具体时间提醒
规则定好后,我让AI生成提醒调度逻辑。核心思路是:每次App启动时,扫描所有health_record,计算未来30天内到期的提醒,用expo-notifications注册本地通知。
export async function scheduleReminders() { const records = queryAll<HealthRecord>('SELECT * FROM health_record WHERE next_date IS NOT NULL'); const now = new Date(); const thirtyDaysLater = new Date(now.getTime() + 30 * 24 * 60 * 60 * 1000); for (const record of records) { const nextDate = new Date(record.nextDate!); if (nextDate > now && nextDate < thirtyDaysLater) { await Notifications.scheduleNotificationAsync({ content: { title: `${record.title} 即将到期`, body: `记得安排时间` }, trigger: { date: nextDate }, }); } } }注意:本地通知在iOS和Android上的权限申请时机不一样,Android 13以上需要动态申请
POST_NOTIFICATIONS权限。这个AI一开始漏了,我是在真机测试时发现通知不弹才补上的。所以涉及系统权限的功能,一定要在真机上验证,模拟器经常表现不一致。
3.5 UI组件生成:从描述到可用界面
UI部分我采用了“先描述布局,再让AI生成,最后自己微调”的流程。以宠物详情页为例,我的描述是:顶部是宠物头像和名字,下面是一排快捷操作按钮(记录疫苗、记录驱虫、记录体重),再下面是时间线形式展示所有健康记录,按日期倒序。
AI生成的组件结构基本可用,但视觉上比较朴素。我做了几处调整:把时间线的圆点改成带类型图标的样式,给不同记录类型配了不同的颜色标识,列表项加了左滑删除。这些调整我没有全部让AI做,因为视觉细节反复对话的效率不如自己直接改。
这里有个提效技巧:让AI生成组件时,要求它把样式和逻辑分离,样式用StyleSheet.create单独放在文件底部。这样我调整视觉时只需要改样式部分,不会碰到逻辑代码。
4. 完整实操流程与关键环节实现
4.1 环境准备与项目启动的完整命令序列
把前面零散的步骤串起来,从零到能跑,完整的命令序列是这样的:
# 1. 创建项目 npx create-expo-app@latest PetLife --template blank-typescript cd PetLife # 2. 安装核心依赖(用expo install保证版本兼容) npx expo install expo-router expo-sqlite expo-notifications expo-image-picker npx expo install zustand # 3. 配置Expo Router入口(修改package.json的main字段) # "main": "expo-router/entry" # 4. 启动开发服务器 npx expo start启动后用手机上的Expo Go扫码就能预览。这里提醒一句:Expo Go对某些原生模块支持有限,如果后面要加蓝牙、后台定位这类功能,需要切换到development build。宠物管理App第一版用不到这些,Expo Go足够。
4.2 数据库初始化与迁移的实操记录
数据库初始化我放在app/_layout.tsx的根布局里,确保App启动时只执行一次:
useEffect(() => { initDB(); scheduleReminders(); }, []);关于迁移,第一版我没做复杂的版本管理,只在initDB里用了CREATE TABLE IF NOT EXISTS。但这里有个隐患:如果后面要加字段,IF NOT EXISTS不会帮你改表结构。我的做法是加了一个简单的版本号机制:
const DB_VERSION = 1; export function initDB() { db.execSync('CREATE TABLE IF NOT EXISTS meta (key TEXT PRIMARY KEY, value TEXT)'); const row = db.getFirstSync<{ value: string }>('SELECT value FROM meta WHERE key = ?', ['db_version']); const currentVersion = row ? parseInt(row.value) : 0; if (currentVersion < 1) { // 建表语句 db.execSync('INSERT OR REPLACE INTO meta (key, value) VALUES (?, ?)', ['db_version', '1']); } // 后续版本在这里加 if (currentVersion < 2) { ... } }这个机制很简单但够用,后面加字段时只需要递增版本号,写对应的ALTER TABLE语句就行。AI一开始没主动提这个,是我要求它“考虑后续表结构变更”之后才补上的。
4.3 核心页面实现:宠物列表与详情页
宠物列表页的逻辑是:从数据库查所有宠物,渲染成卡片列表,点击进入详情。AI生成的初版代码里,数据获取直接写在组件的useEffect里,我改成了用Zustand的store统一管理:
// stores/petStore.ts import { create } from 'zustand'; import { queryAll } from '../db'; interface PetStore { pets: Pet[]; loadPets: () => void; addPet: (pet: Pet) => void; } export const usePetStore = create<PetStore>((set) => ({ pets: [], loadPets: () => { const pets = queryAll<Pet>('SELECT * FROM pet ORDER BY created_at DESC'); set({ pets }); }, addPet: (pet) => { insert('pet', pet); set((state) => ({ pets: [pet, ...state.pets] })); }, }));这样改的好处是,列表页、详情页、提醒模块都能共享同一份宠物数据,任何一处更新,其他页面自动刷新。AI生成的初版是每个页面各自查库,数据不同步的问题很烦人。
详情页的时间线组件是UI里最复杂的一块。我让AI生成时特别强调了“按日期倒序、不同类型显示不同图标和颜色、支持左滑删除”。它生成的代码里,左滑删除用了react-native-gesture-handler,这个库Expo默认就带,不用额外装。
4.4 提醒调度的参数计算与实测
提醒这块我实测了好几轮,把参数计算逻辑理清楚了。以驱虫为例:
- 体外驱虫:周期30天,下次日期 = 上次日期 + 30天
- 体内驱虫:周期90天,下次日期 = 上次日期 + 90天
- 疫苗:周期365天(狂犬),下次日期 = 上次日期 + 365天
这些周期我做成常量放在utils/constants.ts里,方便后续调整:
export const CYCLE_DAYS = { deworm_external: 30, deworm_internal: 90, vaccine_rabies: 365, vaccine_combined: 365, checkup: 365, };提醒触发时机我实测下来,提前7天和提前1天各提醒一次比较合理。提前太多用户会忽略,提前太少来不及安排。通知内容里带上宠物名字和记录类型,比如“豆豆的体外驱虫还有3天到期”,比干巴巴的“驱虫提醒”点击率高很多。
实操心得:本地通知的
trigger如果传具体日期,在iOS上如果这个日期已经过了,通知不会触发也不会报错,静默失败。所以调度前一定要判断日期是否在未来,这个判断AI经常漏,需要自己补。
5. 常见问题与排查技巧实录
5.1 AI生成代码的典型问题速查表
在整個开发过程中,我整理了一份AI生成代码的高频问题清单,这些问题几乎每次都会遇到,提前知道能省大量调试时间:
| 问题现象 | 根本原因 | 解决方式 |
|---|---|---|
| 包版本冲突,启动报错 | AI给的版本号与Expo SDK不匹配 | 改用npx expo install重新安装 |
| SQLite绑定undefined报错 | 参数里有undefined值 | 封装层统一转null |
| 通知不触发 | 权限未申请或日期已过 | 检查权限+判断日期未来 |
| 列表数据不刷新 | 各页面独立查库 | 用Zustand统一状态 |
| 类型报错但运行正常 | AI用了any或类型不完整 | 补全联合类型定义 |
| 路由跳转白屏 | Expo Router文件命名不规范 | 检查app目录下文件结构 |
5.2 数据库操作的避坑要点
SQLite在React Native里是同步API,这意味着如果一次查大量数据,会阻塞UI线程。宠物管理App的数据量不大,一般不会遇到性能问题,但有两个点要注意:
第一,批量插入时不要循环调用insert,每次调用都是一次独立事务,很慢。应该用事务包起来:
db.withTransactionSync(() => { for (const record of records) { insert('health_record', record); } });第二,查询时尽量只查需要的字段,不要SELECT *。虽然数据量小的时候差别不大,但养成习惯没坏处。AI生成的代码默认都是SELECT *,我一般会手动改成具体字段。
5.3 真机测试与模拟器的差异记录
这个必须单独说,因为我在这上面浪费了不少时间。模拟器和真机的差异主要体现在:
- 通知:模拟器上通知行为不稳定,真机才准
- 图片选择:
expo-image-picker在模拟器上可能没有相册权限,真机正常 - 性能:模拟器跑得动不代表真机流畅,尤其是长列表
- 字体渲染:模拟器和真机的字体度量有细微差异,可能导致布局错位
我的建议是,核心功能每完成一块就在真机上验证一次,不要等全部做完再测。真机测试用Expo Go扫码就行,改代码后自动热更新,效率很高。
5.4 数据备份与导出的实现思路
宠物数据对用户来说很珍贵,万一换手机或者误删就麻烦了。我加了一个简单的导出功能:把所有数据序列化成JSON,通过系统分享面板导出。
export async function exportData() { const pets = queryAll('SELECT * FROM pet'); const records = queryAll('SELECT * FROM health_record'); const data = { pets, records, exportAt: new Date().toISOString() }; const json = JSON.stringify(data, null, 2); await Sharing.shareAsync(`data:text/json;base64,${btoa(json)}`); }导入功能我暂时没做,因为涉及数据合并和冲突处理,逻辑比较复杂。第一版先保证能导出,用户至少有个备份。这个功能AI生成得很快,但btoa对中文支持有问题,需要先做编码转换,这个坑我踩过。
6. 我对AI全流程开发这件事的真实体会
两个周末做下来,最大的感受是:AI编程工具确实能把开发效率提升一个量级,但它提升的是“实现效率”,不是“决策效率”。产品要做什么、业务规则怎么定、数据模型怎么设计,这些还是得自己想清楚。AI更像一个执行力极强但需要明确指令的初级开发者,你给它的描述越精确,它返回的代码越可用。
另一个体会是,用AI写代码,调试能力反而更重要了。因为代码不是你一行行敲的,出了问题你得快速定位是哪一层的问题。我的习惯是每生成一块代码就先跑通再往下走,绝不攒着一堆代码一起调,那样排查起来是灾难。
这个宠物生命周期管理App目前还是个原型,但核心流程已经跑通了。后续我打算加数据同步和多人共享(比如家庭成员共同管理一只宠物),这两个功能涉及服务端,AI能帮的比例会下降,但基础的接口定义和客户端逻辑还是可以交给它。如果你也在做类似的小工具,我的建议是先从最痛的那个点做起,别一上来就追求大而全,跑通一个闭环比什么都重要。