news 2026/10/16 3:44:38

Vue事件修饰符完全指南:从冒泡拦截到组合键实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Vue事件修饰符完全指南:从冒泡拦截到组合键实战

做前端开发做得久了,你会越来越清楚地意识到一件事:凡是跟用户交互沾边的页面,里面藏的都是事件。鼠标点一下、键盘敲一下、表单提交一下、窗口滚动一下,背后全是浏览器抛出来的事件。Vue 的事件处理机制看起来非常“轻”——模板里写个@click="handleClick"就算上手了,可一旦你真去做快捷键、弹窗关闭、冒泡拦截这种复杂交互,基础写法很快就会捉襟见肘。真正好用的其实是修饰符,它们能把原本散落在事件回调里的 DOM 操作,压缩成模板里几个短小的关键字。这篇内容会从 Vue 事件处理的执行原理聊起,接着把修饰符体系逐类拆开,再用一个命令面板的例子把它们串起来,最后总结一些我一路踩过的坑。适合那些已经能写基础组件、想让交互更稳更精的开发者。

1. 事件处理的底层逻辑:模板里的 @click 不是黑魔法

1.1 从模板表达式到原生监听器

很多人学 Vue 事件处理,第一反应就是记语法:@click是简写,v-on是完整写法。但如果你只知道这个,遇到复杂场景就会很被动。Vue 模板里的v-on指令,在编译阶段会被转换成对原生事件监听器的注册逻辑——通俗点说,你写在模板里的@click,最终在运行时就是addEventListener('click', handler)。只不过这个过程由 Vue 帮你管理了,组件卸载时监听器也会跟着移除,省去了一堆手动清理的样板代码。

既然是原生事件,那事件对象、事件传播路径、默认行为这些浏览器特性,在 Vue 里全部保留。你写的内联语句也存在,比如:

<button v-on:click="count++">增加</button> <button @click="count++">增加</button>

v-on后面的参数是事件名,等号后面跟的是处理表达式。这个表达式可以是内联语句、方法引用,也可以是方法调用。这里有个经常被忽略的细节:如果你写的是方法引用,比如@click="handler",Vue 会把原生event对象作为第一个参数自动传给handler;但如果你写成方法调用@click="handler()",事件对象不会自动传进去,你需要显式写@click="handler($event)"。这个设计让模板看起来简单,却也埋下了不少新手翻车的雷。

顺便提一句,同一个元素上可以绑定多个事件,也可以绑定不同阶段的事件。Vue 还提供了.capture、.once这类修饰符来控制监听行为,这些都是直接作用在事件监听层面的。理解这一点之后,你再看下面的修饰符会轻松很多,因为它们本质上就是addEventListener配置项和事件对象操作的模板化。

1.2 方法处理器与参数传递的规则

实际项目里,处理逻辑没法像count++这么简单,大部分时候会拆成 methods 或者 setup 里的普通函数。在 Vue 2 的选项式 API 中,methods 里的函数this指向组件实例,所以你可以随手this.list = ...。到了 Vue 3,如果你用<script setup>写组合式代码,事件处理函数就是普通函数,里面已经没有组件实例this可以参考了,得改成操作响应式变量。

这是很多从 Vue 2 迁移过来的老手第一个骂人的地方。Vue 3 模板里的@click="handler"依然能正常工作,但handler内部如果再写this.xxx,拿到的不是组件,大概率是undefined。所以组合式 API 的正确做法是:函数需要什么数据,就通过闭包引用什么响应式变量,比如:

const count = ref(0) function addCount() { count.value++ }

模板上写@click="addCount"就行。函数引用和调用带括号的区别也要记牢,否则事件对象会丢失。比如@input="handleChange",你就能在handleChange里拿到event.target.value;但你要是在模板里写@input="handleChange()",event就没了,需要改成@input="handleChange($event)"。

多参数场景更典型,比如一个列表项的点击事件,既要知道点了哪一项,又需要事件对象做额外判断,模板就得写成:

<li @click="selectItem(item, $event)">选择</li>

item来自循环作用域,$event代表原生事件对象。这样事件对象的作用就显得尤为重要。很多人理解 Vue 事件处理,只记住了@符号的写法,忽略了$event的存在,等到要和原生 DOM 打交道时,就不知道怎么把事件信息传进函数里了。

