☰
AI编程助手从零搭建宠物生命周期管理App实战
2026/10/9 15:38:17 网站建设 项目流程

1. 为什么我决定用 AI 编程助手从零搭一个宠物生命周期管理 App

先说说这个项目的来龙去脉。我手上一直有个想法:做一个宠物生命周期管理 App,覆盖从领养/购买、疫苗接种、驱虫、体检、发情/绝育、配种/繁育、日常喂养记录,到老年护理、临终关怀、身后事处理这一整条时间线。市面上大多数宠物类 App 只做单点功能,比如只做疫苗提醒,或者只做社区晒图,真正把"一只宠物从进家门到离开"全流程串起来的工具非常少。而这件事恰好特别适合用 AI 编程助手来从零搭建——因为它的业务逻辑清晰、数据模型规整、页面结构重复度高,属于"想清楚了就能快速落地"的类型。

我这次全程用的是 Claude Code 这类终端里的 AI 编程助手(下面统一叫"AI 助手"),从空目录开始,一步步把项目骨架、数据模型、核心页面、状态管理、本地存储、提醒逻辑全部搭出来。整个过程我踩了不少坑,也总结出一套比较顺手的流程。这篇文章就是把这套流程完整拆开讲清楚:它是什么、能做什么、适合谁来参考。如果你是想独立做一个垂直领域 App 的开发者、想学 AI 辅助编程的初学者,或者手上正好有宠物相关产品需求的产品同学,这篇内容应该能直接抄作业。

我先把结论放前面:AI 助手不是"你说一句它给你一个完整 App",它更像一个执行力极强但需要你当架构师的搭档。你负责想清楚数据结构和业务边界,它负责把代码敲出来、把报错修掉、把重复劳动干掉。这个分工搞明白了,效率能翻好几倍;搞不明白,就会陷入"它写的东西我看不懂、我改不动"的泥潭。

下面我按真实搭建顺序,从整体设计思路、数据模型、核心页面、状态管理、提醒系统、问题排查几个维度,把整个流程讲透。

2. 项目整体设计与技术选型思路拆解

2.1 为什么先定数据模型再写页面

很多人用 AI 助手做项目,第一句话就是"帮我做一个宠物 App"。这种指令几乎必然翻车,因为 AI 不知道你的业务边界在哪,它会自由发挥,最后给你一堆看起来能跑、但字段对不上、页面之间数据不通的代码。我的做法是:先花时间把数据模型想清楚,再让 AI 写代码。

宠物生命周期管理的核心实体其实就几个:宠物(Pet)、事件记录(Event)、提醒(Reminder)、健康档案(HealthRecord)、喂养日志(FeedingLog)。它们之间的关系是:一只宠物对应多条事件记录、多条提醒、多条健康档案、多条喂养日志。这个一对多关系是整个 App 的骨架。

我让 AI 助手做的第一件事,不是写页面,而是根据我口述的业务需求,生成一份 TypeScript 类型定义。这样做的理由是:类型定义是"契约",一旦定下来,后面所有页面、状态管理、存储逻辑都围绕它展开,AI 每次生成代码时都能引用这份契约,不会跑偏。

// types/pet.ts export type PetSpecies = 'dog' | 'cat' | 'rabbit' | 'bird' | 'other'; export type PetGender = 'male' | 'female' | 'unknown'; export type LifeStage = 'puppy' | 'adult' | 'senior'; export interface Pet { id: string; name: string; species: PetSpecies; breed: string; gender: PetGender; birthday: string; // ISO 日期 adoptedAt: string; // 进家门日期 avatar?: string; weightKg?: number; lifeStage: LifeStage; createdAt: string; updatedAt: string; } export interface EventRecord { id: string; petId: string; type: 'vaccine' | 'deworm' | 'checkup' | 'neuter' | 'mating' | 'birth' | 'other'; title: string; occurredAt: string; note?: string; cost?: number; attachments?: string[]; } export interface Reminder { id: string; petId: string; eventType: EventRecord['type']; nextDueAt: string; repeatCycleDays?: number; // 周期性提醒,如每月驱虫 enabled: boolean; }

这份类型定义看起来简单,但它决定了后面所有工作的方向。我特别想强调lifeStage这个字段——宠物生命周期管理的核心就是"阶段感知"。幼年期该打什么疫苗、成年期该多久体检一次、老年期该关注哪些指标,逻辑完全不同。如果一开始不把这个字段设计进去,后面做提醒逻辑时会非常痛苦,因为你要靠生日去反推阶段,代码里到处是重复计算。

