多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

AI编程助手从零搭建宠物生命周期管理App实战

AI编程助手从零搭建宠物生命周期管理App实战 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 助手每次修改都要重新读整个文件容易改错地方。后来我按领域拆成了三个 storepetStore、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: () Promisevoid; addPet: (pet: OmitPet, id | createdAt | updatedAt) Promisevoid; updatePet: (id: string, patch: PartialPet) Promisevoid; removePet: (id: string) Promisevoid; } export const usePetStore createPetState((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 readJSONT(key: string, fallback: T): PromiseT { 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 writeJSONT(key: string, value: T): Promisevoid { 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: RecordEventType, 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不直接改本地 state5.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说明这个模块做什么、关键文件在哪、有什么注意事项。积累下来项目文档就有了后面回头看或者交接都方便。这个习惯我坚持了整个项目收益很大。
返回列表