1.3 为什么事件对象不自动传,会让老手也翻车

这个问题在面试里出现频率非常高,问得也细:@click="handler"和@click="handler()"有区别吗?当然有,区别就是事件对象是否作为第一个参数自动传递。很多人觉得既然在模板里写函数调用,事件对象应该也能像方法引用一样自动带上。但 Vue 的设计不是这样,方法引用是 Vue 帮你包装了一层调用,方法调用是你自己发起的表达式执行,模板编译器没法替你做这个决定。

我曾见过一段代码,某个搜索框监听回车,模板写的是@keyup.enter="search()",结果search函数内部需要读取输入框的值,总是拿到undefined,排查了半天才发现问题是参数没传对。后来改成@keyup.enter="search($event)",问题立刻消失。

这个细节还引出一个编码习惯:如果函数内部不需要事件对象,用@click="handler"是最简洁的;如果需要传额外参数又需要事件对象,就明确写$event;如果只是内联做点赋值,直接用@click="count = 1"也没问题。保持形式统一,团队里少很多无意义的口角。

2. 修饰符体系拆解:真正让事件处理优雅起来的细节

2.1 控制传播与默认行为:stop、prevent、self、once

修饰符是 Vue 事件处理里最有价值的设计。它不是帮你多写一行代码,而是把常见的 DOM 操作沉淀成声明式语法。最常用的一组修饰符,围绕事件传播和默认行为展开。

.stop对应的是event.stopPropagation(),用来阻止事件继续向父级冒泡。典型场景是列表卡片里嵌了一个点赞按钮,你希望点按钮时只触发点赞,不触发整张卡片的点击跳转:

<div @click="goDetail"> <button @click.stop="likePost">点赞</button> </div>

如果不加.stop,点一下按钮,likePost执行完之后,事件还会冒泡到外层div,顺手把goDetail也触发了。这种交互在管理后台和内容列表里特别容易出问题。

.prevent对应的是event.preventDefault(),用来阻止浏览器默认行为。最典型的是表单提交:

<form @submit.prevent="handleSubmit">...</form>

没有.prevent,点提交按钮页面会被刷新,handleSubmit可能还没来得及把数据发出去,页面状态就已经没了。加了之后,默认的提交行为被拦下,函数可以安安稳稳做异步请求。

.self稍微绕一点,它判断的是event.target是不是当前绑定事件的元素。只有点到这个元素本身时,事件处理函数才执行,从子元素冒泡上来的事件一概忽略。比如弹窗遮罩:

<div class="mask" @click.self="closeDialog">...</div>

点击遮罩空白区域会关闭弹窗,点击弹窗内部的表单区域不会触发关闭,因为event.target是内部元素,不是遮罩元素。这个效果用.stop也能做,但.self更符合“只关心元素自身点击”的直觉。

.once更直白,事件只触发一次,之后自动解绑。某些一次性引导、一次性初始化的场景很实用。注意,.once在组件自定义事件上也能用。

2.2 键盘、系统按键与鼠标修饰符

键盘事件修饰符是另一个大头。你不需要在函数里写一长串if (event.key === 'Enter')这种判断,Vue 把常用按键都做成了别名。Enter、Tab、Delete、Esc、Space、Up、Down、Left、Right,直接拼在事件名后面:

<input @keyup.enter="submit"> <input @keyup.esc="reset">

这里要区分keydown和keyup。keydown在按键按下时就会触发,而且长按会连续触发多次;keyup在松开按键时触发一次。做快捷键交互,如果不想因为长按而重复执行,优先考虑keyup;但如果要尽早拦截默认行为,比如阻止 Tab 切换焦点,就得用keydown。

系统修饰键专门处理 Ctrl、Alt、Shift、Meta 这四类键。Meta 在 Windows 键盘上是 Win 键,在 macOS 上是 Command 键。典型写法:

<button @click.ctrl="selectItem">Ctrl + Click 选中</button>

这个写法表示点击时只要 Ctrl 处于按下状态就会触发,哪怕同时按着 Shift 或 Alt 也会触发。如果你希望“必须精确按某个组合才触发”,就要配合.exact:

<button @click.ctrl.exact="selectOnly">只按 Ctrl 点击</button> <button @click.exact="selectNone">不按任何系统键点击</button>

