5. 组件化开发
5.1 组件[component]
组件(Component)是自定义封装的功能。在前端开发过程中,经常出现多个网页的功能是重复的,而且很多不同的网站之间,也存在同样的功能。
而在网页中实现一个功能,需要使用html定义功能的内容结构,使用css声明功能的外观样式,还要使用js来定义功能的特效,因此就产生了把一个功能相关的[HTML、css和javascript]代码封装在一起组成一个整体的代码块封装模式,我们称之为“组件”。
所以,组件就是一个html网页中的功能,一般就是一个标签,标签中有自己的html内容结构,css样式和js特效。
这样,前端人员就可以在开发时,只需要书写一次代码,随处引入即可使用。
组件有两种:默认组件[全局组件] 和 单文件组件
5.1.1 默认组件
<div id="app"> <addnum></addnum> <addnum></addnum> <addnum></addnum> <addnum></addnum> <addnum></addnum> </div> <script> Vue.component("addnum",{ template:'<div><input type="text" v-model="num"><button @click="num+=1">点击</button></div>', data: function(){ // 写在这里的数据只有当前组件可以使用 return { num:1, } } }); var vm = new Vue({ el:"#app", // 这里写的数据是全局公用的,整个文件共享 data:{ } }) </script>
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script src="js/vue.js"></script> <style> .header{ width: 100%; height: 40px; } .header ul{ list-style:none; padding:0; margin: 0; overflow: hidden; } .header ul li{ width: 120px; height: 40px; line-height: 40px; text-align: center; color: #fff; background-color: blue; float: left; margin-right: 10px; } </style> </head> <body> <div id="app"> <!-- 头部 --> <Tou></Tou> <!-- 脚部--> </div> <script> // Vue.component("组件名称",组件的相关参数[html,css,js代码]); Vue.component("Tou",{ template:`<div class="header"> <ul> <li @click="indexHander">{{index}}</li> <li>{{list}}</li> </ul> </div>`, data:function(){ return { index: "首页", list:"列表页" } }, methods:{ indexHander(){ alert("跳转到首页"); } } }); let vm = new Vue({ el:"#app", data:{} }) </script> </body> </html>
6. Vue自动化工具(Vue-cli)
前面学习了普通组件以后,接下来我们继续学习单文件组件则需要提前先安装准备一些组件开发工具。否则无法使用和学习单文件组件。
一般情况下,单文件组件,我们运行在 自动化工具vue-CLI中,可以帮我们编译单文件组件。所以我们需要在系统中先搭建vue-CLI工具,
Vue CLI 需要 Node.js 8.9 或更高版本 (推荐 8.11.0+)。你可以使用 nvm 或 nvm-windows在同一台电脑中管理多个 Node 版本。(相当于Python虚拟化管理工具)
nvm工具的下载和安装: https://www.jianshu.com/p/d0e0935b150a windows版本的
https://www.jianshu.com/p/622ad36ee020
安装记录:
打开:https://github.com/coreybutler/nvm-windows/releases
安装完成以后,先查看环境变量是否设置好了.
常用的nvm命令
nvm list # 列出目前在nvm里面安装的所有node版本
nvm install node版本号 # 安装指定版本的node.js
nvm uninstall node版本号 # 卸载指定版本的node.js
nvm use node版本号 # 切换当前使用的node.js版本
如果使用nvm工具,则直接可以不用自己手动下载,如果使用nvm下载安装 node的npm比较慢的时候,可以修改nvm的配置文件(在安装根目录下)
# settings.txt
root: C: ool
vm [这里的目录地址是安装nvm时自己设置的地址,要根据实际修改]
path: C: ool
odejs
arch: 64
proxy: none
node_mirror: http://npm.taobao.org/mirrors/node/
npm_mirror: https://npm.taobao.org/mirrors/npm/
6.1 安装node.js
Node.js是一个新的后端(后台)语言,它的语法和JavaScript类似,所以可以说它是属于前端的后端语言,后端语言和前端语言的区别:
-
运行环境:后端语言一般运行在服务器端,前端语言运行在客户端的浏览器上
-
功能:后端语言可以操作文件,可以读写数据库,前端语言不能操作文件,不能读写数据库。
我们一般安装LTS(长线支持版本):
下载地址:https://nodejs.org/en/download/【上面已经安装了nvm,那么这里不用手动安装了】
node.js的版本有两大分支:
官方发布的node.js版本:0.xx.xx 这种版本号就是官方发布的版本
社区发布的node.js版本:xx.xx.x 就是社区开发的版本
Node.js如果安装成功,可以查看Node.js的版本,在终端输入如下命令:
node -v
6.2 npm
在安装node.js完成后,在node.js中会同时帮我们安装一个npm包管理器npm。我们可以借助npm命令来安装node.js的包。这个工具相当于python的pip管理器。
npm install -g 包名 # 安装模块 -g表示全局安装,如果没有-g,则表示在当前项目安装
npm list # 查看当前目录下已安装的node包
npm view 包名 engines # 查看包所依赖的Node的版本
npm outdated # 检查包是否已经过时,命令会列出所有已过时的包
npm update 包名 # 更新node包
npm uninstall 包名 # 卸载node包
npm 命令 -h # 查看指定命令的帮助文档
6.3 安装Vue-cli
npm install -g vue-cli
如果安装速度过慢,一直超时,可以考虑切换npm镜像源:http://npm.taobao.org/
6.4.1 生成项目目录
使用vue自动化工具可以快速搭建单页应用项目目录。
该工具为现代化的前端开发工作流提供了开箱即用的构建配置。只需几分钟即可创建并启动一个带热重载、保存时静态检查以及可用于生产环境的构建配置的项目:
// 生成一个基于 webpack 模板的新项目
vue init webpack 项目名
例如:
vue init webpack myproject
// 启动开发服务器 ctrl+c 停止服务
cd myproject
npm run dev # 运行这个命令就可以启动node提供的测试http服务器
运行了上面代码以后,终端下会出现以下效果提示:
6.4.2 项目目录结构
├── build/
├── config/
├── index.html
├── node_modules/ # 项目运行的依赖库存储目录[非常大]
├── package.json # 项目运行需要的依赖库记录配置
├── src/
│ ├── App.vue # 父级组件
│ ├── assets/ # 静态资源目录,图片存放在这里
│ ├── components/ # 单文件组件保存目录
│ └── main.js
└── static/ # 静态资源目录,所有的css,js等文件放在这个目录
src 主开发目录,要开发的单文件组件全部在这个目录下的components目录下
static 静态资源目录,所有的css,js文件放在这个文件夹
dist 项目打包发布文件夹,最后要上线单文件项目文件都在这个文件夹中[后面打包项目,让项目中的vue组件经过编译变成js 代码以后,dist就出现了]
node_modules目录是node的包目录,
config是配置目录,
build是项目打包时依赖的目录
src/router 路由,后面需要我们在使用Router路由的时候,自己声明.
6.4.3 项目执行流程图
整个项目是一个访问入口文件index.html,index.html中会引入src文件夹中的main.js,main.js中会导入顶级单文件组件App.vue,App.vue中会通过组件嵌套或者路由来引用components文件夹中的其他单文件组件。
7. 单文件组件的使用
组件有两种:普通组件、单文件组件
普通组件的缺点:
-
html代码是作为js的字符串进行编写,所以组装和开发的时候不易理解,而且没有高亮效果。
-
普通组件用在小项目中非常合适,但是复杂的大项目中,如果把更多的组件放在html文件中,那么维护成本就会变得非常昂贵。
-
普通组件只是整合了js和html,但是css代码被剥离出去了。使用的时候的时候不好处理。
将一个组件相关的html结构,css样式,以及交互的JavaScript代码从html文件中剥离出来,合成一个文件,这种文件就是单文件组件,相当于一个组件具有了结构、表现和行为的完整功能,方便组件之间随意组合以及组件的重用,这种文件的扩展名为“.vue”,比如:"Home.vue"。
-
创建组件
在组件中编辑三个标签,编写视图template、vm对象script标签和css样式代码style。
7.1 template 编写html代码的地方
<template> <div id="Home"> <div class="header"> 页面头部 </div> <div class="main"> 页面主题内容 </div> <div class="footer"> 页面脚步内容 </div> </div> </template>
7.2 script编写vue.js代码
<script> export default { name:"Home", // 组件名称,用于以后路由跳转 data(){ // 当前组件中需要使用的数据 return { num:0, } }, } </script>
7.3 style编写当前组件的样式代码
// scoped 表示当前style的样式只作用于当前组件的template代码中,其他地方不会被影响 <style scoped> .header{ height: 100px; line-height: 100px; background-color: #eee; text-align: center; } </style>
在Home.vue中引入子组件----小栗子
创建保存子组件的目录common
在common目录下创建Header.vue
把Home.vue的style标签的头部样式放置到Header.vue
<template> <div id="Header" class="header"> {{message}} </div> </template> <script> export default { name:"Header", // 组件名称,用于以后路由跳转 props:["num"], data(){ // 当前组件中需要使用的数据 return { message:"页面头部", } }, } </script> // scoped 表示当前style的样式只作用于当前组件的template代码中,其他地方不会被影响 <style scoped> .header{ height: 100px; line-height: 100px; background-color: #eee; text-align: center; } </style>
<template> <div id="Home"> <!--使用子组件 将父组件的值传入子组件--> <Header :num="num"/> <div class="main"> 页面主题内容 </div> <div class="footer"> 页面脚步内容 </div> </div> </template> <script> // 导入子组件 import Header from "./common/Header" export default { name:"Home", // 组件名称,用于以后路由跳转 data(){ // 当前组件中需要使用的数据 return { num:0, } }, // 注册子组件 components:{ Header, } } </script> // scoped 表示当前style的样式只作用于当前组件的template代码中,其他地方不会被影响 <style scoped> /*.header{*/ /*height: 100px;*/ /*line-height: 100px;*/ /*background-color: #eee;*/ /*text-align: center;*/ /*}*/ </style>
<template> <div id="app"> <!--原来的代码--> <!--<img src="./assets/logo.png">--> <!--<HelloWorld/>--> <!-- 3. 使用组件 --> <HomeCom/> </div> </template> <script> // 原来的代码 // import HelloWorld from './components/HelloWorld' // 1. 导入组件 import HomeCom from './components/Home' export default { name: 'App', // 2. 注册组件 components: { HomeCom } // 原来的代码 // components: { // HelloWorld // } } </script> <style> /*#app {*/ /*font-family: 'Avenir', Helvetica, Arial, sans-serif;*/ /*-webkit-font-smoothing: antialiased;*/ /*-moz-osx-font-smoothing: grayscale;*/ /*text-align: center;*/ /*color: #2c3e50;*/ /*margin-top: 60px;*/ /*}*/ /* 有时候可以编写整个项目中的公共代码,例如重置样式的代码 */ body{ margin:0; } </style>
7.4 完成案例-点击加减数字---小栗子
在Home.vue的主体部分里面直接编写代码了
<template> <div id="Home"> <Header/> <div class="main"> <p>加减数字的功能</p> <div class="box"> <button @click="num++">+</button> <input type="text" v-model="num"> <button @click="num--">-</button> </div> </div> <div class="footer"> 页面脚步内容 </div> </div> </template> <script> import Header from "./common/Header" export default { name:"Home", // 组件名称,用于以后路由跳转 data(){ // 当前组件中需要使用的数据 return { num:0, } }, components:{ Header, } } </script> // scoped 表示当前style的样式只作用于当前组件的template代码中,其他地方不会被影响 <style scoped> .header{ height: 100px; line-height: 100px; background-color: #eee; text-align: center; } </style>
效果:
以下全代码:
<template> <div id="app"> <!--原来的代码--> <!--<img src="./assets/logo.png">--> <!--<HelloWorld/>--> <!-- 3. 使用组件 --> <HomeCom/> </div> </template> <script> // 原来的代码 // import HelloWorld from './components/HelloWorld' // 1. 导入组件 import HomeCom from './components/Home' export default { name: 'App', // 2. 注册组件 components: { HomeCom } // 原来的代码 // components: { // HelloWorld // } } </script> <style> /*#app {*/ /*font-family: 'Avenir', Helvetica, Arial, sans-serif;*/ /*-webkit-font-smoothing: antialiased;*/ /*-moz-osx-font-smoothing: grayscale;*/ /*text-align: center;*/ /*color: #2c3e50;*/ /*margin-top: 60px;*/ /*}*/ /* 有时候可以编写整个项目中的公共代码,例如重置样式的代码 */ body{ margin:0; } </style>
<template> <div id="Home"> <!--使用子组件 将父组件的值传入子组件--> <Header :num="num"/> <div class="main"> <p>加减数字的功能</p> <div class="box"> <button @click="num++">+</button> <input type="text" v-model="num"> <button @click="num--">-</button> </div> </div> <div class="footer"> 页面脚步内容 </div> </div> </template> <script> // 导入子组件 import Header from "./common/Header" export default { name:"Home", // 组件名称,用于以后路由跳转 data(){ // 当前组件中需要使用的数据 return { num:0, } }, // 注册子组件 components:{ Header, } } </script> // scoped 表示当前style的样式只作用于当前组件的template代码中,其他地方不会被影响 <style scoped> .header{ height: 100px; line-height: 100px; background-color: #eee; text-align: center; } </style>
<template> <div id="Header" class="header"> {{message}} <p>num: <input type="text" v-model="num"></p> </div> </template> <script> export default { name:"Header", // 组件名称,用于以后路由跳转 props:["num"], data(){ // 当前组件中需要使用的数据 return { message:"页面头部", } }, } </script> // scoped 表示当前style的样式只作用于当前组件的template代码中,其他地方不会被影响 <style scoped> .header{ height: 100px; line-height: 100px; background-color: #eee; text-align: center; } </style>
7.5 组件的嵌套
有时候开发vue项目时,页面也可以算是一个大组件,同时页面也可以分成多个子组件.
因为,产生了父组件调用子组件的情况.
例如,我们可以声明一个组件,作为父组件
在components/创建一个保存子组件的目录HomeSon
在HomeSon目录下,可以创建当前页面的子组件,例如,是Menu.vue
// 组件中代码必须写在同一个标签中 <template> <div id="menu"> <span>{{msg}}</span> <div>hello</div> </div> </template> <script> export default { name:"Menu", data: function(){ return { msg:"这是Menu组件里面的菜单", } } } </script>
然后,在父组件中调用上面声明的子组件。
最后,父组件被App.vue调用.就可以看到页面效果.
效果:
7.6 传递数据
例如,我们希望把父组件Home.vue的数据传递给子组件,例如Header.vue.
可以通过props属性来进行传递.
传递数据三个步骤: 1、 在父组件中,调用子组件的组名处,使用属性值的方式往下传递数据 <Menu :mynum="num" title="home里面写的数据"/> # 上面表示在父组件调用Menu子组件的时候传递了2个数据: 如果要传递变量[变量可以各种类型的数据],属性名左边必须加上冒号:,同时,属性名是自定义的,会在子组件中使用。 如果要传递普通字符串数据,则不需要加上冒号: 2、 在子组件中接受上面父组件传递的数据,需要在vm组件对象中,使用props属性类接受。 <script> export default { name:"Menu", props:["mynum","title"], data: function(){ return { msg:"这是Menu组件里面的菜单", } } } </script> // 上面 props属性中表示接受了两个数据。 3、在子组件中的template中使用父组件传递过来的数据. <template> <div id="menu"> <span>{{msg}},{{title}}</span> <div>hello,{{mynum}}</div> </div> </template>
效果:
步骤流程:
使用父组件传递数据给子组件时, 注意一下几点:
-
传递数据是变量,则需要在属性左边添加冒号.
传递数据是变量,这种数据称之为"动态数据传递"
传递数据不是变量,而是数值或者字符串,这种数据称之为"静态数据传递"
-
父组件中修改了数据,在子组件中会被同步修改,但是,子组件中的数据修改了,是不会影响到父组件中的数据.
这种情况,在开发时,也被称为"单向数据流"
-
事实上,我们如果要在子组件中把数据传递给父组件,也可以完成的。
通过事件冒泡的方式,进行数据传递
在vue中提供的this.$emit()方法进行给我们传递数据
8. 在组件中使用axios获取数据
默认情况下,我们的项目中并没有对axios包的支持,所以我们需要下载安装。
在项目根目录中使用 npm安装包在命令行下执行安装包的命令:
npm install axios
接着在main.js文件中,导入axios并把axios对象 挂载到vue属性中多为一个子对象,这样我们才能在组件中使用。
// The Vue build version to load with the `import` command // (runtime-only or standalone) has been set in webpack.base.conf with an alias. import Vue from 'vue' import App from './App' // 这里表示从别的目录下导入 单文件组件 import axios from 'axios'; // 从node_modules目录中导入包 Vue.config.productionTip = false Vue.prototype.$axios = axios; // 把对象挂载vue中 /* eslint-disable no-new */ new Vue({ el: '#app', components: { App }, template: '<App/>' });
8.1 在组件中使用axios获取数据
代码编写在Heaer.vue子组件中
<template> <div id="Header" class="header"> {{message}} <p>num: <input type="text" v-model="num"></p> <p>深圳的天气情况: {{weather_info}}</p> </div> </template> <script> export default { name:"Header", // 组件名称,用于以后路由跳转 props:["num"], data(){ // 当前组件中需要使用的数据 return { message:"页面头部", weather_info:"", } }, // 钩子方法,在页面中vue挂在data数据以后,自动执行 created() { // 使用axios发送请求获取数据 this.$http.get("http://wthrcdn.etouch.cn/weather_mini?city=深圳").then(response=>{ console.log(response.data); console.log(response.data.data.ganmao); this.weather_info = response.data.data.ganmao; }).catch(error=>{ }); } } </script> // scoped 表示当前style的样式只作用于当前组件的template代码中,其他地方不会被影响 <style scoped> .header{ height: 100px; line-height: 100px; background-color: #eee; text-align: center; } </style>
<template> <div id="Home"> <Header :num="num"/> <div class="main"> <p>加减数字的功能</p> <div class="box"> <button @click="num++">+</button> <input type="text" v-model="num"> <button @click="num--">-</button> </div> </div> <div class="footer"> 页面脚步内容 </div> </div> </template> <script> import Header from "./common/Header" export default { name:"Home", // 组件名称,用于以后路由跳转 data(){ // 当前组件中需要使用的数据 return { num:0, } }, components:{ Header, } } </script> // scoped 表示当前style的样式只作用于当前组件的template代码中,其他地方不会被影响 <style scoped> .header{ height: 100px; line-height: 100px; background-color: #eee; text-align: center; } </style>
<template> <div id="app"> <!--原来的代码--> <!--<img src="./assets/logo.png">--> <!--<HelloWorld/>--> <!-- 3. 使用组件 --> <HomeCom/> </div> </template> <script> // 原来的代码 // import HelloWorld from './components/HelloWorld' // 1. 导入组件 import HomeCom from './components/Home' export default { name: 'App', // 2. 注册组件 components: { HomeCom } // 原来的代码 // components: { // HelloWorld // } } </script> <style> /*#app {*/ /*font-family: 'Avenir', Helvetica, Arial, sans-serif;*/ /*-webkit-font-smoothing: antialiased;*/ /*-moz-osx-font-smoothing: grayscale;*/ /*text-align: center;*/ /*color: #2c3e50;*/ /*margin-top: 60px;*/ /*}*/ /* 有时候可以编写整个项目中的公共代码,例如重置样式的代码 */ body{ margin:0; } </style>
使用的时候,因为本质上来说,我们还是原来的axios,所以也会收到同源策略的影响。
后面开发项目的时候,我们会使用cors来解决跨域的问题。