[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"i-ri:search-line":3,"i-ri:moon-line":8,"i-ri:sun-line":10,"i-ri:menu-3-line":12,"i-ri:close-line":14,"notes-module-items-frontend":16,"notes-page-frontend-01-forms\u002F01-clear-semantics":42,"i-ri:arrow-left-s-line":389,"i-ri:menu-2-line":391,"i-ri:arrow-right-s-line":393},{"left":4,"top":4,"width":5,"height":5,"rotate":4,"vFlip":6,"hFlip":6,"body":7},0,24,false,"\u003Cpath fill=\"currentColor\" d=\"m18.031 16.617l4.283 4.282l-1.415 1.415l-4.282-4.283A8.96 8.96 0 0 1 11 20c-4.968 0-9-4.032-9-9s4.032-9 9-9s9 4.032 9 9a8.96 8.96 0 0 1-1.969 5.617m-2.006-.742A6.98 6.98 0 0 0 18 11c0-3.867-3.133-7-7-7s-7 3.133-7 7s3.133 7 7 7a6.98 6.98 0 0 0 4.875-1.975z\"\u002F>",{"left":4,"top":4,"width":5,"height":5,"rotate":4,"vFlip":6,"hFlip":6,"body":9},"\u003Cpath fill=\"currentColor\" d=\"M10 7a7 7 0 0 0 12 4.9v.1c0 5.523-4.477 10-10 10S2 17.523 2 12S6.477 2 12 2h.1A6.98 6.98 0 0 0 10 7m-6 5a8 8 0 0 0 15.062 3.762A9 9 0 0 1 8.238 4.938A8 8 0 0 0 4 12\"\u002F>",{"left":4,"top":4,"width":5,"height":5,"rotate":4,"vFlip":6,"hFlip":6,"body":11},"\u003Cpath fill=\"currentColor\" d=\"M12 18a6 6 0 1 1 0-12a6 6 0 0 1 0 12m0-2a4 4 0 1 0 0-8a4 4 0 0 0 0 8M11 1h2v3h-2zm0 19h2v3h-2zM3.515 4.929l1.414-1.414L7.05 5.636L5.636 7.05zM16.95 18.364l1.414-1.414l2.121 2.121l-1.414 1.414zm2.121-14.85l1.414 1.415l-2.121 2.121l-1.414-1.414zM5.636 16.95l1.414 1.414l-2.121 2.121l-1.414-1.414zM23 11v2h-3v-2zM4 11v2H1v-2z\"\u002F>",{"left":4,"top":4,"width":5,"height":5,"rotate":4,"vFlip":6,"hFlip":6,"body":13},"\u003Cpath fill=\"currentColor\" d=\"M3 4h18v2H3zm6 7h12v2H9zm-6 7h18v2H3z\"\u002F>",{"left":4,"top":4,"width":5,"height":5,"rotate":4,"vFlip":6,"hFlip":6,"body":15},"\u003Cpath fill=\"currentColor\" d=\"m12 10.587l4.95-4.95l1.414 1.414l-4.95 4.95l4.95 4.95l-1.415 1.414l-4.95-4.95l-4.949 4.95l-1.414-1.415l4.95-4.95l-4.95-4.95L7.05 5.638z\"\u002F>",[17,22,26,30,34,38],{"path":18,"title":19,"meta":20},"\u002Fnotes\u002Ffrontend\u002F01-forms\u002F01-clear-semantics","表单清空后，旧数据为什么又回来了？",{"date":-1,"tags":-1,"summary":-1,"lastUpdated":21},"2026-11-05T09:47:15.000Z",{"path":23,"title":24,"meta":25},"\u002Fnotes\u002Ffrontend\u002F01-forms\u002F02-async-persistence","保存请求回来时，文档已经变了",{"date":-1,"tags":-1,"summary":-1,"lastUpdated":21},{"path":27,"title":28,"meta":29},"\u002Fnotes\u002Ffrontend\u002F02-canvas\u002F01-geometry-history","拖一下画布，为什么撤销回不去了？",{"date":-1,"tags":-1,"summary":-1,"lastUpdated":21},{"path":31,"title":32,"meta":33},"\u002Fnotes\u002Ffrontend\u002F03-printing\u002F01-pagination-fonts","预览正常，PDF 为何多一页或缺字？",{"date":-1,"tags":-1,"summary":-1,"lastUpdated":21},{"path":35,"title":36,"meta":37},"\u002Fnotes\u002Ffrontend\u002F04-web-delivery\u002F01-cache-diagnosis","API 更新了，博客页面为什么还是旧的？",{"date":-1,"tags":-1,"summary":-1,"lastUpdated":21},{"path":39,"title":40,"meta":41},"\u002Fnotes\u002Ffrontend","前端与 Web 工程",{"date":-1,"tags":-1,"summary":-1,"lastUpdated":21},{"id":43,"type":44,"sourcePath":45,"routePath":18,"path":18,"title":19,"description":46,"updatedAt":21,"meta":47,"body":56,"toc":381},"bm90ZTpub3Rlcy9mcm9udGVuZC8wMS1mb3Jtcy8wMS1jbGVhci1zZW1hbnRpY3MubWQ","note","notes\u002Ffrontend\u002F01-forms\u002F01-clear-semantics.md","编辑已有资料时，删掉最后一行电话号码，保存成功，再打开，号码却又回来了。输入框看着是空的，服务端收到的请求和页面保存后的状态，未必也空了。",{"id":43,"type":44,"sourcePath":45,"slug":48,"title":19,"updatedAt":21,"description":46,"tags":49,"date":53,"comment":54,"summary":55,"lastUpdated":21},"frontend\u002F01-forms\u002F01-clear-semantics",[50,51,52],"frontend","表单","数据建模","2026-07-23",true,"清空操作可能丢在提交转换，也可能丢在详情回填。区分没改、明确清空和非法值，再追踪空数组经过的每一层。",{"type":57,"children":58},"root",[59,67,72,77,84,89,127,132,275,280,286,299,311,323,329,350,355,360,366,371,376],{"type":60,"tag":61,"props":62,"children":64},"element","h1",{"id":63},"表单清空后旧数据为什么又回来了",[65],{"type":66,"value":19},"text",{"type":60,"tag":68,"props":69,"children":70},"p",{},[71],{"type":66,"value":46},{"type":60,"tag":68,"props":73,"children":74},{},[75],{"type":66,"value":76},"这个问题出现在一套配置式表单中。界面字段需要转换成接口数据，详情返回后再反向转换。一个表格控件对应请求里的一个数组，删除行的意图可能在任意一段转换中丢失。",{"type":60,"tag":78,"props":79,"children":81},"h2",{"id":80},"为什么跳过空值会把旧值留下",[82],{"type":66,"value":83},"为什么跳过空值，会把旧值留下？",{"type":60,"tag":68,"props":85,"children":86},{},[87],{"type":66,"value":88},"更新逻辑先复制原详情，再用表单值覆盖。这个办法能保留用户没有编辑的字段，但如果转换时遇到空字符串就跳过，旧值也会被保留。",{"type":60,"tag":68,"props":90,"children":91},{},[92,94,101,103,109,111,117,119,125],{"type":66,"value":93},"例如原详情是 ",{"type":60,"tag":95,"props":96,"children":98},"code",{"className":97},[],[99],{"type":66,"value":100},"displayName: '旧名称'",{"type":66,"value":102},"，用户清空以后，表单值变成 ",{"type":60,"tag":95,"props":104,"children":106},{"className":105},[],[107],{"type":66,"value":108},"''",{"type":66,"value":110},"。转换器不覆盖这个字段，请求就仍带着“旧名称”。对 ",{"type":60,"tag":95,"props":112,"children":114},{"className":113},[],[115],{"type":66,"value":116},"false",{"type":66,"value":118},"、",{"type":60,"tag":95,"props":120,"children":122},{"className":121},[],[123],{"type":66,"value":124},"0",{"type":66,"value":126}," 使用同样的真假判断，还会误删正常值。",{"type":60,"tag":68,"props":128,"children":129},{},[130],{"type":66,"value":131},"可选的处理方式有逐字段生成更新请求，或在旧详情上覆盖明确提交的字段。无论选哪种，都要先定义值的含义：",{"type":60,"tag":133,"props":134,"children":135},"table",{},[136,155],{"type":60,"tag":137,"props":138,"children":139},"thead",{},[140],{"type":60,"tag":141,"props":142,"children":143},"tr",{},[144,150],{"type":60,"tag":145,"props":146,"children":147},"th",{},[148],{"type":66,"value":149},"输入",{"type":60,"tag":145,"props":151,"children":152},{},[153],{"type":66,"value":154},"应当区分的意图",{"type":60,"tag":156,"props":157,"children":158},"tbody",{},[159,173,189,206,223,245,262],{"type":60,"tag":141,"props":160,"children":161},{},[162,168],{"type":60,"tag":163,"props":164,"children":165},"td",{},[166],{"type":66,"value":167},"没有这个 key",{"type":60,"tag":163,"props":169,"children":170},{},[171],{"type":66,"value":172},"没编辑，或控件没有挂载",{"type":60,"tag":141,"props":174,"children":175},{},[176,184],{"type":60,"tag":163,"props":177,"children":178},{},[179],{"type":60,"tag":95,"props":180,"children":182},{"className":181},[],[183],{"type":66,"value":108},{"type":60,"tag":163,"props":185,"children":186},{},[187],{"type":66,"value":188},"清空文本",{"type":60,"tag":141,"props":190,"children":191},{},[192,201],{"type":60,"tag":163,"props":193,"children":194},{},[195],{"type":60,"tag":95,"props":196,"children":198},{"className":197},[],[199],{"type":66,"value":200},"null",{"type":60,"tag":163,"props":202,"children":203},{},[204],{"type":66,"value":205},"明确置空可空字段",{"type":60,"tag":141,"props":207,"children":208},{},[209,218],{"type":60,"tag":163,"props":210,"children":211},{},[212],{"type":60,"tag":95,"props":213,"children":215},{"className":214},[],[216],{"type":66,"value":217},"[]",{"type":60,"tag":163,"props":219,"children":220},{},[221],{"type":66,"value":222},"删除所有表格行",{"type":60,"tag":141,"props":224,"children":225},{},[226,240],{"type":60,"tag":163,"props":227,"children":228},{},[229,234,235],{"type":60,"tag":95,"props":230,"children":232},{"className":231},[],[233],{"type":66,"value":116},{"type":66,"value":118},{"type":60,"tag":95,"props":236,"children":238},{"className":237},[],[239],{"type":66,"value":124},{"type":60,"tag":163,"props":241,"children":242},{},[243],{"type":66,"value":244},"正常值",{"type":60,"tag":141,"props":246,"children":247},{},[248,257],{"type":60,"tag":163,"props":249,"children":250},{},[251],{"type":60,"tag":95,"props":252,"children":254},{"className":253},[],[255],{"type":66,"value":256},"undefined",{"type":60,"tag":163,"props":258,"children":259},{},[260],{"type":66,"value":261},"结合字段是否隐藏、是否挂载判断；JSON 不保留它",{"type":60,"tag":141,"props":263,"children":264},{},[265,270],{"type":60,"tag":163,"props":266,"children":267},{},[268],{"type":66,"value":269},"非法日期或错误类型",{"type":60,"tag":163,"props":271,"children":272},{},[273],{"type":66,"value":274},"校验失败，不能当作清空",{"type":60,"tag":68,"props":276,"children":277},{},[278],{"type":66,"value":279},"项目保留了更新时合并原详情的方式，但先检查 key 是否存在，再按字段规则处理值。可见字段的空字符串可以覆盖旧值，允许置空的情况转换成 JSON 能提交的 null；隐藏控件未挂载产生的 undefined，则不能顺手清掉服务端数据。创建没有旧详情，部分空值仍可以省略。",{"type":60,"tag":78,"props":281,"children":283},{"id":282},"用户删光了行为什么提交值里没有空数组",[284],{"type":66,"value":285},"用户删光了行，为什么提交值里没有空数组？",{"type":60,"tag":68,"props":287,"children":288},{},[289,291,297],{"type":66,"value":290},"表单库的变更事件能报告 ",{"type":60,"tag":95,"props":292,"children":294},{"className":293},[],[295],{"type":66,"value":296},"{ phonesField: [] }",{"type":66,"value":298},"，完整表单快照却可能过滤空数组，连 key 都不留下。提交转换器只看快照，就会把“全部删除”理解成“没有修改”。",{"type":60,"tag":68,"props":300,"children":301},{},[302,304,309],{"type":66,"value":303},"一种办法是调整表单库的取值规则；另一种是单独保留明确的删除意图。项目采用后者，记录当前可见、可编辑、允许整体清空的表格字段，生成提交快照时补回 ",{"type":60,"tag":95,"props":305,"children":307},{"className":306},[],[308],{"type":66,"value":217},{"type":66,"value":310},"。",{"type":60,"tag":68,"props":312,"children":313},{},[314,316,321],{"type":66,"value":315},"这个记录属于本次编辑。重新加入行后要撤销，切换记录、重置详情或换表单时也要清掉。测试时，变更事件应带 ",{"type":60,"tag":95,"props":317,"children":319},{"className":318},[],[320],{"type":66,"value":217},{"type":66,"value":322},"，完整快照应故意缺 key，才能复现两份值不一致的情况。",{"type":60,"tag":78,"props":324,"children":326},{"id":325},"请求已经清空页面为什么还显示旧行",[327],{"type":66,"value":328},"请求已经清空，页面为什么还显示旧行？",{"type":60,"tag":68,"props":330,"children":331},{},[332,334,340,342,348],{"type":66,"value":333},"还要看反向回填。服务端返回 ",{"type":60,"tag":95,"props":335,"children":337},{"className":336},[],[338],{"type":66,"value":339},"phones: []",{"type":66,"value":341},"，详情转换器却只处理非空数组，最后得到 ",{"type":60,"tag":95,"props":343,"children":345},{"className":344},[],[346],{"type":66,"value":347},"{}",{"type":66,"value":349},"。表单的赋值方法如果采用合并更新，缺少这个字段就会继续保留旧行。",{"type":60,"tag":68,"props":351,"children":352},{},[353],{"type":66,"value":354},"项目因此在详情转换和通用行映射中都保留空数组：响应明确为空，就产生对应的空表格值，覆盖旧行；响应没有这个字段，则不由它修改当前值。只有非法脏行的响应也要单独处理，不能伪装成用户清空。",{"type":60,"tag":68,"props":356,"children":357},{},[358],{"type":66,"value":359},"这样，同样的空数组在提交和回填两侧都有明确含义。",{"type":60,"tag":78,"props":361,"children":363},{"id":362},"删除中间行时未展示的属性怎么保留",[364],{"type":66,"value":365},"删除中间行时，未展示的属性怎么保留？",{"type":60,"tag":68,"props":367,"children":368},{},[369],{"type":66,"value":370},"表格通常只展示原始记录的一部分。合并更新时若按行下标找旧数据，删除中间一行后，后面的隐藏属性就会配错行。",{"type":60,"tag":68,"props":372,"children":373},{},[374],{"type":66,"value":375},"项目为加载进来的每行附带来源标识，优先使用原记录 ID，没有 ID 时使用本次加载的位置。编辑后凭这个标识找回原始行，再按写请求白名单保留允许提交的属性，最后去掉内部标识和只读字段。",{"type":60,"tag":68,"props":377,"children":378},{},[379],{"type":66,"value":380},"位置标识只适用于本次加载基线，跨刷新或服务端重排需要稳定的行 ID 或版本信息。",{"title":382,"searchDepth":383,"depth":383,"links":384},"",2,[385,386,387,388],{"id":80,"depth":383,"text":83},{"id":282,"depth":383,"text":285},{"id":325,"depth":383,"text":328},{"id":362,"depth":383,"text":365},{"left":4,"top":4,"width":5,"height":5,"rotate":4,"vFlip":6,"hFlip":6,"body":390},"\u003Cpath fill=\"currentColor\" d=\"m10.828 12l4.95 4.95l-1.414 1.415L8 12l6.364-6.364l1.414 1.414z\"\u002F>",{"left":4,"top":4,"width":5,"height":5,"rotate":4,"vFlip":6,"hFlip":6,"body":392},"\u003Cpath fill=\"currentColor\" d=\"M3 4h18v2H3zm0 7h12v2H3zm0 7h18v2H3z\"\u002F>",{"left":4,"top":4,"width":5,"height":5,"rotate":4,"vFlip":6,"hFlip":6,"body":394},"\u003Cpath fill=\"currentColor\" d=\"m13.172 12l-4.95-4.95l1.414-1.413L16 12l-6.364 6.364l-1.414-1.415z\"\u002F>"]