1Vuejs之组件
9.1 什么是组件
•可以拓展HTML元素,封装常用的代码
•是自定义元素
•原生HTML的形式,以js特性扩展
每一个应用界面都可以看作是组件构成的,每一个组件都可以看做是一个ViewModel,如图:
9.2 使用组件
•全局注册
§new Vue()
§Vue.component(tagName,options)
•局部注册
§用实例components注册
•DOM模板解析
•data必须是函数
•构成组件
§props down
§events up
代码如下:
// 扩展 Vue 来自定义一个可复用的组件类
var MyComponent = Vue.extend({
template: '<p>{{msg}}</p>',
paramAttributes: ['msg']
})
// 全局注册该组件
Vue.component('my-component', MyComponent)
<my-component msg="Hello!"></my-component>
my-component 组件的模板将会被填充到该元素中,而 msg 则会被作为数据传入该组件实例。渲染结果如下。
<my-component>
<p>Hello!</p>
</my-component>
9.3 使用组件的基本Demo
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>组件-组件使用</title>
</head>
<body>
<div id="example">
<h1>基本用法</h1>
<my-component></my-component>
<h1>data必须是函数</h1>
<my-data></my-data>
<h1>每个组件返回同一个对象引用是错误的做法</h1>
<simple-counter></simple-counter>
<simple-counter></simple-counter>
<simple-counter></simple-counter>
<h1>通过每个组件返回新的data来实现独立不共用</h1>
<simple-counter2></simple-counter2>
<simple-counter2></simple-counter2>
<simple-counter2></simple-counter2>
</div>
<script src="vue.js" type="text/javascript" charset="utf-8"></script>
<script type="text/javascript">
var data = { counter: 0 }
Vue.component('my-component', {
template: '<div>A custom component!</div>'
});
Vue.component('my-data', {
template: '<div>{{msg}}</div>',
data:function(){
return {
msg:'hello vue.js'
}
}
});
Vue.component('simple-counter', {
template: '<button v-on:click="counter += 1">{{ counter }}</button>',
// data 是一个函数,因此 Vue 不会警告,
// 但是我们为每一个组件返回了同一个对象引用
data: function () {
return data
}
})
Vue.component('simple-counter2', {
template: '<button v-on:click="counter += 1">{{ counter }}</button>',
// data 是一个函数,因此 Vue 不会警告,
// 但是我们为每一个组件返回了同一个对象引用
data: function () {
return { counter: 0 }
}
})
var vm = new Vue({
el: '#example'
});
</script>
</body>
</html>
9.4 props
组件实例的作用域是孤立的。这意味着不能并且不应该在子组件的模板内直接引用父组件的数据。可以使用 props 把数据传给子组件。
prop 是父组件用来传递数据的一个自定义属性。子组件需要显式地用 props 选项 声明 “prop”:
Vue.component('child', {
// 声明 props
props: ['message'],
// 就像 data 一样,prop 可以用在模板内
// 同样也可以在 vm 实例中像 “this.message” 这样使用
template: '<span>{{ message }}</span>'
})
<child message="hello!"></child>
•camelCase vs kebab-case
HTML 特性不区分大小写。当使用非字符串模版时,名字形式为 camelCase 的 prop 用作特性时,需要转为 kebab-case(短横线隔开):
Vue.component('child', {
// camelCase in JavaScript
props: ['myMessage'],
template: '<span>{{ myMessage }}</span>'
})
<!-- kebab-case in HTML -->
<child my-message="hello!"></child>
•动态props
类似于用 v-bind 绑定 HTML 特性到一个表达式,也可以用 v-bind 绑定动态 props 到父组件的数据。每当父组件的数据变化时,也会传导给子组件:
<div>
<input v-model="parentMsg">
<br>
<child v-bind:my-message="parentMsg"></child>
</div>
<child :my-message="parentMsg"></child>
•字面量语法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 的情况:
•prop 作为初始值传入,子组件之后只是将它的初始值作为本地数据的初始值使用;
•prop 作为需要被转变的原始值传入。
更确切的说这两种情况是:
•定义一个本地数据,并且将 prop 的初始值设为本地数据的初始值。
•定义一个基于 prop 值的计算属性。
•Prop验证
组件可以为 props 指定验证要求。如果未指定验证要求,Vue会发出警告。当组件给其他人使用时这很有用。
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
}
}
}
})
9.5 props用法Demo
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>组件-props</title>
</head>
<body>
<div id="example">
<h1>基本用法</h1>
<child message="hello!"></child>
<h1> camelCase 转为 kebab-case</h1>
<child2 my-message="hello kebab-case!"></child2><!--html中使用kebab-case-->
<h1>动态props</h1>
<div>
<input v-model="parentMsg"><br>
<child2 :my-message="parentMsg"></child2>
</div>
</div>
<script src="vue.js" type="text/javascript" charset="utf-8"></script>
<script type="text/javascript">
Vue.component('child', {
props: ['message'],// 声明 props
template: '<span>{{ message }}</span>'// 就像 data 一样,prop 可以用在模板内,同样也可以在 vm 实例中像 “this.message” 这样使用
})
Vue.component('child2', {
props: ['myMessage'],// camelCase in JavaScript
template: '<span>{{ myMessage }}</span>'
})
Vue.component('example', {
props: {
propA: Number,// 基础类型检测 (`null` 意思是任何类型都可以)
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
}
}
}
})
var vm = new Vue({
el: '#example',
data:{
parentMsg:''
}
});
</script>
</body>
</html>
9.6 自定义事件
•使用v-on
§$on(eventName)
§$emit(eventName)
•使用自定义事件的表单输入组件
•非父子组件通信
代码如图:
9.7.1 自定义事件Demo
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>组件-自定义事件</title>
</head>
<body>
<div id="example">
<h1>v-on绑定自定义事件</h1>
<p>{{ total }}</p>
<button-counter v-on:increment="incrementTotal"></button-counter>
<button-counter v-on:increment="incrementTotal"></button-counter>
<h1>自定义表单输入</h1>
<currency-input v-model="price"></currency-input>
</div>
<script src="vue.js" type="text/javascript" charset="utf-8"></script>
<script type="text/javascript">
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')
}
},
})
Vue.component('currency-input', {
template: '\
<span>\
$\
<input\
ref="input"\
v-bind:value="value"\
v-on:input="updateValue($event.target.value)"\
>\
</span>\
',
props: ['value'],
methods: {
// 不是直接更新值,而是使用此方法来对输入值进行格式化和位数限制
updateValue: function (value) {
var formattedValue = value// 删除两侧的空格符
.trim()// 保留 2 小数位
.slice(0, value.indexOf('.') + 3)// 如果值不统一,手动覆盖以保持一致
if (formattedValue !== value) {
this.$refs.input.value = formattedValue
}// 通过 input 事件发出数值
this.$emit('input', Number(formattedValue))
}
}
})
var vm = new Vue({
el: '#example',
data: {
total: 0,
price:''
},
methods: {
incrementTotal: function () {
this.total += 1
}
}
});
</script>
</body>
</html>
9.8 使用Slots分发内容
在使用组件时,常常要像这样组合它们:
<app>
<app-header></app-header>
<app-footer></app-footer>
</app>
注意两点:
•<app> 组件不知道它的挂载点会有什么内容。挂载点的内容是由<app>的父组件决定的。
•<app> 组件很可能有它自己的模版。
为了让组件可以组合,我们需要一种方式来混合父组件的内容与子组件自己的模板。这个处理称为 内容分发 (或 “transclusion” 如果你熟悉 Angular)。
•编译作用域
组件作用域简单地说是:父组件模板的内容在父组件作用域内编译;子组件模板的内容在子组件作用域内编译。
如果要绑定子组件内的指令到一个组件的根节点,应当在它的模板内这么做:
Vue.component('child-component', {
// 有效,因为是在正确的作用域内
template: '<div v-show="someChildProperty">Child</div>',
data: function () {
return {
someChildProperty: true
}
}
})
•单个slot
除非子组件模板中包含<slot>,否则父组件的内容将会被抛弃。如果子组件模板只有一个没有特性的 slot,父组件整个内容片段将插到 slot 所在的 DOM 位置并替换掉 slot 标签。
<slot> 标签的内容视为回退内容。回退内容在子组件的作用域内编译,当宿主元素为空并且没有内容供插入时显示这个回退内容。
假定 my-component 组件有下面模板:
<div>
<h2>I'm the child title</h2>
<slot>
如果没有分发内容则显示我。
</slot>
</div>
•单个slot
父组件模版:
<div>
<h1>I'm the parent title</h1>
<my-component>
<p>This is some original content</p>
<p>This is some more original content</p>
</my-component>
</div>
渲染结果:
<div>
<h1>I'm the parent title</h1>
<div>
<h2>I'm the child title</h2>
<p>This is some original content</p>
<p>This is some more original content</p>
</div>
</div>
•具名slots
<slot> 元素可以用一个特殊的属性 name 来配置如何分发内容。多个 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">Here might be a page title</h1>
<p>A paragraph for the main content.</p>
<p>And another one.</p>
<p slot="footer">Here's some contact info</p>
</app-layout>
渲染结果为:
<div class="container">
<header>
<h1>Here might be a page title</h1>
</header>
<main>
<p>A paragraph for the main content.</p>
<p>And another one.</p>
</main>
<footer>
<p>Here's some contact info</p>
</footer>
</div>
9.9 使用Slots分发内容Demo
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>组件-使用solts分发</title>
</head>
<body>
<div id="example">
<h1>单个solt</h1>
<div>
<h3>我是父组件的标题</h3>
<my-component>
<p>这是一些初始内容</p>
<p>这是更多的初始内容</p>
</my-component>
</div>
<h1>具名solt</h1>
<app-layout>
<h3 slot="header">这里可能是一个页面标题</h3>
<p>主要内容的一个段落。</p>
<p>另一个主要段落。</p>
<p slot="footer">这里有一些联系信息</p>
</app-layout>
</div>
<script src="vue.js" type="text/javascript" charset="utf-8"></script>
<script type="text/javascript">
Vue.component('app-layout', {
template: '\
<div class="container">\
<header>\
<slot name="header"></slot>\
</header>\
<main>\
<slot></slot>\
</main>\
<footer>\
<slot name="footer"></slot>\
</footer>\
</div>\
'
});
Vue.component('my-component', {
template: '\
<div>\
<h5>我是子组件的标题</h5>\
<slot>\
只有在没有要分发的内容时才会显示。\
</slot>\
</div>\
'
});
var vm = new Vue({
el: '#example'
});
</script>
</body>
</html>
9.6 动态组件
•Keep-alive
如果把切换出去的组件保留在内存中,可以保留它的状态或避免重新渲染。为此可以添加一个 keep-alive 指令参数:
代码如图:
本站代码下载方法:
关注公众号,在后台回复“代码下载”,如图:
文章评论