Vue基础

Vue基础

一、学习目标

  1. 能够说出Vue的概念和作用
  2. 能够使用@vue/cli脚手架工程化开发
  3. 能够熟练Vue指令
  4. 能够了解更新检测,key作用,虚拟DOM,diff算法
  5. 能够掌握设置动态样式
  6. 能够掌握过滤器,计算属性,监听器
  7. 能够完成品牌管理案例

二、Vue基本概念

1. 为什么要学习Vue

更少的时间,干更多的活,开发网站速度

在这里插入图片描述

1.1. 铺设li
  • 原生js做法
<ul id="myUL"></ul>
<script>
    let arr = ['春天','夏天','秋天','冬天']
	let myUL = document.querySelector('#myUL')
    for(let i = 0; i < arr.length; i++) {
        let theLi = document.createElement('li')
        theLi.innerHTML = arr[i]
        myUL.appendChild(theLi)
    }
    </script>
<li v-for = "item in arr">{{item}}</li>
<script>
 new Vue({
     data: {
         arr: ['春天','夏天','秋天','冬天']
     }
 })
</script>
1.2 企业里都使用Vue开发网站

在这里插入图片描述

2. Vue是什么

渐进式javascript框架,一套拥有自己规则的语法

官网地址: https://cn.vuejs.org/ (作者: 尤雨溪)

2.1 渐进式概念
(1)生活里的渐进式

渐进式:逐渐进步,想用什么就用什么,不必全都使用

在这里插入图片描述

(2)web里的渐进式

HTML能写网页,CSS能让网页更好看,JS赋予网页交互效果,jQ写的更快,Node可以提供动态数据

在这里插入图片描述

(3)Vue里的渐进式

Vue从基础开始,会遵循渐进向前学习,如下知识点可能你现在不明白,但是学完了整个Vue回头来看,会很有帮助

在这里插入图片描述

2.2 库和框架

库:封装的属性和方法(例如:jQuery)

框架:拥有自己的规则和元素,比库强大的多(例如:Vue)

在这里插入图片描述

在这里插入图片描述

3. Vue学习方式

  • 传统开发模式:基于html文件开发Vue
  • 工程化开发方式:在webpack环境中开发Vue,这里最推荐,企业常用的方式

在这里插入图片描述

3.1 Vue如何学
  1. 知识点自测最好做到了如指掌 - 做不到只能花30分钟去记住结论和公式
  2. 记住Vue指令作用,基础语法 - 弄一个字典(——映射关系)
  3. 在课上例子,练习,案例,作业,项目中,反复磨炼使用
  4. 学会查找问题的方式和解决方式(弄个报错总结.md,避免反复进坑)

三、@vue/cli脚手架

  • @vue/cli是Vue官方提供的一个全局模块包(得到vue命令),此包用于创建脚手架项目
  • 脚手架是为了保证各施工过程顺利进行而搭设的工作平台

在这里插入图片描述

1. 脚手架好处

开箱即用

0配置webpack

  • babel支持
  • css.less支持
  • 开发服务器支持

2. @vue/cli安装

  • 全局安装命令
yarn global add @vue/cli
# OR 
npm install -g @vue/cli

注意:如果半天没动静(95%都是网速问题)可以ctrl c

  1. 停止重新来
  2. 换一个网进行重来
  • 查看vue脚手架版本
vue -V

在这里插入图片描述

总结:如果出现版本号就安装成功,否者失败

3. @vue/cli 创建项目

  1. 创建项目

    # vue和create是命令, vuecli-demo是文件夹名
    vue create vuecli-demo
    
  2. 选择模板

    可以上下箭头选择,弄错了ctrl+c重来

在这里插入图片描述

准备用什么方式下载脚手架项目需要的依赖包

在这里插入图片描述

  1. 回车等待生成项目文件夹+文件+下载必须的第三方包

在这里插入图片描述

  1. 进入脚手架项目下,启动内置的热更新本地服务器
cd vuecli-demo

npm run serve
# or
yarn serve

只要看到绿色的,那你就成功了!(底层node+webpack热更新服务)

在这里插入图片描述

打开浏览器输入上述的地址

在这里插入图片描述

4. @vue/cli目录和代码分析

 vuecil-demo        # 项目目录
    ├── node_modules # 项目依赖的第三方包
    ├── public       # 静态文件目录
      ├── favicon.ico# 浏览器小图标
      └── index.html # 单页面的html文件(网页浏览的是它)
    ├── src          # 业务文件夹
      ├── assets     # 静态资源
        └── logo.png # vue的logo图片
      ├── components # 组件目录
        └── HelloWorld.vue # 欢迎页面vue代码文件 
      ├── App.vue    # 整个应用的根组件
      └── main.js    # 入口js文件
    ├── .gitignore   # git提交忽略配置
    ├── babel.config.js  # babel配置
    ├── package.json  # 依赖包列表
    ├── README.md    # 项目说明
	└── yarn.lock    # 项目包版本锁定和缓存地址

