Skip to content

Vue 2 项目里几类看起来像框架 Bug 的问题 ​

这些问题表面上都是“组件坏了”或“数据不对”。分开看,它们分别落在 JavaScript 数值、响应式边界、组件复用、样式作用域和浏览器安全策略上。

大整数在 JSON 里已经丢精度 ​

后端的 long 如果作为 JSON 数字返回,浏览器抓包里看到的文本可能还是完整的。真正丢精度发生在 JSON.parse:JavaScript 的安全整数范围是 Number.MAX_SAFE_INTEGER,也就是 2^53 - 1。超出之后,末尾会被舍入。

这不是表格渲染时截断的。修复要放在解析之前,例如让接口把 ID 传成字符串,或使用能把大整数保留为字符串的解析器。渲染层再怎么格式化,也拿不回已经丢掉的位。

后加的属性不会自动变成响应式 ​

Vue 2 用 Object.defineProperty 劫持初始化时已经存在的字段。渲染之后再给对象加一个新字段,依赖收集时没有这个字段,赋值也不会触发更新。

要么在 data 里先声明它,要么使用 this.$set(target, key, value)。Vue 3 的 reactive 基于 Proxy,新增属性可以被监听到,但“始终通过响应式代理访问状态”仍然成立:拿到原始对象再改,视图还是不知道。

同一个限制也出现在初始化数据上。父组件稍后更新的 prop,子组件能收到新值,但子组件 data() 里那次 localValue: this.value 不会重新执行。需要跟着变的本地状态,应放进 watch,而不是指望 created 再跑一次。

计算属性如果和 data 同名,计算属性不会生效 ​

data 和 computed 都往组件实例上挂属性。同名时,初始化数据会占住这个名字,计算函数不会按预期执行。名字要先分开,再谈缓存和依赖。

侦听器还会按登记顺序执行。前一个侦听器如果改了另一个侦听器正在看的值,后一个会在同一轮里接着跑。这不是死循环检测失效,而是联动本身就写在了侦听器里。能派生出来的值,优先放进计算属性。

同类型组件被 v-if 切换时,实例可能被复用 ​

两个可编辑表格如果都是同一个组件,只用一个布尔值切换显示,第二个表格可能继承第一个的列缓存、滚动位置或内部状态,操作按钮也会表现异常。Vue 的补丁会尽量复用相同类型的节点。

给两个分支不同的 key,让它们成为两个实例。只是把 v-if 改成另一个初始为空的值,有时看起来“好了”,其实只是避开了同一次复用,没有把实例边界说清楚。

路由也有类似的缓存。同一个组件通过不同参数再次进入时,data 和 created 不一定重新执行。要按参数重新请求,应该 watch $route,而不是假设每次跳转都是一次新的创建。

scoped 样式管不到子组件内部的行 ​

给 <style scoped> 之后,选择器只会匹配当前模板上带有该组件作用域标记的元素。表格的行是表格组件内部渲染的,rowClassName 返回的类名挂在那些内部节点上,父组件的 scoped 选择器对不上。

需要作用到子组件内部时,使用深度选择器。列宽、列顺序异常有时也不是表格算法坏了:可选列功能会缓存另一份列配置,改了主表格的 columns 之后,要让那份缓存按新列重置。

上传时要传 File,而不是文件名 ​

<input type="file"> 给脚本的是 File 元数据。出于安全考虑,页面拿不到用户磁盘上的真实路径,也不能直接读任意本地文件。内容要通过 FileReader 或表单里的二进制字段读取。

组件库的上传列表经常在包装对象上放 url、thumbUrl 和 originFileObj。发给后端的是 originFileObj 这个原始 File。HTTP 请求体里传的是它的字节,后端再把它当成文件流处理。把预览地址传上去,服务器拿到的只是一段字符串。

异步回调里的 window.open 会被拦截 ​

下载或预览如果先等接口,再在异步回调里 window.open,浏览器会当成非用户手势触发的弹窗拦掉。延迟几毫秒、或用脚本创建 <a> 再点击,通常也过不了这个判断。

还在点击事件的同步调用栈里时,可以先打开空白页,请求完成后再改它的地址。用表单提交打开新页面,也能保留用户触发的上下文。

文件地址本身若要在页面里预览,接口应返回文件流,再做成 blob URL 赋给 iframe 或 object。直接把跨域文件地址塞给标签,浏览器的下载策略和拦截策略会各自生效,表现会随浏览器变化。

内容均为个人学习与公开总结,不含未脱敏的内部资料。