组件化应用构建
基础示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| 这里有一个 Vue 组件的示例:
// 定义一个名为 button-counter 的新组件 Vue.component('button-counter', { data: function () { return { count: 0 } }, template: '<button v-on:click="count++">You clicked me {{ count }} times.</button>' })
new Vue({ el: '#components-demo' }) 实例化要放后面
HTML <div id="components-demo"> <button-counter></button-counter> </div>
|
创建组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| Vue.component('todo-item', { template: '<li>123</li>' })
var app6 = new Vue({ el:'#app6', data:{ i } });
HTML <ol id="app6"> <todo-item></todo-item> </ol>
|
组件的复用
1 2 3 4 5 6 7 8
| <div id="components-demo"> <button-counter></button-counter> <button-counter></button-counter> <button-counter></button-counter> </div>
注意当点击按钮时,每个组件都会各自独立维护它的 count。因为你每用一次组件,就会有一个它的新实例被创建。
|
组件传值
父组件子组件互相传值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| html <div id="demo"> <input type="text" v-model="valuedata"> <button @click='sub_fn'>提交</button> <ul> <item-li v-for="(item,index) in lists" :key="index" :content="item" :index="index" @delete="handledelete"></item-li> </ul> </div>
js <script> Vue.component('item-li',{ props:['content','index'], template:'<li @click="handleclick">{{content}}</li>', methods:{ handleclick:function(){ this.$emit('delete',this.index) } } }) new Vue({ el:'#demo', data:{ valuedata:'', lists:[] }, methods:{ sub_fn:function(){ this.lists.push(this.valuedata) }, handledelete:function(e){ this.lists.splice(e,1) } } }) </script>
|
父向子组件传值
1.父组件parent代码如下:
1 2 3 4 5 6 7 8
| template <son psMsg="父传子的内容:叫爸爸"></son> 如果传对象 <son :psMsg="mydata"></son>
script import son from './Son' //引入子组件 components:{son},
|
2.子组件son代码如下:
1 2 3 4 5
| template <p>子组件接收到内容:{{ psMsg }}</p>
script props:['psMsg'],//接手psMsg值
|
通过 Prop 向子组件传递数据
子组件向父组件传值
子组件 this.$emit(‘func’,this.msg)传出
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| <template> <div class="app"> <input @click="sendMsg" type="button" value="给父组件传递值"> </div> </template> <script> export default { data () { return { msg: "我是子组件的msg", } }, methods:{ sendMsg(){ this.$emit('func',this.msg) } } } </script>
|
父组件 @func=”getMsgFormSon” 接收并调用方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| <template> <div class="app"> <child @func="getMsgFormSon"></child> </div> </template>
<script> import child from './child.vue' export default { data () { return { msgFormSon: "this is msg" } }, components:{ child, }, methods:{ getMsgFormSon(data){ this.msgFormSon = data console.log(this.msgFormSon) } } } </script>
|
通过插槽分发内容
和 HTML 元素一样,我们经常需要向一个组件传递内容,像这样:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <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> ` })
|
我们只要在需要的地方加入插槽就行了
动态组件
1
| 可以通过 Vue 的 <component> 元素加一个特殊的 is attribute 来实现:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
|
<component v-bind:is="currentTabComponent"></component>
在上述示例中,currentTabComponent 可以包括
已注册组件的名字,或 一个组件的选项对象
计算属性 computed: { currentTabComponent: function () { return "tab-home"; } }
则 <component ></component> 变成 <tab-home><tab-home/> == <component is="tab-home"></component>
|
深入了解组件
组件注册
组件名大小写
kebab-case
PascalCase
全局注册
Vue.component 注册
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| Vue.component('my-component-name', { }) 这些组件是全局注册的。也就是说它们在注册之后可以用在任何新创建的 Vue 根实例 (new Vue) 的模板中。比如:
Vue.component('component-a', { }) Vue.component('component-b', { }) Vue.component('component-c', { })
new Vue({ el: '#app' }) <div id="app"> <component-a></component-a> <component-b></component-b> <component-c></component-c> </div>
|
全局注册例子
1 2 3 4 5 6
| Vue.component('item-li',{ props:['content'], template:'<li>{{content}}</li>' })
|
vue-cli 使用
全局组件
main.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| import hyyyTitle from '@/mycomponents/hyyyTitle.vue'
Vue.component('hyyyTitle',hyyyTitle)
export default {
name: 'Detail',
data(){
return {
}
},
components:{
hyyyTitle
}
}
|
局部注册
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| new Vue({ el:'#demo', data:{ valuedata:'', lists:[] }, methods:{ sub_fn:function(){ this.lists.push(this.valuedata) } } })
|
vue-cli 使用
1 2 3 4 5 6 7 8 9 10 11 12
| // import hyyyTitle from '@/mycomponents/hyyyTitle.vue'
export default { name: 'Detail', data(){ return { } }, components:{ hyyyTitle } }
|
使用组件
vue文件使用标签
模块系统
在模块系统中局部注册
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| 如果你还在阅读,说明你使用了诸如 Babel 和 webpack 的模块系统。在这些情况下,我们推荐创建一个 components 目录,并将每个组件放置在其各自的文件中。
然后你需要在局部注册之前导入每个你想使用的组件。例如,在一个假设的 ComponentB.js 或 ComponentB.vue 文件中:
import ComponentA from './ComponentA' import ComponentC from './ComponentC'
export default { components: { ComponentA, ComponentC }, } 现在 ComponentA 和 ComponentC 都可以在 ComponentB 的模板中使用了。
|
基础组件的自动化全局注册
https://vuejs.bootcss.com/guide/components-registration.html#基础组件的自动化全局注册
导入基础组件库到全局
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| https:
如果你恰好使用了 webpack (或在内部使用了 webpack 的 Vue CLI 3+),那么就可以使用 require.context 只全局注册这些非常通用的基础组件。这里有一份可以让你在应用入口文件 (比如 src/main.js) 中全局导入基础组件的示例代码:
import Vue from 'vue' import upperFirst from 'lodash/upperFirst' import camelCase from 'lodash/camelCase'
const requireComponent = require.context( './components', false, /Base[A-Z]\w+\.(vue|js)$/ )
requireComponent.keys().forEach(fileName => { const componentConfig = requireComponent(fileName)
const componentName = upperFirst( camelCase( fileName .split('/') .pop() .replace(/\.\w+$/, '') ) )
Vue.component( componentName, componentConfig.default || componentConfig ) }) 记住全局注册的行为必须在根 Vue 实例 (通过 new Vue) 创建之前发生。这里有一个真实项目情景下的示例。
|
Prop
Prop 的大小写 (camelCase vs kebab-case)
1 2 3 4 5 6 7 8 9 10
| HTML 中的 attribute 名是大小写不敏感的,所以浏览器会把所有大写字符解释为小写字符。这意味着当你使用 DOM 中的模板时,camelCase (驼峰命名法) 的 prop 名需要使用其等价的 kebab-case (短横线分隔命名) 命名:
Vue.component('blog-post', { // 在 JavaScript 中是 camelCase 的 props: ['postTitle'], template: '<h3>{{ postTitle }}</h3>' })
<blog-post post-title="hello!"></blog-post> 重申一次,如果你使用字符串模板,那么这个限制就不存在了。
|
Prop 类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| https:
到这里,我们只看到了以字符串数组形式列出的 prop:
props: ['title', 'likes', 'isPublished', 'commentIds', 'author'] 但是,通常你希望每个 prop 都有指定的值类型。这时,你可以以对象形式列出 prop,这些 property 的名称和值分别是 prop 各自的名称和类型:
props: { title: String, likes: Number, isPublished: Boolean, commentIds: Array, author: Object, callback: Function, contactsPromise: Promise } 这不仅为你的组件提供了文档,还会在它们遇到错误的类型时从浏览器的 JavaScript 控制台提示用户。你会在这个页面接下来的部分看到类型检查和其它 prop 验证。
https:
|
传递静态或动态 Prop
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| https://vuejs.bootcss.com/guide/components-props.html#传递静态或动态-Prop
像这样,你已经知道了可以像这样给 prop 传入一个静态的值:
<blog-post title="My journey with Vue"></blog-post> 你也知道 prop 可以通过 v-bind 动态赋值,例如:
<blog-post v-bind:title="post.title"></blog-post>
<blog-post v-bind:title="post.title + ' by ' + post.author.name" ></blog-post>
在上述两个示例中,我们传入的值都是字符串类型的,但实际上任何类型的值都可以传给一个 prop。
|
传入一个数字
传入一个布尔值
传入一个数组
传入一个对象
传入一个对象的所有 property
单向数据流
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| https:
所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。这样会防止从子组件意外变更父级组件的状态,从而导致你的应用的数据流向难以理解。
额外的,每次父级组件发生变更时,子组件中所有的 prop 都将会刷新为最新的值。这意味着你不应该在一个子组件内部改变 prop。如果你这样做了,Vue 会在浏览器的控制台中发出警告。
这里有两种常见的试图变更一个 prop 的情形:
这个 prop 用来传递一个初始值;这个子组件接下来希望将其作为一个本地的 prop 数据来使用。在这种情况下,最好定义一个本地的 data property 并将这个 prop 用作其初始值:
props: ['initialCounter'], data: function () { return { counter: this.initialCounter } } 这个 prop 以一种原始的值传入且需要进行转换。在这种情况下,最好使用这个 prop 的值来定义一个计算属性:
props: ['size'], computed: { normalizedSize: function () { return this.size.trim().toLowerCase() } } 注意在 JavaScript 中对象和数组是通过引用传入的,所以对于一个数组或对象类型的 prop 来说,在子组件中改变变更这个对象或数组本身将会影响到父组件的状态。
|
Prop 验证
https://vuejs.bootcss.com/guide/components-props.html#Prop-验证
我们可以为组件的 prop 指定验证要求,例如你知道的这些类型。如果有一个需求没有被满足,则 Vue 会在浏览器控制台中警告你。这在开发一个会被别人用到的组件时尤其有帮助。
为了定制 prop 的验证方式,你可以为 props 中的值提供一个带有验证需求的对象,而不是一个字符串数组。例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| Vue.component('my-component', { props: { 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 ['success', 'warning', 'danger'].indexOf(value) !== -1 } } } })
|
当 prop 验证失败的时候,(开发环境构建版本的) Vue 将会产生一个控制台的警告。
注意那些 prop 会在一个组件实例创建之前进行验证,所以实例的 property (如 data、computed 等) 在 default 或 validator 函数中是不可用的。
类型检查
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| https:
type 可以是下列原生构造函数中的一个:
String Number Boolean Array Object Date Function Symbol 额外的,type 还可以是一个自定义的构造函数,并且通过 instanceof 来进行检查确认。例如,给定下列现成的构造函数:
function Person (firstName, lastName) { this.firstName = firstName this.lastName = lastName } 你可以使用:
Vue.component('blog-post', { props: { author: Person } }) 来验证 author prop 的值是否是通过 new Person 创建的。
|
非 Prop 的 Attribute
https://vuejs.bootcss.com/guide/components-props.html#非-Prop-的-Attribute
替换/合并已有的 Attribute
禁用 Attribute 继承
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| https:
如果你不希望组件的根元素继承 attribute,你可以在组件的选项中设置 inheritAttrs: false。例如:
Vue.component('my-component', { inheritAttrs: false, // ... }) 这尤其适合配合实例的 $attrs property 使用,该 property 包含了传递给一个组件的 attribute 名和 attribute 值,例如:
{ required: true, placeholder: 'Enter your username' } 有了 inheritAttrs: false 和 $attrs,你就可以手动决定这些 attribute 会被赋予哪个元素。在撰写基础组件的时候是常会用到的:
Vue.component('base-input', { inheritAttrs: false, props: ['label', 'value'], template: ` <label> {{ label }} <input v-bind="$attrs" v-bind:value="value" v-on:input="$emit('input', $event.target.value)" > </label> ` }) 注意 inheritAttrs: false 选项不会影响 style 和 class 的绑定。
|
自定义事件
事件名
推荐你始终使用 kebab-case 的事件名。
自定义组件的 v-model
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| https:
一个组件上的 v-model 默认会利用名为 value 的 prop 和名为 input 的事件,但是像单选框、复选框等类型的输入控件可能会将 value attribute 用于不同的目的。model 选项可以用来避免这样的冲突:
Vue.component('base-checkbox', { model: { prop: 'checked', event: 'change' }, props: { checked: Boolean }, template: ` <input type="checkbox" v-bind:checked="checked" v-on:change="$emit('change', $event.target.checked)" > ` }) 现在在这个组件上使用 v-model 的时候:
<base-checkbox v-model="lovingVue"></base-checkbox> 这里的 lovingVue 的值将会传入这个名为 checked 的 prop。同时当 <base-checkbox> 触发一个 change 事件并附带一个新的值的时候,这个 lovingVue 的 property 将会被更新。
注意你仍然需要在组件的 props 选项里声明 checked 这个 prop。
|
将原生事件绑定到组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
| https:
可能有很多次想要在一个组件的根元素上直接监听一个原生事件。这时,你可以使用 v-on 的 .native 修饰符:
你可能有很多次想要在一个组件的根元素上直接监听一个原生事件。这时,你可以使用 v-on 的 .native 修饰符:
<base-input v-on:focus.native="onFocus"></base-input> 在有的时候这是很有用的,不过在你尝试监听一个类似 <input> 的非常特定的元素时,这并不是个好主意。比如上述 <base-input> 组件可能做了如下重构,所以根元素实际上是一个 <label> 元素:
<label> {{ label }} <input v-bind="$attrs" v-bind:value="value" v-on:input="$emit('input', $event.target.value)" > </label> 这时,父级的 .native 监听器将静默失败。它不会产生任何报错,但是 onFocus 处理函数不会如你预期地被调用。
为了解决这个问题,Vue 提供了一个 $listeners property,它是一个对象,里面包含了作用在这个组件上的所有监听器。例如:
{ focus: function (event) { } input: function (value) { }, } 有了这个 $listeners property,你就可以配合 v-on="$listeners" 将所有的事件监听器指向这个组件的某个特定的子元素。对于类似 <input> 的你希望它也可以配合 v-model 工作的组件来说,为这些监听器创建一个类似下述 inputListeners 的计算属性通常是非常有用的:
Vue.component('base-input', { inheritAttrs: false, props: ['label', 'value'], computed: { inputListeners: function () { var vm = this return Object.assign({}, this.$listeners, { input: function (event) { vm.$emit('input', event.target.value) } } ) } }, template: ` <label> {{ label }} <input v-bind="$attrs" v-bind:value="value" v-on="inputListeners" > </label> ` }) 现在 <base-input> 组件是一个完全透明的包裹器了,也就是说它可以完全像一个普通的 <input> 元素一样使用了:所有跟它相同的 attribute 和监听器都可以工作,不必再使用 .native 监听器。
|
.native 修饰符
Vue 提供了一个 $listeners property
Object.assign()将所有对象整合成一个新对象
.sync 修饰符
对一个 prop 进行“双向绑定”。