前端Web框架之Vue part one

Posted by OuterCyrex on August 3, 2024

渲染、事件与绑定

请注意:由于该博客是基于vue开发的,因此代码块中的插值表达式双大括号会被识别,为了将其显示,我们使用

1
{`{ 我是示例}`}

来展示插值表达式

一.引入

Vue官方文档:https://cn.vuejs.org

Webstorm中,只需要创建一个vue.js项目即可,时间较长。如果创建完成,我们可以通过下列cmd命令行来检测安装效果

1
2
3
4
5
6
7
8
9
10
npm run dev

返回内容:

 VITE v5.3.5  ready in 335 ms

  ➜  Local:   http://localhost:5173/
  ➜  Network: use --host to expose
  ➜  press h + enter to show help

一个vue文件的基本样式为:

1
2
3
4
5
6
<template>

</template>
<script>

</script>

由此我们可以写一个简单的HelloWorld效果

1
2
3
4
5
6
7
8
9
10
11
12
<template>
  <p>{`{message}`}</p>
</template>
<script>
export default {
  data(){
    return{
      message:"Hello World",
    }
  }
}
</script>

如果我们想引入外部的vue文件的内容,我们可以通过import语句来实现引入。

1
2
3
4
5
6
<template>
  <lesson1/>
</template>
<script setup>
import lesson1 from '../src/components/lesson1.vue'
</script>

上述代码中,import语句实现了引入页面lesson1的效果,但如果我们想要将其在页面中显示,则需要在模版中使用该变量,即<lesson1/>

二.模版与渲染

1.文本插值

vue中最基本的数据绑定形式就是文本插值,采用双大括号语法。值得注意的是,这种语法在Go语言的gin框架中也同样被使用。

在引入中的vue代码示例中,我们通过``实现了文本插值,将其内部的message参数与data()函数内的键值对进行对应。

我们可以在插值内写入javascript表达式,但请注意:每个绑定仅支持单一表达式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<template>
  <p>{`{number+1}`}</p>
  <p>{`{ok?'yes':'no'}`}</p>
  <p>{`{array.split(',')}`}</p>
</template>
<script>
export default {
  data(){
    return{
      number:20,
      ok:true,
      array:"1,2,3,4,5",
    }
  }
}
</script>
//输出结果
21
yes
[ "1", "2", "3", "4", "5" ]

如果我们要加入原生HTML语句,并将其在页面中显示,我们直接使用``只会将原生HTML语句字符串输出,要想正常显示,我们需要v-html语句。

1
2
3
4
5
6
7
8
9
10
11
12
<template>
  <p v-html="rawHTML"></p>
</template>
<script>
export default {
  data(){
    return{
      rawHTML:"<a href='https://outercyrex.github.io'>原生HTML</a>"
    }
  }
}
</script>

二者的区别就类似于javascriptinnerTextinnerHTML的区别。

2.属性绑定

vue中,如果我们想更改一个标签的属性,并且是通过渲染模版的方式来更改的话,我们可以通过v-bind进行绑定。

1
2
3
4
5
6
7
8
9
10
11
12
<template>
  <p v-bind:class="class">我是box</p>
</template>
<script>
export default {
  data(){
    return{
      class:"box",
    }
  }
}
</script>

其语法为v-bind : 属性名="键",且vue为我们提供了一个语法糖,即v-bind可以直接简写为:

1
<p :class="class">我是box</p>

上述代码是同样有效的。

这个值也可以是布尔值

1
2
3
4
5
6
7
8
9
10
11
12
<template>
  <button :disabled="isButtonDisabled">Button</button>
</template>
<script>
export default {
  data(){
    return{
      isButtonDisabled:true,
    }
  }
}
</script>

如果传入的值是一个对象,我们可以使用v-bind属性来将对象内的键值对按照属性-内容的形式展示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<template>
  <p v-bind=obj>我是box</p>
</template>
<script>
export default {
  data(){
    return{
      obj:{
        class:"box",
        id:"box1"
      }
    }
  }
}
</script>

//输出结果
<p class="box" id="box1">我是box</p>

注意,如果对应的null或者undefine,则该属性会直接被删除。

3.条件渲染

vue中,提供了条件渲染的途径,分别是v-if、v-else、v-else-if、v-show

条件渲染即在判断是否进行渲染的前提下,对特定的模版进行渲染

1
2
3
4
5
6
7
8
9
10
11
12
13
<template>
  <h3>条件渲染</h3>
  <div v-if=flag>v-if测试</div>
