Vue2.0学习--基础--9--组件基础

(day8-2)

在前面我们其实已经接触到了一些关于组件的知识。

关于它的定义,创建,以及简单的使用。

组件是可复用的Vue实例,且带有一个名字(Vue.component()中的第一个参数)。

在实际的开发过程中,可能会遇见很多重复的功能,我们可以封装这么一些Vue组件,来重复使用。

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

 组件的复用、组件的组织

通过Props向子组件传递数据、单个根元素

监听子组件事件

通过插槽分发内容

动态组件

解析DOM模板时的注意事项

一、组件的复用、组件的组织

1、组件的复用

可以将组将进行任意次数的复用

<div>
    <my-component></my-component>
    <my-component></my-component>
    <my-component></my-component>
    <my-component></my-component>
</div>

 每个组件都会各自维护它的count。因为你每用一次组件,就会有一个它的新实例被创建。

 # data 必须是一个函数

我们知道,定义组件时Vue.component()第二个参数,接收的选项和 new Vue 是一样的。但是,组件中 data 选项并不是像这样直接提供一个对象
取而代之的是,一个组件的 data 选项必须是一个函数因此每个实例可以维护一份被返回对象的独立拷贝

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

如果Vue没有这条规则,也就是说 data 不是一个函数,而是一个对象,那么每一个组件实例,他们共享着这个 data 对象的数据,所以这些不同组件中的数据变化将会相互的影响

 2、组件的组织

 前面第一天已经了解过了,一般,一个应用会以一颗嵌套的组件树的形式来组织

 为了能在模板中使用,这些组件必须先注册以便Vue能够识别。这里有两种组件的注册类型:全局注册局部注册

 到现在为止,我们的组件都是通过 Vue.component 全局注册的。

全局注册的组件可以在被注册之后,可以被任何新创建的Vue根实例使用,包括上图组件树中的所有子组件的模板中。

现在只需要知道这么多,之后在后面的 深入了解组件——组件注册 会有更详细的介绍。

二、通过Prop向子组件传递数据、单个根元素

 1、通过Prop向组件传递数据

 在前面我们已经接触到的创建组件中,我们知道我们的自定组件是一个Vue实例,如果不将Vue数据传递给我们创建的自定义组件中,我们的自定义组件是没有数据的。

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

一个组件默认可以拥有任意数量的prop,任何值都可以传递给任何prop。

就比如说前面 v-for 循环创造模板组件的,需要通过设定一个props 值,来和定义在控制着元素的Vue实例中的数组项 绑定,从而读取到该数组项的数据。 (具体的实例在前面其实就已经有了,这里就不赘述了)

  # 当使用自定义组件的属性 绑定  的内容很复杂,写起来会非常麻烦:

<todo-item v-for="post in posts"
           v-bind:key="posts.id" 
           v-bind:title="posts.title" 
           v-bind:content="posts.content" 
           v-bind:publishAt="posts.publishAt" 
           v-bind:comment="posts.comment"
           >
    
</todo-item>

这样写就是这么一个意思:

 这用一个一个的绑定有点麻烦,其实可以直接在props中定义一个 “post” 属性,然后绑定与 post 绑定就好了:

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

 这样写就很简单了,大概是这么个意思:

到目前为止只要知道这多就行了,后面还会继续讨论这个 组件属性。

2、单个根元素

 在自定义组件中,用 template 属性来定义我们自定义组件的模板,如果这个模板比较复杂,需要多个HTML元素,那么这个时候,一定要记住给模板中的所有元素外包一个根元素。否则会报错。

Vue.component("todo-item",{
    template:`
        <div class="blog-post">
            <h3>{{ post.title}}</h3>
            <div v-html="post.content"></div>
        </div>
    `
});

这里的 <div class="blog-post"></div> 就是我们的根元素。

注意:这里的 template 中定义的模板使用了JavaScript的模板字符串来让多行的模板更易读。

三、、监听子组件事件

很简单,前面我们学过 v-on: 指令,在前面的所有例子中, v-on:  后面接的都是我们在“DOM3级事件”所定义的事件类型之类的事件(比如click、keyup等)。那如果我们需要触发自定义事件怎么办?(也就是我们需要触发事件的事件名时我们自己定义的,比如enlarge-text)

自定义事件的详细知识在“JavaScript高级程序设计”里面有讲解,这里就不去赘述。

假设:

