组件(Component)是Vue.js最强大的功能之一。组件可以扩展 HTML 元素,封装可重用的代码。就像是python中封装一个类,在其他类中可以继承和调用类中的属性和方法。所有的 Vue 组件同时也都是 Vue 的实例,所以可接受相同的选项对象 (除了一些根级特有的选项) 并提供相同的生命周期钩子。
全局组件
Vue中全局组件使用Vue.component()方法进行注册,
语法为:Vue.component(component_name, component_attr_obj)
component_name:组件名称
component_attr_obj:组件的其他属性
全局组件示例代码:
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
| <!DOCTYPE html> <html lang="zh"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>vue组件</title> <style type="text/css"> .global_component_css { background-color: coral; } </style> </head> <body> <div id="app"> <global_component></global_component> </div> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script type="text/javascript"> Vue.component( 'global_component', { template: '<div class="global_component_css">我是全局组件</div>' } ) new Vue({ el: "#app" }) </script> </body> </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 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82
| <!DOCTYPE html> <html lang="zh"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>vue组件</title> <style type="text/css"> div { padding: 10px; }
.liyuan_css { background-color: coral; }
.lishimin_css { background-color: forestgreen; }
.liyuanji_css { background-color: royalblue; } </style> </head> <body> <div id="app"> <liyuan></liyuan>
</div> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script type="text/javascript"> let Lishimin = { data: function() { return { name: "李世民" } }, template: `<div class="lishimin_css">我是局部组件【{{name}}】</div>`, } let Liyuanji = { data: function() { return { name: "李元吉" } }, template: `<div class="liyuanji_css">我是局部组件【{{name}}】</div>`, } Vue.component( 'liyuan', { data: function() { return { name: "李渊" } }, template: `<div class="liyuan_css">我是全局组件【{{name}}】<lishimin></lishimin><liyuanji></liyuanji></div>`, components: { 'lishimin': Lishimin, 'liyuanji': Liyuanji } } ) new Vue({ el: "#app" }) </script> </body> </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 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86
| <!DOCTYPE html> <html lang="zh"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>vue组件</title> <style type="text/css"> div { padding: 10px; }
.liyuan_css { background-color: coral; }
.lishimin_css { background-color: forestgreen; }
.liyuanji_css { background-color: royalblue; } </style> </head> <body> <div id="app"> <liyuan></liyuan>
</div> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script type="text/javascript"> let Lishimin = { data: function() { return { name: "李世民" } }, template: `<div class="lishimin_css">我是局部组件【{{name}}】</div>`, } let Liyuanji = { data: function() { return { name: "李元吉" } }, template: `<div class="liyuanji_css">我是局部组件【{{name}}】<lishimin></lishimin></div>`, components: { 'lishimin': Lishimin } } Vue.component( 'liyuan', { data: function() { return { name: "李渊" } }, template: `<div class="liyuan_css">我是全局组件【{{name}}】<lishimin></lishimin><liyuanji></liyuanji></div>`, components: { 'lishimin': Lishimin, 'liyuanji': Liyuanji } } ) new Vue({ el: "#app" }) </script> </body> </html>
|
结果:
![]()
组件数据绑定
上方代码中我们都已经使用了数据绑定了,就是data,一个组件的 data
选项必须是一个函数,因此每个实例可以维护一份被返回对象的独立的拷贝。
组件传值
父组件向子组件传值
父组件给子组件传值,组件中通过props属性传递数据。Prop 是你可以在组件上注册的一些自定义 attribute。当一个值传递给一个 prop attribute 的时候,它就变成了那个组件实例的一个 property。
一个组件默认可以拥有任意数量的 prop,任何值都可以传递给任何 prop。在上述模板中,你会发现我们能够在组件实例中访问这个值,就像访问 data
中的值一样。
示例如下:
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 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90
| <!DOCTYPE html> <html lang="zh"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>vue组件</title> <style type="text/css"> div { padding: 10px; }
.liyuan_css { background-color: coral; }
.lishimin_css { background-color: forestgreen; }
.liyuanji_css { background-color: royalblue; } </style> </head> <body> <div id="app"> <liyuan></liyuan>
</div> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script type="text/javascript"> let Lishimin = { data: function() { return { name: "李世民" } }, props: ['message'], template: `<div class="lishimin_css">我是局部组件【{{name}}】,有人发来消息说:{{message}}</div>`, } let Liyuanji = { data: function() { return { name: "李元吉" } }, props: ['message'], template: `<div class="liyuanji_css">我是局部组件【{{name}}】,有人发来消息说:{{message}}<lishimin message="听说你要杀我?"></lishimin></div>`, components: { 'lishimin': Lishimin } } Vue.component( 'liyuan', { data: function() { return { name: "李渊" } }, template: `<div class="liyuan_css">我是全局组件【{{name}}】<lishimin message="放过你的亲兄弟吧!"></lishimin><liyuanji message="李世民要杀你们,快跑!"></liyuanji></div>`, components: { 'lishimin': Lishimin, 'liyuanji': Liyuanji } } ) new Vue({ el: "#app" }) </script> </body> </html>
|
渲染结果:
![]()
子组件向父组件传值
子组件给父组件传值,通过$emit将数据传递个父组件
代码:
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 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103
| <!DOCTYPE html> <html lang="zh"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>vue组件</title> <style type="text/css"> div { padding: 10px; }
.liyuan_css { background-color: coral; }
.lishimin_css { background-color: forestgreen; }
.liyuanji_css { background-color: royalblue; } </style> </head> <body> <div id="app"> <liyuan></liyuan>
</div> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script type="text/javascript"> let Lishimin = { data: function() { return { name: "李世民" } }, props: ['message'], template: `<div class="lishimin_css">我是局部组件【{{name}}】,有人发来消息说:{{message}}<button @click="send_value('父王,不会有任何人受到伤害的!')">穿给父王</button></div>`, methods:{ send_value(message){ this.$emit('notice', message) } } } let Liyuanji = { data: function() { return { name: "李元吉" } }, props: ['message'], template: `<div class="liyuanji_css">我是局部组件【{{name}}】,有人发来消息说:{{message}}<button @click="$emit('notice', '父王快救我吗!')">救我</button></div>`, components: { 'lishimin': Lishimin } } Vue.component( 'liyuan', { data: function() { return { name: "李渊", warning: "", } }, template: `<div class="liyuan_css">***{{warning}}***我是全局组件【{{name}}】<lishimin message="放过你的亲兄弟吧!" v-on:notice="receive_method"></lishimin><liyuanji message="李世民要杀你们,快跑!" v-on:notice="warning = $event"></liyuanji></div>`, components: { 'lishimin': Lishimin, 'liyuanji': Liyuanji }, methods:{ receive_method(value){ this.warning = value } } } ) new Vue({ el: "#app" }) </script> </body> </html>
|
代码中我们使用了两种方式来演示向父组件传递值的方式。
直接使用$emit传值
子组件中直接定义点击传值
1
| <button @click="$emit('notice', '父王快救我吗!')">救我</button>
|
父组件也直接接收赋值
1
| v-on:notice="warning = $event"
|
使用方法间接传值
子组件定义一个方法用来向父组件传值,其本质还是利用了$emit
1 2 3 4 5 6 7
| methods:{ send_value(message){ this.$emit('notice', message) } }
|
然后在模板中使用该方法
1
| <button @click="send_value('父王,不会有任何人受到伤害的!')">穿给父王</button>
|
父组件接收也定义一个方法
1 2 3 4 5
| methods:{ receive_method(value){ this.warning = value } }
|
模板中使用该方法接收子组件传过来的值
1
| <lishimin v-on:notice="receive_method"></lishimin>
|
最终效果:
单文件组件
上面讲过了组件,所有的组件写到了一个文件中,如果项目过大,维护起来相对麻烦,我们能否使用单文件组件的方式来解决这个问题呢?答案当然是可以的。
单文件组件语法样式
将一个组件相关的html结构,css样式,以及交互的JavaScript代码从html文件中剥离出来,合成一个文件,这种文件就是单文件组件,相当于一个组件具有了结构、表现和行为的完整功能,方便组件之间随意组合以及组件的重用,这种文件的扩展名为“.vue”,我们这里以button.vue为例
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
| // 使用template标签来定义html部分 <template> <button :class="{bg:true,hide:isHide}" @click="isHide=!isHide"> {{ pos }} </button> </template>
// javascript要写成模块导出的形式: <script> export default{ props:['pos'], data:function(){ return { isHide:false } } } </script>
// 样式中如果有scope关键字,表示这些样式是组件局部的,不会影响其他元素 <style scoped> .bg{ background-color:#ff0000; } .hide{ display:none } </style>
|
单文件组件打包流程
我们这里演示下单文件组件的基本打包流程。单文件组件不能直接运行使用,需要依赖node项目对其进行解析打包,在使用之前需要先进行环境配置。
我们这里首先默认你已经安装好了npm和node。
更新npm国内安装源
1
| npm config set registry https://registry.npm.taobao.org
|
创建vue项目
1
| mkdir component_for_page
|
进入项目并初始化
初始化完成后在当前目录中会生成一个package.json文件,该文件指定项目所以依赖的模块
1 2
| cd component_for_page npm init
|
配置package.json文件
该文件定义了这个项目所需要的各种模块,以及项目的配置信息(比如名称、版本、许可证等元数据)。npm install 命令根据这个配置文件,自动下载所需的模块,也就是配置项目所需的运行和开发环境。
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
| { "name": "component_for_page", "version": "1.0.0", "description": "单文件组件", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "build": "webpack" }, "author": "Tony Yu", "license": "ISC", "dependencies": { "babel-core": "^6.22.1", "babel-loader": "^7.1.1", "babel-preset-env": "^1.3.2", "babel-preset-stage-2": "^6.22.0", "babel-register": "^6.22.0", "css-loader": "^0.28.11", "element-ui": "^2.7.2", "file-loader": "^1.1.4", "lodash": "^4.17.4", "style-loader": "^0.23.1", "url-loader": "^1.1.2", "vue": "^2.6.10", "vue-loader": "^15.7.0", "vue-router": "^3.0.2", "vue-style-loader": "^3.0.1", "vue-template-compiler": "^2.5.2", "webpack": "^4.29.6", "webpack-cli": "^3.3.0", "webpack-dev-server": "^3.2.1" } }
|
安装项目依赖模块
创建项目基本文件
可以使用命令创建,亦可借助ide创建。
1
| touch index.html main.js App.vue
|
index.html:文件时项目的首页文件
main.js:文件定义vue及调用单文件组件,也是项目打包时所依赖的文件
App.vue:文件为单文件组件文件
创建webpack打包配置文件
配置文件名称为webpack.config.js。在通过webpack对项目进行打包时,需要指定相应的配置文件,同过配置文件对单文件组件中的各个内容进行解析,生成一个index.js的压缩文件,在index.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
| const path = require('path') const VueLoaderPlugin = require('vue-loader/lib/plugin');
module.exports = { entry: { main: "./main.js" }, output: { filename: 'index.js', path: path.resolve(__dirname), library: 'index' }, plugins: [ new VueLoaderPlugin() ], module: { rules: [ { test: /\.vue$/, loader: 'vue-loader' }, { test: /\.js$/, loader: 'babel-loader' }, { test: /\.css$/, loader: 'vue-style-loader', }, { test: /\.css$/, loader: 'css-loader', options: { minimize: true } }, { test: /\.(eot|svg|ttf|woff|woff2)$/, loader: 'file-loader' }, ] } }
|
以上步骤项目基本创建完成,目录结构大概如图所示:
![]()
编写项目
以上我们的项目已经创建好了,接下来开始编写我们的前端项目了。
main.js
1 2 3 4 5 6 7 8 9 10 11
| import Vue from 'vue' import App from './App.vue'
new Vue({ el:'#app', render(creater) { return creater(App) } })
|
App.vue
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> <!-- 指定本组件的html显示内容 --> <div> 我是<span class="red">单文件组件</span>{{message}} </div> </template>
<script> export default{ data:function(){ return { message: "真棒" } } } </script>
<style> div{ background-color: aqua; } .red{ color: #ff0000; } </style>
|
index.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <!DOCTYPE html> <html lang="zh"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Vue单文件组件</title> </head> <body> <div id="app"> <App></App> </div> <script src="./index.js" type="text/javascript" charset="utf-8"></script> </body> </html>
|
项目打包
文件编写完成后并不能直接运行index.html产生效果,需要对项目进行打包生成一个渲染后的index.js文件进行使用
打包后会在当前目录下生成一个index.js 文件,在index.html中引用该文件,运行index.html文件看到效果
项目调试运行
每次我们需要看到组件效果需要手动生成一个index.js文件,这是我们可以借助webpack-dev-server自动运行我们的代码,这样每次修改前段文件,webpack就帮我们自动重新打包一次,可以实时看到修改结果了。
1 2
| // 在项目目录下,执行下面指令可以开启前端服务,自动运行前端代码 ./node_modules/.bin/webpack-dev-server
|
最终预览
我们使用的方式也是webpack-dev-server运行代码,它自动帮我们开启了一个8080端口的web服务,看下效果吧。
![]()
多个单文件组件
我们在上面代码基础上修改为多个单文件组件.在component_for_page目录下创建components文件夹,然后将所有子组件放入components文件夹下。
![]()
lishimin.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| <template> <div class="lishimin_css">我是{{name}}组件</div> </template>
<script> export default{ data:function(){ return { name: '李世民' } } } </script>
<style> .lishimin_css{ background-color:green; } </style>
|
liyuanji.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| <template> <div class="liyuanji_css">我是{{name}}组件</div> </template>
<script> export default{ data:function(){ return { name: '李元吉' } } } </script>
<style> .liyuanji_css{ background-color: fuchsia; } </style>
|
App.vue
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
| <template> <!-- 指定本组件的html显示内容 --> <div class="global"> 我是<span class="red">单文件组件</span>{{message}} <lishimin-wiget></lishimin-wiget> <liyuanji-wiget></liyuanji-wiget> </div> </template>
<script> // 导入其他组件 import lishipin from './components/lishimin.vue' import liyuanji from './components/liyuanji.vue' export default{ data:function(){ return { message: "真棒" } }, components:{ "lishimin-wiget": lishipin, "liyuanji-wiget": liyuanji } } </script>
<style> .global{ background-color: aqua; } .red{ color: #ff0000; } </style>
|
预览效果
![]()
多组件路由使用
有了以上基础,我们想进一步扩展功能,我们不想只访问首页,想访问下子组件页面该如何呢?这时候路由就该上场了。
定义路由目录及路由文件
1 2
| mkdir router touch router.js
|
编写路由文件router.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 25
| import Vue from 'vue'
import Router from 'vue-router'
import lishimin from '../components/lishimin.vue' import liyuanji from '../components/liyuanji.vue'
Vue.use(Router)
export default new Router({ routes:[ { path: '/', component: lishimin }, { path: '/lyj', component: liyuanji } ] })
|
在main.js中使用路由
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| import Vue from 'vue' import App from './App.vue'
import router from './router/router.js'
new Vue({ el:'#app', router, render(creater) { return creater(App) } })
|
在App.vue中指定路由标签
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
| <template> <!-- 指定本组件的html显示内容 --> <div class="global"> <!-- 记载路由标签 --> <router-view></router-view> </div> </template>
<script> // 导入其他组件 import lishipin from './components/lishimin.vue' import liyuanji from './components/liyuanji.vue' export default{ data:function(){ return { message: "真棒" } }, components:{ "lishimin-wiget": lishipin, "liyuanji-wiget": liyuanji } } </script>
<style> .global{ background-color: aqua; } .red{ color: #ff0000; } </style>
|
预览效果
![]()
![]()
结语:以上项目各个文件都是我们自己一个个手动创建起来的,在实际开发中根本不用我们一个一个去创建,vue就为我们提供好了项目脚手架,即Vue CLI。以上操作你连手动创建项目都会了,那这个自动创建项目脚手架使用起来就更简单了,详细参考官方文档:https://cli.vuejs.org/zh/guide/installation.html,其他关于vue的介绍也可以参考[Vue框架基本使用](https://www.diandian100.cn/16bf34e6.html?highlight=vue)