话说现如今都2021年了,Vue3都已经发布好长一段时间了,如今再来写这篇网络上随处可见的文章意义在哪里呢?其实在写之前我也一直在思考这个问题,最后我得出的原因主要有以下几点:
- 用vue做过一些项目,也实现了一些功能,但是总感觉还是缺乏知识体系
- 日常开发多数围绕业务需求展开,也没有仔细深入的细化一些知识点,导致有的东西可能时间长了会模糊
- 正所谓温故而知新,虽然基础的东西可能看似简单,但是通过自己的想法输出出来,并且时常回顾,我认为还是会有意想不到的收获的。
思维导图
在开始正文之前,需要说明一下,文章中每一个字都是我逐一编写的,难免会有疏漏或者错误,也恳请大家的理解,并期望留言区予以批评指证。本文主要介绍关于模版语法相关,因此不会过多涉及到js逻辑方面。下面是我整理的思维导图简要的来说明一下文章的内容脉络,这样不仅利于文章的编写,也很利于记忆。 
1.vue的简介
Vue 是一套用于构建用户界面的渐进式框架。与其它大型框架不同的是,Vue被设计为可以自底向上逐层应用。Vue的核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合。另一方面,当与现代化 的工具链以及各种支持类库结合使用时,Vue也完全能够为复杂的单页应用提供驱动。具体可参阅 vue官方文档
2.如何引入Vue
初学vue,可能会觉得跟刚开始使用原生HTML.CSS JavaScript有所差异,所以对于初学者而言,其实还是建议使用创建.html文件的方式来学习vue框架,因为我以前也是这么走过来的,可以在html文件中引入下载好的vue.js或者使用cdn的方式(建议使用开发环境下的cdn)引入即可。下面为cdn的链接,有想使用其他框架的cdn可以查阅 BootCDN,后续的话一般不会用这样引入。
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
3.内置指令
关于vue,它内置了我很多指令,其实也是自定义指令,只不过底层做了很多的处理而已。Vue中指令都是以 v- 开头,接下来我们就先来看看都有哪些指令吧 v-clock 该指令主要是为了防止页面加载时出现闪烁问题,这个指令保持在元素上直到关联实例结束编译。和CSS规则如[v-cloak]{display:none}一起用时,编译结束前标签一直有v-cloak属性。
<style>
[v-cloak]{
display: none;
}
</style>
<div id="app">
<div v-cloak>
{{content}}
</div>
</div>
<script>
var vue = new Vue({
el:"#app",
data:{
content:"我是幕筱佳"
}
})
</script>
- v-pre
跳过这个元素和它的子元素的编译过程。可以用来显示原始Mustache 标签。跳过大量没有指令的节点会加快编译。
<div id="app">
<!-- 显示的是{{ hello }}跳过编译过程 -->
<span v-pre>{{ content}}</span>
</div>
<script>
var vue = new Vue({
el:"#app",
data:{
content:"我是幕筱佳"
}
})
</script>
v-text 更新元素的 textContent(标签内文字)。如果要更新部分的 textContent ,需要使用Mustache语法进行插值。
<div id="app">
<div v-text="content">
</div>
</div>
<script>
var vue = new Vue({
el:"#app",
data:{
content:"我是幕筱佳"
}
})
</script>
- v-html
更新元素的innerHTML (html标签会被编译)。 注意:内容按普通 HTML 插入 - 不会作为 Vue 模板进行编译 。如果试图使用 v-html 组合模板,可以重新考虑是否通过使用组件来替代。
<div id="app">
<div v-html="content">
</div>
</div>
<script>
var vue = new Vue({
el:"#app",
data:{
content:"<h1>我是幕筱佳</h1>"
}
})
</script>
- v-show
控制元素的显示与隐藏, 原理:带有 v-show 的元素始终会被渲染并保留在 DOM 中。v-show 只是简单地切换元素的 CSS 属性 display的属性
<div id="app">
<div v-show="flag">显示</div>
<div v-show="flag">未显示</div>
</div>
<script>
var vue = new Vue({
el:"#app",
data:{
flag:true,
}
})
</script>
- v-once
该指令只会渲染元素和组件一次。随后的重新渲染,元素/组件及其所有的子节点将被视为静态内容并跳过。这可以用于优化更新性能。
<div id="app">
<span v-once>{{ content}}</span>
</div>
<script>
var vue = new Vue({
el:"#app",
data:{
hello:"我是幕筱佳"
}
})
</script>
- v-model
常用于表单元素数据的收集,当视图发生变化的时候,数据也会跟着同步变化,
<div id="app">
<input v-model="content">
<div>{{content}}</div>
</div>
<script>
var vue = new Vue({
el:"#app",
data:{
content:""
}
})
</script>
- v-on
用于绑定事件监听,用在普通元素上时,只能监听原生 DOM 事件。用在自定义元素组件上时,也可以监听子组件触发的自定义事件。该指令也可以缩写为@符号。
<div id="app">
<button v-on:click="onClick">点我</button>
<!-- 简写形式 -->
<button @click="onClick">点我</button>
</div>
<script>
var vue = new Vue({
el:"#app",
data:{},
methods:{
onClick() {
console.log("点我干嘛?");
}
}
})
</script>
- v-bind
v-bind 指令被用来响应地更新 HTML 属性,可以缩写为:属性名
<div id="app">
<a v-bind:href="url">{{target}}</a>
<!-- 简写形式 -->
<a :href="url">{{target}}</a>
</div>
<script>
var vue = new Vue({
el:"#app",
data:{
target:"百度",
url:"https://www.baidu.com"
}
})
</script>
- v-if、v-else、v-else-if
通过条件判断展示或者隐藏某个元素,该指令会销毁DOM元素,性能消耗比较大,一般频繁切换显示/隐藏的元素不建议使用。
<div id="app">
<input v-model="score">
<div v-if="score>=90">优秀</div>
<div v-else-if="score>=80&&score<90">良好</div>
<div v-else-if="score>=70&&score<80">一般</div>
<div v-else>较差</div>
</div>
<script>
var vue = new Vue({
el:"#app",
data:{
score:100,
}
})
</script>
- v-for
遍历数组、对象、或者字符串,一般常用来遍历数组 注意: 1.建议在遍历数组的时候,加上key,给每个节点做一个唯一标识,主要是为了更高效的更新虚拟DOM元素 ,在真正的开发项目时,如果不加key,可能会造成一系列的结构紊乱问题,详细请参考官方文档 2.v-for跟v-if不能一起使用,因为v-for的优先级比较高 ,如果同时使用的话,则只会遍历数组,而不会执行v-if中的条件判断。
<div id="app">
<ul>
<li v-for="item in list" :key:"index">{{item}}</li>
</ul>
</div>
<script>
var vue = new Vue({
el:"#app",
data:{
list;["海贼王","海尔兄弟","犬夜叉","画江湖之不良人"]
}
})
</script>
4.动态绑定class & style
<div id="app">
<!-- 变量形式 -->
<h1 :class="obj">我是幕筱佳</h1>
<!-- 对象形式:常用 -->
<h1 :class="{'active':flag}">我是幕筱佳</h1>
<!-- 数组形式 -->
<h1 :class="[obj1,obj2]">我是幕筱佳</h1>
<button @click="colorChange">切换</button>
</div>
<script>
new Vue({
el: "#app",
data: {
obj1: "active",
obj2: "fize",
flag: false,
content: "我是幕筱佳"
},
methods: {
colorChange() {
this.flag = true
this.obj2 = ""
}
}
})
</script>
<div id="app">
<div class="box"></div>
<p>你好世界</p>
<p :style="{color:activeColor,fontSize:fontSize+'px'}">你好世界 </p>
<button type="button" @click="update">更新</button>
</div>
<script>
new Vue({
el: '#app',
data: {
activeColor:"red",
fontSize:20
},
methods:{
update() {
this.activeColor="green";
this.fontSize=30
}
}
})
</script>
5.事件交互
5.1.事件对象
对于vue的事件交互,其实和原生Javascript差异不大,每一个事件都有一个事件对象event,我想说的是事件的参数传递问题。
- 当事件不接受参数时,默认会有一个$event对象
- 当事件接受参数时,会按照接受参数的顺序作为事件的参数
<div id="app">
<h1 @click="h1Click($event,index)" v-for="(item,index) in list">{{item}}</h1>
</div>
<script type="text/javascript">
new Vue({
el: "#app",
data: {
list: ["张三", "李四", "王五"]
},
methods: {
h1Click(e,index) {
console.log(e,index);
}
}
});
</script>
5.2 事件修饰符
事件修饰符用于DOM的事件处理,常用的事件修饰符有以下几个:
- .stop 阻止冒泡(通俗讲就是阻止事件向上级DOM元素传递)
<div class="box1" @click="test1">
<div class="box2" @click.stop="test2"></div>
</div>
- prevent: 默认事件指对DOM的操作会引起自动执行的动作,比如点击超链接的时候会进行页面的跳转,点击表单提交按钮时会重新加载页面等,使用".prevent"修饰符可以阻止这些事件的发生。
<div class="box2" @click.prevent="test2"></div>
- capture:捕获冒泡,即有冒泡发生时,有该修饰符的dom元素会先执行,如果有多个,从外到内依次执行,然后再按自然顺序执行触发的事件。
<div class="box3" @click.capture="test3"></div>
- once:设置事件只能触发一次,比如按钮的点击等。
<div class="box4" @click.once="test4"></div>
- self:将事件绑定到自身,只有自身才能触发,通常用于避免冒泡事件的影响
<div class="box5" @click.once="test5"></div>
- passive:该修饰符大概意思用于对DOM的默认事件进行性能优化,根据官网的例子比如超出最大范围的滚动条滚动的方法,可以使用该修饰符进行加强,但是效果并没有感觉到安静,我具体也很少用。
<div class="box6" @click.once="test6"></div>
5.3 按键修饰符
按键修饰符是根据键盘上的keyCode来触发该事件,使用的时候可以参考相关键盘按键码,我这里就举个enter的例子,使用方式如下:
<!-- 只有在 `key` 是 `Enter` 时调用 `vm.submit()` -->
<input v-on:keyup.enter="submit">
5.4 表单域修饰符
主要用来对表单元素进行处理以及修饰
<div id="app">
<input type="text" v-model.number="age" /><br />
<script type="text/javascript">
new Vue({
el:'#app',
data:{
age:'',
}
})
</script>
- lazy:将input事件切换为chenge事件(绑定的数据不会随时变化,会等到你的鼠标焦点离开输入框同步数据)
<input type="text" v-model.lazy='msg'>
<input type="text" v-model.trim='info'>
5.5 自定义修饰符
Vue.config.keyCodes = {
v: 86,
f1: 112,
mediaPlayPause: 179,
"media-play-pause": 179
}
<input type="text" @keyup.media-play-pause="method">
6.生命周期
其实对于这个词语,可能挺起来有点高大上的样子,其实就相对于我们而言,从出生到死亡的过程其实就是一个生命周期,而vue的生命周期就是指每一个vue实例从创建到销毁的过程,这个过程到底经历了什么呢?经历了很多很多,他经历了从开始创建、初始化数据、编译模板、挂载Dom、渲染→更新→渲染、卸载等一系列过程。 那么这些过程中,就具体来了解一下。  上图其实是vue官网里的,看起来是不是很复杂,其实vue的生命周期主要包括八个钩子函数,每个阶段都有其不同的行为表现。
beforeCreate() {
console.log("beforeCreate ()");
},
created() {
console.log("created ()");
},
beforeMount() {
console.log("beforeMount()");
},
mounted() {
},
beforeUpdate() {
console.log("beforeUpdate ()");
},
updated() {
console.log("updated ()");
},
beforeDestroy() {
},
7.计算属性和监听器
7.1.计算属性computed
首先它是个属性 ,其次这个属性有计算 的能力(计算是动词),这里的计算就是个函数;简单点说,它就是一个能够将计算结果缓存起来的属性(将行为转化成了静态的属性),仅此而已;可以想象为缓存!对于任何复杂逻辑,应当使用计算属性。
<div id="app">
<p>计算属性:{{ reverseMsg() }}</p>
<--作为计算属性使用 -->
<p>计算属性:{{ msg }}</p>
</div>
<script>
new Vue({
el: '#app',
data: {
message: 'Hello Vue !'
},
methods:{
reverseMsg (){
return this.message.split('').reverse().join('')
},
},
computed:{
msg (){
return this.message.split('').reverse().join('')
}
}
})
</script>
看到这里,那么很多人可能很疑惑,methods跟computed实现的结果是其实一样的,那么computed存在的意义到底在哪呢? 这个其实就设计到vue底层的设计了,主要有这几点区别: -计算属性是基于它们的响应式依赖进行缓存的 。只在相关响应式依赖发生改变时它们才会重新求值,如果多次使用时,计算属性只会调用一次。
methods方法,每当触发重新渲染时,调用方法将总会再次执行函数 。而对于计算属性而言,它会首先计算缓存,那么这就很大程度提高了性能。- 对于任何复杂逻辑,都应当使用计算属性。
7.2 监听器watch 主要用于监测组件实例的数据变动,并依据该数据变动作出响应,如更新一个数据,或者发起异步请求从服务器端请求数据。与计算属性不同的是,监听器不需要返回新的数据,不能被当作数据属性使用,当需要在数据变化时执行异步或开销较大的操作时,使用监听器是最合适的 。
<div id = "app">
千米 : <input type = "text" v-model="kilometers">
米 : <input type = "text" v-model="meters">
</div>
<p id="info"></p>
<script>
new Vue({
data() {
return {
kilometers: 0,
meters: 0
}
},
watch: {
kilometers(val) {
this.meters = val * 1000;
console.log("aaa");
},
meters(val, oldVal) {
this.kilometers = val / 1000;
}
}
});
</script>
监听器函数在初始渲染时并不会被调用,只有在后续监听器的属性发生变化时才会被调用;如果要让监听去函数在监听开始后立即执行,可以使用immendiate 选项,将其值设置为true。如下所示:
watch:{
meters:{
handler(val,oldVal){
console.log(val,oldVal);
},
deep:true,
immediate:true
}
}
8.过滤器
在vue中 filter是一个很强大的功能,主要用来处理文本格式化的。过滤器分为两种,一种是局部过滤器,一种全局过滤器。所有的过滤器都是函数,并且参数为要过滤的数据。
局部过滤器:只允许在当前组件中使用 全局过滤器:所有组件都可以使用
- 局部过滤器
<div id="app">
<p>计算属性:{{ msg | dataFormat }}</p>
</div>
new Vue({
el: '#app',
data: {
msg: 'filter'
},
methods: {},
filters: {
dataFormat(msg) {
return msg.reverse();
}
}
});
- 全局过滤器
<div id="app">
<p>计算属性:{{ num| toZero}}</p>
</div>
<script>
Vue.filter('toZero', function(num) {
return num< 10 ? num: "0" +num
})
new Vue({
el: '#app',
data: {
num: 1
},
})
</script>
9. 组件的使用
对于现在比较流行的vue、react、angluar来说,它们都是基于组件化开发的,那么组件到底是什么呢?其实·组件就是一个完整的结构体,包括结构、行为、样式 。组件可以扩展 HTML 元素,组件的本质是封装可复用的代码块 进而提高效率,几乎任意类型的应用的界面都可以抽象为一个组件树。下面说说在vue中如何祖册组件以及使用组件。
组件分为全局组件和局部组件 全局组件可以在任意地方使用 局部组件只能注册在vue实例中使用
<div id="app">
<hello></hello>
</div>
<script>
Vue.component('hello', {
template: '<h1>我是全局组件!</h1>'
})
new Vue({
el: '#app'
})
</script>
- 局部组件
<div id="app">
<runoob></runoob>
</div>
<script>
var Child = {
template: '<h1>我是局部组件!</h1>'
}
new Vue({
el: '#app',
components: {
'hello': Child
}
})
</script>
10. 组件通信
对于每个vue实例都是互相独立的,那么组件之间是如何进行数据传递的呢?接下来就带着这个问题来看看vue中常用的几种通信方式。 2. 父组件的数据需要通过自定义属性把数据传给子组件,子组件通过props接收
<div id="app">
<child message="hello!"></child>
</div>
<script>
Vue.component('child', {
props: ['message'],
template: '<span>{{ message }}</span>'
})
new Vue({
el: '#app'
})
</script>
特别注意 : 3. 遵循单向数据流原则,在父组件中,数据是简单数据类型(number、string、boolean)时,子组件不能直接对父组件prop进行修改。 4. 若父组件中数据是复杂数据类型(object、function、Array)时,子组件可以修改父组件的props下面的属性,但是却违背了vue设计单向数据流的原则,但是当我们不希望父组件那份数据源有任何变化的时候,这就是一个严重的逻辑bug。 5. 如果想修改子组件接收的数据,可以在子组件中定义新的数据,将父组件传过来的值赋值给新定义的数据,之后操作这个新数据或者使用使用.sync修饰符修改数据。
- 子组件通过$emit()派发事件将数据传递给父组件
<template>
<div>
<h1>父组件</h1>
<p>{{mydata}}</p>
<hr/>
<!--向子组件传值:绑定属性tosondata,father为传入的值-->
<son :tosondata="father" @getdata="getsondata"></son>
<!--接收子组件的传值:通过子组件触发getdata事件,父组件在getsondata事件中接收-->
<hr/>
<brother></brother>
</div>
</template>
<script>
import son from './son.vue'
export default{
data(){
return{
father:"我是父组件的数据",
mydata:''
}
},
components:{
son,
brother
},
methods:{
getsondata(value){
this.mydata=value
}
}
}
</script>
<template>
<div>
<h1>子组件</h1>
<p>{{tosondata}}</p>
<h2>{{mydata1}}</h2>
<button @click="btn">传值给父组件</button>
</div>
</template>
<script>
export default{
data(){
return{
sonmsg:'我是子组件的数据',
mydata1:''
}
},
props:{
tosondata:String
},
methods:{
btn(){
this.$emit('getdata',this.sonmsg)
}
},
}
</script>
-通过自定义eventBus实现兄弟组件通信 首先得定义一个新的vue实例作为中转站
let eventBus=new Vue()
export default eventBus
import eventBus from "资源路径"
eventBus.$emit('change',this.recvdata)
import eventBus from "资源路径"
eventBus.$on('change',this.recvdata)
11.插槽
对于插槽的概念和使用,这是vue的一个难点,多用于封装高级组件。还是都深入才能理解和掌握。 插槽:就是子组件中的提供给父组件使用的一个占位符,用 表示,父组件可以在这个占位符中填充任何模板代码,如 HTML、组件等,填充的内容会替换子组件的<slot></slot> 标签。 如果需要用一句话去总结作用域插槽,那就是在父组件中访问子组件的数据,或者从数据流向的角度来讲就是将子组件的数据传递到 父组件 。 白话文描述或者更容易明白:无非就是在子组件中挖个坑,坑里面放什么东西由父组件决定
插槽主要分为默认插槽、具名插槽、作用域插槽
1.默认插槽
- 在子组件中写一个占位符
<template>
<div>
<h1>我是:</h1>
<slot></slot>
</div>
</template>
<script>
export default {
name: 'child'
}
- 在父组件中给这个占位符填充内容
<template>
<div>
<div>使用slot分发内容</div>
<div>
<child>
<div style="margin-top: 30px">幕筱佳</div>
</child>
</div>
</div>
</template>
<script>
import child from "./child.vue";
export default {
name: 'father',
components:{
child
}
}
</script>
2.具名插槽 如果要使用的插槽的地方比较多的话,就必须得用具名插槽。给对应的插槽分别起个名字,方便后边插入父组件将内容根据name来填充对应的内容。 但是当子组件中有多个作用域插槽时,不建议上边这种简单的写法,因为可能会出现作用域不明确的问题,所以就必须得使用作用域插槽。
<!-- 子组件 -->
<template>
<div class='container'>
<div>我是子组件</div>
<slot name="up"></slot>
<slot name="down"></slot>
</div>
</template>
<!-- 父组件 -->
<template>
<div class='container'>
<div>我是父组件</div>
<Child ref="child">
<div v-slot="up">你好,中国</div>
<div v-slot="down">郑州,加油</div>
</Child>
</div>
</template>
<script>
import Child from '@/components/Child'
export default {
components: { Child }
}
</script>
3.作用域插槽 通过上边的默认插槽和具名插槽的使用过程中,发现,基本都是父组件中决定要插入到子组件中的内容,而子组件自行决定插槽的位置。 在作用域插槽内,父组件可以拿到子组件的数据。子组件可以在slot标签上绑定属性值
<slot :nickName="'幕筱佳'"></slot>
<template>
<section>
<slot-child>
<template slot-scope="scope">
<div>{{scope.nickName}}</div>
</template>
</slot-child>
</section>
</template>
<script>
import slotChild from '@/components/Child'
export default {
components: { slotChild:slotChild }
}
</script>
|