//1-HTML中的元素
<div id="blog-post-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>
//2-用一个Vue实例绑定最外层的div,从而完全控制它的行为属性
var blog-post-demo = new Vue({
    el:"#blog-post-demo",
    data:{
        posts:[/*...*/],
        postFontSize:1
    }
});
//3-定义模板中的点击事件
Vue.component('blog-post',{
    props:['post'],
    tempalte:`<li>
                <button v-on:click="enlarge-text">Enlarge Text</button>
              </li>`
});

假设在 Vue自定义组件 中,有一个按键 <button> 元素,这个按键按下就会触发一个click事件,当这个click事件触发,就会调用一个方法——假设为 enlarge-text ,像上面那样。在这里我们的想法是——enlargeText方法会将在HTML中的元素字体增大,上面第三步中的组件定义可以添加上enlarge-text函数定义:

//3-定义模板中的点击事件
Vue.component('blog-post',{
    props:['post'],
    tempalte:`<li>
                <button v-on:click="enlarge-text">Enlarge Text</button>
              </li>`,
    methods:{
        enlarge-text:function(){
            postFontSize +=0.1;
        }
    }
});

这样一来,我们定义了一个 <div> 元素,<div>元素里面有这么些个自定义组件,自定义组件里面都有一个按键,按键按下,会触发click事件,调用enlarge-text函数,这个函数使整个 组件 的字体变大。一切的顺理成章,不是吗?

但是我们忽略了一点,在第三步,自定义组件中的enlarge-text方法,为了使整个 组件中的字体变大,会调用 postFontSize 属性,但是在这个Vue.component()定义的Vue组件中,有这个属性的值吗?没有,因为每一个组件都是Vue实例,在这个 blog-post 实例中,并没有定义 postFontSize 的值,在这个组件中,postFontSize 的值为undefined,所以直接在组件中这修改postFontSize 的值 "postFontSize +=0.1;" 是错误的,达不到效果。

怎么办?

监听子组件事件。

这么说有些云里雾里的。来看我们上面的矛盾是什么,是在定义组件时,我们想要通过组件中的元素的点击事件来改变父级元素中的属性。简单的说,这里的组件是“爸爸”组件中的按钮是“儿子”组件外层的<div> 是“爷爷”。按儿子一下,就要爸爸中所有的字体都变大,这个变大效果需要儿子去改变一个 postFontSize 属性,但是爸爸里面没有这个 postFontSize 属性,为什么呢?因为在JavaScript中时,爸爸和爷爷是两个不同的Vue实例,这个时候爸爸还没有变成这个爷爷的儿子,他们俩没关系(在上面的HTML 定义中,爸爸才变成了爷爷的儿子。在这个HTML中,爷爷包括爸爸,爸爸包括儿子。,爸爸里面没有定义这个属性,也访问不到爷爷中定义的这个属性。

这个时候,儿子就会给爸爸说:“爸爸,我想让爸爸你中的所有字体都变大,但是我改不了postFontSize的值。” 爸爸听到了,知道儿子想要做一件事——enlarge-text,ok,爸爸去做这件事,因为在HTML中,爸爸找到了爷爷,爷爷给了爸爸一个postFontSize 的属性值。

这里就会发生一些改变,儿子发生了click事件以后,不再是调用方法直接去处理事件,而是创建一个事件——"enlarge-text",然后爸爸在HTML 通过 v-on:enlarge-text=“..”,来监听儿子的这个事件并作出处理。说简单就是,想要变大字体的了,儿子你不要做,直接告诉爸爸你想要干嘛,让爸爸来做

这就是监听子组件事件

上面的代码就要这样改一下:

//1-HTML中的元素
<div id="blog-post-demo">
    <div :style="{fontSize:postFontSize +'em'}">
        <blog-post v-for="post in posts"
                   v-bind:key="post.id" 
                   v-bind:post="post"
                   v-on:enlarge-text="postFontSize += 0.1">
        <blog-post>
    </div>
</div>
//2-用一个Vue实例绑定最外层的div,从而完全控制它的行为属性
var blog-post-demo = new Vue({
    el:"#blog-post-demo",
    data:{
        posts:[/*...*/],
        postFontSize:1
    }
});
//3-定义模板中的点击事件
Vue.component('blog-post',{
    props:['post'],
    tempalte:`<li>
                <button v-on:click="$emit('enlarge-text')">Enlarge Text</button>
              </li>`
});

这里的 $emit() 方法就是将enlarge-text 定义为一个自定义事件。使父级组件能够通过 v-on: 监听。

# vm.$emit(eventName,[...args])

  • 参数:
    •  {string} eventName 
    •  {...args} 
      触发当前实例上的事件。附加参数都会传给监听器回调。
  • 示例:
    • 只配合一个事件名使用 $emit ,这里就不细讲这种情况了,上面的例子就是这种用法。
    • 配合额外的参数使用 $emit
      Vue.component('magic-eight-ball', {
        data: function () {
          return {
            possibleAdvice: ['Yes', 'No', 'Maybe']
          }
        },
        methods: {
          giveAdvice: function () {
            var randomAdviceIndex = Math.floor(Math.random() * this.possibleAdvice.length)
            this.$emit('give-advice', this.possibleAdvice[randomAdviceIndex])
          }
        },
        template: `
          <button v-on:click="giveAdvice">
            Click me for advice
          </button>
        `
      <div id="emit-example-argument">
        <magic-eight-ball v-on:give-advice="showAdvice"></magic-eight-ball>
      </div>
      new Vue({
        el: '#emit-example-argument',
        methods: {
          showAdvice: function (advice) {
            alert(advice)
          }
        }
      })

      这里在使用 $emit 的时候,传入了第二个参数,这个参数会在监听这个事件的监听器中回调。(即会在showAdivice中回调 give-advice事件中的this.possiveAdvice[randomAdviceIndex]值)

这里的$emit()方法的详细实现过程应该是JavaScript中的自定事件,需要以后去搞明白这里Vue的$emit()具体实现过程。

1、使用事件抛出一个值

也就是给$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(enlargetAmount){
        this.postFontSize += enlargeAmout;
    }
}

2、在组件上使用 v-model 

 利用自定义事件 创建 支持v-model  的自定义输入组件

我们知道表单输入组件是支持 v-model 的:

<input v-model="searchText">

这个等价于

<input v-bind:value="searchText" v-on:input="searchText = $event.target.value">

当在组件上使用 v-model 的时候,在 v-model 之下的操作并不是上面这种等价于,而是这样的底层操作:

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

此时的操作其实等价于:

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

这就不对了呀,你看我们的v-model 等价的操作第二步是 v-on:input="searchText=$event.target.value",而这里的却是 v-on:input="searchText=$event"。既然在自定义输入组件中使用 v-model 指令的操作和普通表单输入组件中的不一样,那么我们就用一个操作使他们一样。

仔细观察,我们这里差的其实是 赋给 searchText 的值,普通的是 $event.target.value,而自定义组件是 $event。
而在自定义事件中,父级组件就是通过$event来访问 $emit() 第二个参数值。

这就简单了啊,通过自定义事件$emit()的第二个参数,将 $event.target.value 传递给 父级组件的$event。

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

这里就先了解到这,还有一些其他内容将在下一章“深入了解组件”中继续学习。

四、通过插槽分发内容 

 和HTML 一样,我们经常需要向一个组件传递内容:

<alert-box>
    Somthing bad happend
</alert-box>

这样渲染可能不好。而可以通过Vue 元素的 <slot></slot> 来完成这个插入内容的操作:

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

 在 slot 中插入想要插入的内容。
到目前为止就只需要了解这些。下一章还要深入了解它。

五、动态组件

 就是通过Vue 的 <component> 元素加一个特殊的 is attribute来实现的:

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

在上述实例中, currentTabComponent 可以包括:

  • 已经注册组件的名字,或
  • 一个组件的选项对象

请留意,这个 attribute 可以用于常规 HTML 元素,但这些元素将被视为组件,这意味着所有的 attribute 都会作为 DOM attribute 被绑定对于像 value 这样的 property,若想让其如预期般工作,你需要使用 .prop 修饰器

这里先了解到这。

六、解析DOM模板时的注意事项

有些HTML 元素,如 <ul><ol><table><select>,对于哪些元素可以出现在其内部是有严格限制的。而有些元素,诸如 <li><tr> 和 <option>只能出现在其它某些特定的元素内部

这会导致一些问题的出现:

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

这个自定义组件 <blog-post-row> 会被作为无效的内容提升到外部,并导致最终渲染结果出错。幸好这个特殊的 is attribute 给了我们一个变通的办法:

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

上面这些就是 组件的基础知识:

组件注册、组件复用、组件结构、通过Prop向子组件传递数据、单个根元素、监听子组件事件、同通过插槽分发内容、动态组件、解析DOM模板时的注意事项。

在下一章深入的了解这些内容。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值