VUE大神的成长之路--组件

组件是vue最强大的功能之一,组件可以扩展 HTML 元素,封装可重用的代码。在较高层面上,组件是自定义元素, Vue.js 的编译器为它添加特殊功能。在有些情况下,组件也可以是原生 HTML 元素的形式,以 is 特性扩展。
(一)注册一个全局组件:(对于自定义标签名,Vue.js不要求强制遵循W3C规则(小写,并且包含一个短杠),尽管遵循这个规则比较好)

Vue.component('my-component',{
    //内容
})

组件在注册之后,便可以在父实例的模块中以自定义元素

<my-component></my-component>

的形式使用了,
重点:必须要确保在初始化根实例之前注册了组件

举例:

<div id="app">
   <my-component></my-component>
</div>

//全局注册
Vue.component('my-component', {
    template: '<div>A custom component!</div>'
})

//初始化根实例
new Vue({
    el: "#app",
    data: {

    }
});

渲染结果:

<div id="app">
  <div>A custom component!</div>
</div>

一般在项目中,不必在全局注册每个组件。通过使用组件实例选项注册,可以使组件仅在另一个实例/组件的作用域中可用:

var Child = {
    template: '<div>A custom component!</div>'
}

new Vue({
    el: "#app",
    data: {

    },
    components: {
       'my-component': Child
    }
});

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

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

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

以上中,<mr-row>是会被认为是无效内容,因为table中只能包裹td,th,tr等元素,变通方案是使用特殊的is属性:

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

(三)data必须是函数

我们先来看一个案例:

<div id="example-2">
  <simple-counter></simple-counter>
  <simple-counter></simple-counter>
  <simple-counter></simple-counter>
</div>

var data = { counter: 0 }
Vue.component('simple-counter', {
  template: '<button v-on:click="counter += 1">{{ counter }}</button>',
  data: function () {
    return data
  }
})

new Vue({
  el: '#example-2'
})

你会发现,三个按钮是联动的!!!
因为上面中,dada是一个函数,我们返回给每个组件的实例引用了同一个data对象,由于这三个组件共享了同一个data,因此增加一个counter会影响所有的组件

为了解决这个问题,我们需要将data设置为一个方法函数

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

接下来,每个按钮都会有了自己的状态了

(四)构成组件
在Vue中,父子组件的关系可以总结为props down,events up。父组件通过props向下传递数据给子组件,子组件通过events给父组件发送消息

先来看看prop
组件实例的作用域是孤立的。这意味着不能(也不应该)在子组件的模板内直接引用父组件的数据。要让子组件使用父组件的数据,我们需要通过子组件的props选项。
还是举例说明:
子组件要显示的用props选项声明它期待获得的数据:

Vue.component('my-component', {
    //声明props
    props: ['message'],
    //就像data一样,prop可以用在模板内
    //同样也可以在vm实例中像"this.message" 这样使用
    template: '<div>{{ message }}</div>'
})

在使用时可以这样传入一个普通字符串

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

特别注意:

HTML 特性是不区分大小写的。所以,当使用的不是字符串模版,camelCased (驼峰式) 命名的 prop 需要转换为相对应的 kebab-case (短横线隔开式) 命名:

Vue.component('child', {
  // camelCase in JavaScript
  props: ['myMessage'],
  template: '<span>{{ myMessage }}</span>'
})
<!-- kebab-case in HTML -->
<child my-message="hello!"></child>

动态prop

在模板中,要动态地绑定父组件的数据到子模板的props,与绑定到任何普通的HTML特性相类似,就是用 v-bind。每当父组件的数据变化时,该变化也会传导给子组件:

<div id="app">
   <my-component :my-message="a"></my-component>
</div>

var Child = {
     props: ['myMessage'],
     template: '<div>{{ myMessage }}</div>'
}

new Vue({
    el: "#app",
    data: {
        a:1
    },
    components: {
        'my-component': Child
    }
});

字面量语法VS动态语法

初学者常犯的一个错误就是使用字面量语法传递数值:

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

因为它是一个字面 prop ,它的值是字符串 "1" 而不是number。如果想传递一个实际的number,需要使用 v-bind ,从而让它的值被当作 JavaScript 表达式计算:

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

