Featured image of post 第二章 Vue 延伸

第二章 Vue 延伸

Vue 指令

指令修饰符

通过 . 指明一些指令后缀,不同后缀封装了不同的处理操作 → 简化代码

① 按键修饰符

  • @keyup.enter → 键盘回车监听

② v-model修饰符

  • v-model.trim → 去除首尾空格

  • v-model.number → 转数字

③ 事件修饰符

  • @事件名.stop → 阻止冒泡

  • @事件名.prevent → 阻止默认行为

1
<input placeholder="请输入..." class="new-todo" v-model="input" @keyup.enter="add" />

.enter 等价于 if 判断事件触发时 key 的值(addEventListener)

 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
<!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>
</head>

<body>
    <div id="app">
        <h3>@keyup.enter → 监听键盘回车事件</h3>
        <input @keyup="fn" v-model="username" type="text">
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
    <script>
        const app = new Vue({
            el: '#app',
            data: {
                username: ''
            },
            methods: {
                fn(e) {
                    if (e.key === 'Enter') {
                        console.log('键盘回车的时候触发', this.username)
                        this.username = ''
                    }
                }
            }
        })
    </script>
</body>

</html>

v-bind 对于样式控制的增强

操作 class

语法:v-bind:class = "对象 / 数组"

对象 → 键就是类名,值是布尔值。如果值为 true,有这个类,否则没有这个类。(一个类来回切换)

1
<div class="box" :class="{类名1:布尔值,类名2:布尔值}"></div>

数组 → 数组中所有的类,都会添加到盒子上,本质就是一个 class 列表。(多个类批量添加或删除)

1
<div class="box" :class="['类名1','类名2','类名3']"></div>
 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
<!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>
    <style>
        .box {
            width: 300px;
            height: 300px;
            margin: 30px 0px 30px 200px;
            border: 5px solid black;
            border-radius: 10px;
        }

        .pink {
            background-color: rgb(255, 236, 239);
        }

        .big {
            width: 500px;
            height: 300px;
        }
    </style>
</head>

<body>
    <div id="app">
        <div class="box" :class="{ pink: false, big: false }"></div>
        <div class="box" :class="['pink', 'big']"></div>
    </div>
    
    <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>

    <script>
        const app = new Vue({
            el: '#app',
            data: {

            }
        })
    </script>
</body>

</html>

案例:导航栏高亮效果。

 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
<!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>
    <style>
        * {
            margin: 0;
            padding: 0;
        }

        ul {
            display: flex;
            border-bottom: 2px solid #e01222;
            padding: 0 10px;
        }

        li {
            width: 100px;
            height: 50px;
            line-height: 50px;
            list-style: none;
            text-align: center;
        }

        li a {
            display: block;
            text-decoration: none;
            font-weight: bold;
            color: #333333;
        }

        li a.active {
            background-color: #e01222;
            color: #fff;
        }
    </style>
</head>

<body>
    <div id="app">
        <ul>
            <li><a class="active" href="#">京东秒杀</a></li>
            <li><a href="#">每日特价</a></li>
            <li><a href="#">品类秒杀</a></li>
        </ul>
    </div>

    <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
    
    <script>
        const app = new Vue({
            el: '#app',
            data: {
                list: [

                ]
            }
        })
    </script>
</body>

</html>
  1. 使用 v-for 基于数据动态渲染页面。

  2. 记录导航栏下标,用于标记 classactive,以显示高亮。

  3. 使用 @v-on: 监听事件。

 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
<!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>
    <style>
        * {
            margin: 0;
            padding: 0;
        }

        ul {
            display: flex;
            border-bottom: 2px solid #3ea1e4;
            padding: 0 10px;
        }

        li {
            width: 200px;
            height: 50px;
            line-height: 50px;
            list-style: none;
            text-align: center;
        }

        li a {
            display: block;
            text-decoration: none;
            font-weight: bold;
            color: #333333;
        }

        li a.active {
            background-color: #3ea1e4;
            color: #fff;
        }
    </style>
</head>

<body>
    <div id="app">
        <ul>
            <li v-for="(item, index) in list" :key="item.id" @mouseenter="activeIndex=item.id-1">
                <a :class="{active: index === activeIndex}" href="#">{{item.name}}</a>
            </li>
        </ul>
    </div>

    <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>

    <script>
        const app = new Vue({
            el: '#app',
            data: {
                list: [
                    { id: 1, name: '专栏' },
                    { id: 2, name: '活动' },
                    { id: 3, name: '直播' },
                    { id: 4, name: '课堂' },
                    { id: 5, name: '社区中心' },
                    { id: 6, name: '游戏中心' }
                ],
                activeIndex: 0
            }
        })
    </script>
</body>

</html>

操作 style

语法:style = "样式对象"

1
<div class="box" :style="{CSS属性名1: CSS属性值, CSS属性名2: CSS属性值} "></div>
 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
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        .box{
            width: 300px;
            height: 300px;
            background-color: gainsboro;
            margin: 5px;
        }
        .bigger{
            width: 400px;
            height: 400px;
        }
    </style>
</head>
<body>
    <div id="app">
        <div class="box" :style="{backgroundColor: 'pink'}"></div>
        <div class="box" v-bind:class="['bigger']"></div>
    </div>
    <script src="../vue.js"></script>
    <script>
        const app = new Vue({
            el: '#app',
            data: {

            },
            methods: {

            }
        })
    </script>
</body>
</html>

案例:进度条变化。

 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
<!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>
    <style>
        .progress {
            height: 25px;
            width: 400px;
            border-radius: 15px;
            background-color: #272425;
            border: 3px solid #272425;
            box-sizing: border-box;
            margin-bottom: 30px;
        }

        .inner {
            width: 50%;
            height: 19px;
            border-radius: 10px;
            text-align: right;
            position: relative;
            background-color: #409eff;
            background-size: 20px 20px;
            box-sizing: border-box;
            transition: all 1s;
        }

        .inner span {
            position: absolute;
            right: -20px;
            bottom: -25px;
        }
    </style>
