Skip to content

前端工作中的经验与教训

🕒 Published at:

关于表单元素 input 上的 v-model 绑定

一直以来我都有个错觉,认为在 input 等输入类型的表单元素上,v-model 指令绑定的是 value 属性和 change 事件,直到后来自己封装某个自定义指令的时候(需要手动触发一下双向绑定),使用 dispatch 分发 change 事件并没有触发绑定值的更新,遂去翻看文档,发现了以下内容。

image

看来有些事情还是不能想当然,多看文档总是对的。。。

总结: 官方文档中,input 元素上的 v-model 绑定的是 value 属性和 input 事件。

Axios 提交 Date 类型数据时的时区自动变更问题

问题描述

在使用 Axios 提交 Date 类型的数据时,发现请求体中的时间总是与代码中的时间不一致,这使我非常困惑。这个问题是在某次改项目中的 bug 时发现的,排查了很久。

根本原因

Axios 对于提交的 Date 类型的数据,会将其序列化为时间字符串。 这里涉及到 JavaScript 语言在序列化 Date 对象时的默认行为:

JavaScript 语言在序列化 Date 对象时(如通过 JSON.stringify),会调用 Date.prototype.toISOString() 方法,将日期转换为 ISO 8601 格式(如 2025-05-04T01:00:00.000Z)。其中的 Z 表示 UTC 时间(0 时区),而本地时间(如东八区)会被自动转换为 UTC 时间(减去 8 小时)。如图:

image

为什么要这样做?JavaScript 语言这样做的合理性: Date 对象的 ISO 序列化是为了标准化时间表示,便于跨系统传输。UTC 时间是国际通用标准,因此 JavaScript 的行为是合理的。

解决方案

  1. 不要直接提交 Date 类型的数据,而是将 Date 对象转换为本地时间字符串,然后再提交。
  2. 后端进行时区处理

关于使用 CSS order 属性在不改变 dom 结构的情况下改变元素的显示顺序

是这样的,最近我在维护一个项目的时候遇到这样一个需求,页面上有一个元素,它的内部有两个子元素,它们的dom结构从上到下是 A -> B,现在我希望让 B 显示在父盒子的左侧, A 显示在右侧,并且 A 的宽度是父盒子的宽度减去B的宽度(也就是 A 要自适应剩余的宽度)

这种情况下使用 flex 布局 + order 属性 正好可以解决这个问题。

html
<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="UTF-8" />
		<meta
			name="viewport"
			content="width=device-width, initial-scale=1.0"
		/>
		<title>Document</title>
		<style>
			.container {
				display: flex;
				width: 300px;
				color: #fff;
			}
			.item1 {
				order: 2;
				flex: 1;
				background-color: blue;
			}
			.item2 {
				order: 1;
				width: 50px;
				background-color: red;
			}
		</style>
	</head>
	<body>
		<div class="container">
			<div class="item1">item1</div>
			<div class="item2">item2</div>
		</div>
	</body>
</html>
image

🔗查看 mdn 关于 order 属性的描述

关于 重复创建 Vue 应用时报错 Cannot read properties of null (reading 'nextSibling') 的问题

问题描述

在新项目测试发版阶段,页面控制台频繁抛出 Cannot read properties of null (reading 'nextSibling') 错误。

  • 错误栈定位到 Vue 内部的 removeFragment 函数,具体触发点是 hostNextSibling(cur) 调用(该函数内部本质是访问 cur.nextSibling);
  • 错误仅在 “服务端修改前端打包产物后” 出现,本地开发环境未复现;
  • 排查发现,页面中 #app 容器被重复挂载了 Vue 应用实例,且第二次挂载时会触发第一次应用的卸载流程,报错发生在卸载流程的 Fragment 节点清理阶段。

根本原因

1. 错误触发的技术链路

Vue 中 “重复挂载应用到同一 DOM 元素” 会触发 「先卸载旧应用、再挂载新应用」 的逻辑,而报错的核心是旧应用卸载时 removeFragment 函数的遍历逻辑异常:

  • removeFragment 函数的作用: 清理 Fragment 对应的连续 DOM 片段,从起始节点 cur(通常是 Fragment 子节点的 el)到终止节点 end(通常是 Fragment 的 anchor 锚点),通过 while (cur !== end) 来控制循环边界;
    javascript
    const removeFragment = (cur, end) => {
    	let next
    	// 边界控制
    	while (cur !== end) {
    		next = hostNextSibling(cur) // 报错触发点
    		hostRemove(cur)
    		cur = next
    	}
    	hostRemove(end)
    }
  • 异常场景: 由于旧应用的虚拟 DOM(n1)记录的 cur/end 与真实 DOM 状态脱节(如 end 已被提前移除、cur 遍历链断裂),导致 cur 永远无法等于 end,循环持续执行直到 cur 变为 null,此时访问 cur.nextSibling 直接报错。

2. 重复创建应用的根源

重复创建应用的本质是 Vue 入口文件 index.js 被浏览器重复加载并执行,而诱因是服务端的缓存逻辑配置不当:

  • 服务端为了缓存控制,会在 index.html 前端打包产物的 index.js URL 后添加随机参数(如 ?v=xxxx),用于强制浏览器更新新版本资源;

  • 但项目中存在「代码分割(Code Splitting)」或「异步加载逻辑」,拆分出的子 chunk(如 chunk-xxx.js)可能会引用 index.js

  • 浏览器会将不同参数的 index.js判定为不同资源 ,重复下载并执行 —— 而 index.js 中包含 createApp(App).use(router).mount('#app') 的入口逻辑,最终导致同一页面创建并挂载多个 Vue 应用实例。

    image

解决方案

  1. 避免重复创建 Vue 应用,必要时先进行卸载再进行挂载。(服务端取消缓存控制逻辑或前端应用在挂载前先进行卸载)
  2. 如果重复创建了 Vue 应用,且又没有先进行卸载,那么要避免根组件中可能出现的 Fragment 情况,比如: 书写多个根节点,或者组件内书写多个根节点等。

最后更新于: