Vue基础学习笔记Day03_计算属性_过滤器_侦听器

今日目标:

  1. 能够了解key作用, 虚拟DOM, diff算法
  2. 能够掌握设置动态样式
  3. 能够掌握过滤器, 计算属性, 侦听器
  4. 能够完成品牌管理案例

学习内容:

学习目录:

  1. Vue基础
  2. Vue过滤器
  3. Vue计算属性
  4. Vue侦听器
  5. 今日总结
  6. 面试题

知识点自测

  • 会自己定义数据结构
"红色","red", "蓝色","blue", 
// 上面的数据结构, 要用1个变量来装这4个值, 用什么数据结构呢?(数组还是对象) 
   - 对象(可映射key->value)
    
"小明", "小蓝", "小赵"
// 上面的结构用数组比较合适
  • 马上能反应过来循环遍历是什么, 索引(下角标)是什么
let arr = [10, 32, 99];
// 索引就是数字, 标记每个值对应的序号, 从0开始
// 索引是0, 1, 2
// 数组需要用索引来换取值, 固定格式 arr[索引]
// 遍历就是挨个取出来
  • 数组的filter方法使用
let arr = [19, 29, 27, 20, 31, 32, 35];
let newArr = arr.filter((val) => {return val >= 30})
// let newArr = arr.filter((val) => val >= 30)
// 数组调用.filter()方法 - 传入一个函数体 (固定格式)
// 运行过程: filter会遍历数组里的每一项, 对每一项执行一次函数体(会把每个值传给形参)
// 作用: 每次遍历如果val值符合return的条件, 就会被filter收集起来
// 返回值: 当filter遍历结束以后, 返回收集到的符合条件的那些值形成的新数组
console.log(newArr);
  • 重绘与回流(重排)的概念
回流(重排): 当浏览器必须重新处理和绘制部分或全部页面时,回流就会发生  (布局变化)

重绘: 不影响布局, 只是标签页面发生变化, 重新绘制  (标签变化)

注意: 回流(重排)必引发重绘, 重绘不一定引发回流(重排)
  • localStorage浏览器本地存储语法使用
localStorage.setItem("key名", 值) - 把值存在浏览器本地叫key的对应位置上

localStorage.getItem("key名") - 把叫key的对应值, 从浏览器本地取出来

==值只能是字符串类型, 如果不是请用JSON.stringify转, 取出后用JSON.parse转==
  • JSON的方法使用
JSON.stringify(JS数据) - 把JS数据序列化成JSON格式字符串

JSON.parse(JSON字符串)  - 把JSON格式化字符串, 再转回成JS数据

1. vue基础

1.0_vue基础 v-for更新监测

目标: 当v-for遍历的目标结构改变, Vue触发v-for的更新

  • 情况1: 数组翻转
  • 情况2: 数组截取
  • 情况3: 更新值

口诀:

  • 数组变更方法, 就会导致v-for更新, 页面更新
  • 数组非变更方法, 返回新数组, 就不会导致v-for更新, 可采用覆盖数组或this.$set()

代码:

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

<script>
export default {
  data(){
    return {
      arr: [5, 3, 9, 2, 1]
    }
  },
  methods: {
    revBtn(){
      // 1. 数组翻转可以让v-for更新
      this.arr.reverse()
    },
    sliceBtn(){
      // 2. 数组slice方法不会造成v-for更新
      // slice不会改变原始数组
      // this.arr.slice(0, 3)

      // 解决v-for更新 - 覆盖原始数组
      let newArr = this.arr.slice(0, 3)
      this.arr = newArr
    },
    updateBtn(){
      // 3. 更新某个值的时候, v-for是监测不到的
      // this.arr[0] = 1000;

      // 解决-this.$set()
      // 参数1: 更新目标结构
      // 参数2: 更新位置
      // 参数3: 更新值
      this.$set(this.arr, 0, 1000)
    }
  }
}
</script>

<style>

</style>

这些方法会触发数组改变, v-for会监测到并更新页面(以下即为数组的变更方法)

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

这些方法不会触发v-for更新

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

注意: vue不能监测到数组里赋值的动作而更新, 如果需要请使用Vue.set() 或者this.$set(), 或者覆盖整个数组

总结: 改变原数组的方法才能让v-for更新

  • 理解:
    • 数组的变更方法,对原数组进行变更(原数组变更,界面才会跟着一起变化)
    • 数组的非变更方法,不对原数组进行变更,而是返回一个新数组
  1. 哪些数组方法会导致v-for更新页面?
    1. 可以改变原数组的方法
  2. 有的数组方法不导致v-for更新页面, 如何处理?
    1. 拿返回的新数组, 直接替换旧数组
    2. this.$set()方法更新某个值

1.1_vue基础_v-for就地更新

v-for 的默认行为会尝试原地修改元素而不是重新创建

PS:

  • 虚拟Dom下边介绍
  • 红色部分,代表的是改变的部分
  • 新旧Dom对比之后,白色部分可以复用,红色部分需要更新
  • 原地更新,就是尽可能多的利用之前的dom,不会重新全部创建

