目录
第一章:使用Vue脚手架
1.1 初始化脚手架
1.1.1 说明
1. Vue脚手架是Vue官方提供的标准化开发工具(开发平台)
2. 文档:https://cli.vuejs.org/zh/
1.1.2 具体步骤
第一步(仅第一次执行):全局安装@vue/cli
npm install -g @vue/cli
第二步:切换到要创建项目的目录,然后使用命令创建项目
vue create xxxx
第三步:启动项目
npm run serve
备注:
1. 如果出现下载缓慢,就配置 npm 淘宝镜像:
npm config set registryhttps://registry.npm.taobao.org
2. Vue 脚手架隐藏了所有webpack相关的配置,如果想查看具体的 webpack 配置,执行:
vue inspect > output.js
1.1.3 项目模板结构
├── node_modules├── public│ ├── favicon.ico: 页签图标│ └── index.html: 主页面├── src│ ├── assets: 存放静态资源│ │ └── logo.png│ │── component: 存放组件│ │ └── HelloWorld.vue│ │── App.vue: 汇总所有组件│ │── main.js: 入口文件├── .gitignore: git 版本管制忽略的配置├── babel.config.js: babel 的配置文件├── package.json: 应用包配置文件├── README.md: 应用描述文件├── package-lock.json:包版本控制文件
1.1.4 案例
把Vue学习笔记(2)最后面中的案例在脚手架环境执行 ,现在组件的名字不能只用一个单词,所以我把Student改成了MyStudent,把School改成MySchool
1.2 有关render函数
在main.js文件中,使用render函数来使用App组件,是因为引入Vue的时候引入的是残缺版的vue,即'vue.runtime.xxx.js',使用template标签会报错,比如说按照下面的方式来使用App组件,控制台报错
new Vue({
el: '#app',
template: `<App></App>`,
components:{
App
}
})
当创建项目之后,默认的main.js文件就是下面这个样子
import Vue from 'vue'
import App from './App.vue'
Vue.config.productionTip = false
new Vue({
render: h => h(App),
}).$mount('#app')
总结:
1. vue.js 和 vue.runtime.xxx.js 的区别:
1)vue.js 是完整版的Vue,包含:核心功能 + 模板解析器
2)vue.runtime.xxx.js 是运行版的Vue,只包含:核心功能,没有模板解析器
2. 因为 vue.runtime.xxx.js 没有模板解析器,所以不能使用 template 配置项,需要使用render函数接收到的createElement函数去指定具体内容
1.3 修改默认配置
如果想要修改默认的入口文件,或者其它一些webpack设置的默认配置,可以在vue.config.js来修改,下面这个案例设置了修改入口文件,并且将保存就检查错误关闭
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
transpileDependencies: true,
lintOnSave: false,
pages: {
index: {
// 入口
entry: 'src/peiqi.js',
}
},
})
这样子修改main.js名字为peiqi.js也可以执行了,其他的一些配置可以去官网上查看
第二章:refs与props
2.1 refs
当给标签添加了ref属性后,在这个组件的实例对象上就有了refs属性,这里面保存了设置ref属性的DOM元素
如下面这个案例,分别给h1标签、button按钮、school子组件分别设置ref标签,点击按钮后输出
<template>
<div>
<h1 ref="title">{{msg}}</h1>
<button ref="btn" @click="showDOM">点击获取DOM元素</button>
<School ref="school"></School>
</div>
</template>
<script>
import School from './components/School.vue'
export default {
name: 'App',
components: {
School,
},
data(){
return {
msg: '欢迎学习Vue'
}
},
methods:{
showDOM(){
console.log(this.$refs.title);
console.log(this.$refs.btn);
console.log(this.$refs.school);
}
}
}
</script>
总结:
1. ref是被用来给元素或子组件注册引用信息(id的替代者)
2. 应用在html标签上获取真实DOM元素,应用在组件标签上是组件实例对象
3. 使用方式:
打标识:<h1 ref="xxx">......</h1> 或 <School ref="xxx"></School>
获取:this.$refs.xxx
2.2 props
功能:让组件接收外部传过来的数据
1)传递数据:
<Demo name="xxx"/>
2)接收数据:
第一种方式(只接收):
props:[ 'name' ]
第二种方式(限制类型):
props:{
name: Number
}
第三种方式(限制类型、限制必要性、指定默认值):
props:{
name:{
type:String,// 类型
required:true,// 必要性
default:'老王' // 默认值
}
}
备注:props是只读的,Vue底层会监测你对props的修改,如果进行了修改,就会发出警告,若业务需要确实需要修改,那就复制props的内容到data中一份,然后去修改data中的数据
在下面这个案例中,Student组件接收name、sex和age属性,模拟业务需求要修改age,设置一个button按钮,点击让年龄++,在data中用myAge接收传过来的age,在结构中使用myAge属性
<template>
<div>
<h1>{{msg}}</h1>
<h2>学生姓名:{{name}}</h2>
<h2>学生性别:{{sex}}</h2>
<h2>学生年龄:{{myAge + 1}}</h2>
<button @click="myAge++">年龄++</button>
</div>
</template>
<script>
export default {
name: 'MyStudent',
data() {
return {
msg: '我正在学习Vue',
myAge: this.age
}
},
// 简单声明接收
// props:['name', 'sex', 'age']
// 接收的同时对数据进行类型限制
// props:{
// name: String,
// sex: String,
// age: Number
// }
// 接收的同时对数据进行类型限制 + 默认值的指定 + 必要性的限制
props:{
name: {
type: String, // name 的类型是字符串
required: true // name 是必要的
},
age:{
type: Number,
default: 99 // 默认值
},
sex: {
type: String,
required: true
}
}
}
</script>
<template>
<div>
<Student name="zs" sex="男" :age="18"></Student>
</div>
</template>
<script>
import Student from './components/Student.vue'
export default {
name: 'App',
components: {
Student
}
}
</script>
第三章:mixins混入
功能:可以把多个组件公用的配置提取成一个混入对象
使用方式:
第一步定义混合,例如:
{
data(){......},
methods:{......}
......
}
第二部使用混入,例如:
1)全局混入:Vue.mixin(xxx)
2)局部混入:mixins: [ 'xxx' ]
下面分别是Student和School组件
<template>
<div>
<h1>{{msg}}</h1>
<h2 @click="showName">学生姓名:{{name}}</h2>
<h2>学生性别:{{age}}</h2>
</div>
</template>
<script>
import {hunru, hunru2} from '../mixin'
export default {
name: 'MyStudent',
data() {
return {
msg: '我正在学习Vue',
name: '张三',
age: 18
}
},
mounted(){
console.log('组件里面的mounted');
},
mixins:[hunru, hunru2]
}
</script>
<template>
<div>
<h2 @click="showName">学校名称:{{ name }}</h2>
<h2>学校地址:{{ address }}</h2>
</div>
</template>
<script>
import {hunru} from '../mixin'
export default {
name: 'MySchool',
data() {
return {
name: 'Vue',
address: '上海',
}
},
mixins:[hunru]
}
</script>
<style></style>
下面这个是共用的配置mixin.js
export const hunru = {
methods:{
showName(){
alert(this.name)
}
},
mounted() {
console.log('hunru里面的mounted');
},
}
export const hunru2 = {
data(){
return{
age: 20
}
}
}
在这个共用的配置中,定义了showName函数,这样子导入hunru的组件都有了这个方法,定义mounted钩子,如果组件中也有钩子,此时先执行hunru的钩子,再执行组件中定义的钩子,如果定义了age属性,与组件中的age属性发生冲突,此时以组件中的为主,会覆盖掉hunru2里面的属性
如果定义了全局的混入,这样子所有的组件都有了混入里面定义的东西,不管是root还是App都是如此
比如说下面这个案例,定义了全局的混入里面的data有x和y,这样子Root、App、MySchool和MyStudent都有了x和y
Vue.mixin({
data(){
return{
x: 100,
y: 200
}
}
})
第四章:插件
功能:用于增强Vue
本质:包含install方法的一个对象,install的第一个参数是Vue,第二个以后的参数是插件使用者传递的数据
定义插件:
对象.install = function (Vue, options) {
// 1. 添加全局过滤器
Vue.filter(......)
// 2. 添加全局指令
Vue.directive(......)
// 3. 配置全局混入(合)
Vue.mixin(......)
// 4. 添加实例方法
Vue.prototype.$myMethod = function () {......}
Vue.prototype.$myProperty = xxxx
}
使用插件:Vue.use()
下面这个案例,在src文件夹下创建一个plugins.js文件,作为一个插件,里面添加了全局的过滤器、全局指令、全局混入以及实例方法,这样子Vue可以直接使用里面定义的东西,在install函数中,第一个参数是Vue,后面的参数就是在导入了这个插件后可以传入的参数
export default {
install(Vue, x, y, z) {
console.log(Vue, x, y, z)
// 全局过滤器
Vue.filter('mySlice', function (value) {
return value.slice(0, 4)
})
// 定义全局指令
Vue.directive('fbind', {
// 指令与元素成功绑定时
bind(element, binding) {
element.value = binding.value
},
// 指令所在的元素被插入页面时
inserted(element) {
element.focus()
},
// 指令所在的模板被重新解析时
update(element, binding) {
element.value = binding.value
},
})
// 定义混入
Vue.mixin({
data(){
return{
age: 20
}
}
})
// 给原型上添加方法(Vue实例对象和组件实例对象就可以用了)
Vue.prototype.hello = () => {
alert('hello')
}
}
}
import install from './plugins'
Vue.use(install, 1, 2, 3)
在main.js中使用这个插件,并且传入参数,在控制台输出
第五章:scoped样式
作用:让样式在局部生效,防止冲突
写法:<style scoped></style>
因为不同的组件中使用了相同的类名并且定义了样式,此时使用的时候可能会有冲突,加上了scoped之后,会在这个组件的标签里面添加一个属性,然后组件的样式就会添加一个属性选择器,比如说demo这个类,在添加了scoped就变成了 .demo[data-v-22321ebb],例如下面这个案例,不添加scoped,最重添加demo类的背景颜色就会取决于app组件中先导入了哪个组件,后面的样式会覆盖前面的
给两个组件的style都加上scoped,此时就不会有冲突了,并且选择器后面都会添加上一个属性选择器
第六章:自定义事件
6.1 使用
要实现子给父传递数据,有两种方式,一种是通过传递函数类型的prop实现,比如下面的案例,给School传送了getSchoolName的函数,在School组件中,通过点击事件触发传过来的函数,然后把它的name属性传递给父组件,实现了子给父传递数据;另一种是通过自定义事件,先在App组件里面定义一个getStudentName的函数,在Student的标签里面定义自定义事件,模板是 v-on:xxx="xxx" ,第一个xxxx是自定义的,第二个xxx是在自己组件定义的函数,然后在Student组件中,给按钮绑定点击事件,当点击了之后,触发sendStudentName函数,函数体是 “this.$emit('get', this.name)”,这里的get就是自定义的事件,是在App组件中定义的,通过组件实例对象的$emit触发get事件,后面是它的参数
// App.vue
<template>
<div>
<!-- 通过父组件给子组件传递函数类型的prop实现:子给父传递数据 -->
<School :getSchoolName="getSchoolName"></School>
<hr>
<!-- 通过父组件给子组件绑定一个自定义事件实现:子给父传递数据(第一种写法:使用@或v-on) -->
<Student v-on:get="getStudentName"></Student>
<!-- 通过父组件给子组件绑定一个自定义事件实现:子给父传递数据(第二种写法:使用ref) -->
<!-- <Student ref="student"></Student> -->
</div>
</template>
<script>
import Student from './components/Student.vue'
import School from './components/School.vue'
export default {
name: 'App',
components: {
Student,
School
},
methods:{
getSchoolName(name){
console.log('APP接收到了学校名', name);
},
getStudentName(name){
console.log('APP接收到了学生姓名', name);
}
},
mounted(){
// this.$refs.student.$on('get', this.getStudentName)
}
}
</script>
<style></style>
// School.vue
<template>
<div class="demo">
<h2>学校名称:{{ name }}</h2>
<h2>学校地址:{{ address }}</h2>
<button @click="sendSchoolName">点我获取学校名</button>
</div>
</template>
<script>
export default {
name: 'MySchool',
props:['getSchoolName'],
data() {
return {
name: 'Vue',
address: '上海',
}
},
methods:{
sendSchoolName(){
this.getSchoolName(this.name)
}
}
}
</script>
<style scoped>
.demo{
background-color: skyblue;
}
</style>
// Student.vue
<template>
<div class="demo">
<h1>{{msg}}</h1>
<h2>学生姓名:{{name}}</h2>
<h2>学生性别:{{age}}</h2>
<button @click="sendStudentName">点我获取学生名</button>
</div>
</template>
<script>
export default {
name: 'MyStudent',
data() {
return {
msg: '我正在学习Vue',
name: '张三',
age: 18
}
},
methods:{
sendStudentName(){
this.$emit('get', this.name)
}
}
}
</script>
<style scoped>
.demo{
background-color: pink;
}
</style>
自定义事件的v-on可以简写成@,除了这种方式外还有ref实现的,给Student加上ref属性,在mounted钩子中可以通过$refs获取这个对象然后通过$on的方式定义自定义函数‘get’,后面是当组件实例对象触发了get函数之后在App组件中调用的函数,通过这种方式会更加灵活,比如说可以绑定异步事件,定义5秒钟之后再绑定这个自定义事件......
<Student ref="student"></Student>
...
...
...
mounted(){
this.$refs.student.$on('get', this.getStudentName)
}
除此之外,还有一个API可以让自定义事件只触发一次,就是once
// 这是第一种写法
<Student v-on:get.once="getStudentName"></Student>
// 这是第二种写法
this.$refs.student.$once('get', this.getStudentName)
6.2 解绑自定义事件
通过$off()解绑自定义事件,复用上面的Student组件,创建一个button按钮,点击后解绑get自定义事件
<template>
<div class="demo">
<h1>{{msg}}</h1>
<h2>学生姓名:{{name}}</h2>
<h2>学生性别:{{age}}</h2>
<button @click="sendStudentName">点我获取学生名</button>
<button @click="unbind">点我解绑get事件</button>
</div>
</template>
<script>
export default {
name: 'MyStudent',
data() {
return {
msg: '我正在学习Vue',
name: '张三',
age: 18
}
},
methods:{
sendStudentName(){
this.$emit('get', this.name)
},
unbind(){
this.$off('get')
}
}
}
</script>
<style scoped>
.demo{
background-color: pink;
}
</style>
如果要解绑多个自定义事件,传入要解绑的自定义事件的数组,如 this.$off(['xxx', 'yyy', 'zzz'])
如果要解绑所有的自定义事件,此时只要不传入参数即可,即 this.$off()
6.3 一个注意点
如果想要给一个组件绑定点击事件,本来的话应该写成
<Student ref="student" @click='xxx'></Student>
但是这样写的话会认为‘click’是自定义事件的名称,如果想要给Student绑定点击事件,就在点击事件后面加上'.native',即
<Student ref="student" @click.native='show'></Student>
6.4 总结
组件的自定义事件
1. 一种组件间通信的方式,适用于:子组件 ==> 父组件
2. 使用场景:A是父组件,B是子组件,那么B想给A传数据,那么就要在A中给B绑定自定义事件(事件的回调在A中)
3. 绑定自定义事件
1)第一种方式,在父组件中:<Demo @xxx="test"/> 或 <Demo v-on:xxx="test"/>
2)第二种方式,在父组件中:
<Demo ref="demo" />
......
mounted(){
this.$refs.demo.$on('xxx', this.test)
}
3)如果想让自定义事件只能触发一次,可以使用once修饰符,或$once方法
4. 触发自定义事件:this.$emit('xxx',数据)
5. 解绑自定义事件:this.$off('xxx')
6. 组件上也可以绑定元素DOM事件,需要使用 native 修饰符
7. 注意:通过 this.$refs.xxx.$on('xxx', 回调) 绑定自定义事件时,回调要么配置在methods中,要么用箭头函数,否则this的指向会出问题!
第七章:全局事件总线(GlobalEventBus)
1. 一种组件间通信的方式,适用于任意组件间的通信
2. 安装全局事件总线:
new Vue({
......
beforeCreate(){
Vue.prototype.$bus = this // 安装全局事件总线,$bus就是当前应用的vm
}
......
})
3. 使用事件总线:
1)接收数据:A组件想接收数据,则在A组件中给$bus绑定自定义事件,事件的回调留在A组件自身
methods:{
demo(data) { ...... }
}
......
mounted() {
this.$bus.$on('xxxx', this.demo)
}
2)提供数据: this.$bus.$emit('xxxx', 数据)
4. 最好在beforeDestroy钩子中,用$off去解绑当前组件所用到的事件
在下面这个案例中,main.js中安装了全局事件总线,在School组件中,创建了自定义事件“hello”,接收传过来的数据,后面可以用methods中的函数或者使用箭头函数,在Student组件中,点击按钮后调用了$emit事件,传入自定义事件“hello”并且传过去参数,在School可以接受到;在School组件被销毁前,在beforeDestroy钩子中用$off解绑自定义事件
// main.js
import Vue from 'vue'
import App from './App.vue'
Vue.config.productionTip = false
new Vue({
render: h => h(App),
beforeCreate(){
Vue.prototype.$bus = this
}
}).$mount('#app')
// School.vue
<template>
<div class="demo">
<h2>学校名称:{{ name }}</h2>
<h2>学校地址:{{ address }}</h2>
</div>
</template>
<script>
export default {
name: 'MySchool',
data() {
return {
name: 'Vue',
address: '上海',
}
},
mounted(){
this.$bus.$on('hello', data => {
console.log('我是School组件,收到了数据:' + data);
})
},
beforeDestroy(){
this.$bus.$off('hello')
}
}
</script>
<style scoped>
.demo{
background-color: skyblue;
}
</style>
// Student.vue
<template>
<div class="demo">
<h1>{{msg}}</h1>
<h2>学生姓名:{{name}}</h2>
<h2>学生性别:{{age}}</h2>
<button @click="sendStudentName">把学生名传给School组件</button>
</div>
</template>
<script>
export default {
name: 'MyStudent',
data() {
return {
msg: '我正在学习Vue',
name: '张三',
age: 18
}
}
methods:{
sendStudentName(){
this.$bus.$emit('hello', this.name)
}
}
}
</script>
<style scoped>
.demo{
background-color: pink;
}
</style>
第七章:消息订阅与发布(pubsub)
1. 一种组件间通信的方式,适用于任意组件间的通信
2. 使用步骤:
1)安装pubsub:npm i pubsub-js
2)引入:import pubsub from 'pubsub-js'
3)接收数据:A组件想接收数据,则在A组件中订阅消息,订阅的回调留在A组件自身
methods:{
demo(data) { ...... }
}
......
mounted() {
this.pid = pubsub.subscribe('xxx', this.demo) // 订阅消息
}
4)提供数据:pubsub.publish('xxx', 数据)
5)最好在beforeDestroy钩子中,用 pubsub.unsubscribe(pid)去取消订阅
在下面这个案例中,从Student组件向School传递数据, School组件通过pubsub.subscribe()的方式订阅消息‘hello’,后面的回调函数要么定义在methods中,要么使用箭头函数,如果使用function的话,函数内部的this是unefined;Student组件传递数据,通过pubsub.publish()的方式发送消息‘hello’,数据跟在后面
// School.js
<template>
<div class="demo">
<h2>学校名称:{{ name }}</h2>
<h2>学校地址:{{ address }}</h2>
</div>
</template>
<script>
import pubsub from 'pubsub-js'
export default {
name: 'MySchool',
data() {
return {
name: 'Vue',
address: '上海',
}
},
mounted(){
// console.log('school', this.x);
// this.$bus.$on('hello', data => {
// console.log('我是School组件,收到了数据:' + data);
// })
this.pubId = pubsub.subscribe('hello', (msgName, data) => {
console.log('接收到了消息', msgName, data);
})
},
beforeDestroy(){
// this.$bus.$off('hello')
pubsub.unsubscribe(this.pubId)
}
}
</script>
<style scoped>
.demo{
background-color: skyblue;
}
</style>
<template>
<div class="demo">
<h1>{{msg}}</h1>
<h2>学生姓名:{{name}}</h2>
<h2>学生性别:{{age}}</h2>
<button @click="sendStudentName">把学生名传给School组件</button>
</div>
</template>
<script>
import pubsub from 'pubsub-js'
export default {
name: 'MyStudent',
data() {
return {
msg: '我正在学习Vue',
name: '张三',
age: 18
}
},
mounted(){
// console.log('student', this.x);
},
methods:{
sendStudentName(){
// this.$bus.$emit('hello', this.name)
pubsub.publish('hello', this.name)
}
}
}
</script>
<style scoped>
.demo{
background-color: pink;
}
</style>
第八章:过渡与动画
1. 作用:在插入、更新或移除 DOM元素时,在合适的时候给元素添加样式类名。
2. 图示:
3. 写法:
准备好样式:
元素进入的样式:
v-enter:进入的起点
v-enter-active:进入过程中
v-enter-to:进入的终点
元素离开的样式:
v-leave:离开的起点
v-leave-active:离开过程中
v-leave-to:离开的终点
使用
<transition>
包裹要过度的元素,并配置name属性:<transition name="hello"> <h1 v-show="isShow">你好啊!</h1> </transition>备注:若有多个元素需要过度,则需要使用:
<transition-group>
,且每个元素都要指定key
值。
下面三段代码分别是Test、Test2、Test3,在Test1中定义了一个动画,用transition包裹一个h1标签,当它进入的时候从左往右
Test2组件中,使用css样式来定义动画,使用tranrition-group,因为内部有两个标签,它们两个是交错显示的
Test使用了第三方组件库 animation.css ,最终的动画效果如下
<template>
<div>
<button @click="isShow = !isShow">显示/隐藏</button>
<transition appear>
<h1 v-show="isShow" class="go">你好啊</h1>
</transition>
</div>
</template>
<script>
export default {
name: 'MyTest',
data(){
return{
isShow: true
}
}
}
</script>
<style>
h1{
background-color: pink;
}
.v-enter-active{
animation: move 1s ;
}
.v-leave-active{
animation: move 1s reverse;
}
@keyframes move {
from{
transform: translateX(-100%);
}
to{
transform: translate(0);
}
}
</style>
<template>
<div>
<button @click="isShow = !isShow">显示/隐藏</button>
<transition-group name="hello" appear>
<h1 v-show="!isShow" class="go" key="1">你好啊</h1>
<h1 v-show="isShow" class="go" key="2">你好啊</h1>
</transition-group>
</div>
</template>
<script>
export default {
name: 'MyTest2',
data() {
return {
isShow: true,
}
},
}
</script>
<style>
h1 {
background-color: pink;
}
/* 进入的起点、离开的终点 */
.hello-enter,
.hello-leave-to {
transform: translateX(-100%);
}
.hello-enter-active,.hello-leave-active {
transition: 0.5s linear;
}
/* 进入的终点、离开的起点 */
.hello-enter-to,
hello-leave {
transform: translateX(0);
}
</style>
<template>
<div>
<button @click="isShow = !isShow">显示/隐藏</button>
<transition-group
appear
name="animate__animated animate__bounce"
enter-active-class="animate__bounceIn"
leave-active-class="animate__bounceOutDown"
>
<h1 v-show="!isShow" class="go" key="1">你好啊</h1>
<h1 v-show="isShow" class="go" key="2">你好啊</h1>
</transition-group>
</div>
</template>
<script>
import 'animate.css'
export default {
name: 'MyTest2',
data() {
return {
isShow: true,
}
},
}
</script>
<style>
h1 {
background-color: pink;
}
</style>