【Vue入门】scoped与组件通信

🚀 欢迎来到我的CSDN博客:Optimistic _ chen

一名热爱技术与分享的全栈开发者,在这里记录成长,专注分享编程技术与实战经验,助力你的技术成长之路,与你共同进步!


🚀我的专栏推荐

专栏 内容特色 适合人群
🔥C语言从入门到精通 系统讲解基础语法、指针、内存管理、项目实战 零基础新手、考研党、复习
🔥Java基础语法 系统解释了基础语法、类与对象、继承 Java初学者
🔥Java核心技术 面向对象、集合框架、多线程、网络编程、新特性解析 有一定语法基础的开发者
🔥Java EE 进阶实战 Servlet、JSP、SpringBoot、MyBatis、项目案例拆解 想快速入门Java Web开发的同学
🔥Java数据结构与算法 图解数据结构、LeetCode刷题解析、大厂面试算法题 面试备战、算法爱好者、计算机专业学生
🔥Redis系列 从数据类型到核心特性解析 项目必备

🚀我的承诺:

✅ 文章配套代码:每篇技术文章都提供完整的可运行代码示例

✅ 持续更新:专栏内容定期更新,紧跟技术趋势

✅ 答疑交流:欢迎在文章评论区留言讨论,我会及时回复(支持互粉)


🚀 关注我,解锁更多技术干货!
⏳ 每天进步一点点,未来惊艳所有人!✍️ 持续更新中,记得⭐收藏关注⭐不迷路 ✨

📌 标签:#技术博客#编程学习#Java#C语言#算法#程序员

文章目录

scoped

前面忘记说style标签中的scoped属性,我们在vue文件中使用vbase快捷键(安装 Vue VSCode Snippets 插件即可)生成的Vue 组件的初始模板代码中style默认会携带scoped属性。

scoped通常指的是一种样式作用域,使用 scoped 样式可以确保 CSS 只对当前组件生效,从而防止全局样式污染,让样式只作用于当前组件的标签。

父组件:

javascript 复制代码
<template>
  <div class="box">
    <p>父组件的段落</p>
    <!-- 引入子组件 -->
    <Child />
  </div>
</template>

<script>
import Child from './Child.vue'
export default {
  components: {
    Child
  }
}
</script>

<style scoped>
/* 这里也加了 scoped,样式仅作用于当前组件 */
.box {
  border: 2px solid red; /* 红色边框 */
  padding: 10px;
}
p {
  color: red; /* 红色文字 */
}
</style>

子组件:

javascript 复制代码
<template>
  <div class="child-box">
    <p>子组件的段落</p>
  </div>
</template>

<style scoped>
/* 这里加了 scoped,样式仅作用于当前组件 */
.child-box {
  border: 2px solid blue; /* 蓝色边框 */
  padding: 10px;
  margin: 10px;
}
p {
  color: blue; /* 蓝色文字 */
  font-weight: bold;
}
</style>

scoped原理

上面的例子佐证了scoped的作用,观察F12开发者工具,Vue给父组件里的所有元素(包括最外层的 div)添加了一个类似data-v-7a7a37b1的特殊属性。同时,它把父组件的<style scoped>里的所有 CSS 选择器,都改成了类似 .box[data-v-7a7a37b1] 的形式。

父组件的样式规则就只能匹配到带有 data-v-f3f3eg9 这个属性的元素,而无法影响到子组件内部的元素,因为子组件内部的元素拥有的是另一个不同的属性。这就实现了样式的隔离。

组件通信

在上篇博客中我们探讨了组件和组件化的概念。当项目被拆分为多个独立组件后,数据是否也需要拆分呢?显然不行。为了实现组件间的数据共享,当一个组件需要访问另一个组件的数据时,就需要用到组件通信机制。

进行组件通信,首要明确两个组件之间的关系,进而选择不同的通信方案。
比如:父子关系 :谁被使用,谁就是子组件,当前组件就是父组件
   非父子关系 :跨组件通信

父子间通信

父子间通信分为两部分:  父传子 与 子传父