这种 虚拟DOM对比方式, 可以提高性能 - 但是还不够高

v-for更新时, 是如何操作DOM的?

  • 循环出新的虚拟DOM结构, 和旧的虚拟DOM结构对比, 尝试复用标签就地更新内容

1.2_vue基础_虚拟dom

目标: 了解虚拟DOM的概念

  • template标签说明:

    • 在.vue文件中,标签都是要写在template模板标签中的,
    • 这些标签其实不会直接挂载到index.html的dom中
    • 因为浏览器是无法识别vue的指令的,v-for,v-if等等
    • 所以模板内的标签, 都要被vue处理成虚拟DOM对象, 才会渲染显示到真实DOM页面上
  • 虚拟dom

    • 虚拟dom的本质:保存了节点信息,属性,内容,的一个js对象
    • 只存储了,标签中添加的属性
  • 真实dom

    • 真实dom:其实也是保存了节点信息,属性,内容,的一个js对象,但是他已经被渲染到dom树上了
    • 并且真实dom的属性过多(上图p的属性有297个),因为真实dom标签有很多默认属性
    • 即使我们在标签中只给p添加了一个class属性,但是打印p对象之后,还是会发现有297个属性
  • 所以虚拟dom比真实dom的效率会高很多
    虚拟dom在遍历时,可以快速的知道哪个属性改变了

  • vue数据更新(在内存中比较差异, 然后给真实DOM打补丁更新上(更新真实dom,又叫打补丁))

    • 生成新的虚拟DOM结构
    • 和旧的虚拟DOM结构对比(对比是同级进行对比,ul比ul,li比li)
    • 利用diff算法, 找不不同, 只更新变化的部分(重绘/回流)到页面 - 也叫打补丁
    • 好处1: 提高了更新DOM的性能(不用把页面全删除重新渲染)
    • 好处2: 虚拟DOM只包含必要的属性(没有真实DOM上百个属性)

总结: 虚拟DOM保存在内存中, 只记录dom关键信息, 配合diff算法提高DOM更新的性能

  1. 虚拟DOM是什么?
    1. 本质就是一个JS对象, 保存DOM关键信息
  2. 虚拟DOM好处?
    1. 提高DOM更新的性能, 不频繁操作真实DOM, 在内存中找到变化部分, 再更新真实DOM(打补丁)

1.3_vue基础_diff算法

  • vue用diff算法, 新虚拟dom, 和旧的虚拟dom比较
  • diff:difference,不同

情况1: 根元素变了, 删除重建

旧虚拟DOM

<div id="box">
    <p class="my_p">123</p>
</div>

新虚拟DOM

<ul id="box">
    <li class="my_p">123</li>
</ul>

情况2: 根元素没变, 属性改变, 元素复用, 更新属性

旧虚拟DOM

<div id="box">
    <p class="my_p">123</p>
</div>

新虚拟DOM

<div id="myBox" title="标题">
    <p class="my_p">123</p>
</div>
  1. diff算法如何比较新旧虚拟DOM?
    1. 同级比较
  2. 根元素变化?
    1. 删除重新建立整个DOM树
  3. 根元素未变, 属性改变?
    1. DOM复用, 只更新属性

1.4_vue基础_diff算法-key

无key - 就地更新

  • 如果没有key,v-for会最大限度的就地更新,复用相同类型元素
  • 代码:
<ul id="myUL">
    <li v-for="str in arr">
        {{ str }} 
        <input type="text">
    </li>
</ul>
<button @click="addFn">下标为1的位置新增一个</button>
export default {
    data(){
        return {
            arr: ["老大", "新来的", "老二", "老三"]
        }
    },
    methods: {
        addFn(){
            this.arr.splice(1, 0, '新来的')
        }
    }
};
  • 效果:发现原来老二的数值2,跑到了新来的身上了,这种效果是不对的

  • 旧 - 虚拟DOM结构 和 新 - 虚拟DOM结构 对比过程

  • 现象:性能不高, 从第二个li往后都更新了

有key - 值为索引

  • 有key属性, 基于key的来比较新旧虚拟DOM
  • 代码:
<ul id="myUL">
    <li v-for="(str, index) in arr" :key="index">
        {{ str }} 
        <input type="text">
    </li>
</ul>
<button @click="addFn">下标为1的位置新增一个</button>
export default {
    data(){
        return {
            arr: ["老大", "新来的", "老二", "老三"]
        }
    },
    methods: {
        addFn(){
            this.arr.splice(1, 0, '新来的')
        }
    }
};
  • 这一回不是按照li比较了,而是按照li的key来进行比较
    • 发现0,1,2是相同的,所以就复用
    • 3没有,那么就创建
  1. v-for先循环产生新的DOM结构, key是连续的, 和数据对应
  2. 然后比较新旧DOM结构, 找到区别, 打补丁到页面上
  3. 最后补一个li, 然后从第二个往后, 都要更新内容

