主题
目录结构与final项目安装依赖并运行(Day12 2/3)
本页对应 React + TypeScript + 医通项目,共 7 段视频,约 1 小时 11 分。
学习目标
- 能够使用 Ant Design Form/Form.Item 搭建表单,并正确配置 name、rules、initialValues、labelCol、wrapperCol、offset 与 onFinish,完成必填、正则校验及提交数据收集。
- 能够使用 Form.useForm 创建 form 对象并绑定到表单,通过 getFieldsValue、resetFields、setFieldsValue 完成表单数据获取、重置和批量赋值。
- 能够使用 Space、Card、message、Modal.confirm 组件/方法实现按钮间距、内容面板、成功/失败提示及删除确认交互。
- 能够说明 react-project 中 docs、final、base 三个目录的作用,并区分 final 完整项目与 base 二次开发项目。
- 能够分别进入 final 和 base 项目目录完成依赖安装与启动,并针对依赖安装失败、声明文件缺失、token 异常、端口占用等问题执行对应解决方案。
- 能够复述从需求、原型/标注图、接口文档到静态布局、API 封装与数据渲染的项目开发流程,并说明需求变更时应走文档化变更流程。
知识点
整体目录:先认清项目根目录
本节笔记给出的目录结构如下,后续所有操作都要先在这个根目录里定位:
shell
06_React_project
|- syt-admin_docs 项目开发文档
|- syt-admin-final 完成的项目
|- syt-admin-react-base 二次开发的项目为什么需要:拿到一个项目资料时,最先要弄清楚的往往不是某个组件怎么写,而是“我现在在哪个目录、每个目录是干什么的”。目录认错了,后面安装依赖、启动项目、对照页面都会乱。
怎么想:把 06_React_project 看成总仓库,里面不是三个互不相干的项目,而是三种角色:文档、最终效果、二次开发基础。学习时用 final 对照效果,用 react-base 写代码,用 docs 查项目说明。
怎么做:克隆或解压后,先打开根目录确认三个子目录都存在。转写稿中提到,克隆下来的目录可以重命名为 06_React_project 这类更清晰的名称;如果发现仓库里又嵌套了 .git,因为会出现仓库嵌套,需要把多余的 .git 删除后再作为普通项目目录使用。docs 里有 intro,用于介绍项目;项目用 yarn 或 npm 安装都可以,按实际环境来。
易错点:不要把 final 当成自己要改的项目。final 是完成品,主要用于运行起来对照页面效果;真正写功能的是 react-base。另外,目录名不要随意改动后又在终端里按旧路径执行命令,否则会提示找不到目录。
syt-admin-docs:文档目录不是摆设
为什么需要:项目说明、开发文档往往决定了你如何理解项目背景和启动方式。转写稿明确提到 docs 里放的是项目开发文档,里面有 intro 介绍项目。
怎么想:先看文档再动手,不要一上来就在三个目录里乱装依赖。文档能告诉你项目用什么包管理器、项目定位是什么,以及后续开发要参照什么。
怎么做:进入 syt-admin-docs,优先看 intro 一类介绍文件。若文档说明 yarn、npm 都可,就选自己能稳定安装的方式;不要因为笔记里出现某个命令,就忽略文档里的环境说明。
易错点:文档目录通常不需要 npm i 启动。把文档目录当项目目录安装依赖,既浪费时间,也容易误判“项目启动失败”。还要注意同音字纠正:转写里的“dos”应理解为 docs,“样”多指 yarn,“安GD/氨基杠第”是 Ant Design 相关口语,不要照抄成错误目录名。
syt-admin-final:完成项目用来对照效果
为什么需要:本项目没有像常规项目那样一开始就给完整原型图和标注图,转写稿说得很清楚:学习时主要根据 final 运行出来的效果来写自己的页面。也就是说,final 是效果参照物。
怎么想:先把 final 跑起来,观察登录页、首页布局、菜单和功能表现;再去 react-base 里实现。不是照抄整个 final,而是把它当“可运行的设计稿”。
怎么做:进入 syt-admin-final 目录安装依赖并启动。转写稿中的操作是 npm i -f,其中 -f 表示强制安装,用来绕过一些版本兼容提示;然后执行 npm start。final 一般先占用 3000 端口。登录页里用户名、密码等已配置好,登录后要观察控制台有没有错误。
易错点:安装时出现若干警告、甚至“高风险”提示,不等于失败,关键看有没有 error。若启动后出现声明文件相关报错,可删除 node_modules 后切换镜像重装;转写稿提到腾讯源、淘宝新源和 npm 原始源。若页面提示“服务异常”,可能是本地存储里的 token 异常,删除本地 token 后刷新并重新登录。不要把这些启动问题的处理误当成目录结构问题。
syt-admin-react-base:二次开发目录才是主战场
为什么需要:转写稿反复强调,这个项目是“二次开发”,不是从零一行行搭。真实公司里也常常是在已有代码上继续写功能,所以要先接受“项目里已有大量你看不全懂的代码”这一前提。
怎么想:react-base 是基础架子,转写稿说它启动后只有首页架子,功能并不完整;final 功能齐全。学习路径就是:看 final 的效果,在 react-base 的既有结构上补功能。
怎么做:进入 syt-admin-react-base,同样执行 npm i -f 安装依赖,再执行 npm start。如果 final 已占用 3000 端口,base 启动时会提示端口占用,选择切换到 3001 即可。启动后先确认能进入首页,再开始阅读已有目录和代码。
易错点:不要在 react-base 里期待“所有功能都已经写好”;也不要因为一时看不懂已有代码就推翻重来。二次开发的关键是先定位已有入口、组件和请求封装,再在合适位置增量修改。另一个易错点是端口:两个项目同时启动时,3000 被占用是正常现象,按提示换到 3001,不要反复强杀进程。
先理解本节要运行的两个项目
这一节的目标不是写业务代码,而是把后面要用的两个 React 项目跑起来。转写稿里把目录关系讲得很清楚:react-project 下通常会有文档目录、syt-admin-final 完成项目、syt-admin-react-base 二次开发基础项目。final 是已经做好的效果,后面我们一边对照它,一边在 base 上继续开发。为什么要先跑 final?因为它是“参考答案”:页面长什么样、交互是什么效果,都可以先看它;为什么还要跑 base?因为真正写代码是在基础项目上做二次开发,模拟进入公司后接手已有项目的场景。
怎么想:先把“能运行”作为第一目标,不急着读全部源码。怎么做:分别进入两个项目根目录,安装依赖并启动。易错点:两个项目默认都可能使用 3000 端口,后启动的会提示端口占用。
为什么要处理 npm 镜像源
依赖安装是否顺利,首先取决于网络和 npm 源。转写稿里说得很实在:同学习惯设置淘宝镜像、腾讯镜像或其它镜像,但不是“必须改”,而是“取决于你能不能顺利把依赖装上”。如果当前源能装全,就可以继续用;如果安装过程中缺包、报错,再考虑切换源。
怎么想:镜像源影响的是“下载依赖包”的通道,不是项目代码本身。怎么做:在终端执行配置命令。笔记里给了三个源:
shell
# 腾讯
npm config set registry http://mirrors.cloud.tencent.com/npm
# 淘宝
npm config set registry https://registry.npmmirror.com
# npm 原镜像
npm config set registry https://registry.npmjs.org易错点:转写稿提到,有些源里可能缺少声明文件(.d.ts),导致依赖看起来装完了,但运行时报声明文件相关错误。此时不是盲目怀疑代码,而是先换源、删依赖、重装。也不要因为出现若干“高风险”提示就慌,只要没有 error,通常可以先继续。
启动 final 项目
final 项目用于对照最终效果。为什么要先启动它?因为后面在 base 中开发时,需要随时查看“正确效果”。怎么想:进入项目根目录后,先安装依赖,再启动。怎么做:打开终端,进入 syt-admin-final,执行笔记中的命令:
shell
cd syt-admin-final
npm i -f # -f force 强制安装
npm start 启动项目这里的 -f 是 force,强制安装。转写稿解释:项目里有些依赖版本随着时间推移可能不兼容,普通安装可能提示版本错误,所以用 -f 强行安装。安装完成后,观察是否出现 node_modules 目录,以及终端有没有 error。确认没有明显错误后,再执行 npm start。启动成功后浏览器会打开页面,登录页里的账号密码已经预填好,直接点登录即可。进入后看一眼控制台有没有报错。
易错点:npm start 不是安装命令,必须在依赖装好之后执行;终端路径必须在项目根目录,不能在外层目录乱执行。
启动 base 项目
base 是后面真正要开发的二次开发项目。为什么要单独启动?因为它和 final 是两个项目,依赖和启动命令类似,但目录不同。怎么做:进入 syt-admin-react-base,同样安装并启动:
shell
cd syt-admin-react-base
npm i -f
npm start转写稿里有一个关键调试过程:final 已经占用 3000 端口,再启动 base 时终端会提示端口占用,这时选择使用 3001 端口即可。启动成功后会发现 base 目前只有架子,功能没有 final 完整,这正是后面要补业务的原因。
易错点:不要以为 base 启动后页面“空”就是失败;它本来就是一个基础项目。端口占用也不是代码错误,按提示换端口即可。
常见错误一:网络原因导致包没下全
这是最常见的问题。为什么会出现?转写稿说,大家一起下载依赖时网络压力大,或者当前镜像源缺少某些文件,可能导致包没下全。怎么想:先判断是“网络/依赖问题”还是“代码问题”。如果刚装完就报缺包、声明文件找不到,优先怀疑依赖。怎么做:按笔记方案处理:
- 删除
node_modules,切换镜像源,重新执行npm i -f安装。 - 如果只是学习过程中弹错,也可以先把错误叉掉,先对付用,课下网络好时再重装。
易错点:不要在不删 node_modules 的情况下反复盲目安装;残留的半成品依赖会干扰判断。换源后重新安装更稳。
常见错误二:声明文件缺失或全屏报错
转写稿提到,有的同学能进页面,但一刷新就出现全屏错误,提示和 .d.ts 有关。这通常说明安装时声明文件没装上。怎么想:错误既然指向声明文件,就不是业务逻辑写错。怎么做:有两种处理方式:一是删除 node_modules 后换源重装;二是根据报错找到缺的包,用 npm i @types/包名 的方式补装。若一时装不好、又不影响继续学习,可以先叉掉错误,课下再处理。易错点:不要编造包名,必须看终端/浏览器实际报错里的包名。
常见错误三:token 异常导致“服务异常”
转写稿还演示了一个现象:刷新后页面提示“服务异常”,小图标一直转。原因是项目带 token 校验,本地存储里如果有 token,但这个 token 是错误值,就会被判定异常。怎么想:先排除 token,再怀疑接口。怎么做:删除本地存储里的 token,刷新页面,回到登录页重新登录,系统会生成新的合法 token。易错点:不要直接改业务代码;先清 token 验证。
启动后的检查顺序
最后按一个固定顺序检查:终端有没有 error;node_modules 是否生成;npm start 是否成功;浏览器能否登录;控制台有没有红错;base 是否因端口占用跑在 3001。这样后面写功能时,遇到问题就能快速判断是依赖、端口、token 还是代码本身。
先保留原始接口文档地址
本节笔记只给了三个 Swagger 文档地址,没有附图片;下面先把原始地址代码块完整保留:
text
医院接口地址:http://139.198.34.216:8201/swagger-ui.html#/
数据字典接口地址:http://139.198.34.216:8202/swagger-ui.html#/259682545423383208563164929702
登录接口地址:http://139.198.34.216:8212/swagger-ui.html为什么需要:项目不是只连一个后端服务。医院业务、数据字典、登录认证分别在不同端口上,前端联调时必须先分清“我要查哪个文档、请求哪个服务”,否则很容易把登录请求发到字典端口,或者把字典请求发到医院端口。
怎么想:先按业务域记地址,而不是只记一串 URL。医院接口对应医院业务;数据字典接口对应字典数据;登录接口对应认证。三个地址端口分别是 8201、8202、8212,这是最直观的区分点。
怎么做:把三个地址分别打开、收藏,并在笔记里标明用途。打开后先看 Swagger 页面左侧或分组里的接口列表,确认当前文档属于哪个服务。数据字典地址后面的 #/259682545423383208563164929702 是文档内的定位锚点,用来打开后定位到某个分组或接口,不是业务请求参数。
易错点:不要把 swagger-ui.html#/ 这个“文档页面地址”直接当成业务请求地址;真正请求路径要以 Swagger 中每个接口展示的 URL 为准。也不要看到三个地址都是 139.198.34.216 就忽略端口差异。转写稿里提到的“样”应理解为 yarn,“NPMI 杠 F”应理解为 npm i -f,这些是项目启动术语,不要和接口地址混在一起。
用 Swagger 读接口,再封装 API
为什么需要:转写稿在讲项目开发流程时明确说,后端会给前端提供接口文档,前端拿到文档后要知道“怎么封装 API 函数、请求完回来什么数据、怎么渲染”。所以接口地址不是拿来背的,而是拿来读接口约定的。
怎么想:先读文档,再写代码。读的时候按“请求方法、请求路径、请求参数、响应结构、是否需要 token”五件事看。先不要急着在页面里写死请求,而是先确认这个接口属于医院、字典还是登录服务。
怎么做:打开对应 Swagger 地址,找到目标接口,记录请求方法、路径、参数名和返回示例;如果页面支持调试,可以先在文档里试一次;然后在项目的 API 层封装函数,页面只调用封装后的函数。转写稿里 final 项目启动后登录页已经预填账号,密码是 6 个 1,登录后要先看控制台有没有错误,这也是联调前的检查动作。
易错点:不要凭接口名字猜参数;不要把一个服务的路径拼到另一个服务的端口上;不要忽略 token。转写稿里提到 final 项目带 token 校验,如果本地 token 是错误值,刷新后可能出现“服务异常”,这时应删除本地存储的 token,再刷新回到登录页重新登录,而不是继续在页面上乱改请求。
登录接口与 token 调试
为什么需要:登录接口单独放在 8212,说明认证服务和业务接口是分开的。前端登录成功后通常要保存 token,后续请求再携带它。转写稿中 final 项目出现“服务异常”时,处理办法就是删掉本地 token、刷新、重新登录,这说明 token 状态会直接影响接口调试。
怎么想:先确认登录文档地址能打开,再按文档登录;登录成功后检查本地存储里是否有 token;访问业务接口前确认请求是否带 token;出现服务异常先排查 token,而不是先怀疑页面组件。
怎么做:打开登录接口文档,按文档说明发起登录;登录成功后进入 final 项目首页,观察控制台;如果刷新后出现服务异常,删除本地存储中的 token,刷新页面回到登录页,重新登录生成新 token。
易错点:token 错误或过期时不要反复刷新硬扛;删除 token 后要重新登录,不要以为删完就自动恢复业务数据。另外,转写稿里提到的“全屏错误、点 D 点 TS”应理解为声明文件缺失导致的报错,这类问题不一定是接口地址错误,要区分“接口问题”和“依赖安装/声明文件问题”。
联调顺序:先启动项目,再对照文档
为什么需要:接口地址只有放进运行中的项目里才有意义。转写稿中 final 项目启动在 3000 端口,base 项目因端口占用启动在 3001 端口;final 是完整效果,base 是二次开发基础架子。先跑 final 看完整效果,再跑 base 做二次开发,顺序更稳。
怎么想:先保证依赖安装和项目启动成功,再打开接口文档对照页面功能。转写稿里安装依赖用 npm i -f,-f 是强制安装;如果网络或源导致安装失败,可以删除 node_modules、切换源后重装。启动用 npm start。
怎么做:进入项目根目录执行安装和启动;final 启动后登录并检查控制台;base 启动后确认只有基础架子;然后按页面功能回到对应 Swagger 文档找接口,再封装请求。
易错点:端口被占用时 base 会提示并改用 3001,不要以为启动失败;安装时出现若干高风险提示但没有 error,一般不影响启动;若出现声明文件缺失的全屏报错,可先按转写稿思路删 node_modules 重装,或临时关掉错误提示保证学习进度,课下再换网络环境重装。接口调试时始终记住:医院 8201、字典 8202、登录 8212,先对文档,再写请求。
先看目录树:项目边界在哪里
本节笔记没有配图,核心是一张目录树。先把它完整保留下来:
text
├── public # 公共静态资源目录
│ ├── favicon.ico # 网站图标
│ ├── index.html # 主页面
├── src # 主目录
│ ├── api # 接口文件
│ ├── app # redux配置文件
│ ├── components # 公共组件
│ │ ├── Loading # loading组件
│ │ ├── Translation # 国际化组件
│ │ └── withAuthorization # 登陆权限校验组件
│ ├── layouts # 主要布局组件
│ ├── locales # i18n国际化配置
│ ├── pages # 路由组件
│ ├── routes # 路由配置
│ ├── styles # 全局/公共样式
│ ├── utils # 工具函数
│ │ └── http # 封装请求函数
│ ├── App.tsx # App组件
│ ├── index.ts # 主入口
│ ├── react-app-env.d.ts # 类型文件,在编译时会引入额外文件
├── .env.development # 开发环境加载的环境变量配置
├── .env.production # 生产环境加载的环境变量配置
├── .gitignore # git忽略文件
├── craco.config.js # react脚手架配置文件
├── package.json # 包文件
├── README.md # 项目说明文件
├── tsconfig.extend.json # 路径别名配置文件
├── tsconfig.json # ts配置文件
└── yarn.lock # yarn下载包的缓存文件为什么需要先看目录?因为真实项目不是从零一行行写,而是先进入一个已有工程。转写稿里克隆下来的 react-project 下面有 docs、admin-final、admin-react-base 等目录:docs 放开发文档,admin-final 是完成后的参照项目,admin-react-base 是二次开发的基础项目。怎么想?先分清“看效果的项目”和“要写代码的项目”。怎么做?先删掉克隆后可能出现的嵌套 .git,再分别打开 admin-final 和 admin-react-base。易错点:不要在 react-project 根目录直接乱启动,转写稿里是进入具体项目目录再安装、启动。
public 与 src:入口和源码分层
为什么需要分层?public 放不经过模块打包的公共静态资源,src 放真正参与编译的源码。怎么想?看到 index.html、favicon.ico 就知道是页面壳和图标;看到 src/index.ts、App.tsx 就知道主入口和根组件。怎么做?新增页面优先放 pages,路由配置放 routes,接口函数放 api,公共组件放 components,请求封装放 utils/http,布局放 layouts,国际化和 Redux 分别看 locales、app。易错点:不要把页面组件塞进 components,也不要把接口请求散落在页面里;react-app-env.d.ts 是类型声明文件,不是业务代码。转写稿里提到安装依赖后出现 .d.ts 相关报错,说明声明文件包没装上:可以删 node_modules 重装、切换源,或按报错用 npm i @types/包名 补装;若不影响学习进度,也可先关闭错误提示,课下再处理。
根目录配置:环境变量、别名、脚手架
为什么根目录有这么多文件?它们负责工程行为,不是页面功能。怎么想?.env.development、.env.production 管开发/生产环境变量;craco.config.js 管 React 脚手架扩展;tsconfig.json、tsconfig.extend.json 管 TypeScript 和路径别名;package.json 管依赖和脚本;yarn.lock 锁定依赖版本。怎么做?改环境变量先看对应 .env 文件;配别名要同时关注 tsconfig.extend.json 和 craco.config.js;安装依赖前先确认自己在哪个项目目录。易错点:转写稿说项目用 yarn 构建,但 npm 也可以,常用 npm i -f 强制安装;安装后出现若干高风险提示,只要没有 error 可先继续。不要随意删锁文件,也不要把 node_modules 提交到仓库。
启动与排错:从目录到运行
为什么要讲启动?因为目录介绍最终要落到“我能把项目跑起来”。怎么做?进入 admin-final,执行 npm i -f,再 npm start;参照项目跑起来后,再进入 admin-react-base 做同样操作。转写稿里 final 已占用 3000 端口,base 启动时会自动换到 3001,这是正常现象。易错点有两个:一是网络原因导致依赖没装全,处理方式是删 node_modules、切换源后重装;二是登录后刷新出现“服务异常”,原因是本地 token 校验失败,删除本地存储的 token 后刷新,重新登录生成新 token。还有 .d.ts 报错时,按上面说的补声明文件或重装依赖,不要一上来就怀疑业务代码。
用目录支撑开发流程
为什么还要看 docs?因为开发不是背目录,而是按流程找资料。转写稿说这个项目没有原型图,就对照 admin-final 的效果来写;正常公司会给 UI 标注图做静态布局,后端给接口文档后在前端封装 API。怎么想?docs 看说明,final 看目标效果,base 写代码;页面进 pages,路由进 routes,接口进 api。怎么做?先跑 final 对照页面,再在 base 里按目录职责补功能。易错点:需求变更不要只口头答应,转写稿强调要走需求变更文档、发邮件或保留聊天记录,避免最后责任落到自己身上。
先明确“实现功能的流程”要解决什么
这一节不是先讲某个组件怎么用,而是讲拿到一个功能后,按什么顺序把它落地。笔记给出的顺序是:创建路由组件 → 配置路由 → 创建组件静态结构 → 查看接口并封装 interface 和 api 函数 → 调用 api 函数(事件回调或钩子函数)→ 声明状态。
为什么需要:如果一上来就写请求和业务逻辑,页面结构、路由、接口字段、状态会搅在一起,出错后很难定位。怎么想:先把“页面能进来”解决,再把“长什么样”解决,再把“数据从哪来、什么时候来、来了放哪”解决。怎么做:严格按六步推进,每一步只做一类事。易错点:跳过静态结构直接接接口;接口字段还没看清就声明状态;在渲染过程中直接发请求。
创建路由组件并配置路由
为什么需要:功能最终要作为一个页面或模块被访问,路由组件是入口。怎么想:先明确这个功能对应哪个页面,再建组件文件,最后挂到路由配置里。怎么做:按项目已有目录规范创建路由组件,确认组件正常导出;到路由配置中登记路径和组件。转写稿里项目启动后可以看到 final 和 base 两套项目,base 只有首页架子,后续就是在这个架子上按功能补页面。易错点:组件名、路径、导出名不一致;路由配了但组件没导出;多个项目端口冲突时误以为路由坏了——转写稿中 final 已占 3000,base 启动到 3001,这是正常现象。
创建组件的静态结构
为什么需要:先有稳定的页面骨架,后面接数据才不会边改样式边改逻辑。怎么想:转写稿提到正常项目会拿 UI 原型图和标注图做静态布局;这个项目没有原型图时,就对照已经做好的 final 项目效果来写。怎么做:先写结构、占位内容和基础样式,不急着发请求;需要按钮间距可用 Space,需要白色面板可用 Card,提示可用 message,确认框可用 Modal.confirm。易错点:把接口调用塞进静态结构;UI 效果实现代价过大时没有提前沟通。转写稿特别提醒:觉得效果难实现,要在干活前和 UI 协商,别等做完再说。
查看接口并封装 interface 与 api 函数
为什么需要:接口文档决定请求参数、返回字段和渲染方式。不封装类型和请求函数,容易出现字段拼错、重复请求、到处写 any。怎么想:先读接口文档,明确“请求要传什么、回来什么数据”,再定义 interface,最后写 api 函数。怎么做:interface 按接口文档描述请求参数和响应结构;api 函数只负责发请求,不处理页面提示和状态更新。转写稿明确说:后端提供接口文档,我们据此知道怎么封装 API 函数、请求完回来什么数据、怎么渲染。易错点:字段名照抄错;把业务逻辑写进 api;接口没确认就声明状态,后面类型全要返工。
调用 api 函数:事件回调和钩子函数
为什么需要:接口函数封装好后,还要选择合适时机调用。怎么想:用户点击、提交、删除这类由用户触发的,用事件回调;页面进入就需要数据的,用钩子函数。怎么做:事件回调中调用 api,拿到结果后更新状态;钩子函数中调用时注意只执行一次或依赖项写清楚。转写稿中表单的 onFinish 就是典型事件回调:点击提交按钮后触发,并把表单数据收集到 values 中。易错点:在渲染过程中直接调用接口;钩子依赖写错导致重复请求;请求失败没有提示。调试时先打印返回值,确认字段和接口文档一致,再渲染。
声明状态并渲染,必要时用 useForm 管理表单数据
为什么需要:接口数据必须放到状态里,状态变化才能驱动视图更新。怎么想:先按接口返回结构声明初始状态,请求成功后再 set;表单这类成组数据可以用 useForm 统一管理。怎么做:转写稿中表单数据处理的套路是:先创建 form 对象,再绑定到表单组件,最后通过 form 的 API 操作数据。按转写稿可整理为:
tsx
const [form] = Form.useForm()
<Form form={form} onFinish={onFinish}>
{/* 表单项 */}
</Form>
// 获取表单数据
form.getFieldsValue()
// 恢复初始值/重置
form.resetFields()
// 批量设置表单值
form.setFieldsValue({
username: '张三',
password: '123123',
})易错点:初始值类型和接口不一致;直接修改状态对象而不是走 set;表单没有绑定 form 就调用 form 方法。转写稿还演示了:resetFields() 会恢复到初始值,如果初始值为空,就表现为清空;setFieldsValue() 可以按字段批量设值。
联调与启动排错
为什么需要:功能写完必须跑起来验证,很多问题出在依赖、端口和登录态。怎么想:先保证项目能启动,再验证页面和接口。怎么做:转写稿中安装依赖用 npm i -f 强制安装,启动用 npm start;出现声明文件 .d.ts 相关报错,可删 node_modules 重装、切换镜像源或补装类型包;出现“服务异常”时,删除本地存储的 token,刷新后重新登录。易错点:看到若干“高风险”提示就慌,转写稿说只要没有 error 一般可继续;端口占用不是代码错;改需求要走变更流程并留文档,不要只口头答应。
先明确本节要解决的问题
本节标题是“医院设置功能实现”,但在真正写医院设置页面之前,先把表单这条主线打通。医院设置里会有名称、电话、简介等字段,还会有“保存”“重置”这类操作,所以必须先掌握 Form 的结构、校验、取值和重置。转写稿里也是本着“先讲复杂的”原则,从 Form 开始,再补 Space、Card、message、Modal.confirm 等常用组件,最后把 final 和 base 两个项目跑起来。
表单的基本结构与常用属性
为什么需要:任何后台项目都离不开表单,医院设置本质上就是一组字段的录入与回显。
怎么想:先看组件结构。最外层是 Form,里面每一行是一个 Form.Item。Form.Item 上的 label 控制这一行左侧文字,name 是这一行字段的名字,真正输入内容的 Input 放在 Form.Item 内部。易错点:name 写在 Form.Item 上,不是写在 Input 上;写错位置就取不到值。
怎么做:从组件库文档复制示例到 FormTest.tsx,把用到的 Form、Input、Checkbox、Button 逐个引入,再补上示例里引用但未定义的回调函数,报错就消失了。改 label 的值,页面文字会跟着变,说明 label 就是这一行的显示文字。
表单校验规则
为什么需要:手机号、医院名称不能为空,格式也要限制。
怎么想:rules 是一个数组,意味着一个字段可以写多条规则。required: true 表示必填,message 是违反规则后的提示。手机号再加一条正则规则:以 1 开头,第二位是 3 到 9,后面再跟 9 个数字,一共 11 位。
怎么做:
tsx
rules={[
{ required: true, message: '手机号必填' },
{ pattern: /^1[3-9]\d{9}$/, message: '手机号格式不正确' },
]}易错点:正则要写完整,^ 和 $ 不能漏,否则多输一位也能通过;message 要写清楚,不然用户不知道错在哪。
初始值与栅格布局
为什么需要:编辑医院信息时要回显,remember me 默认勾选也属于初始值。
怎么想:Form 的 initialValues 决定表单初次渲染的值。把 remember 改成 false,刷新后就不再勾选,说明初始值直接控制渲染结果。
怎么做:在 Form 上传 initialValues={{ remember: true }} 即可。
栅格方面,labelCol 控制文字宽度,wrapperCol 控制表单控件宽度。Ant Design 一行 24 份,labelCol: 8 加 wrapperCol: 16 正好占满一行;外层 style={{ maxWidth: 600 }} 限制整行宽度,所以看起来只占一小段。没有文字的行(如提交按钮、记住我)用 offset={8} 把左侧空出来,实现与上方输入框对齐。易错点:offset 加在 wrapperCol 上,不是加在 Form.Item 上。
提交与 useForm 管理表单数据
为什么需要:onFinish 的 values 只能拿到提交那一刻的数据,做重置、批量赋值就不够灵活。
怎么想:用 Form.useForm() 创建 form 对象,绑定到 Form 上,再通过它提供的 API 操作数据。
怎么做:
tsx
const [form] = Form.useForm()
const onFinish = (values: any) => {
console.log(values)
console.log(form.getFieldsValue())
}form.getFieldsValue() 取全部字段值;form.resetFields() 恢复初始值;form.setFieldsValue({ username: '张三' }) 批量设置字段值。易错点:resetFields 恢复的是 initialValues,不是清成空;如果初始值里没有值,才会清空。调试时先打印 values 和 getFieldsValue() 对比,两者一致说明绑定成功。
常用组件:Space、Card、message、Modal.confirm
Space 用来给多个按钮加间距,默认有间隔,size 可调宽度。Card 生成一个带圆角的白色面板,放在彩色背景上能明显看出层级。message.success() 和 message.error() 做轻提示,从 antd 直接引入函数调用即可。Modal.confirm() 做删除确认,需要从 antd 引入 Modal 再解构 confirm,并传入图标、标题、内容、onOk、onCancel。易错点:message 是函数不是组件,不要写成标签。
项目克隆、依赖安装与启动排错
final 是完成版项目,base 是二次开发的基础项目,docs 里放开发文档。二次开发意味着代码不是从零写,要一边读旧代码一边加功能。
安装依赖进项目根目录执行 npm i -f,-f 是强制安装,能绕过部分版本不兼容提示。装完有高危提示但没 error 就视为成功。启动用 npm start。两个项目都要 3000 端口时会提示占用,base 会落到 3001。
常见问题有两个:一是缺少声明文件导致全屏报错,解决方式是删掉 node_modules 重装,或按提示 npm i @types/包名;二是本地 token 失效导致“服务异常”,删除本地存储里的 token 后刷新,重新登录即可。易错点:不要看到红色警告就慌,先确认有没有 error。
项目开发流程
正常流程是立项、需求分析、产品原型图、评审会、UI 标注图、接口文档,然后前端做静态布局、封装 API、渲染数据。评审会上前端要判断需求用什么技术实现、大概多久,对不合理或短期做不了的需求,从版本迭代角度提出放到下一版。需求变更要走变更文档并重新排期,避免口头约定。易错点:拿到活之前先把难点和实现方式说清楚,不要等做不出来再解释。
本节目标与背景
本节要完成的是路由组件的“第一块砖”:先创建一个页面级组件文件,为后续路由配置和页面访问做准备。笔记给出的创建路径是:
src/pages/hospital/hospitalSet/HospitalSet.tsx
代码内容如下:
tsx
import React from 'react'
export default function HospitalSet() {
return (
<div>HospitalSet</div>
)
}从转写稿的项目背景看,这个 React 项目属于二次开发:已经有一个 final 完成版项目和一个 base 基础项目,base 目前只有首页架子,后续要参照 final 的效果逐步补功能。因此,创建 HospitalSet 这样的页面组件,不是孤立地“写个 div”,而是在已有项目结构里增加一个可被路由挂载的页面入口。
为什么需要先创建路由组件
路由组件是页面访问的落点。没有组件文件,路由即使配置了也没有内容可渲染;反过来,先创建组件,后续配置路由时才有明确的导入路径和默认导出可用。
转写稿里反复强调“先看已有代码、复制结构、检查报错、运行看效果”的开发套路。创建 HospitalSet 也一样:先把文件放在约定目录,写出最小可编译组件,确认 TypeScript 和 React 没有报错,再进入路由注册。这样可以把问题拆小:组件本身是否正确,和路由是否配置正确,分开排查。
怎么想:路径、命名与导出
路径 src/pages/hospital/hospitalSet/HospitalSet.tsx 表达了三层信息:pages 表示页面级组件,hospital 表示业务模块,hospitalSet 表示具体页面目录,文件名 HospitalSet.tsx 与组件名一致。这样后续在路由文件里导入时,路径语义清晰,不容易和普通 UI 组件混淆。
组件采用函数组件,并用 export default 默认导出。默认导出是路由懒加载或直接导入时最常用的形式。函数名 HospitalSet 使用大驼峰,和文件名保持一致,便于阅读和检索。
怎么做:创建文件并写入最小组件
在 src/pages/hospital/hospitalSet/ 下新建 HospitalSet.tsx,写入笔记中的代码。当前返回 <div>HospitalSet</div> 只是占位内容,目的是先让组件可编译、可渲染。等路由接通后,再把这里替换成真正的医院设置页面结构。
创建后先保存并观察编辑器是否报错。若出现“找不到模块”“未使用的 React”等提示,要区分是路径问题、依赖问题还是配置问题。本节代码保留了 import React from 'react',按笔记原样保留即可。
调试与验证过程
参考转写稿中“新建组件后格式化、检查未引入项、运行看效果”的过程:第一步确认文件路径没有拼错;第二步确认组件名和默认导出没有漏写;第三步确认 TSX 语法闭合正确;第四步运行项目,确认终端和浏览器控制台没有新增报错。
注意,本节只创建组件,还没有配置路由,所以浏览器里不一定能直接访问到 HospitalSet。这不是组件失败,而是“组件尚未挂载到路由”。后续在路由表中导入该组件并配置路径后,才能通过地址访问。调试时不要把“路由未配置”误判为“组件写错”。
易错点清单
第一,路径易错:pages/hospital/hospitalSet/HospitalSet.tsx 中目录名和文件名要一致,注意大小写。第二,导出易错:漏写 export default 会导致路由导入失败。第三,组件名易错:函数名建议与文件名一致,避免后续检索困难。第四,占位内容易错:当前 div 只是占位,不要误以为页面功能已经完成。第五,验证易错:未接路由时页面不显示是正常现象,应先用编辑器编译结果和终端报错判断组件是否可用。
完成本节后,HospitalSet 已经具备被路由引用的条件。下一步才是把该组件接入路由配置,让它成为可访问的医院设置页面。
先明确这一步要完成什么
为什么需要:转写稿里先启动 final 项目对照效果,再启动 base 项目;base 一开始只有首页架子,很多功能没有。现在要把“医院设置”页面接入项目,就必须让它进入路由表,否则页面组件写了也访问不到,侧边栏菜单也没有对应入口。
怎么想:笔记只给了两个动作:第一,导入医院设置路由组件,采用懒加载方式;第二,到 src/routes/index.ts 里配置路由。也就是“组件先能被路由表拿到”,再“把组件挂到正确的路径和菜单元信息上”。
怎么做:先按项目已有的 load 封装导入 HospitalSet。笔记没有给出具体 import 行,不要自己编路径,照现有文件里其他页面的导入方式补。然后修改 src/routes/index.ts。
易错点:不要用普通 import HospitalSet from ... 直接同步导入,既然本节明确要求懒加载,就应保持项目统一的 load(...) 写法;也不要只导入组件却忘了在 children 里挂 element。
路由表代码落在 src/routes/index.ts
为什么需要:项目把路由集中在一个文件里维护,菜单、标题、图标和页面组件都从这里读。分散写会让侧边栏和页面访问对不上。
怎么想:看笔记代码结构:最外层是 /syt,element 是 <Layout />,说明 /syt 是后台布局路由;dashboard、hospital 都是它的子路由。医院设置又挂在 hospital 下面,所以访问路径是 /syt/hospital/hospitalSet。
怎么做:保留笔记里的配置,重点是 HospitalSet 用 load(HospitalSet) 挂到 element:
ts
{
path: "/syt",
element: <Layout />,
children: [
{
// 路由路径
path: "/syt/dashboard",
meta: {
icon: <HomeOutlined />,
title: '首页'
},
element: load(Dashboard),
},
{
path:'/syt/hospital',
meta:{
title:'医院管理',
icon:<ShopOutlined />
},
children:[
{
path:'/syt/hospital/hospitalSet',
meta:{
title:'医院设置'
},
element:load(HospitalSet)
}
]
}
],
}易错点:path 要和菜单跳转地址一致;/syt/hospital 这一层没有 element,它是菜单分组,不要误以为它一定要渲染页面;hospitalSet 的 element 不能漏。
Layout 父路由与 children 的关系
为什么需要:后台页面通常共用头部、侧边栏和内容区。Layout 作为父路由,子页面才能显示在布局的内容区域里。
怎么想:代码中 /syt 的 element 是 <Layout />,dashboard 和 hospital 都在 children 中,说明它们是布局内部的页面。医院设置是三级路径,但仍然在 children 树里。
怎么做:保持 /syt 为父级,所有后台页面放进 children;医院设置放进 hospital 的 children。
易错点:不要把医院设置写成与 /syt 平级的顶层路由,否则可能跳出 Layout;也不要把 children 写成 child 之类拼写错误。
meta 的作用:给菜单提供标题和图标
为什么需要:转写稿里 final 项目登录后能看到侧边栏和菜单;路由表里的 meta.title、meta.icon 就是给菜单展示用的信息。没有 title,菜单文字可能出不来;没有 icon,菜单图标缺失。
怎么想:dashboard 有 icon 和 title;hospital 有“医院管理”和 ShopOutlined;hospitalSet 只给了 title:'医院设置',因为它是子菜单项,按代码原样保留即可。
怎么做:照笔记写 meta,不要额外加笔记没有的字段。icon 使用项目已引入的 HomeOutlined、ShopOutlined。
易错点:meta 不是 React Router 原生匹配路径的字段,真正决定访问地址的是 path,真正渲染的是 element;meta 是项目路由表给菜单用的约定信息。标题不要写错成“医院设计”等同音字。
load 懒加载:组件什么时候加载
为什么需要:后台页面多,如果全部同步导入,首屏会一次性加载很多暂时用不到的页面。懒加载让页面组件在需要访问时再加载。
怎么想:笔记明确写“懒加载方式导入”,并且 element 里是 load(Dashboard)、load(HospitalSet),说明项目已经封装了 load。我们只需按同一模式处理医院设置。
怎么做:导入 HospitalSet 时使用项目现有的懒加载写法;路由里写 element: load(HospitalSet)。Layout 是布局骨架,代码里是 <Layout />,不要擅自改成 load(Layout)。
易错点:load(HospitalSet) 里的 HospitalSet 必须是已经按懒加载方式拿到的变量;如果写成 load(HospitalSet()) 就变成执行函数,错误;如果路径或组件名拼错,启动后访问会白屏或控制台报模块找不到。
自检与调试
为什么需要:路由配置错一点,页面就可能进不去。转写稿中项目启动后会看页面和控制台是否有错误,这个习惯同样适用于路由配置。
怎么想:配置完先看编译是否通过,再看访问 /syt/hospital/hospitalSet 能否进入医院设置页,最后看侧边栏“医院管理”下是否出现“医院设置”。
怎么做:保存 src/routes/index.ts,启动项目,登录后从菜单进入医院设置;若白屏,先查 HospitalSet 导入、load 封装和 path 拼写;若菜单不显示,查 meta.title 和父子层级。
易错点:不要因为页面能打开就忽略控制台报错;转写稿里也强调过启动后看控制台。若出现与 token 相关的“服务异常”,那是转写稿中提到的另一类问题,处理方式是删除本地 token 后刷新重登,不要误判成路由写错。
课堂画面






