跳转到内容

Vue

刀刀

3/20/2025

0 字

0 分钟

组件

概念

组件就是可复用的 vue 文件( Vue 实例, 封装标签, 样式和 JS 代码)。

组件化 :封装的思想,把页面上 可重用的部分 封装为 组件,从而方便项目的 开发 和 维护

一个页面, 可以拆分成一个个组件,一个组件就是一个整体, 每个组件可以有自己独立的 结构 样式 和 行为( html , cssjs )

使用

  1. 创建组件,封装代码

  2. 引入组件,注册组件

    • 全局注册:

      main.jsvue.compnent(自定义组件名, 组件对象)

      js
      // 目标: 全局注册 (一处定义到处使用)
      // 1. 创建组件 - 文件名.vue
      // 2. 引入组件
      import Pannel from './components/Pannel'
      // 3. 全局 - 注册组件
      Vue.component("PannelG", Pannel)
    • 局部注册:某个 vue 文件中,components:{ 自定义组件名: 组件对象 }

      js
      import Pannel from './components/Pannel_1'
      export default {
        // 3. 局部 - 注册组件
        components: {
          PannelL: Pannel
        }
      }
  3. 使用组件

    把注册的组件名当成标签名使用在结构中即可。

scoped 作用

scpoed 添加到 style 标签上的属性;作用:使得样式只作用于当前文件的标签上。

组件通信

因为每个组件的变量和值都是独立的。

父: 使用其他组件的vue文件。父组件中,通过标签属性的方式将数据传递给子组件。

子: 被引入的组件(嵌入)。子组件中,props 定义变量,用于接收父组件传递过来的参数。

父传子

关键代码:props

步骤:

  1. 在文件夹 components 下创建子组件 Product.vue

    html
    <template>
      <div class="my-product">
        <h3>标题: {{ title }}</h3>
        <p>价格: {{ price }}元</p>
        <p>{{ intro }}</p>
      </div>
    </template>
  2. 组件内在 props 定义变量, 用于接收外部传入的值。

    vue
    <script>
    export default {
      props: ['title', 'price', 'intro']
    }
    </script>
  3. App.vue 中引入注册组件, 使用时, 传入具体数据给组件显示。

    vue
    <template>
      <div>
        <Product title="好吃的口水鸡" price="50" intro="开业大酬宾, 全场8折"></Product>
        <Product title="好可爱的可爱多" price="20" intro="老板不在家, 全场1折"></Product>
        <Product title="好贵的北京烤鸭" price="290" :intro="str"></Product>
      </div>
    </template>
    
    <script>
    // 引入组件
    import Product from './components/MyProduct'
    export default {
      data(){
        return {
          str: "好贵啊, 快来啊, 好吃"
        }
      },
      // 3. 注册组件
      components: {
        Product  // Product: Product // key和value变量名同名 - 简写
      }
    }
    </script>

整体步骤概况如下图所示。

props.png

父向子-配合循环

把数据循环分别传入给组件内显示。每次循环,变量和组件互不影响。

子组件代码:

html
<template>
  <div class="my-product">
    <h3>标题: {{ title }}</h3>
    <p>价格: {{ price }}</p>
    <p>{{ intro }}</p>
  </div>
</template>   
<script>
export default {
  props: ["title", "price", "intro"],
};
</script>

父组件代码

html
<template>
  <div>
    <myproduct
      v-for="item in list"
      :key="item.id"
      :title="item.proname"
      :price="item.proprice"
      :intro="item.info"
    ></myproduct>
  </div>
</template>

<script>
import myproduct from "./components/myProduct.vue";
export default {
  data() {
    return {
      list: [
        {
          id: 1,
          proname: "超级好吃的棒棒糖",
          proprice: 18.8,
          info: "开业大酬宾, 全场8折",
        },
        {
          id: 2,
          proname: "超级好吃的大鸡腿",
          proprice: 34.2,
          info: "好吃不腻, 快来买啊",
        },
        {
          id: 3,
          proname: "超级无敌的冰激凌",
          proprice: 14.2,
          info: "炎热的夏天, 来个冰激凌了",
        },
      ],
    };
  },
  components: { myproduct },
};
</script>

父向子-单向数据流

父到子的数据流向,叫单向数据流 ,在 vue 中需要遵循单向数据流原则。

  1. 父组件的数据发生了改变,子组件会自动跟着变。

  2. 子组件不能直接修改父组件传递过来的 props 数据。

    注意:

    关于子组件不能直接修改父组件传递过来的 props 数据中,指的是不能修改其引用的地址(因为是数组类型的数据,属于复杂数据类型),比如用 map 方法或者 filter 方法修改了原数组返回了新数组并覆盖了原数据,此时就是改变了引用地址。

    js
    this.arr = this.arr.filter((item) => item.id != id);

    但是如果仅仅改变了数组内的值,其地址没有发生变化,则可以让子组件改变 props 内的数据。如 splicepush 等方法。

    js
    let index = this.arr.findIndex((item) => item.id == id);
    this.arr.splice(index, 1);

原因:

子组件修改了不通知父组件,会造成数据不一致。因此 vue 规定,props 是只读的。

父组件传给子组件的是一个对象,子组件修改对象的属性,是不会报错的,对象是引用类型, 互相更新

总结: 所以 props 变量本身是不能重新赋值的。

子传父

子组件中:在恰当时机(一般是触发某个数据时候)通过 $emit 自定义事件,把值传递给父组件。

父组件中:使用子组件标签上绑定自定义事件来接收数据。