</template>
<script>
  export default {
    data(){
      return{
        flag:false,
      }
    }
  }
</script>

上述语句中,我们设置了v-if的值为false,则该标签不会被渲染,自然不会在页面中显现。

1
2
<div v-if=flag>v-if测试</div>
<div v-else>v-else测试</div>

v-if、v-else、v-else-if的语法和流程语句中的if、else、if-else是一致的,如v-else会寻找此前与其最接近的v-if语句。

v-showv-if效果相似,但存在以下的区别:

v-if真实条件渲染,因为它确保了在切换时,条件区块内的事件监听器子组件都会被销毁与重建

v-if也是惰性的:如果在初次渲染时条件值为false,则不会做任何事。条件区块只有当条件首次变为true时才被渲染

相比之下,v-show简单许多,元素无论初始条件如何,始终会被渲染,只有CSS的display属性会被切换。

4.列表渲染

列表渲染类似于for循环,可以将一个数组的所有内容遍历并分别进行渲染。在vue中,列表渲染使用的是v-for属性。

其属性的内容为单项 in/of 数组,注意此处使用inof均可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<template>
  <p v-for="name in names">{`{ name }`}</p>
</template>
<script>
  export default {
    data(){
      return{
        names:["Outer","Cyrex",1,2,3,4]
      }
    }
  }
</script>

//输出效果
Outer
Cyrex
1
2
3
4

此外,v-for也支持第二项来实现返回索引值

1
2
3
4
5
6
7
8
9
<p v-for="(name,index) in names">{`{ name }`}——编号为</p>

//输出效果
Outer——编号为0
Cyrex——编号为1
1——编号为2
2——编号为3
3——编号为4
4——编号为5

除了遍历数组外,v-for还可以遍历一个对象所有属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<template>
  <p v-for="(key,value) in names">{`{key}`} : {`{value}`}</p>
</template>
<script>
  export default {
    data(){
      return{
        names: {
          name:"Outer",
          age:19,
          work:"backend",
    }
      }
    }
  }
</script>

//输出效果
Outer : name
19 : age
backend : work

这个过程中也可以加入第三项,其同样会被返回为index索引

有一个重要的知识点是:vue默认采取就地更新的策略来更新v-for渲染元素列表,即一旦有一个元素发生顺序上的变化,vue会默认将列表内的所有元素重新渲染,而非只改变对应的元素。

我们想要减少系统开支,只变化对应元素的顺序,便需要设置对应标签的key属性

1
<p v-for="value in names" :key="index">{`{value}`}</p>

此处的:仍然是v-bind:的缩写。key属性的内容要求是对应数据的唯一索引,如idindex等。

三.事件

1.事件处理

我们可以使用v-on指令(简写为@)来监听DOM事件,并在事件触发时执行对应的javascript 语句。用法on:click="methodName"@click=handler"

事件监听器(处理器)可以是:

  • 内联事件处理器:事件被触发时执行的内联javascript语句(与onclick类似)
  • 方法事件处理器:一个指向组件上定义的方法的属性名或是路径
1
2
3
4
5
6
7
8
9
10
11
12
<template>
  <button @click="count++">{`{count}`}</button>
</template>
<script>
  export default {
    data(){
      return{
        count:0
      }
    }
  }
</script>

上述语句便是一个简单的内联事件处理器,可以发现内联事件处理器即在标签内直接写入处理事件的javascript语句。

方法事件处理器则是通过索引<script>代码块中对应的方法,来处理对应的事件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<template>
  <button @click="addCount">{`{count}`}</button>
</template>
<script>
  export default {
    data(){
      return{
        count:0
      }
    },
    methods:{
      addCount(){
        this.count++;
      }
    }
  }
</script>

2.事件传参

事件参数可以获取event对象和通过事件传输数据

1
2
3
4
5
6
7
8
9
10
methods:{
  addCount(e){
    console.log(e.target)
    this.count++;
  }
}
//输出结果
<button>1</button>
<button>2</button>
...

由于vuejavascript的框架,其肯定支持javascript原生的event参数的各种属性,如target等。

上述语句实现了event的传参,接下来介绍事件传入一定的参数的处理方法。

1
2
3
4
5
6
7
8
9
<button @click="addCount('Hello')">{`{count}`}</button>
<script>
methods:{
  addCount(msg){
    console.log(msg)
    this.count++;
  }
}
</script>

如上述内容便传入了一个字符串给msg参数,并将其输出在控制台中。

如果要同时传入event参数和其他参数,可以将event参数按照$event的形式传参

