freeCodeCamp 课程实战解析:用 `<form>` 与 `action` 属性创建可向服务器提交数据的纯 HTML 表单
本文基于 freeCodeCamp 开源仓库中 Responsive Web Design(响应式网页设计)认证的 Basic HTML and HTML5 课程块里的 CatPhotoApp 练习
Create a Form Element,系统讲解 HTML 表单元素的创建方法、action属性的作用与取值方式,完整还原挑战的初始代码(seed)、题目要求、自动判题(hints)逻辑与标准答案,并结合仓库中的课程结构、挑战文件格式与 Schema 校验规则,帮助读者既通过关卡,也真正理解「纯 HTML 即可实现表单数据提交」这一核心机制。
挑战全景:它在 freeCodeCamp 课程体系中的位置
Create a Form Element 对应仓库文件 curriculum/challenges/english/blocks/basic-html-and-html5/bad87fee1348bd9aede08830.md,属于 Basic HTML and HTML5 课程块。该块的块级定义位于 curriculum/structure/blocks/basic-html-and-html5.json,从该 JSON 可以看到其 helpCategory 为 "HTML-CSS",且块内按顺序列出了全部课程挑战;而本块又是响应式网页设计超块(superblock)的第一个块,见 curriculum/structure/superblocks/responsive-web-design.json。
从 curriculum/structure/blocks/basic-html-and-html5.json 中记录的挑战顺序可以确认它的前后语境:
- 前一个挑战是
Add Placeholder Text to a Text Field(已创建一个带占位文字的文本输入框); - 本挑战
Create a Form Element的任务是把这个文本框用<form>包起来,并给出提交目标地址; - 紧随其后的下一个挑战是
Add a Submit Button to a Form,负责在表单内补充真正的提交按钮,让整条提交链路完整落地。
因此本挑战是 CatPhotoApp 系列表单部分承上启下的一环:上一关准备好了输入控件,这一关把控件「放进会提交数据的容器」,下一关再加「触发提交的按钮」。
挑战文件的 frontmatter 解读
挑战文件头部是一段 YAML frontmatter,字段分别代表:
| 字段 | 本挑战取值 | 含义 |
|---|---|---|
id |
bad87fee1348bd9aede08830 |
挑战全局唯一 ID,也用于结构 JSON 中与标题配对 |
title |
Create a Form Element |
挑战标题 |
challengeType |
0 |
挑战类型,0 代表 HTML 挑战(classic 编辑器形态) |
forumTopicId |
16817 |
论坛讨论主题 ID |
dashedName |
create-a-form-element |
URL 友好的英文短名 |
关于 challengeType: 0 的含义,可在仓库常量定义 packages/shared/src/config/challenge-types.ts 中验证:const html = 0;,且在后续 viewTypes 映射中 [html]: 'classic',说明该类型挑战在经典 HTML 编辑器界面中完成。这一整套 frontmatter 字段还会被 curriculum/schema/challenge-schema.js 里的 Joi Schema 校验(例如要求 challengeType 为 0–33 的数字、dashedName 符合 slug 规则),保证仓库中每个挑战文件结构合法。
核心知识:action 属性如何让表单真正「提交」
挑战的 # --description-- 部分点明了本关要传递的核心概念:
使用纯 HTML,就可以构建出真正把数据提交到服务器的网页表单。实现方式是在
form元素上指定一个action属性。
action 属性决定了表单数据提交时发送到的目标地址。当用户填写表单并触发提交后,浏览器会把表单内带有 name 的各控件取值收集起来,编码后向 action 指定的 URL 发起请求。默认情况下请求方法为 GET,数据会以查询字符串(query string)的形式附加在地址上;也可以在 <form> 上通过 method 属性改为 POST 等其他方法。
挑战给出的示范代码清晰地展示了最小可提交表单的骨架:
<form action="url-where-you-want-to-submit-form-data">
<input>
</form>
这也就是本关需要理解并动手实践的关键结构:<form> 是数据提交的容器,action 是数据送达的目的地。
挑战任务解读:把输入框嵌套进表单并指定提交地址
# --instructions-- 给出的任务分两步:
- 把已经存在的
input元素嵌套到一个form元素内部; - 为这个
form元素的action属性赋值为字符串https://www.freecatphotoapp.com/submit-cat-photo。
这里的 https://www.freecatphotoapp.com/submit-cat-photo 是 freeCodeCamp 为 CatPhotoApp 示例虚构的一个表单处理地址,用于演示「把猫咪照片链接提交到某处」的完整表单流程。
初始代码(seed)
# --seed-- 中的 ## --seed-contents-- 提供了本关开始时编辑器内的完整 HTML:
<h2>CatPhotoApp</h2>
<main>
<p>Click here to view more <a href="#">cat photos</a>.</p>
<a href="#"><img src="https://cdn.freecodecamp.org/curriculum/cat-photo-app/relaxing-cat.jpg" alt="A cute orange cat lying on its back."></a>
<p>Things cats love:</p>
<ul>
<li>catnip</li>
<li>laser pointers</li>
<li>lasagna</li>
</ul>
<p>Top 3 things cats hate:</p>
<ol>
<li>flea treatment</li>
<li>thunder</li>
<li>other cats</li>
</ol>
<input type="text" placeholder="cat photo URL">
</main>
可以看到,初始代码中最后的 <input type="text" placeholder="cat photo URL"> 是裸放在 <main> 里的,尚未被任何表单包裹,这正是本关需要修改的位置。注意 <input> 是一个典型的空元素(void element),它自身没有闭合标签,因此不存在「把闭合标签放进表单」的疑问——本关需要的只是在它外面包上一对 <form>...</form> 标签。
标准答案逐步拆解
# --solutions-- 部分给出的官方参考答案如下:
<h2>CatPhotoApp</h2>
<main>
<p>Click here to view more <a href="#">cat photos</a>.</p>
<a href="#"><img src="https://cdn.freecodecamp.org/curriculum/cat-photo-app/relaxing-cat.jpg" alt="A cute orange cat lying on its back."></a>
<p>Things cats love:</p>
<ul>
<li>catnip</li>
<li>laser pointers</li>
<li>lasagna</li>
</ul>
<p>Top 3 things cats hate:</p>
<ol>
<li>flea treatment</li>
<li>thunder</li>
<li>other cats</li>
</ol>
<form action="https://www.freecatphotoapp.com/submit-cat-photo">
<input type="text" placeholder="cat photo URL">
</form>
</main>
与初始代码相比,改动集中在最后一处:在 <input> 外层套上 <form>,同时写入 action 属性。需要特别强调的是,input 原有的 type="text" 与 placeholder="cat photo URL" 两个属性必须原样保留,因为自动判题会校验这两个属性是否仍然存在且值正确。
写作时的关键提醒:
- 把
<form>的action属性写成https://www.freecatphotoapp.com/submit-cat-photo,不要省略协议头https://; - 把开标签放在
<input>之前、闭标签</form>放在<input>之后; - 保持其余 HTML 内容(标题、段落、列表、图片链接等)不被删改。
参考实现中这一关只改动了 <main> 内部的尾部区域,这也正是挑战文件 # --solutions-- 区块中保存的最终形态,任何能通过下方 hints 的等价写法都是可接受的。
自动判题:hints 究竟在检查什么
freeCodeCamp 的课程文件用 # --hints-- 区块存放自动化测试断言。这些断言以 JavaScript 形式编写,实际运行时会在学习者代码渲染出的 DOM 与源码文本上执行校验。本关的 hints 共四条,逐条解读如下。
Hint 1:input 必须被嵌套在 form 内部
const inputElem = document.querySelector('form input');
assert.strictEqual(inputElem.getAttribute('type'),'text');
assert.strictEqual(inputElem.getAttribute('placeholder'),'cat photo URL');
document.querySelector('form input') 会查找「form 内部的后代 input」。只有把 <input> 放进 <form>,这条选择器才能命中元素;随后再校验该输入框的 type 仍为 text、placeholder 仍为 cat photo URL,确保上一关创建的文本框属性没有被破坏。
Hint 2:action 属性必须指向正确的提交地址
const action = document.querySelector('form').getAttribute('action');
assert.match(action,/^https:\/\/(www\.)?freecatphotoapp\.com\/submit-cat-photo$/i);
这里先取出页面中第一个 form 元素的 action 属性值,再用正则匹配。正则 ^https:\/\/(www\.)?freecatphotoapp\.com\/submit-cat-photo$ 配合末尾的 i 标志,允许 www. 出现或不出现、主机名大小写不敏感,但要求整个值以 https:// 开头、以 /submit-cat-photo 结尾——也就是说地址必须完整指向提交端点。
Hint 3:form 元素必须具有成对的开闭标签
assert.match(code,/<\/form>/g);
assert.match(code,/<form [^<]*>/g);
assert.strictEqual(code.match(/<\/form>/g).length,code.match(/<form [^<]*>/g).length);
这条 hint 直接对学习者提交的源代码文本(code)做正则统计:要求至少出现一个 </form> 闭合标签,至少出现一个带空格与属性内容的 <form ...> 开标签,并且两者的出现次数严格相等。换言之,多写或少写一对标签都无法通过——这是对标签「成对、良好闭合」的最直接检查。
测试框架层面的佐证
挑战内嵌的断言风格(assert.strictEqual、assert.match)与 freeCodeCamp 测试框架一致;而「hints 是 JS 断言 + 运行在浏览器 DOM/源码上」这一事实,从本挑战 hints 内同时查询 document(DOM)与 code(源码文本)即可推断得出。读者在实际练习界面点击「Run the Tests」时,以上四条会逐一在背后执行,全部通过即表示过关。
挑战文件格式:Markdown 分节是如何组织的
以本挑战为代表的 freeCodeCamp 课程 Markdown 文件遵循一套固定分节规范,读者若要阅读或编写同类课程内容,可对照本文件结构理解:
# --description--:概念讲解与示例代码,是教学正文;# --instructions--:给出本关具体的操作要求;# --hints--:一组可执行的 JS 测试断言(自动判题逻辑);# --seed--与## --seed-contents--:学习者在编辑器中的初始代码;# --solutions--:官方参考答案,通常每个答案都保证能通过全部 hints。
这一格式并非随意约定,仓库中的解析与校验链路都在围绕它工作:
- Schema 校验:课程文件的字段合法性与必填项由 curriculum/schema/challenge-schema.js 中基于 Joi 的定义约束;
- 解析示例:挑战解析器在 tools/challenge-parser 中实现,其 fixture 文件(例如 tools/challenge-parser/parser/fixtures/simple.md)展示了与
--description--、--instructions--、--hints--、--seed-contents--、--solutions--完全一致的典型分节结构,可作为理解此格式的最小范例。
学习闭环:从本关到完整表单提交
理解本关后,可以预演一下完整的数据提交链路为何需要后续课程补全:
- 本关让
<input>进入<form>,并给<form>绑定action提交地址; - 紧随其后的
Add a Submit Button to a Form挑战(对应文件 curriculum/challenges/english/blocks/basic-html-and-html5/bad87fee1348bd9aedd08830.md)会在</form>内部追加一个type="submit"的按钮,作为真正触发提交的控件。该文件# --solutions--中呈现的最终代码正是在本关答案基础上增加了<button type="submit">Submit</button>; - 后续还有
Use HTML5 to Require a Field等挑战,进一步引入required属性等表单校验能力。
由此可见,action 属性 + 表单控件 + 提交按钮,构成了「纯 HTML 表单提交」的最小完整闭环:输入控件负责收集数据,form 容器与 action 地址负责定义送往哪里,submit 按钮负责发起动作。完成本关,意味着你已经掌握了三者中的中间一环,并且理解了仅凭 HTML 语义就能把用户数据发往服务端的核心原理。
小结与练习建议
- 语法要点:
<form action="提交地址">包裹若干表单控件;本关中action的精确取值为https://www.freecatphotoapp.com/submit-cat-photo。 - 兼容要点:包入
form后不得破坏<input>已有的type与placeholder属性;开闭标签必须成对且数量一致。 - 验证方式:本关所有测试以 JS 断言驱动(DOM 查询 + 源码正则),分别覆盖嵌套关系、属性取值与标签闭合,全部通过即为过关。
- 进阶方向:继续完成同块内
Add a Submit Button to a Form等后续挑战,即可亲手串联出完整的表单提交演示。
想深入研究的读者,可直接在仓库中打开本挑战源文件 bad87fee1348bd9aede08830.md 查看原始 frontmatter 与全部 hints,或对照 basic-html-and-html5.json 与 responsive-web-design.json 观察其在课程树中的位置,从而把「一个关卡」放进「一条完整的学习与知识链路」中去理解。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0624
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00