【Vue】scoped作用 && 父子组件通信 && props && emit

文章目录

  • [Ⅰ. scoped 作用及原理](#Ⅰ. scoped 作用及原理)
  • [Ⅱ. 父子组件通信](#Ⅱ. 父子组件通信)

Ⅰ. scoped 作用及原理

一、scoped 的作用

默认情况下 ,写在组件中的 style 样式会 全局生效,因此很容易造成多个组件之间的样式冲突问题。

  1. 全局样式:默认组件中的样式会作用到全局,任何一个组件中都会受到此样式的影响
  2. 局部样式 :可以给组件加上 scoped 属性,可以让样式只作用于当前组件的标签

MyLeft.vue 文件:

javascript 复制代码
<script setup></script>

<template>
  <div
    class="my-left"
    style="
      flex: 1;
      align-items: center;
      height: 100px;
      background: paleturquoise;
      text-align: center;
    ">
    <h3>每天起床第一句</h3>
  </div>
</template>

<style>
h3 {
  color: red;
}
</style>

MyRight.vue 文件:

javascript 复制代码
<script setup></script>

<template>
  <div
    class="my-right"
    style="
      flex: 1;
      align-items: center;
      height: 100px;
      background: plum;
      text-align: center;
    ">
    <h3>先给自己打个气</h3>
  </div>
</template>

<style></style>

App.vue 文件:

javascript 复制代码
<script setup>
    import MyLeft from './components/MyLeft.vue'
    import MyRight from './components/MyRight.vue'
</script>

<template>
  <div class="container">
    <MyLeft />
    <MyRight />
  </div>
</template>

<style>
.container {
  display: flex;
}
</style>

至此,发现 MyRight.vueh3 的样式受到了的影响。为了解决样式污染/冲突问题,可以给组件的 style 添加 scoped 属性,这样就避免了不同组件之前样式污染的问题。

二、scoped的原理

  1. 组件内所有标签都被添加 data-v-hash值 的自定义属性
  2. css 选择器都被添加 [data-v-hash值] 的属性选择器

最终效果:必须是当前组件的元素,才会有这个自定义属性,从而保证了样式只能作用到当前组件。

Ⅱ. 父子组件通信

一、介绍

组件通信,是指一个把数组传递给另一个组件。

  1. 组件的数据是独立的,无法直接访问其他组件的数据。
  2. 想使用其他组件的数据,就需要组件通信

组件之间的关系:

  1. 父子关系:组件 A 使用了组件 B,则 A 是父,B 是子
  2. 非父子关系:比如祖先和孙子的关系,兄弟关系

二、父传子:props

当子组件的数据需要按需改变的时候,就得让父组件传递数据给子组件,从而提高组件的灵活性和复用性。

父组件通过 props (自定义属性) 将数据传递给子组件,如下所示:

javascript 复制代码
<MyButton text="提交" color="blue" />

其中 textcolor 就是 props ,然后子组件通过内置方法 defineProps() 来接收这些自定义属性,如下所示:

javascript 复制代码
<script setup>
    // 可以只写自定义属性,而不需要和下面一样写完整!
    // 完整写法如下所示:
    //     type:表示类型
    //     default:表示默认值
    //     required:表示是否必填
    const props = defineProps({
      text: String,
      color: {
        type: String,
        default: 'blue',
        required: false
      },
      price: Number
    })
</script>

<template>
    <button :style="{ backgroundColor: color }">{{ text }}</button>
</template>

<script setup> 中需要用到 defineProps() 中传入的属性时,必须先拿到 props 对象或者解构出对应的属性 ,否则是没办法在 <script setup> 中使用的;

但是在 <template> 中则不需要 ,因为有 "语法糖" 的好处,Vue 会自动解包 props,所以不需要拿到 props 对象就能直接使用对应的属性!

下面举个例子:

App.vue 文件:

javascript 复制代码
<script setup>
    import MyGoods from './components1/MyGoods.vue';

    // 提供数据
    // 商品列表
    const goodsList = [
      {
        id: '4001172',
        name: '称心如意手摇咖啡磨豆机咖啡豆研磨机',
        price: 289,
        picture:
          'https://yanxuan-item.nosdn.127.net/84a59ff9c58a77032564e61f716846d6.jpg'
      },
      {
        id: '4001594',
        name: '日式黑陶功夫茶组双侧把茶具礼盒装',
        price: 288,
        picture:
          'https://yanxuan-item.nosdn.127.net/3346b7b92f9563c7a7e24c7ead883f18.jpg'
      },
      {
        id: '4001009',
        name: '竹制干泡茶盘正方形沥水茶台品茶盘',
        price: 109,
        picture:
          'https://yanxuan-item.nosdn.127.net/2d942d6bc94f1e230763e1a5a3b379e1.png'
      },
      {
        id: '4001874',
        name: '古法温酒汝瓷酒具套装白酒杯莲花温酒器',
        price: 488,
        picture:
          'https://yanxuan-item.nosdn.127.net/44e51622800e4fceb6bee8e616da85fd.png'
      },
      {
        id: '4001649',
        name: '大师监制龙泉青瓷茶叶罐',
        price: 139,
        picture:
          'https://yanxuan-item.nosdn.127.net/4356c9fc150753775fe56b465314f1eb.png'
      },
      {
        id: '3997185',
        name: '与众不同的口感汝瓷白酒杯套组1壶4杯',
        price: 108,
        picture:
          'https://yanxuan-item.nosdn.127.net/8e21c794dfd3a4e8573273ddae50bce2.jpg'
      },
      {
        id: '3997403',
        name: '手工吹制更厚实白酒杯壶套装6壶6杯',
        price: 100,
        picture:
          'https://yanxuan-item.nosdn.127.net/af2371a65f60bce152a61fc22745ff3f.jpg'
      },
      {
        id: '3998274',
        name: '德国百年工艺高端水晶玻璃红酒杯2支装',
        price: 139,
        picture:
          'https://yanxuan-item.nosdn.127.net/8896b897b3ec6639bbd1134d66b9715c.jpg'
      }
    ]
</script>

<template>
  <div class="list">
    <!-- 在子组件中,传入自定义属性 -->
    <MyGoods v-for="item in goodsList" 
            :key="item.id" 
            :imgUrl="item.picture"
            :price="item.price"
            :title="item.name"
    />
  </div>
</template>

<style lang="scss">
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}
.list {
  width: 1000px;
  margin: 0 auto;
  display: flex;
  flex-wrap: wrap;
}
</style>

子组件 MyGoods.vue

javascript 复制代码
<script setup>
    defineProps(['imgUrl', 'title', 'price']) // 采用简写的方式,不要求具体的类型等
</script>

<template>
  <div class="item">
    <img
      :src="imgUrl"
      :alt="title" />
    <p class="name">{{ title}}</p>
    <p class="price">{{ price }}.00</p>
  </div>
</template>

<style lang="scss" scoped>
.item {
  width: 240px;
  margin-left: 10px;
  padding: 20px 30px;
  transition: all 0.5s;
  margin-bottom: 20px;
  .item:nth-child(4n) {
    margin-left: 0;
  }
  &:hover {
    box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.2);
    transform: translate3d(0, -4px, 0);
    cursor: pointer;
  }
  img {
    width: 100%;
  }
  .name {
    font-size: 18px;
    margin-bottom: 10px;
    color: #666;
  }
  .price {
    display: flex;
    align-items: center;
    font-size: 22px;
    color: firebrick;
    button {
      margin-left: 48px;
      font-size: 14px;
      outline: none;
    }
  }
  .price::before {
    content: '¥';
    font-size: 22px;
  }
}
</style>