有key - 值为id

  • key的值只能是唯一不重复的,字符串或数值

  • v-for不会移动DOM, 而是尝试复用, 就地更新,如果需要v-for移动DOM, 你需要用特殊 attribute key 来提供一个排序提示

  • 代码:

<template>
  <div>
    <ul>
      <li v-for="obj in arr" :key="obj.id">
        {{ obj.name }}
        <input type="text">
      </li>
    </ul>
    <button @click="btn">下标1位置插入新来的</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      arr: [
        {
          name: '老大',
          id: 50
        },
        {
          name: '老二',
          id: 31
        },
        {
          name: '老三',
          id: 10
        }
      ],
    };
  },
  methods: {
    btn(){
      this.arr.splice(1, 0, {
        id: 19, 
        name: '新来的'
      })
    }
  }
};
</script>

<style>
</style>
  • 新DOM里数据的key存在, 去旧的虚拟DOM结构里找到key标记的标签, 复用标签
  • 新DOM里数据的key存在, 去旧的虚拟DOM结构里没有找到key标签的标签, 创建
  • 旧DOM结构的key, 在新的DOM结构里没有了, 则移除key所在的标签
  • 理解:
    • 新旧dom对比之后,发现50,31,10,都是相同的,但是位置不同,所以就将31,10进行了移动
    • 还发现少了个19,就添加了19

总结:

不用key也不影响功能(就地更新), 添加key可以提高更新的性能

key值的选择,如果有id使用id,如果没有id使用index
  1. 子元素或者内容改变会分diff哪2种情况比较?
    1. 无key, 就地更新
    2. 有key, 按照key比较
  2. key值要求是?
    1. 唯一不重复的字符串或者数值
  3. key应该怎么用?
    1. 有id用id, 无id用索引
  4. key的好处?
    1. 可以配合虚拟DOM提高更新的性能

1.5_阶段小结

  1. v-for什么时候会更新页面呢?
    1. 数组采用更新方法, 才导致v-for更新页面
  2. vue是如何提高更新性能的?
  3. 采用虚拟DOM+diff算法提高更新性能
  4. 虚拟DOM是什么?
    1. 本质是保存dom关键信息的JS对象
  5. diff算法如何比较新旧虚拟DOM?
    1. 根元素改变 – 删除当前DOM树重新建
    2. 根元素未变, 属性改变 – 更新属性
    3. 根元素未变, 子元素/内容改变
      1. 无key – 就地更新
      2. 有key – 按key比较

1.6_vue基础 动态class

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

  • 语法: :class=“{类名: 布尔值}”
    • 如果布尔值为true,则应用此类名的样式
  • 代码:
<template>
  <div>
    <!-- 语法:
      :class="{类名: 布尔值}"
      使用场景: vue变量控制标签是否应该有类名
     -->
    <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变量中赋予给标签
如何给标签class属性动态赋值?

  • :class=“{类名: 布尔值}”, true使用, false不用

1.7_vue基础-动态style

目标: 给标签动态设置style的值

  • 语法: :style=“{css属性: 值}”
  • 代码:
<template>
  <div>
    <!-- 动态style语法
      :style="{css属性名: 值}"
     -->
    <p :style="{backgroundColor: colorStr}">动态style</p>
  </div>
</template>

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

<style>

</style>

总结: 动态style的key都是css属性名

给style赋值和class区别是?

  1. :class="{类名: 布尔值}”, true使用, false不用
  2. :style=“{css属性名: 值}”

1.8_案例-品牌管理(铺)

目标: 数据铺设

  • 需求1: 把默认数据显示到表格上

  • 需求2: 注意资产超过100的, 都用红色字体标记出来

  • 细节:

    ① 先铺设静态页面

    ② 此案例使用bootstrap, 需要下载, 并导入到工程main.js中

    ③ 用v-for配合默认数据, 把数据默认铺设到表格上显示

    ④ 直接在标签上, 大于100价格, 动态设置red类名

  • 案例效果:
    在这里插入图片描述

  • 步骤:

  1. 因为案例使用了bootstrap, 工程化开发, 模块化用npm/yarn下载引入使用
yarn add bootstrap@4.6.0
  1. 在main.js - 引入bootstrap(我们只用到bootstrap的css样式)
import "bootstrap/dist/css/bootstrap.css" 
// 默认找文件夹下的index文件(但是这个不是所以需要写具体路径)
  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>
        &nbsp;&nbsp;&nbsp;&nbsp;
        <div class="form-group">
          <div class="input-group">
            <input
              type="text"
              class="form-control"
              placeholder="价格"
            />
          </div>
        </div>
        &nbsp;&nbsp;&nbsp;&nbsp;
        <!-- 阻止表单提交 -->
        <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>

主要核心逻辑

<tbody>
    <tr v-for="obj in list" :key="obj.id">
        <td>{{ obj.id }}</td>
        <td>{{ obj.name }}</td>

        <!-- 如果价格超过100,就有red这个类 -->
        <td :class="{red: obj.price > 100}">{{ obj.price }}</td>
        <td>{{ obj.time }}</td>
        <td><a href="#" >删除</a></td>
    </tr>
