vue组件化开发
组件(Component)是自定义封装代码的功能。在前端开发过程中,经常出现多个网页的功能是重复的,而且很多不同的页面之间,也存在同样的功能。
而在网页中实现一个功能,需要使用html定义功能的内容结构,使用css声明功能的外观样式,还要使用js来定义功能的特效,因此就产生了把一个功能相关的[HTML、css和javascript]代码封装在一起组成一个整体的代码块封装模式,我们称之为“组件”。
所以,组件就是一个html网页中的功能,一般就是一个标签,标签中有自己的html内容结构,css样式和js特效。
这样,前端人员就可以在组件化开发时,只需要书写一次代码,随处引入即可使用。
vue的组件有两种:默认组件[全局组件] 和 单文件组件
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script src="vue.min.js"></script> <script src="axios.js"></script> </head> <body> <div id="app"> <!-- 2.调用组件,--> <mynav></mynav> <mynav></mynav> <mynav></mynav> </div> <script> // 1.通过Vue类创建组件,组件与组件之间的数据,方法都是独立的,而且互相不会被影响。 Vue.component("mynav",{ // mynav为组件名 // template为组件模板,多行文本使用反引号`` template: `<div> <button @click="sub" :disabled="disabled">-</button> <input type="text" v-model="num"> <button @click="add">+</button> </div>`, data(){ // 组件中数据,书写格式和vue有所区别 return { num: 0, disabled: false } }, // methods:组件中的方法 methods:{ sub(){ this.num--; }, add(){ this.num++; } }, // 监听属性 watch:{ num(){ if(this.num<1){ this.num=0; this.disabled = true }else{ this.disabled = false } } } }); var vm = new Vue({ el: "#app", data: { }, }); </script> </body> </html>
前面学习了普通组件以后,接下来我们继续学习单文件组件则需要提前先安装准备一些组件开发工具。否则无法使用和学习单文件组件。
一般情况下,单文件组件,我们运行在 自动化工具vue-CLI中,可以帮我们把单文件组件编译成普通的js代码。所以我们需要在电脑先安装搭建vue-CLI工具。
Vue CLI 需要 Node.js 8.9 或更高版本 (推荐 8.11.0+)。你可以使用 nvm (linux或mac)或 nvm-windows(windows上)在同一台电脑中管理多个 Node 版本。
nvm包(windows包)下载地址:
https://github.com/coreybutler/nvm-windows/releases
nvm工具的下载和安装:
linux:https://www.jianshu.com/p/622ad36ee020
curl -o- https://github.com/nvm-sh/nvm/v0.35.3/install.sh | bash
常用nvm命令
常用的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:\tool\nvm [这里的目录地址是安装nvm时自己设置的地址,要根据实际修改]
path: C:\tool\nodejs
arch: 64
proxy: none
node_mirror: http://npm.taobao.org/mirrors/node/
npm_mirror: https://npm.taobao.org/mirrors/npm/
-
运行环境:后端语言一般运行在服务器端,前端语言运行在客户端的浏览器上
-
功能:后端语言可以操作文件,可以读写数据库,前端语言不能操作文件,不能读写数据库。
我们一般安装LTS(长线支持版本 Long-Time Support):
下载地址:https://nodejs.org/en/download/【上面已经安装了nvm,那么这里不用手动安装了】
node.js的版本有两大分支:
官方发布的node.js版本:0.xx.xx 这种版本号就是官方发布的版本
社区发布的node.js版本:xx.xx.x 就是社区开发的版本
Node.js如果安装成功(node安装的时候直接选择安装位置,然后下一步下一步就好了),可以查看Node.js的版本,在终端输入如下命令:
node -v
在安装node.js完成后,在node.js中会同时帮我们安装一个npm包管理器npm。我们可以借助npm命令来安装node.js的包。这个工具相当于python的pip管理器。
在pycharm中的终端执行
npm install -g 包名 # 安装模块 -g表示全局安装,如果没有-g,则表示在当前项目安装 npm list # 查看当前目录下已安装的node包 npm view 包名 engines # 查看包所依赖的Node的版本 npm outdated # 检查包是否已经过时,命令会列出所有已过时的包 npm update 包名 # 更新node包 npm uninstall 包名 # 卸载node包 npm run build # 在前端项目目录下执行,将项目编译打包成一个index.html和一堆js、css npm 命令 -h # 查看指定命令的帮助文档
补充:安装yarn
方法一:使用安装包安装
官方下载安装包,https://yarnpkg.com/zh-Hans/docs/install,安装完毕后,一定要配置环境变量。
方法二:使用npm安装
npm i yarn -g -i:install -g:全局安装(global),使用 -g 或 --global
输入yarn -version 可以看到版本号,说明安装成功了。我们就可以在项目中像使用npm一样使用yarn了
常用命令
npm install -g vue-cli
直接执行命令:
# npm install -g cnpm --registry=https://registry.npm.taobao.org
使用vue-cli自动化工具可以快速搭建单页应用项目目录。
该工具为现代化的前端开发工作流提供了开箱即用的构建配置。只需几分钟即可创建并启动一个带热重载、保存时静态检查以及可用于生产环境的构建配置的项目:
// 生成一个基于 webpack 模板的新项目,webpack是前端比较流行的一个自动化开发工具 // 格式 vue init webpack 项目目录名 // 例如:在想要创建vue项目的目录下输入,这个和django创建项目一样意思 vue init webpack vuedemo // 启动开发服务器 ctrl+c 停止服务 cd vuedemo # 进入项目目录下 npm run dev # 运行这个命令就可以启动node提供的测试http服务器,运行项目
创建项目示例:
E:\vue_project>vue init webpack vuedemo ? Project name vuedemo // 创建的项目名称,直接回车使用命令中的名字 ? Project description A Vue.js project // 项目描述,默认即可,也可以自己输入 ? Author alias // 项目作者,如果电脑上有git,会自动获取git账号作为作者,没有就手动输入 ? Vue build standalone // vue build编译代码的方式,选择第一项Runtime + Compiler ? Install vue-router? No // 是否安装vue路由插件,以后默认安装,现在还未学,先选no ? Use ESLint to lint your code? No // 代码检测工具,ES6的新语法 ? Set up unit tests No // 是否安装单元测试工具 ? Setup e2e tests with Nightwatch? No // 也是测试工具 ? Should we run `npm install` for you after the project has been created? (recommended) npm // 安装第三方模块方式的选择nmp、yarn或手动 vue-cli · Generated "vuedemo". # Installing project dependencies ...
运行项目:ctrl+c停止
# cd vuedemo # 进入项目目录 # npm run dev # 启动测试服务器,运行项目
# 第一步:npm cache clean --force npm cache clean --force # 第二步:切换为淘宝镜像 npm config set registry https://registry.npm.taobao.org/ npm config get registry https://registry.npm.taobao.org/
第二步:
安装完之后重启pycharm
src 主开发目录,要开发的单文件组件全部在这个目录下的components目录下
static 静态资源目录,所有的css,js,图片等资源文件放在这个文件夹
dist项目打包发布文件夹,最后要上线单文件项目文件都在这个文件夹中[后面打包项目,让项目中的vue组件经过编译变成js 代码以后,dist就出现了]
node_modules目录是node的依赖包目录(如果删了这个目录,本地测试开发的时候就无法启动项目了。为了方便本地开发,要保留这个目录,如果这个目录已经被误删了,可以通过# npm install 命令找回)
config是配置目录,
build是项目打包时依赖的目录
src/router 路由,后面需要我们在使用Router路由的时候,自己声明.
整个项目是一个主文件index.html,index.html中会引入src文件夹中的main.js,main.js中会导入顶级单文件组件App.vue,App.vue中会通过组件嵌套或者路由来引用components文件夹中的其他单文件组件。
注解:
main.js
// 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' // 基于node_modules目录中保存的目录名 import App from './App' // 基于当前文件的目录名 .表示当前目录 ..表示上级目录 // vue中导入模块,在vue导包时,如果文件后缀是js或vue结尾的可以不用加后缀 // import 对象/类 from "目录路径" Vue.config.productionTip = false /* eslint-disable no-new */ new Vue({ el: '#app', components: { App }, template: '<App/>' })
组件有两种:普通组件、单文件组件
普通组件的缺点:
-
html代码是作为js的字符串进行编写,所以组装和开发的时候不易理解,而且没有高亮效果。
-
普通组件用在小项目中非常合适,但是复杂的大项目中,如果把更多的组件放在html文件中,那么维护成本就会变得非常昂贵。
-
普通组件只是整合了js和html,但是css代码被剥离出去了。使用的时候的时候不好处理。
将一个组件相关的html结构,css样式,以及交互的JavaScript代码从html文件中剥离出来,合成一个文件,这种文件就是单文件组件(写在src下的components文件夹中,文件名使用驼峰式写法),相当于一个组件具有了结构、表现和行为的完整功能,方便组件之间随意组合以及组件的重用,这种文件的扩展名为“.vue”,比如:"Home.vue"。
在组件中编辑三个标签,编写视图、vm对象和css样式代码。
2.3.1、单文件组件使用示例
先将App.vue还原成像刚创建的样子
Home.vue
<template> <div> home组件的代码内容 </div> </template> <script> // 为什么在App.vue中使用import Home from "./components/Home" 就能导入, // 原因:export抛出对象,只有通过export关键字抛出的对象,才能被其他js文件导入 export default { name: "Home" //组件名称,组件文件名是什么就写什么(驼峰式) } </script> <style scoped> /* scoped:css只对当前组件有效 */ </style>
加载一个组件内容显示到页面中,需要在App.vue中完成3步
App.vue
<template> <!-- 先写一个双标签(注意),并且template下面有且只能由一个标签(写双标签)--> <div id="app"> <input type="text"> <!-- 3.标签内部调用组件名 --> <calculate></calculate> <!-- 调用组件写法一 --> <calculate/> <!-- 调用组件写法二 --> </div> </template> <script> // 1.导入组件,import后面跟的类似于as起别名,将from后的起个别名的意思,注册和调用都用这个名字 import calculate from "./components/Home" export default { name: 'App', // 2.注册组件,多个组件写在后面,逗号隔开 components:{calculate,} } </script> <style> </style>
浏览器访问:
关于组件样式:
/* 直接在style标签写css样式,默认是全局样式,所有的组件都被css渲染到 为了保证css在各个组件中互相独立,不会相互影响,我们需要编写局部的css样式, 编写局部css样式,只需要css样式在当前(App.vue)组件中使用,可以给当前组件(App.vue)的style标签添加上scoped即可 */
App.vue
<template> <!-- 先写一个双标签(注意),并且template下面有且只能由一个标签(写双标签)--> <div id="app"> <input type="text"> <!-- 3.标签内部调用组件名 --> <Home></Home> <!-- 调用组件写法一 --> <Home/> <!-- 调用组件写法二 --> </div> </template> <script> // 1.导入组件 import Home from "./components/Home" export default { name: 'App', // 2.注册组件,多个组件写在后面,逗号隔开 components:{Home,} } </script> <style scoped> /* 直接在style标签写css样式,默认是全局样式,所有的组件都被css渲染到 为了保证css在各个组件中互相独立,不会相互影响,我们需要编写局部的css样式, 编写局部css样式,只需要css样式在当前(App.vue)组件中使用,可以给当前组件(App.vue)的style标签添加上scoped即可 */ div{ color:red; } </style>
Home.vue
<template> <div> home组件的代码内容 </div> </template> <script> // 为什么在App.vue中使用import Home from "./components/Home" 就能导入, // 原因:export抛出对象,只有通过export关键字抛出的对象,才能被其他js文件导入 export default { name: "Home" //组件名称,组件文件名是什么就写什么(驼峰式) } </script> <style scoped> /* scoped:css只对当前组件有效 */ div{ color: red; } </style>
浏览器访问:
在App.vue中导入外部js文件中的对象
settings.js(也需要export 抛出对象)
var settings = {
url:"http://www.baidu.com"
};
export default settings;
App.vue
<template> <!-- 先写一个双标签(注意),并且template下面有且只能由一个标签(写双标签)--> <div id="app"> <input type="text"> <!-- 3.标签内部调用组件名 --> <Home></Home> <!-- 调用组件写法一 --> <Home/> <!-- 调用组件写法二 --> </div> </template> <script> // 1.导入组件 import Home from "./components/Home" import settings from "./settings"; export default { name: 'App', // 2.注册组件,多个组件写在后面,逗号隔开 components:{Home,}, created(){ console.log(settings) } } </script> <style scoped> /* 直接在style标签写css样式,默认是全局样式,所有的组件都被css渲染到 为了保证css在各个组件中互相独立,不会相互影响,我们需要编写局部的css样式, 编写局部css样式,只需要css样式在当前(App.vue)组件中使用,可以给当前组件(App.vue)的style标签添加上scoped即可 */ div{ color:red; } </style>
浏览器访问查看
App.vue
<template> <!-- 先写一个双标签(注意),并且template下面有且只能由一个标签(写双标签)--> <div id="app"> <!-- 3.标签内部调用组件名 --> <Home></Home> <!-- 调用组件写法一 --> <Home/> <!-- 调用组件写法二 --> </div> </template> <script> // 1.导入组件 import Home from "./components/Home" export default { name: 'App', // 2.注册组件,多个组件写在后面,逗号隔开 components:{Home,}, } </script> <style scoped> /* 直接在style标签写css样式,默认是全局样式,所有的组件都被css渲染到 为了保证css在各个组件中互相独立,不会相互影响,我们需要编写局部的css样式, 编写局部css样式,只需要css样式在当前(App.vue)组件中使用,可以给当前组件(App.vue)的style标签添加上scoped即可 */ div{ color:red; } </style>
Home.vue
<template> <div> <button @click="sub" :disabled="disabled">-</button> <input type="text" v-model="num"> <button @click="add">+</button> </div> </template> <script> // 为什么在App.vue中使用import Home from "./components/Home" 就能导入, // 原因:export抛出对象,只有通过export关键字抛出的对象,才能被其他js文件导入 export default { name: "Home", //组件名称,组件文件名是什么就写什么(驼峰式) data(){ // 组件的数据 return { num: 0, disabled: false } }, methods:{ sub(){ this.num-- }, add(){ this.num++ } }, watch:{ num(){ if(this.num<1){ this.num=0; this.disabled = true }else{ this.disabled = false } } } } </script> <style scoped> /* scoped:css只对当前组件有效 */ </style>
效果:
有时候开发vue项目时,页面也可以算是一个大组件,同时页面也可以分成多个子组件.
因为,产生了父组件调用子组件的情况.
例如,我们可以声明一个组件,作为父组件
在components/创建一个保存子组件的目录HomeSon(目录名随意,正常为common)
在HomeSon目录下,可以创建当前页面的子组件,例如,是Menu.vue和Header.vue
Menu.vue
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
// 组件中代码必须写在同一个标签中 <template> <div id="menu"> <span>{{msg}}</span> <div>hello</div> </div> </template> <script> export default { name: "Menu", data: function () { return { msg: "这是Menu组件里面的菜单", } } } </script>
Header.vue
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<template> <div id="header"> <span>{{msg}}</span> </div> </template> <script> export default { name: "Header", data: function () { return { msg: "这是Menu组件里面的菜单", } } } </script> <style scoped> </style>
然后,在父组件Home.vue中调用上面声明的子组件。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<template> <div id="home"> <Header></Header> <button @click="sub" :disabled="disabled">-</button> <input type="text" v-model="num"> <button @click="add">+</button> <Menu></Menu> </div> </template> <script> import Menu from "./HomeSon/Menu" import Header from "./HomeSon/Header" export default { name: "Home", data() { // 组件的数据 return { num: 0, disabled: false } }, methods: { sub() { this.num-- }, add() { this.num++ } }, watch: { num() { if (this.num < 1) { this.num = 0; this.disabled = true } else { this.disabled = false } } }, components: { Menu, Header } } </script> <style scoped> </style>
最后,父组件被App.vue调用.就可以看到页面效果.
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<template> <!-- 先写一个双标签(注意),并且template下面有且只能由一个标签(写双标签)--> <div id="app"> <Home/> </div> </template> <script> // 1.导入组件,import后面跟的类似于as起别名,将from后的起个别名的意思,注册和调用都用这个名字 import Home from "./components/Home" export default { name: 'App', // 2.注册组件,多个组件写在后面,逗号隔开 components: {Home,} } </script> <style> </style>
效果:
例如,我们希望把父组件的数据传递给子组件.
可以通过props属性来进行数据传递.
传递数据三个步骤:
1.在父组件Home.vue中,调用子组件Menu的组名处,使用属性值的方式往下传递数据
<Menu :mynum="num" title="home里面写的数据"/> # 上面表示在父组件调用Menu子组件的时候传递了2个数据: 如果要传递变量[变量可以各种类型的数据],属性名左边必须加上冒号:,同时,属性名是自定义的,会在子组件中使用。 如果要传递普通字符串数据,则不需要加上冒号:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<template> <div id="home"> <Header></Header> <button @click="sub" :disabled="disabled">-</button> <input type="text" v-model="num"> <button @click="add">+</button> <Menu :mynum="num" title="home里面写的数据"></Menu> // 向Menu子组件传递mynum,title </div> </template> <script> import Menu from "./HomeSon/Menu" import Header from "./HomeSon/Header" export default { name: "Home", data() { // 组件的数据 return { num: 0, disabled: false } }, methods: { sub() { this.num-- }, add() { this.num++ } }, watch: { num() { if (this.num < 1) { this.num = 0; this.disabled = true } else { this.disabled = false } } }, components: { Menu, Header } } </script> <style scoped> </style>
2.在子组件中接受上面父组件传递的数据,需要在vm组件对象中,使用props属性类接受。
<script> export default { name:"Menu", props:["mynum","title"], data: function(){ return { msg:"这是Menu组件里面的菜单", } } } </script> // 上面 props属性中表示接受了两个数据。
3.在子组件中的template中使用父组件Home.vue传递过来的数据.
<template> <div id="menu"> <span>{{msg}},{{title}}</span> <div>hello,{{mynum}}</div> </div> </template>
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<template> <div id="menu"> <span>{{msg}},{{title}}</span> <div>hello,{{mynum}}</div> <!-- 使用父组件传递过来的数据 --> </div> </template> <script> export default { name: "Menu", props:["mynum","title"], //接受父组件传递的数据 data: function () { return { msg: "这是Menu组件里面的菜单", } } } </script>
效果:
步骤流程:
使用父组件传递数据给子组件时, 注意一下几点:
-
传递数据是变量,则需要在属性左边添加冒号.
传递数据是变量,这种数据称之为"动态数据传递"
传递数据不是变量,这种数据称之为"静态数据传递"
-
父组件中修改了数据,在子组件中会被同步修改,但是,子组件中的数据修改了,是不不会影响到父组件中的数据.
这种情况,在开发时,也被称为"单向数据流"
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<template> <div id="menu"> <p>{{msg}}</p> <button @click="num1++">Menu.vue组件中的num1={{num1}}</button> </div> </template> <script> export default { name: "Menu", data: function () { return { msg: "这是Menu组件里面的菜单", num1: 10, } }, // 子组件传递数据给父组件[事件的方式进行传递]: watch: { num1() { // this.$emit("父元素的自定义事件名",要传递的参数1,要传递的参数2,要传递的参数3..); // 通过this.$emit()方法,子组件可以把数据传递给父组件 this.$emit("postparentdata", this.num1,"来自Menu.vue的文本数据"); } } } </script>
<template> <div id="home"> <Header></Header> <button @click="sub" :disabled="disabled">-</button> <input type="text" v-model="num"> <button @click="add">+</button> <Menu @postparentdata="getsondata"></Menu> //声明对应事件属性,和方法 <p>父组件的num1来自子组件:{{num1}}</p> </div> </template>
3.父组件中,声明一个自定义方法,在事件被调用时,执行的。
<script> import Menu from "./HomeSon/Menu" import Header from "./HomeSon/Header" export default { name: "Home", data() { return { num: 0, num1: 0, // 来自子组件的参数数据 disabled: false } }, methods: { sub() { this.num-- }, add() { this.num++ }, getsondata(v1, v2) { // message表示来自子组件的$emit里面的参数,有几个就接收几个... console.log(v1, v2); this.num1= v1; } }, watch: { num() { if (this.num < 1) { this.num = 0; this.disabled = true } else { this.disabled = false } } }, components: { Menu, Header } } </script>
父组件Home.vue完整代码
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<template> <div id="home"> <Header></Header> <button @click="sub" :disabled="disabled">-</button> <input type="text" v-model="num"> <button @click="add">+</button> <Menu @postparentdata="getsondata"></Menu> <p>父组件的num1来自子组件:{{num1}}</p> </div> </template> <script> import Menu from "./HomeSon/Menu" import Header from "./HomeSon/Header" export default { name: "Home", data() { return { num: 0, num1: 0, // 来自子组件的参数数据 disabled: false } }, methods: { sub() { this.num-- }, add() { this.num++ }, getsondata(v1, v2) { // message表示来自子组件的$emit里面的参数,有几个就接收几个... console.log(v1, v2); this.num1= v1; } }, watch: { num() { if (this.num < 1) { this.num = 0; this.disabled = true } else { this.disabled = false } } }, components: { Menu, Header } } </script> <style scoped> </style>
补充子组件与子组件直接传递数据
# 方式一 vuex # 方式二 html5的本地存储localstorage #方式三 外部js文件
2.4、vue组件化开发中使用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; // 把axios对象作为一个子对象挂载VM对象中作为属性 /* eslint-disable no-new */ new Vue({ el: '#app', components: { App }, template: '<App/>' });
在组件中使用axios,例如在App.vue中
<template> <!-- 先写一个双标签(注意),并且template下面有且只能由一个标签(写双标签)--> <div id="app"> <Home/> <input type="text" v-model="city"> <button @click="get_weather">获取天气</button> <p>{{data}}</p> </div> </template> <script> // 1.导入组件,import后面跟的类似于as起别名,将from后的起个别名的意思,注册和调用都用这个名字 import Home from "./components/Home" export default { name: 'App', data() { return { city: "上海", data: "", } }, methods: { get_weather() { // 获取天气 this.$axios.get("http://wthrcdn.etouch.cn/weather_mini", { params: { 'city': this.city, }, }).then(response => { let data = response.data.data; this.data = data; this.yesterday = data.yesterday }).catch(error => { console.log(error.response) }); } }, // 2.注册组件,多个组件写在后面,逗号隔开 components: {Home,} } </script> <style> </style>