</head>

<body>
    <div id="app">
        <div class="progress">
            <div class="inner">
                <span>50%</span>
            </div>
        </div>
        <button>设置25%</button>
        <button>设置50%</button>
        <button>设置75%</button>
        <button>设置100%</button>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
    <script>
        const app = new Vue({
            el: '#app',
            data: {

            }
        })
    </script>
</body>

</html>
  1. 使用 v-bind:: 操作 style

  2. button 添加点击事件,改变进度条长度。

  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
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
<!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>
    <style>
        .progress {
            height: 25px;
            width: 400px;
            border-radius: 15px;
            background-color: #272425;
            border: 3px solid #272425;
            box-sizing: border-box;
            margin-bottom: 30px;
        }

        .inner {
            width: 50%;
            height: 19px;
            border-radius: 10px;
            text-align: right;
            position: relative;
            background-color: #409eff;
            background-size: 20px 20px;
            box-sizing: border-box;
            transition: all 1s;
        }

        .inner span {
            position: absolute;
            right: -20px;
            bottom: -25px;
        }
    </style>
</head>

<body>
    <div id="app">
        <!-- 外层盒子底色 (黑色) -->
        <div class="progress">
            <!-- 内层盒子 - 进度(蓝色) -->
            <div class="inner" :style="{ width: percent + '%' }">
                <span>{{ percent }}%</span>
            </div>
        </div>
        <button @click="percent = 25">设置25%</button>
        <button @click="percent = 50">设置50%</button>
        <button @click="percent = 75">设置75%</button>
        <button @click="percent = 100">设置100%</button>
    </div>

    <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
    
    <script>
        const app = new Vue({
            el: '#app',
            data: {
                percent: 30
            }
        })
    </script>
</body>

</html>

v-model 应用于其他表单元素

v-model 可以快速获取或设置表单元素的值,且能够根据控件类型自动选取正确的方法来更新元素。

表单元素:

① 输入框 input:textvalue

② 文本域 textareavalue

③ 复选框 input:checkboxchecked

④ 单选框 input:radiochecked

⑤ 下拉菜单 selectvalue

案例:个人信息档案。

  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
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
<!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>
    <style>
        textarea {
            display: block;
            width: 395px;
            height: 100px;
            margin: 10px 0;
        }

        .info {
            width: 400px;
            height: 500px;
            border: solid rgb(0, 0, 0);
            padding-left: 20px;
            padding-right: 20px;
            border-radius: 20px;
            box-shadow: 5px 5px #ccc;
            ;
        }

        .study {
            width: 20px;
            margin: 0 5px 0 5px;
        }

        .study:first-child {
            width: 50px;
        }

        .necc {
            color: red;
        }
    </style>
</head>

<body>

    <div id="app" class="info">
        <h3>个人信息档案</h3>

        <span>
            姓名:<input type="text" v-model="username" style="width: 80px;"><span class="necc">*</span>
        </span>

        <span style="margin-left: 20px;">
            学号:<input type="text" v-model="stuId" style="width: 120px;"><span class="necc">*</span>
        </span>

        <p>
            <!-- name 属性使得单选框互斥,当选择其中一个的时候,另外的则取消勾选 -->
            <!-- value 向后台提交数据 -->
            性别:
            <input v-model="gender" type="radio" name="gender" value="1">            <input v-model="gender" type="radio" name="gender" value="2">        </p>

        <p>
            籍贯:
            <select v-model="cityId">
                <!-- option 需要设置 value 值,且 select 框的 value 值与选中的 option 的 value 值关联 -->
                <option value="001">北京</option>
                <option value="002">上海</option>
                <option value="003">成都</option>
                <option value="004">南京</option>
                <option value="005">长沙</option>
                <option value="006">武汉</option>
                <option value="007">...</option>
            </select>
        </p>

        <p>
            是否为共产党员:
            <input type="checkbox" v-model="isStar"><span class="necc">*</span>
        </p>

        <p>
            入学时间:<input type="text" v-model="year" class="study"><input type="text" v-model="month"
                class="study"><input type="text" v-model="day" class="study">        </p>

        <p>
            自我评价:
            <textarea v-model="desc" placeholder="本人积极向上..."></textarea>
        </p>

        <button>保存</button>
    </div>

    <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>

    <script>
        const app = new Vue({
            el: '#app',
            data: {
                username: '',
                isStar: false,
                gender: "2",
                cityId: '002',
                desc: "",
                stuId: '',
                year: '2023',
                month: '',
                day: ''
            }
        })
    </script>
</body>

</html>

computed 计算属性

基础语法

概念:基于现有的数据,计算出来的新属性。依赖的数据变化,自动重新计算。

语法:① 声明在 computed 配置项中,一个计算属性对应一个函数

​ ② 使用起来和普通属性一样使用 {{ 计算属性名 }}

注意:计算属性本质上是属性,因此在使用时不能加 (),如 {{ getCount() }} 是错误的。

 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
<!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>
    <style>
        table {
            border: 1px solid #000;
            text-align: center;
            width: 240px;
        }

        th,td {
            border: 1px solid #000;
        }
    </style>
</head>

<body>

    <div id="app">
        <table>
            <tr>
                <th>科目</th>
                <th>分数</th>
            </tr>
            <tr v-for="(item, index) in list" :key="item.id">
                <td>{{ item.name }}</td>
                <td>{{ item.num }}分</td>
            </tr>
        </table>

        <p>总分 {{ getCount }} 分</p>
    </div>

    <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>

    <script>
        const app = new Vue({
            el: '#app',
            data: {
                list: [
                    { id: 1, name: '英语', num: 67 },
                    { id: 2, name: '数学', num: 82 },
                    { id: 3, name: '语文', num: 75 },
                ]
            },
            computed:{
                getCount(){
                    // 遍历数组求和
                    let count = this.list.reduce((sum, item) => sum + item.num, 0)
                    return count
                }
            }
        })
    </script>