</tbody>

<script>
// 1. 明确需求
// 2. 标签+样式+默认数据
// 3. 下载bootstrap, main.js引入bootstrap.css
// 4. 把list数组 - 铺设表格
// 5. 修改价格颜色
</script>

用到了哪些技术点?

  1. 布局用bootstrap, yarn下载引入使用
  2. v-for把数据渲染到表格里
  3. :class动态判断价格给红色类名

1.9_案例-品牌管理(增)

目标: 数据新增

  • 需求1: 实现表单数据新增进表格功能
  • 需求2: 判断用户输入是否为空给提示
  • 分析
    ① 添加资产按钮 – 绑定点击事件
    ② 给表单v-model绑定vue变量收集用户输入内容
    ③ 添加数组到数组中
    ④ 判断用户内容是否符合规定
<!-- 添加资产 -->
      <form class="form-inline">
        <div class="form-group">
          <div class="input-group">
            <input
              type="text"
              class="form-control"
              placeholder="资产名称"
              v-model="name"
            />
          </div>
        </div>
        &nbsp;&nbsp;&nbsp;&nbsp;
        <div class="form-group">
          <div class="input-group">
            <input
              type="text"
              class="form-control"
              placeholder="价格"
              v-model.number="price"
            />
          </div>
        </div>
        &nbsp;&nbsp;&nbsp;&nbsp;
        <!-- 4. 阻止表单提交(刷新网页数据又回去了) -->
        <button class="btn btn-primary" @click.prevent="addFn">添加资产</button>
        // 注意: <button> 默认是submit类型的相当于 <button type="submit">
      </form>

