vue2.x学习笔记(十二)

接着前面的内容:https://www.cnblogs.com/yanggb/p/12592256.html

组件基础

组件化是vue的一个重要特性,也是vue学习中非常重要的一个知识点。

基础示例

这里有一个vue组件的实例:

// 定义一个名为button-counter的新组件
Vue.component('button-counter', {
  data: function () {
    return {
      count: 0
    }
  },
  template: '<button v-on:click="count++">You clicked me {{ count }} times.</button>'
})

从上面的实例可以看出,创建组件使用的是【Vue.component()】方法,组件的内容写在template属性中。组件是可复用的vue实例,且带有一个名字(在这个例子中是<button-counter>)。创建组件之后,我们就可以在一个通过new Vue创建的vue根实例中把这个组件作为自定义元素来使用。

<div id="components-demo">
  <button-counter></button-counter>
</div>
new Vue({ el: '#components-demo' })

因为组件是可复用的vue实例,因此它们与new Vue接收相同的选项,比如data、computed、watch、methods和生命周期钩子函数等,仅有的例外是像el这样根实例特有的选项。

组件的复用

在组件被创建了之后,可以将组件进行任意次数的复用。

<div id="components-demo">
  <button-counter></button-counter>
  <button-counter></button-counter>
  <button-counter></button-counter>
</div>

每使用一次组件,就会有一个它的新实例被创建,每个实例中独立维护自己的属性,因为每个实例都拥有自己的私有作用域,并不共享数据。

data必须是一个函数

在上面的例子中,即定义<button-counter>组件的时候,你可能会发现它的data并不是像这样直接提供一个对象:

data: {
  count: 0
}

取而代之的是,一个组件的data选项必须是一个函数,因此每个实例都可以维护一份被返回对象的独立拷贝:

data: function () {
  return {
    count: 0
  }
}

如果vue没有这条规则的话,组件实例之间就会相互影响,因为这个时候数据是共享的。而返回函数的目的就在于此,因为javascript的函数在执行的时候会创建自己的私有作用域,这样就将单个组件实例中的数据与其他组件实例中的数据隔离开来。弄不清楚的同学可以去了解一下javascript中关于闭包的相关知识,这是javascript语言设计自身的特性所决定的。

组件的组织

通常一个应用会以一棵嵌套的组件树的形式来组织:

例如,你可能会有页头、侧边栏、内容区等组件,每个组件又包含了其他的像导航链接、博文之类的组件。为了能够在模板中进行使用,这些组件必须要先注册以便vue能够识别。组件的注册类型可以分为两种:全局注册和局部注册。在前面的所有内容中,组件都只是通过【Vue.component()】方法进行全局注册的。

Vue.component('my-component-name', {
  // ... options ...
})

全局注册的组件可以用在其被注册之后的任何新创建(通过new Vue)的vue根实例,也包括其组件树中的所有子组件的模板中。

局部注册的方式和组件注册的其他知识将在后面深入了解组件的章节中获知,这里就先了解到这里。

通过prop向子组件传递数据

在上面提到了创建一个博文组件的事情。有一个问题是,如果你不能向这个组件传递某一篇博文的标题或者内容之类的我们想要展示的数据的话,它是没有办法使用的,因为其没有数据来源,并不知道数据到底从哪里来。因此,prop属性应运而生。

prop属性是vue提供给开发者可以在组件上注册的一些自定义属性。当一个值传递给一个prop属性的时候,它就会自动变成那个组件实例的一个属性。比如,为了给博文组件传递一个标题,我们可以使用一个【props】选项将其包含在该组件可以接受的prop列表中。

Vue.component('blog-post', {
  props: ['title'],
  template: '<h3>{{ title }}</h3>'
})

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

在一个prop被注册之后,你就可以像这样把数据作为一个自定义的属性传递到组件中。

<blog-post title="i like yanggb"></blog-post>
<blog-post title="i do like yanggb"></blog-post>
<blog-post title="i really like yanggb"></blog-post>

然而在一个典型的应用中,你可能在data里有一个博文的数组:

new Vue({
  el: '#blog-post-demo',
  data: {
    posts: [
      { id: 1, title: 'i like yanggb' },
      { id: 2, title: 'i do like yanggb' },
      { id: 3, title: 'i really like yanggb' }
    ]
  }
})