</body>

</html>

computed 和 methods

​ methods 侧重于处理业务,使用几次就需要计算几次;computed 侧重于求得结果,具有缓存特性,性能更好。

 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
83
84
85
86
87
88
89
90
<!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>
    <style>
        table {
            border: 1px solid #000;
            text-align: center;
            width: 300px;
        }

        th,td {
            border: 1px solid #000;
        }

        h3 {
            position: relative;
            margin-left: 120px;
        }

        span {
            position: absolute;
            left: 70px;
            top: -4px;
            width: 16px;
            height: 16px;
            color: white;
            font-size: 12px;
            text-align: center;
            border-radius: 50%;
            background-color: #e63f32;
        }
    </style>
</head>

<body>
    <div id="app">
        <h3>购物车🛒<span>{{ getCount() }}</span></h3>
        <table>
            <tr>
                <th>名字</th>
                <th>数量</th>
            </tr>
            <tr v-for="(item, index) in list" :key="item.id">
                <td>{{ item.name }}</td>
                <td>{{ item.num }}个</td>
            </tr>
        </table>

        <p>礼物总数:{{ getCount() }} 个</p>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
    <script>
        const app = new Vue({
            el: '#app',
            data: {
                // 现有的数据
                list: [
                    { id: 1, name: '篮球', num: 3 },
                    { id: 2, name: '玩具', num: 2 },
                    { id: 3, name: '铅笔', num: 5 },
                ]
            },

            methods: {
                getCount() {
                    console.log('methods方法执行了')
                    let count = this.list.reduce((sum, item) => sum + item.num, 0)
                    return count
                }
            },

            computed: {
                // 计算属性:有缓存的,一旦计算出来结果,就会立刻缓存
                // 下一次读取 → 直接读缓存就行 → 性能特别高
                // getCount() {
                //     console.log('计算属性执行了')
                //     let count = this.list.reduce((sum, item) => sum + item.num, 0)
                //     return count
                // }
            }
        })
    </script>
</body>

</html>

修改计算属性

计算属性默认的简写,只能读取访问,不能修改;如果要修改,则需要写计算属性的完整写法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
computed: {
	计算属性名() {
		一段代码逻辑 (计算逻辑)
		return 结果
	}
}

------------------------------------------------------------------------
    
computed: {
	计算属性名: {
		get() {
            一段代码逻辑 (计算逻辑)
            return 结果
        },
		set(修改的值) {
			一段代码逻辑 (修改逻辑)
        }
    }
}

案例:改名卡。

 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
<!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>
    <style>
        .t1 {
            margin: auto;
            width: 300px;
            height: 250px;
            border: 2px solid black;
            border-radius: 10px;
			text-align: center;
        }

        input {
            width: 190px;
        }

        button {
            margin-left: 10px;
        }
    </style>
</head>

<body>
    <div id="app" class="t1">
        <h2>姓名:{{fullName}} </h2>
        姓:<input type="text" v-model="firstName"><br><br>
        名:<input type="text" v-model="lastName"><br><br>
        <button @click="changeFirstName">修改姓</button>
        <button @click="changeLastName">修改名</button>
        <button @click="changeFullName">修改姓名</button><br><br>
    </div>

    <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>

    <script>
        const app = new Vue({
            el: '#app',
            data: {
                firstName: '约斯夫',
                lastName: '乔尼'
            },
            computed: {
                fullName() {
                    return this.firstName + "·" + this.lastName
                }
            },
            methods: {
                changeFirstName() {
                    this.firstName = '迈克尔'
                },
                changeFullName() {
                    this.fullName = '迈克尔杰克逊'
                },
                changeLastName() {
                    this.lastName = '杰克逊'
                },
            }
        })
    </script>
</body>

</html>

​ 如果使用 computed 计算完整姓名,直接修改 fullName 时提示 computed 没有配置 setter 逻辑,[Vue warn]: Computed property "fullName" was assigned to but it has no setter.

 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
<!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>
    <style>
        .t1 {
            margin: auto;
            width: 300px;
            height: 250px;
            border: 2px solid black;
            border-radius: 10px;
            text-align: center;
        }

        input {
            width: 190px;
        }

        button {
            margin-left: 10px;
        }
    </style>
</head>

<body>
    <div id="app" class="t1">
        <h2>姓名:{{fullName}} </h2>
        姓:<input type="text" v-model="firstName"><br><br>
        名:<input type="text" v-model="lastName"><br><br>
        <button @click="changeFirstName">修改姓</button>
        <button @click="changeLastName">修改名</button><br><br>
        <input type="text" class="change"><button @click="changeFullName">修改姓名</button>
    </div>

    <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>

    <script>
        const app = new Vue({
            el: '#app',
            data: {
                firstName: '约斯夫',
                lastName: '乔尼',
            },
            computed: {
                // fullName() {
                //     return this.firstName + "·" + this.lastName
                // }
                fullName: {
                    get: function(){
                        return this.firstName + "·" + this.lastName
                    },
                    set: function(a){
                        this.firstName = a.slice(0, 3)
                        this.lastName = a.slice(3)
                    }
                }
            },
            methods: {
                changeFirstName() {
                    this.firstName = '迈克尔'
                },
                changeFullName() {
                    this.fullName = document.querySelector('.change').value;
                },
                changeLastName() {
                    this.lastName = '杰克逊'
                },
            }
        })
    </script>
</body>

</html>

成绩案例

