vue学习笔记
模板语法
vue模板语法有2大类
1.插值语法:
功能:用于解析标签体内容
写法:{{xxx}},xxx是js表达式,且可以直接读取到data中的所有属性
2.指令语法:
功能:用于解析标签(标签属性,标签体内容,绑定事件)
举例:v-bind:href=“xxx” 或简写成 :href=“xxx” 同样要写js表达式,且可以直接读取到data中的所有属性
数据绑定
Vue中有两种数据绑定的方式
1.单向绑定(v-bind):数据只能从data流向页面
2.双向绑定(v-model):数据不仅能从data流向页面,还可以从页面流向data
备注:
双向绑定一般都应用在表单类元素上
el与data的两种写法
1.el有两种写法
- new Vue 时候配置el属性
- 先创建Vue实例,随后再通过vm.$mount("//el的值")指定el的值
2.data的两种写法
-
对象式
-
函数式
如何选择:目前哪种写法都可以,学到组件的时候必须用函数式,否则会报错
3.一个重要的原则
- 由Vue管理的函数一定不要写箭头函数,一旦写了箭头函数,this就不再是vue实例了
el
第一种
<div id="app">{{name}}</div>
<script>
const vm = new Vue({
el:"#app",
data:{
name:"y"
},
})
</script>
第二种
更加灵活
<div id="app">{{name}}</div>
<script>
const vm = new Vue({
data:{
name:"y"
},
})
v.$mount("#app")
</script>
data
第一种写法:对象式
<div id="app">{{name}}</div>
<script>
const v = new Vue({
el:"#app",
data:{
name:"y"
},
})
</script>
第二种写法:函数式
在组件中使用,vue帮忙调用
<div id="app">{{name}}</div>
<script>
const v = new Vue({
el:"#app",
data(){
//此处的this指向的是vue实例对象
//不能使用箭头函数,必须使用普通函数
return{
name:"y"
}
},
})
</script>
理解MVVM模型
vue没有完全遵循 MVVM 模型,但是 Vue 的设计也受到了它的启发。因此在文档中经常会使用 vm (ViewModel 的缩写) 这个变量名表示 Vue 实例。
MVVM模型
- M 模型(model) : 对应data中的数据
- V 视图(view) : 模板
- VM 视图模型(viewmodel) : Vue实例对象
最后发现:
- data中的所有属性,最后都出现在了vm身上
- vm身上所有的属性及vue原型上的所有属性,在Vue模板中都可以直接使用
- Data Bindings : 将数据绑定到视图上
- DOM Listeners : 对DOM的监听,数据的改变映射到视图上
接下来用一段代码来解释MVVM
<!-- 这里是视图View -->
<div id="app">
<!-- {{}} 花括号里只要是 vm 里面有的都能写 -->
<h1>学校名称:{{name}}</h1>
<h1>学校地址:{{address}}</h1>
<h1>测试一下:{{$options}}</h1>
</div>
<!-- ----------------------- -->
<script>
//-----------------这里是视图模型ViewModel------------
const vm = new Vue({
//----------------------------------------------------
el:"#app",
data:{
//-----------------这里模型Model----------------------
//最后都出现在 vm 里
name:"QX",
address:"福建"
//----------------------------------------------------
},
})
v.$mount("#app")
</script>
数据代理
Object.defineproperty
用于给一个对象定义属性用
<script>
let person = {
name:"张三",
sex:"男",
age:18 //使用这种方法添加的控制台输出的颜色较深
}
//给person添加一个age,值为18
//使用这种方法添加的控制台输出的颜色较浅
Object.defineProperty(person,"age",{
value:18
})
console.log(person);
</script>
使用这种方法添加的控制台输出的颜色较深
let number = 18
let person = {
name:"张三",
sex:"男",
age:18
}
使用这种方法添加的控制台输出的颜色较浅
它想表达的是age是不可被枚举的,age是不参与遍历的,不能修改,也不能删除
Object.defineProperty(person,"age",{
value:18,
//如果想让这个数据可以被遍历添加enumerable配置项
enumerable:true, //控制属性是否可以被枚举,默认值false
writable:true, //控制属性是否可以被修改,默认值是false
configurable:true //控制属性是否可以被删除,默认值是false
})
Object.defineproperty的get
Object.defineProperty(person,"age",{
//value:18,
//当有人读取person的age属性时,get函数就会被调用,且返回值就是age的值
get(){
return number
},
//当有人修改person的age属性时,get函数就会被调用,且会收到修改的具体的值
set(value){
console.log("有人修改了age,值是:",value)
}
})
console.log(person)
此时控制台输出的内容为
- name和sex为正常的值,age用…代替
- get age:f() 每次访问age都会触发get函数的调用
Object.defineproperty的set
Object.defineProperty(person,"age",{
//当有人修改person的age属性时,get函数就会被调用,且会收到修改的具体的值
set(value){
console.log("有人修改了age,值是:",value)
number = value
}
})
console.log(person)
理解数据代理
数据代理:通过一个对象代理对另一个对象中属性的操作(读/写)
<script>
let obj = {x:100}
let obj2 = {y:200}
//通过obj2读取和修改obj的x
Object.defineProperty(obj2,'x',{
get(){
return obj.x
},
set(value){
obj.x = value
}
})
//这是最简单的数据代理
</script>
vue中的数据代理
- Vue中的数据代理:通过vm对象来代理data中属性的操作(读/写)
- Vue中数据代理的好处:更加方便的操作data中的数据
- 基本原理:通过Object.defineProperty()把data对象中所有的属性添加到vm上。为每一个添加到vm上的属性都指定一个getter/setter。在getter/setter内部去操作(读/写)data中对应的属性
div id="app">
<h1>学校名称:{{name}}</h1>
<h1>学校地址:{{address}}</h1>
</div>
<script>
const vm = new Vue({
el:"#app",
data:{
name:"QX",
address:"福建"
}
})
console.log(vm);
</script>
事件处理
点击事件
- 事件的回调需要配置在methods对象中,最终会在vm上
- methods中配置的函数不要使用箭头函数,否则this就不是vm或组件实例对象而是window
- @click=“demo” 和 @click=“demo($event)” 效果一致,但是后者可以传参
div id="app">
<h1>欢迎来到:{{name}}</h1>
<!-- @xxx="yyy"yyy也可以是简单的语句 -->
<button @click="showInfo">点我提示信息</button>
<button @click="showInfo2(123,$event)">点我提示信息</button>
</div>
<script>
const vm = new Vue({
el:"#app",
data:{
name:"QX",
},
methods: {
showInfo(){
//这里的this指向的是vm(Vue实例对象)
//如果用的是箭头函数,指向的就是window
alert("同学你好")
},
showInfo2(num,event){
//$event就是event事件
console.log(num,event.target.innerText)
}
},
})
</script>
事件修饰符
- vue中的事件修饰符 修饰符可以连续写
- prevent:阻止默认事件(常用)
- stop:阻止事件冒泡(常用)
- once:事件只触发一次(常用)
- capture:使用事件的捕获模式
- self:只有event.target是当前的操作元素时才触发事件
- passive:事件的默认行为立即执行,无需等待事件回调执行完毕
<div id="app">
<h1>欢迎来到:{{name}}</h1>
<!-- prevent阻止默认事件 -->
<a href="https://www.baidu.com/" @click.prevent="showInfo">点我提示信息</a>
<!-- 阻止事件冒泡 -->
<!-- 点击按钮将会跳出两次弹框,添加stop后只会弹出一次-->
<div class="demo1" @click="showInfo">
<button @click.stop="showInfo">点我提示信息</button>
</div>
<!-- 事件只触发一次 -->
<!-- 弹框只会出现一次 -->
<button @click.once="showInfo">点我提示信息</button>
</div>
<script>
const vm = new Vue({
el:"#app",
data:{
name:"QX",
},
methods: {
showInfo(){
alert("你好")
}
},
})
</script>
键盘事件
- 1.Vue中常用的按键名 后面可以跟上其他按键组合一起的使用
- 回车 => enter
- 删除 => delete (捕获删除和退格键)
- 退出 => esc
- 空格 => space
- 换行 => tab 必须配合keydown使用
- 上 => up
- 下 => down
- 左 => left
- 右 => right
- 2.vue未提供别名的按键,可以使用按键原始的key去绑定,但注意要转为kebab-case(短横线命名)
- 3.系统修饰键(用法特殊):ctrl,alt,shift,meta
- 配合keyup使用:按下修饰键的同时,再按下其他键,随后释放其他键,事件才能触发
- 配合keydown使用:正常触发事件
- 4.也可以使用keyCode去指定具体的按键(不推荐)
- 5.Vue.config.keyCodes.自定义键名 = 键码 ,可以去定制按键别名
<div id="app">
<h1>欢迎来到:{{name}}</h1>
<!-- keyup表示按下按键,enter表示按下回车键 -->
<input type="text" placeholder="按下回车提示输入" @keyup.enter="showInfo">
<input type="text" placeholder="按下回车提示输入" @keyup.huiche="showInfo">
</div>
<script>
Vue.config.keyCodes.huiche = 13 //定义了一个别名回车
const vm = new Vue({
el:"#app",
data:{
name:"QX",
},
methods: {
showInfo(e){
console.log(e.target.value);
//输出的是按键的名字和值
console.log(e.key,e.keyCode);
}
},
})
</script>
计算属性
- 计算属性:
- 1.定义:要用的属性不存在,要通过已有的属性计算得来
- 2.原理:底层借助了Object.defineproperty方法提供的getter和setter
- 3.get函数什么时候执行?
- 初次读取的时候会执行一次
- 当依赖的数据发生改变时会被再次调用
- 4.优势:与methods实现相比,内部有缓存机制(复用),效率更高,调试方便
- 5.计算属性最终会出现在vm上,直接读取使用即可
姓名案例(插值语法实现)
<div id="app">
姓:<input type="text" v-model="firstname"><br>
名:<input type="text" v-model="lastname"><br>
<!-- slice(0,3)表示只要第0位到第2位,不包括第三位 -->
姓名:<span>{{firstname.slice(0,3)}}-{{lastname}}</span>
</div>
<script>
Vue.config.keyCodes.huiche = 13 //定义了一个别名回车
const vm = new Vue({
el:"#app",
data:{
firstname:"张",
lastname:"三"
}
})
</script>
姓名案例(methods实现)
<div id="app">
姓:<input type="text" v-model="firstname"><br>
名:<input type="text" v-model="lastname"><br>
姓名:<span>{{fullname()}}</span>
</div>
<script>
const vm = new Vue({
el:"#app",
data:{
firstname:"张",
lastname:"三"
},
methods: {
fullname(){
return this.firstname + '-' + this.lastname
}
},
})
</script>
姓名案例(计算属性实现)
<div id="app">
姓:<input type="text" v-model="firstname"><br>
名:<input type="text" v-model="lastname"><br>
姓名:<span>{{fullname}}</span>
</div>
<script>
const vm = new Vue({
el:"#app",
data:{
firstname:"张",
lastname:"三"
},
computed:{
//fullname:{
//get的作用是:当有人读取fullname时,get就会被调用,且返回值就作为fullname的值
//get什么时候调用?1.初次读取fullname时 2.所依赖的数据发生变化时
//这里不能写成箭头函数
//get(){
//console.log("get被调用了");
//return this.firstname + '-' + this.lastname
//},
//set什么时候调用? 当fullname被修改时
//set(value){
// console.log(value);
// const arr = value.split('-')
// this.firstname = arr[0]
// this.lastname = arr[1]
//}
//},
//这是简写的方式,计算属性一般不需要修改,需要修改的时候就不能使用简写
fullname(){
return this.firstname + '-' + this.lastname
}
}
})
</script>
监视属性
- 监视属性watch:
- 1.当被监视的属性变化时,回调函数自动调用,进行相关操作
- 2.监视的属性必须存在才能进行监视
- 3.监视的两种写法:
- new Vue时传入watch配置
- 通过vm.$watch监视
天气案例
<div id="app">
<h2>今天天气很{{info}}</h2>
<button @click="changeWeather">切换天气</button>
</div>
<script>
const vm = new Vue({
el:"#app",
data:{
isHot:true,
},
methods:{
changeWeather(){
this.isHot = !this.isHot
}
},
computed:{
info(){
return this.isHot ? '炎热':'凉爽'
}
},
watch:{
//计算属性也能监测 例:info
//正常写法
//isHot:{
//在isHot被修改的时候调用
// handler(newValue,oldValue){
// console.log("isHot被修改了",newValue,oldValue);
// }
//}
//简写
isHot(newValue,oldValue){
console.log("isHot被修改了",newValue,oldValue);
}
}
})
//第二种监测方法
// vm.$watch("isHot",{
// handler(newValue,oldValue){
// console.log("isHot被修改了",newValue,oldValue);
// }
// })
//简写
//vm.$watch("isHot",function(newValue,oldValue){
// console.log("isHot被修改了",newValue,oldValue);
//})
</script>
深度监视
- 深度监视:
- Vue中的watch默认不检测对象内部值的改变(一层)
- 配置deep:true可以监测对象内部值改变(多层)
- 备注:
- Vue自身可以监测对象内部值的改变,但Vue提供的watch默认不可以
- 使用watch时根据数据的具体结构来决定是否采用深度监视
<div id="app">
<h2>今天天气很{{info}}</h2>
<button @click="changeWeather">切换天气</button>
<hr>
<h3>a的值是:{{number.a}}</h3>
<button @click="number.a++">点我让a+1</button>
<h3>b的值是:{{number.b}}</h3>
<button @click="number.b++">点我让b+1</button>
</div>
<script>
const vm = new Vue({
el:"#app",
data:{
isHot:true,
number:{
a:1,
b:1
}
},
methods:{
changeWeather(){
this.isHot = !this.isHot
}
},
computed:{
info(){
return this.isHot ? '炎热':'凉爽'
}
},
watch:{
//属性和计算属性都能监测
isHot:{
//在isHot被修改的时候调用
handler(newValue,oldValue){
console.log("isHot被修改了",newValue,oldValue);
}
},
number:{
//开启深度监视,监视多级结构中所有属性的变化,没有添加这个配置项时,控制台不会输出
deep:true,
handler(){
console.log("number发生改变了");
}
}
}
})
</script>
watch对比computed
- computed和watch之间的区别
- 1.computed能完成的功能,watch都可以完成
- 2.watch能完成的功能,computed不一定能完成,例如:watch可以进行异步操作,computed不行
- 两个重要的小原则
- 1.所被vue管理的函数,最好写成普通函数,这样this的指向才是vm或组件实例
- 2.所有不被vue所管理的函数(定时器的回调函数,ajax的回调函数,promise的回调函数等),最好写箭头函数,这样this的指向才是vm或组件实例
这里用的时vue官网的例子
Vue 提供了一种更通用的方式来观察和响应 Vue 实例上的数据变动:侦听属性。当你有一些数据需要随着其它数据变动而变动时,你很容易滥用 watch
——特别是如果你之前使用过 AngularJS。然而,通常更好的做法是使用计算属性而不是命令式的 watch
回调。细想一下这个例子:
<div id="demo">{{ fullName }}</div>
var vm = new Vue({
el: '#demo',
data: {
firstName: 'Foo',
lastName: 'Bar',
fullName: 'Foo Bar'
},
watch: {
firstName: function (val) {
this.fullName = val + ' ' + this.lastName
},
lastName: function (val) {
this.fullName = this.firstName + ' ' + val
}
}
})
上面代码是命令式且重复的。将它与计算属性的版本进行比较:
var vm = new Vue({
el: '#demo',
data: {
firstName: 'Foo',
lastName: 'Bar'
},
computed: {
fullName: function () {
return this.firstName + ' ' + this.lastName
}
}
})
computed大大减少了代码量
绑定class样式
<style>
.basic{
width: 200px;
height: 200px;
background-color: red;
}
.changestyle{
width: 200px;
height: 200px;
background-color: skyblue;
border-radius: 50%;
}
.style1{
width: 200px;
height: 200px;
border: solid 1px black;
}
.style2{
background-color: red;
}
.style3{
border-radius: 50%;
}
</style>
</head>
<body>
<div id="app">
<!-- 字符串写法 适用于:样式的类名不确定,名字也不确定 -->
<div class="basic" @click="change" :class="sty">点击切换样式</div>
<br>
<!-- 数组写法 适用于:要绑定的样式个数不确定,名字也不确定,要更改样式直接操作数组里的数据 classArr.shift() classArr.push() -->
<div class="basic" :class="classArr"></div>
<br>
<!-- 对象写法 适用于:要绑定的样式个数确定,名字也确定,但要动态决定用不用 classArr.shift() classArr.push() -->
<div class="basic" :class="classObj"></div>
</div>
<script>
const vm = new Vue({
el:"#app",
data:{
sty:"",
classArr:["style1","style2","style3"],
classObj:{
style1:true,
style2:false
}
},
methods:{
change(){
this.sty = "changestyle"
}
}
})
</script>
</body>
条件渲染
v-if
v-if
指令用于条件性地渲染一块内容。这块内容只会在指令的表达式返回 true 值的时候被渲染。
<h1 v-if="awesome">Vue is awesome!</h1>
也可以用 v-else
添加一个“else 块”:
<h1 v-if="awesome">Vue is awesome!</h1>
<h1 v-else>Oh no 😢</h1>
因为 v-if
是一个指令,所以必须将它添加到一个元素上。但是如果想切换多个元素呢?此时可以把一个 <template>
元素当做不可见的包裹元素,并在上面使用 v-if
。最终的渲染结果将不包含 <template>
元素。
<template v-if="ok">
<h1>Title</h1>
<p>Paragraph 1</p>
<p>Paragraph 2</p>
</template>
v-else
你可以使用 v-else
指令来表示 v-if
的“else 块”:
<div v-if="Math.random() > 0.5">
Now you see me
</div>
<div v-else>
Now you don't
</div>
v-else
元素必须紧跟在带 v-if
或者 v-else-if
的元素的后面,否则它将不会被识别。
v-else-if
v-else-if
,顾名思义,充当 v-if
的“else-if 块”,可以连续使用:
<div v-if="type === 'A'">
A
</div>
<div v-else-if="type === 'B'">
B
</div>
<div v-else-if="type === 'C'">
C
</div>
<div v-else>
Not A/B/C
</div>
类似于 v-else
,v-else-if
也必须紧跟在带 v-if
或者 v-else-if
的元素之后。
用key管理可复用的元素
Vue 会尽可能高效地渲染元素,通常会复用已有元素而不是从头开始渲染。这么做除了使 Vue 变得非常快之外,还有其它一些好处。例如,如果你允许用户在不同的登录方式之间切换:
<template v-if="loginType === 'username'">
<label>Username</label>
<input placeholder="Enter your username">
</template>
<template v-else>
<label>Email</label>
<input placeholder="Enter your email address">
</template>
那么在上面的代码中切换 loginType
将不会清除用户已经输入的内容。因为两个模板使用了相同的元素,<input>
不会被替换掉——仅仅是替换了它的 placeholder
。
这样也不总是符合实际需求,所以 Vue 为你提供了一种方式来表达“这两个元素是完全独立的,不要复用它们”。只需添加一个具有唯一值的 key
attribute 即可:
<template v-if="loginType === 'username'">
<label>Username</label>
<input placeholder="Enter your username" key="username-input">
</template>
<template v-else>
<label>Email</label>
<input placeholder="Enter your email address" key="email-input">
</template>
v-show
另一个用于根据条件展示元素的选项是 v-show
指令。用法大致一样:
<h1 v-show="ok">Hello!</h1>
不同的是带有 v-show
的元素始终会被渲染并保留在 DOM 中。v-show
只是简单地切换元素的 CSS property display
。
注意,v-show
不支持 <template>
元素,也不支持 v-else
。
v-if vs v-show
v-if
是“真正”的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建。
v-if
也是惰性的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块。
相比之下,v-show
就简单得多——不管初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS 进行切换。
一般来说,v-if
有更高的切换开销,而 v-show
有更高的初始渲染开销。因此,如果需要非常频繁地切换,则使用 v-show
较好;如果在运行时条件很少改变,则使用 v-if
较好。
列表渲染
基本用法
-
v-for 指令:
用于展示列表数据
数组,字符串:v-for="(item,index) in xxx" :key=“yyy”
对象:v-for="(value,k"key") in xxx" :key=“yyy”
可遍历:数组,对象,字符串,指定次数
<div id="app">
<h2>人员列表</h2>
<ul>
<li v-for="(item,index) in person" :key="item.id">{{item.name}}--{{item.age}}</li>
</ul>
</div>
<script>
const vm = new Vue({
el:"#app",
data:{
person:[
{id:1,name:"张三",age:18},
{id:2,name:"李四",age:19},
{id:3,name:"王五",age:20}
]
}
})
</script>
key的作用与原理
首先来研究一下,当使用index作为key或者不使用key时会有什么问题
<div id="app">
<h2>人员列表</h2>
<button @click="add">添加一个老刘</button>
<ul>
<li v-for="(item,index) in person" :key="index">{{item.name}}--{{item.age}}<input type="text"></li>
</ul>
</div>
<script>
const vm = new Vue({
el:"#app",
data:{
person:[
{id:1,name:"张三",age:18},
{id:2,name:"李四",age:19},
{id:3,name:"王五",age:20}
]
},
methods:{
change(){
this.sty = "changestyle"
},
add(){
this.person.unshift({id:4,name:"老刘",age:40})
}
}
})
</script>
我们把三个输入框写入对应的名字
然后点击一下按钮
我们可以发现输入框里的数据与不一样
接下来我们把 :key 里的index 换成 item.id
<ul>
<li v-for="(item,index) in person" :key="item.id">{{item.name}}--{{item.age}}<input type="text"></li>
</ul>
现在数据就变得正常了
我们通过下面一张图来解释一下为什么会出现这种错误
如果将数据的顺序破坏了,使用index或者不使用key就会出现以上的问题
面试题:react,vue中的key有什么左右?(key的内部原理)
-
1.虚拟DOM中key的作用
key是虚拟DOM对象的标识,当状态中的数据发生变化时,Vue会根据"新数据"生成"新的虚拟DOM",随后Vue进行"新虚拟DOM"与"旧虚拟DOM"的差异比较,比较规则如下:
-
对比规则:
1.旧虚拟DOM中找到了与新虚拟DOM相同的key:
若虚拟DOM中内容没变,直接使用之前的真实DOM
若虚拟DOM中内容改变了,则生成新的真实DOM,随后替换页面中之前的真实DOM
2.旧虚拟DOM中未找到与新虚拟DOM相同的key:
创建新的真实DOM,随后渲染到页面
-
用index作为key可能会引发的问题
1.若对数据进行:逆序添加,逆序删除等破坏顺序的操作
会产生没有必要的真实DOM更新 ==> 界面效果没问题,但效率低
2.如果结构中还包含输入类的DOM
会产生错误DOM更新 ==> 界面有问题
-
开发中如何选择key
最好使用每条数据唯一标识作为key,比如id,手机号,身份证号,学号等唯一值
如果不存在对数据的逆序添加,逆序删除等破坏顺序操作,仅用于渲染列表用于展示,使用index作为key是没有问题的
Vue监测数据改变的原理
Vue监测数据改变的原理------对象
模拟一个数据监测
<script>
let data = {
name:"QX",
address:"福建"
}
//创建一个监视的实例对象,用于监视data中数据的变化
const obs = new Observer(data)
//准备一个vm实例对象
let vm={}
vm._data = data = obs
function Observer(obj){
//汇总对象中所有的属性形成一个数组
const keys = Object.keys(obj)
//遍历
keys.forEach((k)=>{
//这里的this指向是Observer的实例对象
//如果用的是data而不是用this的话,每次读取data里数据的时候都会重新触发get,然后继续读取data里的数据,从而导致递归死循环
Object.defineProperty(this,k,{
get(){
return obj[k]
},
set(val){
console.log(`${k}被修改了,我要去解析模板,生成虚拟DOM。。。`);
obj[k] = val
}
})
})
}
</script>
Vue.set()方法
首先我们来看一个案例:
<div id="app">
<h2>学生姓名:{{student.name}}----
性别:{{student.sex}}----
来自:{{student.address}}</h2>
</div>
<script>
const vm = new Vue({
el:"#app",
data:{
student:{
name:"张三",
address:"福建"
}
}
})
</script>
这时我们性别上是没有信息的,我们在控制台来为他添加一下看看有什么效果
vm._data.student.sex = "男"
输入回车之后发现页面没有变化
这是因为我们在这里添加的sex,没有set和get
要怎么解决这个问题呢?我们这里用一个api Vue.set()
在控制台输入
因为有数据代理,vm._data.student也可以写成vm.student
Vue.set(vm._data.student,'sex','男')
这里还有一个api可以实现这个功能,这个api不在Vue上,在vm上
vm.$set(vm._data.student,'sex','女')
**注意!!!这两个api只能给data里面的某一个对象添加属性,而不能给data添加 **
控制台报错。显示不允许添加响应式的数据在vue 的实例身上
Vue监测数据改变的原理------数组
我们在之前的代码上添加一个数组
<body>
<div id="app">
<h2>学生姓名:{{student.name}}----
性别:{{student.sex}}----
来自:{{student.address}}</h2>
</div>
<script>
const vm = new Vue({
el:"#app",
data:{
student:{
name:"张三",
address:"福建",
hobby:["喝酒","抽烟","烫头"]
}
}
})
</script>
</body>
打开控制台,查看一下视图student对比一下数据和对象有什么区别
我们可以发现,数组里的数据没有set和get,所以通过索引值修改数组里面的值的时候,数据确实是改了,但是页面不会被渲染
只有用数组里面的方法(push,pop,shift,unshift等)来修改数据才会被vue承认,渲染到页面上
我们也可以用Vue.set 和 vm.$set 来修改数组里面的值,也一样会渲染到页面上
Vue.set(vm._data.student.hobby,1,'跑步')
vm.$set(vm._data.student.hobby,0,'钓鱼')
总结Vue数据监测
-
- Vue会监测data中所有层次的数据
-
2.如何监测对象中的数据?
通过setter实现监测,且要在new Vue时就传入要监测的数据
(1).对象中后追加的属性,Vue默认不做响应式处理
(2).如需给后添加的属性做响应式,请使用如下api
Vue.set(target, propertyName/index , value)
vm.$set(target, propertyName/index , value)
-
如何监测数组中的数据?
通过包裹数组更新元素的方法实现,本质就是做了两件事:
(1).调用原生对应的方法对数组进行更新
(2).重新解析模板,进行更新页面
-
4.在Vue修改数组中的某个元素一定要用如下方法:
1.使用:push,pop,shift,unshift,splice,sort,reverse
2.Vue.set() 或 vm.$set()
-
特别注意:Vue.set() 和 vm.$set() 不能给vm 或 vm的根数据对象添加属性!
收集表单数据
-
v-model收集的是value值,用户输入的就是value值
-
v-model收集的是value值,且要给标签配置value值
-
1.没有配置input的value属性,那么收集的就是checked(勾选 or 未勾选 是布尔值)
2.配置input的value属性:
(1)v-model的初始值是非数组,那么收集的就是checked(勾选 or 未勾选 是布尔值)
(2)v-model的初始值是数组,那么收集的就是value组成的数组
-
v-model 的3个修饰符
1.lazy:失去焦点再收集数据
2.number:输入字符串转为有效的数字
3.trim:输入首尾空格过滤
v-once指令
我们来看一个案例
<div id="app">
<h2>n的初始值为:{{n}}</h2>
<h3>现在的n为{{n}}</h3>
<button @click="n++">n+1</button>
</div>
<input type="checkbox">
<script>
const vm = new Vue({
el:"#app",
data:{
n:1
}
})
</script>
我们可以发现两个n都随着按钮点击增加
现在我们将h2修改一下 添加v-once
<h2 v-once>n的初始值为:{{n}}</h2>
我们可以得出:
- v-once所在节点在初次动态渲染后,就视为静态内容了
- 以后数据的改变不会引起v-once所在结构的更新,可以用于性能优化
自定义指令
自定义一个指令(函数式)
定义一个v-big指令,和v-text功能类似,但会把绑定的数值放大10倍
<div id="app">
<h2 v-once>n的初始值为:{{n}}</h2>
<h3>现在的n为{{n}}</h3>
<h3>放大10倍的n为: <span v-big="n">{{n}}</span></h3>
<button @click="n++">n+1</button>
</div>
<input type="checkbox">
<script>
const vm = new Vue({
el:"#app",
data:{
n:1
},
//通过这个属性来定义指令
directives:{
//element:当前的DOM元素 binding:将元素和指令进行绑定
//big函数何时会被调用? 1.指令与元素成功绑定时 2.指令所在的模板被重新解析时
big(element,binding){
element.value = binding.value * value
}
}
})
</script>
自定义一个指令(对象式)
定义一个v-fbind指令,和v-bind功能类似,但可以让其所绑定的元素默认获取焦点
<body>
<div id="app">
<h2 v-once>n的初始值为:{{n}}</h2>
<h3>现在的n为{{n}}</h3>
<h3>放大10倍的n为: <span v-big="n">{{n}}</span></h3>
<button @click="n++">n+1</button>
<input type="text" v-fbind:value="n">
</div>
<input type="checkbox">
<script>
const vm = new Vue({
el:"#app",
data:{
n:1
},
directives:{
big(element,binding){
element.innerHTML = binding.value * 10
},
fbind:{
//指令与元素成功绑定时
bind(el,binding){
el.value = binding.value
},
//指令所在元素被插入页面时调用
inserted (el, binding) {
el.focus()
},
//指令所在模板被重新解析时
update(el,binding) {
el.value = binding.value
},
}
}
})
</script>
自定义指令总结
-
1.定义语法
(1).局部指令 new Vue({ new Vue({
directives:{指令名:配置对象} 或 directives:{指令名:回调函数}
}) })
(1).全局指令
Vue.directive(指令名,配置对象) 或 Vue.directive(指令名,回调函数)
-
2.配置对象中常用的3个回调
(1)bind:指令与元素成功绑定时调用
(2)inserted:指令所在元素被插入页面时调用
生命周期
引出生命周期
-
生命周期:
1.又名:生命周期回调函数,生命周期函数,生命周期钩子
2.是什么:Vue在关键时刻帮我们调用的一些特殊名称的函数
3.生命周期函数的名字不可更改,但函数的具体内容时程序员根据需要编写的
4.生命周期函数中的this指向是vm或组件实例对象
分析生命周期
生命周期总结
vm的一生(vm的生命周期):
将要创建 ===> 调用beforeCreate函数
创建完毕 ===> 调用created函数
将要挂载 ===> 调用beforeMount函数
(重要)挂载完毕 ===> 调用mounted函数 【重要的钩子】
将要更新 ===> 调用beforeUpdate函数
更新完毕 ===> 调用updated函数
(重要)将要销毁 ===> 调用beforeDestroy函数 【重要的钩子】
销毁完毕 ===> 调用destroyed函数
- 常用的生命周期钩子:
1.mounted:发送ajax请求,请求定时器,绑定自定义事件,订阅消息等(初始化操作)
2.beforeDestroy:清楚定时器,解绑自定义事件,取消订阅消息等(收尾工作)
-
关于销毁Vue实例
1.销毁后的借助Vue开发者工具看不到任何信息
2.销毁后自定义事件会失效,但原生DOM事件依然有效
3.一般不会在beforeDestroy操作数据,因为即使操作数据,也不会再触发更新流程了
组件
什么是组件
-
理解:用来实现局部(特定)功能效果的代码集合(html/css/js/image)
-
作用:复用编码,简化项目编码,提高运行效率
非单文件组件
- 一个文件中包含有n个组件为非单文件组件
基本使用
Vue中使用组件的三大步骤:
1.定义组件(创建组件)
2.注册组件
3.使用组件(写组件标签)
一:如何定义一个组件?
使用Vue.extend(options)创建,其中options和new Vue(options)时传入的那个几乎一模一样,但是区别如下:
1.el不要写,因为最终所有的组件都要经过一个vm 管理,由vm中的el决定服务于哪个容器
2.data必须写成函数,因为要避免组件被复用时,数据存在引用关系
备注:使用template可以配置组件结构
二:如何注册组件“
1.局部注册:new Vue的时候传入components选项
2.全局注册:靠Vue.components(‘组件名’,组件)
三:编写组件标签
<组件名></组件名>
<body>
<div id="app">
<!-- 第三步:编写组件标签,将组件呈现在页面上 -->
<school></school>
<hr>
<student></student>
</div>
<script>
//第一步:创建school组件
const school = Vue.extend({
//这里不要写el配置项,因为最终所有的组件都要被一个vm管理,由vm决定服务于哪个容器
template:`
<div>
<h2>学校名称:{{schoolname}}</h2>
<h2>学校地址:{{address}}</h2>
</div>
`,
data(){
return {
schoolname:"QX",
address:"福建"
}
}
})
//创建student组件
const student = Vue.extend({
template:`
<div>
<h2>学生名称:{{studentname}}</h2>
<h2>学生年龄:{{age}}</h2>
</div>
`,
data(){
return {
studentname:"张三",
age:18
}
}
})
//创建vm
const vm = new Vue({
el:"#app",
//第二部:注册组件(局部注册)
components:{
school,
student
}
})
//全局注册
// Vue.components('student',student)
// 全部的vue实例都能使用
</script>
组件的几个注意点
几个注意点:
1.关于组件名:
一个单词组成:
第一种写法(首字母小写):school
第二种写法(首字母大写):School
多个单词组成:
第一种写法(kebab-case命名):my-school
第二种写法(CamelCase命名):MySchool (需要Vue脚手架支持)
备注:
组件名尽可能回避HTML中已有的元素名称,例如:h2,H2都不行
可以使用name配置项指定组件在开发者工具中呈现的名字
2.关于组件标签:
第一种写法:<组件名></组件名>
第二种写法:<组件名/>
注意!! 不使用脚手架时,<组件名/> 会导致后续组件不能渲染
3.一个简写方式:
const school = Vue.extend(options) 可简写为:const school = options 不要Vue.extend 直接写配置对象
组件的嵌套
<body>
<div id="app">
<!-- 第三步:编写组件标签,将组件呈现在页面上 -->
<school></school>
</div>
<script>
//第一步:创建组件
const student = Vue.extend({
template:`
<div>
<h2>学生名称:{{studentname}}</h2>
<h2>学生年龄:{{age}}</h2>
</div>
`,
data(){
return {
studentname:"张三",
age:18
}
}
})
const school = Vue.extend({
template:`
<div>
<h2>学校名称:{{schoolname}}</h2>
<h2>学校地址:{{address}}</h2>
<student></student>
</div>
`,
data(){
return {
schoolname:"QX",
address:"福建"
}
},
components:{
//要写在创建student组件之后
student
}
})
const vm = new Vue({
el:"#app",
components:{
school,
student
}
})
</script>
VueComponent构造函数
关于VueComponent:
1.school组件本质是一个名为VueComponent的构造函数,且不是程序员定义的,是Vue.extend生成的
2.我们只需写或,Vue解析时会帮我们创建school组件的实例对象,即Vue帮我们执行的:new VueComponent(options)
3.特别注意:每次调用Vue.extend 返回的都是一个全新的VueComponent
4.关于this指向:
(1).组件配置中:
data函数,methods中的函数,watch中的函数,computed中的函数,它们的this均是【VueComponent实例对象】
(1).new Vue(options) 配置中:
data函数,methods中的函数,watch中的函数,computed中的函数,它们的this均是【Vue实例对象】
5. VueComponent的实例对象,以后简称VC (也可称之为:组件实例对象)
Vue的实例对象,以后简称vm
Vue与VueComponent的关系
- 一个重要的内置关系: VueComponent.prototype.__ proto __ === Vue.prototype
- 为什么要有这个关系:让组件实例对象(vc)可以访问到Vue原型上的属性,方法
vue脚手架
初始化脚手架
第一步:全局安装脚手架
npm install -g @vue/cli
第二步:切换到你要创建项目的目录,然后使用命令创建项目
vue create 项目名
前三个为默认的配置,选择一个自己需要的
第四个为自己选择需要的插件(空格选择)
第三步:安装成功启动项目
首先进入项目文件夹
cd ****
然后输入指令启动项目
npm run serve
render函数
打开main.js我们可以看到里面有个 render: h => h(App)
-
因为脚手架引入的vue是精简版的(vue.runtime.xxx.js)而不是vue.js,不包含模板解析器的,所以就需要render函数
-
vue.runtiome.xxx.js是运行版的Vue,只包含:核心功能,没有模板解析器
-
类型:
(createElement: () => VNode) => VNode
-
详细:
字符串模板的代替方案,允许你发挥 JavaScript 最大的编程能力。该渲染函数接收一个
createElement
方法作为第一个参数用来创建VNode
。如果组件是一个函数组件,渲染函数还会接收一个额外的
context
参数,为没有实例的函数组件提供上下文信息。
ref属性
- ref属性
1.被用来给元素或子组件注册引用信息(id的替代者)
2.应用在html标签上获取的是真实DOM元素,应用在组件标签上是组件实例对象(vc)
3.使用方式:
打标识:
或 获取: this.$refs.xxx
首先我们看看在ref属性不在同地方使用有什么不同
<template>
<div id="app">
<h1 v-text="msg" ref="title"></h1>
<button ref="btn" @click="showDOM">惦记我输出上方的DOM元素</button>
<School ref="sch"></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); //输出真实DOM元素
console.log(this.$refs.btn); //输出真实DOM元素
console.log(this.$refs.sch); //输出School组件实例对象(vc)
}
},
}
</script>
点击按钮之后我们可以看到ref在不同地方使用有什么不同
props配置
-
配置项props
功能:让组件接收外部传过来的数据
(1).传递数据:
(2).接收数据:
第一种方式(只接收):
props:[‘name’]
第二种方式(限制类型):
props:{
name:Number
}
第三种方式(限制类型,限制必要性,指定默认值):
props:{
name:{
type:String, //类型
required:true, //必要性
default:‘老王’ //默认值,有required的时候不写默认值
}
}
备注:props是只读的,Vue底层会监视你对props的修改,如果进行了修改,就会发出警告,若业务需求确实需要修改,那么请复制props的内容到data中一份,然后去修改data中的数据
APP组件
<template>
<div id="app">
<Student name="李四" sex="女" age="20"></Student>
<!-- 接下来我们想要显示出来的年龄比真实年龄大一岁,我们需要加上v-bind 这样才能解析里面的js语句,不加的话他只能是string类型的数据-->
<Student name="张三" sex="男" :age="24"></Student>
</div>
</template>
<script>
import Student from './components/Student.vue'
export default {
name: 'App',
components: {
Student
}
}
</script>
School组件
<template>
<div>
<h2>我是一个{{ msg }}的学生</h2>
<h2>学生名称:{{ name }}</h2>
<h2>学生性别:{{ sex }}</h2>
<h2>学生年龄:{{ age }}</h2>
<!-- 接下来我们想要显示出来的年龄比真实年龄大一岁-->
<h2>学生年龄:{{ age+1 }}</h2>
</div>
</template>
<script>
export default {
data() {
console.log(this);
return {
msg: "家里蹲大学",
};
},
//简单接收写法
props:["name","sex","age"]
//接收的同时对数据进行类型限制
// props:{
// name:String,
// age:Number,
// sex:String
// }
//接收的同时对数据进行类型限制+必要性的限制
// props:{
// name:{
// type:String, //name的类型是字符串
// required:true, //这个值是必须要传的
// },
// age:{
// type:Number,
// default:33 //如果没有传入,这个值就为默认值
// },
// sex:{
// type:String,
// required:true
// }
// }
};
</script>
首先我们可以在组件上直接传入,这种传入的方式数据都存在组件实例(vc)上,但是_data里面不存在这些数据
mixin混入
首先我们创建两个组件
School
<template>
<div>
<h2 @click="showname">学校名称:{{ name }}</h2>
<h2>学校地址:{{ address }}</h2>
</div>
</template>
<script>
export default {
data() {
return {
name: "QX",
address: "福建",
};
},
methods: {
showname() {
alert(this.name);
},
},
};
</script>
Student
<template>
<div>
<h2 @click="showname">学生名称:{{ name }}</h2>
<h2>学生年龄:{{ age }}</h2>
</div>
</template>
<script>
export default {
data() {
return {
name: "张三",
age: 18,
};
},
methods: {
showname() {
alert(this.name);
},
},
};
</script>
我们可以发现,两个组件里面有一个同样的方法 showname,这时我们就可以使用mixin
使用方法:
首先在src文件夹下创建一个mixin.js文件写入相同方法的代码
export const mixin = {
methods: {
showname() {
alert(this.name);
},
}
然后将两个组件中的 showname方法删除,然后在两个组件里将mixin文件导入
import {mixin} from '文件路径' //花括号里的名称为 mixin.js文件里导出的名称
然后再配置项中导入 mixins:[mixin]
import {mixin} from '../mixin'
export default {
data() {
return {
name: "张三",
age: 18,
};
},
mixins:[mixin]
};
</script>
scoped样式
**一个组件style上添加scoped之后,其他组件就不会被这个组件的样式影响 **