.exact的本质是精确匹配系统修饰键状态。@click.ctrl是“包含”,@click.ctrl.exact是“等于”。这种差异在快捷键系统里非常关键,不加.exact的 Ctrl+Enter 监听,很可能在用户按 Ctrl+Shift+Enter 时也错误触发。

鼠标修饰符方面,.left、.middle、.right分别对应左键、中键、右键。注意一个坑:click事件主要针对左键,右键点击很多时候不会触发click,会触发contextmenu,所以你想拦截右键菜单时不应该用@click.right,而应该用@contextmenu.prevent。如果只是想在按下时区分左右键,用@mousedown.left、@mousedown.right会更可靠。

2.3 组合键事件的关键:修饰符顺序与 exact

修饰符的组合顺序经常会让人踩坑,尤其同时用.prevent和.self时。Vue 官方文档明确提醒过:修饰符顺序会影响结果。比如:

  • @click.prevent.self,会阻止所有点击的默认行为;
  • @click.self.prevent,只会阻止点击元素自身的默认行为。

这个区别初看有点绕,但本质是代码生成顺序的问题。prevent在前面,意味着所有经过当前元素的 click 事件默认行为都会被拦下;self在前面,先判断 target 是否是自己,再决定要不要阻止默认行为。为了保证可读性,建议一个事件上不要叠超过两个修饰符,超过的话拆到函数里处理。

组合键的经典例子是编辑器里的保存:

<textarea @keydown.ctrl.enter.prevent="sendMessage" @keydown.enter.exact.prevent="insertNewline" >...</textarea>

普通 Enter 会触发insertNewline,Ctrl+Enter 会触发sendMessage,而且互不干扰。如果第一个监听不写.exact,那么 Ctrl+Enter 也会匹配@keydown.enter,导致两个函数都被执行,消息还是会误发。这类问题在真实项目里太常见了,值得记一下。

2.4 与 v-model 搭配的表单修饰符:lazy、number、trim

严格来说,这几个修饰符不完全属于事件修饰符分类,但它们确实在控制事件同步的时机和输入数据格式,所以经常被放在一起讨论。.lazy会把v-model的默认同步从input事件改成change事件,也就是说输入框内容不再每次击键都更新,而是等失焦或者回车之后才更新。对长文本、搜索框来说,能减少很多无谓的响应式更新。

.number会把输入值尝试转成数字,不能用parseFloat转的情况会保留原始字符串。.trim则自动去掉首尾空格。这三者可以组合:

<input v-model.trim="username" /> <input v-model.number="age" /> <input v-model.lazy="description" />

注意,.number不等于给<input>设置type="number",它是对变量值做类型转换。如果你同时设置type="number",原生输入框在某些浏览器上也可能给空字符串,处理逻辑要额外判断。

这几个修饰符非常适合放在表单提交场景里。配合事件修饰符,典型的提交按钮可以写成:

<form @submit.prevent="submitForm"> <input v-model.trim="keyword" @keydown.enter.prevent="submitForm" /> </form>

这样既阻止了表单提交的默认刷新,又统一了回车提交和按钮提交的入口。

3. 实操:从零搭一个快捷键命令面板

3.1 需求定义与整体设计

修饰符看起来零散,放在真实场景里才能体现价值。这里我做一个命令面板组件,功能如下:按快捷键打开面板,输入关键词过滤命令列表,上下方向键移动光标,回车执行当前命令,Ctrl+Enter 执行当前命令但保持面板打开,Esc 关闭面板,点击遮罩也能关闭。

这个需求最大的价值在于:它同时用到了键盘修饰符、组合键修饰符、鼠标修饰符,而且涉及全局事件监听和组件内事件监听的配合,是目前最常见的管理后台交互形态。

整体设计分两部分:全局快捷键负责打开面板,这个监听必须注册在window上,因为用户可能正在页面任意位置;面板内部的键盘导航则用模板修饰符解决,因为焦点在输入框上时,事件自然会派发到输入框。

实现我会用 Vue 3 的<script setup>组合式写法,配合ref、computed、nextTick等 API。

3.2 模板中的事件修饰符组合

先看模板部分:

<template> <div v-if="visible" class="command-mask" @click.self="closePanel"> <div class="command-panel"> <input ref="inputRef" v-model.trim="query" class="command-input" placeholder="输入命令关键词" @keydown.esc="closePanel" @keydown.down.prevent="moveCursor(1)" @keydown.up.prevent="moveCursor(-1)" @keydown.enter.exact.prevent="runCommand(false)" @keydown.ctrl.enter.prevent="runCommand(true)" /> <ul> <li v-for="(item, index) in filteredCommands" :key="item.id" :class="{ 'command-active': index === cursor }" @click="selectItem(item)" > {{ item.name }} </li> </ul> </div> </div> </template>

这里有几个值得拆解的细节。遮罩层的@click.self保证只有点击遮罩本身时才会关闭,点面板内部不会误关。输入框的@keydown.down.prevent和@keydown.up.prevent用.prevent拦掉方向键默认行为,否则按上下键时,输入框光标会在文本里移动,页面也可能滚动,导航体验会很怪。

@keydown.enter.exact.prevent是关键。加上.exact后,普通 Enter 只会触发runCommand(false),Ctrl+Enter 则不会同时匹配这个监听。这样 Ctrl+Enter 走第二条组合键逻辑,行为互不干扰。v-model.trim会去掉搜索词首尾空格,避免输入法或粘贴内容带来的脏数据。

3.3 全局快捷键与生命周期管理

模板里只能绑定组件 DOM 上的事件,全局快捷键需要自己注册到window。这段逻辑放到onMounted,并在onBeforeUnmount里解绑,避免组件卸载后监听器还留在全局,造成内存泄漏或者别的页面误触发:

<script setup> import { ref, computed, nextTick, onMounted, onBeforeUnmount } from 'vue' const visible = ref(false) const query = ref('') const cursor = ref(0) const inputRef = ref(null) const commands = [ { id: 1, name: '新建文档', action: () => console.log('create') }, { id: 2, name: '搜索项目', action: () => console.log('search') }, { id: 3, name: '发布版本', action: () => console.log('deploy') } ] const filteredCommands = computed(() => { const q = query.value.toLowerCase() if (!q) return commands return commands.filter((item) => item.name.toLowerCase().includes(q)) }) async function openPanel() { visible.value = true query.value = '' cursor.value = 0 await nextTick() inputRef.value?.focus() } function closePanel() { visible.value = false query.value = '' cursor.value = 0 } function moveCursor(delta) { const list = filteredCommands.value if (!list.length) return cursor.value = (cursor.value + delta + list.length) % list.length } function runCommand(keepOpen) { const item = filteredCommands.value[cursor.value] if (!item) return item.action() if (!keepOpen) closePanel() else { query.value = '' cursor.value = 0 inputRef.value?.focus() } } function selectItem(item) { item.action() closePanel() } function onGlobalKeydown(event) { if ( (event.ctrlKey || event.metaKey) && event.key.toLowerCase() === 'k' && !event.shiftKey && !event.altKey ) { event.preventDefault() openPanel() } } onMounted(() => { window.addEventListener('keydown', onGlobalKeydown) }) onBeforeUnmount(() => { window.removeEventListener('keydown', onGlobalKeydown) }) </script>

这段代码里,onGlobalKeydown同时监听 Ctrl 和 Meta,是为了兼容 Windows 和 Mac。注意我没有让它匹配 Shift 或 Alt,否则 Ctrl+Shift+K 也会打开面板,不够精确。你也可以改成event.ctrlKey && event.key === 'k' && event.shiftKey这类组合,具体视产品需求而定。

打开面板后要用nextTick再聚焦输入框,因为v-if刚把输入框渲染出来时,DOM 引用还没有建立,立刻focus往往没反应。这个问题我在实际项目中碰到过很多次,换成nextTick之后才算稳定。

3.4 为什么这里不需要事件委托

列表项用了v-for循环渲染,并且每项都绑定了@click。有同学会问,为什么不把点击事件委托到ul上,只绑定一次,顺便省性能?

Vue 的v-for本身在每个列表项上生成事件监听,DOM 数量少时完全没问题。命令面板的列表量级通常在几十条到几百条,逐项绑定并不会造成可感知的性能问题。真正应该考虑事件委托的,是那种动态插入、数量成千上万的节点,比如无限列表。命令面板这种交互场景,可读性和可维护性优先于极致的监听数量优化。

