入门
介绍
渐进式JavaScript框架
作用:动态构建(显示)用户界面
特点
遵循MVVM模式
编码简洁,体积小,运行效率高,适合移动/pc端开发
它本身只关注UI,可以轻松引入VUE插件或其他第三方库开发项目
借鉴angular的模板和数据绑定技术
借鉴react的组件化和虚拟DOM技术
MVC/MVVM
- MVC
- Model 数据层
- View 视图层
- Controller 操作层
数据由控制层去操作,读取数据层的数据,渲染到视图层的页面上,返回
-
MVVM
- Model 数据层(data)
- View 视图层
- ViewModel 视图模型层(vm)
数据由ViewModel控制,加载到视图层上,返回.
也可以从视图层上读取修改的数据,修改数据层的数据,从而达到数据双向绑定(响应式)
引入问题
- vue.min.js生产环境/压缩版本,不能使用vue开发调试工具
- 测试工具Vue Devtools
https://cdn.bootcdn.net/ajax/libs/vue/2.6.12/vue.min.js
https://cdn.bootcdn.net/ajax/libs/vue/2.6.12/vue.js
表达式与语句
if里面可以放的就是表达式
-
表达式
有返回值(返回值可以是undefined)
没有分号
-
语句
没有返回值
末尾有分号(编译会自动加上)
function fn() {} // 语句
fn // 表达式
fn() // 表达式
var fn = function () {} // 函数表达式语句
if (a) {} // 语句
for () {} // 语句
数据代理/响应式数据
- 在数据代理(将data中的数据代理到this上)
- 实例对象上方法一般都有$(数据代理除外)
- data上:响应式数据,响应式数据就是更新值用户界面会发生变化
- this:普通数据,更新数据,用户页面不会发生变化
计算属性会监视内部使用过的属性的变化, 一旦发生变化, 计算属性就要重新计算
vue内部会对data/methods/computed等属性进行数据代理(将对象中的属性挂载到vm上),就能直接通过vm/this直接访问属性
==vue可以直接更新data数据, 同时用户界面也会发生变化(响应式) ==
内存泄漏/溢出
- 没有清除定时器叫泄露(占用空间,但实际没有用,也就是没有释放)
- 泄露多了容易内存溢出(实际使用超过了额定内存)
Vue实例
每个 Vue 应用都是通过用 Vue
函数创建一个新的 Vue 实例开始的:
当创建一个 Vue 实例时,你可以传入一个选项对象。
方式一
<div id="root">
<h1>{{msg}}</h1>
</div>
<script src="../js/vue.js"></script>
<script>
// vm就是Vue实例对象
const vm = new Vue({
//element元素,获取DOM元素
el: "#root",
// 数据:用来渲染到页面上
data: {
msg: "hello world"
}
})
</script>
方式二
<div id="root">
<h1>{{msg}}</h1>
</div>
<script src="../js/vue.js"></script>
<script>
const vm = new Vue({
data: {
msg: "hello world"
}
})
//不通过el指定DOM,也可以通过$mount挂载DOM元素方式来操作DOM元素
vm.$mount("#root")
console.log(vm)
</script>
配置对象
配置对象:属性名固定的对象
DOM
el
只在用
new
创建实例时生效,提供一个在页面上已存在的 DOM 元素作为 Vue 实例的挂载目标。可以是 CSS 选择器,也可以是一个 HTMLElement 实例.在实例挂载之后,元素可以用vm.$el
访问。如果在实例化时存在这个选项,实例将立即进入编译过程,否则,需要显式调用vm.$mount()
手动开启编译。
语法:el:“css选择器”
数据
data
Vue实例的数据对象,Vue的实例会代理data中的所有属性,所以可以通过Vue实例对象直接获取属性
语法:data:{零个或多个键值对}
methods
放置绑定事件的回调函数,注意不能使用箭头函数,this指向会有问题
语法:methods:{零个或多个回调函数}
computed
计算属性,不仅可以计算值,也可以通过函数来过滤,类似fillter等。计算的结果会被缓存,直到依赖项变化
- 第一种
computed: {
// 可读可写
fullName1: {
get() {
return this.firstName + ' ' + this.lastName
},
set(newval) {
const [firstName, lastName] = newval.split(' ')
this.firstName = firstName
this.lastName = lastName
}
}
},
- 第二种
computed: {
// 只读
fullName2() {
return this.firstName + ' ' + this.lastName
}
}
watch
监听某个数据的变化,从而触发对应的回调函数,只有当监听的值发生改变,才会触发回调函数
语法:监听的数据值:function(){}
模板语法
也叫模板页面:HTML+CSS+JS
插值
-
文本
数据绑定最常见的形式就是使用“Mustache”语法 (双大括号) 的文本插值:
<span>Message: {{ msg }}</span>
Mustache 标签将会被替代为对应数据对象上
msg
property 的值。无论何时,绑定的数据对象上msg
property 发生了改变,插值处的内容都会更新。 -
Attribute
Mustache 语法不能作用在 HTML attribute 上,遇到这种情况应该使用 v-bind指令
<div v-bind:id="dynamicId"></div>
-
使用javascript表达式
{{ number + 1 }} {{ ok ? 'YES' : 'NO' }} {{ message.split('').reverse().join('') }} <div v-bind:id="'list-' + id"></div>
指令
指令 (Directives) 是带有
v-
前缀的特殊 attribute。指令 attribute 的值预期是单个 JavaScript 表达式v-for
是例外情况
v-model 用作双向数据绑定(数据既能从JS流向页面,也能从页面流向JS)
v-bind 用来强制绑定数据,单向数据绑定
v-on 用来绑定DOM事件
v-model
- 双向数据绑定
v-bind
- 强制绑定解析表达式,可以省略v-bind
v-on
- 绑定事件监听,一般简写为@
v-for
- 遍历数组/对象
v-if
- 如果为true, 当前标签才会输出到页面
v-else/v-else-if
- 如果为false, 当前标签才会输出到页面
v-show
- 通过控制display样式来控制显示/隐藏
v-html
-
更新元素的 类似于innerHTML
<!-- 会解析 --> <p v-html="msg"></p> data: { msg: "<h1>北京上海广州深圳<h1>", num:0 },
v-text
-
更新元素的 相当于textContent
<!-- 不会解析 --> <p v-text="msg"></p> data: { msg: "<h1>北京上海广州深圳<h1>", num:0 },
v-cloak
-
使用它防止闪现表达式, 与css配合: [v-cloak] { display: none },防止闪现,刷新页面的时候会出现{{msg}}这样的结构,网速慢的时候会出现
<style> [v-cloak] { display: none; } </style> <p v-cloak>{{msg}}</p> <p v-once v-cloak>{{num}}</p> <p v-pre v-cloak>{{num}}</p>
v-once
-
只渲染一次,只解析一次(后面值更新不会重新解析、渲染)
<p v-once v-cloak>{{num}}</p>
v-pre
-
会渲染最原始的内容(里面内容不会被vue解析)
<p v-pre v-cloak>{{num}}</p>
ref
-
为某个元素注册一个唯一标识, vue对象通过$els属性访问这个元素对象
-
设置给DOM元素,获取到的就是DOM元素
-
设置给组件,获取到的就是组件实例对象
<button ref="btna" @click="num++">加数字</button>
beforeMount() { console.log("beforeMount", this.$refs.btna); }, mounted() { console.log("mounted", this.$refs.btna); },
-
参数
一些指令能够接收一个“参数”,在指令名称之后以冒号表示
<a v-bind:href="url">...</a> <a v-on:click="doSomething">...</a>
-
动态参数
从 2.6.0 开始,可以用方括号括起来的 JavaScript 表达式作为一个指令的参数:
<!-- 注意,参数表达式的写法存在一些约束,如之后的“对动态参数表达式的约束”章节所述。 --> <a v-bind:[attributeName]="url"> ... </a>
自定义指令
注册全局指令
Vue.directive('my-directive', function(el, binding){
el.innerHTML = binding.value.toupperCase()
})
注册局部指令
- 只对当前实例生效
directives : {
'my-directive' : {
bind (el, binding) {
el.innerHTML = binding.value.toupperCase()
}
}
}
el 绑定指令的DOM元素
binding 是一个对象,包含指令和表达式等数据
{
expression: "msg" //表达式
name: "upper-text" //指令名称
rawName: "v-upper-text" //指令全称
value: "Atguigu" //表达式的值
}
使用指令
v-my-directive='xxx'
缩写
Vue 为
v-bind
和v-on
这两个最常用的指令,提供了特定简写:
-
v-bind 缩写
强制数据绑定,功能:指定变化的属性值
<!-- 完整语法 --> <a v-bind:href="url">...</a> <!-- 缩写 --> <a :href="url">...</a> <!-- 动态参数的缩写 (2.6.0+) --> <a :[key]="url"> ... </a>
-
v-on缩写
绑定事件监听,功能:绑定指定事件名的回调函数
<!-- 完整语法 --> <a v-on:click="doSomething">...</a> <!-- 缩写 --> <a @click="doSomething">...</a> <!-- 动态参数的缩写 (2.6.0+) --> <a @[event]="doSomething"> ... </a>
计算属性和侦听器
计算属性
对于任何复杂逻辑,你都应当使用计算属性
计算属性,在computed属性对象中定义计算属性的方法,在页面中使用{{方法名}}来显示计算的结果
- 之前的计算方式,也是这个的原理
- 根据已存在属性,来计算生成一个新的值
const person = {
firstName: "chen",
lastName: "guanxi",
};
// 定义对象的属性的属性(原属性)
Object.defineProperty(person, "fullname", {
// value:'chen guanxi', 属性的值
configurable: false, // 是否可以重新配置或者删除
// writable:true, // 是否可以枚举
enumerable: true, // 是否可以枚举,遍历, for in
get() {
// 属性读取时会调用的方法(返回值就是属性的值)
return this.firstName + " " + this.lastName;
},
set(newVal) {
// console.log(newVal); luo yonghao
// 利用数组一一对应关系来解构
const [firstName, lastName] = newVal.split(" ");
console.log(firstName); // luo
console.log(lastName); // yonghao
// console.log(this); { firstName: 'chen', lastName: 'guanxi', fullname: [Getter/Setter] }
this.firstName = firstName;
this.lastName = lastName;
},
});
// 此时会调用get方法
console.log(person.fullname); //chen guanxi
// 此时会调用set方法
person.fullname = "luo yonghao";
console.log(person);
// { firstName: 'chen', lastName: 'guanxi', fullname: [Getter/Setter] } 修改前
// { firstName: 'luo', lastName: 'yonghao', fullname: [Getter/Setter] } 修改后
- 现在
<div id="app">
姓:
<input type="text" placeholder="First Name" v-model="firstname" /><br />
名: <input type="text" placeholder="Last Name" v-model="lastname" /><br />
姓名1:
<input type="text" placeholder="Full Name1" v-model="fullname" /><br />
单向:
<input type="text" placeholder="Full Name2" :value="fullname" /><br />
只读:
<input type="text" placeholder="Full Name2" :value="fullname" /><br />
监视
<input type="text" placeholder="Full Name2" :value="watchdata" /><br />
</div>
<script type="text/javascript" src="../js/vue.js"></script>
<script type="text/javascript">
new Vue({
el: "#app",
data: {
firstname: "chen",
lastname: "guanxi",
watchdata: "luo yonghao",
},
computed: {
fullname: {
get() {
// this指向的是vm实例
return this.firstname + " " + this.lastname;
},
set(newVal) {
const [firstname, lastname] = newVal.split(" ");
this.firstname = firstname;
this.lastname = lastname;
},
},
fullname1() {
return this.firstname + " " + this.lastname;
},
},
watch: {
firstname(newVal) {
const res = this.watchdata.split(" ");
res[0] = newVal;
this.watchdata = res.join(" ");
},
lastname(newVal) {
const res = this.watchdata.split(" ");
res[1] = newVal;
this.watchdata = res.join(" ");
},
},
});
</script>
计算属性高级
通过getter/setter实现对属性数据的显示和监视
计算属性存在缓存, 多次读取只执行一次getter计算
// 计算属性
// 计算属性会监视内部使用过的属性的变化,一旦发生变化,计算属性就要重新计算
computed: {
// 给vm定义一个属性,叫fullName
fullName: {
// fullName属性的读取的方法
get() {
// vue内部会对data、methods、computed等属性进行数据代理(将对象中的属性挂在到vm上)
// 就能直接通过 vm/this 直接访问属性
return this.firstName + " " + this.lastName;
},
// fullName属性的设置的方法
set(newVal) {
const [firstName, lastName] = newVal.split(" ");
// vue可以直接更新data数据,同时用户界面也会发生变化(响应式)
this.firstName = firstName;
this.lastName = lastName;
},
},
// 只读的计算属性(不能修改)
// 这个方法相对于fullName1的get方法,没有set
fullName1() {
return this.firstName + " " + this.lastName;
},
},
watch侦听
通过通过vm对象的$watch()或watch配置来监视指定的属性,当属性变化时, 回调函数自动调用, 在函数内部进行计算
虽然计算属性在大多数情况下更合适,但有时也需要一个自定义的侦听器。这就是为什么 Vue 通过 watch
选项提供了一个更通用的方法,来响应数据的变化。当需要在数据变化时执行异步或开销较大的操作时,这个方式是最有用的
- 监视已存在属性的变化,一旦变化,会保存数据,发送请求等操作
watch: {
// 监视firstName属性的变化,一旦发生变化就会调用函数
firstName(newVal) {
// ['li', 'peihua']
const names = this.fullName3.split(" ");
// ['wang', 'peihua']
names[0] = newVal;
this.fullName3 = names.join(" ");
},
lastName(newVal) {
const names = this.fullName3.split(" ");
names[1] = newVal;
this.fullName3 = names.join(" ");
},
},
Class与Style绑定
class和style到底用哪种?
- 如果样式将来是可变的用style,静态写死就用class
- 如果样式将来是可变
但是只在某个范围变化(red --> green --> red),用class
但是如果变化时无穷,用st yle
静态写死就用class
绑定HTML Class
-
字符串语法
<p :class="isRed ? 'red':'green'">
-
对象语法
v-bind:class指令可以与普通的class attribute共存
<p :class="{red:isRed,green:!isRed}">这是一段文字~</p>
-
数组语法
数组中带引号的会直接使用,不带引号的属性回去data数据中寻找
这是一段文字~
这是一段文字~
```绑定内联样式
-
对象语法
<p :style="{color:'blue',fontSize:fontSize+'px'}">这是一段文字~</p>
对象语法常常结合返回对象的计算属性使用
-
数组语法
数组语法可以将多个样式对象应用到同一个元素上:
<div v-bind:style="[baseStyles, overridingStyles]"></div>
条件渲染
v-if/v-else
用于条件性地渲染一块内容。这块内容只会在指令的表达式返回 truthy 值的时候被渲染。
v-else元素必须紧跟在带v-if或者v-else-if的元素的后面,否侧它将不会被识别
不推荐同时使用v-if和v-for
- v-if 隐藏的时候会删除整个元素(元素会移出DOM树)
<div id="app">
<p v-if="isShow">北京上海广州深圳</p>
<p v-else-if="isElse">内蒙新疆哈尔滨</p>
<p v-else>阿里灵芝</p>
<button @click="handleShow">点击改变状态</button>
</div>
<script type="text/javascript" src="../js/vue.js"></script>
<script type="text/javascript">
new Vue({
el: "#app",
data: {
isShow: true,
isElse:false
},
methods: {
handleShow() {
this.isShow = !this.isShow;
},
},
});
</script>
v-show
注意,
v-show
不支持<template>
元素,也不支持v-else
。
- 通过display样式来控制隐藏(元素还在DOM树上)
<p v-if="isShow">北京上海广州深圳</p>
<p v-show="isShow">北京上海广州深圳</p>
v-if和v-else的区别
v-if 隐藏的时候会删除整个元素(元素会移出DOM树)
v-show 通过display样式来控制隐藏(元素还在DOM树上)
v-if因为要插入和移除DOM,所以性能差
如果需要频繁切换,v-show比较好
如果不频繁,只用一次,v-if
列表渲染
我们可以用
v-for
指令基于一个数组来渲染一个列表。v-for
指令需要使用item in items
形式的特殊语法,其中items
是源数据数组,而item
则是被迭代的数组元素的别名。
- 建议尽可能在使用
v-for
时提供key
attribute,除非遍历输出的 DOM 内容非常简单,或者是刻意依赖默认行为以获取性能上的提升。
遍历数组
<ul id="example-1">
<li v-for="item in items" :key="item.message">
{{ item.message }}
</li>
</ul>
var example1 = new Vue({
el: '#example-1',
data: {
items: [
{ message: 'Foo' },
{ message: 'Bar' }
]
}
})
- 也可以用
of
替代in
作为分隔符,因为它更接近 JavaScript 迭代器的语法
遍历对象
在遍历对象时,会按
Object.keys()
的结果遍历,但是不能保证它的结果在不同的 JavaScript 引擎下都一致。
<div v-for="(value, name, index) in object">
{{ index }}. {{ name }}: {{ value }}
</div>
数组方法
- map 返回新数组特点:长度不变,值变
- filter 返回新数组特点:长度变,值不变
- reduce 返回特点:长度变,值也变
事件处理
监听事件
可以用
v-on
指令监听 DOM 事件,并在触发时运行一些 JavaScript 代码。
事件处理方法
许多事件处理逻辑会更为复杂,所以直接把 JavaScript 代码写在 v-on
指令中是不可行的。因此 v-on
还可以接收一个需要调用的方法名称
- 解决:在配置对象中定义一个methods属性,是一个对象,在它里面保存各种方法
内联处理器中的方法
除了直接绑定到一个方法,也可以在内联 JavaScript 语句中调用方法:
<div id="example-3">
<button v-on:click="say('hi')">Say hi</button>
<button v-on:click="say('what')">Say what</button>
</div><button v-on:click="say('what')">Say what</button>
new Vue({
el: '#example-3',
methods: {
say: function (message) {
alert(message)
}
}
})
$event
有时也需要在内联语句处理器中访问原始的 DOM 事件。可以用特殊变量 $event
把它传入方法:
事件修饰符
使用修饰符时,顺序很重要;相应的代码会以同样的顺序产生。因此,用
v-on:click.prevent.self
会阻止所有的点击,而
v-on:click.self.prevent
只会阻止对元素自身的点击。
修饰符是由点开头的指令后缀来表示的。
<!-- 阻止单击事件继续传播,冒泡 -->
<a v-on:click.stop="doThis"></a>
<!-- 提交事件不再重载页面 -->
<form v-on:submit.prevent="onSubmit"></form>
<!-- 修饰符可以串联 -->
<a v-on:click.stop.prevent="doThat"></a>
<!-- 只有修饰符 -->
<form v-on:submit.prevent></form>
<!-- 添加事件监听器时使用事件捕获模式 -->
<!-- 即内部元素触发的事件先在此处理,然后才交由内部元素进行处理 -->
<div v-on:click.capture="doThis">...</div>
<!-- 只当在 event.target 是当前元素自身时触发处理函数 -->
<!-- 即事件不是从内部元素触发的 -->
<div v-on:click.self="doThat">...</div>
<!-- 点击事件将只会触发一次 -->
<a v-on:click.once="doThis"></a>
按键修饰符
在监听键盘事件时,我们经常需要检查详细的按键。Vue 允许为
v-on
在监听键盘事件时添加按键修饰符:
.enter
.tab
.delete (捕获“删除”和“退格”键)
.esc
.space
.up
.down
.left
.right
// 使用 keyCode attribute 也是允许的:
<input v-on:keyup.13="submit">
表单输入绑定
基础用法
可以用
v-model
指令在表单<input>
、<textarea>
及<select>
元素上创建双向数据绑定。它会根据控件类型自动选取正确的方法来更新元素。尽管有些神奇,但v-model
本质上不过是语法糖。它负责监听用户的输入事件以更新数据,并对一些极端场景进行一些特殊处理。
v-model
会忽略所有表单元素的 value
、checked
、selected
attribute 的初始值而总是将 Vue 实例的数据作为数据来源。你应该通过 JavaScript 在组件的 data
选项中声明初始值。
- text 和 textarea 元素使用
value
property 和input
事件; - checkbox 和 radio 使用
checked
property 和change
事件; - select 字段将
value
作为 prop 并将change
作为事件。 - 使用v-model(双向数据绑定)自动收集数据
修饰符
.lazy
在默认情况下,v-model
在每次 input
事件触发后将输入框的值与数据进行同步 (除了上述输入法组合文字时)。你可以添加 lazy
修饰符,从而转为在 change
事件_之后_进行同步:
<!-- 在“change”时而非“input”时更新 -->
<input v-model.lazy="msg">
.number
如果想自动将用户的输入值转为数值类型,可以给 v-model
添加 number
修饰符:
<input v-model.number="age" type="number">
这通常很有用,因为即使在 type="number"
时,HTML 输入元素的值也总会返回字符串。如果这个值无法被 parseFloat()
解析,则会返回原始的值。
.trim
如果要自动过滤用户输入的首尾空白字符,可以给 v-model
添加 trim
修饰符:
<input v-model.trim="msg">
生命周期
初始化显示
beforeCreate()
-
在数据代理(将data中的数据代理到this上)之前触发
-
此时不能访问data、methods、computed…数据
created()
- 在数据代理之后触发
beforeMount()
- 在挂载之前(页面渲染之前),不能可以操作DOM
mounted()
- 在挂载之后(页面渲染之后),可以直接操作DOM
更新状态
- beforeUpdate()
- updated()
销毁
vm.$destory()
- beforeDestory()
- destoryed()
- 常用的生命周期方法
created()/mounted(): 发送ajax请求, 启动定时器等异步任务
beforeDestory(): 做收尾工作, 如: 清除定时器
Vue生命周期函数:
1. 初始化
beforeCreate 在数据代理之前触发,所以此时不能操作data等数据
created
beforeMount 在挂载之前(页面渲染之前),所以此时不能操作DOM
**mounted**
重要!
发送请求、设置定时器、绑定事件等一次性的任务
2. 更新
beforeUpdate
updated
3. 卸载
beforeDestroy
做收尾工作:清除定时器、解绑事件等任务
destroyed
过渡/动画
单元素/组件的过渡
Vue 提供了 transition
的封装组件,在下列情形中,可以给任何元素和组件添加进入/离开过渡
- 条件渲染 (使用
v-if
) - 条件展示 (使用
v-show
) - 动态组件
- 组件根节点
<div id="demo">
<button v-on:click="show = !show">
Toggle
</button>
// 如果使用了name 那么将用xxx-enter类名
<transition name="fade">
<p v-if="show">hello</p>
</transition>
</div>
当插入或删除包含在 transition
组件中的元素时,Vue 将会做以下处理:
- 自动嗅探目标元素是否应用了 CSS 过渡或动画,如果是,在恰当的时机添加/删除 CSS 类名。
- 如果过渡组件提供了 JavaScript 钩子函数,这些钩子函数将在恰当的时机被调用。
- 如果没有找到 JavaScript 钩子并且也没有检测到 CSS 过渡/动画,DOM 操作 (插入/删除) 在下一帧中立即执行。(注意:此指浏览器逐帧动画机制,和 Vue 的
nextTick
概念不同)
过渡的类名
在进入/离开的过渡中,会有 6 个 class 切换。
v-enter
:定义进入过渡的开始状态。在元素被插入之前生效,在元素被插入之后的下一帧移除。v-enter-active
:定义进入过渡生效时的状态。在整个进入过渡的阶段中应用,在元素被插入之前生效,在过渡/动画完成之后移除。这个类可以被用来定义进入过渡的过程时间,延迟和曲线函数。v-enter-to
:2.1.8 版及以上定义进入过渡的结束状态。在元素被插入之后下一帧生效 (与此同时v-enter
被移除),在过渡/动画完成之后移除。v-leave
:定义离开过渡的开始状态。在离开过渡被触发时立刻生效,下一帧被移除。v-leave-active
:定义离开过渡生效时的状态。在整个离开过渡的阶段中应用,在离开过渡被触发时立刻生效,在过渡/动画完成之后移除。这个类可以被用来定义离开过渡的过程时间,延迟和曲线函数。v-leave-to
:2.1.8 版及以上定义离开过渡的结束状态。在离开过渡被触发之后下一帧生效 (与此同时v-leave
被删除),在过渡/动画完成之后移除。
对于这些在过渡中切换的类名来说,如果你使用一个没有名字的 <transition>
,则 v-
是这些类名的默认前缀。如果你使用了 <transition name="my-transition">
,那么 v-enter
会替换为 my-transition-enter
。
动画
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>10_过渡&动画2</title>
<style>
#box {
width: 100px;
height: 100px;
background-color: red;
position: relative;
left: 0;
/* transition: all 2s; */
animation: move 5s linear 1s infinite alternate-reverse;
}
@keyframes move {
0% {
left: 0;
}
50% {
left: 500px;
}
100% {
left: 100px;
}
}
</style>
</head>
<body>
<div id="app">
<button @click="left+=100">点击我向右偏移100</button>
<div id="box" :style='{left:left+"px"}'></div>
</div>
<script type="text/javascript" src="../js/vue.js"></script>
<script>
new Vue({
el: "#app",
data: {
left: 0,
},
});
</script>
</body>
</html>
过滤器
- 专门用来处理
理解
功能:对要显示的数据进行特定格式化后再显示
注意:并没有改变原本的数据,而是会产生新的对应的数据
编码
定义过滤器
Vue.filter("filterName",(value,form,[three...])=>{
... 进行一定的数据处理
return newValue
})
使用过滤器
<div>{{myData | filterName}}</div>
<div>{{myData | filterName(arg)}}</div>
全局过滤 filter
所有的实例都可以使用
Vue.filter("alldate", (value, form, three) => {
console.log(value, form, three);
return dayjs(value).format(form);
});
局部过滤器filters
只有当前实例可以使用
filters: {
newDate(value, form, three) {
return dayjs(value).format(form);
},
},
插件
-
库
jQuery(操作DOM) dayjs(格式化时间) lodash(节流、防抖…) – 提供特定功能
-
框架
功能更加强大的库 React Vue
-
插件
扩展库、框架的功能
jQuery插件 扩展jQuery功能
React插件 React-Router
Vue插件 Vue-Router Vuex…
自定义插件-方式一
function Myplugin(Vue) {
// 当你使用插件时,会调用插件函数,传入Vue作为参数
// 扩展全局功能
Vue.globalplugin = function () {
console.log("我是globalplugin插件");
};
// 扩展实例对象功能
Vue.prototype.$instanceMethods = function () {
console.log("我是instanceMethods");
};
// 扩展全局过滤器
Vue.filter("nowDate", (val, form) => {
return dayjs(val).format(form);
});
// 扩展全局指令
Vue.directive("lower-text", (el, binding) => {
el.innerText = binding.value.toLowerCase();
});
}
// export default MyPlugin
window.Myplugin = Myplugin;
自定义插件-方式二
const Myplugin = {}
Myplugin.install = function (Vue) {
// 当你使用插件时,会调用插件函数,传入Vue作为参数
// 扩展全局功能
Vue.globalplugin = function () {
console.log("我是globalplugin插件");
};
// 扩展实例对象功能
Vue.prototype.$instanceMethods = function () {
console.log("我是instanceMethods");
};
// 扩展全局过滤器
Vue.filter("nowDate", (val, form) => {
return dayjs(val).format(form);
});
// 扩展全局指令
Vue.directive("lower-text", (el, binding) => {
el.innerText = binding.value.toLowerCase();
});
}
// export default MyPlugin
window.Myplugin = Myplugin;
使用
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>17_插件</title>
</head>
<body>
<div id="app">
<p>{{date | nowDate("YYYY-MM-DD")}}</p>
<p v-lower-text="msg"></p>
</div>
<script src="https://cdn.bootcdn.net/ajax/libs/dayjs/1.9.4/dayjs.min.js"></script>
<script type="text/javascript" src="../js/vue.js"></script>
<script src="./Myplugin.js"></script>
<script type="text/javascript">
// 必须要在new Vue 之前使用插件
// 使用Vue插件 --> 调用插件函数,传入Vue
Vue.use(Myplugin);
new Vue({
el: "#app",
data: {
msg: "BEIJING",
date: Date.now(),
},
mounted() {
Vue.globalplugin();
this.$instanceMethods();
},
});
</script>
</body>
</html>
key
测试代码
<div id="demo">
<div>
<button @click="add">add first</button>
<button @click="remove">remove first</button>
<button @click="sort">sort</button>
</div>
<h2>使用id作为key</h2>
<ul>
<li v-for="p in persons" :key="p.id">
{{p.id}}--{{p.name}}--{{p.age}}--<input/>
</li>
</ul>
<h2>使用index作为key</h2>
<ul>
<li v-for="(p, index) in persons" :key="index">
{{p.id}}--{{p.name}}--{{p.age}}--<input/>
</li>
</ul>
</div>
<script type="text/javascript" src="../js/vue.js"></script>
<script type="text/javascript">
new Vue({
el: '#demo',
data: {
persons: [
{id: 1, name: 'Tom', age: 13},
{id: 2, name: 'Jack', age: 12}
]
},
methods: {
add() {
this.persons.unshift({id: this.persons.length + 1, name: 'Bob', age: 14})
},
remove() {
this.persons.shift()
},
sort() {
this.persons.sort((p1, p2) => p1.age - p2.age)
}
}
})
虚拟DOM的key的作用?
1). react/vue中的key的作用/内部原理
2). 为什么列表的key尽量不要用index
1). 简单的说: key是虚拟DOM对象的标识, 在更新显示时key起着极其重要的作用
2). 详细的说: 当列表数组中的数据发生变化生成新的虚拟DOM后, React进行新旧虚拟DOM的diff比较
a. key没有变
item数据没变, 直接使用原来的真实DOM
item数据变了, 对原来的真实DOM进行数据更新
b. key变了
销毁原来的真实DOM, 根据item数据创建新的真实DOM显示(即使item数据没有变)
key为index的问题
1). 添加/删除/排序 => 产生没有必要的真实DOM更新 ==> 界面效果没问题, 但效率低
2). 如果item界面还有输入框 => 产生错误的真实DOM更新 ==> 界面有问题
注意: 如果不存在添加/删除/排序操作, 用index没有问题
解决:
使用item数据的标识数据作为key, 比如id属性值
key的值能用id用id,没有id的话,如果是往数组最后面添加、删除等操作可以用index
响应式
概念
响应式:当你更新数据的时候,数据会变化,同时用户界面也会更新
data中的数据才是响应式数据,compoteds是根据data计算的也是
原来数据的属性就是响应式,新添加的属性不是响应式
methods: {
add() {
// 不是响应式
this.xxx = 123;
},
data中普通对象
如果属性一开始存在,它就是响应式的
如果属性是后面新添加,它就不是响应式的
除非通过下面方法添加,才是响应式
this.$set(对象, 属性名, 属性值); 给对象添加响应式属性的方法
Vue.set(对象, 属性名, 属性值);
data中数组
如果通过数组下标添加新元素,不是响应式的
其他情况都是响应式的,vue底层修改了push的方法
Component
定义组件
// 定义组件
const TextComponent = Vue.extend({
// 组件的配置对象(和new Vue传入的配置对象基本对象)
data() {
// 返回值就是data数据
return {
num: 0,
};
},
methods: {
handle() {
this.num++;
},
},
// 组件模板页面(组件要渲染的页面)
template:
"<div><p>{{num}}</p><button @click='handle'>点我数字加加</button></div>",
});
注册组件
/*
组件命名:
1. test-component
使用:<test-component />
2. TestComponent
使用:<test-component />
<TestComponent /> 这种只能在脚手架中生效,页面中不行
*/
new Vue({
el: "#app",
// 注册组件(一旦注册了组件,当前就能使用组件)
components: {
// "text-component": TextComponent,
TextComponent: TextComponent,
},
});
使用组件
<div id="app">
<text-component></text-component>
</div>
定义全局组件
// 使用
<div id="app">
<h1>北京上海广州深圳</h1>
<test-component></test-component>
</div>
<script src="../js/vue.js"></script>
<script>
// const TestComponent = Vue.extend({
// data() {
// return {
// num: 0,
// };
// },
// methods: {
// handle() {
// this.num++;
// },
// },
// template:
// "<div><p>{{num}}</p><button @click='handle'>点我数字加加</button></div>",
// });
// Vue.component("test-component", TestComponent);
Vue.component("test-component", {
data() {
return {
num: 0,
};
},
methods: {
handle() {
this.num++;
},
},
template:
"<div><p>{{num}}</p><button @click='handle'>点我数字加加</button></div>",
});
new Vue({
el: "#app",
});
</script>
组件间通讯
data为什么要是函数?
data有两种写法:1. 对象 2. 函数
在普通页面中,两种写法都行
但是在组件中,必须使用函数
如果在组件中,data使用对象,复用组件时这多个组件会共享同一份data数据
如果在组件中,data使用函数,复用组件时这多个组件会调用函数得到一份新data数据,每个组件的data数据都会不一样,所以不会互相影响
1.props(父子组件)
当使用props传数据的时候,如果组件声明接受了,就可以直接通过this使用
如果组件没有声明接受,属性会在this.$attrs上,可以通过$attrs访问
-
适用场景:父子组件
-
父传子:将数据通过 props 直接传递
// 父组件传递数据
<Child :name="name" />
// 子组件声明接受props
{
props: ['name'],
props: {name: String},
props: {name: { type: String, required: true, default: xxx, validator() {} }},
}
// 子组件使用
this.xxx
// 如果属性没有通过props声明接受,那么可以通过$attrs来使用
this.$attrs.xxx
- 子传父
- 父组件定义更新数据的方法,以 props 方式传递给子组件,子组件声明接受调用函数,传递数据给父组件
- 子组件调用使用
2.自定义事件(子向父)
作用:用来子组件向父组件通信
给哪个组件绑定自定义事件,就只有那个组件可以触发(使用)
- 适用场景:子传父
方式一
@add="add"
绑定自定义事件
事件名:add
事件回调函数:add
// 绑定自定义事件
<Child @add="add" />
// 触发事件
this.$listeners.add();
this.$emit('add')
方式二
ref 如果设置给普通DOM元素,那么获取到的就是这个真实DOM元素
如果设置给组件,那么获取带的就是组件实例对象
// 绑定自定义事件
<Child ref="child" />
mounted() {
this.$refs.child.$on('add', this.add)
}
// 触发事件
this.$emit('add')
所有组件实例对象都具备以下方法:
$on(eventName, listener) 绑定自定义事件(持久)
$once(eventName, listener) 绑定自定义事件(一次性)
$off(eventName, listener) 解绑事件
$emit(eventName, data) 触发自定义事件
3.全局事件总线(任意组件)
-
适用场景:任意组件
-
原理:
- 本质上就是自定义事件
- 给 Vue 的原型添加一个可以绑定事件的对象(Vue 的实例 vm、组件的实例 this)
- 使用 Vue 的实例 vm 在 main.js 文件中最方便
-
使用方式有两种:
Vue.prototype.$bus = new Vue()
beforeCreate() { Vue.prototype.$bus = this }
- 所有组件实例对象都能通过原型链的方式访问到
$bus
这个对象,所以就能通过它绑定事件或者触发事件
// 绑定事件(接受数据):
this.$bus.$on(eventName, listener)
// 触发事件(发送数据):
this.$bus.$emit(eventName, data)
4.PubSub消息发布订阅(任意组件)
- 使用场景:任意组件
- 发布(发送数据):PubSub.publish(msg, data)
- 订阅(接受数据): PubSub.subscribe(msg, listener)
5.插槽(父给子传递带数据标签)
- 使用场景:父给子传递带数据的标签
默认插槽
组件写成双标签,里面放入标签数据,那么这个标签数据就会以插槽的方式传递给子组件
// 父组件给子组件传递带数据的标签
<AChild>
<p>hello vue000</p>
<p>hello vue11</p>
<p>{{msg}}</p>
</AChild>
// 子组件使用
// 使用父组件以插槽方式传递的标签数据
<slot></slot>
具名、命名插槽
具名、命名插槽: 给每一个插槽取一个名字
// 具名、命名插槽: 给每一个插槽取一个名字
<BChild>
// 旧语法:slot="名称"
<template slot="header">
<header>头部...{{ title }}</header>
</template>
// 新语法:v-slot:名称
<template v-slot:main>
<main>内容区...</main>
</template>
// 新语法可以简写:#名称
<template #footer>
<footer>底部...</footer>
</template>
</BChild>
- 子组件使用
<template>
<div>
// 显示头部
// 通过name属性来决定使用哪个具名插槽
<slot name="header"></slot>
<p>---------</p>
// 显示内容区
<slot name="main"></slot>
<p>---------</p>
// 显示底部
<slot name="footer"></slot>
</div>
</template>
作用域插槽
- 父组件
<CChild>
// 父组件插槽可以接受子组件通过slot传递的props数据
<template #list="slotProps">
<template v-slot:list="slotProps">
// { person } -> 就是对数据进行解构赋值
<template #list="{ person }">
<template #list="{ person: { name, age } }">
<ul>
<li>姓名:{{ name }}</li>
<li>年龄:{{ age }}</li>
</ul>
</template>
</CChild>
- 子组件
<template>
<div>
// 以标签属性(props)方式传递person数据
<slot name="list" :person="person"></slot>
</div>
</template>
<script>
export default {
name: "CChild",
data() {
return {
person: {
name: "jack",
age: 18,
},
};
},
};
</script>