Vue组件的使用及个人理解与介绍


Posted in Javascript onFebruary 09, 2019

组件的基本使用

注册组件

注册组件就是利用Vue.component()方法,先传入一个自定义组件的名字,然后传入这个组件的配置。

Vue.component('mycomponent',{
  template: `<div>这是一个自定义组件</div>`,
  data () {
   return {
    message: 'hello world'
   }
  }
 })

如上方式,就已经创建了一个自定义组件,然后就可以在Vue实例挂在的DOM元素中使用它。

<div id="app">
  <mycomponent></mycomponent>
  <my-component></my-component>
</div>
<script>
 var app = new Vue({
  el: '#app',
  data: {
  },
  components: {
   'my-component': {
    template: `<div>这是一个局部的自定义组件,只能在当前Vue实例中使用</div>`,
   }
  }
 })
</script>

直接使用Vue.component()创建的组件,所有的Vue实例都可以使用。还可以在某个Vue实例中注册只有自己能使用的组件。

var app = new Vue({
  el: '#app',
  data: {
  },
  components: {
   'my-component': {
    template: `<div>这是一个局部的自定义组件,只能在当前Vue实例中使用</div>`,
   }
  }
 })

模板的要求

注意:组件的模板只能有一个根元素。下面的情况是不允许的。

template: `<div>这是一个局部的自定义组件,只能在当前Vue实例中使用</div>
      <button>hello</button>`,

组件中的data必须是函数

可以看出,注册组件时传入的配置和创建Vue实例差不多,但也有不同,其中一个就是data属性必须是一个函数。
这是因为如果像Vue实例那样,传入一个对象,由于JS中对象类型的变量实际上保存的是对象的引用,所以当存在多个这样的组件时,会共享数据,导致一个组件中数据的改变会引起其他组件数据的改变。

而使用一个返回对象的函数,每次使用组件都会创建一个新的对象,这样就不会出现共享数据的问题来了。

关于DOM模板的解析

当使用 DOM 作为模版时 (例如,将 el 选项挂载到一个已存在的元素上), 你会受到 HTML 的一些限制,因为 Vue 只有在浏览器解析和标准化 HTML 后才能获取模板内容。尤其像这些元素 <ul>,<ol>,<table>,<select> 限制了能被它包裹的元素,而一些像 <option> 这样的元素只能出现在某些其它元素内部。

在自定义组件中使用这些受限制的元素时会导致一些问题,例如:

<table>
 <my-row>...</my-row>
</table>

自定义组件 <my-row> 被认为是无效的内容,因此在渲染的时候会导致错误。这时应使用特殊的 is 属性:

<table>
 <tr is="my-row"></tr>
</table>

也就是说,标准HTML中,一些元素中只能放置特定的子元素,另一些元素只能存在于特定的父元素中。比如table中不能放置div,tr的父元素不能div等。所以,当使用自定义标签时,标签名还是那些标签的名字,但是可以在标签的is属性中填写自定义组件的名字。

应当注意,如果您使用来自以下来源之一的字符串模板,这些限制将不适用:

<script type="text/x-template">
JavaScript 内联模版字符串
.vue 组件
其中,前两个模板都不是Vue官方推荐的,所以一般情况下,只有单文件组件.vue可以忽略这种情况。

组件的属性和事件

在html中使用元素,会有一些属性,如class,id,还可以绑定事件,自定义组件也是可以的。当在一个组件中,使用了其他自定义组件时,就会利用子组件的属性和事件来和父组件进行数据交流。
<img src="/assets/images/props-events.png"/>

如上如所示,父子组件之间的通信就是 props down,events up,父组件通过 属性props向下传递数据给子组件,子组件通过 事件events 给父组件发送消息。
比如,子组件需要某个数据,就在内部定义一个prop属性,然后父组件就像给html元素指定特性值一样,把自己的data属性传递给子组件的这个属性。
而当子组件内部发生了什么事情的时候,就通过自定义事件来把这个事情涉及到的数据暴露出来,供父组件处理。

<my-component v-bind:foo="baz" v-on:event-a="doThis(arg1,...arg2)"></my-component>

如上代码,