并想要为每篇博文渲染一个组件:

<blog-post
  v-for="post in posts"
  v-bind:key="post.id"
  v-bind:title="post.title"></blog-post>

如上所示,你会发现我们可以使用【v-bind】指令来动态传递prop。这在你一开始不清楚要渲染的具体内容的情况下,比如从一个api获取博文列表的时候,是非常有用的。

单个根元素

当构建一个<blog-post>组件的时候,你的模板最终会包含的东西远不止一个标题,最起码会包含这篇博文的正文:

<h3>{{ title }}</h3>
<div v-html="content"></div>

但是如果你在模板中尝试这样写的话,vue会显示一个错误,并解释【every component must have a single root element】,即每个组件必须只有一个根元素。出现这种情况的话,可以将模板的内容包裹在一个父元素内,以此来修复这个问题。

<div class="blog-post">
  <h3>{{ title }}</h3>
  <div v-html="content"></div>
</div>

监听子组件事件

在我们开发<blog-post>组件的时候,它的一些功能可能要求我们和父级的组件进行沟通。例如我们可能会引入一个辅助功能来放大博文的字号,同时让页面的其他部分保持默认的字号。

在父组件中,先通过添加一个postFontSize的数据属性来支持这个功能。

new Vue({
  el: '#blog-posts-events-demo',
  data: {
    posts: [/* ... */],
    postFontSize: 1
  }
})

然后就可以通过这个数据属性在模板中控制所有博文的字号(包裹组件)。

<div id="blog-posts-events-demo">
  <div :style="{ fontSize: postFontSize + 'em' }">
    <blog-post
      v-for="post in posts"
      v-bind:key="post.id"
      v-bind:post="post"
    ></blog-post>
  </div>
</div>

然后我们在每篇博文正文的前面添加一个按钮来放大字号:

Vue.component('blog-post', {
  props: ['post'],
  template: `
    <div class="blog-post">
      <h3>{{ post.title }}</h3>
      <button>
        Enlarge text
      </button>
      <div v-html="post.content"></div>
    </div>
  `
})

问题是这个按钮不会做任何事:

<button>
  Enlarge text
</button>

因此,在点击这个按钮的时候需要我们去告诉父级组件放大所有博文的文本,而vue实例提供了一个自定义事件的系统来解决这个问题。

父级组件可以像处理native dom事件一样通过【v-on】指令监听子组件实例的任意事件:

<blog-post
  ...
  v-on:enlarge-text="postFontSize += 0.1"></blog-post>

同时子组件可以通过调用内建的【$emit】方法并传入事件名称来触发一个事件:

<button v-on:click="$emit('enlarge-text')">
  Enlarge text
</button

有了这个监听器,父级组件就会接收该事件并更新postFontSize的值。

官方文档给出的这个例子并不是十分清晰,讲解也并不是十分到位,理解起来可能会有一些困难。其实简单点的理解就是,父级组件在页面上定义了一个方法,并将这个方法通过【v-on:方法名】指令传递到子组件中,然后子组件通过vue提供的全局属性【$emit】去获取到父级组件传递的这个方法并通过【v-on】指令绑定到点击事件中。后面会详细学到【$emit】的相关知识,了解了之后就明白了。

使用事件抛出一个值(传递参数)

有的时候用一个事件去抛出一个特定的值(传递参数)是非常有用的,例如我们可能像让<blog-post>组件决定它的文本要放大多少,这个时候就可以使用【$emit】的第二个参数来提供这个值:

<button v-on:click="$emit('enlarge-text', 0.1)">
  Enlarge text
</button>

然后当在父组件监听这个事件的时候,我们就可以通过【$event】来访问到被抛出的这个值:

<blog-post
  ...
  v-on:enlarge-text="postFontSize += $event"></blog-post>

或者,如果这个事件处理函数是一个方法:

<blog-post
  ...
  v-on:enlarge-text="onEnlargeText"></blog-post>

那么这个值就会被作为第一参数传入到这个方法中:

methods: {
  onEnlargeText: function (enlargeAmount) {
    this.postFontSize += enlargeAmount
  }
}

