单文件组件
单文件组件就是一个文件对应一个组件, 单文件组件的名字通常是xxx.vue(命名规范和组件名的命名规范相同)
,这个文件是Vue框架规定的只有它能够认识,浏览器无法直接打开运行
- Vue框架可以将
xxx.vue文件
进行编译为浏览器能识别的html js css
的代码
xxx.vue文件
的内容包括结构HTML代码(template标签),交互JS代码(script标签),样式CSS代码(style)三大类
- VSCode工具插件:
vetur
在编写xxx.vue文件的时候代码有高亮提示,并且也可以通过输入<v
生成代码,Auto Rename Tag
插件方便同时修改标签名
export和import
export
用于暴露数据(属性和方法以及对象): 常见的暴露方式有分别暴露,统一暴漏,默认暴露
// m1.js文件中使用分别暴露属性和方法
export let school = '尚硅谷';export function teach() {console.log("我们可以教给你开发技能");
}// m2.js文件中使用对象的方式统一暴露
let school = '尚硅谷';
function findJob(){console.log("我们可以帮助你找工作!!");
}
export {school, findJob};// m3.js文件中使用默认暴露(可以暴露任意类型,一般是个对象)
export default {school: 'ATGUIGU',change: function(){console.log("我们可以改变你!!");}
}
import
用于导入js文件中暴露的数据(属性,方法,对象)
<body><script type="module">// 通用的导入方式,m1,m2,m3分别存储了暴露的数据import * as m1 from "./src/js/m1.js";import * as m2 from "./src/js/m2.js";import * as m3 from "./src/js/m3.js";// 解构赋值形式即通过对象的形式使用暴露的数据,属性方法重名时使用as关键字// import {a, b} from ‘模块标识符'import {school, teach} from "./src/js/m1.js";import {school as guigu, findJob} from "./src/js/m2.js";// default是个关键字不能直接使用,需要使用别名机制import {default as m3} from "./src/js/m3.js";// 导入的简便形式但是只能针对默认暴露,m3是别名存储了暴露的数据和方法// import 任意名称 from ‘模块标识符’import m3 from "./src/js/m3.js";// 访问暴露的属性和方法需要加一层default结构console.log(m3.default.school);</script>
</body>
将所有模块的引入单独写到一个js文件中
,在index,html
中使用script标签,类型是module
直接引入这个js文件即可
//模块引入的入口文件
import * as m1 from "./m1.js";
import * as m2 from "./m2.js";
import * as m3 from "./m3.js";
<body><!--引入js文件,类型是module--><script src="./src/js/app.js" type="module"></script>
</body>
组件嵌套之单文件组件
组件化开发的流程: 浏览器不认识.vue文件
并且也不认识 ES6 的模块化语法, 需要安装Vue脚手架
第一步: 创建xxx.vue
文件创建组件可以嵌套使用其他组件
<!--App.vue-->
<template><div><h1>App组件</h1><!-- 使用组件 --><X></X><Y></Y></div>
</template>
<script>import X from './X.vue'import Y from './Y.vue'// 把组件暴露出去export default {// 注册组件components : {X, Y}}
</script><!--X.vue-->
<template><div><h2>X组件</h2><!-- 使用组件 --><X1></X1></div>
</template>
<script>import X1 from './X1.vue'export default {// 注册组件components : {X1}}
</script>
<!--X1.vue-->
<template><div><h3>X1组件</h3></div>
</template>
<script>export default {// 暴露X1组件}
</script><!--Y.vue-->
<template><div><h2>Y组件</h2><!-- 使用组件 --><Y1></Y1></div>
</template>
<script>// 导入组件Y1(Y1可以使用其它名字)import Y1 from './Y1.vue'export default {// 注册组件components : {Y1}}
</script><!--Y1.vue-->
<template><div><h3>Y1组件</h3></div>
</template><script>export default {// 暴露Y1组件}
</script>
第二步: 使用js代码
导入根组件App.vue
文件,创建vm实例并注册App组件,最后将这段代码写到一个main.js入口文件
中即可
import App from './App.vue'
new Vue({el : '#root',// 使用组件template : `<App></App>`,// 注册App组件components : {App}
})
第三步: 引入vue.js
文件和main.js
文件,在index.html
文件中创建vm实例要挂载的元素
第四步: 打开index.html
页面,浏览器加载vue.js和main.js文件后会创建Vue实例vm,完成所有组件及其子组件的创建和注册,最后编译模板语句然后渲染页面
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>单文件组件</title>
</head>
<body><div id="root"></div><script src="../js/vue.js"></script><script src="./main.js"></script>
</body>
</html>
使用脚手架组件开发
安装Vue CLI(脚手架)
Vue 的脚手架(Command Line Interface)是Vue官方为方便开发者而提供的标准化开发平台,Vue CLI 4.x
需要Node.js v8.9及以上版本(推荐v10以上)
第一步:执行npm install -g @vue/cli
命令安装脚手架,-g
表示全局安装即只需要安装一次,使用vue
命令测试是否安装成功
第二步: 在指定目录下执行 vue creat vue_pro
命令, 表示创建一个vue_pro项目(内置Hello Word案例),自带脚手架环境且内置了webpack loader
第三步: 在vue_pro项目的根目录下执行 npm run serve
命令编译Vue程序,此时会自动将项目中的xxx.vue
文件转成html css js
代码
认识脚手架项目结构
index.html文件
脚手架项目中的index.html
文件中只有一个容器,并且没有看到引入vue.js文件和main.js文件,因为Vue脚手架会自动找到main.js文件,前提是文件名称和位置固定
<!DOCTYPE html>
<html lang=""><head><meta charset="utf-8"><!--让IE浏览器启用最高渲染标准,IE8是不支持Vue的--><meta http-equiv="X-UA-Compatible" content="IE=edge"><!-- 开启移动端虚拟窗口(理想视口),可以设置虚拟窗口的宽高以及初始化比例,达到手机端浏览网页时缩放和移动的效果--><meta name="viewport" content="width=device-width,initial-scale=1.0"><!--设置页签图标,BASE_URL是Vue模仿Jsp语法设置的绝对路径,绝对路径比相对路径更加稳定--><link rel="icon" href="<%= BASE_URL %>favicon.ico"><!--webpack会去package.json中找name作为标题,也可以自己手动设置--><title><%= htmlWebpackPlugin.options.title %></title><title>欢迎使用本系统</title></head><body><!--当浏览器不支持JS语言的时候,显示如下的提示信息--><noscript><strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><!-- 容器 --><div id="app"></div><!-- built files will be auto injected --></body>
</html>
配置文件vue.config.js
vue.config.js
文件是脚手架默认的配置文件,具体配置项可以参考Vue CLI
官网手册
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({transpileDependencies: true,// 保存时是否进行语法检查(如组件的名字应该由多单词组成), 默认值true表示检查,false表示不检查lintOnSave : false,// 配置入口文件的位置和名称pages: {index: {entry: 'src/main.js',}},
入口文件main.js
Vue包含两部分: 一部分是Vue的核心
,一部分是模板编译器(占整个vue.js文件体积的三分之一)
- 由于最终使用webpack打包的时候代码肯定是已经编译好了, 这时候Vue中的模板编译器就没有存在的必要了,所以为了缩小体积Vue脚手架中默认直接引入的就是一个
缺失模板编译器的vue.js文件即Vue的仅运行时版本
由于Vue引入的是缺失了模板编译器的vue.js文件,所以在main.js文件
中创建Vue实例时无法编译template配置项
中的模板语句, 但xxx.Vue文件
的template标签
中的模板语句可以正常编译,因为Vue在package.json文件中进行了配置
- 第一种方式: 引入一个完整的vue.js,
import Vue from 'vue/dist/vue.js'
- 第二种方式: 使用
render函数
代替template配置项,这个函数由vue自动调用,并且会传递过来一个函数createElemen
可以用来创建元素(创建完必须返回)
// 这里默认引入的是dist/vue.runtime.esm.js(esm版本是ES6模块化版本)
import Vue from 'vue'
// 导入App组件(根组件)
import App from './App.vue'// 关闭生产提示信息
Vue.config.productionTip = false// 创建Vue实例
new Vue({el : '#app',// 完整写法render(createElement){ // 创建了一个div元素//return createElement('div', 'render函数')return createElement(App)} // 箭头函数的简写形式,参数只有一个省略括号,函数体只有一条语句可以省略{}和retuenrender : h => h(App)})
App根组件和其他组件
<!--App.vue-->
<template><div><h1>App组件</h1><!-- 使用组件 --><X></X><Y></Y></div>
</template><script>// 确定组件引入时的路径import X from './components/X.vue'import Y from './components/Y.vue'export default {// 注册组件components : {X, Y}}
</script>
<!--剩下的和组件嵌套的内容相同-->
组件间的数据传递
发送组件(父组件)
通过给子组件标签属性赋值的形式传递数据, 接收组件(子组件)
使用props配置项声明属性接收父组件传过来的数据
简单接收
: props : [‘brand’,‘color’,‘price’]接收时添加类型限制(非必要)
: props : { brand : String,color : String,price : Number}接收时添加类型限制(必要)和默认值
: props : {name : {type : Number, required : true}, age : {type : Number, default : 10}}
创建一个子组件Car
接收父组件传递过来的数据,每当父组件重新渲染时,子组件中使用props中的属性
接收到的值会被重新赋值,有默认值是默认值
- 使用props接收到的数据不建议直接修改,因为父组件下次重新渲染时又会给子组件中使用prop配置项接收到的属性重新赋值
- 一般通过间接修改一个中间变量的值避免父组件重新渲染时子组件修改后的值被覆盖的问题
<template><div><h3>品牌:{{brand}}</h3><h3>价格:{{cprice}}</h3><h3>颜色:{{color}}</h3><button @click="add">价格加1</button></div>
</template>
<script>
export default {name : 'Car',data() {return {// 将接收到的price属性的值赋值给一个中间变量cprice : this.price}},methods : {add(){// 修改中间变量的值,即使父组件重新渲染时子组件修改后的值也不会被覆盖this.cprice++}},// 在Car这个子组件当中使用props配置项动态的接收其他组件传递过来的数据,不再使用自己配置项data中的静态数据data() {return {brand : '宝马520',price : 10,color : '黑色'}}, // 第一种:简单的接收方式,直接采用数组接收props : ['brand','color','price']// 第二种:接收的时候添加类型限制(类型不符合会报错)props : {brand : String,color : String,price : Number} // 第三种:接收的时候添加类型的必要性限制和默认值props : {brand : {type : String,required : true},color : {type : String,default : '红色'},price : {type : Number,required : true}}}
</script>
在父组件中获取子组件及其属性,在子组件标签上或者Dom元素上使用ref属性
进行标识
- 获取某个子组件及其属性: 在当前组件中使用
$refs
来获取当前组件的所有子组件, 然后通过组件的标识获取具体的某一个组件及其属性 - 获取子组件时支持嵌套的形式:
this.$refs.组件标识.$refs.name
表示访问子组件的子组件属性
<template><div><!--普通的HTML标签上也可以使用HTML标签进行标识--><h1 ref="hh">{{msg}}</h1><!--price="10"表示传递过去的数据是字符串"10",:price="10"表示传递过去的数据是数字10(属性绑定,""内的10被看作常量)--><Car brand="宝马520" color="黑色" :price="10" ref="car1"></Car><Car brand="比亚迪汉" color="红色" :price="20" ref="car2"></Car><button @click="printCarInfo">打印汽车信息</button></div>
</template><script>import Car from './components/Car.vue'export default {name : 'App',data() {return {msg : '汽车信息'}},methods : {printCarInfo(){// 使用$refs属性获取子组件console.log(this.$refs.car1)console.log(this.$refs.car2)// 访问子组件的属性console.log(this.$refs.car1.brand)console.log(this.$refs.car1.color)console.log(this.$refs.car1.price)// 使用$refs属性也可以获取普通的DOM元素的值,替代原生Dom的操作console.log(this.$refs.hh.innerText)}},components : {Car}}
</script>
mixins配置项(混入)
如果多个xxx.vue
文件的配置项代码相同(配置项没有限制如methods),此时为了复用可以使用mixins
配置项进行混入
- 第一步: 提取相同的配置项并单独定义到一个
mixin.js
文件,一般和main.js在同级目录 - 第二步: 在对应的
xxx.vue
文件中导入mixin.js
文件,然后在mixins配置项
中使用暴露的配置
混入时配置项的冲突问题
对于普通的配置项采取就近原则
: 即只执行组件自身的配置不会执行混入的配置,因为混入的意思就是不破坏对于生命周期周期钩子函数采用叠加方式
: 即先执行混入的再执行自己的
混入的方式分为局部混入
和全局混入
两种
// 分别暴露
export const mix1 = {methods: {printInfo(){console.log(this.name, ',' , this.age) }}
}// 普通的配置
export const mix2 = {methods: {a(){console.log('mixin.js a.....')}},
}
// 生命周期函数
export const mix3 = {mounted() {console.log('mixin.js mounted...')}
}
对于普通的配置项采取就近原则
: 即只执行组件自身的配置不会执行混入的配置,因为混入的意思就是不破坏
<template><div><h1>{{msg}}</h1><h3>姓名:{{name}}</h3><h3>年龄:{{age}}</h3><button @click="printInfo">执行printInfo方法打印用户信息</button><button @click="a">执行a方法</button></div>
</template><script>// 导入mixin配置项import {mix1} from '../mixin.js'import {mix2} from '../mixin.js'export default {name : 'Vip',data() {return {msg : '会员信息',name : '李四2',age : 21}},// 局部混入,数组接收表示可以混入多个mixins : [mix1,mix2],// 这里只调用自身的a方法,不会执行混入的a方法methods: {a(){console.log('vip a....')}},}
</script>
对于生命周期周期钩子函数采用叠加方式
: 即先执行混入的钩子函数再执行自己的钩子函数
<template><div><h1>{{msg}}</h1><h3>姓名:{{name}}</h3><h3>年龄:{{age}}</h3><button @click="printInfo">执行printInfo方法打印用户信息</button><button @click="a">执行a方法</button></div>
</template><script>// 导入mixin.js文件中的mixin配置项import {mix1} from '../mixin.js'import {mix2} from '../mixin.js'import {mix3} from '../mixin.js'export default {name : 'User',// 这里先执行混入的mounted函数,再执行自身的mounted函数mounted() {console.log('User mounted...')},data() {return {msg : '用户信息',name : '张三2',age : 20}},// 局部混入,数组接收表示可以混入多个mixins : [mix1,mix2, mix3],}
</script>
在main.js
文件中全局混入
的配置项会在所有的组件包括根组件root即vm
中自动导入混入的配置项并按照规则执行
// 等同于引入vue.js文件
import Vue from 'vue'
// 导入App组件(根组件)
import App from './App.vue'// 导入mixin配置项
import {mix1} from './mixin.js'
import {mix2} from './mixin.js'
import {mix3} from './mixin.js'// 全局混入
Vue.mixin(mix1)
Vue.mixin(mix2)
Vue.mixin(mix3)// 关闭生产提示信息
Vue.config.productionTip = false// 创建Vue实例
new Vue({el : '#app',render : h => h(App)
})
plugins配置(插件)
插件是一个对象
: 是给用来Vue做功能增强的(可插拔), 一个插件一般对应一个独立的功能,插件一般都放到一个plugins.js
文件中(一般和main.js在同级目录)
插件对象中必须有install方法
,这个方法有两个参数并且会被自动调用
- 第一个参数:Vue构造函数
- 第二个参数:用户在使用这个插件时传过来的数据,参数个数无限制
第一步: 定义插件对象并暴露,如我们这里可以定义一个插件,功能是给Vue的原型对象扩展一个counter属性(通过vm和vc都可以访问)
export const p1 = {install(Vue, a, b, c, d){console.log('这个插件正在显示一个可爱的封面')console.log(Vue)console.log(a,b,c,d)// 获取Vue的原型对象,给Vue的原型对象扩展一个counter属性,通过vm和vc都可以访问Vue.prototype.counter = 1000}
}
第二步: 在main.js
文件中导入创建的插件对象,然后使用导入的插件
// 等同于引入vue.js文件
import Vue from 'vue'
// 导入App组件(根组件)
import App from './App.vue'// 导入插件
import {p1} from './plugins.js'// 插上插件(删除就是拔下插件),通常放在创建Vue实例之前
Vue.use(p1, 1,2,3,4)// 关闭生产提示信息
Vue.config.productionTip = false// 创建Vue实例
new Vue({el : '#app',render : h => h(App)})
第三步: 访问我们定义的插件对象给Vue原型对象扩展的一个counter属性
<template><div><button @click="a">执行a方法</button></div>
</template><script>export default { methods: {a(){console.log('vip a....')// 访问插件对象给Vue原型对象扩展的一个counter属性console.log('通过插件扩展的一个counter:', this.counter)}},}
</script>
局部样式 scoped
默认情况下所有组件中的style标签
中定义的样式最终会汇总到一块,如果样式名一致会导致冲突,此时会采取就近原则即以后来加载的组件样式为准
- 组件中的style样式支持多种样式语言(如css、less、sass), 使用less语法需要执行
npm i less-loader
命令安装less-loader
给组件的style标签加入scoped属性
后,Vue底层会给标签加上属性,这样即使样式汇到一块也不会发生冲突了
<template><div class="s"><h1>{{msg}}</h1></div>
</template>
<script>export default {data() {return {msg : '用户信息',}}}
</script>
<!--给组件的style标签加入scoped属性后,Vue底层会给标签加上属性,这样即使样式汇到一块也不会发生冲突了-->
<style scoped>.s {background-color: aquamarine;}
</style>
<template><div class="s"><h1>{{msg}}</h1></div>
</template>
<script>export default {data() {return {msg : '会员信息',}}}
</script>
<!--给组件的style标签加入scoped属性后,Vue底层会给标签加上属性,这样即使样式汇到一块也不会发生冲突了-->
<style scoped>.s {background-color:bisque}
</style>
App根组件
中的样式style一般采用全局的方式
不建议添加scoped属性,子组件的样式style一般添加scoped属性
<template><div><User></User><Vip></Vip></div>
</template><script>// 先导入Vip,由于样式冲突那么User组件的样式会覆盖掉Vip组件的样式import Vip from './components/Vip.vue'import User from './components/User.vue'export default {name : 'App',components : {User, Vip}}
</script><style>/* 一般在App根组件当中样式不会添加scoped,因为根组件的样式还是希望采用全局的方式处理*/
</style>