likes
comments
collection
share

Vue2基础笔记回顾(二)

作者站长头像
站长
· 阅读数 11

Vue 数组的变异方法

目标

了解 v-for 更新的机制。

概念:所谓数组变异方法就是数组数据的变化会导致视图更新的方法,常见的有如下几个。

push()pop()shift()unshift()splice()sort()reverse()

非变异方法有这几个:slice()filter()concat()

<template>
    <div>
        <ul>
            <li v-for="(val, index) in arr" :key="index">{{ val }}</li>
        </ul>
        <button @click="revBtn">数组翻转</button>
        <button @click="sliceBtn">截取前3个</button>
    </div>
</template>

<script>
    export default {
        data() {
            return {
                arr: [5, 3, 9, 2, 1],
            }
        },
        methods: {
            revBtn() {
                // 使用变异方法 reverse
                this.arr.reverse()
            },
            sliceBtn() {
                // slice 是非变异方法,可以把处理后的结果重新赋值给 data 中的数据
                let newArr = this.arr.slice(0, 3)
                this.arr = newArr
            },
        },
    }
</script>

两种非响应式的操作

所谓非响应式,就是数据命名变了,但是视图没有更新,常见的有下面两种情况。

  • 通过索引去修改数组中的元素。
  • 给对象后续新增的 key 进行赋值。
  • 解决 this.$set(target, key, value) 或 Vue.set(target, key, value)

Vue 基础虚拟 DOM

虚拟 DOM 的概念。

  • 什么是虚拟 DOM:用 JS 对象来模拟真实 DOM(虚拟 DOM 的本质就是一个 JS 对象)。
  • 为什么要有虚拟 DOM:真实 DOM 会有上百个属性,绝大多是我们不需要关心的,都对比起来会造成性能浪费。
  • 虚拟 DOM 的好处:虚拟 DOM 是用 JS 对象对真实 DOM 的模拟,只包含了必要的属性,方便对比差异,提高了更新页面的性能。
  • Vue 模板和虚拟 DOM 的关系:.vue 文件中的 template 里写的标签我们称为模板,最终都要被 Vue 处理成虚拟 DOM 对象,再生成真实 DOM 片段,才会渲染显示到真实页面上。
  1. template 结构。
<template>
    <div id="box">
        <p class="my_p">123</p>
    </div>
</template>
  1. 对应的虚拟 DOM 结构。
const dom = {
    type: 'div',
    attributes: [{ id: 'box' }],
    children: {
        type: 'p',
        attributes: [{ class: 'my_p' }],
        text: '123',
    },
}
  1. 以后 Vue 数据更新。

    • 生成新的虚拟 DOM 结构。
    • 和旧的虚拟 DOM 结构对比。
    • 找不同,只更新变化的部分到页面,这个操作也叫打补丁。

Vue 基础 key 作用

key 不能是索引

演示下面代码的问题。

<template>
    <div>
        <ul id="myUL">
            <li v-for="(str, index) in arr" :key="index">
                {{ str }}
                <input type="text" />
            </li>
        </ul>
        <button @click="addFn">下标为1的位置新增一个</button>
    </div>
</template>

<script>
    export default {
        data() {
            return {
                arr: ['老大', '老二', '老三'],
            }
        },
        methods: {
            addFn() {
                this.arr.splice(1, 0, '新来的')
            },
        },
    }
</script>

分析代码的执行过程。

1. 【初始数据】 => ['老大', '老二', '老三']

2. 根据数组 arr 在内存中生成虚拟 DOM(为了方便观察,这儿直接写成了标签,其实应该是对象)
   <li :key="0"> 老大 <input type="text" /> </li>
   <li :key="1"> 老二 <input type="text" /> </li>
   <li :key="2"> 老三 <input type="text" /> </li>

3. 将内存中的虚拟 DOM 转成真实 DOM 并渲染到页面

4. 用户输入 123
1. 【新数据】 => ['老大', '新来的', '老二', '老三']