<script>
// 目标: 新增
// 1. 按钮 - 事件
// 2. 给表单v-model绑定vue变量
export default {
  // ...省略其他
  methods: {
    addFn(){
      // 5. 判断是否为空
      if (this.name.trim().length === 0 || this.price === 0) {
        alert("不能为空")
        return
      }
      // 3. 把值以对象形式-插入list
      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>

涉及到了哪些技术点?

  1. @绑定事件
  2. v-model收集表单数据
  3. .prevent阻止按钮提交表单刷新页面
  4. v-model修饰符
  5. 数组取最后一个元素

1.10_案例-品牌管理(删)

目标: 数据删除

  • 需求1: 点击删除的a标签, 删除数据
  • 需求2: 删除没数据了要提示暂无数据的tfoot
  • 分析
    ① a标签绑定点击事件
    ② 给事件方法传id
    ③ 通过id, 找到对应数据删除
    ④ 删除光了要让tfoot显示
    ⑤ 删除光了再新增, 有bug(id值问题)需要修复

<td><a href="#" @click="delFn(obj.id)">删除</a></td>
          
<script>
// 目标: 删除功能
// 1. 删除a标签-点击事件
// 2. 对应方法名
// 3. 数据id到事件方法中
// 4. 通过id, 找到这条数据在数组中的下标
// 5. splice方法删除原数组里的对应元素
// 6. 设置tfoot, 无数据给出提示
// 7. 无数据再新增, id要判断一下
export default {
  // ...其他代码
  methods: {
    // ...其他代码
    delFn(id){
      // 通过id找到这条数据在数组中下标
      let index = this.list.findIndex(obj => obj.id === id)
      this.list.splice(index, 1)
    }
  }
};
</script>

涉及到了哪些技术点?

  1. .prevent使用
  2. 事件传索引
  3. 删除数组元素方法
  4. id值判断问题

2. vue过滤器

2.0_vue过滤器-定义使用

目的: 转换格式, 过滤器就是一个函数, 传入值返回处理后的值

  • 过滤器:对数据进行格式转换的函数

  • 过滤器只能用在, 插值表达式和v-bind表达式

  • Vue中的过滤器场景

    • 字母转大写, 输入"hello", 输出"HELLO"
    • 字符串翻转, “输入hello, world”, 输出"dlrow ,olleh"
  • 语法:

    • 定义语法:
      • 全局过滤器:Vue.filter(“过滤器名”, (值) => {return “返回处理后的值”})
      • 局部过滤器:filters: {过滤器名字: (值) => {return “返回处理后的值”}
    • 使用语法:
      • 插值表达式使用:{{ 值 | 过滤器名字 }}
      • 属性绑定使用: v-bind:属性 = “值 | 过滤器名字”
  • 例子:

    • 全局定义字母都大写的过滤器
    • 局部定义字符串翻转的过滤器
  • 代码:

main.js中定义全局过滤器
// 方式1: 全局 - 过滤器 (全局过滤器一般定义在main.js中)
// 任意的.vue文件内"直接"使用
// 语法: Vue.filter("过滤器名", 值 => 处理结果)
// Vue.filter("reverse", val => val.split("").reverse().join(""))

// 过滤器接参数
Vue.filter("reverse", (val) => {
  return val.split("").reverse().join("")
})
<template>
  <div>
    <p>原来的样子: {{ msg }}</p>
    <!-- 2. 过滤器使用
      语法: {{| 过滤器名字 }}
     -->
    <p>使用翻转过滤器: {{ msg | reverse }}</p>   // reverse(msg)
      //注意:过滤器在执行的时候,会将竖线前边的值,作为第一个参数传递给过滤器函数
    <p :title="msg | toUp">鼠标长停</p>
  </div>
</template>

<script>
export default {
  data(){
    return {
      msg: 'Hello, Vue'
    }
  },
  // 方式2: 局部 - 过滤器
  // 只能在当前vue文件内使用
  /*
     语法: 
     filters: {
       过滤器名字 (val) {
         return 处理后的值
       }
     }
  */
  filters: {
    toUp (val) {
      return val.toUpperCase()
    }
  }
}
</script>

<style>

</style>

总结: 把值转成另一种形式, 使用过滤器, Vue3用函数替代了过滤器.

全局注册最好在main.js中注册, 一处注册到处使用

  1. Vue中过滤器作用?
    1. 传入一个值, 处理后返回另外一个值
  2. 如何定义过滤器?
    1. main.js – Vue.filter(‘过滤器名字’, 函数体)
    2. 某.vue文件 – filters: {‘过滤器名字’: 函数体}
  3. 如何使用过滤器?
    1. Vue变量 | 过滤器名字
  4. 过滤器注意事项?
    1. 插值表达式 / 动态属性

2.1_vue过滤器-传参和多过滤器

目标: 可同时使用多个过滤器, 或者给过滤器传参

  • 语法:
    • 过滤器传参: vue变量 | 过滤器(实参)
    • 多个过滤器: vue变量 | 过滤器1 | 过滤器2
  • 代码:
// 修改main.js中的全局过滤器
// 增加参数s:分隔符
Vue.filter("reverse", (val, s) => {
  return val.split("").reverse().join(s)
})
<template>
  <div>
    <p>原来的样子: {{ msg }}</p>
    <!-- 1.
      给过滤器传值
      语法: vue变量 | 过滤器名()
     -->
    <p>使用翻转过滤器: {{ msg | reverse('|') }}</p // reverse(msg,'|')
    <!-- 2.
      多个过滤利使用
      语法: vue变量 | 过滤器1 | 过滤器2
     -->
    <p :title="msg | toUp | reverse('|')">鼠标长停</p>
    // 理解:reverse过滤器在调用的时候,会将msg,toUp之后的结果,作为第一个参数传递过去
      //     "|" 会作为第二个参数进行传递
  </div>
</template>

<script>
export default {
  data(){
    return {
      msg: 'Hello, Vue'
    }
  },
  filters: {
    toUp (val) {
      return val.toUpperCase()
    }
  }
}
</script>

<style>

</style>

总结: 过滤器可以传参, 还可以对某个过滤器结果, 后面在使用一个过滤器

  1. 多个过滤器使用?
    1. vue变量 | 过滤器1 | 过滤器2
  2. 如何给过滤器传额外值
    1. vue变量 | 过滤器(值)

2.2_案例-品牌管理(时间格式化)

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

  1. 下载moment处理日期的第三方工具模块
    moment官网文档: http://momentjs.cn/docs/#/displaying/
yarn add moment
  1. 定义过滤器, 把时间用moment模块格式化, 返回我们想要的格式
// 目标: 处理时间
// 1. 下载moment模块
import moment from 'moment'


// 2. 定义过滤器, 编写内部代码
filters: { 
    formatDate (val){
        return moment(val).format('YYYY-MM-DD')
    }
}

<!-- 3. 使用过滤器 -->
<td>{{ obj.time | formatDate }}</td>

涉及到了哪些技术点?

  1. yarn下包
  2. 过滤器定义和使用
  3. moment复习

3. vue计算属性

3.0_vue计算属性-computed

目标: 一个变量的值, 需要用另外变量计算而得来

  • 计算属性:computed,定义时是一个函数,但本质上是一个属性(使用时不加小括号)
  • 语法:
computed: { //computed: 计算
    "计算属性名" () {
        return "值" // 必须将最终结果返回
    }
}
  • 需求: 求2个数的和显示到页面上
<template>
  <div>
    <p>{{ num }}</p>
  </div>
</template>

<script>
export default {
  data(){
    return {
      a: 10,
      b: 20
    }
  },
  // 计算属性:
  // 场景: 一个变量的值, 需要用另外变量计算而得来
  /*
    语法:
    computed: {
      计算属性名 () {
        return}
    }
  */
 // 注意: 计算属性和data属性都是变量-不能重名
 // 注意2: 函数内变量变化, 会自动重新计算结果返回
  computed: {
    num(){
        //打印一下this,发现computed的内容也会被挂载到this上,并且发现num是一个属性,值就是a+b的结果
        // computed第一次被执行的时候,会将结果计算出来,转换为属性,挂载到this身上:num:结果
      return this.a + this.b
    }
  }
}
</script>

<style>

</style>

注意: 计算属性也是vue数据变量, 所以不要和data里重名, 用法和data相同

总结: 一个变量的值, 需要用另外变量计算而得来

  1. 计算属性使用场景?
    1. 当变量的值, 需要通过别人计算而得来
  2. 计算属性特点?
    1. 函数内使用的变量改变, 重新计算结果返回
  3. 计算属性注意事项?
    1. 计算属性名和data里名字不能重复

3.1_vue计算属性-缓存

目标: 计算属性是基于它们的依赖项的值结果进行缓存的,只要依赖的变量不变, 都直接从缓存取结果

  • 计算属性在第一次被使用时,会执行出结果,并且将自己挂载到this上:reverseMessage:‘euv olleh’
<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 {
      message: "Hello, Vue"
    }
  },
  // 计算属性优势:
  // 带缓存
  // 计算属性对应函数执行后, 会把return值缓存起来
  // 依赖项不变, 多次调用都是从缓存取值
  // 依赖项值-变化, 函数会"自动"重新执行-并缓存新的值
  computed: {
    reverseMessage(){
      console.log("计算属性执行了"); //计算属性使用三次,只会调用一次
      return this.message.split("").reverse().join("")// 除非依赖项message的值改变,message改变之后会再次自动重新执行
    }
  },
  methods: {
    getMessage(){
      console.log("函数执行了");
      return this.message.split("").reverse().join("")
    }
  }
}
</script>

<style>

</style>

总结: 计算属性根据依赖变量结果缓存, 依赖变化重新计算结果存入缓存, 比普通方法性能更高

  1. 计算属性好处是?
    1. 带缓存
    2. 依赖项不变, 直接从缓存取
    3. 依赖项改变, 函数自动执行并重新缓存
  2. 计算属性使用场景?
    1. 当变量值, 依赖其他变量计算而得来才用

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

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

<tr style="background-color: #EEE">
     <td>统计:</td>
     <td colspan="2">总价钱为: {{ allPrice }}</td>
     <td colspan="2">平均价: {{ avgPrice }}</td>
</tr>

<script>
// 目标: 总价和均价显示
// 1. 末尾补tr - 显示总价和均价
export default {
  // ...源代码省略
  // 2. 计算属性
  computed: {
      allPrice(){
          // 3. 求总价
          return this.list.reduce((sum, obj) => sum += obj.price, 0)
      },
      avgPrice(){
          // 4. 求均价 - 保留2位小数
          return (this.allPrice / this.list.length).toFixed(2)
      }
  }
}
</script>

总结: 总价来源于所有数据计算而来的结果, 故采用计算属性

涉及到的知识点?

  1. computed定义和使用计算属性
  2. 数组reduce方法累积运算

3.3_vue计算属性-完整写法

目标: 计算属性也是变量, 如果想要直接赋值, 需要使用完整写法

  • 语法:
computed: {
    "属性名": {
        //set:设置值,使用者可以设置计算属性的值
        set(){
            
        },
        //get,获取值,使用者可以获取值
        get() { //原来computed的写法,相当于这里的get方法
            return "值"
        }
    }
}
  • 需求: 计算属性给v-model使用

  • 页面准备输入框

<template>
  <div>
      <div>
          <span>姓名:</span>
          <input type="text" v-model="full">
      </div>
  </div>
</template>

<script>
// 问题: 给计算属性赋值 - 需要setter
// 解决:
/*
    完整语法:
    computed: {
        "计算属性名" (){},
        "计算属性名": {
            set(){

            },
            get(){
                return}
        }
    }
*/
export default {
    computed: {
        full: {
            // 给full赋值触发set方法
            set(val){
                console.log(val)
            },
            // 使用full的值触发get方法
            get(){
                return "无名氏"
            }
        }
    }
}
</script>

<style>

</style>

总结: 想要给计算属性赋值, 需要使用set方法

<template>
  <div>
      <div>
          <span>姓名:</span>
          <input type="text" v-model="full">
      </div>
  </div>
</template>

<script>
// 问题: 给计算属性赋值 - 需要setter
// 解决:
/*
    完整语法:
    computed: {
        "计算属性名" (){},
        "计算属性名": {
            set(){

            },
            get(){
                return}
        }
    }
*/
export default {
  data() {
    return {
      msg: "无名氏"
    };
  },
  computed: {
    full: {
      // 给full赋值触发set方法
      set(val) {
        console.log(val);
        this.msg = val;
      },
      // 使用full的值触发get方法
      get() {
        return this.msg;
      }
    }
  }
};
</script>

<style>
</style>
  1. 何时用计算属性完整写法?
    1. 给计算属性变量赋值时
  2. set函数和get函数什么执行?
    1. set接收要赋予的值
    2. get里要返回给这个计算属性具体值

3.4_案例-小选影响全选

目标: 小选框都选中(手选), 全选自动选中

  • 需求: 小选框都选中(手选), 全选自动选中

  • 分析:
    ① 先静态后动态
    ② 循环生成复选框和文字, 对象的c属性和小选框的选中状态, 用v-model双向绑定
    ③ 定义isAll计算属性, 值通过小选框们统计c属性状态得来

<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: "猪八戒",
          c: false,
        },
        {
          name: "孙悟空",
          c: false,
        },
        {
          name: "唐僧",
          c: false,
        },
        {
          name: "白龙马",
          c: false,
        },
      ],
    };
  }
};
</script>
<template>
  <div>
    <span>全选:</span>
    <!-- 4. v-model 关联全选 - 选中状态 -->
    <input type="checkbox" v-model="isAll"/>
    <button>反选</button>
    <ul>
      <li v-for="(obj, index) in arr" :key="index">
        <!-- 3. 对象.c - 关联 选中状态 -->
        <input type="checkbox" v-model="obj.c"/>
        <span>{{ obj.name }}</span>
      </li>
    </ul>
  </div>