2.2 技术栈选择:为什么是这套组合

技术选型我考虑了几个约束:一是要能快速跑起来看到效果,二是要方便 AI 助手理解和生成代码,三是尽量少依赖原生能力,避免 AI 生成的代码在真机上跑不起来。

最终我选的是:React Native + Expo + TypeScript + Zustand + AsyncStorage。理由如下。

React Native 配合 Expo,最大的好处是 AI 助手对这套组合的代码模式非常熟悉,生成的代码质量稳定。Expo 把原生配置、构建、调试都封装好了,AI 不需要去处理那些容易出错的 Gradle、Podfile 配置。TypeScript 前面说了,是契约。Zustand 做状态管理,比 Redux 轻太多,AI 生成起来也不容易出错。AsyncStorage 做本地持久化,对于这种个人数据为主的 App 完全够用,不需要一上来就上数据库。

提示:如果你打算做多端同步或者数据量很大,AsyncStorage 会不够用,那时候再考虑 SQLite 或者云端方案。但第一版千万别过度设计,先把核心流程跑通。

这里有个经验:技术栈越主流,AI 助手的表现越稳。我试过让它用一些比较小众的库,生成的代码经常是过时的 API 或者干脆编造不存在的函数。主流组合虽然"不酷",但省下来的调试时间远超那点新鲜感。

2.3 目录结构规划:让 AI 有章可循

在写任何业务代码之前,我让 AI 助手先生成一份目录结构。这一步很多人会跳过,但它极其重要。因为 AI 助手在后续每次生成代码时,都会参考已有目录结构来决定文件放哪。如果目录是乱的,它就会乱放。

pet-lifecycle/ ├── app/ # Expo Router 页面 │ ├── (tabs)/ │ │ ├── index.tsx # 首页:宠物列表 │ │ ├── timeline.tsx # 时间线:事件记录 │ │ ├── reminders.tsx # 提醒中心 │ │ └── profile.tsx # 我的 │ ├── pet/ │ │ ├── [id].tsx # 宠物详情 │ │ └── new.tsx # 新增宠物 │ └── event/ │ └── new.tsx # 新增事件 ├── components/ # 通用组件 ├── store/ # Zustand 状态 ├── storage/ # 持久化封装 ├── types/ # 类型定义 ├── utils/ # 工具函数 └── constants/ # 常量配置

这个结构定下来之后,我后面每次让 AI 写代码,都会明确告诉它"文件放在 store/petStore.ts",它就不会乱来。这是让 AI 助手保持长期一致性的关键技巧。

3. 核心数据层与状态管理的实操要点

3.1 Zustand store 的拆分逻辑

状态管理这块,我踩过一个坑:一开始把所有状态塞进一个 store,结果文件越来越长,AI 助手每次修改都要重新读整个文件,容易改错地方。后来我按领域拆成了三个 store:petStore、eventStore、reminderStore。

拆分的依据是"谁经常一起变"。宠物信息和事件记录虽然有关联,但更新频率和触发场景不同,分开管理更清晰。提醒逻辑又依赖事件记录(比如打完疫苗后自动生成下次提醒),所以它单独一个 store,通过订阅事件变化来触发。

// store/petStore.ts import { create } from 'zustand'; import { Pet } from '../types/pet'; import { loadPets, savePets } from '../storage/petStorage'; interface PetState { pets: Pet[]; loading: boolean; loadAll: () => Promise<void>; addPet: (pet: Omit<Pet, 'id' | 'createdAt' | 'updatedAt'>) => Promise<void>; updatePet: (id: string, patch: Partial<Pet>) => Promise<void>; removePet: (id: string) => Promise<void>; } export const usePetStore = create<PetState>((set, get) => ({ pets: [], loading: false, loadAll: async () => { set({ loading: true }); const pets = await loadPets(); set({ pets, loading: false }); }, addPet: async (input) => { const now = new Date().toISOString(); const pet: Pet = { ...input, id: generateId(), createdAt: now, updatedAt: now, }; const next = [...get().pets, pet]; set({ pets: next }); await savePets(next); }, // updatePet / removePet 同理 }));

这里有个关键设计:每次修改状态后立即持久化。很多教程会建议"退出时统一保存",但对于宠物记录这种数据,用户随时可能杀进程,统一保存容易丢数据。即时持久化虽然多写几次磁盘,但数据安全性高得多。

3.2 持久化封装的注意事项

AsyncStorage 的封装我让 AI 写了两层:底层是通用的storage.ts,负责 JSON 序列化和错误处理;上层是各领域的petStorage.ts、eventStorage.ts,负责具体的 key 管理和数据迁移。