主要文件及含义

node_modules // 下都是下载的第三方包
public/index.html  // 浏览器运行的网页
src/main.js // webpack打包的入口文件
src/App.vue // vue项目入口页面
package.json  // 依赖包列表文件

在这里插入图片描述

5. @vue/cli 项目架构了解

目标:知道项目入口,以及代码执行顺序和引入关系

在这里插入图片描述

6. @vue/cli自定义配置

目标:项目中没有webpack.config.js文件,因为@vue/cli用的vue.config.js

src 并列处新建vue.config.js

/*覆盖webpack的配置*/
module.exports = {
    devServer: { // 自定义服务配置
        open: true, // 自动打开浏览器
        port: 3000
    }
}

7. eslint了解

目标:知道eslint的作用,和如何暂时关闭,它是一个代码检查工具

例子:先在main.js随便声明个变量,但是不要使用

在这里插入图片描述

这时候你会发现,终端和页面都报错了

记住以后见到这样子的错误,证明你的代码不够严谨

在这里插入图片描述

在这里插入图片描述

  • 方式1:手动解决掉错误,以后项目中会讲到如何自动解决
  • 方式2:暂时关闭eslint检查(因为现在主要精力在学习Vue语法上)在vue.config.js中配置后重启服务
module.exports = {
    // ... 其他配置
    lintOnSave: false // 关闭eslint 检查
}

8. @vue/cli 单vue文件讲解

目标:单vue文件好处,独立作用域互不影响

  • Vue推荐采用.vue文件来开发项目
  • template里只能有一个根标签
  • vue文件-独立模块-作用域互不影响
  • style配合scoped属性,保证样式只针对当前template内标签生效
  • vue文件配合webpack,把他们打包起来插入到index.html
<!-- template必须, 只能有一个根标签, 影响渲染到页面的标签结构 -->
<template>
    <div>欢迎使用vue</div>    
</template>

<!-- js相关 -->
<script>
export default {
    name: 'App'
}
</script>

<!-- 当前组件的样式,设置scoped,可以保证样式只对当前的页面有效 -->

<style scoped>

</style>

最终:Vue文件配合webpack,把他们打包起来插入到index.html,然后在浏览器运行

9. @vue/cli 欢迎界面清理

目标:我们开始写我们自己的代码,无需欢迎页面

  • src/App.vue默认有很多内容,可以全部删除留下框
  • assets和components文件夹下的一切都删掉(不要默认的欢迎界面)

四、Vue指令

1. vue基础-插值表达式

目的:在dom标签中,直接插入内容

  • 又叫:声明式渲染 /文本插值

  • 语法:{{表达式}}

  • msg和obj是vue数据变量

  • 要在js中data函数里声明

<template>
  <div>
    // 把值赋予到标签里
    <h1>{{msg}}</h1>
    <h1>{{obj.name}}</h1>
    <h1>{{obj.age >= 18 ? '已成年':'未成年'}}</h1>
  </div>
</template>

<script>
export default {
  // 1. 变量在data函数return的对象上
  data(){  // 格式固定定义vue数据之处
    return {  // key 相当于变量名
      msg: 'Hello Vue',
      obj: {
        name: '刘德华',
        age: 20
      }
    }
  }
}
</script>

<style>

</style>

2. vue基础-MVVM设计模式

目的:转变思维,用数据驱动视图改变,操作dom的事,vue源码内干了

  • 设计模式:是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结

  • 演示:在上个代码基础上,在devtool工具改变M层的变量,观察V层(视图的自动同步)

在这里插入图片描述

  • MVVM:一种软件架构模式,决定了写代码的思想和层次
    • M:model数据模型 (data里定义)
    • V: view视图 (html页面)
    • VM: ViewModel视图模型(vue.js源码)
  • MVVM通过数据双向绑定让数据自动地双向同步不需要操作DOM
    • V(修改视图) -> M(数据自动同步)
    • M(修改数据)-> V (视图自动同步)

在这里插入图片描述

  1. 在vue中,不推荐直接动手操作DOM
  2. 在vue中,通过数据驱动视图,不要想着怎么操作DOM,而是想着如何操作数据!!!(思想转变)

在这里插入图片描述

3. vue指令 v-bind

目标:给标签属性设置vue变量的值

vue指令,实质上就是特殊的html标签属性,特点:v-开头

每个指令,都有独立的作用

  • 语法:v-bind:属性名="vue变量"
  • 简写: :属性名="vue变量"
<!-- 语法:v-bind:原生标签属性名="vue变量" -->
<a v-bind:href="url">我是a标签</a>
<!-- 简写语法: :原生属性名="vue变量" -->
<img :src="imgSrc">

4. vue 指令 v-on

目标:给标签绑定事件

  • 语法
    • v-on:事件名=“要执行的少量代码
    • v-on:事件名=“methods中的函数”
    • v-on:事件名=“methods中的函数(实参)”
  • 简写:@事件名=“methods中的函数”
