Vue.js 组件
组件Component是 Vue.js 最强大的功能 可以扩展 HTML 元素,封装可重用的代码。
组件系统让 用独立可复用的小组件构建大型应用,应用的界面抽象为个组件树
注册个全局组件语法格式
Vue.component(tagName, options)
tagName 为组件名,options 为配置选项。注册后,用以下方式调用组件:
<tagName></tagName>
全局组件
所有实例都能用全局组件
全局组件实例
注册个简单的全局组件 facesoho,并使用它:
尝试一下 »
局部组件
也可在实例选项中注册局部组件,局部组件只能在这个实例中使用:
局部组件实例
注册个简单的局部组件 facesoho,并使用它:
尝试一下 »
Prop
prop 是父组件用来传递数据的个自定义属性。
父组件的数据需要通过 props 把数据传给子组件,子组件需要显式地用 props 选项声明 "prop":
Prop 实例
尝试一下 »
动态 Prop
类似于用 v-bind 绑定 HTML 特性到表达式,也可以用 v-bind 动态绑定 props 的值到父组件的数据中。每当父组件的数据变化时,该变化也会传导给子组件:
Prop 实例
尝试一下 »
将 v-bind 指令将 todo 传到每个重复的组件中:
Prop 实例
尝试一下 »
prop 是单向绑定的:当父组件的属性变化时,将传导给子组件,但是不会反过来。
Prop 验证
组件可以为 props 指定验证要求。
prop 是个对象而不是字符串数组时,它包含验证要求:
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 绑定自定义事件, Vue 实例都实现了事件接口(Events interface),即:
使用
$on(eventName)
监听事件使用
$emit(eventName)
触发事件
父组件可在使用子组件的地方直接用 v-on 来监听子组件触发的事件
组件已经和它外部完全解耦。它所做的只是触发父组件关心的内部事件。
实例
尝试一下 »
想在某个组件的根元素上监听个原生事件。使用 .native 修饰 v-on 。例如:
<my-component v-on:click.native="doTheThing"></my-component>
data 必须是个函数
button-counter 组件中的 data 不是个对象,而是个函数:
data: function () { return { count: 0 } }
好处是每个实例维护一份被返回对象的独立的拷贝,如果 data 是个对象则会影响到其 实例,如下所示:
实例
尝试一下 »
使用组件
1.全局组件
创建Vue实例
new Vue({ el: '#some-element' })
全局注册组件
Vue.component(tagName, options)
如
Vue.component('my-component', { // 选项
})
自定义标签的命名 Vue.js 不强制遵循 W3C 规则 (小写 并且包含一个短杠) 尽管这被认为是最佳实践
组件在注册之后 便可作为自定义元素
<my-component></my-component>使用了
确保在初始化根实例之前注册组件
在初始化根实例之前注册组件
Vue.component('my-component', { template: '<div>A custom component!</div>'})
var vm = new Vue({ el: '#box' })
这样的顺序是对的 而
var vm = new Vue({ el: '#box'})
Vue.component('my-component', { template: '<div>A custom component!</div>'})是错的
<div id="example"> <my-component></my-component></div>
// 注册
Vue.component('my-component', { template: '<div>A custom component!</div>'})
// 创建根实例
new Vue({ el: '#example'})
选项中需要添加template 就是HTML的构造
渲染为<div id="example"><div>A custom component!</div></div>
局部注册
不必把每个组件都注册到全局
通过某个 Vue 实例/组件的实例选项 components 注册仅在其作用域中可用的组件
var Child = { template: '<div>A custom component!</div>'}
var vm = new Vue({
el: '#box',
components: {
'my-component': Child
}
});
var vm = new Vue({ el: '#box2'});
<div id="box"><my-component></my-component></div>
<div id="box2"><my-component></my-component></div>
Vue组件的全局注册 可在多个Vue实例中使用
Vue构造器中局部注册 只能在此Vue实例中使用
在<div id="box">下面的<my-component></my-component>将不会渲染出来
这种封装也适用于其它可注册的 Vue 功能 比如指令
DOM模板解析注意事项
使用 DOM 作为模板的时候(如 用 el 选项来把 Vue 实例挂载到一个已有内容的元素上)
会受到 HTML 本身的限制 因为 Vue 只有在浏览器解析 规范化之后才能获取其内容
像 <ul> <ol>, <table> <select> 这样的元素里面允许包含的元素有限制
而另外一些像 <option> 这样的元素只能出现在某些特定元素的内部
在自定义组件中使用这些受限制元素时候会导致一些问题
如<table> <my-row>...</my-row></table>
自定义组件 <my-row> 会被当成无效的内容 因此会导致错误的渲染结果
变通方式是使用特殊的 is 特性 <table> <tr is="my-row"></tr></table>
如果使用来自以下来源之一的字符串模板 则没有这些限制:
<script type="text/x-template">
JavaScript 内联模板字符串
.vue 组件
因此尽可能使用字符串模板
data必须是函数
构造 Vue 实例时传入的各种选项大多数都可以在组件里使用
只有一个例外
data 必须是函数
Vue.component('my-component', {
template: '<span>{{ message }}</span>',
data: {
message: 'hello'
}
})
Vue 会停止运行 控制台发出警告 在组件实例中 data 必须是一个函数
但理解这种规则为何存在也是很有益处的 所以让我们先作个弊:
<div id="example-2">
<simple-counter></simple-counter>
<simple-counter></simple-counter>
<simple-counter></simple-counter>
</div>
data参数中 一般是返回的js对象
data: function () {
return {
text: 'text',
name: 'Hello'
}
}
var data = { counter: 0 }
Vue.component('simple-counter', {
template: '<button v-on:click="counter += 1">{{ counter }}</button>', # 注意:这里是 += 不是 + // 技术上 data 的确是一个函数了 因此 Vue 不会警告 // 但是给每个组件实例返回了同一个对象的引用
data: function () { return data }
})
new Vue({ el: '#example-2'})
由于这三个组件实例共享了同一个 data 对象 因此递增一个 counter 会影响所有组件!就错了
通过为每个组件返回全新的数据对象来修复这个问题
data: function () {
return {
counter: 0
}
}
现在每个 counter 都有它自己内部的状态了
组件组合
组件设计是为了配合使用 常形成父子组件的关系
组件 A 在它的模板中使用了组件 B 之间需要相互通信:父组件可能要给子组件下发数据 子组件则可能要将它内部发生的事情告知父组件
通过一个良好定义的接口来将父子组件解耦 保证每个组件的代码可在相对隔离的环境中书写和理解 提高可维护性和复用性
Vue父子组件的关系可总结为 prop 向下传递 事件向上传递
父组件通过 prop 给子组件下发数据 子组件通过事件给父组件发送消息
Prop
1.使用Prop传值
组件实例的作用域是孤立的 不能 也不应该 在子组件的模板内直接引用父组件的数据
父组件的数据需要通过 prop 才能下发到子组件中
子组件要显式用 props 选项声明它预期的数据
Vue.component('child', { // 声明 props
props: ['message'], // 就像 data 一样 prop 也可以在模板中使用 // 也可在 vm 实例中通过 this.message 来使用
template: '<span>{{ message }}</span>'
})
<div id="box">
<child message="hellow"></child>
<child message="hellow"></child>
<child message="123"></child>
</div>
2.驼峰法 vs 短横线分隔法
HTML不区分大小写 使用的不是字符串模板时 camelCase (驼峰式命名) 的 prop 需要转换为相对应的 kebab-case (短横线分隔式命名)
Vue.js区分大小写
Vue.component('child', { // 在 JavaScript 中使用 camelCase
props: ['myMessage'],
template: '<span>{{ myMessage }}</span>'
})
<!-- 在 HTML 中使用 kebab-case --><child my-message="hello!"></child>
是自动转换
使用模板时<child my-message="hello!"></child>
动态Prop
与绑定到任何普通的 HTML 特性类似 可以用 v-bind 动态将prop绑定到父组件的数据
每当父组件的数据变化时 该变化也会传导给子组件:
<div> <input v-model="parentMsg"> <br> <child v-bind:my-message="parentMsg"></child></div>
也可以使用 v-bind 的缩写语法 <child :my-message="parentMsg"></child>
如果想把一个对象的所有属性作为 prop 进行传递 可用不带参数的 v-bind (即用 v-bind 而不是 v-bind:prop-name)如
var vm = new Vue({
el: '#box',
data: {
todo: {
text: 'Learn Vue',
isComplete: false
}
}
})
<todo-item v-bind="todo"></todo-item>
等价于<todo-item v-bind:text="todo.text" v-bind:is-complete="todo.isComplete"></todo-item>
<div id="box"> <child v-bind="todo"></child></div>
Vue.component('child', {
props: ['text', 'is-complete'],
template: '<span>{{ text }}</span>'
})
var vm = new Vue({ el: '#box',
data: {
todo: {
text: 'Learn Vue',
isComplete: false
}
}
})
或直接传递一个对象
<body><div id="box"> <child v-bind:todo="todo"></child></div>
Vue.component('child', {
props: ['todo'],
template: '<span>{{ todo.text }} {{ todo.isComplete }} </span>'
})
var vm = new Vue({
el: '#box',
data: {
todo: {
text: 'Learn Vue',
isComplete: false
}
}
})
还是这样传递最好 <child v-bind:todo="todo"></child>
不要v-bind="todo" 这样不是很方便样
字面量语法 vs 动态语法
字面量语法 就是直接传递字符串这样
常犯的错误是用字面量语法传递数值<!-- 传递了一个字符串 "1" -->
<comp some-prop="1"></comp> 因为是一个字面量 prop 它的值是字符串 "1" 而不是一个数值
如果传递一个真正的 JavaScript 数值 则需要使用 v-bind 从而让它的值被当作 JavaScript 表达式计算:
<!-- 传递真正的数值 --><comp v-bind:some-prop="1"></comp>
单向数据流
Prop 是单向绑定的:当父组件的属性变化时 将传导给子组件 但是反过来不会 是为了防止子组件无意间修改了父组件的状态 避免应用的数据流变得难以理解
每次父组件更新时 子组件的所有 prop 都会更新为最新值 不应该在子组件内部改变 prop 如果这么做 Vue 会在控制台给出警告
两种情况容易忍不住想去修改 prop 中数据
1)Prop 作为初始值传入后 子组件想把它当作局部数据来用;
2)Prop 作为原始数据传入 由子组件处理成其它数据输出
正确的应对方式是
1)定义一个局部变量 使用prop的值初始化它
Vue.component('child', {
props: ['todo'],
template: '<span>{{ todo }} </span>',
data: function () {
return {
com_todo: this.todo + " ok"
}
}
})
外部传入了todo进来 那么现在又返回的有一个com_todo
现在组件中可以使用的数据就有两个了
todo 和 com_todo
在组件的template中 可以这样写
template: '<span>{{ todo }} </span>'
template: '<span>{{ this.todo }} </span>',
template: '<span>{{ com_todo }} </span>',
template: '<span>{{ this.com_todo }} </span>',
定义一个计算属性(computed) 来处理prop的值并返回
<div id="box"><child v-bind:todo="123"></child></div>
Vue.component('child', {
props: ['todo'],
template: '<span>{{ computed_todo }} </span>',
data: function () {
return {
com_todo: this.todo + " ok"
}
},
computed: {
computed_todo: function () {
return this.todo + "2" # 注意这里不能 return todo + "2"
}
}
})
JavaScript 中对象和数组是引用类型 指向同一个内存空间 如果 prop 是一个对象或数组 在子组件内部改变它会影响父组件的状态
Prop验证 为组件的 prop 指定验证规则 如果传入的数据不符合要求 Vue 会发出警告 对开发给他人使用的组件非常有用
要指定验证规则 需要用对象的形式来定义 prop 而不能用字符串数组(字符串数组形式是这样的:props:['todo', 'doit'])
对象形式是
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
非Prop特性
指可直接传入组件 而不需要定义相应的 prop
尽管为组件定义明确的 prop 是推荐的传参方式 组件的作者却并不总能预见到组件被使用的场景
组件可接收任意传入的特性 这些特性都会被添加到组件的根元素上
例如 假设用第三方组件 bs-date-input 它包含一个 Bootstrap 插件 该插件需要在 input 上添加 data-3d-date-picker 这个特性可把特性直接添加到组件上 (不需要事先定义 prop):
<bs-date-input data-3d-date-picker="true"></bs-date-input>添加属性 data-3d-date-picker="true" 之后 它会被自动添加到 bs-date-input 的根元素上
自定义事件
父组件使用 prop 传递数据给子组件,子组件跟父组件通信 使用 Vue的自定义事件系统
使用 v-on 绑定自定义事件 每个Vue实例都实现了事件接口 即:
使用 $on(eventName) 监听事件 (也叫绑定事件)
使用 $emit(eventName) 触发事件
Vue 的事件系统与浏览器的 EventTarget API 有所不同
尽管运行起来类似 但是 $on 和 $emit 并不是addEventListener 和 dispatchEvent 的别名
父组件用子组件的地方直接用 v-on 来监听子组件触发的事件
不能用 $on 监听子组件释放的事件 而必须在模板里直接用 v-on 绑定
父组件在使用子组件的地方使用v-on监听子组件触发的事件
<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="incrementCounter">{{ counter }}</button>',
data: function () {
return {
counter: 0
}
},
methods: {
incrementCounter: function () {
this.counter += 1
this.$emit('increment')
}
},
})
new Vue({
el: '#counter-event-example',
data: {
total: 0
},
methods: {
incrementTotal: function () {
this.total += 1
}
}
})
有两个v-on:click=事件绑定 注意这个 increment 是自定义事件:是使用this.$emit('increment')触发这个事件的
<button v-on:click="incrementCounter">{{ counter }}</button> # 子级(自己)
<button-counter v-on:increment="incrementTotal"></button-counter> # 父级
子组件已经和它外部完全解耦了 所做的只是报告自己的内部事件 因为父组件可能会关心这些事件
给组件绑定原生事件
某个组件的根元素上监听一个原生事件 使用 v-on 的修饰符 .native
<my-component v-on:click.native="doTheThing"></my-component>
.sync修饰符
对一个 prop 进行 双向绑定
Vue 1.x 的 .sync 修饰符所提供的功能
当子组件改变了一个带 .sync 的 prop 的值时 这个变化也会同步到父组件中所绑定的值
这很方便 但也会导致问题 因为它破坏了单向数据流
由于子组件改变 prop 的代码和普通的状态改动代码毫无区别 当光看子组件的代码时 你完全不知道它何时悄悄地改变了父组件的状态
这在 debug 复杂结构的应用时会带来很高的维护成本
2.3.0 起.sync 修饰符 只是作为一个编译时的语法糖存在 会被扩展为一个自动更新父组件属性的 v-on 监听器
<comp :foo.sync="bar"></comp> 会被扩展为
<comp :foo="bar" @update:foo="val => bar = val"></comp>
当子组件需要更新 foo 的值时 它需要显式地触发一个更新事件:
this.$emit('update:foo', newValue)
使用自定义事件的表单输入组件
自定义事件用来创建自定义的表单输入组件 使用 v-model 来进行数据双向绑定
<input v-model="something">是以下示例的语法糖:
<input v-bind:value="something" v-on:input="something = $event.target.value">
components: {'m-header':MHeader}
与components: {MHeader}是一样的 只是省略掉了默认的那个
Vue的父组件与子组件之间的交互(数据和事件)
子组件和父组件 函数的传递 是不需要写在props中的
子组件 this.$emit('function', params)
父组件 <sub-component @function="function_in_parent" ></sub-component>
这里的function_in_parent是在父组件中实现
大王叫我来巡山1998
这个例子的执行过程注解:
大王叫我来巡山1998
props 验证补充代码:注意 替换 vue.min.js 为 vue.js,验证结果可以到浏览器 console 查看,自定义验证函数尚未尝试出来
尝试一下 »
大王叫我来巡山1998
你可能在找的系统点击事件的例子。
尝试一下 »
大王叫我来巡山1998
拓展-如何通过调整组件属性,实现修改组件的显示等内部属性。
使用 Props(全局)
全局定义组件
尝试一下 »
大王叫我来巡山1998
子组件通过 $emit 触发父组件的方法时,如果需要传递参数,可在方法名后面加参数数组。
比如 $emit("FunctionName") 当要传递参数时 :$emit("FunctionName",[arg1,arg2...])。
尝试一下 »
大王叫我来巡山1998
父组件给子组件传值的时候,如果想传入一个变量,写法如下:
尝试一下 »
大王叫我来巡山1998
寻隐者不遇 / 孙革访羊尊师诗
唐代:贾岛
松下问童子,言师采药去。
只在此山中,云深不知处。
大王叫我来巡山1998
乐游原 / 登乐游原
唐代:李商隐
向晚意不适,驱车登古原。
夕阳无限好,只是近黄昏。
大王叫我来巡山1998
静夜思
唐代:李白
床前明月光,疑是地上霜。
举头望明月,低头思故乡。
大王叫我来巡山1998
给元素绑定href时可以也绑一个target,新窗口打开页面。
尝试一下 »
大王叫我来巡山1998
When I questioned your pupil, under a pine-tree,
"My teacher," he answered, " went for herbs,
But toward which corner of the mountain,
How can I tell, through all these clouds ?"
大王叫我来巡山1998
和张仆射塞下曲·其三
唐代:卢纶
月黑雁飞高,单于夜遁逃。
欲将轻骑逐,大雪满弓刀。
大王叫我来巡山1998
秋夕
唐代:杜牧
银烛秋光冷画屏,轻罗小扇扑流萤。
天阶夜色凉如水,卧看牵牛织女星。
(天阶 一作:天街;卧看 一作:坐看)
大王叫我来巡山1998
凉州词二首·其一
唐代:王翰
葡萄美酒夜光杯,欲饮琵琶马上催。
醉卧沙场君莫笑,古来征战几人回?
大王叫我来巡山1998
九月九日忆山东兄弟
唐代:王维
独在异乡为异客,每逢佳节倍思亲。
遥知兄弟登高处,遍插茱萸少一人。
大王叫我来巡山1998
登鹳雀楼
唐代:王之涣
白日依山尽,黄河入海流。
欲穷千里目,更上一层楼。