Vue2.x 核心基础(Vue概述,Vue基本使用,@vue/cli脚手架,Element-UI 的基本使用,Vue模板语法)

249 阅读14分钟

Vue2.x 核心基础

1. Vue概述

尤雨溪:Vue.js的创建者

  • 2014年2月,Vue.js正式发布
  • 2015年10月27日,正式发布1.0.0
  • 2016年4月27日,发布2.0的预览版本

Vue:渐进式JavaScript框架

声明式渲染→组件系统→客户端路由→集中式状态管理→项目构建

官网:cn.vuejs.org/v2/guide/

  1. 易用:熟悉HTML、CSS、JavaScript知识后,可快速上手Vue
  2. 灵活:在一个库和一套完整框架之间自如伸缩
  3. 高效:20kB运行大小,超快虚拟 DOM

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

image.png

小结

1. Vue是什么?

  • Vue是一个javaScript渐进式框架

2. 什么是渐进式?

  • 渐进式就是按需逐渐集成功能使用

3. 什么是库和框架?

  • 库是方法的集合, 一般是个js文件
  • 框架是拥有自己一套规则和语法

2. Vue基本使用

2.1 开发方式

传统开发模式:基于html/css/js文件开发Vue

image.png

工程化(脚手架)开发方式:在webpack环境中开发Vue,这是最推荐, 企业常用的方式

image.png

3.@vue/cli脚手架

3.1 @vue/cli和脚手架介绍

概念

脚手架是为了保证各施工过程顺利进行而搭设的工作平台

好处

  • 开箱即用
  • 0配置webpack
  • babel支持
  • css, less支持
  • 开发服务器支持

小结

1. 用Vue开发项目,需要自己配置webpack吗?

  • Vue官方提供了脚手架, 一套标准的文件夹+文件结构+webpack配置,快速搭建项目基本环境

2. 使用脚手架的好处是什么?

  • 零配置,开箱即用,基于它快速搭建项目基本开发环境

3.2 脚手架安装步骤

  1. 全局安装@vue/cli 模块包
  • win系统: npm i @vue/cli -g
  • mac系统: sudo npm i @vue/cli -g
  1. 查看Vue命令版本
    vue -V

3.3 脚手架-创建项目-启动服务

创建脚手架步骤

  1. 创建项目(注意: 项目名不能带大写字母, 中文和特殊符号)
//vue和create是命令, vuecli-demo是自己的文件夹名
vue create vuecli-demo
// 1. 基于 交互式命令行 的方式,创建 新版 vue 项目
vue create my-project
// 2. 基于 图形化界面 的方式,创建 新版 vue 项目
vue ui
// 3. 基于 2.x 的旧模板,创建 旧版 vue 项目
npm install -g @vue/cli-init
vue init webpack my-project
  1. 选择模板 可以上下箭头选择, 回车确定, 弄错了ctrl+c从第1步来

image.png 3. 选择包管理器

image.png 4. 等待下载脚手架项目, 需要的依赖包

image.png 5. 终端切换脚手架项目下, 启动内置的==webpack热更新开发服务器

cd vuecil-demo

yarn serve
# 或 npm run serve

3.4 脚手架-目录分析

image.png

脚手架里主要文件和作用

  1. node_modules - 都是下载的包
  2. public/index.html - 浏览器运行的网页
  3. src/main.js - webpack打包的入口
  4. src/App.vue - Vue页面入口
  5. package.json - 项目描述信息

3.5 脚手架-代码和结构分析

一切从main.js开始, 到index.html结束

image.png

main.js和App.vue以及index.html作用和关系?

  1. main.js - 项目打包入口 - Vue初始化
  2. App.vue - Vue页面入口
  3. index.html - 浏览器运行的文件
  4. App.vue => main.js => index.html

3.6 脚手架-自定义配置

1. 通过 package.json 配置项目

// 必须是符合规范的json语法
"vue": {
"devServer": {
    "port": "8888",
    "open" : true
    }
},

注意:不推荐使用这种配置方式。因为 package.json 主要用来管理包的配置信息;为了方便维护,推荐将 vue 脚手架相关的配置,单独定义到 vue.config.js 配置文件中。

2. 通过单独的配置文件配置项目

  1. 在项目的跟目录创建文件 vue.config.js
  2. 在该文件中进行相关配置,从而覆盖默认配置
// vue.config.js

module.exports = {
devServer: {
    port: 8888;
    }
}

3.7 脚手架-eslint了解

eslint是一个插件, 内置在脚手架项目里配置好了, 运行时检查你的代码风格

例子:

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

