# Vue 的笔记记录 ----- 万字基础篇
![]()
# 指令
指令是 Directives, 是模板语法,用于辅助开发渲染页面的基本结构
# 1. 内容渲染指令
v-text
1 2 3 4 5 6 7 8 9 10 11
| <p v-text='username'></p> const vm=new Vue({ el:'app', data:{ username:'zhangsan' } })
|
缺点:会把标签内原有的内容覆盖调
插值语法,用来解决 text 覆盖原有内容问题,除了可以插值还可以进行一些 js 里面的运算
1 2 3 4 5 6 7 8 9 10 11
| <p >{{username}}</p> const vm=new Vue({ el:'app', data:{ username:'zhangsan' } })
|
v-html 可以渲染带标签的字符串到页面中
# 2. 属性绑定指令
v-bind😕: 为元素属性动态绑定值还可以进行一些 js 里面的运算
1 2 3 4 5 6 7 8 9 10 11 12
| input :placeholder="a+b"/"'gh'+b"/"username" const vm=new Vue({ el:'app', data:{ username:'zhangsan', a:1, b:2, } })
|
# 3. 事件绑定指令
v-on 事件绑定指令简写 @click
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| <button v-on:click="add">提交</button> el:'#app' data:{ }, methods:{ add(){ cl(1) } }
|
$.event 原生的 dom 对象 e
1
| <button @click="add(0,$event)">
|
# 事件修饰符
.prevent 消除默认事件 @click.prevent
事件修饰符 | 说明 |
---|
.prevent | 阻止默认行为 |
.stop | 阻止事件冒泡 |
.capture | 以捕获模式触发当前的事件处理函数 |
.once | 绑定的事件只触发一次 |
.self | 只有在 event.target 是当前元素的自身触发事件处理函数 |
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
| <!-- 单击事件将停止传递 --> <a @click.stop="doThis"></a> <!-- 提交事件将不再重新加载页面 --> <form @submit.prevent="onSubmit"></form> <!-- 修饰语可以使用链式书写 --> <a @click.stop.prevent="doThat"></a> <!-- 也可以只有修饰符 --> <form @submit.prevent></form> <!-- 仅当 event.target 是元素本身时才会触发事件处理器 --> <!-- 例如:事件处理器不来自子元素 --> <div @click.self="doThat">...</div>
<!-- 添加事件监听器时,使用 `capture` 捕获模式 --> <!-- 例如:指向内部元素的事件,在被内部元素处理前,先被外部处理 --> <div @click.capture="doThis">...</div> <!-- 点击事件最多被触发一次 --> <a @click.once="doThis"></a> <!-- 滚动事件的默认行为 (scrolling) 将立即发生而非等待 `onScroll` 完成 --> <!-- 以防其中包含 `event.preventDefault()` --> <div @scroll.passive="onScroll">...</div>
|
.passive
修饰符一般用于触摸事件的监听器,可以用来改善移动端的滚屏性能
# 按键修饰符
摁返回键触发
enter 键触发
向下键触发
1
| <input @keyup.page-down="onPageDown" />
|
1 2 3 4 5 6 7 8 9
| .enter .tab .delete (捕获“Delete”和“Backspace”两个按键) .esc .space .up .down .left .right
|
# 系统按键修饰符
系统按键修饰符
1 2 3 4 5
| <input @keyup.alt.enter="clear" />
<div @click.ctrl="doSomething">Do something</div>
|
鼠标按键
# 4. 双向绑定指令 v-model
下面这段就等价于 v-model
1 2 3 4
| <input :value="searchText" @input="searchText = $event.target.value" />
|
可以让程序员在不操作 dom 的情况下读取和修改 data 中的值
# v-model 修饰符
.lazy input 输入完之后失去焦点才更新数据
.number 输入之后如果值是数字就转成 number 类型,是文字就原封不动
.trim 自动去除空格
1
| <input v-model.trim="msg" />
|
# 5. 条件渲染指令
1 2 3 4 5 6 7
| v-if
v-else
v-else-if
v-show
|
当这几个元素的值为 true 时被绑定元素内的内容从才会被显示
# v-if 和 v-show 的区别
v-if 是 “真实的” 按条件渲染,因为它确保了在切换时,条件区块内的事件监听器和子组件都会被销毁与重建。
相比之下,v-show 简单许多,元素无论初始条件如何,始终会被渲染,只有 CSS display 属性会被切换。
总的来说,v-if 操作 dom 元素的销毁于重建,v-show 操作 dom 元素的显示与隐藏,v-if 有更高的切换开销,而 v-show 有更高的初始渲染开销。因此,如果需要频繁切换,则使用 v-show 较好;如果在运行时绑定条件很少改变,则 v-if 会更合适。
# 6. 列表渲染指令 v-for
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 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82
| <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Document</title> <script src="../js-vue/vue.js"></script>
</head> <body> <div id="app"> <input type="search" value="" @keyup.esc="clearn" /> <input v-model.lazy="searchText" /> <input type="text" name="" id="" v-model.number="checkbox" /> <h1 v-if="checkbox">11s</h1> <h1 v-else>else</h1> <h1 v-for="(item,index) in dataBase"> {{item.name}}-{{index}}-{{item.age?item.age:"未填写"}}-{{item.sex}} </h1> <h1 v-for="({name,age,sex},index) in dataBase"> {{name}}-{{index}}-{{age?age:"未填写"}}-{{sex}} </h1> <div v-for="({name,age,sex},index) in dataBase"> <span v-for="item in jg">{{name}}-{{item}}</span> </div> <div v-for="(key,value,index) in searchText"> {{index}}-{{key}}:{{value}} </div> <p v-for="n in 15">{{n}}</p> <ul> <template v-for="item in dataBase"> <li>{{ item.name }}</li> <li class="divider" role="presentation"></li> </template> </ul> </div> <script> new Vue({ el: '#app', data: { searchText: { name: 'nihao1', area: '云南', }, checkbox: 0, jg: [1, 2, 3, 4, 5], dataBase: [ { name: 'zs', sex: '男', }, { name: 'ls', sex: '男', }, { name: 'lt', sex: '男', age: 25, }, ], }, methods: { clearn(e) { console.log('触发了按键修饰符'); e.target.value = ''; }, }, }); </script> </body> </html>
|
同时使用 v-if
和 v-for
是不推荐的,因为这样二者的优先级不明显。
在外新包装一层 <template>
再在其上使用 v-for
可以解决这个问题 (这也更加明显易读):
1 2 3 4
| <template v-for="item in dataBase"> <li v-if="item.age">{{ item.name }}</li> <li class="divider" role="presentation"></li> </template>
|
# 绑定 Key 管理 vue 的 dom 的状态:
Vue 默认按照 “就地更新” 的策略来更新通过 v-for 渲染的元素列表。确保他们在原本指定的位置进行渲染。
为了便于 vue 跟踪节点的标识,从而重新、重用对现有的元素排序,需要给对应元素加一个唯一的 key 属性。key 是通过 v-bind 绑定的一个特殊的属性而不是 item 中的那个 key 属性。
值一般是一个 number 类型,一般用 item.id。
1 2 3
| <template v-for="todo in todos" :key="todo.name"> <li>{{ todo.name }}</li> </template>
|
key 值需要是一个唯一值,不能以 index 当作 key 的值,因为没有强制的绑定方式,也不能说是个严谨的值。
例如:原来列表中有两个元素
在前面添加一个元素的时候索引值会变成
所以说是不严谨的
# 组件上使用 v-for
可以在组件上直接使用 v-for 但是不会将任何数据传到组件里面,因为组件有自己独立的作用域,将数据传递到组件中需要用 props
省略..... 后面再说
# 数组变化侦测
侦听数组变化,在他们被调用时触发相关的更新:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| push()向数组的末尾添加一个或多个元素,并返回新的长度。 pop()数组最后一位元素删除并返回数组的最后一个元素。 shift()数组的第一个元素从其中删除,并返回第一个元素的值。 unshift()向数组的开头添加一个或更多元素,并返回新的长度。 splice(index,howmany,item1, …, itemX)向/从数组中添加/删除项目,然后返回被删除的项目 第一个参数:表示从哪个索引位置(index)添加/删除元素 第二个参数:要删除的项目数量。如果设置为 0,则不会删除项目。 第三个参数:可选。向数组添加的新项目。 splice(1,2,‘a’,‘b’) 从索引位置(index:1)删除,删除2个元素,并添加2个新元素来替代被删除的元素 splice(1,0,‘a’)从索引位置(index:1)添加,添加两个元素 sort()正序 reverse()倒序
<label for="test">label标签</label> <input type="text" id="test"> 当点击label标签内的文本后,就会触发绑定的表单元素。也就是说,当用户渲染该标签时,浏览器就会自动将焦点转到绑定的表单控件上。
|
# 过滤器 filter (vue3 已淘汰) 只能在 vue2 中使用
可以用在两个地方:1. 插值语法 2.v-bind
通过管道符进行・调用
1 2 3
| <div :id='rawid|过滤器'> {{message|过滤器}} </div>
|
私有过滤器(在 Vue 不同实例中不能通用只能在当前实例使用)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| <div id="app"> <p>{{message|filterL}}</p> </div> <script> new Vue({ el: '#app', data: { message: 'hellow shuaige', }, filters: { // 过滤器函数形参永远都是管道符前面的那个值 filterL(val) { // 取索引对应的字符串 const vl = val.charAt(0).toUpperCase(); // 截取字符串从指定索引向后截取不包括当前索引 const ou = val.slice(1, 5); return vl + ou; }, }, }); </script> </body>
|
全局过滤器挂在 Vue 全局指令上
1 2 3 4 5 6 7
| // 全局过滤器Vue.filter(过滤器名字,回调函数) Vue.filter('filterL', (val) => { const vl = val.charAt(0).toUpperCase(); // 截取字符串从指定索引向后截取不包括当前索引 const ou = val.slice(1, 5); return vl + ou; });
|
全局过滤器和私有过滤器冲突,私有过滤器优先级高
也可以通过管道符调用多个过滤器
# 侦听器 Watch
监视数据变化时而调用的函数,在组合式 API 中,我们可以使用 watch 函数在每次响应式状态发生变化时触发回调函数:
但是学习之前需要了解一下 ref 声明响应式状态,主要用于组合式 API 中
为什么要使用 ref?
在模板中使用了一个 ref, 改变 ref 时,vue 会自动检测到变化,并更新相应的 dom。通过依赖追踪的响应式系统实现的。当一个组件被首次渲染时,Vue 会在追踪渲染的过程中使用的每一个 ref, 当 ref 被修改时,会触发追踪它的这个组件重新渲染一次。
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
| <script setup> import { ref, watch } from 'vue' const question = ref('') const answer = ref('Questions usually contain a question mark. ;-)') const loading = ref(false) // 可以直接侦听一个 ref watch(question, async (newQuestion, oldQuestion) => { if (newQuestion.includes('?')) {//includes是查询字符串中是否包含某个字符 loading.value = true answer.value = 'Thinking...' try { const res = await fetch('https://yesno.wtf/api') answer.value = (await res.json()).answer } catch (error) { answer.value = 'Error! Could not reach the API. ' + error } finally { loading.value = false } } }) </script> <template> <p> Ask a yes/no question: <input v-model="question" :disabled="loading" />//默认不能选中是false </p> <p>{{ answer }}</p> </template>
|
侦听 que's'ting,查看输入字符中是否包含问号,包含?时就让输入框不能输入,调用随机返回 api
watch
的第一个参数可以是不同形式的 “数据源”:它可以是一个 ref (包括计算属性)、一个响应式对象、一个 getter 函数、或多个数据源组成的数组:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| const x = ref(0) const y = ref(0) // 单个 ref watch(x, (newX) => { console.log(`x is ${newX}`) }) // getter 函数 watch( () => x.value + y.value, (sum) => { console.log(`sum of x + y is: ${sum}`) } ) // 多个来源组成的数组 watch([x, () => y.value], ([newX, newY]) => { console.log(`x is ${newX} and y is ${newY}`) })
|
注意,你不能直接侦听响应式对象的属性值
1 2 3 4 5 6
| const obj = reactive({ count: 0 }) // 错误,因为 watch() 得到的参数是一个 number watch(obj.count, (count) => { console.log(`count is: ${count}`) })
|
需要返回这个属性的 getter 函数值
1 2 3 4 5 6 7
| // 提供一个 getter 函数 watch( () => obj.count, (count) => { console.log(`count is: ${count}`) } )
|
页面打开先执行一次
1 2 3 4 5 6
| username: { handler(n, o) { console.log(n); }, immediate: true, },
|
# 一次性侦听器 once:true 3.4+
当数据发生变化时只触发一次
# watcheffect()
简化请求远程资源的监听,不用多次调用
1 2 3 4 5 6 7 8 9 10 11 12 13
| const todoId = ref(1) const data = ref(null) watch( todoId, async () => { const response = await fetch( `https://jsonplaceholder.typicode.com/todos/${todoId.value}` ) data.value = await response.json() }, { immediate: true } )
|
简写为
1 2 3 4 5 6
| watchEffect(async () => { const response = await fetch( `https://jsonplaceholder.typicode.com/todos/${todoId.value}` ) data.value = await response.json() })
|
而且回调会立即执行,不需要指定 immediate: true。在执行期间,它会自动追踪 todoId.value 作为依赖(和计算属性类似)。每当 todoId.value 变化时,回调会再次执行。有了 watchEffect (),我们不再需要明确传递 todoId 作为源值。
# watch 和 Watcheffect 的区别
1.watch 不会追踪回调函数里面访问到的东西。只有数据发生改变后才会触发回调,可以准确的控制回调函数触发时机;(可定制)
2.watcheffect 可以追踪到回调函数里面的属性,代码更简洁,但是响印依赖关系不那么明确。(不可定制)
# 同步侦听器
1 2 3 4 5
| import { watchSyncEffect } from 'vue' watchSyncEffect(() => { /* 在响应式数据变化时同步执行 */ })
|
# 计算属性 computed
通过一系列运算后得出来的值,动态计算出来的值可以被模板结构或 methods 方法使用
插 slice、splice、split 三者区别
slice 截取字符串或数组,并返回一个新的字符串或数组
splice 可以对数组进行插入删除替换返回删除的数组元素
split 以什么字符分割字符串返回一个数组
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <div id="app"> <div> <input type="text" v-model="message" /> <p>{{messageRe}}</p> </div> </div> <script> new Vue({ el: '#app', data: { message: 'hellow', }, computed: { messageRe() { return this.message.split('').reverse().join(''); }, }, });
|
计算属性所依赖的值发生改变时,计算属性的值也会发生改变
计算属性和侦听属性
计算属性相对于监听属性来说代码更简洁,计算属性默认只有一个 getter,但是需要的时候也可以创建一个 setter
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| computed: { fullName: { // getter get() { return this.firstName + ' ' + this.lastName }, // setter set(newValue) { var names = newValue.split(' ') this.firstName = names[0] this.lastName = names[names.length - 1] } } }
|
计算属性在大多数情况下更合适,但有时也需要一个自定义的侦听器。这就是为什么 Vue 通过 watch
选项提供了一个更通用的方法,来响应数据的变化。当需要在数据变化时执行异步或开销较大的操作时,这个方式是最有用的。
# Axios 基本使用
1 2 3 4 5 6 7 8 9
| axios({ metnod:"请求类型", url:"请求地址" }).then((result)=>{ }).catch((err)=>{ })
|
# Vue-cli 脚手架
是 vue.js 的开发标准工具。简化 webpack 创建工程化项目的过程。
安装
创建项目
![]()
通过 main.js 把 App.vue 渲染到 index.html 的指定区域中
1 2 3 4 5 6 7 8
| import Vue from 'vue' import App from './App.vue' Vue.config.productionTip = false new Vue({ render: h => h(App),将组件渲染到页面 }).$mount('#app')提取dom元素主要是query函数也就是queryselector查找第一个符合的dom元素,也就是··。。。。。。。。。。。。el绑定的元素,但是,el不能是body或者html,原因是vue在挂载是会将对应的dom对象替换成新的div,但body和html是不适合替换的。
|
# 组件化开发(封装思想,一件多用)
三个组成部分:template、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
| <template> <div id="app"> <img alt="Vue logo" src="./assets/logo.png" /> <HelloWorld msg="Welcome to Your Vue.js App" /> </div> </template> <script> import HelloWorld from './components/HelloWorld.vue'; export default { name: 'App', components: { HelloWorld, }, }; </script> <style lang="less">启用less #app { font-family: Avenir, Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin-top: 60px; } </style>
|
![]()
组件中的 data 必须是一个函数,里面要 return 一个对象
1 2 3 4 5
| data(){ return{ name:;;;;;; } }
|
组件中定义 methods 方法
1 2 3 4 5
| methods: { add() { alert('哇哈哈'); }, },
|
# 使用组件的步骤:
# 1. 在当前组件中暴露出去,在需要用到该组件的里面导入组件
# 2. 注册组件(调入后在 components 中注册组件)
1 2 3 4 5
| export default:{ compoents:{ 组件名 } }
|
# 3. 在 template 中以标签形式直接使用该组件
1 2 3 4 5 6
| <template> <div id="app"> <img alt="Vue logo" src="./assets/logo.png" /> <HelloWorld msg="Welcome to Your Vue.js App" /> </div> </template>
|
# 通过 components 注册的是私有组件。
# 全局组件注册使用
1 2 3 4 5 6 7 8 9 10 11 12 13
| main.js import a from b Vue.component('A',a)
|
# props (自定义属性)
在子组件中声明
父组件中赋值:
1
| <zujian name='张三'><zujian>
|
传参数用 v-bind:
1
| <All :msg="0"></All> 传的就是数字0,不使用就是字符0
|
props 值只读直接修改会报错,想修改需要在 data 中声明变量接受,从 this 挂载的 props 赋值给
1 2 3 4 5 6 7 8 9
| props:{ num } data(){ return{ count:this.num } }
|
配置默认值
1 2 3 4 5
| props:{ num:{ default:0 } }
|
明确知道数据类型后可以设置接受数据的类型
1 2 3 4 5 6 7
| props:{ num:Number/ num:{ type:Number required:true//必填项,默认值不算必须从组件中传过来的值 } }
|
# 样式冲突解决:scoped
.vue 中的样式默认加载到全局中会发生样式冲突
给当前组件添加 scoped 的时候组件实例生成一个唯一标识,给组件中的每个标签对应的 dom 元素添加一个标签属性,但是不会在编辑页面显示
1 2 3 4 5 6 7 8
| <template> <div class="example" data-v-49729759>hello world</div> </template> <style scoped> .example[data-v-49729759] { color: red; } </style>
|
缺点:父元素添加了 scoped 的样式想修改子元素中的标签时修改不了(类似信息可以编辑但是没有网),但是没有加 scoped 的里面又会生效于所有组件
当在 style 前面添加、/deep/ 的时候就可以修改子元素的样式相当于后代选择器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| 加了scoped的样式唯一标识是在后面: h5[data-v-3vxxxx]{ } /deep/ h5{ } 就是 [data-v-3vxxxx] h5{ } 是 [data-v-3vxxxx] 就是父元素的唯一标识
|
使用第三方库的时候需要修改一些样式的时候用到 /deep/
万字 基础篇结束 共 10999 个字 (下一篇万字基础篇 2)