​ 编写程序,使得页面在没有数据时显示 “暂无数据”,有数据时,显示数据并将未及格的成绩标红,实现添加、删除、求总分及平均分等功能。

  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
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
<!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" />
    <link rel="stylesheet" href="./styles/index.css" />
    <title>Document</title>
    <style>
        .score-case {
            width: 1000px;
            margin: 50px auto;
            display: flex;
        }

        .score-case .table {
            flex: 4;
        }

        .score-case .table table {
            width: 100%;
            border-spacing: 0;
            border-top: 1px solid #ccc;
            border-left: 1px solid #ccc;
        }

        .score-case .table table th {
            background: #f5f5f5;
        }

        .score-case .table table tr:hover td {
            background: #f5f5f5;
        }

        .score-case .table table td,
        .score-case .table table th {
            border-bottom: 1px solid #ccc;
            border-right: 1px solid #ccc;
            text-align: center;
            padding: 10px;
        }

        .score-case .table table td.red,
        .score-case .table table th.red {
            color: red;
        }

        .score-case .table .none {
            height: 100px;
            line-height: 100px;
            color: #999;
        }

        .score-case .form {
            flex: 1;
            padding: 20px;
        }

        .score-case .form .form-item {
            display: flex;
            margin-bottom: 20px;
            align-items: center;
        }

        .score-case .form .form-item .label {
            width: 60px;
            text-align: right;
            font-size: 14px;
        }

        .score-case .form .form-item .input {
            flex: 1;
        }

        .score-case .form .form-item input,
        .score-case .form .form-item select {
            appearance: none;
            outline: none;
            border: 1px solid #ccc;
            width: 200px;
            height: 40px;
            box-sizing: border-box;
            padding: 10px;
            color: #666;
        }

        .score-case .form .form-item input::placeholder {
            color: #666;
        }

        .score-case .form .form-item .cancel,
        .score-case .form .form-item .submit {
            appearance: none;
            outline: none;
            border: 1px solid #ccc;
            border-radius: 4px;
            padding: 4px 10px;
            margin-right: 10px;
            font-size: 12px;
            background: #ccc;
        }

        .score-case .form .form-item .submit {
            border-color: #069;
            background: #069;
            color: #fff;
        }
    </style>
</head>

<body>
    <div id="app" class="score-case">
        <div class="table">
            <table>
                <thead>
                    <tr>
                        <th>编号</th>
                        <th>科目</th>
                        <th>成绩</th>
                        <th>操作</th>
                    </tr>
                </thead>
                <tbody>
                    <tr>
                        <td>1</td>
                        <td>语文</td>
                        <td class="red">46</td>
                        <td><a href="#">删除</a></td>
                    </tr>
                    <tr>
                        <td>2</td>
                        <td>英语</td>
                        <td>80</td>
                        <td><a href="#">删除</a></td>
                    </tr>
                    <tr>
                        <td>3</td>
                        <td>数学</td>
                        <td>100</td>
                        <td><a href="#">删除</a></td>
                    </tr>
                </tbody>
                <tbody>
                    <tr>
                        <td colspan="5">
                            <span class="none">暂无数据</span>
                        </td>
                    </tr>
                </tbody>

                <tfoot>
                    <tr>
                        <td colspan="5">
                            <span>总分:246</span>
                            <span style="margin-left: 50px">平均分:79</span>
                        </td>
                    </tr>
                </tfoot>
            </table>
        </div>
        <div class="form">
            <div class="form-item">
                <div class="label">科目:</div>
                <div class="input">
                    <input type="text" placeholder="请输入科目" />
                </div>
            </div>
            <div class="form-item">
                <div class="label">分数:</div>
                <div class="input">
                    <input type="text" placeholder="请输入分数" />
                </div>
            </div>
            <div class="form-item">
                <div class="label"></div>
                <div class="input">
                    <button class="submit">添加</button>
                </div>
            </div>
        </div>
    </div>
    
    <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>

    <script>
        const app = new Vue({
            el: '#app',
            data: {
                list: [
                    { id: 1, subject: '语文', score: 20 },
                    { id: 7, subject: '数学', score: 99 },
                    { id: 12, subject: '英语', score: 70 },
                ],
                subject: '',
                score: ''
            }
        })
    </script>
</body>

</html>
  1. v-if v-else 判断条件渲染或删除 tbody

  2. v-for 动态渲染tbody 中的数据, v-bind 控制不及格科目成绩的颜色。

  3. v-on 绑定删除事件,filter 覆盖数组,v-model 双向绑定数据,unshfit 更新视图。

  4. computed 计算属性求总分及平均分。

  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
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
<!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>
    <style>
        .score-case {
            width: 1000px;
            margin: 50px auto;
            display: flex;
        }

        .score-case .table {
            flex: 4;
        }

        .score-case .table table {
            width: 100%;
            border-spacing: 0;
            border-top: 1px solid #ccc;
            border-left: 1px solid #ccc;
        }

        .score-case .table table th {
            background: #f5f5f5;
        }

        .score-case .table table tr:hover td {
            background: #f5f5f5;
        }

        .score-case .table table td,
        .score-case .table table th {
            border-bottom: 1px solid #ccc;
            border-right: 1px solid #ccc;
            text-align: center;
            padding: 10px;
        }

        .score-case .table table td.red,
        .score-case .table table th.red {
            color: red;
        }

        .score-case .table .none {
            height: 100px;
            line-height: 100px;
            color: #999;
        }

        .score-case .form {
            flex: 1;
            padding: 20px;
        }

        .score-case .form .form-item {
            display: flex;
            margin-bottom: 20px;
            align-items: center;
        }

        .score-case .form .form-item .label {
            width: 60px;
            text-align: right;
            font-size: 14px;
        }

        .score-case .form .form-item .input {
            flex: 1;
        }

        .score-case .form .form-item input,
        .score-case .form .form-item select {
            appearance: none;
            outline: none;
            border: 1px solid #ccc;
            width: 200px;
            height: 40px;
            box-sizing: border-box;
            padding: 10px;
            color: #666;
        }

        .score-case .form .form-item input::placeholder {
            color: #666;
        }

        .score-case .form .form-item .cancel,
        .score-case .form .form-item .submit {
            appearance: none;
            outline: none;
            border: 1px solid #ccc;
            border-radius: 4px;
            padding: 4px 10px;
            margin-right: 10px;
            font-size: 12px;
            background: #ccc;
        }

        .score-case .form .form-item .submit {
            border-color: #069;
            background: #069;
            color: #fff;
        }
    </style>