</template>

<script>
// 目标: 小选框 -> 全选
// 1. 标签+样式+js准备好
// 2. 把数据循环展示到页面上
export default {
  data() {
    return {
      arr: [
        {
          name: "猪八戒",
          c: false,
        },
        {
          name: "孙悟空",
          c: false,
        },
        {
          name: "唐僧",
          c: false,
        },
        {
          name: "白龙马",
          c: false,
        },
      ],
    };
  },
  // 5. 计算属性-isAll
  computed: {
    isAll () {
         // 6. 统计小选框状态 ->  全选状态
        // every口诀: 查找数组里"不符合"条件, 直接原地返回false
        return this.arr.every(obj => obj.c === true)
    }
  }
};
</script>

PS:every补充
在这里插入图片描述
小选框如何影响全选框选中状态的?

  1. v-model给全选框绑定isAll计算属性
  2. isAll计算属性里返回统计小选框选中状态结果

3.5_案例-全选影响小选

目标: 全选影响小选

  • 需求1: 获取到全选状态 – 改装isAll计算属性

  • 需求2: 全选状态同步给所有小选框

  • 分析:
    ①: isAll改成完整写法, set里获取到全选框, 勾选的状态值
    ②: 遍历数据数组, 赋给所有小选框v-model关联的属性