Props声明:一个组件需要显式声明它所接受的 props,这样 Vue 才能知道外部传入的哪些是props。

使用组合式API的<script setup>单文件组件中,props可以使用defineProps()来声明;

javascript 复制代码
<script setup>
//数组方法
const props = defineProps(['foo'])
console.log(props.foo)
</script>

在选项式API的组件中,props使用props选项来声明

javascript 复制代码
export default {
  props: ['foo'],
  setup(props) {
    // setup() 接收 props 作为第一个参数
    console.log(props.foo)
  }
}

上述使用数组方法声明,我们更多的时候使用对象来声明:

javascript 复制代码
//完整语法
const props = defineProps({
  属性名: {
    type: 类型,  // Number String Boolean ...
    required: true, // 是否必填

    default: 默认值, // 默认值


    // value: ⽗传⼦的值
    validator(value) {
      //⾃定义校验逻辑
      return 布尔值
    }
  }
})


//对象方法
defineProps({
  title: String,
  likes: Number
})

以对象形式声明的每个属性,key 是 prop 的名称,而值则是该 prop 预期类型的构造函数。

一句话总结:props 就是子组件声明它可以接收哪些数据,然后父组件通过自定义属性的形式把这些数据传进去

父传子(子接父传)

传递步骤:

  1. 子组件通过defineProps接收数据(子接)
  2. 父组件通过自定义属性传递数据(父传)

父组件:

javascript 复制代码
<template>
  <div class="parent">
    <h3>父组件</h3>
    <!-- 传递 props -->
     <!-- 自定义属性:message age user  -->
    <Child 
      message="Hello from parent" 
      :age="25"
      :user="{ name: '张三', age: 18 }"
    />
  </div>
</template>

<script setup>
import Child from './Child.vue'
</script>

子组件:

javascript 复制代码
<template>
  <div class="child">
    <h3>子组件</h3>
    <p>收到的消息:{{ message }}</p>
    <p>年龄:{{ age }}</p>
    <p>用户信息:{{ user.name }} - {{ user.age }}</p>
  </div>
</template>

<script setup>
// 定义 props(不需要引入,直接使用)
const props = defineProps({
  message: String,
  age: Number,
  user: {
    type: Object,
    default: () => ({})
  }
})

</script>

总结:所有的 props 都遵循着单向绑定原则,props 因父组件的更新而变化,自然地将新的状态向下流往子组件,而不会逆向传递。也就是说,数据还是属于父组件的,子组件只能"看"而不能写。

子传父

想要修改数据子组件通知(emit)父组件,让父组件修改后重新传递数据,子组件重新渲染即可

  1. ⽗组件内,⼦组件标签上,绑定(监听)⾃定义事件,@⾃定义事件="⽗修改数据的函数" (⽗绑定)
  2. ⼦组件恰当时机,触发⽗组件的⾃定义事件,emit('⾃定义事件', 携带的参数...), 从⽽导致⽗组件修改函数的时候(⼦触发)

父组件:

javascript 复制代码
<template>
  <div class="parent">
    <h3>父组件</h3>
    <p>从子组件接收到的消息:{{ receivedMessage }}</p>
    <p>从子组件接收到的计数:{{ childCount }}</p>
    
    <!-- 监听子组件的事件 -->
    <Child 
      @send-message="handleMessage"
      @update-count="handleCount"
    />
  </div>
</template>

<script setup>
import { ref } from 'vue'
import Child from './Child.vue'

const receivedMessage = ref('')
const childCount = ref(0)

const handleMessage = (msg) => {
  receivedMessage.value = msg
}

const handleCount = (count) => {
  childCount.value = count
}
</script>

子组件:

javascript 复制代码
<template>
  <div class="child">
    <h3>子组件</h3>
    <p>当前计数:{{ count }}</p>
    <button @click="sendToParent">发送数据给父组件</button>
    <button @click="updateCount">更新内部计数</button>
  </div>
</template>

<script setup>
import { ref } from 'vue'

// 定义要触发的事件
const emit = defineEmits(['send-message', 'update-count'])

// 子组件自己的数据
const count = ref(0)