// storage/storage.ts import AsyncStorage from '@react-native-async-storage/async-storage'; export async function readJSON<T>(key: string, fallback: T): Promise<T> { try { const raw = await AsyncStorage.getItem(key); if (!raw) return fallback; return JSON.parse(raw) as T; } catch (e) { console.warn(`[storage] read ${key} failed`, e); return fallback; } } export async function writeJSON<T>(key: string, value: T): Promise<void> { try { await AsyncStorage.setItem(key, JSON.stringify(value)); } catch (e) { console.warn(`[storage] write ${key} failed`, e); } }

注意:readJSON一定要有 fallback,否则首次启动时读到 null 会直接崩。这个坑我在真机上遇到过,模拟器上因为之前存过数据反而没暴露。

数据迁移这块我留了个心眼。因为 App 会迭代,字段会变,我在 storage 层加了个schemaVersion字段。每次读取时检查版本号,如果低于当前版本就执行迁移函数。这个设计第一版用不上,但第二版加字段时能救命。

3.3 事件与提醒的联动逻辑

这是整个 App 最有技术含量的部分。宠物生命周期管理的核心价值,就是"记录一次事件,自动推导出下一次该做什么"。比如记录了一次疫苗接种,系统应该自动算出下次接种时间并生成提醒。

我让 AI 助手实现了一个deriveReminders函数,输入是事件记录,输出是应该生成的提醒列表。逻辑用一张规则表驱动:

事件类型触发条件生成提醒周期
vaccine幼年首次下次疫苗21 天后
vaccine成年加强年度加强365 天后
deworm任意下次驱虫30 或 90 天
checkup成年年度体检365 天后
checkup老年半年体检180 天后
neuter任意术后复查7 天后

这张表是整个 App 的"业务大脑"。我把它抽成常量配置,而不是硬编码在函数里,这样调整规则时不用改逻辑代码。AI 助手生成这个函数时,我明确告诉它"规则从 constants/reminderRules.ts 读取",它就乖乖照做了。

// constants/reminderRules.ts export const REMINDER_RULES: ReminderRule[] = [ { eventType: 'vaccine', lifeStage: 'puppy', offsetDays: 21, cycleDays: undefined }, { eventType: 'vaccine', lifeStage: 'adult', offsetDays: 365, cycleDays: 365 }, { eventType: 'deworm', lifeStage: 'puppy', offsetDays: 30, cycleDays: 30 }, { eventType: 'deworm', lifeStage: 'adult', offsetDays: 90, cycleDays: 90 }, { eventType: 'checkup', lifeStage: 'adult', offsetDays: 365, cycleDays: 365 }, { eventType: 'checkup', lifeStage: 'senior', offsetDays: 180, cycleDays: 180 }, ];

这里有个实操心得:规则表要允许"无匹配"的情况。比如用户记录了一个"其他"类型的事件,规则表里没有对应项,函数应该返回空数组而不是报错。我在让 AI 写这个函数时特意强调了这点,它一开始写的是rules.find(...)然后直接取属性,会崩。改成rules.find(...)后判断是否存在,才稳。

4. 核心页面搭建与 AI 协作实操过程

4.1 首页宠物列表:从空状态到有数据

首页是整个 App 的门面,也是 AI 助手最容易"过度设计"的地方。我第一次让它写首页,它给我整了个带轮播图、推荐位、社区入口的复杂页面,完全偏离了"宠物列表"这个核心。后来我调整了指令方式:先描述清楚页面要解决什么问题,再让它写。

我的描述是:"首页只做一件事,展示用户所有宠物的卡片列表,每张卡片显示头像、名字、品种、年龄、最近一次事件。没有宠物时显示引导新增的空状态。不要加任何其他模块。"

这样它写出来的就干净多了。卡片组件我单独抽成PetCard,方便复用。

// components/PetCard.tsx export function PetCard({ pet, lastEvent, onPress }: PetCardProps) { const age = calcAge(pet.birthday); return ( <Pressable style={styles.card} onPress={onPress}> <Image source={{ uri: pet.avatar }} style={styles.avatar} /> <View style={styles.info}> <Text style={styles.name}>{pet.name}</Text> <Text style={styles.meta}>{pet.breed} · {age}</Text> <Text style={styles.lastEvent}> {lastEvent ? `最近:${lastEvent.title}` : '暂无记录'} </Text> </View> </Pressable> ); }