三、子传父:v-on && emit

在上面父传子的案例中,如果新增一个砍价功能的按钮,会发现子组件不能直接修改父组件传递的数据 ,因为 props 是只读的,子组件不能修改。

解决方案:

  1. 在父组件中提供砍价功能对应的方法,然后在使用子组件的时候将该方法绑定起来,和 props 一起发送给子组件。绑定的语法实际上就是 v-on,如下所示:

    javascript 复制代码
       @自定义事件="父组件中修改数据的函数"
  2. 在子组件中通过 defineEmits() 拿到触发自定义事件的函数 emit(),然后在砍价按钮中绑定点击事件,在该点击事件中去触发自定义事件,此时该自定义事件就会发送到父组件,父组件就会执行砍价功能对应的方法。emit() 方法参数如下所示:

    javascript 复制代码
       emit('自定义事件', 父组件中对应方法所需的参数...)

defineEmits() 本质是一个编译宏 ,用于生成一个 "事件触发器函数",通常叫 emit,而 emit 才是真正用来发射事件的函数 ,所以不要搞错(直接拿 defineEmits() 去发射事件,那就错了)

App.vue 文件:

javascript 复制代码
<script setup>
    import MyGoods from './components2/MyGoods.vue';
    import {ref} from 'vue'
    // 商品列表
    const goodsList = ref([
      {
        id: '4001172',
        name: '称心如意手摇咖啡磨豆机咖啡豆研磨机',
        price: 289,
        picture:
          'https://yanxuan-item.nosdn.127.net/84a59ff9c58a77032564e61f716846d6.jpg'
      },
      {
        id: '4001594',
        name: '日式黑陶功夫茶组双侧把茶具礼盒装',
        price: 288,
        picture:
          'https://yanxuan-item.nosdn.127.net/3346b7b92f9563c7a7e24c7ead883f18.jpg'
      },
      {
        id: '4001009',
        name: '竹制干泡茶盘正方形沥水茶台品茶盘',
        price: 109,
        picture:
          'https://yanxuan-item.nosdn.127.net/2d942d6bc94f1e230763e1a5a3b379e1.png'
      },
      {
        id: '4001874',
        name: '古法温酒汝瓷酒具套装白酒杯莲花温酒器',
        price: 488,
        picture:
          'https://yanxuan-item.nosdn.127.net/44e51622800e4fceb6bee8e616da85fd.png'
      },
      {
        id: '4001649',
        name: '大师监制龙泉青瓷茶叶罐',
        price: 139,
        picture:
          'https://yanxuan-item.nosdn.127.net/4356c9fc150753775fe56b465314f1eb.png'
      },
      {
        id: '3997185',
        name: '与众不同的口感汝瓷白酒杯套组1壶4杯',
        price: 108,
        picture:
          'https://yanxuan-item.nosdn.127.net/8e21c794dfd3a4e8573273ddae50bce2.jpg'
      },
      {
        id: '3997403',
        name: '手工吹制更厚实白酒杯壶套装6壶6杯',
        price: 100,
        picture:
          'https://yanxuan-item.nosdn.127.net/af2371a65f60bce152a61fc22745ff3f.jpg'
      },
      {
        id: '3998274',
        name: '德国百年工艺高端水晶玻璃红酒杯2支装',
        price: 139,
        picture:
          'https://yanxuan-item.nosdn.127.net/8896b897b3ec6639bbd1134d66b9715c.jpg'
      }
    ])

    // 砍价功能对应的方法,由父组件提供
    const bargain = (index, price) => {
        goodsList.value[index].price -= price
    }