foo是<my-component>组件内部定义的一个prop属性,baz是父组件的一个data属性,
event-a是子组件定义的一个事件,doThis是父组件的一个方法
过程就是这样:

父组件把baz数据通过prop传递给子组件的foo;
子组件内部得到foo的值,就可以进行相应的操作;
当子组件内部发生了一些变化,希望父组件能知道时,就利用代码触发event-a事件,把一些数据发送出去
父组件把这个事件处理器绑定为doThis方法,子组件发送的数据,就作为doThis方法的参数被传进来
然后父组件就可以根据这些数据,进行相应的操作

属性Props

Vue组件通过props属性来声明一个自己的属性,然后父组件就可以往里面传递数据。

Vue.component('mycomponent',{
  template: '<div>这是一个自定义组件,父组件传给我的内容是:{{myMessage}}</div>',
  props: ['myMessage'],
  data () {
   return {
    message: 'hello world'
   }
  }
 })

然后调用该组件

<div id="app">
  <mycomponent my-message="hello"></mycomponent>
</div>

注意,由于HTML特性是不区分大小写的,所以传递属性值时,myMessage应该转换成 kebab-case (短横线隔开式)my-message="hello"。

v-bind绑定属性值
这里说一下v-bind绑定属性值的一个特性:一般情况下,使用v-bind给元素特性(attribute)传递值时,Vue会将""中的内容当做一个表达式。
比如:

<div attr="message">hello</div>

上面这样,div元素的attr特性值就是message。

而这样

<div v-bind:attr="message">hello</div>

这里的message应该是Vue实例的data的一个属性,这样div元素的attr特性值就是message这个属性的值。

之所以说是一般情况,是因为class和style特性并不是这样。用v-bind:class和class传入正常的类名,效果是一样的,因为对于这两个特性,Vue采用了合并而不是替换的原则。

动态绑定特性值

根据上面,想要把父组件的属性绑定到子组件,应该使用v-bind,这样,父组件中数据改变时能反映到子组件。
注意,根据父组件传递给子组件的属性类型的不同,当在子组件中更改这个属性时,会有以下两种情况:

当父组件传递的属性是引用类型时,在子组件中更改相应的属性会导致父组件相应属性的更改。

<div id="app2">
   <div>这是父组件的parentArray:{{parentArray}}</div>
   <my-component :child-array="parentArray"></my-component>
  </div>
  <script>
   Vue.component('my-component', {
    template: `
    <div>这是接收了父组件传递值的子组件的childArray: {{childArray}} <br>
      <button type="button" @click="changeArray">
      点击我改变父元素的parentArray</button>
     </div>`,
    props: ['childArray'],
    data () {
     return {
      counter: 1
     }
    },
    methods: {
     changeArray () {
      this.childArray.push(this.counter++)
     }
    }
   })
   new Vue({
    el: '#app2',
    data: {
     parentArray: []
    }
   })
  </script>

当父组件传递值为基本类型时,在子组件中更改这个属性会报错。正确的做法是,在父组件中绑定属性值时,加上.sync修饰符。

<my-component :child-array.sync="parentArray"></my-component>

然后在子组件中改变相应的属性

methods: {
   changeArray () {
    this.counter++
    this.$emit('update:childArray', this.counter)
   }
  }

子组件希望对传入的prop进行操作

一般来说,是不建议在子组件中对父组件中传递来的属性进行操作的。如果真的有这种需求,可以这样:

父组件传递了一个基本类型值,那么可以在子组件中创建一个新的属性,并以传递进来的值进行初始化,之后就可以操作这个新的属性了

props: ['initialCounter'],
data: function () {
 return { counter: this.initialCounter }
}

父组件传递了一个引用类型值,为了避免更改父组件中相应的数据,最好是对引用类型进行复制。复杂的情况,肯定应该是深复制。

给子组件传递正确类型的值

同样是上面的原因,静态的给子组件的特性传递值,它都会把他当做一个字符串。

<!-- 传递了一个字符串 "1" -->
<comp some-prop="1"></comp>

子组件中,特性的值是字符串 "1" 而不是 number 1。如果想传递正确的数值,应该使用v-bind传递,这样就能把传递的值当做一个表达式来处理,而不是字符串。

<!-- 传递实际的 number 1 -->
<comp v-bind:some-prop="1"></comp>

