普通视图

发现新文章,点击刷新页面。
今天 — 2025年10月15日首页

她问我::is-logged 是啥?我说:前面加冒号,就是 Vue 在发暗号

作者 洛小豆
2025年10月15日 13:04

深夜代码系列 · 第4期

关注我,和小豆一起在掘金看小说

🔥 开篇引爆

周五下午,我刚想摸鱼打开掘金,水篇小说,她突然走过来,一脸困惑地指着我屏幕上的代码。

“豆子,你看看这个,冒号和 @ 都是啥意思?我知道它们是 Vue 的语法糖,但具体怎么理解?我 Vue2 写到吐,Vue3 一升级全不会了!”

我一看,正是我们项目里最常见的 Header 组件调用:

<Header
  :is-logged-in="isLoggedIn"
  :username="username"
  @logout="handleLogout"
  @login-success="handleLoginSuccess"
/>

我放下鼠标,给她倒了杯水,笑眯眯地说:“这三个符号,就像是父子组件之间的三条秘密通道,它们分别负责传递数据接收信号。”


🎯 初步分析:父子组件通信的“传声筒”原理

父组件需要向子组件传递数据(如登录状态),子组件需要向父组件发送事件(如用户点击登出),实现双向通信。

核心概念:

  1. props(父 → 子):父组件通过属性向子组件传递数据。
  2. emit(子 → 父):子组件通过事件向父组件发送消息。

:is-logged-in:它负责“传递数据

我指着代码中的冒号,开始解释:

“你看这个 :,它是 v-bind 的简写。你可以把它想象成一个单向快递。”

<!-- 动态绑定 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
  • 子组件中使用 camelCaseisLoggedin

类型安全

defineProps({
  isLoggedin: Boolean,
  username: {
    type: String,
    required: true,
    default: '游客'
  }
})

@logout@login-success:它们负责“接收信号

我继续指着 @ 符号,解释道:

“如果说冒号是快递,那么 @ 就是一个对讲机。”

<!-- 监听自定义事件 -->
@logout="handleLogout"       // 等价于 v-on:logout="handleLogout"

“当用户在 Header 组件里点击了‘登出’按钮,子组件会对着对讲机喊一声:‘logout’!而父组件这边一直开着对讲机,听到这个信号后,就会立即调用 handleLogout 方法,把 isLoggedIn 设为 false,清空 username。”

@login-success 也是同理,当子组件完成登录操作后,它会对着对讲机喊:‘login-success’,甚至还会顺便把用户信息作为‘暗号’一起发送过来。父组件接收到信号和暗号后,就能调用 handleLoginSuccess 方法来更新用户信息了。”

小汐听完,露出了恍然大悟的表情:“所以,@ 就是 v-on 的简写,用来监听子组件发出的自定义事件。这就像是子组件在告诉父组件:‘我干完活了,你来处理一下吧!’”

事件命名规范

  • 使用 kebab-case@login-success
  • 事件名要有动词:login-successupdate-userdelete-item

事件声明

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):状态的“总指挥官”

<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):事件的“执行者”

<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>

流程图

image.png


⚠️ 常见坑点:

  • 坑1:在子组件中直接修改 prop

    // ❌ 错误做法
    props.isLoggedin = false // 会报警告
    
    // ✅ 正确做法
    emit('update:isLoggedin', false) // 或使用 v-model
    
  • 坑2:忘记声明 emits

    // ❌ 未声明的事件在 strict 模式下会报警告
    emit('logout')
    
    // ✅ 正确做法
    const emit = defineEmits(['logout'])
    
  • 坑3:事件名大小写错误

    <!-- ❌ 模板中不能用 camelCase -->
    @loginSuccess="handleLoginSuccess"
    
    <!-- ✅ 必须用 kebab-case -->
    @login-success="handleLoginSuccess"
    
  • 坑4:静态字符串导致布尔值失效

    <!-- ❌ 恒为真,变量失效 -->
    is-logged-in="true"
    
    <!-- ✅ 使用绑定,让 Vue 知道这是 JS 表达式 -->
    :is-logged-in="true"
    
  • 坑5:emit 名称与声明大小写不一致

    // ❌ 与声明不符,控制台警告
    emit('loginSuccess')
    
    // ✅ 与模板保持一致
    emit('login-success')
    
  • 坑6:prop 类型对不上,dev 爆红

    // ❌ 类型对不上,dev 直接爆红
    defineProps({ isLoggedIn: String })
    
    // ✅ 类型保持一致
    defineProps({ isLoggedIn: Boolean })
    

🌙 温馨收尾:凌晨两点的顿悟

小汐兴奋地拍了拍我的肩膀:“原来如此!这样一讲,我感觉整个组件的通信逻辑都清晰了。怪不得你总是说,理解了 propsemit,就掌握了 Vue 的精髓!”

我看着她远去的背影,心里默默想道:今天下午的摸鱼时间没了,掘金我都还没看呢,这波真是亏大了

❌
❌