vue2.x组件编写总结

vue2.x组件编写总结

组件是可复用的 Vue 实例,它们与 new Vue 接收相同的选项,例如 datacomputedwatchmethods 以及生命周期钩子等。

比如我们写一个计数器组件。


<template>
  <button @click="counter++">点击按钮{{counter}}次button>
template>
<script>
export default {
  data() {
    return {
      counter: 0
    }
  }
}
script>

组件注册

局部注册

在写完一个组件后,需要进行注册才能使用。

在父组件中注册使用:

<template>
  <div>
    <h1>使用组件h1>
    <counter />
  div>
template>

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

在父组件中引入counter,之后在componets进行声明才能使用。

全局注册

vue2中是直接使用vue的原型中component进行注册.

import Vue from 'vue';
Vue.component('xxx', xxx);

vue3中,则是在实例中进行添加

import { createApp } from 'vue'

const app = createApp({})
app.component('xxx', xxx);

全局注册后,就不需要在每个vue组件声明了,可直接使用。

组件中的data说明

在前面的一些例子中,你可能已经注意到了data并不是一个对象,而是一个函数。这是为了防止组件中数据互相影响的问题。

如果data是一个对象

data: {
  counter: 0
}

那么其他组件都引用counter时,counter的值变动就会影响到所有的组件。而使用函数,那么在其他引用组件中都是返回一个独立的拷贝对象,不会相互影响。

数据传递

vue提供了prop可以使子组件能够接收父组件传递的数据。

prop 可以在组件上注册的一些自定义数据。当一个值传递给一个prop的时候,它就变成了那个组件实例的一个值。

在子组件上定义一个propmessage,表示子组件可以接收一个message值。


<template>
  <div>
    <h1>{{message}}h1>
    <button @click="counter++">点击按钮{{counter}}次button>
  div>
template>
<script>
export default {
  props: {
    message: {
      type: Number,
      default: 'no message'
    }
  },
  data() {
    return {
      counter: 0
    }
  }
}
script>

定义好子组件后,在父组件中传值

<template>
  <counter :message="title" />
template>
<script>
export default {
  data() {
    return {
      title: '来自父组件传递的消息'
    }
  }
}
script>

一个组件默认可以拥有任意数量的 prop,任何值都可以传递给任何 prop。在上述模板中,你会发现我们能够在组件实例中访问这个值,就像访问 data 中的值一样。

prop的类型

1. 数组

props: ['title', 'content']

使用数组格式的话并不能明确值的类型,不推荐使用

2. 对象

props: {
  title: String,
  content: String
}

当传递的值类型不符合时会在浏览器中进行提示。

对于父组件而言,当子组件需要传递的prop太多时,可以使用一个对象进行传递

<template>
  <counter v-bind="propObj" />
template>
<script>
export default {
  data() {
    return {
      propObj: {
        title: '来自父组件',
        content: '内容'
      }
    }
  }
}
script>

prop类型验证

我们可以为组件的 prop 指定验证要求,例如你知道的这些类型。如果有一个需求没有被满足,则 Vue 会在浏览器控制台中警告你。这在开发一个会被别人用到的组件时尤其有帮助。

为了定制 prop 的验证方式,你可以为 props 中的值提供一个带有验证需求的对象,而不是一个字符串数组。

props: {
  // 基础类型
  prop1: String,
  // 多个类型
  prop2: [String, Number],
  // 必填
  prop3: {
    type: String,
    required: true
  },
  // 带有默认值
  prop4: {
    type: Number,
    default: 1
  },
  // 带有默认对象
  prop5: {
    type: Object,
    // 对象或数组默认值必须从一个工厂函数获取
    default: function () {
      return { message: 'hello' }
    }
  },
  // 自定义验证函数
  prop6: {
    validator: function (value) {
      // 这个值必须匹配下列字符串中的一个
      return ['success', 'warning', 'danger'].indexOf(value) !== -1
    }
  }
}

prop 验证失败的时候,(开发环境构建版本的) Vue 将会产生一个控制台的警告。

proptype类型可以是以下几种:

  • String
  • Number
  • Boolean
  • Array
  • Object
  • Date
  • Function
  • Symbol

其次,type 还可以是一个自定义的构造函数,并且通过 instanceof 来进行检查确认。

class Animal {
  constructor(options) {}
}
props: {
  animal: Animal
}

prop传递的数据是单向的

所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。这样会防止从子组件意外变更父级组件的状态,从而导致你的应用的数据流向难以理解。

当需要对prop传递的值进行更改时,最好在data中重新定义一个数据并将prop作为初始值。

props: {
  value: Number
},
data() {
  return {
    counter: this.value
  }
}

需要注意的是,如果传递的是数组或者对象,由于父子组件的数据是通过引用传入的,在子组件中改变变更这个对象或数组本身将会影响到父组件的状态。

未在prop中声明的值

当父组件传递了一些未在子组件中定义的prop数据时,在子组件中可以通过this.$attr访问到那些未声明的属性(除style和class外)

在父组件中传递了多个数据

<template>
  <son title="title" content="content" />
template>