Prop验证

我们可以给组件的props属性添加验证,当传入的数据不符合要求时,Vue会发出警告。

Vue.component('example', {
 props: {
  // 基础类型检测 (`null` 意思是任何类型都可以)
  propA: Number,
  // 多种类型
  propB: [String, Number],
  // 必传且是字符串
  propC: {
   type: String,
   required: true
  },
  // 数字,有默认值
  propD: {
   type: Number,
   default: 100
  },
  // 数组/对象的默认值应当由一个工厂函数返回
  propE: {
   type: Object,
   default: function () {
    return { message: 'hello' }
   }
  },
  // 自定义验证函数
  propF: {
   validator: function (value) {
    return value > 10
   }
  }
 }
})

type 可以是下面原生构造器:

String
Number
Boolean
Function
Object
Array
Symbol
type 也可以是一个自定义构造器函数,使用 instanceof 检测。

// 自定义Person构造器
 function Person(name, age) {
  this.name = name
  this.age = age
 }
 Vue.component('my-component', {
  template: `<div>名字: {{ person-prop.name }}, 年龄: {{ person-prop.age }} </div>`,
  props: {
   person-prop: {
    type: Person   // 指定类型
   }
  }
 })
 new Vue({
  el: '#app2',
  data: {
   person: 2    // 传入Number类型会报错
  }
 })

非Prop类型的属性

也可以像在html标签中添加data-开头的自定义属性一样,给自定义组件添加任意的属性。而不仅限于data-*形式,这样做的话,Vue会把这个属性放在自定义组件的根元素上。一个自定义组件的模板只能有一个根元素。

覆盖非Prop属性

如果父组件向子组件的非prop属性传递了值,那么这个值会覆盖子组件模板中的特性。

<div id="app3">
  <my-component2 att="helloParent"></my-component2>
</div>
<script>
 Vue.component('my-component2', {
  template: `<div att="helloChild">子组件原有的特性被覆盖了</div>`
 })
 new Vue({
  el: '#app3'
 })
</script>

上面渲染的结果是,div的att属性是helloParent。
注意:前面已经提到过,覆盖原则对于class和style不适用,而是采用了合并(merge)的原则。

<div id="app3">
  <my-component2 att="helloParent" class="class2" style="color: red;"></my-component2>
</div>
<script>
 Vue.component('my-component2', {
  template: `<div att="helloChild" class="class1" style="background: yellow;">子组件原有的特性被覆盖了</div>`
 })
 new Vue({
  el: '#app3'
 })
</script>

上面的渲染结果是,div的类名是class1 class2,行内样式是color:red; background:yellow;。

自定义事件

通过prop属性,父组件可以向子组件传递数据,而子组件的自定义事件就是用来将内部的数据报告给父组件的。

<div id="app3">
  <my-component2 v-on:myclick="onClick"></my-component2>
</div>
<script>
 Vue.component('my-component2', {
  template: `<div>
  <button type="button" @click="childClick">点击我触发自定义事件</button>
  </div>`,
  methods: {
   childClick () {
    this.$emit('myclick', '这是我暴露出去的数据', '这是我暴露出去的数据2')
   }
  }
 })
 new Vue({
  el: '#app3',
  methods: {
   onClick () {
    console.log(arguments)
   }
  }
 })
</script>

如上所示,共分为以下步骤:

子组件在自己的方法中将自定义事件以及需要发出的数据通过以下代码发送出去

this.$emit('myclick', '这是我暴露出去的数据', '这是我暴露出去的数据2')

第一个参数是自定义事件的名字
后面的参数是依次想要发送出去的数据
父组件利用v-on为事件绑定处理器

<my-component2 v-on:myclick="onClick"></my-component2>

这样,在Vue实例的methods方法中就可以调用传进来的参数了

注意: 在使用v-on绑定事件处理方法时,不应该传进任何参数,而是直接写v-on:myclick="onClick",不然,子组件暴露出来的数据就无法获取到了

绑定原生事件

如果想在某个组件的根元素上监听一个原生事件。可以使用 .native 修饰 v-on

<my-component v-on:click.native="doTheThing"></my-component>

探究v-model

