51工具盒子

依楼听风雨
笑看云卷云舒,淡观潮起潮落

Vue笔记[一]-初识

QX-AI
GPT-4
QX-AI初始化中...
暂无预设简介,请点击下方生成AI简介按钮。
介绍自己
生成预设简介
推荐相关文章
生成AI简介

Vue简介 {#Vue简介}

Vue 是一套用于构建用户界面渐进式 JavaScript 框架

构建用户界面: 在合适的时刻将数据 应用到合适的位置
渐进式: Vue可以自底向上逐层的应用,即需要什么用什么,可以只用一个vue核心库,也可以装一堆插件库

Vue的特点:

  1. 组件化模式,提高代码复用率,让代码更好维护
  2. 声明式编码,无需直接操作DOM,提高开发效率
  3. 响应性,Vue 会自动跟踪 JavaScript 状态并在其发生变化时响应式地更新 DOM
  4. 使用虚拟DOM 和优秀的Diff算法,尽量复用DOM节点

Vue2API

体验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构造函数,它接收一个配置对象参数

初次使用步骤:

  1. 创建vue实例
  2. 挂载到容器
  3. 插值语法应用数据

**思想:**将数据交给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"> <!-- vue的插值语法 --> <h1>hello {{msg}}</h1> </div> <script> // 创建vue实例 new Vue({ el: '#root', // element元素,挂载到哪个容器 // 数据对象,只能给该容器使用 data: { msg: 'chuckle' } }); // 效果 // hello chuckle </script> |

插值语法 {{ }} 中写的是js表达式(表达式即能生成一个值的代码),访问的数据以data为基准,当data中的数据发生改变,vue会自动重新解析模板更新数据

容器中的代码仍然符合html规范,vue会解析容器中的特殊语法(vue模板),然后应用数据和进行操作

注意: 容器与实例是一一对应 的,且不能嵌套容器,但一个容器和实例可以拆分成许多组件

模板语法 {#模板语法}

Vue有两种模板语法:

  1. 插值 语法 {{ }}
  2. 指令 语法 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"> <!-- vue的插值语法 --> <h1>hello {{msg}}</h1> <!-- v-bind简写为冒号 --> <a :href="url">前往首页</a> </div> <script> // 创建vue实例 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> // 创建vue实例 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: function(),在对象属性值中写函数,一般删掉冒号和function data(){ return{ msg: "chuckle" } } }); |

MVVM模型 {#MVVM模型}

Vue参考了MVVM模型

  1. **M:**模型(Model) - data 中的数据
  2. **V:**视图(View) - 模板
  3. **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, // 使新添加的属性可被枚举,默认false writable: true, // 控制属性是否能被修改,默认false configurable: true // 控制属性是否能被删除,默认false }) |

当 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', { // 当读取obj的age属性时,getter就会被调用,返回age的值 get() { return num; } // 当修改obj的age属性时,setter就会被调用 set(value) { num = value; } }) |

Vue中的数据代理:

配置对象 data 中的键值对都会通过数据代理给 vm 管理,当读取或修改属性时触发对应的 getter 和 setter

原来的data对象通过数据劫持变为 vm 上的 _data 属性,不直接赋值是为了实现响应式

触发 setter 后,_data里对应的属性也会发生改变,Vue监听_data有发生改变,让模板(页面)也发生改变

事件处理 {#事件处理}

v-on事件监听 {#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> <!-- v-on:可以简写为@ --> <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 | <!-- prevent修饰符 --> <div class="root"> <a href="/" @click.prevent="fun">{{msg}}</a> </div> <script> new Vue({ el: ".root", data: { msg: "点击", }, methods: { fun() { console.log('点击'); }, }, }); </script> <!-- e.preventDefault() --> <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> |

常用的点击事件修饰符:

  1. prevent 阻止默认事件
  2. stop 阻止事件冒泡
  3. once 事件只触发一次
  4. capture 使用事件的捕获模式
  5. self 只有e.target是当前操作的元素才触发事件
  6. passive 事件的默认行为立即执行,无需等待回调函数执行完毕

鼠标修饰符:

  1. left 左键
  2. right 右键
  3. middle 中建

键盘事件 {#键盘事件}

Vue中可以使用按键别名(修饰符)让特定按键去触发键盘事件,而无需使用e.key去判断

常用按键别名:

  1. 回车 enter
  2. 删除、退格 delete
  3. 退出 esc
  4. 空格 space
  5. 换行 tab(不适合keyup 适合keydown)
  6. 上 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" // 绑定字母A键 @keyup.caps-lock="fun" // 绑定切换大小写键 @keyup.Control="fun" // 绑定ctrl键 |

系统修饰键: ctrl、alt、shift、meta
(1) 配合kyup使用:按下修饰键的同时,再按下其他键,随后释放其他键,事件才被触发
(2) 配合kydown使用:正常触发

自定义键盘别名(不推荐使用):

|-----------|--------------------------------------| | 1 | Vue.config.keyCodes.自定义键名=键码 |

技巧:利用keyup系统修饰键特性绑定组合键

|-------------|--------------------------------------------------------------------------------------------| | 1 2 | @keyup.ctrl.a="fun" // 绑定ctrl+a,先按下ctrl再按下a,然后释放a才触发 @keyup.alt.q="fun" // 绑定alt+q |

计算属性computed {#计算属性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;缓存会减少很多计算量

computedmethods 区别:

  1. computed是属性访问,而methods是函数调用。computed带有缓存功能,而methods没有
  2. computed是响应式的,methods并非响应式
  3. computed是以对象的属性方式存在的,在视图层直接调用就可以得到值,如:{{msg}} ,而methods必须以函数形式调用,例如:{{msg()}} ,computed直接以对象属性方式调用,而methods必须要函数执行才可以得到结果
  4. computed带缓存,只有依赖数据发生改变,才会重新进行计算,而methods里的函数在每次调用时都要执行
  5. computed不支持异步,当computed内有异步操作时无效,无法监听数据的变化

监视属性watch {#监视属性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: { // 初始化时调用一次handler immediate: true, // 当监视属性发生变化时触发handler handler(newValue, oldValue){ // 传入属性新老值两个参数 console.log(newValue, oldValue); } } } }); </script> |

也可以通过vm的 $watch() 方法来监视某个属性

|------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 1 2 3 4 5 6 7 8 9 10 | vm.$watch("weather", { // 初始化时调用一次handler immediate: true, // 当监视属性发生变化时触发handler 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': { // 初始化时调用一次handler immediate: true, // 当监视属性发生变化时触发handler 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: { // 监视整个num和num里的属性 deep: true, // 初始化时调用一次handler immediate: true, // 当监视属性发生变化时触发handler 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); } } // 不再传一个配置对象,直接传一个函数作为handler vm.$watch("weather", function(newValue, oldValue){ console.log(newValue, oldValue); }); |

**注意:**计算属性多个影响一个的时候用,监听属性一个影响多个或有复杂(异步)业务的时候用

绑定class样式 {#绑定class样式}

通过 v-bind 动态得将样式class应用到盒子上

每个标签只能有一个 class 属性,但vue会自动将绑定的 class 合并到现有的 class 属性上

有三种绑定形式:

  1. 字符串写法,适用于样式的类名不确定,需要动态指定
  2. 数组写法,适用于要绑定的样式个数不确定,名字也不确定
  3. 对象写法,适用于要绑定的样式个数、名字确定,但需要动态决定用不用

|------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 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: { // true则应用样式,false不应用 fcolor: false, bg: true, bdcolor: true, }, }, methods: { //点击随机切换大小 changeSize() { this.size = sizeArr[Math.floor(Math.random() * sizeArr.length)]; }, }, }); </script> |

绑定style行内样式 {#绑定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-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"> <!-- v-show --> <div v-show="isShow">{{msg}}</div> <!-- v-if --> <div v-if="isShow">{{msg}}</div> </div> <script> new Vue({ el: "#root", data: { msg: "chuckle", isShow: false, }, }); </script> |

当显隐切换频率高使用 v-show 性能更好

v-else-ifv-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"> <!-- v-else-if --> <div v-if="n === 1">{{n}}</div> <!-- 当if匹配到一个就不会再往后判断 --> <div v-else-if="n === 1">重复{{n}}</div> <div v-else-if="n === 2">{{n}}</div> <div v-else-if="n === 3">{{n}}</div> <!-- v-else不用写表达式 --> <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}

v-for 将数组或对象中一系列重复、相似的数据渲染到页面中,这些数据应该要有唯一标识(通常是id)

使用方法类似js中的 for-in

使用 v-for 遍历渲染需要在标签内绑定上 key 属性作为唯一标识 用法

|-------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 1 2 3 4 5 | <ul> <!-- 参数1 p是persons中的元素,参数2 index/key是索引,遍历数组时是从0开始的数组下标,遍历对象时是每个属性名--> <!-- key标签属性是唯一标识 --> <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 {#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", // 控制排序类型,0原序1升-1降 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 | // data数据 let data = { name: 'chuckle', age: 19 } // 创建监视实例对象,监视data中数据变化 const obs = new Observer(data) // vue实例对象 let vm = {} // 将obs引用地址赋给vm._data和data // 此刻就完成了数据劫持,原来的data的地址只有当时传入Observer的参数obj才知道 // data只是地址发生了改变,但原来的数据仍然在一个地址中,被obs所管理 vm._data = data = obs // 监视构造函数,能创建监视实例对象 // 传入要监视的对象 function Observer(obj) { // 拿到对象后提取所有属性名到一个数组中 const keys = Object.keys(obj) // 遍历keys,加工属性 keys.forEach((k)=>{ // 和数据代理一样,用到defineProperty // 构造函数中的this是其实例对象 // 这一步相当于将data上的数据代理到实例对象上 Object.defineProperty(this,k,{ // 添加getter、setter。实现属性的读写 get(){ return obj[k] }, set(val){ obj[k] = val // 修改完值后,就可以去重新解析模板 parsingTemplates() } }) }) } // vue重新解析模板的函数实现,怎么实现的暂时不研究 function parsingTemplates(){ console.log('重新解析模板'); } |

输出 vm._data 对象:

|---------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------| | 1 2 3 4 5 6 7 8 9 | Observer {} age: (...) // 19 name: (...) // 'qx' 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中的响应式实现要比这完善得多

  1. 没有考虑data数据是多层次情况,Vue会找到data中所有层次的对象的属性进行加工
  2. 数组中元素的响应式

$set()追加属性 {#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') // $set()还能用于修改属性值 // 一般是在修改数组元素后为了能被Vue监视到这次修改而使用 vm.$set(vm.student, 'name', 'qx') // 不能直接在data或vm实例上添加属性 vm.$set(vm, 'name', 'qx') vm.$set(vm._data, 'name', 'qx') // 直接添加不会被Vue监视,无法响应式 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'] } }) // 修改student数组中第一个元素,不会触发视图更新 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 对不同表单元素有特殊的处理

  1. < input type='text'/> 文本输入框 v-model 收集 value 值,即收集用户在输入框的输入
  2. < input type='radio'/> 单选框 v-model 收集 value 值,但要给标签配置 value 值
  3. < input type='checkbox'/> 多选框
    1. 没有配置value值,则默认收集checked,一个被勾选会导致全部被勾选
    2. 配置value值后,v-model初始值为数组,收集的是value值作为元素到数组中,初始值为空字符串,则收集checked

v-model 也有修饰符,对收集的数据进行简单处理

  1. lazy 失去焦点再收集数据
  2. number 输入字符串转为有效数字,通常和 < input type='number'/> 一起使用
  3. 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"> <!-- 绑定提交事件,后续发送ajax请求 --> <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() { // 输出收集的表单数据并转为json格式 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中还有一些常见的内置指令:

  1. v-text innerText同款效果

  2. v-html innerHtml同款效果,需注意安全性问题xss攻击

  3. v-clock 当Vue接管容器时,删除所有v-clock属性,配合css属性选择器实现隐藏未编译的 Mustache 标签直到实例准备完毕,或加载动画

    |-----------|--------------------------------------| | 1 | [v-cloak] { display: none; } |

  4. v-once 只渲染元素和组件一次,后续重新解析模板渲染页面会直接复用带有v-once的标签,视为静态内容,这可以用于优化更新性能

  5. v-pre 跳过这个元素和它的子元素的编译过程,用于跳过没有指令和插值语法的节点,能加快编译。

自定义指令 {#自定义指令}

指令通过操控dom来控制交互或样式,如 v-show 通过操控css display 属性,来控制元素显隐

Vue的指令就是将原生操控dom的JS代码进行了封装

自定义指令需要亲自去写操控 dom 的JS代码

注册指令:
自定义指令函数式传入两个参数

  1. el 指令所在的标签dom元素
  2. 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){ } } // 使用指令: // <span v-num-tenfold="n"></span> |

**注意:**指令中的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值: <!-- v-tenfold让n值放大10倍再应用到标签内容上 --> <span v-tenfold="n"></span> </h3> <button @click="n++">让n+1</button> </div> <script> new Vue({ el: '#root', data: { n: 0 }, // 注册局部指令 directives: { // 指令名不用带v- tenfold: { // 指令与元素成功绑定(还未放到页面上)时调用 bind(el, binding){ el.innerHTML = binding.value * 10; }, // 元素应用到页面后,进行dom相关操作 inserted(el, binding){ // 让其父元素背景变灰色 el.parentNode.style.background = "#ccc"; }, // 指令所在的模板被重新解析时调用 update(el, binding) { el.innerHTML = binding.value * 10; }, } } }) </script> |

对象式指令被调用:

  1. 指令与元素成功绑定(还未放到页面上)时,调用对象中 bind() 函数
  2. 指令所在元素被插入页面上时,调用对象中 inserted() 函数
  3. 指令所在的模板被重新解析时,调用对象中 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值: <!-- v-tenfold让n值放大10倍再应用到标签内容上 --> <span v-tenfold="n"></span> </h3> <button @click="n++">让n+1</button> </div> <script> new Vue({ el: '#root', data: { n: 0 }, // 注册局部指令 directives: { // 指令名不用带v- tenfold(el, binding){ el.innerHTML = binding.value * 10; // 因为没有inserted状态,且在bind状态时获取不到dom元素 // 父元素一开始并不会应用此灰色背景 // 后续重新解析模板时就有效果,因为元素已被应用可获取到父元素 el.parentNode.style.background = "#ccc"; } } }) </script> |

函数式将对象式的 bind()update() 函数合二为一,Vue2中没有合并上 inserted() 函数的效果,即Vue2中函数式里一般不能进行dom相关操作

函数式指令被调用:

  1. 指令与元素成功绑定(还未放到页面上)时
  2. 指令所在的模板被重新解析时
赞(1)
未经允许不得转载:工具盒子 » Vue笔记[一]-初识