QX-AI初始化中...
暂无预设简介,请点击下方生成AI简介按钮。
Vue简介
Vue 是一套用于构建用户界面的渐进式 JavaScript 框架
构建用户界面: 在合适的时刻将数据应用到合适的位置
渐进式: Vue可以自底向上逐层的应用,即需要什么用什么,可以只用一个vue核心库,也可以装一堆插件库
Vue的特点:
- 组件化模式,提高代码复用率,让代码更好维护
- 声明式编码,无需直接操作DOM,提高开发效率
- 响应性,Vue 会自动跟踪 JavaScript 状态并在其发生变化时响应式地更新 DOM
- 使用虚拟DOM和优秀的Diff算法,尽量复用DOM节点
Vue2,API
体验Vue的方便:
命令式编码1 2 3 4 5
| let html = ''; data.forEach(item => { html += `<li>${item.id}-${item.name}</li>` }) document.getElementById('list').innerHTML = html;
|
声明式1 2 3 4 5
| <ul id="list"> <li v-for="item in data"> {{item.id}} - {{item.name}} </li> </ul>
|
下面的笔记是从Vue2开始的,直到Vue3
初次使用与理解
使用cdn或本地文件引入
1 2
| <script src="https://cdn.jsdelivr.net/npm/vue@2.7.14/dist/vue.js"></script> <script src="/js/vue.js"></script>
|
引入后,全局就多了一个名为 Vue 的构造函数,它接收一个配置对象参数
初次使用步骤:
- 创建vue实例
- 挂载到容器
- 插值语法应用数据
思想:将数据交给Vue实例动态地去使用,而不去直接操作DOM
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| <div id="root"> <h1>hello {{msg}}</h1> </div>
<script>
new Vue({ el: '#root', data: { msg: 'chuckle' } });
</script>
|
插值语法 {{ }}
中写的是js表达式(表达式即能生成一个值的代码),访问的数据以data为基准,当data中的数据发生改变,vue会自动重新解析模板更新数据
容器中的代码仍然符合html规范,vue会解析容器中的特殊语法(vue模板),然后应用数据和进行操作
注意:容器与实例是一一对应的,且不能嵌套容器,但一个容器和实例可以拆分成许多组件
模板语法
Vue有两种模板语法:
- 插值语法
{{ }}
- 指令语法
v-
插值语法用于标签体内容
指令语法用于解析标签(标签属性、标签体内容、绑定事件),数据都以data为基准
{{ }}
能看见(写) Vue 实例和原型上所有的属性
data数据对象中的键值对,最终都会通过数据代理作为 Vue 实例的属性
数据绑定
单向数据绑定 v-bind
,只能由data去影响页面,可以简写为冒号
双向数据绑定 v-model
,data和页面中数据变化都会互相影响
注意: v-model只能用于表单标签的value属性上,v-model:value= 可以简写为 v-model=
v-bind 案例:数据绑定标签属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| <div class="root">
<h1>hello {{msg}}</h1> <a :href="url">前往首页</a> </div> <script>
new Vue({ el: ".root", data: { msg: "chuckle", url: "https://www.qcqx.cn/" }, }); </script>
|
v-model案例:输入框内容变化会影响 data.msg 的值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <div class="root"> 单向数据绑定<input type="text" :value="msg"> <br /> 双向数据绑定<input type="text" v-model="msg"> </div> <script>
new Vue({ el: ".root", data: { msg: "chuckle" }, }); </script>
|
挂载和数据写法
挂载容器有两种写法:配置对象中的 el 属性和实例对象的 $mount()
方法
tip: Vue实例和原型上以 $ 开头的属性和方法都是提供给程序员使用的
第一种1 2 3
| new Vue({ el: ".root" });
|
第二种1 2
| const vm = new Vue({ }); vm.$mount('.root');
|
data数据也有两种写法:对象式和函数式
函数式返回一个数据对象,组件中需用函数式,函数式中的this是Vue实例对象。由Vue所管理的函数都不要写成箭头函数
对象式1 2 3 4 5
| new Vue({ data: { msg: "chuckle" } });
|
函数式1 2 3 4 5 6
| new Vue({ data(){ return{ msg: "chuckle" } } });
|
MVVM模型
Vue参考了MVVM模型
- M:模型(Model) - data 中的数据
- V:视图(View) - 模板
- VM:视图模型(ViewModel) - Vue 实例对象
Model 通过 VM 向 View 绑定数据,View 通过 VM 监听 Model 的数据变化
前端的框架大多都遵循这种模型,将数据放到指定位置,写好指定的模板代码,如何将数据和模板关联起来是框架的事,需要学习框架的语法
数据代理
数据代理:通过一个对象代理对另一个对象中属性的操作(读/写)
Vue2中使用 Object.defineProperty
实现数据代理,Vue3使用 Object.proxy()
以后再说
该方法可以向一个对象添加新属性或修改现有属性,且默认不可枚举、修改、删除
1 2 3 4 5 6 7 8
| const obj = { name: 'chuckle' } Object.defineProperty(obj, 'age', { value: 19, enumerable: true, writable: true, configurable: true })
|
当 num 发生修改,obj.age 获取的值也会发生改变,当修改 obj.age ,num也会被修改,实现了两个对象间数据的双向绑定
1 2 3 4 5 6 7 8 9 10 11 12
| const obj = { name: 'chuckle' } let num = 19; Object.defineProperty(obj, 'age', { get() { return num; } set(value) { num = value; } })
|
Vue中的数据代理:
配置对象 data 中的键值对都会通过数据代理给 vm 管理,当读取或修改属性时触发对应的 getter 和 setter
原来的data对象通过数据劫持变为 vm 上的 _data 属性,不直接赋值是为了实现响应式
触发 setter 后,_data里对应的属性也会发生改变,Vue监听_data有发生改变,让模板(页面)也发生改变
事件处理
v-on事件监听
使用 v-on
指令进行事件监听,可简写为@,@<事件名>="<回调函数名>"
,也可以写一些简单的语句
事件触发后的回调函数写在 methods 属性中,不做数据代理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| <div class="root"> <button v-on:click="fun">{{msg}}</button> <button @click="fun">{{msg}}</button> </div> <script> new Vue({ el: ".root", data: { msg: "点击", }, methods: { fun() { alert("chuckle"); }, }, }); </script>
|
v-on 可以传入参数,默认传入 event,后续传入参数会覆盖 event,但可以传入 $event 来获取事件对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <div class="root"> <button @click="fun('你好', $event)">{{msg}}</button> </div> <script> new Vue({ el: ".root", data: { msg: "点击", }, methods: { fun(str, e) { alert(`${str}chuckle`); console.log(e); }, }, }); </script>
|
事件修饰符
v-on 有事件修饰符。使用: @<事件名>.<修饰符>
修饰符可以连着写多个 @<事件名>.<修饰符>.<修饰符>
,分先后执行顺序
作用:为了让方法中数据逻辑更加的纯粹,而不用去处理 DOM 的逻辑细节
使用 prevent 修饰符代替 e.preventDefault() 阻止默认事件:
e.preventDefault()和prevent修饰符阻止默认事件1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| <div class="root"> <a href="/" @click.prevent="fun">{{msg}}</a> </div> <script> new Vue({ el: ".root", data: { msg: "点击", }, methods: { fun() { console.log('点击'); }, }, }); </script>
<div class="root"> <a href="/" @click="fun">{{msg}}</a> </div> <script> new Vue({ el: ".root", data: { msg: "点击", }, methods: { fun(e) { console.log('点击'); e.preventDefault(); }, }, }); </script>
|
常用的点击事件修饰符:
- prevent 阻止默认事件
- stop 阻止事件冒泡
- once 事件只触发一次
- capture 使用事件的捕获模式
- self 只有e.target是当前操作的元素才触发事件
- passive 事件的默认行为立即执行,无需等待回调函数执行完毕
鼠标修饰符:
- left 左键
- right 右键
- middle 中建
键盘事件
Vue中可以使用按键别名(修饰符)让特定按键去触发键盘事件,而无需使用e.key去判断
常用按键别名:
- 回车 enter
- 删除、退格 delete
- 退出 esc
- 空格 space
- 换行 tab(不适合keyup 适合keydown)
- 上 up 下 down 左 left 右 right
1 2 3 4 5 6 7 8 9 10 11 12 13
| <div class="root"> <input type="text" @keyup.enter="fun" placeholder="回车输入"></input> </div> <script> new Vue({ el: ".root", methods: { fun(e) { console.log(e.target.value); }, }, }); </script>
|
也可以通过 .键名 来绑定其它按键
双驼峰的键名需要写成小写再用横杆连接,如 CapsLock 要写成 caps-lock
1 2 3
| @keyup.a="fun" @keyup.caps-lock="fun" @keyup.Control="fun"
|
系统修饰键:ctrl、alt、shift、meta
(1) 配合kyup使用:按下修饰键的同时,再按下其他键,随后释放其他键,事件才被触发
(2) 配合kydown使用:正常触发
自定义键盘别名(不推荐使用):
1
| Vue.config.keyCodes.自定义键名=键码
|
技巧:利用keyup系统修饰键特性绑定组合键
1 2
| @keyup.ctrl.a="fun" @keyup.alt.q="fun"
|
计算属性computed
计算属性:通过已有的数据(属性)加工出一个新的属性
在 computed
配置项中定义计算属性,计算属性要写成对象形式,并添加getter和setter(非必须)
使用this访问vm上的属性
计算属性会在第一次被调用后被缓存,即getter只触发一次,后续使用该属性会从缓存中拿
当计算属性所依赖数据(在vm上的)发生变化时,会再次调用getter更新缓存
案例:通过姓和名计算出全名
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| <div class="root"> 姓:<input type="text" v-model="firstName"><br /> 名:<input type="text" v-model="lastName"><br /> 全名:<input type="text" v-model="fullName"><br /> </div> <script> new Vue({ el: ".root", data: { firstName: "张", lastName: "三" }, computed:{ fullName:{ get(){ return `${this.firstName}-${this.lastName}` }, set(name){ let arr = name.split("-"); this.firstName = arr[0]; this.lastName = arr[1]; } } } }); </script>
|
当计算属性只读不写时,可以简写,直接将计算属性值写成一个函数,作为getter
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| new Vue({ el: ".root", data: { firstName: "张", lastName: "三" },
computed:{ fullName(){ return `${this.firstName}-${this.lastName}` } } } });
|
当计算量非常大的时候,而且访问量次数非常多,改变的时机却很小,那就需要用到computed;缓存会减少很多计算量
computed 与 methods 区别:
- computed是属性访问,而methods是函数调用。computed带有缓存功能,而methods没有
- computed是响应式的,methods并非响应式
- computed是以对象的属性方式存在的,在视图层直接调用就可以得到值,如:
{{msg}}
,而methods必须以函数形式调用,例如:{{msg()}}
,computed直接以对象属性方式调用,而methods必须要函数执行才可以得到结果
- computed带缓存,只有依赖数据发生改变,才会重新进行计算,而methods里的函数在每次调用时都要执行
- computed不支持异步,当computed内有异步操作时无效,无法监听数据的变化
监视属性watch
watch 配置项中定义监视属性,监视某一属性的变化,监视属性本身也是一个配置项
可以监视data中的属性,也可以监视计算属性,若属性不存在也不会报错
作用:当属性值发生变化后进行某些操作
1 2 3 4 5
| watch: { <属性名>: { <监视配置项> } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| <div class="root"> <h2>今天天气: {{weather}}</h2> <button @click="changeWeather">切换天气</button> </div> <script> const vm = new Vue({ el: ".root", data: { isHot: true, }, computed: { weather(){ return this.isHot ? "炎热" : "凉爽" } }, methods: { changeWeather(){ this.isHot = !this.isHot; } }, watch: { weather: { immediate: true, handler(newValue, oldValue){ console.log(newValue, oldValue); } } } }); </script>
|
也可以通过vm的 $watch()
方法来监视某个属性
1 2 3 4 5 6 7 8 9 10
| vm.$watch("weather", { immediate: true, handler(newValue, oldValue) { console.log(newValue, oldValue); }, });
|
深度监视:
当监视多级结构中的某个属性时,属性名要写成字符串,或者 num[a]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| const vm = new Vue({ el: ".root", data: { num: { a: 1, b: 2 } }, watch: { 'num.a': { immediate: true, handler(newValue, oldValue){ console.log(newValue, oldValue); } } } });
|
监视整个多级结构所有属性时,要添加配置项 deep: true,handler参数是该num对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| const vm = new Vue({ el: ".root", data: { num: { a: 1, b: 2 } }, watch: { num: { deep: true, immediate: true, handler(newValue, oldValue){ console.log(newValue, oldValue); } } } });
|
当监视属性配置中只有 handler 方法时,可以简写
1 2 3 4 5 6 7 8 9 10
| watch: { weather(newValue, oldValue){ console.log(newValue, oldValue); } }
vm.$watch("weather", function(newValue, oldValue){ console.log(newValue, oldValue); });
|
注意:计算属性多个影响一个的时候用,监听属性一个影响多个或有复杂(异步)业务的时候用
绑定class样式
通过 v-bind 动态得将样式class应用到盒子上
每个标签只能有一个 class 属性,但vue会自动将绑定的 class 合并到现有的 class 属性上
有三种绑定形式:
- 字符串写法,适用于样式的类名不确定,需要动态指定
- 数组写法,适用于要绑定的样式个数不确定,名字也不确定
- 对象写法,适用于要绑定的样式个数、名字确定,但需要动态决定用不用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| .basic { border: 1px solid #222; margin: 20px; }
.big { width: 300px; height: 300px; } .small { width: 100px; height: 100px; } .normal { width: 200px; height: 200px; }
.fcolor { color: rgb(227, 57, 57); } .bg { background: #ccc; } .bdcolor { border-color: rgb(20, 153, 149); }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| <div id="root"> <div class="basic" :class="size" @click="changeSize">{{msg}}</div> <div class="basic normal" :class="classArr">{{msg}}</div> <div class="basic normal" :class="classObj">{{msg}}</div> </div> <script> let sizeArr = ["small", "normal", "big"]; new Vue({ el: "#root", data: { msg: "chuckle", size: "normal", classArr: ["fcolor", "bg", "bdcolor"], classObj: { fcolor: false, bg: true, bdcolor: true, }, }, methods: { changeSize() { this.size = sizeArr[Math.floor(Math.random() * sizeArr.length)]; }, }, }); </script>
|
绑定style行内样式
两种写法:对象写法、数组写法。对象中属性名单驼峰
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| <div id="root"> <div class="basic" :style="styleObj">{{msg}}</div> <div class="basic" :style="styleArr">{{msg}}</div> </div> <script> new Vue({ el: "#root", data: { msg: "chuckle", styleObj: { fontSize: '40px', color: '#363636', background: '#ccc', }, styleArr: [ { fontSize: '40px', }, { background: '#ccc', } ] } }); </script>
|
条件渲染v-if
条件渲染,符合某些条件才渲染元素,即通过指令去控制元素的显隐,不同指令实现原理不同
v-show="<表达式>"
表达式结果需能表示为布尔值
原理:使用 display:'none'
隐藏元素,节点仍在
v-if="<表达式>"
表达式结果需能表示为布尔值
原理:在模板解析时不渲染该元素,节点不存在,会用一个空注释占位
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <div id="root"> <div v-show="isShow">{{msg}}</div> <div v-if="isShow">{{msg}}</div> </div> <script> new Vue({ el: "#root", data: { msg: "chuckle", isShow: false, }, }); </script>
|
当显隐切换频率高使用 v-show 性能更好
v-else-if
和v-else
需配合 v-if 使用,效果和js中 if-else 一样
使用这两个指令需要div结构相邻
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| <div id="root"> <div v-if="n === 1">{{n}}</div> <div v-else-if="n === 1">重复{{n}}</div> <div v-else-if="n === 2">{{n}}</div> <div v-else-if="n === 3">{{n}}</div> <div v-else>{{n}}</div> </div> <script> new Vue({ el: "#root", data: { msg: "chuckle", n: 1 }, }); </script>
|
当多个标签显隐条件一样时,可以使用 <template>
标签包裹,在该标签中写 v-if ,实际html结构中不会存在该标签
1 2 3 4 5
| <template v-if="n===1"> <h1>{{msg}}</h1> <h2>{{msg}}</h2> <h3>{{msg}}</h3> </template>
|
列表渲染v-for
v-for
将数组或对象中一系列重复、相似的数据渲染到页面中,这些数据应该要有唯一标识(通常是id)
使用方法类似js中的 for-in
使用 v-for 遍历渲染需要在标签内绑定上 key 属性作为唯一标识
用法1 2 3 4 5
| <ul> <li v-for="(p,index) in persons" :key="p.id">{{index}} - {{p.name}} - {{p.age}}</li> </ul>
|
遍历数组:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| <div id="root"> <ul> <li v-for="(p,index) in persons" :key="p.id">{{index}} - {{p.name}} - {{p.age}}</li> </ul> </div> <script> new Vue({ el: "#root", data: { msg: "chuckle", persons: [ { id: '001', name: 'chuckle', age:'19'}, { id: '002', name: 'qx', age:'18'}, { id: '003', name: 'giggles', age:'20'}, ], }, }); </script>
|
遍历对象:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| <div id="root"> <ul> <li v-for="(p,key) in commodity" :key="key">{{key}} - {{p}}</li> </ul> </div> <script> new Vue({ el: "#root", data: { msg: "chuckle", commodity: { name: '商品1', price: 20, place: '中国' } }, }); </script>
|
key
v-for 遍历渲染时需要给每个标签绑定 key 属性,作为唯一标识
key 在 vue 内部虚拟DOM上使用,解析模板时不会渲染到页面上
作用:vue在解析模板到真实DOM上时,会先将数据转为虚拟DOM,当数据发生变化,又会再生成一次虚拟DOM,然后会用diff算法对比新旧两份虚拟DOM,对比过程依赖 key,将key相同的标签内所有属性和节点单独进行对比,将相同的节点进行dom复用,不同的则新覆盖旧,新虚拟DOM中没有的key则删除对应标签
diff算法: 对比两个新旧虚拟dom(两棵树)之间的差异的一种算法
选择key:
使用后端维护的唯一标识,id、手机号、学号等
若不手动绑定key,vue会自动将index作为key
一般不使用 index 作为key,因为index不与数据一一对应,会导致diff算法对比时发生问题(逆序操作时无法正常复用dom、虚拟dom中不存在的用户输入[input]会发生错位)
如果不存在对数据的逆序添加、逆序明除等破坏顺序操作,仅用于渲染列表用于展示,可以使用index作为key
列表过滤
业务中经常需要对原数据进行搜索过滤后再展示,可以使用计算属性实现
若业务较复杂也可使用监视属性实现,但应优先考虑计算属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| <div id="root"> <input type="text" placeholder="模糊搜索姓名" v-model="keyWord"> <ul> <li v-for="(p,index) in fillPersons" :key="p.id"> {{index+1}} - {{p.name}} - {{p.age}} - {{p.sex}} </li> </ul> </div> <script> new Vue({ el: "#root", data: { msg: "chuckle", keyWord: "", persons: [ { id: "001", name: "张三", age: "19", sex: "男" }, { id: "002", name: "李三", age: "18", sex: "女" }, { id: "003", name: "李四", age: "20", sex: "女" }, { id: "004", name: "刘四", age: "18", sex: "男" }, ] }, computed: { fillPersons: { get(){ return this.persons.filter((item)=>{ return item.name.indexOf(this.keyWord) !== -1; }) } } } }); </script>
|
列表排序
实际业务中多是后端排好序,但前端排序有时也会用到
列表排序和列表过滤通常一起用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| <div id="root"> <input type="text" placeholder="模糊搜索姓名" v-model="keyWord" /> <br /> <button @click="sortType=1">年龄升序</button> <button @click="sortType=-1">年龄降序</button> <button @click="sortType=0">原序</button> <ul> <li v-for="(p,index) in fillPersons" :key="p.id"> {{index+1}} - {{p.name}} - {{p.age}} - {{p.sex}} </li> </ul> </div> <script> new Vue({ el: "#root", data: { msg: "chuckle", sortType: 0, keyWord: "", persons: [ { id: "001", name: "张三", age: 19, sex: "男" }, { id: "002", name: "李三", age: 18, sex: "女" }, { id: "003", name: "李四", age: 21, sex: "女" }, { id: "004", name: "刘四", age: 20, sex: "男" }, ], }, computed: { fillPersons: { get() { let arr = this.persons.filter((item) => { return item.name.indexOf(this.keyWord) !== -1; }); if (this.sortType) arr.sort((a, b) => this.sortType * (a.age - b.age)); return arr; }, }, }, }); </script>
|
监视数据(响应式)原理
Vue 会监视 data 中所有层次的数据。当数据发生改变,会重新解析模板,也就是数据的响应式
这种监视是通过每个属性对应的 setter 实现的,在 setter 中对数据进行实际的修改、重新解析模板等操作
数据劫持
对 data 中的数据进行加工,添加 getter、setter 以实现某些交互功能(响应式) 就是数据劫持
与数据代理的区别:数据代理是通过一个对象代理对另一个对象中属性的操作(读/写),而数据劫持是对一个对象中原有的属性进行加工
利用数据劫持简单复刻一下 Vue 的响应式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| let data = { name: 'chuckle', age: 19 }
const obs = new Observer(data)
let vm = {}
vm._data = data = obs
function Observer(obj) { const keys = Object.keys(obj) keys.forEach((k)=>{ Object.defineProperty(this,k,{ get(){ return obj[k] }, set(val){ obj[k] = val parsingTemplates() } }) }) }
function parsingTemplates(){ console.log('重新解析模板'); }
|
输出 vm._data 对象:
1 2 3 4 5 6 7 8 9
| Observer {} age: (…) name: (…) get age: ƒ get() set age: ƒ set(val) get name: ƒ get() set name: ƒ set(val) [[Prototype]]: Object
|
实现效果:当修改 vm._data 中的数据,控制台会输出【重新解析模板】
发生了什么:通过数据代理将 data 中数据的读和写操作代理到了监视实例对象 obs 身上,并且在 setter 中调用解析模板的函数,这是实现响应式的关键,然后将 obs 引用地址赋值给 vm._data,上面这些步骤就实现了数据劫持,即 Vue 中并不是直接将 data 赋值给 vm._data,而是先劫持 data,对 data 进行加工后,将加工好的 obs 赋给 vm._data
后面就是正常的数据代理操作,将 vm._data 通过数据代理到 vm 身上,方便操作
注意:Vue中的响应式实现要比这完善得多
- 没有考虑data数据是多层次情况,Vue会找到data中所有层次的对象的属性进行加工
- 数组中元素的响应式
$set()追加属性
通过之前的操作 Vue 可以响应式地监视 data 数据
但如果不是编写代码时就写在 data 中的数据,而是之后通过 . 动态添加的属性,Vue则无法监视,修改这些属性值,模板不会重新解析,页面不会变化
可以通过 Vue.set()
或 vm.$set()
动态添加可被 Vue 监视的属性
传入三个参数 (<目标对象>, <属性名>, <属性值>)
作用:向响应式对象中添加一个 property(属性),并让这个新 property 同样是响应式的,且触发视图更新。
注意:目标对象不能是data或vm实例,只能是其中的某个实际存在的对象,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| const vm = new Vue({ el: '#root', data: { student: {} } })
Vue.set(vm.student, 'name', 'chuckle')
vm.$set(vm.student, 'name', 'qx')
vm.$set(vm, 'name', 'qx') vm.$set(vm._data, 'name', 'qx')
vm._data.student.name = 'chuckle' vm.student.name = 'chuckle'
|
对数组的监视
JS中数组也是一个对象,但它不能添加getter和setter,也就是不能通过数据劫持来实现数组中元素的响应式,无法触发视图更新
Vue 无法在元素被通过索引值修改后,响应式地重新解析模板,触发视图更新
1 2 3 4 5 6
| const vm = new Vue({ el: '#root', data: { student: ['001', 'chuckle', '19'] } })
vm.student[0] = '002';
|
Vue对数组元素的监视:将被监视的数组的变更方法进行了包装,调用这些变更方法就会触发视图更新。
变更方法:push()、pop()、shift()、unshift()、splice()、sort()、reverse()
包装:在Vue中的数组上重写同名的变更方法,在重写的方法中也会调用原来 Array 上的变更方法,但还加入了视图更新等操作
也可以通过 $set()
在变更数组时也触发视图更新:
1 2
| vm.$set(vm.student, 1, 'qx')
|
收集表单数据
v-model 对不同表单元素有特殊的处理
- 若 < input type=’text’/> 文本输入框 v-model 收集 value 值,即收集用户在输入框的输入
- 若 < input type=’radio’/> 单选框 v-model 收集 value 值,但要给标签配置 value 值
- 若 < input type=’checkbox’/> 多选框
- 没有配置value值,则默认收集checked,一个被勾选会导致全部被勾选
- 配置value值后,v-model初始值为数组,收集的是value值作为元素到数组中,初始值为空字符串,则收集checked
v-model 也有修饰符,对收集的数据进行简单处理
- lazy 失去焦点再收集数据
- number 输入字符串转为有效数字,通常和 < input type=’number’/> 一起使用
- trim 去除字符串首尾空格
案例:用户信息收集1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
| <div id="root"> <form @submit.prevent="submit"> 用户名: <input type="text" v-model.trim="formInfo.username" /><br /> 密码: <input type="password" v-model="formInfo.password" /><br /> 年龄: <input type="number" v-model.number="formInfo.age" /><br /> 性别: <label for="man">男</label> <input type="radio" v-model="formInfo.sex" name="sex" id="man" value="男"/> <label for="woman">女</label> <input type="radio" v-model="formInfo.sex" name="sex" id="woman" value="女"/><br /> 爱好: <label for="hobbyOne">吃饭</label> <input type="checkbox" v-model="formInfo.hobby" name="hobby" id="hobbyOne" value="吃饭"/> <label for="hobbyTwo">睡觉</label> <input type="checkbox" v-model="formInfo.hobby" name="hobby" id="hobbyTwo" value="睡觉"/> <label for="hobbyThree">打豆豆</label> <input type="checkbox" v-model="formInfo.hobby" name="hobby" id="hobbyThree" value="打豆豆"/><br /> 城市: <select v-model="formInfo.city"> <option value="北京">北京</option> <option value="上海">上海</option> <option value="广州">广州</option> </select><br /> 其他信息: <textarea v-model.lazy="formInfo.other"></textarea><br /> <input type="checkbox" v-model="formInfo.agree" name="agree"/>接受用户协议<br /> <button>提交</button> </form> </div>
<script> new Vue({ el: "#root", data: { formInfo: { username: "", password: "", age: "", sex: "", hobby: [], city: "", other: "", agree: false, }, }, methods: { submit() { console.log(JSON.stringify(this.formInfo)); }, }, }); </script>
|
过滤器
Vue3中已废弃过滤器
在 filters 中配置局部过滤器,Vue.filter()
中配置全局过滤器
和计算属性一样,过滤器也是对现有数据进行加工,本质上是函数
使用: {{ 数据 | 过滤器1 | 过滤器2 }}
管道符分隔,会依次传参调用,也可以在指令语法中使用,前一个的结果作为后面过滤器的第一个参数,返回这作为结果,还可以再传额外的参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| <div id="root"> <h3>时间:{{ time | fmtTime | interceptStr }}</h3> </div> <script> Vue.filter('interceptStr',(value, num=4)=>{ return value.slice(0,num) }) new Vue({ el: '#root', data: { time: 1683449879865, }, filters: { fmtTime(value, str='YYYY-MM-DD HH:mm:ss'){ return dayjs(value).format(str); } } }) </script>
|
常见内置指令
前面已经学习了不少内置指令,v-on、v-bind、v-model、v-show、v-if、v-for等
Vue中还有一些常见的内置指令:
v-text
innerText同款效果
v-html
innerHtml同款效果,需注意安全性问题xss攻击
v-clock
当Vue接管容器时,删除所有v-clock属性,配合css属性选择器实现隐藏未编译的 Mustache 标签直到实例准备完毕,或加载动画1
| [v-cloak] { display: none; }
|
v-once
只渲染元素和组件一次,后续重新解析模板渲染页面会直接复用带有v-once的标签,视为静态内容,这可以用于优化更新性能
v-pre
跳过这个元素和它的子元素的编译过程,用于跳过没有指令和插值语法的节点,能加快编译。
自定义指令
指令通过操控dom来控制交互或样式,如 v-show 通过操控css display 属性,来控制元素显隐
Vue的指令就是将原生操控dom的JS代码进行了封装
自定义指令需要亲自去写操控 dom 的JS代码
注册指令:
自定义指令函数式传入两个参数
- el 指令所在的标签dom元素
- binding 一个对象,包含绑定相关的信息,其中 value 属性值是表达式的值
分为全局指令和局部指令:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| Vue.directive('<指令名>', function(el, binding){ el.innerHTML = binding.value })
new Vue({ el: '#root', data: { }, directives: { '<指令名>'(el, binding){ el.innerHTML = binding.value } } })
|
指令名的问题:
指令名不应该写成驼峰形式,要用横杠形式。若指令名为单驼峰形式,使用指令时会找不到指令
1 2 3 4 5
| directives: { 'num-tenfold'(el, binding){ } }
|
注意:指令中的this指向window
指令内容写法
自定义指令的内容有两种写法:对象式、函数式
1、对象式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| <div id="root"> <h3>n值:{{n}}</h3> <h3> 放大十倍n值: <span v-tenfold="n"></span> </h3> <button @click="n++">让n+1</button> </div> <script> new Vue({ el: '#root', data: { n: 0 }, directives: { tenfold: { bind(el, binding){ el.innerHTML = binding.value * 10; }, inserted(el, binding){ el.parentNode.style.background = "#ccc"; }, update(el, binding) { el.innerHTML = binding.value * 10; }, } } }) </script>
|
对象式指令被调用:
- 指令与元素成功绑定(还未放到页面上)时,调用对象中
bind()
函数
- 指令所在元素被插入页面上时,调用对象中
inserted()
函数
- 指令所在的模板被重新解析时,调用对象中
update()
函数
Vue在不同时刻会调用指令对象中不同的函数
2、函数式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| <div id="root"> <h3>n值:{{n}}</h3> <h3> 放大十倍n值: <span v-tenfold="n"></span> </h3> <button @click="n++">让n+1</button> </div> <script> new Vue({ el: '#root', data: { n: 0 }, directives: { tenfold(el, binding){ el.innerHTML = binding.value * 10; el.parentNode.style.background = "#ccc"; } } }) </script>
|
函数式将对象式的 bind()
与 update()
函数合二为一,Vue2中没有合并上 inserted()
函数的效果,即Vue2中函数式里一般不能进行dom相关操作
函数式指令被调用:
- 指令与元素成功绑定(还未放到页面上)时
- 指令所在的模板被重新解析时