1
2
3
4
5
6
7
8
9
10
<button @click="addCount('Hello',$event)">{`{count}`}</button>
<script>
methods:{
  addCount(msg,e){
    console.log(msg)
    console.log(e.target)
    this.count++;
  }
}
</script>

此时我们也可以组合此前的v-for等内容来实现批量的操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<template>
  <button @click="output(item)" v-for="(item,index) of count" :key="index">{`{item}`}</button>
</template>
<script>
  export default {
    data(){
      return{
        count:[1,2,3,4,5,6,7,8,9,10]
      }
    },
    methods:{
      output(msg){
        console.log(msg)
      }
    }
  }
</script>

3.事件修饰符

javascript中,我们经常会调用event.preventDefault()event.stopPropagation等方法来阻止默认方法或是停止冒泡

vue中,存在许多事件修饰符来简化这些操作。

v-on属性可以填入.stop、.prevent、once、enter事件修饰符来进行更改。

event.preventDefault()vue中的实现为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<template>
  <a @click.prevent="output" href="https://outercyrex.github.io">测试</a>
</template>
<script>
  export default {
    data(){
      return{
        count:[1,2,3,4,5,6,7,8,9,10]
      }
    },
    methods:{
      output(e){
        <!-- e.preventDefault() -->
        console.log(e.target)
      }
    }
  }
</script>

我们当然可以通过event.preventDefault()来实现,只是vue为我们提供了.prevent来简化这一步骤。

事件修饰符 作用
.stop 阻止事件冒泡,等同于event.stopPropagation()
.prevent 防止默认操作,等同于event.preventDefault()
.self 阻止事件继承,如某按钮内有多个子节点,则点击事件只对该按钮有效,对其子节点无效
.capture 使用.capture修饰符时,事件监听器会在捕获阶段被触发,即该事件比冒泡阶段的事件监听器更早地接收到事件
.once 该事件只会触发一次
.passive 对应事件的默认行为将立即发生而非等待对应函数完成
1
<div @scroll.passive="onScroll">...</div>

以上述代码为例,由于使用了.passive修饰符,浏览器知道这个方法不会阻止滚动事件的默认行为,因此可以立即执行滚动操作,无需等待javascript执行完成。

4.数组变化侦查

javascript中,存在多种数组方法,有些数组方法会变更原数组,也会影响v-for的渲染效果,这些方法被称为变更方法,包括:

  • push()
  • pop()
  • shift()
  • unshift()
  • splice()
  • sort()
  • reverse()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<template>
  <button @click="output">增加数据</button>
  <div v-for="(item) of count">{`{item}`}</div>
</template>
<script>
  export default {
    data(){
      return{
        count:[1,2,3,4,5,6,7,8,9,10]
      }
    },
    methods:{
      output(){
        this.count.push(11)
      }
    }
  }
</script>

上述所列举的数组方法均可实现实时更新。

另一部分方法则会创建一个新数组,并不会变更原数组,这些方法被称为替换方法,包括:

  • filter()
  • concat()
  • slice()

这些方法并不会变更原本的数组,而是返回一个新数组。

我们可以给原数组赋值为concat返回的数组,也可以实现变更方法的效果。

1
2
3
4
5
6
methods:{
  output(){
    this.count = this.count.concat([11])
    console.log(this.count)
  }
}

四.计算属性

计算属性的定位类似于函数,用于处理重复且冗杂的代码块,将其单独处理为一个函数,便于调用和更改。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<template>
  <div v-for="item of count">{`{item}`}</div>
  <p></p>
</template>
<script>
  export default {
    data(){
      return{
        count:[1,2,3,4,5,6,7,8,9,10]
      }
    },
    computed:{
      isEmpty:function(){
        return this.count.length > 0 ? 'yes' : 'no'
      }
    }
  }
</script>

vue中,计算属性都被放入computed对象内,以函数的形式出现和调用。

注意,计算属性在被调用时,一定传出的是函数本身而不是其调用结果

其与方法methods的区别不仅如此,还在缓存上存在区别:

  • 计算属性计算属性值会基于其响应式依赖被缓存。一个计算属性仅会在其响应式依赖更新时才重新计算。

  • 方法方法调用总是会在重渲染发生时再次执行函数。

简单而言,计算属性静态的,其调用结果会被缓存,每次被调用时直接返回对应的结果,而不是重新计算。方法则是每次调用时都会再次执行。

五.特殊绑定

特殊绑定主要是针对class属性和style属性而言的。

