深夜代码系列 · 第4期
关注我,和小豆一起在掘金看小说
🔥 开篇引爆
周五下午,我刚想摸鱼打开掘金,水篇小说,她突然走过来,一脸困惑地指着我屏幕上的代码。
"豆子,你看看这个,冒号和 @
都是啥意思?我知道它们是 Vue 的语法糖,但具体怎么理解?我 Vue2 写到吐,Vue3 一升级全不会了!"
我一看,正是我们项目里最常见的 Header
组件调用:
html
<Header
:is-logged-in="isLoggedIn"
:username="username"
@logout="handleLogout"
@login-success="handleLoginSuccess"
/>
我放下鼠标,给她倒了杯水,笑眯眯地说:"这三个符号,就像是父子组件之间的三条秘密通道,它们分别负责传递数据 和接收信号。"
🎯 初步分析:父子组件通信的"传声筒"原理
父组件需要向子组件传递数据(如登录状态),子组件需要向父组件发送事件(如用户点击登出),实现双向通信。
核心概念:
props
(父 → 子):父组件通过属性向子组件传递数据。emit
(子 → 父):子组件通过事件向父组件发送消息。
:is-logged-in
:它负责"传递数据"
我指着代码中的冒号,开始解释:
"你看这个 :
,它是 v-bind
的简写。你可以把它想象成一个单向快递。"
js
<!-- 动态绑定 prop -->
:is-logged-in="isLoggedIn" // 等价于 v-bind:is-logged-in="isLoggedIn"
"父组件(我们现在所在的这个页面)是快递公司,isLoggedIn
是一个包裹,里面装着'用户是否登录'这个信息。我们用 :is-logged-in
这个'快递单',把这个包裹寄给了子组件 Header
。"
"所以,当父组件里的 isLoggedIn
变量从 false
变成 true
时,这个包裹里的内容也会自动更新,子组件就会立刻收到最新的状态。"
小汐若有所思地点点头:"我懂了,这个冒号就是把父组件的数据动态地'喂'给子组件,对吧?"
"没错,"我打了个响指,"这就是 props 传值 的过程。父组件通过 props
把数据传递给子组件,让子组件知道'现在是什么情况'。"
Prop 命名规范:
- 父组件模板中使用
kebab-case
::is-logged-in
- 子组件中使用
camelCase
:isLoggedin
类型安全:
js
defineProps({
isLoggedin: Boolean,
username: {
type: String,
required: true,
default: '游客'
}
})
@logout
和 @login-success
:它们负责"接收信号"
我继续指着 @
符号,解释道:
"如果说冒号是快递,那么 @
就是一个对讲机。"
js
<!-- 监听自定义事件 -->
@logout="handleLogout" // 等价于 v-on:logout="handleLogout"
"当用户在 Header
组件里点击了'登出'按钮,子组件会对着对讲机喊一声:'logout '!而父组件这边一直开着对讲机,听到这个信号后,就会立即调用 handleLogout
方法,把 isLoggedIn
设为 false
,清空 username
。"
"@login-success
也是同理,当子组件完成登录操作后,它会对着对讲机喊:'login-success ',甚至还会顺便把用户信息作为'暗号 '一起发送过来。父组件接收到信号和暗号后,就能调用 handleLoginSuccess
方法来更新用户信息了。"
小汐听完,露出了恍然大悟的表情:"所以,@
就是 v-on
的简写,用来监听子组件发出的自定义事件。这就像是子组件在告诉父组件:'我干完活了,你来处理一下吧!'"
事件命名规范:
- 使用
kebab-case
:@login-success
- 事件名要有动词:
login-success
、update-user
、delete-item
事件声明:
js
defineEmits(['logout', 'login-success'])
// 或带验证
defineEmits({
logout: null,
'login-success': (user) => {
return user && typeof user.name === 'string'
}
})
三兄弟身份档案(必背)
符号 | 长写 | 身份 | 方向 | 场景 |
---|---|---|---|---|
: |
v-bind: |
动态绑定 | 父 → 子(prop) | 把变量塞给子组件 |
@ |
v-on: |
事件监听 | 子 → 父(emit) | 子组件喊"爸,有人点我!" |
. |
修饰符 | 语法糖plus | ------ | 如 @click.stop |
记住口诀:
"有冒号传变量,无冒号传字面量;有 @ 等孩子喊妈。 "
示例代码
父组件 (App.vue
):状态的"总指挥官"
html
<template>
<div>
<Header
:is-logged-in="isLoggedIn"
:username="username"
@logout="handleLogout"
@login-success="handleLoginSuccess"
/>
<p>当前登录状态: {{ isLoggedIn ? '已登录' : '未登录' }}</p>
</div>
</template>
<script setup>
import { ref } from 'vue';
import Header from './Header.vue';
// 定义父组件的状态
const isLoggedIn = ref(false);
const username = ref('');
// 定义处理子组件发出的事件的方法
const handleLogout = () => {
isLoggedIn.value = false;
username.value = '';
console.log('✅ 父组件收到登出信号,状态已更新!');
};
const handleLoginSuccess = (user) => {
isLoggedIn.value = true;
username.value = user.name;
console.log(`✅ 父组件收到登录成功信号,用户:${user.name}!`);
};
</script>
子组件 (Header.vue
):事件的"执行者"
html
<template>
<header>
<div v-if="isLoggedIn">
<span>欢迎,{{ username }}</span>
<button @click="logout">登出</button>
</div>
<div v-else>
<button @click="login">登录</button>
</div>
</header>
</template>
<script setup>
import { defineProps, defineEmits } from 'vue';
// 接收父组件传递的props
defineProps({
isLoggedIn: Boolean,
username: String,
});
// 声明子组件将要发出的事件
const emit = defineEmits(['logout', 'login-success']);
// 触发登出事件
const logout = () => {
console.log('➡️ 子组件发出登出信号...');
emit('logout');
};
// 触发登录成功事件,并传递参数
const login = () => {
const user = { name: '小明' };
console.log('➡️ 子组件发出登录成功信号,并附带用户信息...');
emit('login-success', user);
};
</script>
流程图