<template>
  <div>
    <p>你要购买的产品数量:{{count}}</p>
  <!-- 1. 绑定事件 -->
  <!-- 语法:v-on:事件名="要执行的少量代码" -->
  <button v-on:click="count = count + 1">+1</button>
  <!-- 语法:v-on:事件名="methods中的函数" -->
  <button v-on:click="addbtn">+1</button>
  <!-- 语法:v-on:事件名="methods中的函数(实参)" -->
  <button v-on:click="addFn(10)">一次加10</button>
  <!-- 简写:@事件名="methods中的函数" -->
  <button @click="subFn">-1</button>
  </div>
</template>

<script>
export default {
  data () {
    return {
      count: 1
    }
  },
  methods: {  // this代表export default后面的组件对象(下属有data里return出来的属性)
    addbtn() {
      this.count++
    },
    addFn(num){
      this.count += num
    },
    subFn() {
      this.count--
    }
  }
}
</script>

<style>

</style>

总结:常用@事件名,给dom标签绑定事件,以及=右侧事件处理函数

5. vue指令 v-on事件对象

  • 语法
    • 无传参:通过形参直接接收
    • 传参:通过$event指代事件对象传给事件处理函数
<template>
  <div>
    <a @click="one" href="http://www.baidu.com">百度</a>
    <br>
    <a @click="two(10,$event)" href="http://ww.taobao.com">淘宝</a>
  </div>
</template>

<script>
export default {
  methods: {
    // 1. 事件触发,无传值,可以直接获取事件对象
    one(e) {
      e.preventDefault()
    },
    // 2. 事件触发,传值,需要手动传入$event
    two(num,e) {
      e.preventDefault()
    }
  }
}
</script>

<style>

</style>

6. vue指令 v-on修饰符

目标:在事件后面.修饰符,给事件带来更强大的功能

  • 语法
    • @事件名.修饰符=“methods里函数”
  • 修饰符列表
    • .stop 阻止事件冒泡
    • .prevent 阻止默认行为
    • .once 程序运行期间,只能触发一次事件处理函数
<template>
  <div>
    <!-- vue对事件进行了修饰设置,在事件后面,修饰符名即可使用更多功能 -->
    <div @click="fatherFn">
      <p @click.stop="oneFn">阻止事件冒泡</p>
      <a href="www.baidu.com" @click.stop.prevent>去百度</a>
      <button @click.once="btn">.once程序运行期间,只触发一次事件处理函数</button>
    </div>
  </div>
</template>

<script>
export default {
  methods: {
    fatherFn(){
      console.log('fatcher-触发了click事件');
    },
    oneFn(){
      console.log('p标签触发了click事件');
    },
    btn() {
      console.log('1');
    }
  }
}
</script>

<style>

</style>

总结:修饰符给事件扩展额外功能

7. vue指令 v-on按键修饰符

目标:给键盘事件,添加修饰符,增强能力

  • 语法:
    • @keyup.enter 检测回车按键
    • @keyup.esc 检测返回按键
  • 更多修饰符: https://cn.vuejs.org/v2/guide/events.html
<template>
  <div>
    <!-- 1. 绑定键盘按下事件.enter回车 -->
    <input type="text" @keydown.enter="enterFn">
    <br>
    <!-- 2. esc修饰符 取消键 -->
    <input type="text" @keydown.esc="escFn">
  </div>
</template>

<script>
export default {
  methods: {
    enterFn(){
      console.log('用户按下了回车键');
    },
    escFn(){
      console.log('用户按下了ESC键');
    }
  }
}
</script>

<style>

</style>

8. 练习-翻转世界

需求:点击按钮 - 把文字取反显示 - 在点击取反显示回来

提示:点击事件里,把Vue变量值挨个字母取反赋予回来

分析:先静后动

  1. 定义变量message: ‘Hello,World’
  2. 插值表达式赋予到dom上,准备按钮和文字
  3. 按钮绑定点击事件和函数
  4. 对message值用split拆分,返回数组
  5. 数组元素翻转用reverse方法
  6. 再把数组用join拼接成字符串赋予给message
  7. 因为Vue是MVVM设计模式,数据驱动视图,所以视图自动改变

正确代码:

<template>
  <div>
    <!-- 2. 插值表达式赋予到dom上 -->
    <h1>{{message}}</h1>
    <!-- 3.按钮绑定点击事件和函数 -->
    <button @click="btn">翻转世界</button>
  </div>
</template>

<script>
export default {
  data () {
    return {
      // 1. 定义变量message: 'Hello,World'
      message: 'Hello,World'
    }
  },
  methods: {
    btn() {
      // 4. 先用split方法把字符串转为数组,再用reverse方法翻转数组,然后用join方法把数组转化为字符串
      this.message = this.message.split('').reverse().join('')
    }
  }
}
</script>

<style>

</style>

总结:记住方法特点,多做需求,vue是数据变化视图自动更新,减少操作DOM时间,提高开发效率

9. vue指令 v-model