1.class绑定

我们可以通过在class的内容中添加限定,来管理class属性的内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<template>
  <div :class="{ 'active':isActive }">class绑定展示</div>
</template>

<script>
export default {
  data(){
    return{
      isActive:true,
    }
  }
}
</script>
<style>
.active{
  color:red;
}
</style>

上述代码中,'active'是否是class的内容取决于isActive返回的布尔值。且该class是可以被css选择器选中的。

我们也可以通过多对象绑定来简化过程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<template>
  <div :class="classObj">class绑定展示</div>
</template>

<script>
export default {
  data(){
    return{
      classObj:{
        active:true,
        danger:false,
      }
    }
  }
}
</script>

如上述代码所示,我们直接传入了一个对象,则vue会根据对应字段的布尔值来判断是否将该字段在class内渲染。

我们也可以直接传入数组。

1
2
3
4
5
6
7
8
9
10
11
12
13
<template>
  <div :class="classObj">class绑定展示</div>
</template>

<script>
export default {
  data(){
    return{
      classObj:['active','danger']
    }
  }
}
</script>

数组是无法对应布尔值的,数组内的元素都会被渲染出来。

想对应布尔值便需要再数组内再写入对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<template>
  <div :class="[{active:isActive},danger]">class绑定展示</div>
</template>

<script>
export default {
  data(){
    return{
      isActive:true,
      danger:"error",
    }
  }
}
</script>

注意,一定是数组内写入对象,对象内是不能写入数组的。

2.style绑定

style绑定是针对内联style属性而言的。其书写规范与class绑定类似。

且注意,对应的css属性一定要求是小驼峰命名法,否则就需要加'',这一点与dom中更改style的规范是一致的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<template>
  <div :style="{color:isColor,fontSize:fontsize}">class绑定展示</div>
</template>

<script>
export default {
  data(){
    return{
      isColor:"deepskyblue",
      fontsize:'30px',
    }
  }
}
</script>

我们同样也可以之间传入一个对象即可。

1
2
3
4
5
6
return{
      classBind:{
        color:'deepskyblue',
        fontSize:'30px'
      }
}

3.表单输入绑定

vue中,我们可以通过v-model来将表单的输入内容传入给某个变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<template>
  <form>
    <input type="text" v-model="message">
    <p> {`{ message }`} </p>
  </form>
</template>

<script>
export default {
  data(){
    return{
          message:""
    }
  }
}
</script>

我们可以认为,v-bind单项绑定,而v-model双向绑定,将用户输入与对应参数绑定。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<template>
  <form>
    <input type="checkbox" v-model="message">{`{message}`}
  </form>
</template>

<script>
export default {
  data(){
    return{
          message:false
    }
  }
}
</script>

除此之外,表单输入绑定也支持各种修饰符,如.lazy、.number、.trim

修饰符 作用
.lazy 只有发生change事件,如按回车等操作,才会传递参数
.number 将表单传入的参数自动转换为数值类型
.trim 去除用户输入末尾的空白字符,如空格
1
<input type="text" v-model.lazy="message">

4.模版绑定

如果我们想在vue<script>内获取某个元素的dom对象,我们便需要给对应的对象添加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
<template>
  <div ref="container" class="container">{`{ Hello }`}</div>
  <button @click="refDemo">显示</button>
</template>

<script>
export default {
  data(){
    return{
      Hello:"Hello,World!"
    }

  },
  methods:{
    refDemo:function(){
      console.log(this.$refs.container);
      console.log(this.$refs.container.innerText);
    }
  }
}
</script>

<!-- 输出结果 -->
<div class="container">Hello,World!</div>
Hello,World!

通过$refs检索获取的对象即是一个dom节点,我们可以对其进行任何javascript中的操作,如获取其innerHTML等。

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
<template>
  <div ref="container" class="container">{`{ Hello }`}</div>
  <button @click="refDemo">显示</button>
</template>

<script>
export default {
  data(){
    return{
      Hello:"Hello,World!"
    }
  },
  methods:{
    refDemo:function(){
      this.Hello = "OuterCyrex"
      // this.$refs.container.innerText = "OuterCyrex"
    }
  },
  watch:{
    Hello(newValue,oldValue){
      console.log(`从${oldValue}变为了${newValue}`);
    }
  }
}
</script>

注意,该方法并不能监听dom形式的更改,只能针对对应参数的更改。且其watch内的函数名必须与对应参数的名字一致。

其实际上是检测参数在<script>内的变化,因此dom方法无效。