不过有一点要注意:如果commands后续通过异步接口加载,并且列表项组件会很复杂,那可以再抽象成一个子组件,把点击事件和样式封装在内部,避免模板越来越臃肿。修饰符在子组件里同样适用,但要注意自定义事件和原生事件的区分,这块放在下面的常见问题里讲。

4. 常见问题与排查技巧实录

4.1 .stop 不生效?先分清楚原生事件和组件自定义事件

.stop不生效,是我被问得最多的问题。其中一大半,问题不在于修饰符本身,而在于监听的根本不是原生 DOM 事件。

当你写<child-component @click.stop="handleClick">时,如果这个click是子组件通过$emit('click')抛出来的自定义事件,那.stop不会阻止任何 DOM 冒泡,因为它压根不在原生事件传播链路里。自定义事件是 Vue 自己实现的组件通信接口,它没有 DOM 级别的传播路径,自然也没有stopPropagation的概念。

正确做法是:处理原生事件逻辑放在子组件内部,比如在子组件的根元素上写@click.stop,把冒泡挡在子组件内部;然后子组件在合适时机向上$emit('confirm')之类的业务事件,父组件只负责监听业务事件,不做原生 DOM 层面的截断。这样层次清楚,排查问题也方便。

另一个.stop不生效的原因是捕获阶段事件在外层先执行了。如果外层元素用了.capture修饰符,事件会在捕获阶段先到达外层,你内层再怎么.stop,也只能停止后续冒泡,没法撤销已经执行过的捕获监听器。这种情况需要在设计阶段就统一事件监听阶段,别混用。

4.2 中文输入法下回车键被误触

中文输入法是一个非常典型的坑。用户在输入框里打中文,按回车选词时,浏览器会触发keydown,这个事件的event.key在某些浏览器里就是"Enter",于是你监听的回车提交就误触发了,消息没打完就被发出去了。

Vue 的@keyup.enter没有内置处理输入法合成事件的能力,你得在事件处理函数里手动判断:

function handleEnter(event) { if (event.isComposing || event.keyCode === 229) return submit() }

event.isComposing为true表示当前正处于输入法组合状态;keyCode === 229是浏览器判断组合事件的兼容写法。建议在搜索框、聊天输入框、任何可能输入中文的场景里都加上这个判断,否则上线后大概率会被用户骂。

4.3 keydown 长按重复触发,把一次回车变成无数次

keydown和keyup的行为差异,很多人一开始没注意到。长按一个键时,keydown会连续触发多次,keyup只触发一次。

如果业务是“按下回车发送消息”,用@keydown.enter就可能导致用户手一抖没及时松开,发了多条重复消息。改成@keyup.enter能够避免大部分重复,但keyup也有个缺点:事件触发时机晚,用户想连续快速输入时,按钮响应会显得慢半拍。

折中方案是保留keydown,在事件处理函数里做防抖或者判断重复触发间隔。具体用什么,还得看交互预期。快捷键导航类交互我倾向于keyup,即时性要求高的输入类交互则用keydown加防抖。

4.4 被动监听和 prevent 冲突:.passive 的隐性警告

.passive修饰符是性能优化用的。它告诉浏览器:这个事件监听器不会调用preventDefault,你可以放心做滚动优化。典型场景是监听touchmove、wheel,为了移动端滚动的流畅度,浏览器可以不必等待事件处理函数执行完再决定要不要滚动。

但.passive和.prevent是互斥的。你在同一个事件上写@wheel.passive.prevent,prevent会被浏览器忽略,控制台还会给出警告。这属于浏览器安全限制,不是 Vue 能改变的。所以只做统计、不做拦截的事件才用.passive;需要真正阻止默认行为的场景,就别用。

4.5 全局监听器没有解绑,页面出现“幽灵执行”

全局事件监听器如果不随组件卸载而移除,组件销毁后监听器依然存在,事件一触发就会执行已经销毁组件里的逻辑。更麻烦的是,如果多个组件都注册了同一个全局快捷键,快捷键可能同时响应好几处。

排查这类问题最简单的方式,是在开发者工具控制台输入getEventListeners(window),看当前window上挂了多少监听器。如果发现实际上已卸载组件残留的监听器,毛病基本就能定位。

