首页 > 解决方案 > 我可以用更新的 Object.fromEntries(new FormData(form)) 替换表单元素循环解析吗?

问题描述

我可以安全地更换这个吗

document.getElementById("frm-Main").addEventListener("submit", (e) =>
{        
    e.preventDefault();
    
    // const frmMain = e.target;
    const frmMain = document.querySelector('#frm-Main');

    let params = [];

    const inputs = frmMain.getElementsByTagName("input");
    console.log(inputs);

    for (let i = 0; i < inputs.length; i++)
    {
        if (inputs[i].type == 'radio' && inputs[i].checked != true) continue;
        if (inputs[i].type == 'checkbox' && inputs[i].checked != true) continue;

        params.push({ id: inputs[i].name, value: inputs[i].value });
    }

    const selects = frmMain.getElementsByTagName("select");
    for (let i = 0; i < selects.length; i++)
    {            
        params.push({ id: selects[i].name, value: selects[i].value });
    }    

    fetch(url,
    {
        method: 'POST',
        body: JSON.stringify(params),
        headers: { 'Content-type': 'application/json; charset=UTF-8' }
    }).then(response => response.json()).then(data => console.log(data));    
});

有了这个 ?

document.getElementById("frm-Main").addEventListener("submit", (e) =>
{        
    e.preventDefault();

    const frmMain = document.querySelector('#frm-Main');    
    const formData = Object.fromEntries(new FormData(frmMain));

    fetch(url,
    {
        method: 'POST',
        body: JSON.stringify(formData),
        headers: { 'Content-type': 'application/json; charset=UTF-8' }
    }).then(response => response.json()).then(data => console.log(data));
});

标签: javascriptforms

解决方案


在某些情况下,是的,但它通常不是安全的。这将取决于表单数据是否包含具有重复名称的条目。例如,多选可以,复选框组也可以。您还可能在 Safari 中遇到与 radiogroup API 变化相关的问题。

此外,FormData 值可以是文件。您可能需要执行额外的自定义序列化以将这些值转换为 JSON 表示。

以下交互式演示显示了Object.fromEntries在 formdata 包含具有通用名称的成员的情况下 using 将如何无法正常工作(没有一些额外的先前转换)。如果作为多部分或 urlencoded 表单数据而不是 JSON 表示形式发送,它还显示了在 HTTP 请求正文中出现的输出。

let controller;

form.addEventListener('submit', event => {
  event.preventDefault();
  update();
});

form.addEventListener('change', update);

update();

function renderEntry([ key, value ]) {
  return `[ ${ JSON.stringify(key) }, ${ JSON.stringify(value) } ]`
}

async function update() {
  controller?.abort();

  let { signal } = controller = new AbortController;
  let formData = new FormData(form);
  let object = Object.fromEntries(formData);
  let rawUSP = await new Response(new URLSearchParams(formData)).text();
  let rawFD = await new Response(formData).text();

  if (!signal.aborted) {
    outputEntries.innerText = Array.from(formData, renderEntry).join('\n');
    outputFromEntries.innerText = JSON.stringify(object, null, 2);
    outputRawUSP.innerText = rawUSP;
    outputRawFD.innerText = rawFD;
  }
}
code {
  display: block;
  padding: 1em 1ch;
  white-space: pre;
}

dd, dl, dt {
  all: unset;
  display: block;
}

dt {
  background-color: aquamarine;
  font-weight: 700;
  padding: 0 1ch;
}

.fields {
  display: grid;
  grid-gap: 0.5em;
  grid-template-columns: min-content 1fr;
}

form {
  display: grid;
  grid-gap: 1em;
  grid-template-columns: 1fr 1fr 1fr;
}

.field {
  display: flex;
  flex-direction: column;
}

.field:last-child {
  grid-column: 1 / span 3;
}