2. 根据数组 arr 在内存中生成新的虚拟 DOM
    <li :key="0"> 老大 <input type="text" /> </li>
    <li :key="1"> 新来的 <input type="text" /> </li>
    <li :key="2"> 老二 <input type="text" /> </li>
    <li :key="3"> 老三 <input type="text" /> </li>

3. 将内存中新的虚拟 DOM 和旧的虚拟 DOM 进行比较(VNode Diff4. 取出 key 0
    看一下旧的虚拟 DOM 中有没有(找到了就对比标签名及标签内容)
    发现有
    发现标签名 li 和内部的内容一样则直接进行复用(一动不动,就直接进行复用渲染完毕的真实 DOM,之前输入的 1 还会在那放着)

5. 取出 key 1
    看一下旧的虚拟 DOM 中有没有
    发现有
    发现标签名 li 一样则直接进行复用
    发现 li 内部的文本不一样则更新
    发现 li 内部的 input 标签一样则直接进行复用(一动不动,就直接进行复用渲染完毕的真实 DOM,之前输入的 2 还会在那放着)

6. 取出 key 2
    看一下旧的虚拟 DOM 中有没有
    发现有
    发现标签名 li 一样则直接进行复用
    发现 li 内部的文本不一样则更新
    发现 li 内部的 input 标签一样则直接进行复用(一动不动,就直接进行复用渲染完毕的真实 DOM,之前输入的 2 还会在那放着)

7. 取出 key 3
    看一下旧的虚拟 DOM 中有没有
    发现没有
    根据此虚拟 DOM 重新创建真实 DOM 并渲染

Vue2基础笔记回顾(二)

Vue2基础笔记回顾(二)

  • 理想情况:只创建一个新增的 li 节点是性能最好的。

  • 实际情况:更新了两个 li 中的内容,创建创建了一个新的 li 节点。

  • 结论总结:不建议用 index 当做 key,为什么?

    • 可能会带来不必要的 Diff,性能浪费(例如从列表顶部或中间删除、添加元素)。
    • 当页面存在 input 框时,界面可能会发生错乱。

key 不写可以吗?

key 不写,默认也是索引,同上

key 应该是什么?

应该是唯一不重复的 ID,一般是后端返回的。 (当页面仅仅是作为列表展示时,不存在往列表的非最后添加、删除等操作,也可以使用 index)。

1. 【初始数据】 => ['老大', '老二', '老三']

2. 根据数组 arr 在内存中生成虚拟 DOM
   <li :key="老大"> 老大 <input type="text" /> </li>
   <li :key="老二"> 老二 <input type="text" /> </li>
   <li :key="老三"> 老三 <input type="text" /> </li>

3. 将内存中的虚拟 DOM 转成真实 DOM 并渲染到页面

4. 用户输入 123
1. 【新数据】 => ['老大', '新来的', '老二', '老三']

2. 根据数组 arr 在内存中生成新的虚拟 DOM
    <li :key="老大"> 老大 <input type="text" /> </li>
    <li :key="新来的"> 新来的 <input type="text" /> </li>
    <li :key="老二"> 老二 <input type="text" /> </li>
    <li :key="老三"> 老三 <input type="text" /> </li>

3. 将内存中新的虚拟 DOM 和旧的虚拟 DOM 进行比较(VNode Diff4. 取出 key “老大”
    看一下旧的虚拟 DOM 中有没有(找到了就对比标签名及标签内容)
    发现有
    发现标签名 li 和内部的内容一样则直接进行复用(一动不动,就直接进行复用渲染完毕的真实 DOM,之前输入的 1 还会在那放着)

5. 取出 key “新来的”
    看一下旧的虚拟 DOM 中有没有
    发现没有
    根据此虚拟 DOM 重新创建真实 DOM 并渲染

6. 取出 key “老二”
    看一下旧的虚拟 DOM 中有没有
    发现有
    发现标签名 li 和内部的内容一样则直接进行复用(一动不动,就直接进行复用渲染完毕的真实 DOM,之前输入的 2 还会在那放着)

7. 取出 key “老三”
    看一下旧的虚拟 DOM 中有没有
    发现有
    发现标签名 li 和内部的内容一样则直接进行复用(一动不动,就直接进行复用渲染完毕的真实 DOM,之前输入的 3 还会在那放着)

Vue2基础笔记回顾(二)

Vue2基础笔记回顾(二)

key 作用是什么

总结:key 是用来标记虚拟 DOM 的,当数据变化后,Vue 会根据变化后的数据生成新的虚拟 DOM,DOM Diff 的时候能根据 key 快速找到旧的虚拟 DOM,然后用新的虚拟 DOM 和旧虚拟 DOM 进行比较,如果旧的虚拟 DOM 中找到了与新的虚拟 DOM 相同的 key。

  • 节点及内容没变化直接复用原来的真实 DOM。
  • 节点或内容变了,新的虚拟 DOM 会转成真实 DOM 并更新对应的页面部分。

Vue 基础 动态 class

用 v-bind 给标签 class 设置动态的值。

学习

  • 语法规则::class="{类名: 布尔值}"
  • 使用场景:Vue 变量控制标签是否应该有类名。
<template>
    <div>
        <p :class="{ red_str: bool }">动态 class</p>
    </div>
</template>

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

Vue 基础 动态 style

给标签动态设置 style 的值。

学习

语法规则::style="{css 属性: 值}"

<template>
    <div>
        <p :style="{ backgroundColor: colorStr }">动态style</p>
    </div>
</template>

<script>
    export default {
        data() {
            return {
                colorStr: 'red',
            }
        },
    }
</script>

案例 品牌管理

准备结构

  • 需求:把默认数据显示到表格上;资产超过 100 的,都用红色字体标记出来。

  • 步骤分析。

    1. 复制数据和标签模板,铺设静态页面。
    2. 此案例使用 bootstrap,需要下载,并导入到入口文件 main.js 中。
    3. 用 v-for 配合默认数据,把数据默认铺设到表格上显示。
    4. 大于 100 价格,动态绑定 class 设置 red 类名。

Vue2基础笔记回顾(二)

步骤

  1. 安装 bootstrap。
yarn add bootstrap 
  1. 引入 bootstrap,main.js
import 'bootstrap/dist/css/bootstrap.css' 
  1. 模板代码(直接复制)。
<template>
    <div id="app">
        <div class="container">
            <!-- 顶部框模块 -->
            <div class="form-group">
                <div class="input-group">
                    <h4>品牌管理</h4>
                </div>
            </div>

            <!-- 数据表格 -->
            <table class="table table-bordered table-hover mt-2">
                <thead>
                    <tr>
                        <th>编号</th>
                        <th>资产名称</th>
                        <th>价格</th>
                        <th>创建时间</th>
                        <th>操作</th>
                    </tr>
                </thead>
                <tbody>
                    <tr>
                        <td></td>
                        <td></td>

                        <!-- 如果价格超过100,就有red这个类 -->
                        <td class="red"></td>
                        <td></td>
                        <td><a href="#">删除</a></td>
                    </tr>
                </tbody>
                <!-- 
                <tfoot >
                    <tr>
                        <td colspan="5" style="text-align: center">暂无数据</td>
                    </tr>
                </tfoot>
                -->
            </table>

            <!-- 添加资产 -->
            <form class="form-inline">
                <div class="form-group">
                    <div class="input-group">
                        <input type="text" class="form-control" placeholder="资产名称" />
                    </div>
                </div>
                <br />
                <div class="form-group">
                    <div class="input-group">
                        <input type="text" class="form-control" placeholder="价格" />
                    </div>
                </div>
                <br />
                <!-- 阻止表单提交 -->
                <button class="btn btn-primary">添加资产</button>
            </form>
        </div>
    </div>
</template>

<script>
    export default {
        data() {
            return {
                name: '', // 名称
                price: 0, // 价格
                list: [
                    { id: 100, name: '外套', price: 199, time: new Date('2010-08-12') },
                    { id: 101, name: '裤子', price: 34, time: new Date('2013-09-01') },
                    { id: 102, name: '鞋', price: 25.4, time: new Date('2018-11-22') },
                    { id: 103, name: '头发', price: 19900, time: new Date('2020-12-12') },
                ],
            }
        },
    }
</script>

<style>
    .red {
        color: red;
    }
</style>
  1. 正确代码。
<tbody>
    <tr v-for="obj in list" :key="obj.id">
        <td>{{ obj.id }}</td>
        <td>{{ obj.name }}</td>

        <!-- 如果价格超过 100,就有 red 这个类,注意不要忘了 class 前面的冒号 -->
        <td :class="{red: obj.price > 100}">{{ obj.price }}</td>
        <td>{{ obj.time }}</td>
        <td><a href="#">删除</a></td>
    </tr>
</tbody>

新增品牌

  • 需求:实现把表单数据添加进表格功能;用户输入为空给出提示。

  • 步骤分析。

    1. 通过 v-model 指令收集 name 和 price。
    2. 给添加资产按钮,绑定点击事件(注意不要忘记阻止默认行为)。
    3. 在事件的回调中,判断用户内容是否符合规定。
    4. 组装成新对象数据,添加数据到数组中。

Vue2基础笔记回顾(二)

代码

正确代码。

<!-- 添加资产 -->
<form class="form-inline">
    <div class="form-group">
        <div class="input-group">
            <!-- #1 通过 v-model 指令收集 name 和 price -->
            <input type="text" class="form-control" placeholder="资产名称" v-model="name" />
        </div>
    </div>
    <br />
    <div class="form-group">
        <div class="input-group">
            <input type="text" class="form-control" placeholder="价格" v-model.number="price" />
        </div>
    </div>
    <br />
    <!-- #2 给添加资产按钮,绑定点击事件(注意不要忘记阻止默认行为) -->
    <button class="btn btn-primary" @click.prevent="addFn">添加资产</button>
</form>

<script>
    export default {
        // ...省略其他
        methods: {
            addFn() {
                // #3 在事件的回调中,判断用户内容是否符合规定
                if (this.name.trim().length === 0 || this.price === 0) {
                    alert('不能为空')
                    return
                }
                // #4 组装成新对象数据,添加数据到数组中
                this.list.push({
                    // 当前数组最后一个对象的 id+1 作为新对象 id 值
                    id: this.list[this.list.length - 1].id + 1,
                    name: this.name,
                    price: this.price,
                    time: new Date(),
                })
            },
        },
    }
</script>

删除品牌

  • 需求:点击删除的 a 标签,删除数据;没数据了要提示暂无数据的 tfoot。

  • 步骤分析。

    1. a 标签绑定点击事件并传递 id,<td><a href="#" @click.prevent="delFn(obj.id)">删除</a></td>
    2. 在事件回调中,根据 id 找索引,根据索引去删除。
    3. 删除光了要让 tfoot 显示。
    4. 解决删光了再新增的 Bug,{id: this.list.length === 0 ? 0 : this.list[this.list.length - 1].id + 1}

Vue2基础笔记回顾(二)

代码

<script>
    export default {
        // ...其他代码
        methods: {
            // ...其他代码
            delFn(id) {
                // 通过 id 找到这条数据在数组中下标
                const index = this.list.findIndex((obj) => obj.id === id)
                this.list.splice(index, 1)
            },
        },
    }
</script>

时间格式化

复制上个案例,在此基础上,把表格里的时间用过滤器+moment 模块,格式化成 YYYY-MM-DD 格式。

Vue2基础笔记回顾(二)

实现

  1. 下载 moment 处理日期的第三方工具模块。
yarn add moment 
  1. 定义方法,接收时间,返回经过 moment 处理后的格式。
// #1 在 methods 中定义方法
formatDate (val){
    return moment(val).format('YYYY-MM-DD')
}

// #2 在模板中使用方法
<td>{{ formatDate(obj.time)}}</td>

Vue 计算属性 computed

计算属性的基本使用。

学习

概念:依赖 data 中的数据而产生的结果,语法如下。

{
    computed: {
        "计算属性名" () {
            return "值"
        }
    }
}

需求:两数之和展示到页面上。

<template>
    <div>
        <p>{{ num }}</p>
    </div>
</template>

<script>
    export default {
        data() {
            return {
                a: 10,
                b: 20,
            }
        },
        computed: {
            num() {
                return this.a + this.b
            },
        },
    }
</script>

🤔 注意:计算属性也是 Vue 数据变量,所以不要和 data 里的变量/数据重名。

Vue 计算属性和方法

内容

  • 计算属性是基于它们的依赖(data 中的数据)进行缓存,只要依赖项不变,都是直接从缓存取结果,性能高。
  • 而方法都要每次重新调用/执行才能拿到处理后的结果。

Vue2基础笔记回顾(二)

<template>
    <div>
        <p>{{ reverseMessage }}</p>
        <p>{{ reverseMessage }}</p>
        <p>{{ reverseMessage }}</p>
        <p>{{ getMessage() }}</p>
        <p>{{ getMessage() }}</p>
        <p>{{ getMessage() }}</p>
    </div>
</template>

<script>
    export default {
        data() {
            return {
                msg: 'Hello, Vue',
            }
        },
        computed: {
            reverseMessage() {
                console.log('计算属性执行了')
                return this.msg.split('').reverse().join('')
            },
        },
        methods: {
            getMessage() {
                console.log('函数执行了')
                return this.msg.split('').reverse().join('')
            },
        },
    }
</script>

案例-品牌管理(总价和均价)

基于之前的案例,完成总价和均价的计算效果。

Vue2基础笔记回顾(二)

<!-- #1 末尾补充显示总价和均价的 tr -->
<tr style="background-color: #EEE">
    <td>统计:</td>
    <td colspan="2">总价钱为: {{ allPrice }}</td>
    <td colspan="2">平均价: {{ avgPrice }}</td>
</tr>

<script>
    export default {
        // ...源代码省略
        // #2 定义计算属性
        computed: {
            allPrice() {
                // #3 求总价
                return this.list.reduce((acc, cur) => acc + cur.price, 0)
            },
            avgPrice() {
                // #4 求均价 - 保留 2 位小数
                return (this.allPrice / this.list.length).toFixed(2)
            },
        },
    }
</script>

Vue 计算属性 完整写法

计算属性本质上也是变量,如果想要直接赋值,需要使用完整写法。

学习

语法如下。

{
    computed: {
        "属性名": {
            set(){
                // 设置数据会触发这儿
            },
            get() {
                // 获取数据会触发这儿
                return "值"
            }
        }
    }
}

需求:计算属性给 v-model 使用。

<template>
    <div>
        <p>firstName: {{ firstName }}</p>
        <p>lastName: {{ lastName }}</p>
        <input type="text" v-model="fullName" />
    </div>
</template>

<script>
    export default {
        data() {
            return {
                firstName: 'foo',
                lastName: 'bar',
            }
        },
        computed: {
            fullName: {
                // 给 fullName 赋值会自动触发 set 方法
                set(newValue) {
                    const arr = newValue.split('~')
                    this.firstName = arr[0]
                    this.lastName = arr[1]
                },
                // 使用 fullName 的值触发 get 方法
                get() {
                    return this.firstName + '~' + this.lastName
                },
            },
        },
    }
</script>

案例-单选影响全选

单选框都选中的时候,全选自动选中。

分析

  • 需求:单选都选中,全选自动选中。

  • 步骤。

    1. 复制静态结构和数据。
    2. 循环生成复选框和文字,用 obj.checked 属性和单选框进行 v-model 双向绑定。
    3. 定义 isAll 计算属性,值通过每一个单选框的 checked 属性计算而来。
    4. 使用 v-model 绑定 isAll,作用于全选 checkbox。

Vue2基础笔记回顾(二)

静态结构和数据(直接复制)。

<template>
    <div>
        <span>全选:</span>
        <input type="checkbox" />
        <button>反选</button>
        <ul>
            <li>
                <input type="checkbox" />
                <span>任务名</span>
            </li>
        </ul>
    </div>
</template>

<script>
    export default {
        data() {
            return {
                arr: [
                    {
                        name: '猪八戒',
                        checked: false,
                    },
                    {
                        name: '孙悟空',
                        checked: false,
                    },
                    {
                        name: '唐僧',
                        checked: false,
                    },
                    {
                        name: '白龙马',
                        checked: false,
                    },
                ],
            }
        },
    }
</script>

代码

<template>
    <div>
        <span>全选:</span>
        <input type="checkbox" v-model="isAll" />
        <button>反选</button>
        <ul>
            <li v-for="(obj, index) in arr" :key="index">
                <input type="checkbox" v-model="obj.checked" />
                <span>{{ obj.name }}</span>
            </li>
        </ul>
    </div>
</template>

<script>
    export default {
        data() {
            return {
                arr: [
                    {
                        name: '猪八戒',
                        checked: false,
                    },
                    {
                        name: '孙悟空',
                        checked: false,
                    },
                    {
                        name: '唐僧',
                        checked: false,
                    },
                    {
                        name: '白龙马',
                        checked: false,
                    },
                ],
            }
        },
        computed: {
            isAll() {
                return this.arr.every((obj) => obj.checked === true)
            },
        },
    }
</script>

案例 全选影响单选

步骤

  1. 改变 isAll 计算属性为完整写法,在 set 中获取到全选状态。
  2. 把全选状态同步给所有单选框。

Vue2基础笔记回顾(二)

<script>
    export default {
        // ...其他代码
        computed: {
            isAll: {
                set(val) {
                    this.arr.forEach((obj) => (obj.checked = val))
                },
                get() {
                    return this.arr.every((obj) => obj.checked === true)
                },
            },
        },
    }
</script>

案例-反选

目标

反选功能:点击反选,让所有单选框,各自取反。

Vue2基础笔记回顾(二)

<button @click="btn">反选</button>

<script>
    export default {
        // ...其他代码省略
        methods: {
            btn() {
                this.arr.forEach((obj) => (obj.checked = !obj.checked))
            },
        },
    }
</script>

Vue 侦听器-watch

可以侦听 data/computed 属性值改变,然后做出相应的操作。

学习

语法如下。

{
    watch: {
        "被侦听的属性名" (newVal, oldVal){

        }
    }
}

需求:侦听 name 的变化,打印变化后的新值。

<template>
    <div>
        <input type="text" v-model="name" />
    </div>
</template>

<script>
    export default {
        data() {
            return {
                name: '',
            }
        },
        watch: {
            name(newVal, oldVal) {
                console.log(newVal, oldVal)
            },
        },
    }
</script>

等价写法。

<script>
    export default {
        data() {
            return {
                name: '',
            }
        },
        watch: {
            name: {
                // handler 是固定的名字
                handler(newVal, oldVal) {
                    console.log(newVal, oldVal)
                },
            },
        },
    }
</script>

Vue 侦听对象内部数据的变化

<template>
    <div>
        <input type="text" v-model="user.name" />
        <input type="text" v-model="user.age" />
    </div>
</template>

<script>
    export default {
        data() {
            return {
                user: {
                    name: '',
                    age: 0,
                },
            }
        },
        watch: {
            // 只有 user 本身的变化才能被监听到
            /* user(newValue) {
                console.log(newValue)
            }, */
            user: {
                handler(newVal) {
                    console.log(newVal)
                },
                deep: true, // 深度监听 user 内部数据的变化
            },
        },
    }
</script>

案例-品牌管理(数据缓存)

持久化存储品牌数据。

​1. 在 watch 侦听 list 变化的时候,把最新的数组 list 转成 JSON 字符串存入到本地。

​2. data 里默认把 list 变量从本地取值,如果取不到给个默认的空数组。

效果

<script>
    import moment from 'moment'
    export default {
        data() {
            return {
                name: '',
                price: 0,
                // #2 本地取 list
                list: JSON.parse(localStorage.getItem('pList')) || [],
            }
        },
        // ...其他代码省略
        watch: {
            list: {
                handler() {
                    // #1 存入本地
                    localStorage.setItem('pList', JSON.stringify(this.list))
                },
                deep: true,
            },
        },
    }
</script>

Vue2基础笔记回顾(二)