</head>

<body>
    <div id="app" class="score-case">
        <div class="table">
            <table>
                <thead>
                    <tr>
                        <th>编号</th>
                        <th>科目</th>
                        <th>成绩</th>
                        <th>操作</th>
                    </tr>
                </thead>

                <tbody v-if="list.length > 0">
                    <tr v-for="(item, index) in list" :key="item.id">
                        <td>{{index+1}}</td>
                        <td>{{item.subject}}</td>
                        <td :class="{red:item.score<60}">{{item.score}}</td>
                        <td><a href="#" @click.prevent="del(item.id)">删除</a></td>
                    </tr>
                </tbody>

                <tbody v-else>
                    <tr>
                        <td colspan="5">
                            <span class="none">暂无数据</span>
                        </td>
                    </tr>
                </tbody>

                <tfoot>
                    <tr>
                        <td colspan="5">
                            <span>总分:{{getSum}}</span>
                            <span style="margin-left: 50px">平均分:{{getAvg}}</span>
                        </td>
                    </tr>
                </tfoot>
            </table>
        </div>
        <div class="form">
            <div class="form-item">
                <div class="label">科目:</div>
                <div class="input">
                    <input type="text" placeholder="请输入科目" v-model.trim="subject" />
                </div>
            </div>
            <div class="form-item">
                <div class="label">分数:</div>
                <div class="input">
                    <input type="text" placeholder="请输入分数" v-model.number="score" />
                </div>
            </div>
            <div class="form-item">
                <div class="label"></div>
                <div class="input">
                    <button class="submit" @click="add">添加</button>
                </div>
            </div>
        </div>
    </div>

    <script src="/MyPra/vue.js"></script>

    <script>
        const app = new Vue({
            el: '#app',
            data: {
                list: [
                    { id: 1, subject: '语文', score: 20 },
                    { id: 7, subject: '数学', score: 99 },
                    { id: 12, subject: '英语', score: 70 },
                ],
                subject: '',
                score: ''
            },
            methods: {
                del(id) {
                    // this.list = this.list.filter(item => item.id !== id)
                    let index = 0;
                    for(let i = 0; i < this.list.length; i++){
                        if(this.list[i].id === id){
                            index = i;
                        }
                    }
                    // console.log(this.list)
                    this.list.splice(index, 1);
                    // console.log(this.list)
                },
                add() {
                    if (!this.subject) {
                        alert('科目不能为空!')
                        return
                    }
                    if (typeof this.score !== 'number') {
                        alert('成绩输入格式错误!')
                        return
                    }
                    // push 在最后添加,unshift 在前面插入
                    this.list.unshift({
                        id: +new Date(),
                        subject: this.subject,
                        score: this.score
                    })
                    this.subject = ''
                    this.score = ''
                }
            },
            computed: {
                getSum() {
                    // return this.list.reduce((sum, item) => sum + item.score, 0)
                    let sum = 0;
                    for(let i = 0; i < this.list.length; i++){
                        sum += this.list[i].score;
                    }
                    return sum;
                },
                getAvg() {
                    if (this.list.length === 0) {
                        return 0
                    }
                    return (this.getSum / this.list.length).toFixed(2)
                }
            }
        })
    </script>
</body>

</html>

Vue 生命周期钩子函数

每个 Vue 实例在创建过程中都有一系列的初始化步骤。例如,创建函数绑定、编译模板、将实例挂在到 DOM 并在数据变化时触发 DOM 更新、销毁实例等。

通俗的说,Vue 实例从创建到销毁的过程,就是生命周期。在这个过程中会运行一些叫做生命周期钩子的函数,通过这些钩子函数可以定义业务逻辑。

(1)beforeCreate:在 Vue 实例开始初始化时调用。

(2)created:在实例创建之后,DOM 编译之前调用。

(3)beforeMount:在 DOM 渲染前调用。

(4)mounted:在 DOM 渲染后调用,等于 window.onload() 方法。

(5)beforeUpdate():在组件更新前调用。

(6)updated():在组件中的任意 DOM 元素更新后调用。

(7)beforeDestory:在销毁实例前调用,此时实例仍有效。

(8)destoryed:在实例销毁后调用。

 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
<!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>
</head>

<body>
    <div id="app"></div>

    <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>

    <script>
        const app = new Vue({
            el: '#app',
            beforeCreate() {
                console.log('beforeCreate');
            },
            created() {
                console.log('created');
            },
            beforeMount() {
                console.log('beforeMount');
            },
            mounted() {
                console.log('mounted');
            },
            beforeDestory() {
                console.log('beforeDestory');
            },
            destoryed() {
                console.log('destoryed');
            },
        })
    </script>
</body>

</html>

watch 侦听器

基础语法

作用:监听数据变化,执行一些业务逻辑异步操作

语法:① 简单写法 → 简单类型数据,直接监视。

1
2
3
4
5
6
7
8
watch: {
	数据属性名(newValue, oldValue) {
		一些业务逻辑或异步操作
	},
	'对象.属性名'(newValue, oldvalue) {
		一些业务逻辑或异步操作
	}
}

注意:watch 中函数的名称需要与在 data 中定义的属性名称一致。

 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
<!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>
</head>