据我自己的习惯,所有addEventListener都必须在同一作用域内找到对应的removeEventListener,并且用onMounted/onBeforeUnmount成对管理。这个原则在原生 JS 项目里成立,在 Vue 项目里同样是纪律。

4.6 常见事件问题速查表

问题现象常见原因解决办法
点击子元素触发了父级跳转事件冒泡子元素监听加.stop
表单提交刷新页面未阻止默认行为@submit.prevent
Ctrl+Enter 误触发普通 Enter缺少.exact普通 Enter 监听加.exact
中文输入法选词触发回车提交输入法组合事件判断event.isComposing
长按回车重复发送keydown重复触发改用keyup或防抖
全局快捷键面板打不开事件没注册到 window用addEventListener全局注册
遮罩点击关闭后内部按钮也触发缺少 target 判断遮罩用@click.self

真把这些修饰符用顺之后,回头再看以前的代码,很多函数体第一行的preventDefault()和stopPropagation()其实都可以删掉。把控制行为放进模板里,代码的可读性会好很多。不过我也要提醒一句:修饰符不是用得越多越好。如果一个@keydown上叠了四五个修饰符,阅读成本其实很高。我个人的习惯是:单个元素只保留最必要的组合,超过两个逻辑,比如还要判断输入法组合状态或者跟业务条件联动,就拆成一个独立函数。模板负责声明意图,函数负责复杂判断,这样既有声明式语法的简洁,又不会把业务逻辑全塞到模板里。

最后再分享一个排查事件问题的老办法:在浏览器开发者工具里用getEventListeners(node)看当前节点到底挂了多少监听器,或者临时在事件处理函数里打印event,先搞清楚事件到底有没有派发到目标元素,再谈修饰符。这套方法帮我解决了大部分诡异事件问题。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/16 1:53:41

JDK17 GC调优实战:G1与ZGC参数策略及排查路径

JDK17刚发布那阵子&#xff0c;有个老同事翻出一份Java 8时代的调优参数文档&#xff0c;往启动脚本里一粘&#xff0c;程序直接起不来。这事儿当时让我印象很深&#xff1a;JDK17的GC调优&#xff0c;其实已经不是大多数人习惯的那套玩法了。默认回收器换成了G1&#xff0c;CM…

作者头像 李华
网站建设 2026/10/16 9:57:53

AMD显卡驱动更新的四层逻辑与实战避坑指南

1. 项目概述&#xff1a;为什么A卡驱动更新这件事&#xff0c;比你想象中更值得认真对待“AMD显卡驱动怎么更新&#xff1f;”——这看似是个入门级问题&#xff0c;但在我过去十年服务过数百台A卡工作站、游戏主机和创意渲染设备的过程中&#xff0c;它几乎每年都会以不同形态…

作者头像 李华
网站建设 2026/10/16 0:23:08

Win10 wsappx进程CPU占用过高原理与四级解决方案

1. 项目概述&#xff1a;为什么wsappx进程会突然“发疯”吃光CPU&#xff1f; 你有没有遇到过这样的场景&#xff1a;刚打开Win10&#xff0c;系统明明没运行大型软件&#xff0c;任务管理器里CPU使用率却稳稳卡在80%甚至95%以上&#xff0c;点开详细信息一看&#xff0c;一个…

作者头像 李华
网站建设 2026/10/15 9:17:19

VC写的Shp Editor:轻量级Shapefile编辑与数据抢救指南

简介&#xff1a;这是一份面向GIS开发者与地理信息专业学生的Shapefile矢量编辑工具源码包&#xff0c;基于Visual C开发&#xff0c;适合需要深入理解矢量数据编辑原理或进行二次开发的中高级用户。压缩包共167个文件&#xff0c;以53个.h头文件与49个.cpp源文件为核心&#x…

作者头像 李华
网站建设 2026/10/16 14:12:18

AI技术博客翻译实战:从术语处理到长难句拆解

1. 从一篇博客翻译说起&#xff1a;为什么人工翻译依然值得做做技术内容翻译这件事&#xff0c;我从2019年开始断断续续做到现在&#xff0c;前后翻过几百篇英文技术博客。说实话&#xff0c;机器翻译在这几年进步神速&#xff0c;但每次有人问我“现在AI翻译这么强了&#xff…

作者头像 李华