// 触发事件,传给父组件
const sendToParent = () => {
  emit('send-message', '这是子组件传来的数据')
}

const updateCount = () => {
  count.value++
  // 把最新的 count 传给父组件
  emit('update-count', count.value)
}
</script>

props数据和响应式数据的区别

角度 响应式数据 props
谁的数据 组件自己的 父组件的
能否修改 能✅ 不能❌
定义方式 组件内部 父组件传递
更新方式 随数据变化 父组件更新才会更新
API ref() reavtive() defineProps()

非父子间通信

当两个组件不是直接的父子关系时(比如兄弟组件、隔代组件、无关联组件),就需要用其他方式通信。

方案一:

安装第三方库mitt

bash 复制代码
npm i mitt

创建事件(中间人)

javascript 复制代码
// utils/eventBus.js
import mitt from 'mitt'
const emitter = mitt()
export default emitter

组件A发送数据

javascript 复制代码
<template>
  <div>
    <h3>组件A</h3>
    <button @click="sendMessage">发送消息</button>
  </div>
</template>

<script setup>
import eventBus from '../utils/eventBus'

const sendMessage = () => {
  // 发送事件,任何地方都能监听
  eventBus.emit('message', '你好,我是组件A')
}
</script>

组件B接收数据

javascript 复制代码
<template>
  <div>
    <h3>组件B</h3>
    <p>收到的消息:{{ message }}</p>
  </div>
</template>

<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
import eventBus from '../utils/eventBus'

const message = ref('')

// 监听事件
onMounted(() => {
  eventBus.on('message', (msg) => {
    message.value = msg
  })
})

// 组件销毁时取消监听(重要!)
onUnmounted(() => {
  eventBus.off('message')
})
</script>

方法二:状态管理(Pinia )

当应用复杂,很多组件需要共享状态时,用专门的状态管理库,所有数据都写到管理库,其他组件直接去调用接口

安装

bash 复制代码
npm i pinia

创建store

javascript 复制代码
// stores/counter.js
import { defineStore } from 'pinia'

export const useCounterStore = defineStore('counter', {
  state: () => ({
    count: 0,
    user: { name: '张三' }
  }),
  actions: {
    increment() {
      this.count++
    },
    setUser(name) {
      this.user.name = name
    }
  }
})

使用

javascript 复制代码
<template>
  <div>
    <p>计数:{{ counter.count }}</p>
    <p>用户:{{ counter.user.name }}</p>
    <button @click="counter.increment()">增加</button>
  </div>
</template>

<script setup>
import { useCounterStore } from '../stores/counter'

const counter = useCounterStore()
// 直接使用,任何组件都能访问同一个状态
</script>

总结:mitt 是广播 而 Pinia 集中管理

完结撒花!🎉

如果这篇博客对你有帮助,不妨点个赞支持一下吧!👍
你的鼓励是我创作的最大动力~

想获取更多干货? 欢迎关注我的专栏 → optimistic_chen

📌 收藏本文,下次需要时不迷路!

我们下期再见!💫 持续更新中......


悄悄说:点击主页有更多精彩内容哦~ 😊

相关推荐
qingwufeiyang_5301 小时前
统一网关GateWay
linux·服务器·gateway
L1624761 小时前
linux中mdadm命令生产环境全流程实战总结
linux·运维·数据库
SuperEugene2 小时前
前端空值处理规范:Vue 实战避坑,可选链、?? 兜底写法|项目规范篇
前端·javascript·vue.js
前端百草阁2 小时前
Vue3 Diff 算法详解
前端·javascript·vue.js·算法·前端框架
im_AMBER2 小时前
前后端对接: ESM配置与React Router
前端·javascript·学习·react.js·性能优化·前端框架·ecmascript
小比特_蓝光2 小时前
Linux权限
linux·运维·服务器
学且思2 小时前
使用import.meta.url实现传递路径动态加载资源
前端·javascript·vue.js
problc2 小时前
OpenClaw 的前端用的React还是Vue?
前端·vue.js·react.js
凰轮2 小时前
vue实现大文件切片上传
vue.js