目标:把value属性和vue数据变量,双向绑定到一起

  • 语法:v-model = “vue数据变量”
  • 双向数据绑定
    • 数据变化 -> 视图自动同步
    • 视图变化 -> 数据自动同步
  • 演示:用户名绑定 - vue内部是MVVM设计模式
<template>
  <div>
    <!-- v-model:是实现vue.js变量和表单标签value属性,双向绑定的指令 -->
    <div>
      <span>用户名:</span>
      <input type="text" v-model="username">
    </div>
    <div>
      <span>密码:</span>
      <input type="password" v-model="password">
    </div>
    <div>
      <span>来自于:</span>
      <!-- 下拉菜单哟啊绑定在select上 -->
      <select v-model="from">
        <option value="北京市">北京</option>
        <option value="南京市">南京</option>
        <option value="天津市">天津</option>
      </select>
    </div>
    <div>
      <!-- (重要) 遇到复选框,v-model的变量值 非数组 - 关联的是复选框的checkout属性 数组 - 关联的是复选框的value属性 -->
      <span>爱好:</span>
      <input type="checkbox" v-model="hobby" value="干饭">干饭
      <input type="checkbox" v-model="hobby" value="睡觉">睡觉
      <input type="checkbox" v-model="hobby" value="打代码">打代码
    </div>
    <div>
      <span>性别:</span>
      <input type="radio" value="" name="sex" v-model="gender"><input type="radio" value="" name="sex" v-model="gender"></div>
    <div>
      <span>自我介绍</span>
      <textarea v-model="intro"></textarea>
    </div>
  </div>
</template>

<script>
export default {
  data () {
    return {
      username: '',
      password: '',
      from: '',
      hobby: [],
      gender: '',
      intro: ''
    }
  }
}
</script>

<style>

</style>

总结:特别注意:v-model,在input[checkbox]的多选框状态

变量为非数组,则绑定的是checked的属性(true/false) 常用于:单个绑定使用

变量为数组,则绑定的是他们的value属性里的值 常用于:收集勾选了那些值

10. vue指令 v-model修饰符

目标:让v-model拥有更强大的功能

  • 语法:
    • v-model.修饰符=“vue数据变量”
      • .number 以parseFloat转成数字类型
      • .trim 去除首尾空白字符
      • .lazu 在change时触发而非input时
<template>
  <div>
    <div>
      <!-- .number修饰符 以parseFolat转为数字型 -->
      <span>年龄:</span>
      <input type="number" v-model.number = "age">
    </div>
    <!-- .trim修饰符 去除首尾空白字符 -->
    <div>
      <span>人生格言:</span>
      <input type="text" v-model.trim="motto">
    </div>
    <!-- .lazy修饰符 失去焦点内容改变(onchange)事件 -->
    <div>
      <span>自我介绍</span>
      <textarea v-model.lazy="inro"></textarea>
    </div>
  </div>
</template>

<script>
export default {
  data () {
    return {
      age: 0,
      motto: '',
      inro: ''
    }
  }
}
</script>

<style>

</style>

总结:v-model修饰符,可以对值进行预处理,非常高效好用

11. vue指令 v-text和v-html

目的:更新DOM对象的innerText/innerHTML

  • 语法:
    • v-text=“vue数据变量”
    • v-html=“vue数据变量”
  • 注意:会覆盖插值表达式
<template>
  <div>
    <p v-text="str"></p>
    <p v-html="str"></p>
    <!-- 注意:v-text或v-html会覆盖插值表达式 -->
    <p v-html="str">{{10 + 20}}</p>
    <p>{{10 + 20}}</p>
  </div>
</template>

<script>
export default {
  data () {
    return {
      str: '<span>我是一个span标签</span>'
    }
  }
}
</script>

<style>

</style>

总结:v-text把值当成普通的字符显示,v-html把值当做html解析

12. vue指令 v-show和v-if

目标:控制标签的隐藏或出现

  • 语法:
    • v-show=“vue变量”
    • v-if=“vue变量”
  • 原理
    • v-show用的display:none隐藏(频繁切换使用)
    • v-if 直接从DOM树上移除
  • 高级
    • v-else使用
<template>
  <div>
    <!-- v-show隐藏:采用display:none // 频繁切换 -->
    <h1 v-show="isShow">v-show显示</h1>
    <!-- v-if隐藏:采用从DOM树直接移除 // 移除 -->
    <h1 v-if="isOK">v-if显示</h1>
    <!-- v-if和v-else使用 -->
    <p v-if="age >= 18">成年了</p>
    <p v-else>未成年</p>
  </div>
</template>

<script>
export default {
  data () {
    return {
      isShow: true,
      isOK: true,
      age: 17
    }
  }
}
</script>

<style>

</style>

总结:使用v-show和v-if以及v-else指令,方便通过变量控制一套标签出现/隐藏

13. 案例 _折叠面板

目标:点击展开或收起时,把内容区域显示或隐藏