在子组件中只定义了一个title, 可以通过this.$attrs获取到其他未声明的prop

 
<template>
  <div>
    <h1>{{title}}h1>
  div>
template>
<script>
export default {
  props: {
    title: String
  },
  created() {
    console.log(this.$attrs)   // { content: "content" }
  }
}
script>

同时未声明的prop也会渲染在子组件的根节点中。
比如上面的例子中content未声明,子组件的渲染结果为

<div content="content">
  <h1>titleh1>
div>

对于一些组件而言,不希望接收未声明的prop,可以通过设置inheritAttrs: false

// son.vue
inheritAttrs: false,
props: {
  title: String
},

此时根节点就不会存在未声明的prop
子组件的渲染结果为

<div>
  <h1>titleh1>
div>

事件监听

比如说子组件需要改变prop,但是自身不能修改这些数据,因此可以通过事件来实现。

 
<template>
  <div>
    <h1>{{title}}h1>
    <button @click="$emit('changeTitle')">改变titlebutton>
  div>
template>
<script>
export default {
  props: {
    title: String
  },
}
script>

使用$emit可以触发父组件定义在子组件上的方法。


<template>
  <son @changeTitle="titleChange" :title="" >
template>
<script>
export default {
  data() {
    return {
      title: '标题'
    }
  },
  titleChange() {
    this.title += '1';
  }
}
script>

同时$emit还可以传递参数,$emit('methodName', params)

在父组件中接收

titleChange(params) {
  console.log(params)
}

通过插槽分发内容

HTML 元素一样,我们经常需要向一个组件传递内容。

<son>
  <h2>插槽的使用h2>
son>

在子组件中使用来安排插槽的位置


<template>
  <div>
    <h1>{{title}}h1>
    <slot>slot>
  div>
template>

当组件渲染时,将会被替换为

插槽的使用

插槽作用域

父级模板里的所有内容都是在父级作用域中编译的;子模板里的所有内容都是在子作用域中编译的。

虽然插槽时用来替换父组件中的元素,但是插槽中的只能访问父组件中定义的数据,不能访问子组件的。

在父组件中没有定义value

<son>
  <h2>插槽的使用 {{value}}h2>
son>

但是在子组件中定义了value

// son.vue
data() {
  return {
    value: 1
  }
}

这时访问value会报错。

后备内容

你也可以理解成插槽默认值


<template>
  <div>
    <h1>{{title}}h1>
    <slot>dafault textslot>
  div>
template>

当父组件没有传入任何插槽内容时,默认会渲染出一个文本节点(内容为slot标签内的文本)

渲染结果

<div>
  <h1>titleh1>
  dafault text
div>

具名插槽

有时候在组件中可能传入多个插槽,为了将插槽放入正确的位置,我们需要给插槽命名。

比如我们有一个layout组件

<div class="container">
  <header>
    
  header>
  <main>
    
  main>
  <footer>
    
  footer>
div>

如果没有给slot命名,直接插入3个slot

<div class="container">
  <header>
    <slot>slot>
  header>
  <main>
    <slot>slot>
  main>
  <footer>
    <slot>slot>
  footer>
div>

在父组件中传值

<layout>
  <div>headerdiv>
  <div>contentdiv>
  <div>footerdiv>
layout>

那么layout中的所有的内容都会被当成一个名为dafaultslot,最终的渲染结果为

<div class="container">
  <header>
    <div>headerdiv>
    <div>contentdiv>
    <div>footerdiv>
  header>
  <main>
    <div>headerdiv>
    <div>contentdiv>
    <div>footerdiv>
  main>
  <footer>
    <div>headerdiv>
    <div>contentdiv>
    <div>footerdiv>
  footer>
div>

因此我们可以给每个slot添加一个name属性用来保证slot被插入到正确的位置。


<div class="container">
  <header>
    <slot name="header">slot>
  header>
  <main>
    <slot>slot>
  main>
  <footer>
    <slot name="footer">slot>
  footer>
div>

在父组件中给标签添加slot属性,值与子组件中的name值相等。

<layout>
  <div slot="header">headerdiv>
  <div>contentdiv>
  <div slot="footer">footerdiv>
layout>

通过具名即使在父组件中我们把插槽的顺序打乱,最终的渲染结果也没啥问题。未传slot值默认为dafault

<layout>
  <div>contentdiv>
  <div slot="footer">footerdiv>
  <div slot="header">headerdiv>
layout>

但是由于vue2.6.0引入v-slot指令,能够提供更好的支持 slotslot-scopeAPI 替代方案。而且vue3已经废弃使用slot来给插槽命名,因此不在推荐使用slot的方式,而是改用v-slot指令。

在向具名插槽提供内容的时候,我们可以在一个元素上使用 v-slot 指令,并以 v-slot 的参数的形式提供其名称。

<layout>
  <template v-slot:header>
    <div>headerdiv>
  template>
  
  <div>contentdiv>
  <template v-slot:footer>
    <div>footerdiv>
  template>
layout>

任何没有被包裹在带有 v-slot 的元素中的内容都会被视为默认插槽的内容。

注意 v-slot 只能添加在