</script>

<template>
  <div class="list">
    <!-- 在子组件中,传入自定义属性 -->
    <MyGoods 
      v-for="(item, index) in goodsList" 
      :key="item.id" 
      :urlImg="item.picture" 
      :price="item.price" 
      :title="item.name"
      :idx="index"
      @ccc="bargain"
    />
  </div>
</template>

<style lang="scss">
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}
.list {
  width: 1000px;
  margin: 0 auto;
  display: flex;
  flex-wrap: wrap;
}
</style>

MyGoods.vue 文件:

javascript 复制代码
<template>
    <div class="item">
      <img
        :src="urlImg"
        :alt="title" />
      <p class="name">{{ title }}</p>
      <p class="price">
        <span>{{ price }}.00</span>
        <button @click="cutPrice">砍价</button>
      </p>
    </div>
</template>

<script setup>
    const props = defineProps([
        'urlImg', 'title', 'price', 'idx'
    ])

    const emit = defineEmits();

    const cutPrice = () => {
      emit('ccc', props.idx, 3);
    }
</script>

<style scoped>
.item {
  width: 240px;
  margin-left: 10px;
  padding: 20px 30px;
  transition: all 0.5s;
  margin-bottom: 20px;
  .item:nth-child(4n) {
    margin-left: 0;
  }
  &:hover {
    box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.2);
    transform: translate3d(0, -4px, 0);
    cursor: pointer;
  }
  img {
    width: 100%;
  }
  .name {
    font-size: 18px;
    margin-bottom: 10px;
    color: #666;
  }
  .price {
    font-size: 22px;
    color: firebrick;
    display: flex;
    align-items: center;
    height: 36px;

    button {
      font-size: 14px;
      margin-left: 50px;
    }
  }
  .price::before {
    content: '¥';
    font-size: 22px;
  }
}
</style>
相关推荐
恋猫de小郭1 小时前
Flutter Zero 是什么?它的出现有什么意义?为什么你需要了解下?
android·前端·flutter
崔庆才丨静觅8 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60619 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了9 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅9 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅9 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅10 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment10 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅10 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊10 小时前
jwt介绍
前端