分析:

  1. 准备标签和样式,下载less模块和less-loader@5.0.0模块
  2. 熟悉下标签结构和样式,对应页面那一部分
  3. 按钮绑定点击事件
  4. 声明变量isShow来控制下面标签是否显示/隐藏
  5. 点击时,改变isShow的值,来影响页面效果

此案例使用了less语法,项目中下载模块

yarn add less-loader@5.0.0 less -D
<template>
  <div id="app">
    <h3>案例:折叠面板</h3>
    <div>
      <div class="title">
        <h4>芙蓉楼送辛渐</h4>
        <!-- 1. 绑定点击事件 -->
        <span class="btn" @click="btn">
          {{isShow ? '收起':'展开'}}
        </span>
      </div>
      <!-- 2. v-show控制标签的显示和隐藏 -->
      <div class="container" v-show="isShow">
        <p>寒雨连江夜入吴,</p>
        <p>平明送客楚山孤。</p>
        <p>洛阳亲友如相问,</p>
        <p>一片冰心在玉壶。</p>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      isShow: true
    }
  },
  methods: {
    btn(){
      // 3. 点击时把值改成false
      this.isShow = !this.isShow
    }
  }
}
</script>

<style lang="less">
body {
  background-color: #ccc;
  #app {
    width: 400px;
    margin: 20px auto;
    background-color: #fff;
    border: 4px solid blueviolet;
    border-radius: 1em;
    box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.5);
    padding: 1em 2em 2em;
    h3 {
      text-align: center;
    }
    .title {
      display: flex;
      justify-content: space-between;
      align-items: center;
      border: 1px solid #ccc;
      padding: 0 1em;
    }
    .title h4 {
      line-height: 2;
      margin: 0;
    }
    .container {
      border: 1px solid #ccc;
      padding: 0 1em;
    }
    .btn {
      /* 鼠标改成手的形状 */
      cursor: pointer;
    }
  }
}
</style>

14. vue指令 v-for

目标:列表渲染,所在标签结构,按照数据数量,循环生成

  • 语法

    • v-for=“(值,索引) in 目标结构”
    • v-for=“值 in 目标结构”
  • 目标结构:

    • 可以遍历数组/对象/数字/字符串(可遍历结构)
  • 注意:

    v-for的临时变量名不能用到v-for范围外

<template>
  <div id="app">
    <div id="app">
      <!-- v-for 把一组数据, 渲染成一组DOM -->
      <!-- 口诀: 让谁循环生成, v-for就写谁身上 -->

      <!-- 语法1:v-for"(值变量名,索引变量名) in 目标结构" -->
      <p>学生姓名</p>
      <ul>
        <li v-for="(item,index) in arr" :key="index">
          {{index}} - {{item}}
        </li>
      </ul>

      <!-- 语法2: v-for="值变量名 in 目标结构" -->
      <p>学生详细信息</p>
      <ul>
        <li v-for="obj in stuArr" :key="obj.id">
          <span>{{obj.name}}</span>&nbsp;
          <span>{{obj.sex}}</span>&nbsp;
          <span>{{obj.hobby}}</span>
        </li>
      </ul>

      <!-- 语法3:v-for="(value,key) in 对象" -->
      <p>老师信息</p>
      <div v-for="(value,key) in tObj" :key="value">
        {{key}} -- {{value}}
      </div>

      <!-- 语法4:v-for"变量名 in 固定数字" -->
      <div v-for="i in count" :key="i">
        {{i}}
      </div>
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      arr: ["小明", "小欢欢", "大黄"],
      stuArr: [
        {
          id: 1001,
          name: "孙悟空",
          sex: "男",
          hobby: "吃桃子",
        },
        {
          id: 1002,
          name: "猪八戒",
          sex: "男",
          hobby: "背媳妇",
        },
      ],
      tObj: {
        name: "小黑",
        age: 18,
        class: "1期",
      },
      count: 10,
    };
  },
};
</script>

五、更新检测&key作用

1. 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: [1,3,5,7,9]
    }
  },
  methods: {
    revBtn() {
      // 1. 数组翻转可以让 v-for更新
      this.arr.reverse()
    },
    sliceBtn() {
      // 2. 数组slice方法不会造成v-for更新
      // slice不会改变原数组
      this.arr.slice(0,3)
    },
    updateBtn() {
      // 3. 更新某个值的时候,v-for是检测不到的
      this.arr[0] = 1000
    }
  }
}
</script>

<style>

</style>
  • 这些方法会触发数组改变,v-for会检测到并更新页面
    • puch()
    • pop()
    • shift()
    • unshift()
    • splice()
    • sort()
    • reverse()
  • 这些方法不会触发v-for更新
    • slice()
    • filter()
    • concat()

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

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

2. v-for就地更新

v-for的默认行为会尝试原地修改元素而不是移动他们。

在这里插入图片描述

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

3. 虚拟DOM

目标:了解虚拟DOM的概念