image.png 2.运行后观察发现, 终端页面都报错了 这样的错误, 证明eslint发现你代码不严谨

  1. 解决方式
  • 方式1: 手动解决掉错误
  • 方式2: 暂时关闭eslint检查,在vue.config.js中配置后重启服务

image.png

3.8 脚手架-单vue文件

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

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

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

小结

1. 单vue文件的好处?

  • 独立作用域,不再担心变量重名问题

2. 单vue文件使用注意事项?

  • template里只能有一个根标签

3. 单vue文件里标签和样式最后怎么显示到页面?

  • webpack打包后, 插入到index.html显示

3.9 脚手架-清理欢迎界面

  1. src/App.vue默认有很多内容, 可以全部删除留下框
<template>
  <div></div>
</template>

<script>
export default {

}
</script>

<style>

</style>
  1. assets 和 components 文件夹下的一切删除掉 (不要logo和HelloWorld页面)

image.png

4.Element-UI 的基本使用

Element-UI:一套为开发者、设计师和产品经理准备的基于 Vue 2.0 的桌面端组件库。

官网地址为: element-cn.eleme.io/#/zh-CN

4.1 基于命令行方式手动安装

  1. 安装依赖包 npm i element-ui –S
  2. 导入 Element-UI 相关资源
// 导入组件库
import ElementUI from 'element-ui';
// 导入组件相关样式
import 'element-ui/lib/theme-chalk/index.css';
// 配置 Vue 插件
Vue.use(ElementUI);

4.2 基于图形化界面自动安装

  1. 运行 vue ui 命令,打开图形化界面
  2. 通过 Vue 项目管理器,进入具体的项目配置面板
  3. 点击 插件 -> 添加插件,进入插件查询面板
  4. 搜索 vue-cli-plugin-element 并安装
  5. 配置插件,实现按需导入,从而减少打包后项目的体积

5. Vue模板语法

5.1 模板语法概述

1. 如何理解前端渲染?

image.png

2. 前端渲染方式

  1. 原生js拼接字符串
  2. 使用前端模板引擎
  3. 使用vue特有的模板语法

3. 原生js拼接字符串

基本上就是将数据以字符串的方式拼接到HTML标签中

var d = data.weather;

var info = document.getElementById('info');

info.innerHTML = '';

for(var i=0;i<d.length;i++){

var date = d[i].date;

var day = d[i].info.day;

var night = d[i].info.night;

var tag = '';

tag += '<span>日期:'+date+'</sapn><ul>';

tag += '<li>白天天气:'+day[1]+'</li>'

tag += '<li>白天温度:'+day[2]+'</li>'

tag += '<li>白天风向:'+day[3]+'</li>'

tag += '<li>白天风速:'+day[4]+'</li>'

tag += '</ul>';

var div = document.createElement('div');

div.innerHTML = tag;

info.appendChild(div);

}

缺点:不同开发人员的代码风格差别很大,随着业务的复杂,后期的维护变得逐渐困难起来。

4. 使用前端模板引擎

基于模板引擎art-template的一段代码,与拼接字符串相比,代码明显规范了很多,它拥有自己的一套模板语法规则。

语法: {{ 表达式 }}

<template>
  <div>
    <h1>{{ msg }}</h1>
    <h2>{{ obj.name }}</h2>
    <h3>{{ obj.age > 18 ? '成年' : '未成年' }}</h3>
  </div>
</template>

<script>
export default {
  data() { // 格式固定, 定义vue数据之处
    return {  // key相当于变量名
      msg: "hello, vue",
      obj: {
        name: "小vue",
        age: 5
      }
    }
  }
}
</script>

优点:大家都遵循同样的规则写代码,代码可读性明显提高了,方便后期的维护。

缺点:没有专门提供事件机制。

5. 模板语法概览

  1. 插值表达式
  2. 指令
  3. 事件绑定
  4. 属性绑定
  5. 样式绑定
  6. 分支循环结构

5.2 指令

1. 什么是指令?

属性

  • 指令的本质就是自定义属性
  • 指令的格式:以v-开始(比如:v-cloak)

2. v-cloak指令用法

  • 插值表达式存在的问题:“闪动”
  • 如何解决该问题:使用v-cloak指令
  • 解决该问题的原理:先隐藏,替换好值之后再显示最终的值 官网:cn.vuejs.org/v2/api/

3. 数据绑定指令

v-text 填充纯文本

  • 相比插值表达式更加简洁 v-html 填充HTML片段
  • 存在安全问题
  • 本网站内部数据可以使用,来自第三方的数据不可以用 v-pre 填充原始信息
  • 显示原始信息,跳过编译过程(分析编译过程)