<body>
    <div id="app">
        <p>{{ user.name }}</p>
        <p>{{ user.age }}</p>
        <textarea name="" id="" v-model="msg"></textarea>
    </div>
    <script src="./vue.js"></script>
    <script>
        const vue = new Vue({
            el: '#app',
            data() {
                return {
                    user: {
                        name: 'John',
                        age: 30
                    },
                    msg: ''
                }
            },
            watch: {
                user(newValue, oldValue) {
                    console.log('user changed');
                    console.log(oldValue);
                    console.log(newValue);
                },
                msg(newValue, oldValue) {
                    console.log(`${oldValue} changed to ${newValue}`);
                }
            },
            mounted() {
                // 这会触发上面的 watch 回调,因为 user 的引用改变了
                this.user = {
                    name: 'Doe',
                    age: 32
                };

                // 这不会触发上面的 watch 回调,因为 user 的引用没有改变
                this.user.name = 'Jane';
                this.user.age = 31;
            }
        });
    </script>

</body>

</html>

拓展:模拟实时翻译。

  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
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
<!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>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
            font-size: 18px;
        }

        #app {
            padding: 10px 20px;
        }

        .query {
            margin: 10px 0;
        }

        .box {
            display: flex;
        }

        textarea {
            width: 300px;
            height: 160px;
            font-size: 18px;
            border: 1px solid #dedede;
            outline: none;
            resize: none;
            padding: 10px;
        }

        textarea:hover {
            border: 1px solid #1589f5;
        }

        .transbox {
            width: 300px;
            height: 160px;
            background-color: #f0f0f0;
            padding: 10px;
            border: none;
        }

        .tip-box {
            width: 300px;
            height: 25px;
            line-height: 25px;
            display: flex;
        }

        .tip-box span {
            flex: 1;
            text-align: center;
        }

        .query span {
            font-size: 18px;
        }

        .input-wrap {
            position: relative;
        }

        .input-wrap span {
            position: absolute;
            right: 15px;
            bottom: 15px;
            font-size: 12px;
        }

        .input-wrap i {
            font-size: 20px;
            font-style: normal;
        }
    </style>
</head>

<body>
    <div id="app">
        <!-- 条件选择框 -->
        <div class="query">
            <span>翻译成的语言:</span>
            <select>
                <option value="italy">意大利</option>
                <option value="english">英语</option>
                <option value="german">德语</option>
            </select>
        </div>

        <!-- 翻译框 -->
        <div class="box">
            <div class="input-wrap">
                <textarea v-model="words"></textarea>
                <span><i>⌨️</i>文档翻译</span>
            </div>
            <div class="output-wrap">
                <div class="transbox">{{result}}</div>
            </div>
        </div>
    </div>

    <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>

    <script>
        // 接口地址:https://applet-base-api-t.itheima.net/api/translate
        // 请求方式:get
        // 请求参数:
        // (1)words:需要被翻译的文本(必传)
        // (2)lang: 需要被翻译成的语言(可选)默认值-意大利
        // -----------------------------------------------

        const app = new Vue({
            el: '#app',
            data: {
                words: '',
                obj: {
                    w1: ''
                },
                result: '',
                timer: null
            },
            watch: {
                // 检测 words 的变化,一般只使用新值,因此 oldValue 可以省略
                // words(newValue, oldValue) {
                //     console.log(newValue, oldValue);
                //     clearTimeout(this.timer);   // 防抖优化
                //     this.timer = setTimeout(async () => {
                //         const res = await axios({
                //             method: 'get',
                //             url: 'https://applet-base-api-t.itheima.ne s.data.data
                //     }, 300)
                // },
                // 如果检测某个对象里面的子属性,只需要改名字就可以了,但在 js 中不能出现.-等符号,因此需要用 '' 包裹
                'obj.w1'(newValue, oldValue) {
                    console.log(newValue, oldValue);
                },
                async words(newValue, oldValue) {
                    const res = await axios({
                        method: 'get',
                        url: 'https://applet-base-api-t.itheima.net/api/translate',
                        params: {
                            words: newValue
                        }
                    })
                    this.result = res.data.data;
                    console.log(res.data.data);
                }
            }
        })
    </script>
</body>

</html>

完整写法

语法:② 完整写法 → 添加额外配置项

(1) deep: true 对复杂类型深度监视

(2) immediate: true 初始化立刻执行一次 handler 方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
data: {
	obj: {
		words:苹果,
		lang: 'italy'
	}
},
watch: {
	数据属性名: {
		deep: true,
		handler (newValue) {
			console.log(newValue)}
		}
	}
}

完整写法:

  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
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
<!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>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
            font-size: 18px;
        }

        #app {
            padding: 10px 20px;
        }

        .query {
            margin: 10px 0;
        }

        .box {
            display: flex;
        }

        textarea {
            width: 300px;
            height: 160px;
            font-size: 18px;
            border: 1px solid #dedede;
            outline: none;
            resize: none;
            padding: 10px;
        }

        textarea:hover {
            border: 1px solid #1589f5;
        }

        .transbox {
            width: 300px;
            height: 160px;
            background-color: #f0f0f0;
            padding: 10px;
            border: none;
        }

        .tip-box {
            width: 300px;
            height: 25px;
            line-height: 25px;
            display: flex;
        }

        .tip-box span {
            flex: 1;
            text-align: center;
        }

        .query span {
            font-size: 18px;
        }

        .input-wrap {
            position: relative;
        }

        .input-wrap span {
            position: absolute;
            right: 15px;
            bottom: 15px;
            font-size: 12px;
        }

        .input-wrap i {
            font-size: 20px;
            font-style: normal;
        }
    </style>
</head>