.vue文件中的template里写的标签,都是模板,都要被vue处理成虚拟DOM对象,才会渲染显示到真实DOM页面上

  1. 内存中生成一样的虚拟DOM结构(本质是个JS对象)

    因为真实的DOM属性好几百个,没办法快速的字段那个属性改变了

    比如template里标签结构

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

    对应的虚拟DOM结构

    const dom = {
        type: 'div',
        attributes: [{id: 'box'}],
        children: {
            type: 'p',
            attributes: [{class: 'my_p'}]text: '123'
        }
    }
    
  2. 以后vue数据更新

  • 生成新的虚拟DOM结构

  • 和旧的虚拟DOM结构对比

  • 利用diff算法,找不同,只更新变化的部分(重绘/回流)到页面 - 也叫补丁

  • 好处1:提高了更新DOM的性能(不用把页面全删除重新渲染)

  • 好处2:虚拟DOM只包含必要的属性(没有真实DOM上百个属性)

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

在内存中比较差异,然后给真实DOM打补丁更新上

在这里插入图片描述

4. diff算法

vue用diff算法,新虚拟dom,和旧的虚拟dom比较

情况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>

5. diff算法-key

情况3:根元素没变,子元素没变,元素内容改变
无key - 就地更新

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

<template>
  <div>
    <ul>
      <li v-for="item in arr">
          {{item}}
      </li>
    </ul>
    <button @click="arrFn">下标位置为1新增一个</button>
  </div>
</template>

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

<style>

</style>

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

在这里插入图片描述

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

有key - 值为索引
  • 还是就地更新

因为新旧虚拟DOM对比,key存在就复用此标签更新内容,如果不存在就直接建立一个新的

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

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

<style>

</style>

在这里插入图片描述

  1. v-for先循环产生新的DOM结构,key是连续的,和数据对应
  2. 然后比较新旧DOM结构,找到区别,打补丁到页面上最后补一个li,然后从第二个往后,都要更新内容

口诀:key的值有id用id,没id用索引

有key -值为id

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

v-for不会移动DOM,你需要用特殊attributekey来提供一个排序提示

新DOM里数据的key存在,去旧的虚拟DOM结构里找到key标记的标签,复用标签

新DOM里数据的key存在,去旧的虚拟DOM结构里没有找到key标签的标签,创建

旧DOM结构的key,在新的DOM结构里没有了,则移除key所在的标签

<template>
  <div>
    <ul>
      <li v-for="obj in arrStr" :key="obj.id">
          {{obj.name}}
          <input type="text">
      </li>
    </ul>
    <button @click="arrFn">下标位置为1新增一个</button>
  </div>
</template>

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

<style>

</style>

在这里插入图片描述

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

6.阶段小结

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

7. 动态class

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

  • 语法:
    • :class=“{类名:布尔值}”
<template>
  <div>
    <!-- 语法::class="{类名:布尔值}",使用场景:vue变量控制标签是否应该有类名 -->
      <div :class="{div_str:true}">动态class</div>
  </div>
</template>

<script>
export default {
data () {
  return {
    isOk: true
  }
}
}
</script>

<style scoped>
  .div_str {
    width: 200px;
    height: 200px;
    background-color: pink;
    color: red;
    text-align: center;
    line-height: 200px;
  }
</style>

总结:就是把类名保存在vue变量中赋予给标签

8. 动态style

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

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

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

<style>

</style>

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

9. 案例-品牌管理

9.1 品牌管理(铺)

目标:数据铺设

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

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

  • 细节:

    1. 先铺设静态页面
    2. 此案例使用bootstrap,需要下载,并导入到工程main.js中
    3. 用v-for配合默认数据,把数据默认铺设到表格上显示
    4. 直接在标签上,大于100价格,动态设置red类名

图示:

在这里插入图片描述

  1. 因为案例使用了bootstrpa,工程化开发,模块化用npm/yarn下载引入使用

    yarn add bootstrap
    
  2. 在main.js引入bootstrap

    import 'bootstrap/dist/css/bootstrap.css'
    
  3. 代码

    <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 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>
              <!-- 
            <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>
    
9.2 品牌管理(增)

目标:数据新增

  • 需求1:实现表单数据新增进表格功能
  • 需求2:判断用户输入是否为空提示
  • 分析:
    1. 添加资产按钮,绑定点击事件
    2. 给表单v-model绑定vue变量收集用户输入内容
    3. 添加数组到数组中
    4. 判断用户内容是否符合规定
<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 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>
          <!-- 
        <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="资产名称"
              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. 阻止表单提交 -->
        <!--  2. 给按钮绑定点击事件 -->
        <button class="btn btn-primary" @click.prevent="btnAdd">添加资产</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') }
      ],
    };
  },
  methods: {
    // 3.把值以对象的形式插入list
    btnAdd () {
      // 5. 判断是否为空
      if(this.name.trim() === '' || this.price === 0) {
        return alert('不能为空!')
      }

      this.list.push({
        id: this.list[this.list.length - 1].id + 1,
        name: this.name,
        price: this.price,
        time: new Date()
      })
    }
  }
};
</script>