步骤:

  • 子:

    js
    @自定义事件名="methods函数"
    methods函数(){
        this.$emit(自定义名称,索引或id,数值)
    }
    • 参数1:自定义的名称,可随便更换。
    • 参数2:由父传子获得的索引或 id
    • 参数3:变量或数值。

    姊川福.png

  • 父:

    js
    自定义名称="自定义函数名"
    自定义函数名(参数1,参数2){}
    • 参数1:接收子组件传递过来的索引或 id
    • 参数2:接收子组件传递过来的数值。

整体流程如下所示:

  • 子组件:

    html
      <div class="my-product">
        <h3>标题: {{ title }}</h3>
        <p>价格: {{ price }}</p>
        <p>{{ intro }}</p>
        <button @click="kanFn">砍价</button>
      </div>
    </template>   
    <script>
    export default {
      props: ["title", "price", "intro", "index"],
      methods: {
        kanFn() {
          this.$emit("subprice", this.index, 2);
        },
      },
    };
    </script>
  • 父组件

    html
    <template>
      <div>
        <h1>hello world</h1>
        <aa></aa>
        <bb></bb>
        <myproduct
          v-for="(item, index) in list"
          :key="item.id"
          :title="item.proname"
          :price="item.proprice"
          :intro="item.info"
          :index="index"
          @subprice="fn"
        ></myproduct>
      </div>
    </template>
    
    <script>
    import myproduct from "./components/myProduct.vue";
    export default {
      data() {
        return {
          list: [
            {
              id: 1,
              proname: "超级好吃的棒棒糖",
              proprice: 18.8,
              info: "开业大酬宾, 全场8折",
            },
            {
              id: 2,
              proname: "超级好吃的大鸡腿",
              proprice: 34.2,
              info: "好吃不腻, 快来买啊",
            },
            {
              id: 3,
              proname: "超级无敌的冰激凌",
              proprice: 14.2,
              info: "炎热的夏天, 来个冰激凌了",
            },
          ],
        };
      },
      methods: {
        // 这里的参数是由子组件中的this.$emit中第二第三个参数传来的
        fn(index, price) {
          this.list[index].proprice = this.list[index].proprice - price;
        },
      },
      components: { myproduct },
    };
    </script>

子传父.png

总结: 父自定义事件和方法, 等待子组件触发事件给方法传值。

跨组件通信

作用:让非父子的组件能够使用通用的组件进行通讯:事件总线(event-bus)。

核心语法: 在src文件夹下创建一个 EventBus 的文件夹,里面放一个 index.js文件,用于定义事件总线 bus 对象。

js
import Vue from 'vue'
// 导出空白vue对象
export default new Vue()

创建一个子组件 a.vue

引入事件总线 /EventBus/index.js ,为其绑定事件,触发事件后用 $emit() 方法传递参数。

  • 参数1:自定义方法名称。
  • 参数2:变量。
html
<template>
  <div>
    <h1>我是a</h1>
    <button @click="give">点我</button>
  </div>
</template>

<script>
import eventBus from "../EventBus/index.js";
export default {
  data() {
    return {
      msg: "我是a",
    };
  },
  methods: {
    give() {
      eventBus.$emit("sendata", this.msg);
    },
  },
};
</script>

创建一个子组件 b.vue

引入事件总线 /EventBus/index.js ,创建好 b.vue 组件后立即监听,使用的方法是创建一个 created 函数,在其之内用 $on 方法来监听自定义事件方法,返回回调函数。

html
<template>
  <div>
    <h1>我是b</h1>
  </div>
</template>

<script>
import eventBus from "../EventBus/index.js";
export default {
  created() {
    eventBus.$on("sendata", (val) => {
      console.log(val);
    });
  },
};
</script>

App.vue 组件中引入两个子组件。

html
<template>
  <div>
    <aa></aa>
    <bb></bb>
  </div>
</template>

<script>
import aa from "./components/a.vue";
import bb from "./components/b.vue";
export default {
  components: { aa, bb },
};
</script>

整体流程如下所示。

组件传参.png

总结: 空的Vue对象, 只负责on注册事件, emit触发事件, 一定要确保$on先执行

拓展

原生事件绑定到组件

vue
<base_input @input.navite="inputFn"></base_input>

如上有一个 base_input 的子组件,想要使用原生 input 框自带的输入事件,可使用 .navite 修饰符。但是当该子组件根节点不是 input 输入框而是 label (如下),则加了修饰符也不会生效。

vue
<label>
  {{ label }}
  <input
    v-bind="$attrs"
    v-bind:value="value"
  >
</label>

因此官方特地提供了一个 $listeners ,它是一个对象,里面包含了作用在这个组件上的所有监听器。例如:

js
{
  focus: function (event) { /* ... */ }
  input: function (value) { /* ... */ },
}

有了这个 $listeners,可以配合 v-on="$listeners" 将所有的事件监听器指向这个组件的某个特定的子元素。对于类似 <input> 的你希望它也可以配合 v-model 工作的组件来说,为这些监听器创建一个类似下述 inputListeners 的计算属性通常是非常有用的:

js
Vue.component('base-input', {
  inheritAttrs: false,
  props: ['label', 'value'],
  computed: {
    inputListeners: function () {
      var vm = this
      // `Object.assign` 将所有的对象合并为一个新对象
      return Object.assign({},
        // 我们从父级添加所有的监听器
        this.$listeners,
        // 然后我们添加自定义监听器,
        // 或覆写一些监听器的行为
        {
          // 这里确保组件配合 `v-model` 的工作
          input: function (event) {
            vm.$emit('input', event.target.value)
          }
        }
      )
    }
  },
  template: `
    <label>
      {{ label }}
      <input
        v-bind="$attrs"
        v-bind:value="value"
        v-on="inputListeners"
      >
    </label>
  `
})

现在 <base-input> 组件是一个完全透明的包裹器了,也就是说它可以完全像一个普通的 <input> 元素一样使用了:所有跟它相同的 attribute 和监听器都可以工作,不必再使用 .native 监听器。