由一次排错想到的
写多了 vue 和 react,有时候会忘了原生的一些基础知识。
之前做的一个带文件上传功能的模块,后期由其他同事维护,一次修改后出现一个 bug:该功能模块某项可以上传两份文件,假设这两个文件为 A、B,上传 A 文件需要验证,B 文件并不需要验证,结果发现 A 文件上传后文件可以正常显示在列表里,组件的state
里却没有该文件的 url,而 B 文件上传后的url
不会从state
里莫名消失。
排查过程:
表单组件使用的是element-ui
的Upload
组件,
我们试过修改各个文件在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 官方文档,看看对于事件处理是怎么说的:
以下将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>
简单来说,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 可不要搞混淆了。