<style >
.red{
  color: red;
}
</style>
9.3 品牌管理(删)

目标:数据删除

  • 需求1:点击删除的a标签,删除数据
  • 需求2:删除没数据了要提示暂无数据的tfoot
  • 分析
    1. a标签绑定点击事件
    2. 给事件方法传id
    3. 通过id,找到对应数据删除
    4. 删除光了要让tfoot显示
    5. 删除光了再新增,有bug(id值问题)需要修复
<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 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>
            <!-- 3.使用过滤器 -->
            <td>{{obj.time | formatDate}}</td>
            <td><a href="#" @click="delFn(obj.id)">删除</a></td>
          </tr>
        </tbody>
          
        <tfoot v-if="list.length === 0">
          <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="资产名称"
              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. 阻止表单提交 -->
        <!--  2. 给按钮绑定点击事件 -->
        <button class="btn btn-primary" @click.prevent="btnAdd">添加资产</button>
      </form>
    </div>
  </div>
</template>

<script>
// 1. 导入下载模块
import moment from 'moment'
// 2. 定义过滤器
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') }
      ],
    };
  },
  methods: {
    // 3.把值以对象的形式插入list
    btnAdd () {
      // 5. 判断是否为空
      if(this.name.trim() === '' || this.price === 0) {
        return alert('不能为空!')
      }
        // 解决bug:无数组新增list没有数据,id需要给一个固定值
        let id = this.list.length > 0 ? this.list[this.list.length - 1].id + 1 : 100
      this.list.push({
        id: id,
        name: this.name,
        price: this.price,
        time: new Date()
      })
    },
    delFn(id) {
      // 通过id找到这条数据在数组中下标
      let index = this.list.findIndex(obj => obj.id === id)
      this.list.splice(index,1)
    }
  },
  filters: {
    formatDate(val) {
      return moment(val).format('YYYY-MM-DD')
    }
  }
};
</script>

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

六、过滤器

1. 过滤器的定义使用

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

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

  • Vue中的过滤器场景
    • 字母转大写,输入"hello",输出"HELLO"
    • 字符串翻转,输入"hello,world",输出"dlrow,olleh"
  • 语法
    • Vue.filter(“过滤器”,(值) => {return “返回处理后的值”})
    • filters:{过滤器的名字:(值) => {return “返回处理后的值”}}
  • 例子:
    • 全局定义字符都大写的过滤器
    • 局部定义字符串翻转的过滤器
<template>
  <div>
    <p>原来的样子:{{msg}}</p>
    <!-- 过滤器的使用语法:{{值 | 过滤器文字}} -->
    <p>使用翻转过滤器:{{msg | reverse}}</p>
    <p :title="msg | toUp">鼠标长停留</p>
  </div>
</template>

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

<style>

</style>

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

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

2. 过滤器-传参和多过滤器

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

  • 语法:
    • 过滤器传参:vue变量 | 过滤器(实参)
    • 多个过滤器:vue变量 | 过滤器1 | 过滤器2
<template>
  <div>
    <p>原来的样子:{{msg}}</p>
    <!-- 给过滤器传参语法:vue变量 | 过滤器名(值) -->
    <p>使用翻转过滤器:{{msg | reverse('|')}}</p>
    <!-- 多个过滤器使用语法:vue变量 | 过滤器1 | 过滤器2 -->
    <p :title="msg | toUp | reverse('|')">鼠标长停留</p>
  </div>
</template>

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

<style>

</style>

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

3. 案例-品牌管理(时间格式优化)

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

图示:

在这里插入图片描述

  1. 下载moment处理日期的第三方工具模块

    moment官方文档http://momentjs.cn/docs/#/displaying/

    yarn add moment
    
  2. 定义过滤器,把时间用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. 计算属性- computed

目标:一个数据,依赖另外一些数据计算而来的结果

  • 语法:

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

    • 求2两个数的和显示到页面上

      <template>
        <div>
          <p>a+b的结果是:{{num}}</p>
        </div>
      </template>
      
      <script>
      export default {
      data () {
        return {
          a: 10,
          b: 20
        }
      },
      computed: {
        num() {
          return this.a + this.b
        }
      }
      }
      </script>
      
      <style>
      
      </style>
      

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

总结:一个数据,依赖另为一些数据计算而来的结果

2. 计算属性-缓存

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

在这里插入图片描述

在这里插入图片描述

<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,World'
  }
},
methods: {
  getMessage(){
    console.log('函数执行了');
    return this.msg.split('').reverse().join('')
  }
},
computed: {
  reverseMessage(){
    console.log('计算属性执行了');
  return this.msg.split('').reverse().join('')
  }
}
}
</script>

<style>

</style>

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

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

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

在这里插入图片描述

<tr style="background-color: #EEE">
     <td>统计:</td>
     <td colspan="2">总价钱为: {{ allPrice }}</td>
     <td colspan="2">平均价: {{ svgPrice }}</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>

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

