一、Vue组件
一个Vue项目是由很多个组件构成的。
Vue组件分为全局组件和局部组件。
组件是可以复用的Vue实例,且带有一个名字,它与new Vue({})接收相同的选项,比如data,computed,watch,methods以及生命周期钩子等,除了el。
组件可以复用,每个相同的组件相互独立,各自独立维护自己的数据,你每用一次组件,就会有一个它的新实例被创建。
一个组件的data选项必须是一个函数,每个实例可以维护一份被返回对象的独立的拷贝。
通常一个应用会以一棵嵌套的组件树的形式来组织。
为了能在模板中使用,这些组件必须先注册以便Vue能够识别。
在注册一个组件的时候,必须给组件一个名字;
在Vue里 ,每一个Vue组件,都是一个Vue实例 【实例=组件】;
Vue实例有的功能,Vue组件都拥有。
每个组件都是vue的实例;
每个实例的组成部分:
props、template、data、methods...
每一个组件都有一个template模板,如果没有写template,默认为挂载点下面的所有DOM标签作为模板。
组件的三个特点:独立性,可复用性,完整性
组件化问题:
1、组件状态管理(vuex)
2、多组件的混合使用、多页面复杂业务(vue-router)
3、组件之间的传参、消息、事件管理(props,emit/on,bus)
为什么要组件化:
1. 实现功能模块的复用
2. 高效执行
3. 开发单页面复杂应用
二、全局组件:
通过Vue.component定义的组件是全局组件, 可以在任何地方使用
Vue.component('todo-item', { props: ['content'], //这个组件接收从外部传进来的叫 content的属性 template: '<li>{{content}}</li>' });
全局组件的组件名,就是Vue.component的第一个参数。
定义全局组件名的方式有两种:
第一种:Vue.component('my-component-name', { /* ... */ }) 组件名使用短横线-进行连接;
第二种:Vue.component('MyComponentName', { /* ... */ }) 组件名首字母大写;
子组件通过props选项来接收父组件传递的参数,比如:
Vue.component('blog-post', { props: ['title'], template: '<h3>{{ title }}</h3>' })
记住:全局注册的行为必须在根 Vue 实例 (通过 new Vue) 创建之前发生。
全局组件在注册之后可以用在任何新创建的 Vue 根实例 (new Vue) 的模板中。
实例代码1:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Vue组件</title> <script type="text/javascript" src="./vue.js"></script> </head> <body> <div id="components-demo"> <!--组件多次复用--> <button-counter></button-counter> <button-counter></button-counter> <button-counter></button-counter> </div> <script type="text/javascript"> //自定义全局组件 Vue.component('button-counter', { data: function(){ return{ count: 0 } }, template: '<button v-on:click="count++">你点击了{{count}}次</button>' }) new Vue({ el: "#components-demo" }) </script> </body> </html>
实例代码2:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Vue组件</title> <script type="text/javascript" src="./vue.js"></script> <script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script> </head> <body> <div id="blog"> <div v-bind:style="{fontSize: postFontSize + 'em'}"> <!-- 使用v-bind来动态传递prop --> <blog-post v-for="post in posts" v-bind:key="post.id" v-bind:post="post" v-on:enlarge-text="onEnlargeText"> </blog-post> </div> </div> <script type="text/javascript"> Vue.component('blog-post', { props: ['post'], template: '<div class="blog-post">\ <h3>{{post.title}}</h3>\ <div v-html="post.content"></div>\ </div>' }) new Vue({ el: "#blog", data: { posts: [ {id: 1, title: "雨霖铃-柳永", content: "杨柳岸,晓风残月。此去经年,应是良辰好景虚设。便纵有千种风情,更与何人说?"}, {id: 2, title: "将近酒-李白", content: "岑夫子,丹丘生,将进酒,杯莫停。"}, {id: 3, title: "虞美人-李煜", content: "剪不断,理还乱,是离愁,别是一般滋味在心头。"} ], postFontSize: 1 }, methods: { onEnlargeText: function(enlargeAmount){ this.postFontSize += enlargeAmount } } }) </script> </body> </html>
实例代码3:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>vue - 全局组件</title> <script type="text/javascript" src="./vue.js"></script> </head> <body> <div id="todo-list-example"> <form v-on:submit.prevent="addNewTodo"> <label for="new-todo">Add a todo</label> <input v-model="newTodoText" id="new-todo" placeholder="todo list"> <button>Add Todo</button> </form> <ul> <li is="todo-item" v-for="(todo,index) in todos" v-bind:key="todo.id" v-bind:title="todo.title" v-on:remove="todos.splice(index, 1)"> </li> </ul> </div> <script type="text/javascript"> //定义全局组件 Vue.component('todo-item', { template: '\ <li>\ {{ title }}\ <button v-on:click="$emit(\'remove\')">Remove</button>\ </li>\ ', props: ['title'] }) new Vue({ el: '#todo-list-example', data: { newTodoText: '', todos: [ {id: 1, title: '不思量'}, {id: 2, title: '自难忘'}, {id: 3, title: '千里孤坟'}, {id: 4, title: '无处话凄凉'} ], nextTodoId: 5 }, methods: { //方法 addNewTodo: function(){ //添加新的Todo this.todos.push({ id: this.nextTodoId++, title: this.newTodoText }) this.newTodoText = '' //添加之后清空文本框 } } }) </script> </body> </html>
实例代码4:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Vue-TodoList</title> <script type="text/javascript" src="./vue.js"></script> <style type="text/css"> </style> </head> <body> <!-- 父组件通过属性的形式向子组件传递数据; 子组件通过发布订阅模式向父组件传递数据;子组件发布一个事件,父组件通过监听(订阅)这个事件,就可以实现子组件向父组件传递数据。 --> <div id="root"> <div> <input type="text" name="name" v-model="inputValue" style="width: 250px;height: 30px;border: solid;"> <button v-on:click="handleSubmit">提交</button> <ul> <!-- <li v-for="(item, index) of list" :key="index" style="color: blue;font-size: 30px;">{{item}}</li> --> <!-- 使用组件 --> <todo-item v-for="(item, index) of list" :key="index" :content="item" :index="index" v-on:delete="handleDelete" style="color: blue;font-size: 30px;"> </todo-item> </ul> </div> </div> <script type="text/javascript"> //定义全局组件 Vue.component('todo-item', { props: ['content','index'], template: '<li @click="handleClick">{{content}}{{index}}</li>', methods: { handleClick: function(){ //删除内容 this.$emit('delete', this.index) } } }) new Vue({ el: "#root", //挂载点 data: { //数据 inputValue: '', list: [] }, methods: { //事件 handleSubmit: function(){ this.list.push(this.inputValue) //添加内容 this.inputValue = '' //内容提交之后,恢复为空 }, handleDelete: function(index){ console.log(index) this.list.splice(index, 1) } } }) </script> </body> </html>
三、局部组件
局部组件必须在new Vue()内通过components注册声明
var TodoItem = { template: '<li>item</li>' } new Vue({ el: '#root', data: { list: [] }, components: { 'todo-item': TodoItem } //注册局部组件 });
对于 components 对象中的每个属性来说,其属性名就是自定义元素的名字,其属性值就是这个组件的选项对象。
注意:局部注册的组件不能在子组件中使用;
如果你希望局部组件ComponentA在子组件ComponentB中可以用,你可以这样写:
var ComponentA = { /* ... */ } var ComponentB = { components: { 'component-a': ComponentA }, // ... }
实例代码:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Vue-TodoList</title> <script type="text/javascript" src="./vue.js"></script> <style type="text/css"> </style> </head> <body> <!-- v-model: 实现双向数据绑定 v-for: 实现循环列表 v-on: 实现事件绑定 this.list.push(): 向列表中添加内容数据 --> <div id="root"> <div> <input type="text" name="name" v-model="inputValue" style="width: 250px;height: 30px;border: solid;"> <button v-on:click="handleSubmit">提交</button> <ul> <!-- <li v-for="(item, index) of list" :key="index" style="color: blue;font-size: 30px;">{{item}}</li> --> <!-- 使用组件 --> <todo-item v-for="(item, index) of list" :key="index" :content="item" style="color: blue;font-size: 30px;"> </todo-item> </ul> </div> </div> <script type="text/javascript"> //定义全局组件 //Vue.component('todo-item', { //template: '<li>item</li>' //}) //定义局部组件 var TodoItem = { template: '<li>item</li>' } new Vue({ el: "#root", //挂载点 data: { //数据 inputValue: '', list: [] }, methods: { //事件 handleSubmit: function(){ this.list.push(this.inputValue) //添加内容 this.inputValue = '' //内容提交之后,恢复为空 } }, components: { //局部组件注册 'todo-item' : TodoItem } }) </script> </body> </html>
四、组件属性——props
在这里以下面的组件为例:
Vue.component('blog-post', { props: ['postTitle'], template: '<h3>{{ postTitle }}</h3>' })
使用组件:
<blog-post post-title="hello!"></blog-post>
一个组件默认可以拥有任意数量的 prop,任何值都可以传递给任何 prop。
通常情况下,prop是以字符串数组的形式列出的,如下:
props: ['title', 'likes', 'isPublished', 'commentIds', 'author']
我们也可以以对象形式列出prop,如下:
props: { title: String, likes: Number, isPublished: Boolean, commentIds: Array, author: Object, callback: Function, contactsPromise: Promise // or any other constructor }
给prop赋值:
(1)给prop传入一个静态值:
<blog-post title="Vue.js的点点滴滴"></blog-post>
(2)给prop传入一个变量的值
<blog-post v-bind:title="post.title"></blog-post>
(3)给prop传入一个复杂的表达式
<blog-post v-bind:title="post.title + ' by ' + post.author.name"></blog-post>
(4)给prop传入一个数字
<!-- 即便 `42` 是静态的,我们仍然需要 `v-bind` 来告诉 Vue --> <!-- 这是一个 JavaScript 表达式而不是一个字符串。--> <blog-post v-bind:likes="42"></blog-post>
<!-- 用一个变量进行动态赋值。--> <blog-post v-bind:likes="post.likes"></blog-post>
(5)给prop传入一个布尔值
<!-- 包含该 prop 没有值的情况在内,都意味着 `true`。--> <blog-post is-published></blog-post>
<!-- 即便 `false` 是静态的,我们仍然需要 `v-bind` 来告诉 Vue --> <!-- 这是一个 JavaScript 表达式而不是一个字符串。--> <blog-post v-bind:is-published="false"></blog-post>
<!-- 用一个变量进行动态赋值。--> <blog-post v-bind:is-published="post.isPublished"></blog-post>
(6)给prop传入一个数组
<!-- 即便数组是静态的,我们仍然需要 `v-bind` 来告诉 Vue --> <!-- 这是一个 JavaScript 表达式而不是一个字符串。--> <blog-post v-bind:comment-ids="[234, 266, 273]"></blog-post>
<!-- 用一个变量进行动态赋值。--> <blog-post v-bind:comment-ids="post.commentIds"></blog-post>
(7)给prop传入一个对象
<!-- 即便对象是静态的,我们仍然需要 `v-bind` 来告诉 Vue --> <!-- 这是一个 JavaScript 表达式而不是一个字符串。--> <blog-post v-bind:author="{ name: '刘豆豆', company: '云天之上科技有限公司' }" ></blog-post>
<!-- 用一个变量进行动态赋值。--> <blog-post v-bind:author="post.author"></blog-post>
(8)给prop传入一个对象的所有属性
我们可以通过不带参数的v-bind指令将一个对象的所有属性传给prop
比如:对象user包含如下信息:
user: { id: 1, name: "刘豆豆", sex: "男", age: 18, content: "天国虽美,没有了你,万杯觥筹只不过是提醒寂寞罢了" }
直接传入:<blog-post v-bind="user"></blog-post>
所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定,也就是父组件的 prop的更新会向下流动到子组件中,但是反过来则不行。
换句话说:父组件与子组件之间的数组传输是单向的,只能是父组件传递给子组件,当父组件的prop更新时,子组件中所有的prop都会被刷新为最新的值。
注意在 JavaScript 中对象和数组是通过引用传入的,所以对于一个数组或对象类型的 prop 来说,在子组件中改变这个对象或数组本身将会影响到父组件的状态。
在实际的开发过程中,我们需要对props中的键值对进行验证,为了定制prop的验证方式,我们可以为props中的值提供一个带有验证需求的对象,而不是一个字符串数组,比如:
Vue.component('my-component', { props: { // 基础的类型检查 (`null` 和 `undefined` 会通过任何类型验证) 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中的type对应的类型有:String, Number, Boolean, Array, Object, Date, Function, Symbol, 也可以是一个自定义的构造函数,我们可以使用instanceof类进行检查确认。
比如:
//自定义构造函数 function User(id, name){ this.id = id; this.name = name; } //在组件中使用 Vue.component('blog-post',{ props: { author: User } })
通常情况下,组件可以接受任意的特性,这些接受到的特性会被添加到这个组件的根元素上。为什么会这样呢?因为开发组件库的作者并不知道你会把这个组件用在什么场景下。一个非 prop 特性是指传向一个组件,但是该组件并没有相应 prop 定义的特性。
比如说:
在Bootstrap插件中使用<bootstrap-date-input>组件,而这个插件需要在其 <input> 上用到一个 data-date-picker 特性。我们可以将这个特性添加到你的组件实例上,如下:
<bootstrap-date-input data-date-picker="activated"></bootstrap-date-input>
这个 data-date-picker="activated" 特性就会自动添加到 <bootstrap-date-input> 的根元素上。
在这里,如果你不希望组件的根元素继承特性,你可以在组件中设置inheritAttrs: false,来禁用特性继承。
Vue.component('my-component', { inheritAttrs: false, // ... })
注意 inheritAttrs: false 选项不会影响 style 和 class 的绑定。
五、动态组件:
在一个Vue项目中,会有很多个组件,我们可以使用v-bind:is特性来切换不同的组件,如下:
<component v-bind:is="currentTabComponent"></component>
每次切换组件的时候,Vue都会创建一个新的组件实例。
通常情况下,我们更希望在组件实例第一次创建的时候,被及时缓存下来。为了解决这个问题,我们可以使用<keep-alive>元素将一个动态组件包裹起来,已达到缓存的目的。如下:
<!-- 失活的组件将会被缓存!--> <keep-alive> <component v-bind:is="currentTabComponent"></component> </keep-alive>
全局组件和局部组件都可以使用<keep-alive>元素包裹,以达缓存目的。
六、异步组件:
在大型应用中,我们可能需要将应用分割成小一些的代码块,并且只在需要的时候才从服务器加载一个模块。为了简化,Vue 允许你以一个工厂函数的方式定义你的组件,这个工厂函数会异步解析你的组件定义。Vue 只有在这个组件需要被渲染的时候才会触发该工厂函数,且会把结果缓存起来供未来重渲染。
比如:
Vue.component('async-example', function (resolve, reject) { setTimeout(function () { // 向 `resolve` 回调传递组件定义 resolve({ template: '<div>I am async!</div>' }) }, 1000) })
这个工厂函数会收到一个 resolve 回调,这个回调函数会在你从服务器得到组件定义的时候被调用。
一个推荐的做法是将异步组件和 webpack 的 code-splitting 功能一起配合使用:
Vue.component('async-webpack-example', function (resolve) { // 这个特殊的 `require` 语法将会告诉 webpack // 自动将你的构建代码切割成多个包,这些包 // 会通过 Ajax 请求加载 require(['./my-async-component'], resolve) })
你也可以在工厂函数中返回一个 Promise,所以把 webpack 2 和 ES2015 语法加在一起,我们可以写成这样:
Vue.component( 'async-webpack-example', // 这个 `import` 函数会返回一个 `Promise` 对象。 () => import('./my-async-component') )
当使用局部注册的时候,你也可以直接提供一个返回 Promise 的函数:
new Vue({ // ... components: { 'my-component': () => import('./my-async-component') } })
在Vue的2.3.0+ 的版本中,这里的异步组件工厂函数也可以返回一个如下格式的对象:
const AsyncComponent = () => ({ // 需要加载的组件 (应该是一个 `Promise` 对象) component: import('./MyComponent.vue'), // 异步组件加载时使用的组件 loading: LoadingComponent, // 加载失败时使用的组件 error: ErrorComponent, // 展示加载时组件的延时时间。默认值是 200 (毫秒) delay: 200, // 如果提供了超时时间且组件加载也超时了, // 则使用加载失败时使用的组件。默认值是:`Infinity` timeout: 3000 })
七、自定义组件的v-model
一个组件上的 v-model 默认会利用名为 value 的 prop 和名为 input 的事件,但是像单选框、复选框等类型的输入控件可能会将 value 特性用于不同的目的。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)" > ` }) <base-checkbox v-model="lovingVue"></base-checkbox>
这里的 lovingVue 的值将会传入这个名为 checked 的 prop。同时当 <base-checkbox> 触发一个 change 事件并附带一个新的值的时候,这个 lovingVue 的属性将会被更新。
以上就是Vue.js组件相关的知识点了,关于Vue.js的更多相关知识,本人还在继续学习中!