Skip to content

目录结构与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 启动后页面“空”就是失败;它本来就是一个基础项目。端口占用也不是代码错误,按提示换端口即可。

常见错误一:网络原因导致包没下全 ​

这是最常见的问题。为什么会出现?转写稿说,大家一起下载依赖时网络压力大,或者当前镜像源缺少某些文件,可能导致包没下全。怎么想:先判断是“网络/依赖问题”还是“代码问题”。如果刚装完就报缺包、声明文件找不到,优先怀疑依赖。怎么做:按笔记方案处理:

  1. 删除 node_modules,切换镜像源,重新执行 npm i -f 安装。
  2. 如果只是学习过程中弹错,也可以先把错误叉掉,先对付用,课下网络好时再重装。

易错点:不要在不删 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 后刷新重登,不要误判成路由写错。

课堂画面 ​

课堂画面 1

课堂画面 2

课堂画面 3

课堂画面 4

课堂画面 5

课堂画面 6

代码示例 ​

示例一: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 里写标签,直接放在事件回调里调用即可。

常见错误 ​

  1. 复制官方示例后满屏红波浪线,编译不通过 现象:组件文件里一堆红色报错,Form、Input、Checkbox、onFinish 全部标红。 原因:示例代码省略了 import,且回调函数体没跟着复制过来。 修法:逐个把标红标识补上 import(antd 的组件、@ant-design/icons 的图标);未定义的回调(onFinish/onFinishFailed)在组件内补上函数体。删了再敲,让编辑器自动补全,别硬记。

  2. 表单提交后拿不到某个字段的值 现象:onFinish 打印的 values 里少了字段,或 getFieldsValue() 返回空对象。 原因:name 属性写在了 Input 上,而不是 Form.Item 上;或 Form.Item 压根没写 name。 修法:把 name 移到 Form.Item 上,Input 只保留占位符等展示属性。检查标准是:Form.Item 有 name 才会被表单接管。

  3. 点击提交按钮没反应,校验也不触发 现象:按钮能点,控制台无输出,必填项的红字提示不出现。 原因:Button 没有 htmlType="submit",或者按钮被放到了 Form 外面。 修法:提交按钮写 htmlType="submit" 并确保在 Form 标签内部;纯前端操作的按钮(重置/填充)用普通 onClick,不要混用。

  4. resetFields() 之后表单里还有值 现象:点了“清空”,输入框仍然显示内容。 原因:Form 上配置了 initialValues,resetFields 的语义是“恢复初始值”,不是“清空”。 修法:要真清空就用 form.setFieldsValue({ ... }) 把各字段置空,或去掉 initialValues;要恢复默认值才用 resetFields。二者语义先想清楚再选。

  5. 没有 label 的行(提交按钮行、记住我行)跟上面的输入框错位 现象:按钮行明显偏左,跟上面输入框的左边线对不齐。 原因:上方行有 labelCol 占据左侧栅格,无 label 的行没有补偿这段宽度。 修法:给该 Form.Item 的 wrapperCol 加 offset,数值等于 labelCol 的 span 值,把左侧空出来。

  6. 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 引入。

  7. 装完依赖启动后一刷新就全屏报错,或提示“服务异常” 现象:能进首页,一刷新出现 .d.ts 相关报错;或页面弹服务异常、图标一直转。 原因:前者是 npm i 时声明文件没装全(多半是镜像源问题);后者是 localStorage 里残留了失效/被改坏的 token。 修法:声明文件问题——删 node_modules → 换源(腾讯/淘宝/官方)→ 重新 npm i -f,或按报错单独 npm i @types/xxx,赶进度可先点叉继续。token 问题——清掉本地存储的 token,刷新回登录页重新登录。排错顺序永远是:先看是不是 .d.ts,再看 token,最后才怀疑自己的代码。

练习 ​

  1. 表单三件套:用 Form + Form.useForm() 实现一个含“用户名 / 密码 / 手机号”的表单。要求:手机号必填 + 正则校验(/^1[3-9]\d{9}$/),提交时用 getFieldsValue() 打印数据。再补三个按钮——“重置”“清空”“一键填充”,分别用 resetFields、setFieldsValue 置空、setFieldsValue 赋值实现,验证三者的行为差异。

  2. 对齐还原:给上面的表单加上“记住我”复选框行和“提交”按钮行,两行都没有 label。用 wrapperCol 的 offset 让它们与上方输入框左对齐;再把 labelCol/wrapperCol 的 span 改小,观察布局变化并说明原因。

  3. 交互组合:用 Card 包一个列表区域,用 Space 放“编辑/删除”两个按钮(间距调大)。点删除时弹 Modal.confirm,点确定后调 message.success,点取消后调 message.error。全程只用函数式 API,不用组件标签。

  4. 环境排障复盘:手动把 localStorage 里的 token 改成一个错误值,刷新观察现象并记录修复步骤;再清空 node_modules,分别用两个不同镜像源各装一次,记录耗时与报错差异。把结论写成三行以内的排查清单。

  5. 需求评审演练:假设产品经理在会上提出“1.0 版本要加数据大屏实时刷新”。请写出你的回应:哪些放 1.0、哪些放 2.0、理由是什么;并说明如果中途对方口头要求加功能,你走什么流程、留什么证据。

原视频对照 ​

#视频时长
1表单基本使用14:56
2useForm-创建form对象处理表单数据09:23
3其他组件及方法使用08:49
4react项目clone及目录介绍08:36
5final项目-依赖安装及启动-可能错误的解决方案09:32
6base项目-依赖安装及启动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 → 公共组件”倒推着读,边读边改比读完再改效率高得多。
  • 需求变更只认文档:邮件或需求变更单,口头答应等于把责任揽到自己身上。