年龄计算这个calcAge函数看着简单,其实有讲究。宠物年龄不能简单按 365 天算,因为幼年期按月算更直观。我让 AI 写的逻辑是:不满 1 岁显示"X 个月",1 到 7 岁显示"X 岁",7 岁以上显示"X 岁(老年)"。这个细节让 App 显得更懂宠物。

4.2 宠物详情页:生命周期时间线的呈现

详情页是整个 App 的灵魂,它要把一只宠物的所有事件按时间倒序排成一条时间线。这个页面我让 AI 助手迭代了三轮才满意。

第一轮它写的是简单的 FlatList,每条记录一行文字。问题是视觉上完全体现不出"生命周期"的感觉。第二轮我要求加时间轴样式,左侧一条竖线,每个事件一个圆点。第三轮我要求按事件类型用不同颜色区分,疫苗是蓝色、驱虫是绿色、体检是橙色。

// app/pet/[id].tsx 核心片段 const events = useEventStore(s => s.eventsByPet[petId] ?? []); return ( <ScrollView> <PetHeader pet={pet} /> <LifeStageBar stage={pet.lifeStage} /> <View style={styles.timeline}> {events.map((ev, idx) => ( <TimelineItem key={ev.id} event={ev} isLast={idx === events.length - 1} color={EVENT_COLORS[ev.type]} /> ))} </View> </ScrollView> );

这里有个经验:AI 助手对"视觉描述"的理解能力有限,但对"结构描述"理解得很好。与其说"做得好看点",不如说"左侧竖线,每个事件一个圆点,圆点颜色按类型区分"。后者它能一次写对。

LifeStageBar这个组件是我比较得意的设计,它把宠物当前所处的生命阶段可视化出来——幼年、成年、老年三段,当前阶段高亮。这个组件让用户一眼就知道自己的宠物处于什么阶段,该关注什么。AI 助手写这个组件时,我给它画了个 ASCII 草图,它理解得很快。

4.3 新增事件表单:动态字段的处理

新增事件表单是交互最复杂的地方,因为不同事件类型需要的字段不一样。疫苗需要疫苗名称和批号,驱虫需要药品名称,体检需要体重和结论。如果每种类型写一个表单,代码量爆炸。

我的方案是:一个表单,字段动态渲染。用一份配置描述每种事件类型需要哪些字段,表单根据配置渲染。

// constants/eventFields.ts export const EVENT_FIELDS: Record<EventType, FieldConfig[]> = { vaccine: [ { key: 'title', label: '疫苗名称', type: 'text', required: true }, { key: 'batchNo', label: '批号', type: 'text' }, { key: 'occurredAt', label: '接种日期', type: 'date', required: true }, ], checkup: [ { key: 'title', label: '体检项目', type: 'text', required: true }, { key: 'weightKg', label: '体重(kg)', type: 'number' }, { key: 'note', label: '体检结论', type: 'textarea' }, { key: 'occurredAt', label: '体检日期', type: 'date', required: true }, ], // ... };

这个设计的好处是,加新的事件类型只需要改配置,不用动表单代码。AI 助手写这个表单时,我明确告诉它"字段从 EVENT_FIELDS 读取,用 map 渲染",它写出来的代码就很干净。

提示:动态表单最容易出的问题是"字段值初始化"。切换事件类型时,旧字段的值要清掉,否则会串数据。我让 AI 在类型切换时重置表单状态,这个细节它一开始漏了,我测试时才发现。

4.4 提醒中心:时间排序与状态管理

提醒中心要展示所有待办提醒,按到期时间排序,已过期的标红,即将到期的标黄。这个页面逻辑不复杂,但有个坑:提醒的"已过期"状态是随时间变化的,不是静态的。

我一开始让 AI 把状态存在数据里,结果发现 App 放几天再打开,状态就不对了。正确做法是每次渲染时根据当前时间实时计算状态。

function getReminderStatus(dueAt: string): 'overdue' | 'soon' | 'normal' { const now = Date.now(); const due = new Date(dueAt).getTime(); const diffDays = (due - now) / (1000 * 60 * 60 * 24); if (diffDays < 0) return 'overdue'; if (diffDays <= 7) return 'soon'; return 'normal'; }

这个函数每次渲染都调用,虽然有点计算量,但提醒数量通常不多,完全没问题。这个"实时计算而非存储状态"的思路,是处理时间相关逻辑的通用原则,值得记住。

5. 常见问题排查与避坑经验实录

5.1 AI 助手生成代码的典型问题

用 AI 助手做项目,最常遇到的问题不是它写不出来,而是它写出来的东西"看起来对但跑不通"。我整理了几类高频问题。

