1. 前言

具体细节参考 Vue 官方文档 ,这一章的内容非常重要,我们开发完成之后会通过一个案例将这些知识点全部串起来。

本文主要将讲解 Vue 的基础语法,包括如下内容:

  • 条件渲染( v-if 、v-else、v-else-if、v-show)
  • 列表渲染( v-for )
  • 属性绑定( v-bind、v-model )
  • 事件处理( v-on )
  • 计算属性( computed )
  • 监听器( watch )
  • Class 与 Style 绑定
  • 任务清单-案例

2. 条件渲染

如下四个条件渲染指令在开发中使用频率非常高,类似于java中的 ifelseelse-if。例如,任务状态信息等需求。

  • v-if
  • v-else
  • v-else-if
  • v-show

2.1. v-if

v-if:用于条件判断指令,一般用于界面内容的渲染,该指令会走表达式为 true 的分支。

2.2. v-else

v-else: 一般结合 v-if 使用,该指令走与 v-if 相反的分支。

2.3. v-else-if

v-else-if:提供的是相应于v-if的,它可以连续多次重复使用。

v-elsev-else-if 必须紧跟在 v-ifv-else-if 的后面,并且它们只能与相同父元素的 v-ifv-else-if 一起使用。

具体信息如下图

假设,根据 type 类型展示不同的字母。

  • HTML 模板
1
2
3
4
5
6
7
8
<body>
<div id="app">
<div v-if="type === 'A'"> A </div>
<div v-else-if="type === 'B'"> B </div>
<div v-else-if="type === 'C'"> C </div>
<div v-else> Not A/B/C </div>
</div>
</body>
  • Vue 实例和数据
1
2
3
4
5
6
7
8
<script>
new Vue({
el: '#app',
data: {
type: "A"
}
})
</script>
  • 结果信息

image-20230807160807290

2.4. v-show

v-show:也是条件渲染指令,控制元素显示或者隐藏。 用法与 v-if 一致,但不能和 v-elsev-else-if 一起使用。

假设,根据 type 类型展示不同的字母。

  • HTML 模板
1
2
3
4
5
<body>
<div id="app">
<div v-show="type === 'A'"> A </div>
</div>
</body>
  • Vue 的实例和数据
1
2
3
4
5
6
7
8
<script>
new Vue({
el: '#app',
data: {
type: "A"
}
})
</script>
  • 结果信息

image-20230807160807290

2.5. v-ifv-show 区别

  1. 渲染方式

    • v-if:当条件为 false 时,元素中不会在 DOM 中存在,它会被完全移除。
    • v-show:当条件为 false 时,元素仍然在 DOM 中存在,但是通过CSS样式将其隐藏(display)。
  2. 性能开销

    • v-if:当条件为 true 时,Vue 会重新创建元素并渲染。当条件为 false 时,元素不会被渲染。这也就是说只要切换条件,元素都需要被创新创建和销毁,如果切换频繁这需要的性能开销就比较大了。
    • v-show:当条件不管是 true 或者 false 元素都已经被渲染完成了,所以就不存在切换时的性能开销。
  3. 如何选择

    • v-if:当条件为 false 时,我们也希望减少相关的 DOM 数量,那我们使用 v-if 就是合适的。

      例如如下代码中,我希望展示列表页时,删除详情页的页面信息减少请求次数,就可以使用 v-if 来完成。

      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
      <template>
      <div class="class-style">
      <!-- 根据状态渲染不同组件 -->
      <!-- 当状态为 'list' 时,显示列表页 -->
      <list-page v-if="status === 'list'"></list-page>

      <!-- 当状态为 'detail' 时,显示详情页 -->
      <detail-page v-else-if="status === 'detail'"></detail-page>
      </div>
      </template>

      <script>
      import ListPage from './ListPage.vue'; // 导入列表页组件
      import DetailPage from './DetailPage.vue'; // 导入详情页组件

      export default {
      components: {
      ListPage,
      DetailPage
      },
      data() {
      return {
      status: 'list', // 默认状态为 'list'
      };
      }
      };
      </script>
    • v-show:如果需要频繁切换时,而不产生较大的性能开销时,我们就可以使用 v-show

      例如如下代码中, 展开/收起文章的详细内容时。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      <template>
      <div id="app">
      <h3>{{ article.title }}</h3>
      <button @click="toggleContent(article)">Toggle Content</button>
      <div v-show="article.showContent">
      {{ article.content }}
      </div>
      </div>
      </template>

      <script>
      export default {
      el: '#app',
      data: {
      article: { title: 'Article', content: 'Content of Article', showContent: false }
      },
      methods: {
      toggleContent(article) {
      article.showContent = !article.showContent;
      }
      }
      };
      </script>