<body>
    <div id="app">
        <!-- 条件选择框 -->
        <div class="query">
            <span>翻译成的语言:</span>
            <select v-model="obj.lang">
                <option value="italy">意大利</option>
                <option value="english">英语</option>
                <option value="german">德语</option>
            </select>
        </div>

        <!-- 翻译框 -->
        <div class="box">
            <div class="input-wrap">
                <textarea v-model="obj.words"></textarea>
                <span><i>⌨️</i>文档翻译</span>
            </div>
            <div class="output-wrap">
                <div class="transbox">{{result}}</div>
            </div>
        </div>
    </div>

    <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>

    <script>
        const app = new Vue({
            el: '#app',
            data: {
                obj: {
                    words: '',
                    lang: 'italy'
                },
                result: '',
                timer: null
            },
            watch: {
                // 'obj.words' (newValue, oldValue) {
                //     console.log(newValue, oldValue);
                //     clearTimeout(this.timer);   // 防抖优化
                //     this.timer = setTimeout(async () => {
                //         const res = await axios({
                //             method: 'get',
                //             url: 'https://applet-base-api-t.itheima.net/api/translate',
                //             params: {
                //                 words: newValue
                //             }
                //         })
                //         this.result = res.data.data
                //     }, 300)
                // },
                obj: {
                    deep: true,
                    // immediate: true,
                    handler(newValue, oldValue) {
                        console.log(newValue, oldValue);
                        clearTimeout(this.timer);
                        this.timer = setTimeout(async () => {
                            const res = await axios({
                                method: 'get',
                                url: 'https://applet-base-api-t.itheima.net/api/translate',
                                params: newValue
                            })
                            this.result = res.data.data
                        }, 300)
                    }
                }
            }
        })
    </script>
</body>

</html>

过滤器

对于一些需要经过复杂计算的数据绑定,简单的表达式可能无法实现,这时可以使用 vue.js 的过滤器进行处理,通过自定义过滤器可以对文本进行格式化。

过滤器可以用在插值表达式和 v-bind 指令中,其需要在被添加在 JavaScript 表达式的尾部,由符号 | 表示,格式如下:

1
2
3
4
<!-- 在插值表达式中 -->
{{ msg | filter }}
<!--  v-bind  -->
<div v-bind:id="msg | filter"></div>

定义过滤器有两种方式,第一种是通过全局方法 Vue.filter() 进行定义;第二种是应用选项对象中的 filters 选项进行定义。

全局过滤器

1
2
3
4
5
Vue.filter('capitalize', function (value) {
    if (!value) return ''
    value = value.toString() 
    return value.charAt(0).toUpperCase() + value.slice(1)
});

该方法定义了一个名为 capitalize 的全局过滤器,其中,Vue.filter 方法接受两个参数,第一个参数 capitalize 是过滤器的名称,第二个参数是一个函数,这个函数会接收从模板传递过来的值,并对其进行处理。

在该过滤器中,如果传入的 value 是假值(比如 nullundefined、空字符串等),则直接返回一个空字符串。这是为了防止在调用 toUpperCase() 方法时出现错误。

该过滤器将传入的值转换为一个字符串,将第一个字母大写并与剩余的字符串片段拼接起来,形成一个新的字符串。

 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
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <div>{{ name | capitalize }}</div>
    <script src="./vue.js"></script>
    <script>
        Vue.filter('capitalize', function (value) {
            if (!value) return ''
            value = value.toString()
            // 将其转换为一个字符串。

            return value.charAt(0).toUpperCase() + value.slice(1)
            // value.charAt(0) 获取字符串的第一个字符。
            // toUpperCase() 将第一个字符转换为大写。
            // value.slice(1) 获取除了第一个字符之外的所有字符。
        });
        const vue = new Vue({
            el: 'div',
            data: {
                name: 'bob'
            }
        })
    </script>
</body>

</html>

局部过滤器

这个局部过滤器 capitalize 只能在定义它的组件内部使用。

1
2
3
4
5
6
7
8
9
filters: {
    capitalize: function (value) {
        // 这是定义在组件的 `filters` 选项中的一个过滤器。
        // `filters` 是一个对象,其中的每个属性都是一个过滤器函数。
        if (!value) return ''
        value = value.toString()
        return value.charAt(0).toUpperCase() + value.slice(1)
    }
}

过滤器串联

1
{{ msg | filterA | filterB }}

多个过滤器可以串联使用,在串联使用过滤器时,先调用左边的过滤器,再依次往右调用。

 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
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <div>{{ str | lowercase | capitalize }}</div>
    <script src="./vue.js"></script>
    <script>
        const vue = new Vue({
            el: 'div',
            data: {
                str: 'hElLoW woRlD!'
            },
            filters: {
                lowercase: (str)=>{
                    return str.toLowerCase();
                },
                capitalize: (str)=>{
                    return str.charAt(0).toUpperCase() + str.slice(1);
                }
            }
        })
    </script>
</body>

</html>

案例:定义两个过滤器,第一个过滤器用来区分信息类别,使得不同标题的颜色不同,第二个过滤器用来将超出 15 个字符的新闻内容截取,以省略号结尾。

 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
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        .blue {
            color: lightskyblue;
        }
        .red {
            color: red;
        }
        .orange {
            color: orange;
        }
        .gray {
            color: gray;
        }
    </style>
</head>

<body>
    <div>
        <ul>
            <li v-for="(item, index) in list">
                <span>{{item.title}}</span>{{item.news}}
            </li>
        </ul>
    </div>
    <script src="./vue.js"></script>
    <script>
        const vue = new Vue({
            el: 'div',
            data: {
                list: [
            { title: '【公告】', news: '让“幸福产业”造福群众' },
            { title: '【头条】', news: '韩国宣布多地进入灾难状态沸' },
            { title: '【热点】', news: '张文宏谈退休:不会做牛做马一辈子' },
            { title: '【新闻】', news: '水清岸绿 家门口就有“诗和远方”' },
            { title: '【热点】', news: '200多万买二手房遭遇“20年租约”' },
            { title: '【热点】', news: '卖一台车亏10万 蔚来必须听劝了' },
            { title: '【新闻】', news: '成都竹子开花 错过再等60年' }
                ]
            }
        })
    </script>