output {
  background: azure;
  display: block;
  font-family: monospace;
  margin: 1em 0;
  padding: 1ch;
}
<form id="form">
  <div class="field">
    <label for="foo">Foo</label>
    <select multiple id="foo" name="foo">
      <option selected>bar</option>
      <option selected>baz</option>
    </select>
  </div>
  <div class="field">
    <label for="qux">Qux</label>
    <input id="qux" name="qux" value="quux">
  </div>
  <fieldset>
    <legend>Corge</legend>
    <div class="fields">
      <input checked id="grault" name="corge" type="checkbox" value="grault">
      <label for="grault">grault</label>
      <input id="garply" name="corge" type="checkbox" value="garply">
      <label for="garply">garply</label>
      <input checked id="waldo" name="corge" type="checkbox" value="waldo">
      <label for="waldo">waldo</label>
    </div>
  </fieldset>
  <div class="field">
    <label for="output">Output</label>
    <output id="output">
      <dl>
        <dt>FormData entries</dt>
        <dd><code id="outputEntries"></code></dd>
        <dt>Object.fromEntries</dt>
        <dd><code id="outputFromEntries"></code></dd>
        <dt>FormData application/x-www-form-urlencoded body</dt>
        <dd><code id="outputRawUSP"></code></dd>
        <dt>FormData multipart/form-data body</dt>
        <dd><code id="outputRawFD"></code></dd>
      </dl>
    </output>
  </div>
</form>

结果缺少“bar”和“grault”(在表单的初始状态)的原因Object.fromEntries是,随着条目的消费,每次在条目中遇到相同的名称时,都会导致覆盖之前的属性。一个对象只能有一个给定键的自有属性。

这并不意味着您永远不应该将它用于快速的表单到对象的映射。如果您正在使用一个简单的表单,其中所有字段都是单数文本值,那就没问题了。但是您可能需要一个更强大的解决方案来避免以后产生危险,以防有人添加确实需要映射到 JSON 中的其他表示的新字段。

如果创建一个更健壮的映射函数,Object.fromEntries仍然可能有用。您只需要首先执行从表单到其“json 条目”的映射(想想Object.fromEntries(getFormEntries(form)))。该FormData类型不仅不以对 JSON 有意义的方式对数组进行建模——它也不知道其他 JSON 类型,如 Number 和 Boolean。

中间的那个getFormEntries函数可以写成产生[ name, value ]对的生成器函数。派生值时,您可能会查看表单控件本身的类型,以确定如何在 JSON 中表示它们的值。例如,element.type === 'select-multiple'告诉您将 JSON 值基于element.selectedOptions, whileelement.type === 'number'element.type === 'range'建议您想要element.valueAsNumber而不是element.value.

// Pseudo code! — a real implementation would need to account
// for RadioNodeList/its children, deciding whether checkboxes
// are booleans, string-valued, or arrays, and lots of other
// things — but the gist is something like this:

function formToJSON(form) {
  return Object.fromEntries(getFormEntries(form));
}

function * getFormEntries(form) {
  for (let control of form.elements) {
    if (!control.disabled) {
      yield [ control.name, getFormControlValue(control) ];
    }
  }
}

function getFormControlValue(control) {
  switch (control.type) {
    case 'number':
      return control.valueAsNumber;
    case 'select-multiple':
      return Array.from(control.selectedOptions, option => option.value);
    /* ... */
    default:
      return control.value;
  }
}

您还希望排除disabled. 查看FormData构造算法可以在这里提供线索,因为您可能想要复制其规则来决定哪个表单控制模型可提交值 - 扭曲只是您想要以不同方式导出这些值的表示。

请注意,将表单值映射到 JSON 没有正确答案。例如,我们应该如何表示 a 的值<input type="date">?我们可能想使用element.value( "2021-02-09"),但也许我们更喜欢用element.valueAsDate.toISOString()( "2021-02-09T00:00:00.000Z") 来表示它。答案将取决于您的具体需求和您正在开发的合同。


推荐阅读