首页
/ axios HTML 表单提交实战:postForm、字段名路径记法与 JSON 转换原理

axios HTML 表单提交实战:postForm、字段名路径记法与 JSON 转换原理

2026-09-06 18:42:57作者:温玫谨Lighthearted

axios 除了传统的 post 方法外,还提供了专门用于表单提交的 postForm/putForm/patchForm 快捷方法,可以直接把页面上的 <form> 元素作为请求体发送,也可以在显式指定 Content-Type: application/json 时将其序列化为 JSON。本文以官方文档 HTML form posting (browser) 为主线,结合当前仓库源码,讲清楚 HTML 表单提交的三种用法、字段名路径记法(dot / bracket notation)的解析规则,以及底层 transformRequestformDataToJSON 的完整调用链。

一、用 postForm 直接提交 HTML 表单

当页面上已经存在一个 <form> 元素时,无需手动构造 FormData,也无需编写任何取值的 JavaScript 代码,只需用 document.querySelector 拿到表单元素,传给 axios.postForm 即可:

await axios.postForm('https://your.server/api/post', document.querySelector('#htmlForm'));

postForm 的语义是"发送 multipart 表单数据"。从源码看,它并不是一个独立实现,而是在 Axios 类初始化时由 post/put/patch 三个方法统一生成的姊妹方法——生成时会额外注入一个默认的 Content-Type: multipart/form-data 请求头(见 lib/core/Axios.js):

utils.forEach(['post', 'put', 'patch', 'query'], function forEachMethodWithData(method) {
  function generateHTTPMethod(isForm) {
    return function httpMethod(url, data, config) {
      return this.request(
        mergeConfig(config || {}, {
          method,
          headers: isForm
            ? {
                'Content-Type': 'multipart/form-data',
              }
            : {},
          url,
          data,
        })
      );
    };
  }

  Axios.prototype[method] = generateHTTPMethod();

  // query 是幂等读方法,multipart 表单体不符合其语义,因此不生成 queryForm
  if (method !== 'query') {
    Axios.prototype[method + 'Form'] = generateHTTPMethod(true);
  }
});

也就是说 postFormputFormpatchForm 三者等价于对应方法 + multipart/form-data 内容类型,唯一不生成 *Form 变体的是 query(源码注释说明这是出于幂等读方法的语义考虑)。

二、HTML 表单在 transformRequest 中如何变成 FormData

postForm 只是设置了内容类型,真正把 HTMLFormElement 转成 FormData 的动作发生在默认请求转换器里(见 lib/defaults/index.js):

transformRequest: [
  function transformRequest(data, headers) {
    const contentType = headers.getContentType() || '';
    const hasJSONContentType = contentType.indexOf('application/json') > -1;
    const isObjectPayload = utils.isObject(data);

    if (isObjectPayload && utils.isHTMLForm(data)) {
      data = new FormData(data);   // 关键:HTML 表单元素 → FormData
    }

    const isFormData = utils.isFormData(data);

    if (isFormData) {
      return hasJSONContentType ? JSON.stringify(formDataToJSON(data)) : data;
    }
    // ...其余分支(ArrayBuffer / Blob / URLSearchParams / 对象序列化等)
  },
],

这段代码解释了文档中两种提交方式的完整行为:

  1. 表单以 multipart 形式提交HTMLFormElement 先被 new FormData(data) 转换,随后因内容类型不是 JSON 而原样返回 FormData,由 xhr/fetch 适配器负责带上 multipart/form-data 的 boundary 发送;
  2. 表单以 JSON 形式提交:若内容类型包含 application/json,则 FormData 会被 formDataToJSON 转换成普通对象后再 JSON.stringify

其中 utils.isHTMLForm 是基于 Object.prototype.toStringkindOfTest('HTMLFormElement') 类型探测(见 lib/utils.js),这也是该功能被标注为"仅浏览器"的原因——HTMLFormElement 只存在于浏览器环境。

三、把 FormData / HTMLForm 显式提交为 JSON

文档给出的第二种用法是:显式把 Content-Type 设为 application/json,普通 axios.post 也能直接提交 FormData<form> 元素:

await axios.post('https://your.server/api/post', document.querySelector('#htmlForm'), {
  headers: {
    'Content-Type': 'application/json',
  },
});

走的是上文 transformRequesthasJSONContentType ? JSON.stringify(formDataToJSON(data)) : data 这条分支。除了随请求隐式触发,axios 还在实例上暴露了可直接调用的工具方法 axios.formToJSON(见 lib/axios.js),它接收 HTMLFormElementFormData,内部同样先统一转成 FormData 再交给 formDataToJSON

axios.formToJSON = (thing) =>
  formDataToJSON(utils.isHTMLForm(thing) ? new FormData(thing) : thing);

类型定义中 formToJSON 的签名也明确支持两种输入(见 index.d.ts),而 postForm/putForm/patchForm 的 Promise 签名与普通请求方法一致(见 index.d.ts)。

四、字段名路径记法(Field-name path notation)

文档给出了一个可以直接被上述代码提交的示例表单:

<form id="htmlForm">
  <input type="text" name="foo" value="1" />
  <input type="text" name="deep.prop" value="2" />
  <input type="text" name="deep prop spaced" value="3" />
  <input type="text" name="baz" value="4" />
  <input type="text" name="baz" value="5" />

  <select name="user.age">
    <option value="value1">Value 1</option>
    <option value="value2" selected>Value 2</option>
    <option value="value3">Value 3</option>
  </select>

  <input type="submit" value="Save" />
</form>

以 JSON 方式提交时,它会被序列化为:

{
  "foo": "1",
  "deep": {
    "prop": "2"
  },
  "deep prop spaced": "3",
  "baz": ["4", "5"],
  "user": {
    "age": "value2"
  }
}

这段示例恰好覆盖了 lib/helpers/formDataToJSON.jsparsePropPath 的全部解析规则:

function parsePropPath(name) {
  // foo[x][y][z] -> ['foo', 'x', 'y', 'z']
  // foo.x.y.z   -> ['foo', 'x', 'y', 'z']
  const path = [];
  const pattern = /[^.[\]]+|\[([^.[\]]*)]/g;
  let match;

  while ((match = pattern.exec(name)) !== null) {
    throwIfDepthExceeded(path.length);
    path.push(match[0] === '[]' ? '' : match[1] || match[0]);
  }

  return path;
}

规则逐条对应文档中的 tip:

  • 只有 .[...] 会创建嵌套属性路径deep.prop 被拆成 ['deep', 'prop'],最终生成 {"deep": {"prop": "2"}}user.age 同理生成 {"user": {"age": "value2"}}
  • 空格、-+*& 等字符保留为字面字段名的一部分deep prop spaced 不含 . 或括号,整体就是顶层 key;源码注释也明确说明 user-nameuser name 这类 key 会被原样保留;
  • 重复的 name 合并为数组:两个 baz 输入框被合并为 "baz": ["4", "5"]。这一行为发生在 formDataToJSON 内部的 buildPath 函数中——当目标 key 已存在时,若原值已是数组则 concat,否则包装成 [旧值, 新值](见 lib/helpers/formDataToJSON.js);
  • [] 语义foo[] 中的 [] 段被解析为空字符串段,buildPath 里空段配合目标为数组时会取 target.length 作为下标,实现数组追加。

值得注意的还有源码中两个健壮性设计:

  1. 嵌套深度保护parsePropPathbuildPath 都会调用 throwIfDepthExceeded,超过 DEFAULT_FORM_DATA_MAX_DEPTH(与 toFormData 共享的默认嵌套上限,见 lib/helpers/toFormData.js 的导出)会抛出 AxiosError(错误码 ERR_FORM_DATA_DEPTH_EXCEEDED),防止畸形字段名导致递归失控;
  2. 原型链污染防护buildPath 遇到名为 __proto__ 的段直接跳过,不写入目标对象(见 lib/helpers/formDataToJSON.js)。formDataToJSON 只遍历 utils.forEachEntry 中的实际条目,不会触碰构造器原型链。

相关行为在仓库中有独立测试覆盖,可查阅 tests/unit/helpers/formDataToJSON.test.js 验证路径解析、重复 key、深度上限等用例。

五、限制与注意事项

文档明确声明的一条限制:

Sending Blobs/Files as JSON (base64) is not currently supported.

当前不支持把表单中的 Blob/文件字段以 base64 形式编码进 JSON。当表单包含 <input type="file"> 时,应走第一节的 multipart 路径(postForm 默认行为),由 FormData 原生携带二进制文件,而不是显式指定 JSON 内容类型。

另外两点来自源码的适用前提:

  • 直接提交 <form> 元素依赖 HTMLFormElementnew FormData(formElement),属于浏览器能力;在 Node 环境(http/https 适配器)中没有对应的 DOM 表单元素,JSON 路径转换则同样可用(Node 的 FormData 实现同样满足 utils.isFormData);
  • 显式 JSON 提交走的是 JSON.stringify(formDataToJSON(data)),因此最终 body 是字符串;如需自定义序列化(如字段重命名),可覆盖 transformRequest,或在发送前用 axios.formToJSON 先取出普通对象自行处理。

六、小结

  • axios.postForm(url, formElement) 直接提交页面上的 HTML 表单,等价于 post + 默认 Content-Type: multipart/form-datalib/core/Axios.js);
  • HTMLFormElementFormData 的转换发生在默认 transformRequest 中(lib/defaults/index.js);
  • 显式设置 Content-Type: application/json 后,FormData/HTML 表单会经 formDataToJSON 转为 JSON 字符串发送,也可单独调用 axios.formToJSON 获取普通对象;
  • 字段名只有 .[...] 产生嵌套路径,其余字符是字面名,重复字段名自动合并为数组;实现细节见 lib/helpers/formDataToJSON.js,含深度上限与 __proto__ 防护;
  • Blob/文件的 base64 JSON 编码目前不受支持,文件字段请使用 multipart 表单路径。
登录后查看全文
热门项目推荐
相关项目推荐