这里的【$event】也是vue提供的一个全局属性,本身的含义是事件本身,默认是触发事件的处理函数的第一参数。而在上面的例子中,子组件通过【$emit】传递参数的时候,覆盖了这个第一参数,因此$event变量的值变成了传递的值,属于一种特殊的用法。你可以尝试着在子组件中执行函数而不传递参数看看,你会发现这个时候【$event】的值是事件本身。

在组件上使用【v-model】指令

自定义事件也可以用于创建支持【v-model】指令的自定义输入组件。

首先需要知道的是,【v-model】指令用在表单元素上的时候实际上相当于【v-bind:value】指令和【v-on:input】指令的组合使用。

<input v-model="searchText">
<!-- 等价于 -->
<input
  v-bind:value="searchText"
  v-on:input="searchText = $event.target.value">

而当用在组件上的时候,【v-model】则是相当于:

<custom-input
  v-bind:value="searchText"
  v-on:input="searchText = $event"></custom-input>

因此,为了能让【v-model】指令在自定义组件上正常工作,这个组件内的<input>必须:

1.将其value属性绑定到一个名叫value的prop上。

2.在其input事件被触发的时候,将新的值通过自定义的input事件抛出。

Vue.component('custom-input', {
  props: ['value'],
  template: `
    <input
      v-bind:value="value"
      v-on:input="$emit('input', $event.target.value)">
  `
})

这样【v-model】指令就应该能在这个自定义组件上完美地工作起来了:

<custom-input v-model="searchText"></custom-input>

秘诀在于使用【$emit】抛出参数值。

通过插槽分发内容

和html元素一样,我们经常会需要向一个组件传递内容,像这样:

<alert-box>
  Something bad happened.
</alert-box>

因此,vue提供了一个自定义的<slot>元素来实现这一个功能。

Vue.component('alert-box', {
  template: `
    <div class="demo-alert-box">
      <strong>Error!</strong>
      <slot></slot>
    </div>
  `
})

插槽相当于一种占位符的机制,在使用子组件的时候父组件将需要的html内容插入到子组件的元素中,子组件就会自动替换占位符。插槽的内容十分重要,在后面会详细学习。

动态组件

有的时候,在不同的组件之间进行动态切换是非常有用的,比如在一个多标签的界面里,可以通过vue的<component>元素并添加一个特殊的【is】属性来达到切换不同的组件显示隐藏的目的。

<!-- 组件会在 `currentTabComponent` 改变时改变 -->
<component v-bind:is="currentTabComponent"></component>

在这个例子中,currentTabComponent属性可以包括已注册组件的名字或一个组件的选项对象。

要注意的是,这个属性可以用于常规的html元素,但是这些元素将会被视为组件,这就意味着所有的属性都会作为dom属性被绑定。对于像value这样的对象属性,如果你想要让其如预期般工作的话,需要使用【.prop】修饰符。

解析dom模板时候的注意事项

有些html元素,比如<ul>、<ol>、<table>和<select>等,对于哪些元素可以出现在其内部是严格限制的。而有些元素,诸如<li>、<tr>和<option>,也只能出现在其他特定的元素内部。这就会导致我们在使用这些有约束条件的元素的时候遇到一些问题。

<table>
  <blog-post-row></blog-post-row>
</table>

在上面的这个例子中,自定义组件<blog-pos-row>就会被视作无效的内容被提升到外部,并导致最终渲染结果出错。为了解决这样的问题,vue提供了一个特殊的【is】属性作为一个变通的方法。

<table>
  <tr is="blog-post-row"></tr>
</table>

属性值是组件名,这样相当于给组件套了一层外衣<tr>,也就避免了渲染出错的问题。

另外要注意的是,当我们从以下的来源使用模板的时候,这一条限制是不存在的:

1.字符串(例如template:'...')。

2.单文件组件(.vue文件)。

3.<script type="text/x-template">。

至此,vue的相关基础知识已经学得有七七八八,接下来就该学习一些比较深入的知识了。

"我还是很喜欢你,像雨落湖心泛涟漪,廖无声息。"

猜你喜欢

转载自www.cnblogs.com/yanggb/p/12595860.html