一、模板语法
(一) 插值操作
[1]. Mustache语法
也称为双大括号语法。
- 双大括号中可以使用JS表达式
<div id="app">
<h2>{{cn}}</h2>
<h2>{{count * 2}}</h2>
<h2>{{cn + ' ' + count}}</h2>
<h2>{{1 + 1}}</h2>
<h2>{{Date.now()}}</h2>
<h2>{{message.toUpperCase()}}</h2>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
//let(变量), const(常量)
const vm = new Vue({
el:"#app", //用于挂载要管理的元素
data:{//定义数据
message:"hello,world!",
cn:"你好,世界!",
count: 100
}
});
</script>
[2]. v-once
该指令表示元素和组件只渲染一次,不会随着数据改变而改变。
<h2 v-once>{{message}}</h2>
[3]. v-html
如果我们希望按照HTML格式进行解析数据,并且显示对应的内容,可以使用v-html指令。
会有xss风险,会覆盖子组件。
<div id="app">
<!-- 输出 <h2><a href="http://www.baidu.com">百度一下</a><h2> -->
<h2 v-html="url"></h2>
</div>
<script src="./vue.js"></script>
<script>
const app = new Vue({
el: "#app", //用于挂载要管理的元素
data: { //定义数据
url: '<a href="http://www.baidu.com">百度一下</a>'
}
});
</script>
[4]. v-text
作用和Mustache比较相似:都是用于将数据显示在界面中。
使用这种方法会将标签总原本的文本内容给替换掉。
<h2 v-text="message">这段文字将被替换掉</h2>
[5]. v-pre
-
v-pre用于跳过这个元素和它的子元素的编译过程,用于直接显示原本的Mustache语法。
-
可利用它跳过:没有使用指令、也没有使用差插值语法的节点,会加快编译。
<!-- 输出 <h2>{{message}}</h2> --> <h2 v-pre>{{message}}</h2>
[6]. v-cloak
如果未执行到vue代码时,页面会显示出未编译额Mustache标签。为了避免这种情况可以使用v-cloak
在vue解析之前,如果有属性v-cloak,会添加一个样式[v-cloak]{display:none}
在vue解析之后,将删除v-cloak。
<h2 v-cloak>{{message}}</h2>
[7]. 自定义指令
https://cn.vuejs.org/v2/guide/custom-directive.html
-
局部自定义指令:
directives:{指令名:配置对象}
或directives:{指令名:回调函数}
-
全局指令:
Vue.directive(指令名, 配置对象)
或Vue.directive(指令名, 回调函数)
-
自定义指令提供的钩子函数 (均为可选):
bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。
inserted:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。
update:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新 (详细的钩子函数参数见下)。
componentUpdated:指令所在组件的 VNode 及其子 VNode 全部更新后调用。
unbind:只调用一次,指令与元素解绑时调用。
-
钩子函数被赋予了以下参数:
el: 指令所绑定的元素,可以用来直接操作 DOM 。
binding: 一个对象,包含以下属性:- name: 指令名,不包括 v- 前缀。
- value: 指令的绑定值, 例如: v-my-directive=“1 + 1”, value 的值是 2。 - oldValue: 指令绑定的前一个值,仅在 update 和 componentUpdated 钩子中可用。无论值是否改变都可用。
- expression: 绑定值的字符串形式。 例如 v-my-directive=“1 + 1” , expression 的值是 “1 + 1”。
- arg: 传给指令的参数。例如 v-my-directive:foo, arg 的值是 “foo”。- modifiers: 一个包含修饰符的对象。 例如: v-my-directive.foo.bar, 修饰符对象 modifiers 的值是 { foo: true, bar: true }。
- vnode: Vue 编译生成的虚拟节点,查阅 VNode API 了解更多详情。
- oldVnode: 上一个虚拟节点,仅在 update 和 componentUpdated 钩子中可用。
- name: 指令名,不包括 v- 前缀。
<div id="app">
<p v-add-number="number"></p>
<button @click="add">添加number</button>
<input type="text" v-fbind:value="number" />
</div>
</div>
<script src="./../js/vue.js"></script>
<script>
const vm = new Vue({
el: "#app",
data: {
number: 0
},
methods: {
add() {
this.number++;
}
},
directives: {
'add-number'(element, binding) { //等于 bind()和update()钩子函数的简写
console.log(this); //window
element.innerText = binding.value;
},
fbind: {
bind(element, binding) {
console.log('指令与元素成功绑定时调用');
console.log(this); //window
element.value = binding.value;
},
inserted(element, binding) {
console.log('指令所在元素被插入父元素中时被调用');
console.log(this); //window
element.focus(); //元素获得焦点
},
update(element, binding){
console.log('指令所在的模板被重新解析时被调用');
console.log(this); //window
element.value = binding.value;
}
}
}
});
</script>
(二)绑定数据和元素属性 v-bind
v-bind 单向数据绑定:数据只能从data流向页面。
[1].基本使用
- v-bind指令动态绑定属性值
<img v-bind:src="imgUrl">
- v-bind语法糖,在属性前添加“:”
<a :href="url">
- 直接使用js表达式
<h2 v-bind:data-date="Date.now()">time</h2> <h2 v-bind:data-date="name.toUpperCase()">name</h2>
[2].动态绑定class
-
通过变量来确定需要绑定的class。
<div id="root"> <!-- 通过点击事件,将class中的moon 变为 bad --> <p :class="moon" @click="changeMoon"> 点击却换心情 {{moon}}</p> </div> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script> const vm = new Vue({ el: '#root', data: { moon: 'happy', }, methods: { changeMoon(){ this.moon = 'bad'; } }, }); </script>
-
通过数组语法来添加多个样式。如果参数没有使用单引号括住,是会被当成变量解析的。
<!-- 为h2添加 active和line两个样式 --> <h2 v-bind:class="['active', 'line']">v-bind:class</h2>
-
可以通过对象属性对应的布尔值来判断是否添加该样式。
可以和普通的类可以同时存在。<!-- 当isActive为true时,将增加active和line两个class--> <h2 class="title" v-bind:class="{active: isActive, line: isActive}">v-bind:class</h2> <script> const vm = new Vue({ el:"#app", //用于挂载要管理的元素 data:{//定义数据 isActive: true } }); </script>
-
如果过于复杂,可以放在一个methods或者computed中。
<h2 :class="getclasses()">getclasses</h2> <script> const app = new Vue({ el:"#app", data:{ isActive:true, }, methods: { getclasses: function(){ return {active: this.isActive}; } } }); </script>
[3].动态绑定style
利用v-bind:style来绑定css内联样式,在写css属性名的时候,比如font-size,可以使用驼峰法fontSize或短横线分割(记得使用单引号括起来)‘font-size’
绑定class有两种方式:对象语法和数组语法
<div id="app">
<!--输出:<h2 style="font-size: 100px;">123</h2> -->
<h2 v-bind:style="{fontSize: finalSize}">123</h2>
<!--输出: <h2 style="font-size: 100px;">123</h2> -->
<h2 v-bind:style="[finalSize2]">123</h2>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
const vm = new Vue({
el : '#app',
data : {
finalSize: '100px',
finalSize2: {'font-size': '100px'},
}
});
</script>
[4].动态绑定属性
- 在某些情况下,我们属性的名称可能也不是固定的,可以使用 :[属性名]=“值” 的格式来定义
<div id="app">
<~<h2 data-id="123">123</h2>
<h2 v-bind:[attrname]="attrValue">123</h2>
</div>
<script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.13/vue.js"></script>
<script>
const vm = new Vue({
el: '#app',
data: {
attrname: 'data-id', // 不支持大写, vue3支持
attrValue: 123,
}
});
</script>
[4].绑定一个对象中的所有属性
v-bind用于绑定一个或多个属性值,或者向另一个组件传递props值
<div id="app">
<!-- 最后渲染结果:<h2 name="张三" age="32" height="180">123</h2> -->
<h2 v-bind="person">123</h2>
</div>
<script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.13/vue.js"></script>
<script>
const vm = new Vue({
el: '#app',
data: {
person:{
name: '张三',
age: 32,
height: 180
}
}
});
</script>
(三) 事件监听 v-on
v-on:可以简写为@
[1]. 基本使用
- 点击按钮实现counter加和减
<div id="app"> <h2>{{counter}}</h2> <button v-on:click="counter++">+</button> <button v-on:click="counter--">-</button> </div> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script> const vm = new Vue({ el : '#app', data : { counter: 0 } }); </script>
- 监听图片加载
<!-- 图片加载完成触发自定义方法imgLoadfuinish() --> <img src="../img.png" @load="imgLoadFinish()">
[2]. 事件监听传参 v-on
-
methods中定义的方法不需要额外的参数,那么
v-on:方法名
后可以不添加() -
如果在组件中使用v-on绑定方法而没有传递参数,那么会默认将原生事件
event
当作参数传递进去。 -
如果需要传递某个参数的同时也需要event时,可以通过$event传入事件。
1). vue的event是原生的
2). 事件被挂载到当前元素 -
methods中的方法中的this为vue实例。
-
$event是一个占位符,可以在参数的任意位置。
<div id="app"> <h2>{{counter}}</h2> <button @click="add">+1</button> <button @click="addNum($event, 10)">+10</button> </div> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script> const vm = new Vue({ el : '#app', data : { counter: 0 }, methods :{ add(event){ //event 是原生的 console.log(event.target); this.counter++; }, addNum(event, num){ console.log(event); this.counter += num; } } }); </script>
[3]. v-on事件修饰符
- 注:修饰符可以多个连续使用:
<div @click="divClick"> <button @click.stop.prevent="btnClick">按钮</button> </div>
-
.stop阻止事件冒泡
<!--触发btnClick事件,不会触发divClick事件 --> <div @click="divClick"> <button @click.stop="btnClick">按钮</button> </div>
-
.prevent 阻止元素的默认事件
<!--触发preventClick事件的同时,页面不会跳转 --> <a href="http://www.baidu.com" @click.prevent="preventClick">百度一下 你就知道</a>
-
.{keyCode | keyAlias} 按下指定按键时触发
- 常用的按键别名:
回车 (enter),删除(delete),退出(esc),空格(space),换行(tab,必须配合keydown事件使用),上(up),下(down),左(left),右(right)
- 系统修饰键:ctrl、alt、shift、meta
(1). 配合keyup事件使用:按下修饰键的同时,再按下其他键,随后释放其他键,事件才会被触发
(2). 配合keydown事件使用:正常触发事件
- 可以使用keyCode去指定具体的按键(不推荐)
- Vue.config.keyCodes.自定义键名 = 键码; 自定义自己的按键别名。
<!-- 松开键盘任意按键时触发 --> <input type="text" @keyup="keyClick"> <!-- 松开回车键时触发 --> <input type="text" @keyup.enter="enter"> <!-- 松开字母a键时触发(不区分大小写) --> <input type="text" @keyup.65="aClick"> <!-- 组合键ctrl + y 时触发 --> <input type="text" @keyup.ctrl.y ="aClick">
- 常用的按键别名:
-
.once事件只触发一次事件
<button @click.once="once">once</button>
-
.native 监听子组件事件
当子组件cpn发生点击事件时触发父组件的parentReceive方法<!-- 1.父组件 --> <div id="app"> <!-- 2.子组件中 --> <cpn @click.native="parentReceive"></cpn> </div>
尝试监听一个类似 <input> 的非常特定的元素时,有时会监听不到。因为<base-input> 组件的根元素实际上是一个 <label> 元素,父级的 .native 监听器将静默失败。它不会产生任何报错,但是 onFocus 处理函数不会如你预期地被调用。
解决方案:https://cn.vuejs.org/v2/guide/components-custom-events.html#%E5%B0%86%E5%8E%9F%E7%94%9F%E4%BA%8B%E4%BB%B6%E7%BB%91%E5%AE%9A%E5%88%B0%E7%BB%84%E4%BB%B6
<div id="app"> <base-input v-on:focus.native="onFocus"></base-input> </div> <template id="base-input"> <!-- 添加上label标签后将监听不到input标签的focus事件 --> <label for="">{{info}}<input type="text" v-model="info"></label> </template> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script> const baseInput = { template:"#base-input", data () { return { info: '' } }, } const app = new Vue({ el: "#app", methods: { onFocus(){ console.log('获得焦点'); } }, components: { baseInput } }); </script>
-
.capture 内部元素触发的事件先在此处理,然后再交由内部元素处理
<div v-on:click.capture="doThis">...</div>
-
.self 只当在 event.target 是当前元素自身时触发处理函数,即事件不是从内部元素触发的
<div v-on:click.self="doThat">...</div>
-
.passive 事件的默认行为立即执行,无需等待事件回调函数执行完毕。
<!-- 当滚动滚轮时,先执行滚轮行为,再执行doSome函数 --> <div @wheel.passive="doSome">...</div>
(四) 条件判断 v-if
- 当 v-if 的条件为false时,v-if 包含的元素,不会存在于DOM中
<p v-if="score >= 90"> 优秀 </p> <p v-else-if="score >= 60"> 及格 </p> <p v-else> 不及格 </p>
(五) 显示元素 v-show
- 当需要在显示与隐藏之间切换很频繁时,使用v-show
- 当只有一次切换时,通常使用v-if
- 当isShow为false时,给元素的样式display改为none
<div v-show="isShow">显示元素</div>
(六) 遍历数组(对象) v-for
- 遍历数组
<li v-for="(value, index) in arr" :key="(value + index)">{{index + '-' + value}}</li>
- 遍历对象
key和index可以省略<li v-for="(val, key, index) in obj" :key="val.id">{{index + '-' + key + ':' + val}}</li>
- 在使用v-for时,给对应的元素或组件添加上一个:key属性(默认使用序号index作为:key的值),key的主要作用是更高效的更新虚拟dom (使用了虚拟DOM对比算法)。
- 使用key来给每个节点做一个唯一标识符,key不推荐使用random或者index,可以使用id或对象的key
- v-for 和 v-if 不能一起使用, v-for 比 v-if 的计算优先级高
(八) 表单绑定v-model
v-model 双向绑定:数据可以从data流向页面,也可以从页面流向data。
[1].v-model基础绑定
-
v-model其实是一个语法糖,背后的本质是包含两个操作:
- v-bind绑定一个value属性
- v-on指令给当前元素绑定input事件
<input type="text" v-model="message"> <!-- 等同于 --> <input type="text" v-model:value="message"> <!-- 等同于 --> <input type="text" v-bind:value="messge" v-on:input="message = $event.target.value">
-
v-model 绑定在模板上
- 为了让它正常工作,这个组件内的 必须:
- 将其 value attribute 绑定到一个名叫 value 的 prop 上
- 在其 input 事件被触发时,将新的值通过自定义的 input 事件抛出
<custom-input v-modal="searchText"></custom-input> <!-- 等价于 --> <custom-input v-bind:value="searchText" v-on:input="searchText = $event"></custom-input>
Vue.component('custom-input', { props: ['value'], template: ` <input v-bind:value="value" v-on:input="$emit('input', $event.target.value)" > ` })
- 为了让它正常工作,这个组件内的 必须:
[2]. v-model结合radio
-
即使radio标签中的name属性省略,也能互斥同一组radio标签。
-
如果给变量sex了一个默认值(如“男”),vue会自动选中和value属性默认值一样标签
<div id="app"> <label for="man"> <!-- 默认选中该选项 --> <input type="radio" name="" id="man" v-model="sex" value="男">男 </label> <label for="woman"> <input type="radio" name="" id="woman" v-model="sex" value="女">女 </label> <p>你选择的性别是:{{sex}}</p> </div> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script> const vm = new Vue({ el:"#app", data:{ sex:'男' }, }); </script>
[3]. v-model结合checkbox
-
没有配置checkbox的value属性,那么收集的就是checked属性(勾选 or 未勾选,是布尔值)
<div id="app"> <label> <!-- 如果勾选则 agree的值为true --> <input type="checkbox" v-model="agree">是否同意协议 </label> </div> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script> const vm = new Vue({ el: "#app", data: { agree: '', //选中时,值为true(也可以给一个默认值false,表示刚开始不选择) }, }); </script>
-
配置input的value属性
- (1). v-model的初始值为非数组,那么收集的就是checked属性(勾选 or 未勾选,是布尔值)
- (2). v-model的初始值为数组,则收集的就是value属性值组成的数组
<div id="app"> <input type="checkbox" name="" id="" v-model="hobbies" value="篮球">篮球 <input type="checkbox" name="" id="" v-model="hobbies" value="足球">足球 <input type="checkbox" name="" id="" v-model="hobbies" value="羽毛球">羽毛球 <p>爱好为:{{hobbies}}</p> </div> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script> const vm = new Vue({ el:"#app", data:{ hobbies: ['篮球'] //hobbies需为数组,默认选中篮球 }, }); </script>
[4]. v-model结合select
<div id="app">
<!-- 1.单选下拉框,默认选中香蕉 -->
<select name="" id="" v-model="fruit">
<option value="香蕉">香蕉</option>
<option value="苹果">苹果</option>
<option value="葡萄">葡萄</option>
</select>
<p>你选择的水果是:{{fruit}}</p>
<!-- 2. 多选下拉框 -->
<select name="" id="" v-model="fruits" multiple>
<option value="香蕉">香蕉</option>
<option value="苹果">苹果</option>
<option value="葡萄">葡萄</option>
</select>
<p>你选择的水果是:{{fruits}}</p>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
const vm = new Vue({
el:"#app",
data:{
fruit:'香蕉',
fruits: []
},
});
</script>
[5]. v-model修饰符
- .lazy修饰符
-
默认情况下,v-model默认是在input事件中同步输入的数据的。也就是说,一旦数据发生改变对应的data中的数据就会自动发生改变。
-
lazy修饰符可以让数据在失去焦点或者回车时才更新。
<input type="text" v-model.lazy="text"> <p>文本框内容:{{text}}</p>
- number修饰符
-
默认情况下,在输入框中无论我们输入的是字母还是数字,都会被当做字符串类型进行处理,但是如果我们希望处理的是数字类型,那么最好直接将内容当做数字处理。
-
.number修饰符可以在输入框中输入的内容自动转换为数字类型
<input type="number" v-model.number="number"> <p>文本框内容:{{number}} - {{typeof number}}</p>
- trim修饰符
-
trim修饰符可以过滤输入内容左右两边的空格。
<input type="tex" v-model.trim="trim"> <p>文本框内容:{{trim}}</p>
(九) 自定义v-module双向绑定数据
https://cn.vuejs.org/v2/guide/components-custom-events.html#%E8%87%AA%E5%AE%9A%E4%B9%89%E7%BB%84%E4%BB%B6%E7%9A%84-v-model
v-model 双向绑定实际上就是通过子组件中的 $emit 方法派发 input 事件,父组件监听 input 事件中传递的 value 值,并存储在父组件 data 中;然后父组件再通过 prop 的形式传递给子组件 value 值,在子组件中绑定 input 的 value 属性即可。
[1]. 自定义v-model 1
<div id="app">
<p>{{content}}</p>
<cpn v-model="content"></cpn>
</div>
<template id="cpn">
<input type="text" :value="content" @input="$emit('change1', $event.target.value)">
</template>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
const cpn = {
template: '#cpn',
model: {
prop: 'content',//props中的content与该名字要一致
event: 'change1'//触发父组件的事件
},
props: {
content: {
type: String,
default () {
return ''
}
}
}
};
const vm = new Vue({
el: "#app",
data:{
content:''
},
components: {
cpn,
}
});
</script>
[2]. 自定义v-model 2
<div id="app">
<p>{{content}}</p>
<cpn @change1="value=>content = value" :message="content"></cpn>
</div>
<template id="cpn">
<input type="text" @input="$emit('change1', $event.target.value)" :value="message">
</template>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
const cpn = {
template:'#cpn',
props:{
message:{
type:String,
}
}
}
const app = new Vue({
el: "#app",
data: {
content: ''
},
components: {
cpn,
},
});
</script>
[3]. v-module和.sync
从 2.3.0 起我们重新引入了 .sync 修饰符,但是这次它只是作为一个编译时的语法糖存在。它会被扩展为一个自动更新父组件属性的 v-on 监听器。
<div id="app">
<p>{{bar}}</p>
<!-- <cpn :content="bar" @update:content="val=> bar = val"></cpn> -->
<!-- 等同于 -->
<cpn :content.sync="bar"></cpn>
</div>
<template id="cpn">
<input type="text" :value="content" @input="$emit('update:content', $event.target.value)">
</template>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
const cpn = {
template: '#cpn',
props: {
content: {
type: String,
default () {
return ''
}
}
}
};
const app = new Vue({
el: "#app",
data: {
bar: ''
},
components: {
cpn,
},
});
</script>
二、 计算属性computed
-
计算属性有缓存,计算属性会把函数执行一次,把结果存起来,依赖的data改变,会重新计算。
-
原理:底层截住了Object.defineproperty方法提供的getter和setter
-
methods和computed有时都可以实现我们的功能,但是计算属性会进行缓存,如果多次使用时,计算属性只会调用一次。
<div id="app"> <h2>{{fullName}}</h2> <h2>{{fullName2}}</h2> <button @click="updateFullName('张 三')">点击修改</button> </div> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script> //在浏览器控制台运行 vm.fullName = 'John Doe' 时,setter 会被调用,vm.firstName 和 vm.lastName 也会相应地被更新。 const vm = new Vue({ el: '#app', data: { firstName: 'Kobe', lastName: 'Bryant', }, computed: { //计算属性方法一:完整版 fullName: { set: function (newValue) { //有set方法而方法体为空时表示是只读属性 console.log('fullName setter方法被调用'); let arr = newValue.split(' '); this.firstName = arr[0]; this.lastName = arr[1]; }, get: function () { //get被调用:1.初次读取fullName时执行 2.所依赖的数据发生变化时 return this.firstName + ' ' + this.lastName; } }, //方法二:简写版 (只读不改时),相当于只有getter函数 fullName2: function () { return this.firstName + ' ' + this.lastName; } }, methods: { updateFullName(val) { this.fullName = val; } }, }); </script>
三、过滤器 filters
- 过滤器就是一个函数。格式
{{要过滤的值 | 过滤器函数 [| 过滤器函数2] }}
- v-bind也可以使用过滤器。
<div id="app">
<h2>{{price | showPrice('我是参数')}}</h2>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
const vm = new Vue({
el:"#app",
data:{
price: 88
},
filters:{
showPrice(val, str){ //str非必有
//str 就是 '我是参数'
return '¥' + val.toFixed(2);
}
}
});
</script>
//定义全局过滤器capitalize
Vue.filter('capitalize', function (value) {
if (!value) return ''
value = value.toString()
return value.charAt(0).toUpperCase() + value.slice(1)
})
四、对象、数组添加和删除属性并实现响应式
https://cn.vuejs.org/v2/guide/reactivity.html#%E5%A6%82%E4%BD%95%E8%BF%BD%E8%B8%AA%E5%8F%98%E5%8C%96
由于 JavaScript 的限制,Vue 不能检测数组和对象的变化。
(一)对象添加和删除属性的响应式
- Vue 无法检测 property 的添加或移除。(由于 Vue 会在初始化实例时对 property 执行 getter/setter 转化,所以 property 必须在 data 对象上存在才能让 Vue 将它转换为响应式的。)
- 使用 Vue.set(target, property, value)或vm.$set(target, property, value)新增属性,并且为新添加的属性实现响应式。
- 使用 Vue.delete(target, property)或vm.$delete(target, property)删除属性,删除时也能实现响应式。
<div id="app"> <ul> <li v-for="(item,index) in obj" :key="index">{{item}}</li> </ul> <button @click="setObj">更改姓名</button> <button @click="addAttr">添加属性</button> <button @click="delAttr">删除属性</button> <button @click="addAttr2">添加属性2</button> </div> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script> const vm = new Vue({ el: "#app", data: { obj: { name: '张三', age: 123 } }, watch: { obj(newValue, oldValue) { console.log(newValue); console.log(oldValue); } }, methods: { setObj() { //更改属性值,是响应式的 this.obj.name = '李四' }, addAttr() { //添加属性,不是响应式的 this.obj.sex = '男' }, delAttr() { //删除属性,不是响应式的 delete this.obj.age; }, addAttr2(){ //方式一:添加单个属性,是响应式的 Vue.set(this.obj, 'school', '阳光小学'); //方式二:添加单个属性,是响应式的,this就是vm this.$set(this.obj, 'class', '五年级三班'); //方式三:添加多个属性,是响应式的 this.obj = Object.assign({}, this.obj,{a:1,b:2}) //方式四:删除属性,是响应式的,this就是vm this.$delete(this.obj, 'sex') } }, }); </script>
(二)数组添加和删除的响应式
-
对数组进行push()、pop()、shift()、unshift()、splice()、sort()、reverse()操作可以触发vue的响应式。(vue重写了这七个方法,实现响应式)
-
Vue.set(target,index, value), 对数组也管用。
-
Vue 不能检测以下数组的变动:
- 当你利用索引直接设置一个数组项时,例如:vm.items[indexOfItem] = newValue
- 当你修改数组的长度时,例如:vm.items.length = newLength
<div id="app"> <ul> <li v-for="item in arr"> {{item}} </li> <button @click="setArr">修改年龄</button> <button @click="addAttr">添加学校</button> <button @click="setLength">改变数组长度</button> <button @click="setAttr2">更改数组</button> <button @click="setLength2">改变数组长度</button> <button @click="arrPush">使用push</button> </ul> </div> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script> const vm = new Vue({ el: "#app", data: { arr:['张三','12岁'] }, methods: { setArr(){ //非响应式 this.arr[1] = '11岁'; }, addAttr(){ //非响应式 this.arr[2] = '阳光小学'; }, setLength(){ //非响应式 this.arr.length = 1; }, setAttr2(){ //响应式 //方式一:Vue.set(vm.items, indexOfItem, newValue) Vue.set(this.arr, 0, '李四'); //方式二: Array.prototype.splice //删除年龄,并用新年龄代替删除的 this.arr.splice(1,1,'13岁') //方式三:是方式一的另一种写法,this就是vm this.$set(this.arr, 2, '阳光小学') }, setLength2(){ this.arr.splice(1); }, arrPush(){ //响应式 this.arr.push('三班') } } }); </script>
(三)$nextTick
-
语法:this.$nextTick(回调函数)
-
作用:在下一次DOM更新结束后执行其指定的回调。
-
什么时候用:当改变数据后,要基于更新后的新DOM进行某些操作
-
Vue是异步渲染,data改变之后,DOM不会立刻渲染。
-
因为 $nextTick() 返回一个 Promise 对象,所以你可以使用 async/await 语法,参考:https://v2.cn.vuejs.org/v2/guide/reactivity.html
<div id="app"> <div id="example" ref="msg">{{message}}</div> <button @click="updateMessage">更新msg</button> </div> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script> const vm = new Vue({ el: "#app", data: { message: "未更新!", }, methods: { updateMessage() { this.message = '已更新' console.log(this.$el);//<div id="app">...</div> console.log(this.$refs.msg);//<div id="example">...</div> console.log('结果1', this.$refs.msg.textContent) // => '未更新' this.$nextTick(function () { //异步调用 console.log('结果2', this.$refs.msg.textContent) // => '已更新' }) console.log('结果3', this.$refs.msg.textContent) // => '未更新' } } }); </script>
五、监听属性 watch
https://cn.vuejs.org/v2/api/#vm-watch
(一)watch
-
Vue 提供了一种更通用的方式来观察和响应 Vue 实例上的数据变动:侦听属性。当你有一些数据需要随着其它数据(data中的属性和计算属性)变动而变动时,你很容易滥用 watch——特别是如果你之前使用过 AngularJS。然而,通常更好的做法是使用计算属性而不是命令式的 watch 回调。
-
注意:在变更 (不是替换) 对象或数组时,旧值将与新值相同,因为它们的引用指向同一个对象/数组。Vue 不会保留变更之前值的副本。
<div id="app"> <p>{{message}}</p> <p>所在城市:{{info.city}}, 温度:{{info.weather}}</p> <p>numbers.a:{{numbers.a}}</p> <button @click="setMessage">修改message</button> <button @click="setInfo">修改城市</button> <button @click="changeWeather()">修改天气</button> <button @click="numbersItemAAdd()">numbers.a+1</button> </div> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script> const vm = new Vue({ el: "#app", data: { message: "hello,world!", isFlag: true, info: { city: '北京' }, numbers: { a: 1, b: 2 } }, computed: { weather() { return this.isFlag ? '炎热' : '寒冷'; } }, methods: { setMessage() { this.message = '你好' }, setInfo() { this.info.city = '上海' }, numbersItemAAdd() { this.numbers.a++; }, changeWeather() { this.isFlag = !this.isFlag; } }, watch: { //方法名称要与data中的变量一致,方法有两个参数一个是新值,一个是旧值(newValue [, oldValue]) message(newValue, oldValue) { console.log(newValue); // 你好 console.log(oldValue); // hello,world! }, info: { //引用类型需要深度监听 handler(newVal, oldVal) { // 引用类型,拿不到oldVal,因为指针相同,此时已经指向了新的val console.dir(newVal); // {city:"上海"} console.dir(oldVal); // {city:"上海"} }, deep: true //深度监听,如果不添加则监听不到info的变化 }, weather(newVal, oldVal) { //监听计算属性 console.log('weather发生变化:', newVal, oldVal); }, "numbers.a"(newVal, oldVal) { //监听多层级结构中的某个属性变化 console.log('numbers.a发生变化:', newVal, oldVal); } } }); </script>
(二)handler方法和immediate属性
watch 的一个特点是,最初绑定的时候是不会执行的,要等到 firstName 改变时才执行监听计算。如果需要最初绑定的时候就执行该需要添加immediate
属性
watch: {
firstName: {
handler(newName, oldName) {
console.log(this.fullName = newName + ' ' + this.lastName);
},
// 代表在wacth里声明了firstName这个方法之后立即先去执行handler方法
immediate: true
}
}
(三) deep属性
为了发现对象
内部值的变化(默认只能监听一层),可以在选项参数中指定 deep: true。注意监听数组的变更不需要这么做。
(四) vm.$watch
使用实例的vm.$watch(监听的属性名,配置项)方法,也可以实现监听。
vm.$watch('message', {
//方法名称要与data中的变量一致,方法有两个参数一个是新值,一个是旧值(newValue [, oldValue])
handler(newValue, oldValue) {
console.log(newValue); // 你好
console.log(oldValue); // hello,world!
},
deep: true
})
vm.$watch('message', function(newValue, oldValue) {
console.log(newValue); // 你好
console.log(oldValue); // hello,world!
})
六、数据代理
数据代理:通过一个对象代理对另一个对象属性的操作
(一) Object.defineProperty()
var person = {
name: '张三',
age: 13,
}
var gender = '男';
Object.defineProperty(person, 'sex', {
// value: '男',
// enumerable: true, //是否可以枚举,默认为false
// writable: true, //是否可以修改,默认为false
// configurable: true, //是否可以删除,默认为false
get: function () { //读取person的sex属性时触发,get函数整体被称为(getter)
console.log('person的sex属性被读取');
return gender;
},
set: function (val) { //设置person的sex属性时触发,set函数整体被称为(setter)
console.log('person的sex属性被设置,设置的值为', val);
gender = val;
}
});
(二) Vue中的数据代理
vue通过Object.defineProperty()把data对象中的所有属性添加到vm实例上。
//vue通过使用vm._data代理data中的数据
const vm = new Vue({
el: "#app",
data: { //data对象
message: "hello,world!",
cn: "你好,世界!",
count: 100
}
});
(三)简单实现vue监听对象
- 简单实现vue监听对象数据(不能实现深度监听)
var data = { name: '张三', age: 15, sex: '男' } function Obsever(data) { var keys = Object.keys(data); keys.forEach((item) => { // this为Obsever实例 Object.defineProperty(this, item, { get() { console.log(item + '被读'); return data[item]; }, set(val) { console.log(item + '被写'); data[item] = val; } }) }); } var obs = new Obsever(data); var vm = {}; vm._data = data = obs; console.log(vm._data)