<script>
export default {
  // ...其他代码
  // 5. 计算属性-isAll
  computed: {
    isAll: {
      set(val){
        // 7. 全选框 - 选中状态(true/false)
        this.arr.forEach(obj => obj.c = val)
      },
      get(){
        // 6. 统计小选框状态 ->  全选状态
        // every口诀: 查找数组里"不符合"条件, 直接原地返回false
        return this.arr.every(obj => obj.c === true)
      }
    }
  }
};
</script>

全选如何影响小选框选中状态的?

  1. 小选框v-model, 关联数组里对象的c属性
  2. isAll计算属性的set方法, 拿到全选框状态
  3. 状态值赋给, 数组里每个对象的c属性

3.6_案例-反选

目标: 反选功能

  • 需求: 点击反选, 让所有小选框, 各自取相反勾选状态

  • 分析:
    ①: 小选框的勾选状态, 在对象的c属性
    ②: 遍历所有对象, 把对象的c属性取相反值赋予回去即可


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

<script>
export default {
  // ...其他代码省略
  methods: {
    btn(){
      // 8. 让数组里对象的c属性取反再赋值回去
      this.arr.forEach(obj => obj.c = !obj.c)
    }
  }
};
</script>

反选如何实现的?

  1. 遍历每个对象, 把c属性的值取反再赋予回去

4. vue侦听器

4.0_vue侦听器-watch

目标: 可以侦听data/computed属性值改变

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

完整例子代码:

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

<script>
export default {
  data(){
    return {
      name: ""
    }
  },
  // 目标: 侦听到name值的改变
  /*
  语法:
    watch: {
      变量名 (newVal, oldVal){
        // 变量名对应值改变这里自动触发
      }
    }
  */
  watch: {
    // newVal: 当前最新值(新值)
    // oldVal: 上一刻值(老值)
    name(newVal, oldVal){
      console.log(newVal, oldVal);
    }
  }
}
</script>

<style>

</style>

总结: 想要侦听一个属性变化, 可使用侦听属性watch

如何侦听到某个变量值改变呢?

  1. 使用watch配置项, key是要侦听的data/计算属性名

4.1_vue侦听器-深度侦听和立即执行

目标: 侦听复杂类型, 或者立即执行侦听函数

  • 语法 : 这个也可以认为是watch的完整写法
watch: {
    "要侦听的属性名": {
        handler (newVal, oldVal) { //这个handler就是之前的简写形式
        },   
        deep: true, // 深度侦听复杂类型内变化
        immediate: true, // 立即执行     
    }
}

完整例子代码:

<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: {
      变量名 (newVal, oldVal){
        // 变量名对应值改变这里自动触发
      },
      变量名: {
        handler(newVal, oldVal){

        },
        deep: true, // 深度侦听(对象里面层的值改变)
        immediate: true // 立即侦听(网页打开handler执行一次)
      }
    }
  */
  watch: {
      //深度监听:user属性的值改变了,也会监听到
    user: {
      handler(newVal, oldVal){
        // 这里打印的都是user里的对象,因为监听的是user对象
        console.log(newVal, oldVal);
      },
      deep: true,
      immediate: true
    }
  }
}
</script>