5.3 双向数据绑定指令

1. 什么是双向数据绑定?

  • 数据变化 -> 视图自动同步
  • 视图变化 -> 数据自动同步

设计模式: 是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结。(代码分层, 架构设计)

2. MVVM设计思想

MVVM,一种软件架构模式,决定了写代码的思想和层次

  • M: model数据模型 (data里定义)
  • V: view视图 (页面标签)
  • VM: ViewModel视图模型 (vue.js源码)

MVVM通过数据双向绑定让数据自动地双向同步 不再需要操作DOM

  • V (修改视图) -> M(数据自动同步)
  • M(修改数据) -> V (视图自动同步)

image.png

5.4 事件绑定

1. Vue如何处理事件?

  • v-on指令用法
<input type=‘button' v-on:click='num++'/>
  • v-on简写形式
<input type=‘button' @click='num++'/>

2. 事件函数的调用方式

  • 直接绑定函数名称
<button v-on:click='say'>Hello</button>
  • 调用函数
<button v-on:click='say()'>Say hi</button>

案例:点击随机生成笑话

<template>
  <div>
    <p>{{ word }}</p>
    <button @click="jockBtnFn">点击说笑话</button>
  </div>
</template>

<script>
export default {
  data(){
    return {
      word: '这里是一条笑话',
      jockArr: ['我去相亲网站去了, 那你找到对象了吗? 不! 我找到了他们网站的一个Bug', '这个需求很简单, 怎么实现我不管, 明天上线', '程序员是推动这个世界进步的人']
    }
  },
  methods: {
    jockBtnFn(){
      let randNum = Math.floor(Math.random() * this.jockArr.length)
      let str = this.jockArr[randNum]
      this.word = str
    }
  }
}
</script>

3. 事件函数参数传递

普通参数和事件对象

<template>
  <div>
    <a @click="one" href="http://www.baidu.com">阻止百度</a>
    <hr>
    <a @click="two(10, $event)" href="http://www.baidu.com">阻止去百度</a>
  </div>
</template>

<script>
export default {
  methods: {
    one(e){
      e.preventDefault()
    },
    two(num, e){
      e.preventDefault()
    }
  }
}
</script>

4. 事件修饰符

语法:@事件名.修饰符="methods里函数"

  • .stop 阻止冒泡
  • .prevent 阻止默认行为
<template>
  <div @click="fatherFn">
    <!-- vue对事件进行了修饰符设置, 在事件后面.修饰符名即可使用更多的功能 -->
    <button @click.stop="btn">.stop阻止事件冒泡</button>
    <a href="http://www.baidu.com" @click.prevent="btn">.prevent阻止默认行为</a>
  </div>
</template>

<script>
export default {
  methods: {
    fatherFn(){
      console.log("father被触发");
    },
    btn(){
      console.log(1);
    }
  }
}
</script>

5. 按键修饰符

语法:

  • @keyup.enter - 监测回车按键
  • @keyup.esc - 监测返回按键
<template>
  <div>
    <input type="text" @keydown.enter="enterFn">
    <hr>
    <input type="text" @keydown.esc="escFn">
  </div>
</template>

<script>
export default {
 methods: {
   enterFn(){
     console.log("enter回车按键了");
   },
   escFn(){
     console.log("esc按键了");
   }
 }
}
</script>

6. 自定义按键修饰符

全局 config.keyCodes 对象

Vue.config.keyCodes.f1 = 112

案例:翻转世界

需求:点击按钮 - 把文字取反显示 - 再点击取反显示(回来了)

<template>
  <div id="app">
    <h1>{{msg}}</h1>
    <button @click="btn">逆转</button>
    <button @click="tra">世界</button>
  </div>
</template>

<script>


export default {
    data() {
      return {
        msg:"逆转世界"
      }
    },
    methods: {
      btn(){
        this.msg = this.msg.split('').reverse().join('');
      },

      tra(){
        this.msg=this.msg.substring(2,4)
      }

    },
}
</script>

<style scoped>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
h1{
  color: red;
  font-size: 160px;

}
</style>

5.5 属性绑定

1. Vue如何动态处理属性?

  • 语法:v-bind:属性名="vue变量"
  • 简写::属性名="vue变量"
<!-- vue指令-v-bind属性动态赋值 -->
<a v-bind:href="url">我是a标签</a>
<img :src="imgSrc">

2. v-model的低层实现原理分析

<input v-bind:value="msg" v-on:input="msg=$event.target.value">

5.6 样式绑定

1. class样式处理

对象语法

语法:

  • :class="{类名: 布尔值}"
<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>

数组语法:class="[类名: errorClass]"