4. 计算属性-完整写法

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

  • 语法:

    computed: {
        "属性名" :{
            set(){
                
            },
            get() {
                return "值"
            }
        }
    }
    
  • 需求:

    • 计算属性给v-mode使用
<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方法

5. 案例-小选影响全选

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

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

分析:

  1. 先静态后动态,从md拿到静态标签和数据
  2. 循环生成复选框和文字,对象的c属性和小选项框的选中状态,用v-model双向绑定
  3. 定义isAll计算属性,值通过小选框们统计c属性状态得来
<template>
  <div>
    <span>全选:</span>
    <!-- v-model关联选中状态 -->
    <input type="checkbox" v-model="isAll"/>
    <button>反选</button>
    <ul>
      <li v-for="(obj,index) in arr" :key="index">
        <!-- 对象.c关联选中状态 -->
        <input type="checkbox" v-model="obj.c"/>
        <span>{{obj.name}}</span>
      </li>
    </ul>
  </div>
</template>

<script>
export default {
  data() {
    return {
      arr: [
        {
          name: "猪八戒",
          c: false,
        },
        {
          name: "孙悟空",
          c: false,
        },
        {
          name: "唐僧",
          c: false,
        },
        {
          name: "白龙马",
          c: false,
        },
      ],
    };
  },
  computed: {
    isAll() {
      // every口诀:查找数组里"不符合"条件,直接原地返回false
      return this.arr.every(obj => obj.c === true)
    }
  }
};
</script>

6. 案例-全选影响小选

目标:全选影响小选

  • 需求1:获取到全状态-改装isAll计算属性
  • 需求2:全选状态同步给所有小选框

分析:

  1. isAll改成完整写法,set里获取到全选框,勾选的状态值
  2. 遍历数据数组,赋给所有小选框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>

7. 案例-反选

目标:反选功能

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

分析:

  1. 小选框的勾选状态,在对象c属性
  2. 遍历所有对象,把对象的c属性取反赋予回去即可

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

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

八、侦听器

目标:可以侦听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>

2. 侦听器=深度侦听和立即执行

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

  • 语法:

    watch:{
        "要侦听的属性名"{
            immediate:true, //立即执行
            deep:true, //深度侦听复杂类型内变化
            handler (newVal,oldVal) {
                
            }
        }
    }
    
<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: {
    变量名:{
      handler(newVal,oldVal){

      },
      deep: true,
      immediate: true
    }
*/
watch: {
  // newVal:当前最新值
  // oldVal:上一刻值
  user: {
    handler(newVal,oldVal){
      console.log(newVal,oldVal);
    },
    deep: true,
    immediate: true
  }
}
}
</script>

<style>

</style>

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

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

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

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

分析:

  1. 在watch侦听list变化的时候,把最新的数组list转成JSON字符串存入到localStorage本地
  2. 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>

4. 品牌管理(完整版)

<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 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 | FormData}}</td>
            <td><a href="#" @click="delFn(obj.id)">删除</a></td>
          </tr>
          <tr style="background-color: #EEE">
            <td>统计:</td>
            <td colspan="2">总价钱为:{{allprice}}</td>
            <td colspan="2">平均价:{{avgprice}}</td>
          </tr>
        </tbody>
          
        <tfoot v-if="list.length === 0">
          <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="资产名称"
              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. 阻止表单提交 -->
        <!--  2. 给按钮绑定点击事件 -->
        <button class="btn btn-primary" @click.prevent="btnAdd">添加资产</button>
      </form>
    </div>
  </div>
</template>

<script>

//时间格式化包
import moment from 'moment'
export default {
  data() {
    return {
      name: "", // 名称
      price: 0, // 价格
      list: JSON.parse(localStorage.getItem('pList')) || [],
    };
  },
  methods: {
    // 3.把值以对象的形式插入list
    btnAdd () {
      // 5. 判断是否为空
      if(this.name.trim() === '' || this.price === 0) {
        return alert('不能为空!')
      }
        // 解决bug:无数组新增list没有数据,id需要给一个固定值
        let id = this.list.length > 0 ? this.list[this.list.length - 1].id + 1 : 100
      this.list.push({
        id: id,
        name: this.name,
        price: this.price,
        time: new Date()
      })
    },
    delFn(id) {
      // 通过id找到这条数据在数组中下标
      let index = this.list.findIndex(obj => obj.id === id)
      this.list.splice(index,1)
    }
  },
  filters: {
    FormData(val) {
      return moment(val).format('YYYY-MM-DD')
    }
  },
  computed: {
    // 计算总价
    allprice() {
      return this.list.reduce((sum,obj) => sum += obj.price,0)
    },
    // 计算平均价
    avgprice() {
      return (this.allprice / this.list.length).toFixed(2)
    }
  },
  watch: {
    // 侦听器list
    list: {
      handler(){
        // 2. 存入本地
        localStorage.setItem('pList',JSON.stringify(this.list))
      },
      deep:true
    }
  }
};
</script>

<style >
.red{
  color: red;
}
</style>
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值