3. 列表渲染

列表渲染(v-for)是非常非常非常常用的一个指令,在开发中 100% 会使用到的一个指令,类似于 java 中的 for 循环。

3.1. v-for

v-for:基于一个数组进行列表的渲染。例如,我们的 form 表单就一定会使用到。

  • 语法
1
2
3
4
5
6
7
8
<li v-for="{ message } in items">
{{ message }}
</li>

<!-- 有 index 索引时 -->
<li v-for="({ message }, index) in items">
{{ message }} {{ index }}
</li>

假设我们有一个数组,需要将数组里面的元素全部遍历出来,使用 v-for 实现。

  • HTML 模板

    1
    2
    3
    4
    5
    6
    7
    <body>
    <div id="app">
    <ul v-for="item in items" :key="item.message">
    <li>{{ item.message }}</li>
    </ul>
    </div>
    </body>
  • Vue 实例和数据

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <script>
    new Vue({
    el: "#app",
    data: {
    items: [
    { message: 'Foo' },
    { message: 'Bar' }
    ]
    }
    })
    </script>
  • 结果

    image-20230807171914191


4. 属性绑定

属性绑定:在前端页面中如果需要将数据绑定在 HTML 元素或者表单的属性上时,我们需要这些属性绑定的值可以随着数据的变化而进行更新。

在属性绑定中,常用的指令有如下两种:

  • v-bind
  • v-model

4.1. v-bind

v-bind:用于将数据绑定到 HTML 元素 上,使属性值可以动态的随数据的变化而变化。

如果绑定的值是 null 或者 undefined,那么该 attribute 将会从渲染的元素上移除。

  • 语法
1
<div v-bind:id="dynamicId"></div>
  • 其中 v-bind 可以省略不写,直接简化为 :
1
<div :id="dynamicId"></div>

其中针对 v-bind 的典型用法应该时分页条,如下是 Element-ui 分页条 的用法。

image-20230807182109294

1
2
3
<template>
<el-pagination :page-size="20" :pager-count="11" layout="prev, pager, next" :total="1000" />
</template>

4.2. v-model

v-model:用于将数据绑定在 表单元素(文本输入框、复选框、单选按钮等等) 上创建双向绑定数据,可以实现用户的输入与数据之间的同步。

  • 语法
1
<input v-model="message" placeholder="edit me" />

假设我们将多个复选框绑定到同一个数组

  • HTML 模板
1
2
3
4
5
6
7
8
9
10
11
<body>
<div id="app">
<div>Checked names: {{ checkedNames }}</div>
<input type="checkbox" id="jack" value="Jack" v-model="checkedNames">
<label for="jack">Jack</label>
<input type="checkbox" id="john" value="John" v-model="checkedNames">
<label for="john">John</label>
<input type="checkbox" id="mike" value="Mike" v-model="checkedNames">
<label for="mike">Mike</label>
</div>
</body>
  • Vue 实例和数据
1
2
3
4
5
6
7
8
9
10
<script>
new Vue({
el: "#app",
data() {
return {
checkedNames: []
}
}
});
</script>
  • 结果

动画


5. 事件处理

事件处理:在前端开发中我们有时需要监听 DOM 事件,并在触发时执行相应的 JavaScript 代码,控制数据的变化或执行特定的操作。

事件处理的值可以是如下两种:

  • 内联事件处理器
  • 方法事件处理器
  • 自定义事件处理器(后面在组建通信时讲解)
  • 按钮修饰符
  • Vue 语法
1
<button v-on:click="handler"> Click Me </button>
  • 简写:省略 v-on: 修改为 @
1
<button @click="handler"> Click Me </button>

5.1. 内联事件处理器

内联事件处理器:指的是在 HTML 元素中指定事件监听函数,类似于 JavaScriptonclick 事件。