<div v-bind:class="[activeClass, errorClass]"></div>

2. 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>

数组语法

<div v-bind:style="[baseStyles, overridingStyles]"></div>

5.7 分支循环结构

1. 分支结构

  1. v-if
  2. v-else
  3. v-else-if
  4. v-show

2. v-if与v-show与v-else使用

语法:

  • v-show="vue变量"
  • v-if="vue变量" 原理:
  • v-show 用的display:none隐藏 (频繁切换使用)
  • v-if 直接从DOM树上移除,v-if可以配合v-else使用
<template>
  <div>
    <h1 v-show="isOk">v-show的盒子</h1>
    <h1 v-if="isOk">v-if的盒子</h1>

    <div>
      <p v-if="age > 18">我成年了</p>
      <p v-else>还得多吃饭</p>
    </div>
  </div>
</template>

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

案例-折叠面板

需求: 点击展开或收起时,把内容区域显示或者隐藏

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

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

<script>
export default {
  data() {
    return {
      isShow: false
    }
  }
}
</script>

3. 循环结构

语法

  • v-for="(值变量, 索引变量) in 目标结构"
  • v-for="值变量 in 目标结构"
v-for遍历数组
<li v-for='item in list'>{{item}}</li>

<li v-for='(item,index) in list'>{{item}} + '---' +{{index}}</li>
<template>
  <div id="app">
      <!-- v-for 把一组数据, 渲染成一组DOM -->
      <!-- 口诀: 让谁循环生成, v-for就写谁身上 -->
      <p>学生姓名</p>
      <ul>
        <li v-for="(item, index) in arr" :key="item">
          {{ index }} - {{ item }}
        </li>
      </ul>
  </div>
</template>

<script>
export default {
  data() {
    return {
      arr: ["小明", "小欢欢", "大黄"]
  }
}
</script>
v-for遍历对象
<div v-for='(value, key, index) in object'></div>
<template>
  <div id="app">
      <!-- 省略其他 -->
      <p>学生详细信息</p>
      <ul>
        <li v-for="obj in stuArr" :key="obj.id">
          <span>{{ obj.name }}</span>
          <span>{{ obj.sex }}</span>
          <span>{{ obj.hobby }}</span>
        </li>
      </ul>
  </div>
</template>

<script>
export default {
  data() {
    return {
      // ...省略其他
      stuArr: [
        {
          id: 1001,
          name: "孙悟空",
          sex: "男",
          hobby: "吃桃子",
        },
        {
          id: 1002,
          name: "猪八戒",
          sex: "男",
          hobby: "背媳妇",
        }
      ]
    }
  }
}
</script>
key的作用:帮助Vue区分不同的元素,从而提高性能
<li :key='item.id' v-for='(item,index) in list'>{{item}} + '---' {{index}}</li>
v-if和v-for结合使用
<div v-if='value==12' v-for='(value, key, index) in object'></div>
v-for更新监测

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

<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更新

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

详解v-for就地更新流程

image.png

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

6.虚拟dom

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

6.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'
    }
}

6.2 以后vue数据更新

  • 生成新的虚拟DOM结构
  • 和旧的虚拟DOM结构对比
  • 利用diff算法, 找不不同, 只更新变化的部分(重绘/回流)到页面 - 也叫打补丁

优点:

  1. 提高了更新DOM的性能(不用把页面全删除重新渲染)
  2. 虚拟DOM只包含必要的属性(没有真实DOM上百个属性

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

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

image.png

7. diff算法

7.1根节点

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>

3. 根元素没变, 子元素没变, 元素内容改变

7.2 子节点

1. 无key - 就地更新

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

<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, '新来的')
        }
    }
};

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

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

2. 有key - 值为索引 - 就地更新

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

<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, '新来的')
        }
    }
};

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

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

3. 有key - 值为id

  1. key的值只能是唯一不重复的, 字符串或数值
  2. v-for不会移动DOM, 而是尝试复用, 就地更新,如果需要v-for移动DOM, 你需要用特殊 attribute key 来提供一个排序提示
  3. 新DOM里数据的key存在, 去旧的虚拟DOM结构里找到key标记的标签, 复用标签
  4. 新DOM里数据的key存在, 去旧的虚拟DOM结构里没有找到key标签的标签, 创建
  5. 旧DOM结构的key, 在新的DOM结构里没有了, 则移除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结构 和 新 - 虚拟DOM结构 对比过程 image.png 总结: 不用key也不影响功能(就地更新), 添加key可以提高更新的性能

