先说个结论用 Claude Code 从零搭一个新项目不是把需求文档甩给它然后等着收代码而是一项“拆解任务、设定边界、逐屏验收”的协作工程。我拿宠物生命周期管理 App 当练手项目从空白目录开始用 AI 辅助完成了需求收敛、数据建模、页面开发、调试修复和基础测试前后大概两周的业余时间。整个过程让我对“AI 实现全流程”有了非常具体的认知它能帮你省掉大量样板代码和查文档的时间但剩下的决策、纠偏和常识判断恰恰决定了项目能不能真的跑起来。如果你也想用 AI 编程工具从零做自己的 App这篇文章应该能帮你少走弯路。1. 为什么是“生命周期”把模糊想法收敛成可执行项目1.1 从养宠真实需求倒推功能边界一开始我其实想做个“宠物管家”类的应用但“管家”这个词太宽了要管喂食、管洗澡、管社交、管医院、管商城随便一想就是几十个功能。如果把这个需求直接丢给 Claude Code它大概率会给你生成一个看起来五脏俱全、实际上每个模块都很浅的半成品。所以我花了半天时间把问题从“宠物管家”收窄成“宠物生命周期管理”。这个转变的关键在于理解“生命周期”在宠物场景里到底意味着什么。一只猫或狗的一生不同阶段的需求是完全不同的幼年时期要密集打疫苗、驱虫几乎每个月都有事成年之后要关注体重、绝育、牙齿健康进入老年期后体检频率和慢性病风险都会上升。也就是说年龄不是一个静态标签而是一条会影响提醒、体检建议和健康记录的动态主线。围绕这条主线功能边界就清楚了宠物档案、疫苗与驱虫提醒、成长时间线、体重记录这四块是必须做的。1.2 MVP 范围与“不做清单”给 AI 派活最忌讳的是任务描述里只说“要做什么”。我之前踩过几次坑之后学乖了每次开工前先写清楚“不做什么”。这个项目第一版我就明确列了一张不做清单。功能方向第一版做不做原因宠物档案与基础信息做所有数据的主线必须有疫苗/驱虫记录与到期提醒做生命周期管理的核心场景成长时间线与里程碑做能直观体现“生命周期”价值体重记录与简单曲线做数据量小实现简单宠物社区/动态不做社交功能会吃掉大量时间商城/美容洗澡预约不做偏业务运营不适合练手附近医院地图不做涉及 LBS 和第三方地图 SDK推送通知第一版不做见第 4 节AI 最容易在这翻车多用户云同步不做本地优先后续再扩展这张表最终也写进了项目记忆文件里。它的作用不是给 AI 看而是给“我自己”看当 Claude Code 在某次会话里突然自作主张要加社交功能时我能立刻判断出这不是当前版本的范畴防止项目失控。2. Claude Code 开工前技术选型、项目记忆与任务拆分2.1 技术栈为什么选 Expo TypeScript 本地优先Claude Code 的能力边界和它见过的代码量直接相关。选技术栈时我优先考虑三个因素生态成熟度、类型约束能力、以及是否能把 MVP 的后端复杂度降到零。最后定的是React Native 的 Expo 托管工作流 TypeScript expo-sqlite 本地数据库 zustand 管理全局状态。选 Expo 而不是裸 React Native是因为它能省掉原生编译的很多麻烦Claude Code 对 Expo 项目的目录结构和常见配置也非常熟悉生成的代码基本可以直接跑。选 TypeScript 则纯粹是为了“约束 AI”数据结构一旦用 interface 定义好AI 在写页面时就不太容易乱创建字段类型检查也能帮我自动揪出很多低级错误。至于本地优先是我刻意做出的取舍MVP 阶段不碰后端服务就不用折腾部署、鉴权和数据库迁移AirCode 那种无后端方案虽然快但会引入外部依赖不符合我“从零搭一个新项目”的初衷。2.2 CLAUDE.md让 AI 拥有项目记忆Claude Code 有一个很好用的机制项目根目录放一份 CLAUDE.md每次会话开始它会自动读入这份文件相当于给 AI 注入“项目记忆”。很多人在聊天框里反复强调“不要用后端”结果聊到后面还是被 AI 加了后端依赖核心原因就是这些约束没有被固化下来只存在于某一条聊天记录里。我建完项目目录后做的第一件事就是手动写 CLAUDE.md而不是让 AI 用 /init 自动生成。自动生成的版本太泛泛我想要的是能直接约束行为的规则。我写的内容大致是这样的# 项目约定 - 技术栈React Native Expo TypeScript expo-sqlite zustand - 目录结构 - app/ 存放 expo-router 页面 - src/models 存放类型定义 - src/services 存放存储与计算逻辑 - src/utils 存放纯函数工具 - 常用命令 - npm start 启动开发服务 - npx tsc --noEmit 做类型检查 - npm test 跑 vitest 测试 - 强制约束 - 禁止引入后端服务或云数据库当前阶段全部本地存储 - 日期统一用 YYYY-MM-DD 字符串保存本地日期禁止用 Date 对象跨时区计算 - 生命周期阶段必须通过生日物种体型实时推算禁止存储为固定字段 - 修改代码后必须运行类型检查和测试这份文件的威力是后置的。项目进入第 4 天新开一个会话时即使聊天窗口里完全没有之前的上下文Claude Code 也会因为这份文件而保持“不引入后端”“日期用字符串”等基本设定。如果没有它几乎每次新会话都会出现设定漂移。2.3 把项目拆成 5 个可独立验收的里程碑用 AI 做全流程构建最怕的是所有需求一次性塞进一个会话。Claude Code 的上下文窗口虽然大但需求在中途不断变化时它在长会话后半段的输出质量会肉眼可见地下降甚至会忘记最开始确定的数据结构。我的办法是把项目拆成 5 个里程碑每个里程碑单独开一个会话完成并验证后才进入下一个。里程碑内容验收标准M1初始化 Expo 工程配置路由与基础导航模拟器能启动页面可跳转M2定义类型与 SQLite 数据层实现宠物档案 CRUD能新增/编辑/删除宠物并持久化M3实现生命周期阶段推算、疫苗驱虫提醒算法单元测试通过命令行可验证M4实现首页、详情页、时间线页、表单页真机预览走通完整流程M5模拟数据、自动化测试、错误边界与打磨全新用户首次使用体验顺畅每个里程碑开工时我给 AI 的 prompt 不是一大段需求描述而是一句话点明目标附上验收命令。例如 M3 的开场是“请阅读 src/models 下的类型和 src/services 下的存储层然后实现 getLifeStage 和 getNextDueDate 两个纯函数放 src/utils/lifecycle.ts写完运行 npx tsc --noEmit 自检。”这种短 prompt 看起来简陋但配合 CLAUDE.md 里的项目约定效果出奇地稳定。3. 数据模型与生命周期算法AI 生成代码责任人是我3.1 宠物档案建模“生命周期”不存字段靠推算第一次让 Claude Code 设计宠物表时它很本能地把“当前阶段”设计成一个字段比如 status: 幼年。我当时就纠正了这个字段存下来就是一颗定时炸弹因为宠物每天都在长大存进去的阶段过几个月就过期还得定期去更新太蠢了。正确做法是只存不会变的基础信息生日、物种、体型需要看阶段的时候实时计算。这样宠物从 8 个月长到 3 岁同一个档案不用做任何数据迁移显示出来的阶段自然跟着变。最终定下的 Pet 类型大概是这样export type Species dog | cat; export type BodySize small | medium | large; export interface Pet { id: string; name: string; species: Species; bodySize?: BodySize; // 狗需要猫可以不填 breed?: string; birthday: string; // YYYY-MM-DD gender: male | female; neutered: boolean; avatarUri?: string; createdAt: string; }这里有一个我特别想强调的点给 AI 的界面设计约束越清楚它写出来的代码越好。如果你只说“做一个宠物档案表”它可能把体型造在 breed 字段里也可能自己脑补出 medicalHistory 数组。但你给出明确的 TypeScript interface它就知道边界在哪。3.2 阶段推算与提醒算法医疗常识必须人工把关生命周期 App 的核心逻辑是把宠物月龄换算成阶段再根据阶段决定疫苗和驱虫的提醒优先级。我让 Claude Code 生成 getLifeStage 函数时它第一次给的是狗 1 岁等于人 7 岁的旧公式也没有区分大型犬和小型犬的衰老速度。这属于典型的“AI 写得逻辑自洽但领域常识有偏差”。宠物年龄换算的常见经验值大概是这样的猫的 1 岁约等于人类的 15 岁2 岁约等于 24 岁之后每增加 1 年约等于人类 4 年。狗的换算要按体型分小型犬进入老年期大约是 10 岁以后大型犬由于衰老更快7 岁左右就该算老年了幼犬期也不一样大型犬的骨骼发育慢一岁半以前都算青少年期。我最后把这个逻辑写成可配置的常量而不是魔法数字散在代码里const CAT_ADULT_MONTHS 12; const CAT_SENIOR_MONTHS 120; const DOG_ADULT_MONTHS { small: 12, medium: 12, large: 18 }; const DOG_SENIOR_MONTHS { small: 120, medium: 108, large: 84 }; export function getLifeStage( species: Species, bodySize: BodySize | undefined, birthday: string, today: string ): puppy | kitten | adult | senior { // 用月份差计算当前月龄再对照上表 }提醒算法的坑更多。Claude Code 第一次生成的逻辑很简单下次提醒日期 上次日期 固定周期。但现实中疫苗和驱虫的周期会随着年龄阶段改变比如幼犬驱虫往往每月一次成年后可能变为每三个月一次。所以 getNextDueDate 必须接收当前阶段作为参数让调用方先算阶段再算日期。另外我要对读者多说一句以上这些数字只是帮助理解逻辑的常见参考值具体到你自己家的宠物疫苗和驱虫间隔一定要遵兽医医嘱App 里的提醒只能作为辅助。3.3 一次典型的“AI 写错—人工修正—AI 补齐”过程拿疫苗记录表举个例子。Claude Code 第一次设计时把所有疫苗都当成“每 12 个月一次”nextDueDate 直接由上次接种日期加一年生成。这显然不行犬猫的核心疫苗初次接种要做 2 到 3 针每针间隔 3 到 4 周之后才是年度加强狂犬疫苗的加强间隔也有区域差异。如果完全按它这个逻辑做用户打第一针后App 会在一年后才提醒打第二针这个产品就废了。我的修正方式是把疫苗记录拆成两个字段——administeredAt本次接种日期和 nextDueDate计算出来的下次日期并用一个 vaccineRules 表维护不同疫苗的初次间隔和加强周期。改完 schema 后我把错误现场和修正要求拼成一个 prompt 丢回给 AI“请阅读 src/models/vaccine.ts 和 src/services/reminder.ts。当前 nextDueDate 一律加 12 个月未区分初次免疫和年度加强。请按疫苗类型配置规则表并让提醒页读取该表。修改后运行类型检查和测试。”Claude Code 读文件、改代码、跑命令这套链路确实顺两轮之后就补齐了。但我必须盯着它收尾才敢放心因为这种业务规则不是它能凭空推理出来的。4. 页面落地的 AI 协作节奏一个页面一个会话4.1 首页与宠物档案页复用类型别让 AI 另起炉灶数据层稳定之后进入页面开发阶段。我的经验是一个页面一个会话不要让 AI 同时写三个页面再等你验收。页面之间一旦共享组件或状态AI 在自己的“脑补版图”里很容易出现不一致比如首页用 Pet.name详情页用 Pet.petName查半天才发现是同一个字段。首页我要求它实现的内容很明确宠物卡片列表每张卡片显示头像、名字、物种、当前生命阶段标签以及距离下一次疫苗或驱虫提醒的剩余天数。这里有个值得注意的细节Claude Code 很喜欢在写页面时顺手帮你“重构”数据层比如觉得日期字段不好用就想改成时间戳。我的 prompt 里每次都会带一句“只使用 src/models/pet.ts 里已有的类型禁止修改数据层接口”。这句话看起来多余实际能拦住一大堆随机改动。宠物档案页的另一个功能点是编辑和删除。这块 AI 实现得很快因为 CRUD 页面是它训练数据里最常见的模式。真正需要我手动过一遍的是删除的交互确认AI 默认会用一个 Alert 弹窗我觉得在生命周期这类记录型 App 里删除确认文案要温和一点比如“删除后疫苗记录也会一起清空且无法恢复”这类产品细节 AI 是不会自己想到的。4.2 生命周期时间线合并数据源要看着 AI 做时间线是本项目最“生命周期”味儿的页面。它要把疫苗记录、驱虫记录、里程碑和体重变化全部按日期倒序混排再按月份分组。这个逻辑不复杂但涉及多个数据源合并AI 在合并排序时容易出问题特别容易忘记把类型为 record 的条目和类型为 milestone 的条目按统一格式输出。我让 Claude Code 先定义时间线条目的联合类型type TimelineItem | { type: vaccine; id: string; title: string; date: string } | { type: deworming; id: string; title: string; date: string } | { type: milestone; id: string; title: string; date: string };所有数据源先映射成这个结构再统一按 date 排序。这种“先统一结构再排序”的思路是防止时间线混乱的关键。实现完之后我又让它处理了一个边角情况幼年阶段每个月都有驱虫记录时间线月度分组里会很长没关系但空状态一定要有——新用户没录任何数据时时间线页要显示“去记录第一个成长里程碑”的引导按钮。4.3 表单与提醒设置AI 最容易“过度设计”的地方新增宠物和编辑提醒设置这两块是 Claude Code 发挥“热情”的重灾区。我第一次让它做提醒设置页时它认为“提醒”就要发推送通知自动引入了 expo-notifications还设计了权限申请流程。这直接导致项目多出一个原生依赖需要额外配置权限描述白折腾半天。后来我给自己定了一条规矩第一版所有“提醒”一律是页面内的倒计时和列表角标。用户在详情页打开 App 时只要能看到“距离妙三多加强还有 23 天”这种提示就够了。不做推送不申请系统通知权限不接后台任务。这个决定不仅省了工时还避免了审核和权限弹窗的麻烦。如果你也想用 AI 快速做原型我的建议是把“提醒”两个字在需求文档里明确成“站内倒计时显示”不给 AI 留下发散空间。表单页的实现倒是顺利很多只要给 AI 列出字段清单和校验规则它生成的代码基本一次过。5. 埋坑最多的调试阶段上下文丢失与 AI 过度热情5.1 上下文丢失当 AI 忘了“不要后端”项目进行到第 4 天我新开一个会话想让 AI 补一个数据导出功能。结果它读完代码后直接发来一段消息“建议引入后端服务用于数据备份我们可以用云端数据库这样导出就不会丢失。”我愣了一下因为 CLAUDE.md 白纸黑字写着“禁止后端服务”。后来我复盘发现它在那个会话里根本没有主动读取 CLAUDE.md 的约束部分或者它觉得“导出”这个需求暗示了云备份想给我一个“更好的方案”。这件事给我的教训是不能默认 AI 每次都会严格遵守记忆文件。它读完文件之后依然可能会根据当前任务的上下文“自由发挥”。我的解决方案是在关键 prompt 里显式要求它先复述约束“开始改代码前请先读 CLAUDE.md并用两句话说明项目约定和本次方案。”让 AI 先把约束说出来其实是在逼它对齐上下文相当于一个低成本的任务对齐仪式。这个习惯之后救了我很多次。5.2 expo-sqlite 的版本坑AI 会自作主张升级依赖expo-sqlite 这些年改过 API老版本用 openDatabase新版本推荐 openDatabaseAsync。Claude Code 在一次报错后的自动修复中没有检查工程里包的版本号直接用了新 API 并尝试升级依赖。结果新版本需要更高级的 Expo SDK 版本又把别的依赖牵扯进来问题连锁放大。处理办法很土但有效我把“禁止升级依赖版本遇到 API 报错先检查 package.json 中实际版本再写对应 API”写进了 CLAUDE.md并在 prompt 里加了一句话“如果确定是依赖版本问题先停下来说明不要自行升级。”Claude Code 的“自主修错”能力是把双刃剑它能自己跑测试修报错但也会把“修复”扩大成“升级”所以给它的行动加上缰绳是必要的。5.3 日期存储的时区坑与自检习惯宠物疫苗提醒是对日期极其敏感的功能日期算错一天都不行。Claude Code 默认把所有日期都存成 ISO 字符串带时区在计算“距离下次还有几天”时它理所当然地把字符串转成了 Date 对象。模拟器上看起来没问题可我换了台机器测试时区不同提醒就莫名其妙提前了一天。根因是 Date 对象会按本机时区解析YYYY-MM-DD 会被当成 UTC 午夜在东八区显示就成了当天早上 8 点再配合日历计算就会产生偏移。这个坑让我意识到在生命周期这种强日期场景日期必须存成本地日期字符串所有计算都按字符串做天数差不经过 Date 的时区自动解析。从那以后我给每个会话都加了“改完必须跑三件事tsc 类型检查、vitest 测试、npm start 启动验证。”自检命令固定之后AI 每次交付的代码质量确实稳定了一大截。6. 从“能跑”到“像个产品”测试、打磨与后续演进6.1 用 AI 生成模拟数据和手动测试清单功能都跑通后我让 Claude Code 写了一个 seed 脚本往本地库里插入三只模拟宠物一只两个月大的幼猫、一只五岁的成年中型犬、一只十岁的老年小型犬。这三只宠物分别对应不同生命周期阶段能一次性覆盖大部分页面状态。比如幼猫应该显示“两周后驱虫”成年犬应该显示“体重稳定”老年犬的时间线里应该出现多次体检记录。这种数据驱动的人工走查比空库里点来点去高效得多。脚本是让 AI 写的但我要求所有模拟数据只在开发环境跑并且标记清楚是模拟数据避免后续真机测试时混淆。同时我还让它生成了一份手动测试清单大概是新增宠物后重启 App 数据是否还在、修改生日后阶段标签和提醒天数有没有实时变化、时间线里不同来源的记录排序是否准确。这些项目不在自动化测试范围里但每次改动之后人工过一遍能省下不少 debug 时间。6.2 核心算法的自动化测试让 AI 给自己上保险生命周期推算和提醒日期计算是本项目里最适合自动化测试的纯函数。我让 Claude Code 用 vitest 写了两个测试文件覆盖不同物种、体型和年龄的组合比如describe(getLifeStage, () { it(8个月的小型犬应为幼犬阶段, () { expect(getLifeStage(dog, small, 2024-01-01, 2024-09-01)).toBe(puppy); }); it(9岁的大型犬应为老年阶段, () { expect(getLifeStage(dog, large, 2015-06-01, 2024-06-01)).toBe(senior); }); });这些测试跑通之后我后续每次用 Claude Code 改逻辑都会在会话结束时让它跑一遍 npm test。只要测试过就说明基本的生命周期推算没被改坏。这可能是我这次项目里最满意的一个工作流AI 写代码AI 写测试然后我用测试去约束下一次的 AI。循环几次之后项目质量是往上走的而不是靠运气。6.3 最后一公里空状态、错误边界与 App 图标很多时候“能不能用”和“像不像产品”的差距就在这些边角细节上。Claude Code 很擅长补这些不起眼的部分前提是我愿意花时间给它提需求。我做了一个全局错误边界组件当某个页面抛出未捕获异常时显示一个友好的错误页而不是白屏给列表页做了空状态没有宠物时显示引导图。App 的图标和名称我用配置改了用的是项目根目录 app.json 里的字段这部分 AI 几乎不用动脑照着文档改就行。老实说这些界面打磨工作加起来不超过半天但做完之后整个项目的完成度立刻不一样了。6.4 后续演进本地优先如何向云端同步扩展当前版本是纯本地存储好处是简单坏处是换手机数据就没了。后续如果要扩展一个务实的路径是先把本地数据导出成 JSON 文件支持导入恢复这算最轻量的备份方案更进一步可以上云端数据库把宠物表、疫苗表、提醒表的 schema 直接迁移过去因为最初的数据模型设计没有绑定本地实现方式接口层面的改动不会伤筋动骨。到那时Claude Code 也能用来生成数据库迁移脚本和同步冲突处理逻辑。不过那已经是下一个阶段的事了至少现在这个版本已经能完整回答“用 AI 从零搭一个 App”这个命题了。最后说点个人体会。Claude Code 在“从零搭建新项目”这件事上真正改变的不是写代码的速度而是思考项目的方式。我养成了先写 CLAUDE.md 再写需求、先定验收标准再动手的习惯产出的代码质量明显比直接开聊高。另外分享一个小技巧每次改完让 AI 用固定命令自检把测试当成交付门槛。做这个宠物生命周期管理 App我最大的收获不是 App 本身而是这条可以复用到任何新项目的 AI 协作流水线。下一步我打算把提醒模块完善一下再做数据导出有结论了再来分享。
阅读完成 · 觉得有帮助?