假设我们现在通过 内联事件处理器 实现点击按钮数字+1

  • HTML 模板
1
2
3
4
5
6
<body>
<div id="app">
<p> Count is: {{ count }} </p>
<button @click="count++">Add 1</button>
</div>
</body>
  • Vue 实例和数据
1
2
3
4
5
6
7
8
9
10
<script>
new Vue({
el: "#app",
data() {
return {
count: 0
}
}
})
</script>
  • 结果

动画

5.2. 方法事件处理器

方法事件处理器:如果简单的案例可以通过 内联事件处理器 进行解决,但业务一旦复杂,那么 内联事件处理器 的劣势(不够灵活)就出来了。这个时候我们将点击事件的处理逻辑提出来抽成一个方法,就形成了 方法事件处理器

假设我们现在通过 方法事件处理器 实现点击按钮数字+1。如下的案例虽然很简单,但是后续我们

  • HTML 模板
1
2
3
4
5
6
<body>
<div id="app">
<p> Count is: {{ count }} </p>
<button @click="add">Add 1</button>
</div>
</body>
  • Vue 实例和数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<script>
new Vue({
el: "#app",
data() {
return {
count: 0
}
},
methods: {
add() {
this.count++
}
}
})
</script>
  • 结果

动画


6. 计算属性

计算属性:我们在开发中,无法避免两个属性发生计算的,一旦某个属性的值发生变化,那么他们计算出来的结果也一定会发生变化,这个时候就需要使用到属性计算了。

6.1. computed

computed:当其依赖的属性的值发生变化的时,计算属性会重新计算。

  • 语法
1
2
3
4
5
6
7
8
export default {
data() {

},
computed: {

}
}

假设我们需要判断当前数组的数量是否显示元素,不使用 计算属性 computed

  • HTML 模板
1
2
3
4
5
6
<body>
<div id="app">
<p>Has published books:</p>
<span>{{ author.books.length > 0 ? 'Yes' : 'No' }}</span>
</div>
</body>
  • Vue 实例和数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<script>
new Vue({
el: "#app",
data() {
return {
author: {
name: 'John Doe',
books: [
'Vue 2 - Advanced Guide',
'Vue 3 - Basic Guide',
'Vue 4 - The Mystery'
]
}
}
}
})
</script>
  • 结果

image-20230808135722435

假设我们需要判断当前数组的数量是否显示元素,使用 计算属性 computed

计算属性带来的优点

  • 复用性高:如果在后期我们使用组件开发,那将会导致大量的冗余代码。如果使用 computed 把复杂的逻辑封装为一个属性,使模板中的代码更简洁、易读。
  • 性能提高: 计算属性具有自动缓存机制,只有当其依赖的响应式数据发生变化时,才会重新计算。

使用 computed 修改之后的代码如下

  • HTML 模板
1
2
3
4
5
6
<body>
<div id="app">
<p>Has published books:</p>
<span>{{ publishedBooksMessage }}</span>
</div>
</body>
  • 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
<script>
new Vue({
el: "#app",
data() {
return {
author: {
name: 'John Doe',
books: [
'Vue 2 - Advanced Guide',
'Vue 3 - Basic Guide',
'Vue 4 - The Mystery'
]
}
}
},
computed: {
// 一个计算属性的 getter
publishedBooksMessage () {
// `this` 指向当前组件实例
return this.author.books.length > 0 ? 'Yes' : 'No';
}
}
})
</script>

7. 监听器

监听器:监听观察某个事件(程序)发生变化时,事件发生者(事件源) 就会给注册该事件的监听者(监听器)发送消息,告诉监听者某些信息已经发生变化了。

7.1. watch

watch:在 Vue 中,监听器通常指的是 watch 去监听属性来响应数据的变化。

  • 浅层监听器语法
1
2
3
4
5
6
7
8
export default {
data() {

},
watch: {

}
}
  • 深层监听器语法
1
2
3
4
5
6
7
8
9
10
11
12
export default {
watch: {
someObject: {
handler(newValue, oldValue) {
// 注意:在嵌套的变更中,
// 只要没有替换对象本身,
// 那么这里的 `newValue` 和 `oldValue` 相同
},
deep: true
}
}
}