案例-品牌管理(铺增删)

  • 需求1: 把默认数据显示到表格上
  • 需求2: 注意资产超过100的, 都用红色字体标记出来
  • 需求3: 点击删除的a标签, 删除数据
  • 需求4: 实现底部添加资产的功能

细节:

  • 注意a标签有默认行为-跳转刷新页面(如果有href属性)
  • 添加资产时, 提示用户数据不能为空
  • form表单里的button的点击事件, 会触发默认表单提交的行为
  1. 因为案例使用了bootstrap, 工程化开发, 模块化用npm/yarn下载引入使用
    yarn add bootstrap
  1. 在main.js - 引入bootstrap
import "bootstrap/dist/css/bootstrap.css" // 默认找文件夹下的index文件(但是这个不是所以需要写路径)
<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, index) in list" :key="index">
            <td>{{ obj.id }}</td>
            <td>{{ obj.name }}</td>

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

        <tfoot>
          <tr>
            <td colspan="5" style="text-align: center; display: none">
              暂无数据
            </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="price"
            />
          </div>
        </div>
        &nbsp;&nbsp;&nbsp;&nbsp;
        <!-- 阻止表单提交 -->
        <button class="btn btn-primary" @click.prevent="addfn">添加资产</button>
      </form>
    </div>
  </div>
</template>

<script>
// 导入
import dayjs from 'dayjs';
export default {
  data() {
    return {
      name: '', // 名称
      price: '', // 价格

      // 资产列表数组
      list: [
        { id: 100, name: '外套', price: 199, time: new Date('2020/1/1') },
        { 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: {
    addfn() {
      if (this.name.trim().length === 0 || this.price === 0) {
        alert('不能为空');
        return;
      }
      let id =
        this.list.length == 0 ? 100 : this.list[this.list.length - 1].id + 1;

      this.list.push({
        // 当前数组最后一个对象的id+1作为新对象id值
        id: id,
        name: this.name,
        price: this.price,
        time: new Date('2017-02-22'),
      });
      (this.name = ''), (this.price = '');
    },
    delfn(e) {
      this.list.splice(e, 1);
    },
    //格式化时间
    formatTime(date) {
      return dayjs(date).format('YYYY年MM月DD日');
    },
  },
};
</script>

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

8. vue计算属性

8.1 vue计算属性-computed

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

computed: {
    "计算属性名" () {
        return "值"
    }
}
  • 需求1: 求2个数的和显示到页面上
  • 需求2: 字符串翻转
<template>
  <div>
    <p>{{ num }}</p>
  </div>
</template>

<script>
export default {
  data(){
    return {
      a: 10,
      b: 20
    }
  },
  // 计算属性:
  // 场景: 一个变量的值, 需要用另外变量计算而得来
  /*
    语法:
    computed: {
      计算属性名 () {
        return 值
      }
    }
  */
 // 注意: 计算属性和data属性都是变量-不能重名
 // 注意2: 函数内变量变化, 会自动重新计算结果返回
  computed: {
    num(){
      return this.a + this.b
    }
  }
}
</script>

<style>

</style>

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

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

8.2 vue计算属性-缓存

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

image.png

<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"
    }
  },
  // 计算属性优势:
  // 带缓存
  // 计算属性对应函数执行后, 会把return值缓存起来
  // 依赖项不变, 多次调用都是从缓存取值
  // 依赖项值-变化, 函数会"自动"重新执行-并缓存新的值
  computed: {
    reverseMessage(){
      console.log("计算属性执行了");
      return this.msg.split("").reverse().join("")
    }
  },
  methods: {
    getMessage(){
      console.log("函数执行了");
      return this.msg.split("").reverse().join("")
    }
  }
}
</script>

<style>

</style>

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

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

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

image.png

// 目标: 总价和均价显示
// 1. 末尾补tr - 显示总价和均价

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

<script>
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>

案例-全选和反选

<template>
  <div>
    <span>全选:</span>
    <!-- 4. v-model 关联全选 - 选中状态 -->
    <input type="checkbox" v-model="isAll"/>
    <button @click="btn">反选</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: {
      set(val){
        // 7. 全选框 - 选中状态(true/false)
        this.arr.forEach(obj => obj.c = val)
      },
      get(){
        // 6. 统计小选框状态 ->  全选状态
        // every口诀: 查找数组里"不符合"条件, 直接原地返回false
        return this.arr.every(obj => obj.c === true)
      }
    }
  },
  methods: {
    btn(){
      // 8. 让数组里对象的c属性取反再赋予回去
      this.arr.forEach(obj => obj.c = !obj.c)
    }
  }
};
</script>

<style>
</style>

9. vue侦听器

vue侦听器-watch

vue侦听器-深度侦听和立即执行