代码示例
示例一:Form 表单基本使用(src/components/FormTest.tsx)
tsx
// src/components/FormTest.tsx
import React from 'react'
import { Form, Input, Checkbox, Button } from 'antd'
export default function FormTest() {
// onFinish:点击 type="submit" 的按钮时触发,values 就是收集到的表单数据
const onFinish = (values: any) => {
console.log('Success:', values)
}
return (
<Form
name="basic"
labelCol={{ span: 8 }} // 文字区占 8 份
wrapperCol={{ span: 16 }} // 表单控件区占 16 份(8+16=24 正好一行)
style={{ maxWidth: 600 }}
initialValues={{ remember: true }} // 初始值:让 remember 默认勾选
onFinish={onFinish}
>
<Form.Item
label="Username"
name="username"
rules={[{ required: true, message: 'Please input your username!' }]}
>
<Input />
</Form.Item>
<Form.Item
label="Password"
name="password"
rules={[{ required: true, message: 'Please input your password!' }]}
>
<Input.Password />
</Form.Item>
<Form.Item
label="Phone"
name="phone"
rules={[
{ required: true, message: '手机号必填' },
{ pattern: /^1[3-9]\d{9}$/, message: '手机号格式不正确' },
]}
>
<Input />
</Form.Item>
<Form.Item
name="remember"
valuePropName="checked"
wrapperCol={{ offset: 8, span: 16 }}
>
<Checkbox>Remember me</Checkbox>
</Form.Item>
{/* 没有文字的表单项,用 offset:8 把左侧文字区的位置空出来,保证与上方控件对齐 */}
<Form.Item wrapperCol={{ offset: 8, span: 16 }}>
<Button type="primary" htmlType="submit">Submit</Button>
</Form.Item>
</Form>
)
}在 App.tsx 中挂载:
tsx
import FormTest from './components/FormTest'
function App() {
return (
<>
<h2>Form 表单</h2>
<FormTest />
</>
)
}
export default App关键行与易错点
name写在Form.Item身上,不是写在Input身上;它对应提交给服务器的字段名。rules是数组,可以写多条:required管必填,pattern用正则校验格式,message是违反该规则后的提示文字。initialValues决定表单初始值,比如remember: true就让复选框默认勾选;改成false刷新就不勾选了。labelCol / wrapperCol按 24 栅格算:8 + 16 = 24占满一行;style={{ maxWidth: 600 }}限制整行宽度,否则会撑满屏幕。- 没有文字的行(复选框行、按钮行)必须加
offset: 8,否则控件不会和上面输入框对齐。 - Checkbox 在
Form.Item里要加valuePropName="checked",否则initialValues的值不会体现在勾选状态上。 - 提交按钮必须是
htmlType="submit",点击才会触发Form的onFinish。
示例二:useForm 创建 form 对象处理表单数据(src/components/FormData.tsx)
tsx
// src/components/FormData.tsx
import React from 'react'
import { Form, Input, Checkbox, Button, Space } from 'antd'
export default function FormData() {
// 第一步:创建 form 对象
const [form] = Form.useForm()
const onFinish = (values: any) => {
console.log('values:', values)
// 第三步:通过 form 的 API 获取表单数据
console.log('getFieldsValue:', form.getFieldsValue())
}
// 恢复初始值
const reset = () => {
form.resetFields()
}
// 批量设置表单字段的值
const setValue = () => {
form.setFieldsValue({
username: '大佐',
password: '123123',
phone: '13800000000',
})
}
return (
// 第二步:把 form 对象绑定到 Form 组件
<Form
form={form}
name="formData"
labelCol={{ span: 8 }}
wrapperCol={{ span: 16 }}
style={{ maxWidth: 600 }}
onFinish={onFinish}
>
<Form.Item
label="Username"
name="username"
rules={[{ required: true, message: 'Please input your username!' }]}
>
<Input />
</Form.Item>
<Form.Item
label="Password"
name="password"
rules={[{ required: true, message: 'Please input your password!' }]}
>
<Input.Password />
</Form.Item>
<Form.Item
label="Phone"
name="phone"
rules={[
{ required: true, message: '手机号必填' },
{ pattern: /^1[3-9]\d{9}$/, message: '手机号格式不正确' },
]}
>
<Input />
</Form.Item>
<Form.Item name="remember" valuePropName="checked" wrapperCol={{ offset: 8, span: 16 }}>
<Checkbox>Remember me</Checkbox>
</Form.Item>
<Form.Item wrapperCol={{ offset: 8, span: 16 }}>
<Space>
<Button type="primary" htmlType="submit">Submit</Button>
<Button onClick={reset}>清空</Button>
<Button onClick={setValue}>设置值</Button>
</Space>
</Form.Item>
</Form>
)
}关键行与易错点
- 固定三步走:
const [form] = Form.useForm()创建 →<Form form={form}>绑定 → 用form.xxx()操作数据。useForm返回的是数组,要解构出第一个元素。 form.getFieldsValue()取到的值和onFinish的values完全一致,它是onFinish之外的第二种取数据方式。- 用
form的意义在于“能操作”:values只能读,而form.resetFields()可以重置、form.setFieldsValue({...})可以批量回填。 resetFields()恢复到的是initialValues,不是一定清空;如果没写initialValues,重置后才是全空。- 忘记写
form={form}绑定,则form.getFieldsValue()/resetFields()全部失效。 setFieldsValue只传需要改的字段即可,不必把所有字段都写全。
示例三:Space / Card / message / Modal.confirm(src/components/Other.tsx)
tsx
// src/components/Other.tsx
import React from 'react'
import { Space, Button, Card, message, Modal } from 'antd'
import { ExclamationCircleOutlined } from '@ant-design/icons'
export default function Other() {
// Modal.confirm 从 Modal 里解构出来
const { confirm } = Modal
const showConfirm = () => {
confirm({
title: '提示',
icon: <ExclamationCircleOutlined />,
content: '确定删除当前记录吗?',
onOk() {
console.log('OK')
},
onCancel() {
console.log('Cancel')
},
})
}
return (
<div style={{ background: 'skyblue', padding: 20 }}>
{/* Space 给子元素之间加间距,size 控制间距宽度 */}
<Space size={50}>
<Button type="primary">保存</Button>
<Button>取消</Button>
</Space>
{/* Card 生成一个带圆角的白色背景面板 */}
<Card style={{ marginTop: 20 }}>
<h3>卡片内容</h3>
<Space>
<Button onClick={() => message.success('添加成功')}>成功提示</Button>
<Button onClick={() => message.error('添加失败')}>失败提示</Button>
<Button danger onClick={showConfirm}>删除</Button>
</Space>
</Card>
</div>
)
}关键行与易错点
message是函数式调用(message.success()),不是组件,不能写成<message />;同理Modal.confirm()也是函数调用。Modal.confirm必须先const { confirm } = Modal解构再用;icon要自己从@ant-design/icons导入(如ExclamationCircleOutlined),不导入会直接报错。onOk/onCancel分别是点“确定”“取消”时的回调,是对象里的方法写法,注意别写成箭头函数赋值到外面。Space只负责间距,默认就带空隙,size={50}只是把间距调大;不加Space两个按钮会紧贴在一起。Card是“白色背景面板”,用来把内容包起来;父级 div 上的background: 'skyblue'只是为了让 Card 的白色底更明显。message/Modal不用在 JSX 里写标签,直接放在事件回调里调用即可。
常见错误
复制官方示例后满屏红波浪线,编译不通过 现象:组件文件里一堆红色报错,
Form、Input、Checkbox、onFinish全部标红。 原因:示例代码省略了 import,且回调函数体没跟着复制过来。 修法:逐个把标红标识补上 import(antd的组件、@ant-design/icons的图标);未定义的回调(onFinish/onFinishFailed)在组件内补上函数体。删了再敲,让编辑器自动补全,别硬记。表单提交后拿不到某个字段的值 现象:
onFinish打印的values里少了字段,或getFieldsValue()返回空对象。 原因:name属性写在了Input上,而不是Form.Item上;或Form.Item压根没写name。 修法:把name移到Form.Item上,Input只保留占位符等展示属性。检查标准是:Form.Item有name才会被表单接管。点击提交按钮没反应,校验也不触发 现象:按钮能点,控制台无输出,必填项的红字提示不出现。 原因:
Button没有htmlType="submit",或者按钮被放到了Form外面。 修法:提交按钮写htmlType="submit"并确保在Form标签内部;纯前端操作的按钮(重置/填充)用普通onClick,不要混用。resetFields()之后表单里还有值 现象:点了“清空”,输入框仍然显示内容。 原因:Form上配置了initialValues,resetFields的语义是“恢复初始值”,不是“清空”。 修法:要真清空就用form.setFieldsValue({ ... })把各字段置空,或去掉initialValues;要恢复默认值才用resetFields。二者语义先想清楚再选。没有 label 的行(提交按钮行、记住我行)跟上面的输入框错位 现象:按钮行明显偏左,跟上面输入框的左边线对不齐。 原因:上方行有
labelCol占据左侧栅格,无 label 的行没有补偿这段宽度。 修法:给该Form.Item的wrapperCol加offset,数值等于labelCol的 span 值,把左侧空出来。message/Modal.confirm用不了,报 “is not a function” 或组件不渲染 现象:写了<message />或message.xxx未定义。 原因:这两个是函数式 API,不是可当标签用的组件;且导入方式不对。 修法:从antd解构导入message、Modal,调用message.success('...')、Modal.confirm({ title, content, onOk, onCancel })。图标需另外从@ant-design/icons引入。装完依赖启动后一刷新就全屏报错,或提示“服务异常” 现象:能进首页,一刷新出现
.d.ts相关报错;或页面弹服务异常、图标一直转。 原因:前者是npm i时声明文件没装全(多半是镜像源问题);后者是 localStorage 里残留了失效/被改坏的 token。 修法:声明文件问题——删node_modules→ 换源(腾讯/淘宝/官方)→ 重新npm i -f,或按报错单独npm i @types/xxx,赶进度可先点叉继续。token 问题——清掉本地存储的 token,刷新回登录页重新登录。排错顺序永远是:先看是不是.d.ts,再看 token,最后才怀疑自己的代码。
练习
表单三件套:用
Form+Form.useForm()实现一个含“用户名 / 密码 / 手机号”的表单。要求:手机号必填 + 正则校验(/^1[3-9]\d{9}$/),提交时用getFieldsValue()打印数据。再补三个按钮——“重置”“清空”“一键填充”,分别用resetFields、setFieldsValue置空、setFieldsValue赋值实现,验证三者的行为差异。对齐还原:给上面的表单加上“记住我”复选框行和“提交”按钮行,两行都没有 label。用
wrapperCol的offset让它们与上方输入框左对齐;再把labelCol/wrapperCol的 span 改小,观察布局变化并说明原因。交互组合:用
Card包一个列表区域,用Space放“编辑/删除”两个按钮(间距调大)。点删除时弹Modal.confirm,点确定后调message.success,点取消后调message.error。全程只用函数式 API,不用组件标签。环境排障复盘:手动把 localStorage 里的 token 改成一个错误值,刷新观察现象并记录修复步骤;再清空
node_modules,分别用两个不同镜像源各装一次,记录耗时与报错差异。把结论写成三行以内的排查清单。需求评审演练:假设产品经理在会上提出“1.0 版本要加数据大屏实时刷新”。请写出你的回应:哪些放 1.0、哪些放 2.0、理由是什么;并说明如果中途对方口头要求加功能,你走什么流程、留什么证据。
原视频对照
| # | 视频 | 时长 |
|---|---|---|
| 1 | 表单基本使用 | 14:56 |
| 2 | useForm-创建form对象处理表单数据 | 09:23 |
| 3 | 其他组件及方法使用 | 08:49 |
| 4 | react项目clone及目录介绍 | 08:36 |
| 5 | final项目-依赖安装及启动-可能错误的解决方案 | 09:32 |
| 6 | base项目-依赖安装及启动 | 04:24 |
| 7 | 项目开发流程 | 15:19 |
小结
- 表单取值的两条路径不是二选一:只读场景用
onFinish的values足够;一旦涉及写(重置、批量填充、联动),必须拿到form实例。 Form.Item才是表单的数据单元,name、rules、label、栅格配置都在它身上;Input只是展示层。判断自己写错没,先看这四样是不是都在Form.Item上。rules是数组,意味着一条字段可以叠加多条规则,校验顺序即数组顺序,报错只显示第一条未通过的。resetFields和“清空”是两件事,用错就是 bug 而不是风格问题;凡是看到“重置后还有值”,先找initialValues。- 组件库的正确用法只有一处权威来源——官方示例;复制之后必做两件事:补 import、补回调,缺一不可。
- 环境类报错的排查顺序应当固定下来:
.d.ts/依赖 → token/登录态 → 业务代码。反过来查,时间全浪费在不可能出错的地方。 -f是兜底手段不是常规手段;能正常装就不要加,加了之后遇到诡异问题反而难定位。- 二次开发项目不要通读源码,按“路由 → 页面 → api → 公共组件”倒推着读,边读边改比读完再改效率高得多。
- 需求变更只认文档:邮件或需求变更单,口头答应等于把责任揽到自己身上。