❔监听器和计算属性有什么区别呢

  • 异步操作:如果说我们需要的数据发生变化需要调用外部 API 时,使用计算属性是不行的,因为计算属性采用的是同步,而监听器采用的异步。

  • 监控多个数据变化:如果说我们需要监听多个数据源发生变化,并且只要其中一个数据发生变化时就执行操作。那么监听器是更加合适的。

假设我们使用监听器实现登录功能,需求如下:

  1. 用户名长度必须大于5
  2. 密码长度必须大于8
  3. 登录按钮必须满足以上两个条件才可以登录
  • HTML 模板
1
2
3
4
5
6
7
8
9
10
11
12
13
<body>
<div id="app">
<h2>Login Form</h2>

<label>Username:</label>
<input v-model="username" />

<label>Password:</label>
<input v-model="password" type="password" />

<button :disabled="!isValid">Login</button>
</div>
</body>
  • 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

<script>
new Vue({
el: "#app",
data() {
return {
username: "",
password: ""
};
},
computed: {
isValid() {
return this.username.length >= 5 && this.password.length >= 8;
}
},
watch: {
username(newValue, oldValue) {
console.log(`Username changed from ${oldValue} to ${newValue}`);
},
password(newValue, oldValue) {
console.log(`Password changed from ${oldValue} to ${newValue}`);
}
}
});
</script>
  • 结果

动画


8. Class 与 Style 绑定

ClassStyle 绑定:指的是将 HTML 元素的样式与 CSS 类进行联动的过程。

8.1. Class 绑定

通过为 HTML 元素添加一个或多个类名,你可以将元素与预定义的 CSS 样式规则关联起来。

  • HTML 模板
1
<div class="box red-border"></div>
  • CSS 样式
1
2
3
4
5
6
7
8
9
.box {
width: 100px;
height: 100px;
background-color: lightgray;
}

.red-border {
border: 2px solid red;
}

8.2. Style 绑定

内联样式是直接在 HTML 元素中使用 style 属性来定义样式。

  • HTML 模板
1
<div style="width: 100px; height: 100px; background-color: lightgray;"></div>

内联样式虽然灵活,但通常不推荐在大规模项目中频繁使用,因为它容易导致代码混乱,难以维护。

8.3. Vue 绑定 class

属性绑定的一个常见需求场景是操纵元素的 CSS class 列表和内联样式,这个需求是非常常见的。例如我们需要通过按钮控制表单是需要编辑还是保存。

  • 语法
1
<div :class="{ active: isActive }"></div>

假设我们通过按钮控制字体的样式

  • HTML 模板
1
2
3
4
5
6
<body>
<div id="app">
<button @click="toggleStatus(task)">Toggle Status</button>
<div :class="{ active: isActive, 'text-danger': hasError }">Dynamic Class Binding</div>
</div>
</body>
  • Vue 实例和数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<script>
new Vue({
el: "#app",
data() {
return {
isActive: false,
hasError: false
};
},
methods: {
toggleStatus() {
this.isActive = !this.isActive
this.hasError = !this.hasError
}
}
})
</script>
  • css 样式
1
2
3
4
5
6
7
8
<style>
.active {
font-weight: bold;
}
.text-danger {
color: red;
}
</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
30
31
<body>
<div id="app">
<button @click="toggleStatus(task)">Toggle Status</button>
<div :class="classObject">Dynamic Class Binding</div>
</div>
</body>
<script>
new Vue({
el: "#app",
data() {
return {
isActive: false,
hasError: false
};
},
computed: {
classObject() {
return {
active: this.isActive,
'text-danger': this.hasError
}
}
},
methods: {
toggleStatus() {
this.isActive = !this.isActive
this.hasError = !this.hasError
}
}
})
</script>

9. 任务清单-案例

需求如下

  1. 通过输入框输入任务点,添加到任务列表中;
  2. 在添加的任务点通过按钮实现任务是否完成以及删除任务点;
  3. 统计已经完成的任务数量
  4. 需要将任务列表添加进缓存,实现关闭浏览器也可以展示任务清单列表

效果图如下:

动画

  1. 实现前端页面

    • HTML 前端页面
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    <body>
    <div id="app">
    <h3>Todo 任务清单</h3>
    <input type="text" placeholder="回车添加任务清单" />
    <ul>
    <li>
    <span> 任务点 </span>
    <button> 是否完成 </button>
    <button> 删除任务 </button>
    </li>
    </ul>
    <p>已经完成的任务数量:0</p>
    </div>
    </body>
  2. 通过输入框输入任务点,添加到任务列表中

    • HTML 前端页面
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    <body>
    <div id="app">
    <h3>Todo 任务清单</h3>
    <input type="text" @keyup.enter="addTask" v-model="newTask" placeholder="回车添加任务清单" />
    <ul v-for="(task , index) in taskList" :key="index">
    <li>
    <span> {{ task.task }} </span>
    <button> 是否完成 </button>
    <button> 删除任务 </button>
    </li>
    </ul>
    <p>已经完成的任务数量:0</p>
    </div>
    </body>
    • Vue 实例和数据
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    <script>
    new Vue({
    el: "#app",
    data() {
    return {
    newTask: '',
    taskList: []
    }
    },
    methods: {
    // 添加任务点
    addTask() {
    if(this.newTask.trim() !== ''){
    this.taskList.push({task: this.newTask});
    this.newTask = ''
    }
    }
    }
    })
    </script>
    • 结果

    动画

  3. 在添加的任务点通过按钮实现任务 是否完成 以及 删除任务

    • HTML 前端页面
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    <body>
    <div id="app">
    <h3>Todo 任务清单</h3>
    <input type="text" @keyup.enter="addTask" v-model="newTask" placeholder="回车添加任务清单" />
    <ul v-for="(task , index) in taskList" :key="index">
    <li>
    <span :class="{ active: task.isActive }"> {{ task.task }} </span>
    <button @click="completedTask(task)">是否完成</button>
    <button @click="removeTask(index)">删除任务</button>
    </li>
    </ul>
    <p>已经完成的任务数量:0</p>
    </div>
    </body>
    • CSS 样式
    1
    2
    3
    4
    5
    6
    7
    <style>
    .active{
    color: green;
    text-decoration: line-through;
    }
    </style>

    • 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
    <script>
    new Vue({
    el: "#app",
    data() {
    return {
    newTask: '',
    taskList: []
    }
    },
    methods: {
    // 添加任务点
    addTask() {
    if(this.newTask.trim() !== ''){
    this.taskList.push({task: this.newTask, isActive: false});
    this.newTask = ''
    }
    },
    // 是否完成
    completedTask(task) {
    task.isActive = !task.isActive
    },
    // 删除任务
    removeTask(index) {
    this.taskList.splice(index, 1);
    }
    }
    })
    </script>
    • 结果

      动画

  4. 统计已经完成的任务数量

    • HTML 模板
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    <body>
    <div id="app">
    <h3>Todo 任务清单</h3>
    <input type="text" @keyup.enter="addTask" v-model="newTask" placeholder="回车添加任务清单" />
    <ul v-for="(task , index) in taskList" :key="index">
    <li>
    <span :class="{ active: task.isActive }"> {{ task.task }} </span>
    <button @click="completedTask(task)">是否完成</button>
    <button @click="removeTask(index)">删除任务</button>
    </li>
    </ul>
    <p>已经完成的任务数量:{{ finishedTotal }}</p>
    </div>
    </body>
    • Vue 实例和数据
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <script>
    new Vue({
    el: "#app",
    // 属性计算
    computed: {
    finishedTotal() {
    return this.taskList.filter(item => item.isActive).length;
    }
    }
    })
    </script>
    • 结果

    动画

  5. 需要将任务列表添加进缓存,实现关闭浏览器也可以展示任务清单列表

    • 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
    <script>
    new Vue({
    el: "#app",
    data() {
    return {
    newTask: '',
    taskList: []
    }
    },
    // 监听数组
    watch: {
    taskList: {
    handler(newTaskList, oldTaskList) {
    localStorage.setItem('taskList', JSON.stringify(newTaskList));
    },
    deep: true
    }
    },
    // 在生命周期中会讲到 created
    created() {
    const storedTasks = JSON.parse(localStorage.getItem("taskList"));
    if (storedTasks) {
    this.taskList = storedTasks;
    }
    }
    })
    </script>
    • 结果

    动画