⚠️ 常见坑点:
-
坑1:在子组件中直接修改 prop
js// ❌ 错误做法 props.isLoggedin = false // 会报警告 // ✅ 正确做法 emit('update:isLoggedin', false) // 或使用 v-model
-
坑2:忘记声明 emits
js// ❌ 未声明的事件在 strict 模式下会报警告 emit('logout') // ✅ 正确做法 const emit = defineEmits(['logout'])
-
坑3:事件名大小写错误
vue<!-- ❌ 模板中不能用 camelCase --> @loginSuccess="handleLoginSuccess" <!-- ✅ 必须用 kebab-case --> @login-success="handleLoginSuccess"
-
坑4:静态字符串导致布尔值失效
vue<!-- ❌ 恒为真,变量失效 --> is-logged-in="true" <!-- ✅ 使用绑定,让 Vue 知道这是 JS 表达式 --> :is-logged-in="true"
-
坑5:emit 名称与声明大小写不一致
js// ❌ 与声明不符,控制台警告 emit('loginSuccess') // ✅ 与模板保持一致 emit('login-success')
-
坑6:prop 类型对不上,dev 爆红
js// ❌ 类型对不上,dev 直接爆红 defineProps({ isLoggedIn: String }) // ✅ 类型保持一致 defineProps({ isLoggedIn: Boolean })
🌙 温馨收尾:凌晨两点的顿悟
小汐兴奋地拍了拍我的肩膀:"原来如此!这样一讲,我感觉整个组件的通信逻辑都清晰了。怪不得你总是说,理解了 props
和 emit
,就掌握了 Vue 的精髓!"
我看着她远去的背影,心里默默想道:今天下午的摸鱼时间没了,掘金我都还没看呢,这波真是亏大了