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

深夜代码系列 · 第4期

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

🔥 开篇引爆

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

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

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

html 复制代码
<Header
  :is-logged-in="isLoggedIn"
  :username="username"
  @logout="handleLogout"
  @login-success="handleLoginSuccess"
/>

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


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

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

核心概念:

  1. props(父 → 子):父组件通过属性向子组件传递数据。
  2. 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
  • 子组件中使用 camelCaseisLoggedin

类型安全

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-successupdate-userdelete-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 })

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

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

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

相关推荐
我有一棵树3 小时前
前端开发中 SCSS 变量与 CSS 变量的区别与实践选择,—— 两种变量别混为一谈
前端·css·scss
麦麦大数据3 小时前
F024 CNN+vue+flask电影推荐系统vue+python+mysql+CNN实现
vue.js·python·cnn·flask·推荐算法
white-persist3 小时前
JWT 漏洞全解析:从原理到实战
前端·网络·python·安全·web安全·网络安全·系统安全
IT_陈寒4 小时前
React 性能优化:5个实战技巧让首屏加载提升50%,开发者亲测有效!
前端·人工智能·后端
rising start4 小时前
前端基础一、HTML5
前端·html·html5
鬼谷中妖4 小时前
JavaScript 循环与对象:深入理解 for、for...in、for...of、不可枚举属性与可迭代对象
前端
南北是北北4 小时前
android ViewBinding
面试
大厂码农老A4 小时前
你打的日志,正在拖垮你的系统:从P4小白到P7专家都是怎么打日志的?
java·前端·后端