单项数据流

prop是单向绑定的:当父组件的属性变化时,将传导给子组件,但是不会反过来。这是为了防止子组件无意修改了父组件的状态--这会让应用的数据流难以理解

另外,每次父组件更新时,子组件的所有prop都会更新为最新值,这意味着你不应该在子组件内改变prop

对于为什么会有修改prop中数据的冲动,VUE官方给出的解释:
1.prop 作为初始值传入后,子组件想把它当作局部数据来用;
2.prop 作为初始值传入,由子组件处理成其它数据输出。

对于这两种原因,正确的应对方式时:
1.定义一个局部变量,并用prop的值初始化它:

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

2.定义一个计算属性,处理 prop 的值并返回。

props: ['size'],
computed: {
  normalizedSize: function () {
    return this.size.trim().toLowerCase()
  }
}

注意在 JavaScript 中对象和数组是引用类型,指向同一个内存空间,如果 prop 是一个对象或数组,在子组件内部改变它会影响父组件的状态。

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

type也可以是一个自定义构造器函数,使用instanceof检测。

(四)自定义事件
父组件是使用props传递数据给子组件,但是如果子组件要把数据传递回去,应该怎么做呢?那就是接下来要说的是自定义事件

使用v-on绑定自定义事件

来看一个例子
使用 $on(eventName) 监听事件
使用 $emit(eventName) 触发事件

<div id="counter-event-example">
  <p>{{ total }}</p>
  <button-counter v-on:increment="incrementTotal"></button-counter>
  <button-counter v-on:increment="incrementTotal"></button-counter>
</div>

Vue.component('button-counter', {
    template: '<button v-on:click="increment">{{ counter }}</button>',
    data: function(){
       return {
          counter: 0
       }
    },
    methods: {
       increment: function() {
          this.counter += 1
          this.$emit('increment')
       }
    }
})
new Vue({
    el: '#counter-event-example',
    data: {
       total: 0
    },
    methods: {
       incrementTotal: function() {
           this.total += 1
       }
    }
})

在本例中,子组件已经和它外部完全解耦了。它所做的只是报告自己的内部事件,至于父组件是否关心则与它无关。留意到这一点很重要。

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

自定义事件可以用来创建自定义的表单输入组件,使用 v-model 来进行数据双向绑定

非父子组件通信

有时候两个组件也需要通信(非父子关系)在简单的场景下,可以使用一个空的 Vue 实例作为中央事件总线:

var bus = new Vue()

// 触发组件 A 中的事件

bus.$emit('id-selected', 1)

// 在组件 B 创建的钩子中监听事件

bus.$on('id-selected', function (id) {
  // ...
})

(五)使用Slot分发内容
为了让组件可以组合,我们需要一种方式来混合父组件的内容与子组件自己的模板,这个过程被称作内容分发
(1)编译作用域
父组件模板的内容在父组件作用域内编译;子组件模板的内容在子组件作用域内编译。
如果要绑定作用域内的指令到一个组件的根节点,你应当在组件自己的模板上做:

Vue.component('child-component', {
    //有效,因为是在正确的作用域内
    template: '<div v-show="someChildProperty">Child</div>',
    data: function() {
        return {
          someChildProperty: true
        }
    }
})

(2)单个Slot

--------未完待续

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容

  • 这篇笔记主要包含 Vue 2 不同于 Vue 1 或者特有的内容,还有我对于 Vue 1.0 印象不深的内容。关于...
    云之外阅读 5,044评论 0 29
  • 下载安装搭建环境 可以选npm安装,或者简单下载一个开发版的vue.js文件 浏览器打开加载有vue的文档时,控制...
    冥冥2017阅读 6,027评论 0 42
  • Vue 实例 属性和方法 每个 Vue 实例都会代理其 data 对象里所有的属性:var data = { a:...
    云之外阅读 2,198评论 0 6
  • 此文基于官方文档,里面部分例子有改动,加上了一些自己的理解 什么是组件? 组件(Component)是 Vue.j...
    陆志均阅读 3,799评论 5 14
  • 什么是组件 组件(Component)是 Vue.js 最强大的功能之一。组件可以扩展 HTML 元素,封装可重用...
    angelwgh阅读 774评论 0 0