第一类是依赖版本不匹配。AI 的训练数据有时间截止点,它可能生成某个库旧版本的 API。比如它给我写的 Expo Router 代码用的是旧版的文件命名约定,新版已经改了。解决办法是:每次引入新库,先让它查一下当前版本的正确用法,或者我自己去官方文档确认后再让它写。

第二类是类型定义和实际使用不一致。它可能在 A 文件定义Pet有avatar字段,在 B 文件使用时却写成photo。解决办法是:所有类型定义集中在一个目录,让 AI 每次生成代码时引用同一份类型。

第三类是异步逻辑遗漏 await。这在存储操作里特别常见,它写了savePets(next)但没 await,导致数据可能没存完就返回了。解决办法是:让 AI 写完后自己检查一遍所有异步调用,或者用 ESLint 规则强制检查。

问题类型典型表现解决思路
版本不匹配API 报错、函数不存在引入新库前先确认版本用法
类型不一致字段名对不上、类型报错类型定义集中管理
异步遗漏数据偶发丢失强制 await + ESLint 检查
过度设计页面臃肿、偏离需求指令先讲清"解决什么问题"
状态不同步页面数据不刷新统一走 store,不直接改本地 state

5.2 真机调试踩过的坑

模拟器上跑得好好的,真机上出问题,这是移动开发的老毛病。我这次遇到两个。

一个是日期处理。iOS 和 Android 对日期字符串的解析行为不一致,new Date('2024-01-01')在 iOS 上可能被解析成 UTC 时间,导致显示差一天。解决办法是统一用 ISO 格式带时区,或者用日期库处理。

另一个是AsyncStorage 的容量限制。Android 上单个 key 有大小限制,如果用户存了大量事件记录,可能会写失败。我的处理是把事件按宠物分 key 存储,而不是所有事件一个大 key。这样单个 key 不会太大。

注意:真机测试一定要在项目早期就开始,不要等全部做完才上真机。越早暴露平台差异,修复成本越低。

5.3 让 AI 助手更听话的指令技巧

用了这么久,我总结出几条让 AI 助手输出质量更高的指令技巧。

第一,给上下文,不给结论。不要说"帮我优化这个函数",要说"这个函数在宠物数量超过 100 时会卡,因为每次都全量重算,帮我改成增量更新"。后者它能精准定位问题。

第二,一次只做一件事。不要在一个指令里让它同时改数据模型、改页面、改存储。分开做,每步验证通过再下一步。这样出问题时容易定位。

第三,要求它解释。我经常让它写完代码后解释"为什么这么写"。这不仅能帮我理解代码,还能暴露它的错误假设。有几次它解释到一半自己发现逻辑有问题。

第四,善用"参考已有代码"。让它写新页面时,告诉它"参考 app/pet/[id].tsx 的结构"。这样风格统一,也减少它自由发挥的空间。

5.4 数据安全与隐私的考虑

宠物 App 虽然不涉及敏感信息,但用户的宠物数据、消费记录、位置信息还是要注意。我的处理是:所有数据默认只存本地,不上传。如果后续要做云同步,也要让用户明确授权。

另外,导出功能我做了个 JSON 导出,让用户可以随时把自己的数据导出来。这既是隐私保护,也是数据安全——万一 App 出问题,用户数据不丢。这个功能 AI 助手写起来很快,但价值很高,建议所有本地存储类 App 都加上。

6. 项目后续可扩展的方向与个人体会

这个 App 第一版跑通后,我陆续加了些东西。比如体重曲线图,用事件记录里的体重数据画折线,能直观看到宠物胖瘦变化。比如多宠物对比,把几只宠物的疫苗记录放一起看,方便多宠家庭管理。比如纪念日提醒,宠物生日、进家门纪念日这些。

技术上,如果要做云端同步,我建议用 Supabase 这类带实时能力的后端,AI 助手对它的支持也不错。但一定要先把本地版本跑稳,再考虑上云,否则问题会翻倍。

我个人在实际操作中的体会是:AI 助手最大的价值不是"帮你写代码",而是"帮你把想法快速变成可运行的东西"。它让你能在几小时内看到一个能跑的原型,而不是花几天搭环境。但前提是你自己得想清楚要做什么。想不清楚,AI 只会帮你更快地做出一个混乱的东西。

最后分享一个小技巧:每次让 AI 助手写完一个模块,让它顺手写一份简短的 README,说明这个模块做什么、关键文件在哪、有什么注意事项。积累下来,项目文档就有了,后面回头看或者交接都方便。这个习惯我坚持了整个项目,收益很大。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询