</body>

</html>

实现方法:

观察代码可发现,标题颜色通过不同类来实现,因此可以使用 v-bind 绑定 class ,并使用过滤器对标题内容进行判断从而改变标题颜色。

新闻内容可以直接使用插值表达式加过滤器的方式修改,即判断字符串的长度大于 15 时,截取字符串内容,str.slice(0, 15),再在末尾加上 …。

 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
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        .blue {
            color: lightskyblue;
        }
        .red {
            color: red;
        }
        .orange {
            color: orange;
        }
        .gray {
            color: gray;
        }
        .omit{
            width: 200px;
            text-overflow: ellipsis;
            text-wrap: nowrap;
            overflow: hidden;
        }
    </style>
</head>

<body>
    <div>
        <ul>
            <li v-for="(item, index) in list" class="omit">
                <span :class="item.title | hot">{{item.title}}</span>{{item.news}}
            </li>
        </ul>
    </div>
    <script src="./vue.js"></script>
    <script>
        const vue = new Vue({
            el: 'div',
            data: {
                list: [
            { title: '【公告】', news: '让“幸福产业”造福群众' },
            { title: '【头条】', news: '韩国宣布多地进入灾难状态沸' },
            { title: '【热点】', news: '张文宏谈退休:不会做牛做马一辈子' },
            { title: '【新闻】', news: '水清岸绿 家门口就有“诗和远方”' },
            { title: '【热点】', news: '200多万买二手房遭遇“20年租约”' },
            { title: '【热点】', news: '卖一台车亏10万 蔚来必须听劝了' },
            { title: '【新闻】', news: '成都竹子开花 错过再等60年' }
                ]
            },
            filters: {
                hot: function (str) {
                    switch (str) {
                        case '【公告】':
                            return 'blue';
                        case '【头条】':
                            return 'red';
                        case '【热点】':
                            return 'orange';
                        default:
                            return 'gray';
                    }
                },
                omit: (str)=>{
                    if(str.length > 15){
                        return str.slice(0, 15) + '...';
                    }else{
                        return str;
                    }
                }
            }
        })
    </script>
</body>

</html>

练习

  1. 创建一个数组,当点击页面内按钮时,为数组添加一个 1-100 的随机整数,并在页面内显示数组中所有元素。
 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
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <div id="app">
        <!-- <span v-for="(item, index) in list">{{ item }}</span> -->
        <button @click="addRandom">添加</button>
        <br>
        <span>{{list.toString()}}</span>
    </div>
</body>
<script src="./vue.js"></script>
<script>
    const app = new Vue({
        el: '#app',
        data: {
            // list: [15, 61, 16, 18, 65, 95]
            list: []
        },
        methods: {
            addRandom() {
                const random = Math.floor(Math.random() * 100) + 1;
                this.list.push(random);
            }
        }
    })
</script>

</html>
  1. 随机给出一个 0 ~ 99(包括 0 和 99)的数字,然后让用户在规定的次数内猜出是什么数字。当用户随便猜一个数字输入后,游戏会提示该数字太大或太小,然后缩小结果范围,最终得出正确结果。界面设计如实验图 2-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
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
83
84
85
86
87
88
89
90
91
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        div {
            text-align: center;
            margin: 10px;
        }
    </style>
</head>

<body>
    <div>
        <h2>猜数字游戏</h2>
        <p>请输入一个0-99之间的随机整数</p>
        <div>
            <input type="text" name="" id="" v-model.number="guess" :disabled="subFlag">
            <button @click="submit" :disabled="subFlag">提交</button>
        </div>
        <div>
            <input type="text" name="" id="" disabled v-model="msg">
            <button @click="start" v-if="flag">开始</button>
        </div>
        <div>
            <span>当前还可以猜测次数:</span>
            <input type="text" name="" id="" disabled :value="count">
        </div>
        <div>
            <button @click="restart">再来一局</button>
        </div>
    </div>
    <script src="./vue.js"></script>
    <script>
        const vue = new Vue({
            el: 'div',
            data: {
                random: 0,
                flag: true,
                count: 3,
                msg: '点击按钮开始游戏!',
                guess: '',
                subFlag: false
            },
            methods: {
                start() {
                    this.flag = false;
                    this.random = Math.floor(Math.random() * 100)
                    this.msg = '请输入数字!'
                },
                submit() {
                    if(this.flag == true){
                        alert('请先点击开始按钮!');
                        return;
                    }
                    if (this.count == 1) {
                        console.log(111);
                        this.count--;
                        this.msg = '次数用尽,请重新开始游戏!';
                        this.subFlag = true;
                        return;
                    }      
                    if (this.guess > this.random) {
                        this.msg = '猜大了!';
                        this.count--;
                    }
                    if (this.guess < this.random) {
                        this.msg = '猜小了!';
                        this.count--;
                    }
                    if (this.guess == this.random) {
                        this.msg = '猜对了!要再来一局吗?';
                        this.subFlag = true;
                    }
                },
                restart(){
                    this.flag = true;
                    this.count = 3;
                    this.msg = '点击按钮开始游戏!'
                    this.guess = '';
                    this.subFlag = false;
                }
            }
        })
    </script>
</body>

</html>
  1. 使用 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
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <div id="app">
        <div v-for="a in b">
            <span v-for="b in a">
                {{a}} * {{b}} = {{a*b}}
            </span>
        </div>
    </div>
</body>
<script src="./vue.js"></script>
<script>
    const app = new Vue({ 
        el: '#app',
        data: {
            a: 9,
            b: 9,
        },
        methods: {

        }
    })
</script>

</html>
Blog for Sandy Memories