<style>

</style>

总结: immediate立即侦听, deep深度侦听, handler固定方法触发

  1. 如何侦听一个对象/数组呢?
    1. 把侦听器写成对象形式, 给handler方法和deep:true
  2. 侦听器函数马上执行?
    1. immediate: true

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

目标: 侦听list变化, 同步到浏览器本地

  • 需求: 把品牌管理的数据实时同步到本地缓存

  • 分析:

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

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

  • 效果:

    新增/删除 – 刷新页面 – 数据还在

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

涉及到了哪些知识点?

  1. watch深度侦听数组, deep: true
  2. localStorage本地缓存里只能存储字符串
  3. JSON.stringify和JSON.parse使用
  4. 逻辑或

今日总结

  • v-for能监测到哪些数组方法变化, 更新页面
  • key的作用是什么
  • 虚拟dom好处, diff算法效果
  • 动态设置class或style
  • vue过滤器作用和分类
  • vue计算属性作用
  • vue侦听器的作用

面试题

1. Vue 中怎么自定义过滤器

Vue.js允许自定义过滤器,可被用于一些常见的文本格式化。
过滤器可以用在两个地方:双花括号插值和v-bind表达式

全局的用Vue.filter()

局部的用filters属性

2. Vue中:key作用, 为什么不能用索引

:key是给v-for循环生成标签颁发唯一标识的, 用于性能的优化

因为v-for数据项的顺序改变,Vue 也不会移动 DOM 元素来匹配数据项的顺序,而是就地更新每个元素

:key如果是索引, 因为索引是连续的, 如果删除其中某一个, 会导致最后一个被删除

当我们再删除的时候, :key再根据数据来把新旧的dom对比时, 删除:key不存在的对应的标签(添加也是一样的插入到指定位置, 别的都不会动)

3. 数组更新有的时候v-for不渲染

因为vue内部只能监测到数组顺序/位置的改变/数量的改变, 但是值被重新赋予监测不到变更, 可以用 Vue.set() / vm.$set()

附加练习_1.买点书练习

目标: 把数据铺设到页面上, 当用户点击买书按钮, 书籍数量增加1, 并且要计算累计的和

<template>
  <div>
    <p>请选择你要购买的书籍</p>
    <ul>
      <li v-for="(item, ind) in arr" :key="ind">
        <span>{{ item["name"] }}</span>
        <button @click="buy(ind)">买书</button>
      </li>
    </ul>
    <table border="1" width="500" cellspacing="0">
      <tr>
        <th>序号</th>
        <th>书名</th>
        <th>单价</th>
        <th>数量</th>
        <th>合计</th>
      </tr>
      <tr v-for="(item, index) in arr" :key="index">
        <td>{{ index + 1 }}</td>
        <td>{{ item["name"] }}</td>
        <td>{{ item["price"] }}</td>
        <td>{{ item["count"] }}</td>
        <td>{{ item["price"] * item["count"] }}</td>
      </tr>
    </table>
    <p>总价格为: {{ allPrice }}</p>
  </div>
</template>

<script>
export default {
  data() {
    return {
      arr: [
        {
          name: "水浒传",
          price: 107,
          count: 0,
        },
        {
          name: "西游记",
          price: 192,
          count: 0,
        },
        {
          name: "三国演义",
          price: 219,
          count: 0,
        },
        {
          name: "红楼梦",
          price: 178,
          count: 0,
        },
      ],
    };
  },
  methods: {
    buy(index) {
      this.arr[index]["count"]++;
    },
  },
  computed: {
    allPrice() {
      // 数组里放的是对象, 而对象是复杂类型, 引用关系, 值改变会触发计算属性重新执行
      return this.arr.reduce((sum, obj) => {
        return (sum += obj["price"] * obj["count"]);
      }, 0);
    },
  },
};
</script>

<style>
</style>

附加练习_2.选你爱我求和

目标: 把用户选中的数字, 累计求和显示

提示:

  • v-model绑定的变量是数组, 可以收集checkbox的value属性呦
<template>
  <div>
    <!-- 无id时, 可以使用index(反正也是就地更新) -->
    <div
      v-for="(item, index) in arr"
      style="display: inline-block"
      :key="index"
    >
      <input type="checkbox" v-model="checkNumArr" :value="item" />
      <span>{{ item }}</span>
    </div>
    <p>你选中的元素, 累加的值和为: {{ theSum }}</p>
  </div>
</template>

<script>
export default {
  data() {
    return {
      arr: [9, 15, 19, 25, 29, 31, 48, 57, 62, 79, 87],
      checkNumArr: [], //  保存用户选中的数字
    };
  },
  computed: {
    theSum() {
      return this.checkNumArr.reduce((sum, val) => {
        return (sum += val);
      }, 0);
    },
  },
};
</script>

<style>
</style>

总结, 当计算属性函数里引用的vue变量发生改变, 函数就执行并重新返回结果并缓存起来

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值