v-model可以对表单控件实现数据的双向绑定,它的原理就是利用了绑定属性和事件来实现的。比如input控件。不使用v-model,可以这样实现数据的双向绑定:

<div id="app4">
  <input type="text" v-bind:value="text" v-on:input="changeValue($event.target.value)">
  {{text}}
 </div>
 <script>
   new Vue({
    el: '#app4',
    data: {
     text: '444'
    },
    methods: {
     changeValue (value) {
      this.text = value
     }
    }
   })
 </script>

上面的代码同样实现了数据的双向绑定。其本质就是:

把input的value特性绑定到Vue实例的属性text上,text改变,input中的内容也会改变
然后把表单的input事件处理函数设置为Vue实例的一个方法,这个方法会根据输入参数改变Vue中text`的值
相应的,在input中输入内容时,触发了input事件,把event.target.value传给这个方法,最后就实现了改变绑定的数据的效果。
而v-model就是上面这种方式的语法糖,也就是把上面的写法封装了一下,方便我们使用。

使用自定义事件创建自定义的表单输入组件

理解了v-model的内幕,也就可以把这个效果用在自定义表单组件上了。
来实现一个简单的只能输入hello的表单输入组件。

<div id="app5">
  <my-component3 v-model="hello"></my-component3>
  <div>{{hello}}</div>
</div>
<script>
 Vue.component('my-component3', {
  template: `<input ref="input" type="text" :value="value" @input="checkInput($event.target.value)">`,
  props: ['value'],
  methods: {
   checkInput (value) {
    var hello = 'hello'
    if (!hello.includes(value)) {
     this.$emit('input', hello)
     this.$refs.input.value = hello
    } else {
     this.$emit('input', value)
    }
   }
  }
 })
 new Vue({
  el: '#app5',
  data: {
   hello: ''
  }
 })
</script>

定制组件的v-model

默认情况下,一个组件的 v-model 会使用 value 属性和 input 事件,但是诸如单选框、复选框之类的输入类型可能把 value 属性用作了别的目的。model 选项可以回避这样的冲突:

Vue.component('my-checkbox', {
 model: {
  prop: 'checked',  // 将输入的特性改为checked
  event: 'change'    // 触发的自定义事件类型为change
 },
 props: {
  checked: Boolean,
  // this allows using the `value` prop for a different purpose
  value: String
 }
})

这样设置的话,

<my-checkbox v-model="foo" value="some value"></my-checkbox>

上面的代码就等同于

<my-checkbox :checked="foo" @change="val => { foo = val }" value="some value"></my-checkbox>

实际使用时,与之前不同的地方是:

把子组件中接收外部数据的prop属性改为checked
向父组件发出事件时,事件类型应改为change

Vue.component('my-component3', {
  template: `<input ref="input" type="text" :value="checked" @input="checkInput($event.target.value)">`,
  props: ['checked'],    // 属性名改变
  model: {
   prop: 'checked',
   event: 'change'
  },
  methods: {
   checkInput (value) {
    var hello = 'hello'
    if (!hello.includes(value)) {
     this.$emit('change', hello)  // 事件类型改变
     this.$refs.input.value = hello
    } else {
     this.$emit('change', value) // 事件类型改变
    }
   }
  }
 })

动态组件

通过使用保留的 <component> 元素,动态地绑定到它的 is 特性,可以让多个组件使用同一个挂载点,并动态切换:

<div id="app6">
  <select v-model="currentComponent">
   <option value="home">home</option>
   <option value="post">post</option>
   <option value="about">about</option>
  </select>
  <component :is="currentComponent"></component>
 </div>
 <script>
   new Vue({
    el: '#app6',
    data: {
     currentComponent: 'home'
    },
    components: {
     home: {
      template: `<header>这是home组件</header>`
     },
     post: {
      template: `<header>这是post组件</header>`
     },
     about: {
      template: `<header>这是about组件</header>`
     }
    }
   })
</script>

也可以直接绑定到组件对象上:

var Home = {
 template: `<header>这是home组件</header>`
}
new Vue({
 el: '#app6',
 data: {
  currentComponent: Home
 }
})

保留切换出去的组件,避免重新渲染

如果把切换出去的组件保留在内存中,可以保留它的状态或避免重新渲染。为此可以添加一个 keep-alive 指令参数:

<keep-alive>
 <component :is="currentComponent">
  <!-- 非活动组件将被缓存! -->
 </component>
</keep-alive>

使用slot分发内容

终于到了基本知识的最后一个了。官网写的很详细。

单个slot

上面用到的很多组件的使用方式是这样的:

<component></component>

也就是说组件中是空的,没有放置任何文本或元素。但是原生的html元素都是可以进行嵌套的,div里面放table
什么的。自定义组件开闭标签之间也可以放置内容,不过需要在定义组件时使用slot。

slot相当于子组件设置了一个地方,如果在调用它的时候,往它的开闭标签之间放了东西,那么它就把这些东西放到slot中。

当子组件中没有slot时,父组件放在子组件标签内的东西将被丢弃;
子组件的slot标签内可以放置内容,当父组件没有放置内容在子组件标签内时,slot中的内容会渲染出来;
当父组件在子组件标签内放置了内容时,slot中的内容被丢弃
子组件的模板:

<div>
 <h2>我是子组件的标题</h2>
 <slot>
  只有在没有要分发的内容时才会显示。
 </slot>
</div>

父组件模板:

<div>
 <h1>我是父组件的标题</h1>
 <my-component>
  <p>这是一些初始内容</p>
 </my-component>
</div>

渲染结果:

<div>
 <h1>我是父组件的标题</h1>
 <div>
  <h2>我是子组件的标题</h2>
  <p>这是一些初始内容</p>
 </div>
</div>

具名slot

slot可以有很多个。那么子组件对于父组件放置的多余的内容如何放到各个slot中呢?方法就是子组件给每个slot起一个名字name,父组件放置多余的元素时,给每个元素的slot属性分配一个代表slot的名字。到时候,多余的内容就会根据自己的slot属性去找具有对应名字的slot元素。

注意:

子组件可以有一个匿名的slot,当分发的多余内容找不到对应的slot时,就会进入这里面
如果子组件没有匿名的slot,当分发的多余内容找不到对应的slot时,就会被丢弃
例如,假定我们有一个 app-layout 组件,它的模板为:

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

父组件模版:

<app-layout>
 <h1 slot="header">这里可能是一个页面标题</h1>
 <p>主要内容的一个段落。</p>
 <p>另一个主要段落。</p>
 <p slot="footer">这里有一些联系信息</p>
</app-layout>

渲染结果为:

<div class="container">
 <header>
  <h1>这里可能是一个页面标题</h1>
 </header>
 <main>
  <p>主要内容的一个段落。</p>
  <p>另一个主要段落。</p>
 </main>
 <footer>
  <p>这里有一些联系信息</p>
 </footer>
</div>

作用域插槽

作用域插槽也是一个插槽slot,但是他可以把数据传递给到父组件的特定元素内,然后有父组件决定如何渲染这些数据。

首先,子组件的slot需要有一些特性(prop)

Vue.component('my-component4', {
   template: `<div>
    <slot :text="hello" message="world"></slot>
   </div>`,
   data () {
    return {
     hello: [1,'2']
    }
   }
  })

父组件在调用子组件时,需要在里面添加一个template元素,并且这个template元素具有scope特性

<div id="app7">
 <my-component4>
  <template scope="props">
  </template>
 </my-component4>
 </div>

scope特性的值,就代表了所有子组件传过来的数据组成的对象。相当于

props = {
  text: '',
  message: ''
}

最后,父组件就可以在template中渲染子组件传过来的数据了

<div id="app7">
  <my-component4>
   <template slot-scope="props">
    <span>{{props.text}}</span>
    <span>{{props.message}}</span>
   </template>
  </my-component4>
 </div>

最新的Vue支持将作用域插槽的属性解构。所以上述代码可以简写为:

<div id="app7">
  <my-component4>
   <template slot-scope="{text, message}">
    <span>{{text}}</span>
    <span>{{message}}</span>
   </template>
  </my-component4>
 </div>

作用域插槽也是插槽,只不过是多加了些特性,然后父组件多进行了些处理。

以上即是个人对Vue中的组件的理解,希望对大家有所帮助

Javascript 相关文章推荐
javascript中关于break,continue的特殊用法与介绍
May 24 Javascript
jquery内置验证(validate)使用方法示例(表单验证)
Dec 04 Javascript
Jquery创建一个层当鼠标移动到层上面不消失效果
Dec 12 Javascript
一个JavaScript用逗号分割字符串实例
Sep 22 Javascript
js随机生成网页背景颜色的方法
Feb 26 Javascript
在easyUI开发中,出现jquery.easyui.min.js函数库问题的解决办法
Sep 11 Javascript
JavaScript验证Email(3种方法)
Sep 21 Javascript
gulp-uglify 与gulp.watch()配合使用时报错(重复压缩问题)
Aug 24 Javascript
原生js图片轮播效果实现代码
Oct 19 Javascript
jQuery无刷新上传之uploadify简单代码
Jan 17 Javascript
BootStrap 弹出层代码
Feb 09 Javascript
解决element-ui的下拉框有值却无法选中的情况
Nov 07 Javascript
Vue自定义指令写法与个人理解
Feb 09 #Javascript
Vue指令指令大全
Feb 09 #Javascript
vue基于两个计算属性实现选中和全选功能示例
Feb 08 #Javascript
vue计算属性get和set用法示例
Feb 08 #Javascript
vue多次循环操作示例
Feb 08 #Javascript
JS散列表碰撞处理、开链法、HashTable散列示例
Feb 08 #Javascript
ES6 对象的新功能与解构赋值介绍
Feb 05 #Javascript
You might like
PHP Zip压缩 在线对文件进行压缩的函数
2010/05/26 PHP
深入分析php中接口与抽象类的区别
2013/06/08 PHP
Prototype Template对象 学习
2009/07/19 Javascript
jquery设置控件位置的方法
2013/08/21 Javascript
基于Bootstrap实现Material Design风格表单插件 附源码下载
2016/04/18 Javascript
Bootstrap前端开发案例二
2016/06/17 Javascript
JavaScript检测原始值、引用值、属性
2016/06/20 Javascript
AngularJS ng-change 指令的详解及简单实例
2016/07/30 Javascript
JS简单获取及显示当前时间的方法
2016/08/03 Javascript
详解在 Angular 项目中添加 clean-blog 模板
2017/07/04 Javascript
聊聊Vue.js的template编译的问题
2017/10/09 Javascript
mint-ui在vue中的使用示例
2018/04/05 Javascript
解决Vue在封装了Axios后手动刷新页面拦截器无效的问题
2018/11/08 Javascript
Vue实现回到顶部和底部动画效果
2019/07/31 Javascript
Jquery 动态添加元素并添加点击事件实现过程解析
2019/10/12 jQuery
解决vue admin element noCache设置无效的问题
2019/11/12 Javascript
Vue项目结合Vue-layer实现弹框式编辑功能(实例代码)
2020/03/11 Javascript
详解JavaScript匿名函数和闭包
2020/07/10 Javascript
ES6学习教程之Promise用法详解
2020/11/22 Javascript
javascript实现简单留言板案例
2021/02/09 Javascript
Django模板Templates使用方法详解
2019/07/19 Python
python输出第n个默尼森数的实现示例
2020/03/08 Python
Python3 selenium 实现QQ群接龙自动化功能
2020/04/17 Python
基于python3.7利用Motor来异步读写Mongodb提高效率(推荐)
2020/04/29 Python
CSS3教程(2):网页边框半径和网页圆角
2009/04/02 HTML / CSS
css 省略号 css3让多余的字符串消失并附加省略号的实现代码
2013/02/07 HTML / CSS
全球知名巧克力品牌:Godiva
2016/07/22 全球购物
运行时异常与一般异常有何异同?
2014/01/05 面试题
介绍一下Make? 为什么使用make
2016/07/31 面试题
大学生职业规划书的范本
2014/02/18 职场文书
国际残疾人日广播稿范文
2014/10/09 职场文书
南京导游词
2015/02/03 职场文书
2015年感恩母亲节的演讲稿
2015/03/18 职场文书
2016年师德师风学习心得体会
2016/01/12 职场文书
小学美术教学反思
2016/02/17 职场文书
原来实习报告是这样写的呀!
2019/07/03 职场文书