Vue2.x 核心基础
1. Vue概述
尤雨溪:Vue.js的创建者
- 2014年2月,Vue.js正式发布
- 2015年10月27日,正式发布1.0.0
- 2016年4月27日,发布2.0的预览版本
Vue:渐进式JavaScript框架
声明式渲染→组件系统→客户端路由→集中式状态管理→项目构建
- 易用:熟悉HTML、CSS、JavaScript知识后,可快速上手Vue
- 灵活:在一个库和一套完整框架之间自如伸缩
- 高效:20kB运行大小,超快虚拟 DOM
Vue渐进式: Vue从基础开始, 会循序渐进向前学习, 如下知识点可能你现在不明白, 但是学完整个vue回过头来看, 会很有帮助
小结
1. Vue是什么?
- Vue是一个javaScript渐进式框架
2. 什么是渐进式?
- 渐进式就是按需逐渐集成功能使用
3. 什么是库和框架?
- 库是方法的集合, 一般是个js文件
- 框架是拥有自己一套规则和语法
2. Vue基本使用
2.1 开发方式
传统开发模式:基于html/css/js文件开发Vue
工程化(脚手架)开发方式:在webpack环境中开发Vue,这是最推荐, 企业常用的方式
3.@vue/cli脚手架
3.1 @vue/cli和脚手架介绍
概念
脚手架是为了保证各施工过程顺利进行而搭设的工作平台
好处
- 开箱即用
- 0配置webpack
- babel支持
- css, less支持
- 开发服务器支持
小结
1. 用Vue开发项目,需要自己配置webpack吗?
- Vue官方提供了脚手架, 一套标准的文件夹+文件结构+webpack配置,快速搭建项目基本环境
2. 使用脚手架的好处是什么?
- 零配置,开箱即用,基于它快速搭建项目基本开发环境
3.2 脚手架安装步骤
- 全局安装@vue/cli 模块包
- win系统:
npm i @vue/cli -g - mac系统:
sudo npm i @vue/cli -g
- 查看
Vue命令版本
vue -V
3.3 脚手架-创建项目-启动服务
创建脚手架步骤
- 创建项目(注意: 项目名不能带大写字母, 中文和特殊符号)
//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
- 选择模板 可以上下箭头选择, 回车确定, 弄错了ctrl+c从第1步来
3. 选择包管理器
4. 等待下载脚手架项目, 需要的依赖包
5. 终端切换脚手架项目下, 启动内置的==webpack热更新开发服务器
cd vuecil-demo
yarn serve
# 或 npm run serve
3.4 脚手架-目录分析
脚手架里主要文件和作用
- node_modules - 都是下载的包
- public/index.html - 浏览器运行的网页
- src/main.js - webpack打包的入口
- src/App.vue - Vue页面入口
- package.json - 项目描述信息
3.5 脚手架-代码和结构分析
一切从main.js开始, 到index.html结束
main.js和App.vue以及index.html作用和关系?
- main.js - 项目打包入口 - Vue初始化
- App.vue - Vue页面入口
- index.html - 浏览器运行的文件
- 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. 通过单独的配置文件配置项目
- 在项目的跟目录创建文件 vue.config.js
- 在该文件中进行相关配置,从而覆盖默认配置
// vue.config.js
module.exports = {
devServer: {
port: 8888;
}
}
3.7 脚手架-eslint了解
eslint是一个插件, 内置在脚手架项目里配置好了, 运行时检查你的代码风格
例子:
- 先在main.js 随便声明个变量, 但是不要使用
2.运行后观察发现, 终端和页面都报错了
这样的错误, 证明eslint发现你代码不严谨
- 解决方式
- 方式1: 手动解决掉错误
- 方式2: 暂时关闭eslint检查,在vue.config.js中配置后重启服务
3.8 脚手架-单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>
小结
1. 单vue文件的好处?
- 独立作用域,不再担心变量重名问题
2. 单vue文件使用注意事项?
- template里只能有一个根标签
3. 单vue文件里标签和样式最后怎么显示到页面?
- webpack打包后, 插入到index.html显示
3.9 脚手架-清理欢迎界面
- src/App.vue默认有很多内容, 可以全部删除留下框
<template>
<div></div>
</template>
<script>
export default {
}
</script>
<style>
</style>
- assets 和 components 文件夹下的一切删除掉 (不要logo和HelloWorld页面)
4.Element-UI 的基本使用
Element-UI:一套为开发者、设计师和产品经理准备的基于 Vue 2.0 的桌面端组件库。
官网地址为: element-cn.eleme.io/#/zh-CN
4.1 基于命令行方式手动安装
- 安装依赖包 npm i element-ui –S
- 导入 Element-UI 相关资源
// 导入组件库
import ElementUI from 'element-ui';
// 导入组件相关样式
import 'element-ui/lib/theme-chalk/index.css';
// 配置 Vue 插件
Vue.use(ElementUI);
4.2 基于图形化界面自动安装
- 运行
vue ui命令,打开图形化界面 - 通过
Vue 项目管理器,进入具体的项目配置面板 - 点击
插件 -> 添加插件,进入插件查询面板 - 搜索
vue-cli-plugin-element并安装 - 配置插件,实现按需导入,从而减少打包后项目的体积
5. Vue模板语法
5.1 模板语法概述
1. 如何理解前端渲染?
2. 前端渲染方式
- 原生js拼接字符串
- 使用前端模板引擎
- 使用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. 模板语法概览
- 插值表达式
- 指令
- 事件绑定
- 属性绑定
- 样式绑定
- 分支循环结构
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 (视图自动同步)
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. 分支结构
- v-if
- v-else
- v-else-if
- 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就地更新流程
这种 虚拟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算法, 找不不同, 只更新变化的部分(重绘/回流)到页面 - 也叫打补丁
优点:
- 提高了更新DOM的性能(不用把页面全删除重新渲染)
- 虚拟DOM只包含必要的属性(没有真实DOM上百个属性
总结: 虚拟DOM保存在内存中, 只记录dom关键信息, 配合diff算法提高DOM更新的性能
在内存中比较差异, 然后给真实DOM打补丁更新上
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结构 对比过程
性能不高, 从第二个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结构 对比过程
- v-for先循环产生新的DOM结构, key是连续的, 和数据对应
- 然后比较新旧DOM结构, 找到区别, 打补丁到页面上,最后补一个li, 然后从第二个往后, 都要更新内容 口诀: key的值有id用id, 没id用索引
3. 有key - 值为id
- key的值只能是唯一不重复的, 字符串或数值
- v-for不会移动DOM, 而是尝试复用, 就地更新,如果需要v-for移动DOM, 你需要用特殊 attribute
key来提供一个排序提示 - 新DOM里数据的key存在, 去旧的虚拟DOM结构里找到key标记的标签, 复用标签
- 新DOM里数据的key存在, 去旧的虚拟DOM结构里没有找到key标签的标签, 创建
- 旧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结构 对比过程
总结: 不用key也不影响功能(就地更新), 添加key可以提高更新的性能
案例-品牌管理(铺增删)
- 需求1: 把默认数据显示到表格上
- 需求2: 注意资产超过100的, 都用红色字体标记出来
- 需求3: 点击删除的a标签, 删除数据
- 需求4: 实现底部添加资产的功能
细节:
- 注意a标签有默认行为-跳转刷新页面(如果有href属性)
- 添加资产时, 提示用户数据不能为空
- form表单里的button的点击事件, 会触发默认表单提交的行为
- 因为案例使用了bootstrap, 工程化开发, 模块化用npm/yarn下载引入使用
yarn add bootstrap
- 在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>
<div class="form-group">
<div class="input-group">
<input
type="text"
class="form-control"
placeholder="价格"
v-model="price"
/>
</div>
</div>
<!-- 阻止表单提交 -->
<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计算属性-缓存
目标: 计算属性是基于它们的依赖项的值结果进行缓存的,只要依赖的变量不变, 都直接从缓存取结果
<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>
总结: 计算属性根据依赖变量结果缓存, 依赖变化重新计算结果存入缓存, 比普通方法性能更高
案例-品牌管理(总价和均价)
目标: 基于之前的案例, 完成总价和均价的计算效果
// 目标: 总价和均价显示
// 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>