Skip to main content

由一次排错想到的

· 5 min read

写多了 vue 和 react,有时候会忘了原生的一些基础知识。

前期提要

之前做的一个带文件上传功能的模块,后期由其他同事维护,一次修改后出现一个 bug:该功能模块某项可以上传两份文件,假设这两个文件为 A、B,上传 A 文件需要验证,B 文件并不需要验证,结果发现 A 文件上传后文件可以正常显示在列表里,组件的state里却没有该文件的 url,而 B 文件上传后的url不会从state里莫名消失。

排查过程:

表单组件使用的是element-uiUpload组件, 我们试过修改各个文件在state中对应的字段,以及开启debug,都无果,最后同事发现是代码中某行写法不同导致的问题,改正后问题解决,代码大致如下:

example-1
<!-- vue-template,handleRemoveFile的任务是清空某项文件url -->
<!-- A -->
<el-upload :on-remove="handleRemoveFile('A')" />
<!-- B -->
<el-upload :on-remove="() => handleRemoveFile('B')" />

看出不同了吗,其中 A 的on-remove属性传递的是 js 语句,而 B 则传递了一个箭头函数

同事发出了灵魂拷问:为什么我这么写,它们的效果居然一样?

example-2
<!-- vue-template -->
<button @click="handleClick('A')">A</button>

<button @click="() => handleClick('B')">B</button>

<button @click="handleClick">C</button>

说实话,一开始我也懵了,这三种写法不一样,为什么效果却是一样的,都是在点击按钮后触发handleClick事件,除了传递给触发函数的参数不一样。这里我们打开 vue 官方文档,看看对于事件处理是怎么说的:

info

以下将v-on:click一律简写为@click

  • 可以用 v-on 指令监听 DOM 事件,并在触发时运行一些 JavaScript 代码。

    <button @click="counter += 1">Add 1</button>
  • 然而许多事件处理逻辑会更为复杂,所以直接把 JavaScript 代码写在 v-on 指令中是不可行的。因此 v-on 还可以接收一个需要调用的方法名称。

    <button @click="greet">Greet</button>
  • 除了直接绑定到一个方法,也可以在内联 JavaScript 语句中调用方法:

    <button @click="say('hi')">Say hi</button>
tip

简单来说,vue 模板引擎针对事件绑定做了处理,可以直接传一个函数名、一个函数表达式,或者函数调用式。

而在example-2中,我们的写法分别是 js 语句、函数表达式和函数名,所以效果是一样的,都只会在点击时触发。

这时我突然想到那在纯 html 中,是不是也可以这样?于是:

<button onclick="handleClick">click me</button>

<script>
function handleClick() {
alert("hello")
}
</script>

很不幸,这样不行,你必须写成

<button onclick="handleClick()">click me</button>

因为 html 内联事件只接受 js 语句,只有在 js 里绑定事件时才可以传函数名或者函数表达式:

const click = () => alert("hello")
// ok
document.body.onclick = click
// also ok
document.body.onclick = () => alert("hello")
// or
document.body.addEventListener("click", click)
// or this
document.body.addEventListener("click", () => alert("hello"))

论事件绑定的四种写法,回到example-1,这里是在绑定props,A 的写法会在属性绑定时就调用删除函数,传递过去的其实是函数返回值。

切记,vue 里事件绑定和属性传递的 syntax 可不要搞混淆了。