计算属性
使用场景
如果一个结果需要依赖data中的数据,但是需要经过一些逻辑处理,才能得到你想要的数据。此时就可以使用计算属性。
例如:要对给定的字符串做翻转处理之后再来显示。
<div id="app">
<!-- 此处逻辑复杂 -->
<h3>{{msg.split('').reverse().join('')}}</h3>
</div>
<script src="./vue.js"></script>
<script>
const vm = new Vue({
el: '#app',
data: {
msg: 'javascript'
}
})
</script>
定义格式
在vue实例中,补充computed配置项。
new Vue({
// 声明计算属性
computed: {
//属性名字(计算属性名称)
//属性的值(计算属性处理函数)
计算属性名1 () {
// 对依赖的数据进行处理,且进行return
return
},
计算属性名2 () {
// 对依赖的数据进行处理,且进行return
return
}
}
})
computed 是vue的配置选项,它的值是一个对象,其中可定义多个计算属性,每个计算属性就是一个函数。
- 属性名称: 计算属性的名称
- 属性的值:计算属性的素材函数
- 对需要依赖的数据,进行逻辑上的处理
- 处理完毕后,需要return出去,返回的值就是计算属性的值
使用格式
在两个地方使用:
- 模板
- 用插值表达式 {{计算属性名}}
- 用其它指令
- 在实例内
- this.计算属性名
示例
颠倒字符串
<div id="app">
<!-- 逻辑复杂 -->
<h3>{{msg.split('').reverse().join('')}}</h3>
<!-- 计算属性 和data类似-->
<h3>{{reverseMsg}}</h3>
</div>
<script src="./vue.js"></script>
<script>
const vm = new Vue({
el: '#app',
data: {
msg: 'hi vue'
},
// 声明计算属性
computed: {
//属性名字(计算属性名称)
//属性的值(计算属性处理函数)
reverseMsg () {
// 对依赖的数据进行处理,且进行return
return this.msg.split('').reverse().join('')
}
}
})
</script>
- 在模板中使用计算属性,和使用data的方式是一样的。
- 虽然在计算属性中声明的是函数,但是在模板中使用,当中数据来使用,不需要加括号。
总结:
-
什么时间用:需要对数据进行复杂的逻辑加工,产生新的数据时。
-
定义: 就是一个特殊的配置项
computed
。其中有多个函数。 -
使用:计算属性的使用方式与data中的数据项一致;
- 计算属性-计算:这个值是对原数据进行计算之后得到的新的数据
- 计算属性-属性:它的使用方法与原数据一样。
this.计算属性名
,{{计算属性名}}
-
执行的时机: 如果计算属性中依赖的数据项变化时,它会自动调用。
computed有缓存
问:
当我们在模板中来显示一份经过对数据项进行复杂计算之后的结果时,我们有两种解决方案:
- 计算属性
- 函数
应该如何选择?
答:
-
methods定义函数,如果在模板中使用,每使用一次,就相当于调用了一次,处理逻辑会重新执行。
-
computed定义计算属性,如果在模板中使用,使用多次,但是如果依赖的数据不发生改变,计算属性对应的函数不会重新执行。
- 计算属性会做缓存,提高渲染的性能。
示例
<div id="app">
<h3>学习计算属性</h3>
<p>计算属性:{{ reversedMsg }}</p>
<p>计算属性:{{ reversedMsg }}</p>
<p>计算属性:{{ reversedMsg }}</p>
<hr>
<p>函数:{{fReversedMsg()}}</p>
<p>函数:{{fReversedMsg()}}</p>
<p>函数:{{fReversedMsg()}}</p>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
// 计算属性的特点:缓存
// - 如果计算属性所依赖的数据项并没有发生变化,则就算使用多个计算函数,其函数也只执行一次
// 因为它把结果缓存起来了。
const vm = new Vue({
el: '#app',
data: {
msg: 'javascript'
},
methods: {
fReversedMsg () {
console.log( '函数 fReversedMsg' )
//把msg的翻转一下
let newMsg = this.msg.split('').reverse().join('')
return newMsg
}
},
computed: {
reversedMsg () {
console.log( 'reversedMsg' )
//把msg的翻转一下
let newMsg = this.msg.split('').reverse().join('')
return newMsg
}
}
})
//
setTimeout (function() {
vm.msg = "abc"
// 由于计算属性有缓存,虽然在页面上用到三次,但它的函数体只执行一次。
// 对于普通的函数,在页面上用到了三次,它就会执行三次
},2000)
</script>
总结:
- 计算属性有缓存,提高渲染性能。
- 如果在页面上需要用到 对现有的数据进行加工得到新数据,则时要使用计算属性
案例-资产列表(续)
目标
-
计算总资产(计算属性)
-
根据输入的内容筛选符合条件的资产(计算属性)
筛选
思路:
- 给筛选输入框添加双向绑定
- 添加一个计算属性:根据输入框的内空对list进行筛选
- 把计算属性的值显示在表格中
求和
思路:
- 补充一个计算属性,对cList进行循环,求出它的price的和
代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/4.4.1/css/bootstrap.min.css" rel="stylesheet">
<style>
.red {
color:red
}
</style>
</head>
<body>
<div id="app">
<div class="container">
<div class="form-group">
<div class="input-group">
<!-- 使用自定义指令来让input自动获取焦点
.trim 去掉空格
-->
<input v-focus v-model.trim="key" type="text" class="form-control" placeholder="搜索">
</div>
</div>
<table class="table table-bordered table-hover">
<thead>
<tr>
<th>编号</th>
<th>资产名称</th>
<th>价格</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<!-- <tr v-for="(循环变量,索引变量) in 数据">
在计算属性的基础上进行循环
-->
<tr v-for="(item,idx) in cList">
<td>{{item.id}}</td>
<td>{{item.name}}</td>
<!-- 如果价格超过100,就有red这个类 -->
<!-- <td class="red">{{item.price}}</td> -->
<!-- 三元
-->
<!-- <td :class='item.price > 100 ? "red" : ""'>{{item.price}}</td> -->
<!-- :class= 放一个对象
如果对象中的属性值是true,
则把对象中的属性名添加到类名中
-->
<!-- 过滤器:
把12345 ===> ¥12,345
1. 在前面加一个¥
2. 千分位的分隔,加,
-->
<!-- <td :class="{red:item.price>100}">{{item.price | currency}}</td> -->
<td :class="{red:item.price>100}">{{item.price | $}}</td>
<td><a href="#" @click.prevent="hDel(idx)">删除</a></td>
</tr>
</tbody>
<tfoot>
<tr>
<!-- colspan合并单元格:把4列合成一个 -->
<td colspan="4">共计:{{cTotal | $}}</td>
</tr>
</tfoot>
</table>
<!-- 添加资产 -->
<form class="form-inline">
<div class="form-group">
<div class="input-group">
<input v-model.trim="name" type="text" class="form-control" placeholder="资产名称">
</div>
</div>
<div class="form-group">
<div class="input-group">
<input v-model.trim.number="price" type="text" class="form-control" placeholder="价格">
</div>
</div>
<!-- 阻止表单提交 -->
<button class="btn btn-primary" @click.prevent="hAdd">添加资产</button>
</form>
</div>
</div>
<!-- <script src="https://cdn.bootcdn.net/ajax/libs/vue/1.0.11/vue.js"></script> -->
<script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.11/vue.js"></script>
<script>
// 目标: 用vue.js实现
// 1. 把表格数据显示出来
// 2. 实现删除功能
// 3. 添加功能
// 4. 标注:如果价格超过100,标红色显示。
// 步骤:
// 1.引入vue.js
// 2. 实例化vue对象
// 三大件: el,data,methods
// 3. 实现功能
// (1) 循环渲染数据: v-for
// (2) 删除数据:@click,并传入要删除的下标
// (3) 标注红色 动态的添加样式,只有>100才加。 使用对象来绑定class.
// (4) 添加功能
// 1) 基本输入框结构
// 2) 点击添加
// - 收集用户的输入信息:v-model,补一些必要的修饰符。
// - 简单判空
// - 添加到list中
// (5) 计算总和
// 补充一个计算属性,在其中对list进行循环,算出总共的price
// (6) 筛选功能
// 在筛选区域的input框中输入关键字,则在下面的list中找出对应的项,其它项应该要隐藏起来
// - 给input添加v-model,收集用户需要的关键字
// - 定义一个计算属性,根据关键字来在list中进行数据的过滤,找出符合关键字要求的项
const vm = new Vue({
el: '#app',
data: {
name: '', // 名称
price: 0, // 价格
list: [
{ id: 1, name: '外套', price: 199 },
{ id: 2, name: '裤子', price: 34 },
{ id: 3, name: '鞋', price: 25.4 },
{ id: 4, name: '头发', price: 19900 },
{ id: 5, name: '梳子', price: 899 }
],
key: '' // 搜索关键字
},
computed: {
cList () {
if(this.key === '') {
return this.list
}
// 这个计算属性依赖于 key 和list,只有一个变化,则它会重新执行
// 如果视图上没有用计算属性,那么,这个依赖项变化时,计算属性不会执行
console.log("cList",Date.now())
// 根据this.key对list进行过滤
// 对于this.list中的每一项,如果其中name包含 this.key关键字,则保留
let newList = this.list.filter(item => {
// 如果其中name包含 this.key关键字
if(item.name.includes( this.key )) {
return true
} else {
return false
}
})
// console.log( newList )
return newList
},
// 由于添加了筛选功能,则要对整个的计算属性cList中数据进行循环,计算出 总共的price
cTotal: function() {
// 这个计算属性依赖于:另一个计算属性 cList
// 当cList 变化时,这个计算属性也会重新执行
let totalPrice = 0
this.cList.forEach(item => {
totalPrice = totalPrice + item.price
})
return totalPrice.toFixed(2)
}
},
methods: {
hDel (index) {
console.log('要删除的下标是',index)
// 删除数组中指定位置的元素
this.list.splice(index, 1)
},
hAdd () {
// 收集用户的输入信息
// 简单判空
if(this.name ==='') {
return
}
if(this.price == '' || this.price < 0) {
return
}
// 添加到list中
console.log(this.name,this.price)
// 向数组中添加一个元素
let id = 1
if (this.list.length > 0) {
// 新的id = 数组中最后一个元素的id值+1
id = this.list[this.list.length-1].id + 1
}
this.list.push({
id: id,
name: this.name,
price: this.price
})
}
},
filters: {
// 定义过滤器
// 过滤器的名字就是$
$: function (value, _currency) {
// 正则表达式: 全局匹配 三个连续的,且之后也是数值的 数值
var digitsRE = /(\d{3})(?=\d)/g;
value = parseFloat(value);
// 判断是否数值
if (!isFinite(value) || !value && value !== 0) return '';
// 设置金额的符号
_currency = _currency != null ? _currency : '¥';
// 取绝对值,保存两位有效数字,转成字符串
var stringified = Math.abs(value).toFixed(2);
// 获取整数部分。 -3 表示倒数3位
var _int = stringified.slice(0, -3);
// 整数部分以3为基准长度划分,剩下几位
var i = _int.length % 3;
// 取出头部
// 12345 ---> 12,
var head = i > 0 ? _int.slice(0, i) + (_int.length > 3 ? ',' : '') : '';
// 取出小数字部分
var _float = stringified.slice(-3);
// 判断符号
var sign = value < 0 ? '-' : '';
// 整体输出
return _currency + sign + head + _int.slice(i).replace(digitsRE, '$1,') + _float;
}
// $: function(value) {
// // 1. 在前面加一个¥
// // 2. 千分位的分隔,加,
// // 有难度,与vue,与过滤器没有关系。请大家自己在下面做练习。
// // 我们这里直接去vue的源码中抄一段出来,直接用。
// // 在Vue1.0版本中,它是自带这个货币金额过滤器
// console.log(value)
// return '¥'+value
// }
},
directives: {
focus: {
inserted (el) {
el.focus()
}
}
}
})
// list: [
// { id: 1, name: '外套', price: 99 },
// { id: 2, name: '裤子', price: 34 },
// { id: 3, name: '鞋', price: 25.4 },
// { id: 4, name: '头发', price: 19900 }
// ]
</script>
</body>
</html>
computed: {
cList () {
if(this.key) {
return this.list.filter(it => it.name.includes(this.key))
}
else {
return this.list
}
},
cTotal () {
return this.cList.reduce((total,it)=>{ return total+it.price*1}, 0)
}
},
自已写接口
在开发过程中,经常会遇到后端接口滞后的情况:前端页面写完了,后端的接口没有就绪,此时,我们就可以先准备接口。
前后端分离开发
代码层的分离
- 代码上的分离(前后端是同一个项目,开发目录上的区分)
- 项目级的分离(前端是一个单独的项目,后台是一个单独的项目)
开发的内容的分离
- 后端负责业务逻辑,写接口
- 前端负责业面交互,调接口。
我们的理解:必须后台先提供接口,前端才能进行开发。
分离的意义:前后端可以并行开发。
- 收到任务
- 前端后端一起制定接口规则,形成接口文档:接口名,功能,参数,返回值…
- 并行开发
- 后端开发接口:接口1,接口2,…
- 前端写页面,调用接口(自己按第2步的接口文档要求,写真的假接口)
json-server
前端自己写接口,可以用nodejs自己写,也可以借助第三方现成的工具:json-server
json-server快速地根据json文件,生成接口
json ----json-server-----> 接口
在后端接口没有交付之前,使用json-server快速创建模拟接口,推进前端开发进程。
在后端接口实现之后,再切换回去。
使用步骤
全局安装json-server
它是依赖于nodejs的第三方包。需要全局安装(不需要初始化)。
npm i json-server -g
# 安装完成
C:\Users\fanyoufu\AppData\Roaming\npm\json-server -> C:\Users\fanyoufu\AppData\Roaming\npm\node_modules\json-server\lib\cli\bin.js
+ [email protected]
updated 1 package in 12.418s
##
只需要安装一次就行了。
准备空文件夹
在任意目录下,准备一个空文件夹,取名mock-server(可改成其它名字)
创建json文件
在文件夹中新建一个名为db.json文件(可改其它名称,注意名字是是英文)
初始化结构
在db.json文件中,按json格式的要求,去定义一个对象:
- 键值对格式
- 用双引号括起来
{
"assets": [
{ "id": 1, "name": "外套", "price": 99 },
{ "id": 2, "name": "裤子", "price": 34 },
{ "id": 3, "name": "鞋","price": 25.4 },
{ "id": 4, "name": "头发", "price": 19900 }
]
}
启动接口服务
根据上面创建的db.json自动地生成接口。
在此文件夹下,打开命令行窗口,输入命令json-server db.json
(json-server后有空格)
如果没有错误,则运行结果如下:
测试
在浏览器中访问上面地址
注意:
- json格式。
- 属性名 —> 接口的地址
- 属性值 —> 接口返回数据 (数组|对象)
原理图
RESTful接口
json-server提供的接口是符合RESTful接口规范的。
在写接口时,每个程序员都有自己的写法:取不同的名字,例如:实现添加资产
A同学: localhost:3000/addAsset | delAsset
B同学: localhost:3000/insertAsset | deleteAsset
C同学: localhost:3000/tjzj | sczj
针对上述问题,提出一套约定:
RESTful接口的规范是通过
- 请求方式来决定操作类别(添加,删除,修改,查询)
- 用名词来表示要操作的目标
以上面启动的服务为例:
接口地址 | 请求方式 | 操作类型 |
---|---|---|
/assets | GET | 获取全部数据 |
/assets/1 | GET | 获取单个数据 |
/assets | POST | 添加操作 {name,price} |
/assets/1 | DELETE | 删除操作 |
/assets/1 | PUT | 完整修改{name,price} |
/assets/1 | PATCH | 局部修改{name} |
以上接口规则,就是restful规则。json-server提供的就是符合restful规则的接口。
postman测试
使用postmant来测试json-server提供的接口。
获取全部数据
GET: http://localhost:3000/assets
获取单个数据
以id为查询依据
GET: http://localhost:3000/assets/1
根据条件查询数据
json-server服务器提供了条件查询的功能
格式: ? 字段**_like**=值
不可以加引号!
添加操作
以json格式传递参数,id会自动增加。
POST:http://localhost:3000/assets/
{"name": "电脑", "price": 6880}
另一个操作示例:
删除操作
以id为查询依据
DELETE: http://localhost:3000/assets/1
完整修改
以json格式传递参数
PUT: http://localhost:3000/assets/3
要求必须传入一个完整的对象(如果这一条记录中有100个属性,则要传入100个属性),因为它会整体覆盖这条数据。
局部修改
以json格式传递参数
PATCH: http://localhost:3000/assets/3
只需要传入要更改的字段(要修改哪个属性就传入哪个属性 )。
axios调用接口
我们发ajax请求接口,有如下方式:
- 原生的。纯手写。XMLHttpRequest…
- $.ajax()。jquery中的ajax
- axios。它是一个专业的,只发ajax请求的工具。它可以在nodejs中和浏览器使用。
下载并使用
axios是一个js工具,要想使用,就要先下载。
http://www.axios-js.com/docs/
- axios提交数据的时候,默认的数据类型(content-type)是 application/json
下载地址:https://unpkg.com/axios/dist/axios.min.js
格式
完整写法
axios({
// 请求方式 get|post|put|patch|delete 大小写不区分
method: 'get',
// 路径上传参在url后进行拼接
url: 'http://localhost:3000/brands/1',
// ?后键值对传参
params: {},
// 请求体传参
data: {},
// 请求头传参
headers: {}
}).then(res=>{
console.log('成功')
}).catch(err=>{
console.log('失败')
})
简写格式
对于比较简单接口调用,可以采用简写格式:
格式:
axios.请求方式(url, .......).then().catch()
示例:
// get类型的 参数要放在写params中
axios.get(url,params: {}).then(res=>{console.log('成功')})
.catch(err=>{
console.log('失败')
})
// post类型 直接写参数
axios.post(url,{}).then(res=>{console.log('成功')})
.catch(err=>{
console.log('失败')
})
示例
通过json-server启动接口服务器,然后再写axios代码来进行调用测试
查询
// 1. 查询
// 所有
// axios.get('http://localhost:3000/assets').then(res=>{
// // res是响应对象 // res.data是后台响应内容(数据)
// console.log(res.data)
// })
// // 单个
// axios.get('http://localhost:3000/assets/2').then(res=>{
// // res是响应对象 // res.data是后台响应内容(数据)
// console.log(res.data)
// })
// 筛选条件查询
// 方式1:自己手动在地址栏?后进行拼接
// 方式2:针对get传参,?后键值对传参,固定传参方式 {params:{//传参对象 }}
axios.get('http://localhost:3000/assets',{params:{name_like:'奥'}}).then(res=>{
// res是响应对象 // res.data是后台响应内容(数据)
console.log(res.data)
})
添加
// 2. 添加
// 除去get请求外,函数的第二个参数都是请求体传参,是对象类型。
axios.post('http://localhost:3000/assets',{name:"宝马",price:500000}).then(res=>{
console.log('成功')
})
删除
// 3. 删除
axios.delete('http://localhost:3000/assets/2').then(res=>{
console.log('成功')
})
修改
// 4. 修改 完整
// axios.put('http://localhost:3000/assets/3',{name:"长安奔奔",price:36000}).then(res=>{
// console.log('成功')
// })
// 5. 修改 局部
axios.patch('http://localhost:3000/assets/3',{name:"奔奔"}).then(res=>{
console.log('成功')
})
总结:不同方式的参数怎么传递给后台。
案例-资产列表-接口版
目标:
-
用json-server启动后端接口服务器;
-
axios发请求
-
用vue管理视图
渲染列表
目标:用axios请求json-server提供的接口,并显示数据出来。
准备静态页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link rel="stylesheet" href="./bootstrap.min.css">
</head>
<body>
<div id="app">
<div class="container">
<!-- 搜索 -->
<form class="form-inline" style="padding: 20px 0">
<input type="text" class="form-control" placeholder="输入关键字进行搜索">
</form>
<!-- 表格 -->
<table class="table table-bordered table-hover">
<thead>
<tr>
<th>编号</th>
<th>资产名称</th>
<th>创建时间</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>xxx</td>
<td>xxxx</td>
<td><a href="#">删除</a></td>
</tr>
</tbody>
</table>
<!-- 添加资产 -->
<form class="form-inline">
<input type="text" class="form-control" placeholder="资产名称">
<button class="btn btn-primary">添加资产</button>
</form>
</div>
</div>
</body>
</html>
创建vue实例,实现列表渲染
实现渲染列表,大致步骤:
- 在data配置项中,声明一个列表数据list,先设置一些假数据。
- 在模板当中根据list和数据中的字符段进行渲染
- 使用v-for指令
- 注意处理无数据的情况。
vue实例如下:
<script src="./vue.js"></script>
<script src="./axios.min.js"></script>
<script>
const vm = new Vue({
el: '#app',
data: {
// 资产列表数据
list: [{id:1,name:'t',price:1}]
}
})
</script>
对应模板如下:
<tbody>
<tr v-for="item in list" :key="item.id">
<td>{{item.id}}</td>
<td>{{item.name}}</td>
<td>{{item.price}}</td>
<td><a href="#">删除</a></td>
</tr>
<tr v-if="list.length===0">
<td colspan="4" style="text-align: center;">暂无数据</td>
</tr>
</tbody>
发ajax请求,取回真数据
现在一切就绪,只需在某个时间点,向后台接口发请求,拿回数据,给list重新赋值即可
以前:window.onload = function(){} 默认渲染,页面加载完成之后
或者:$(function(){}) 默认渲染,页面文章加载完成之后
在vue中,需要默认渲染,vue实例初始化完毕后,发请求获取数据进行渲染。
vue提供一个配置选项
created
类型是函数,实例化后执行的函数。
<script src="./vue.js"></script>
<script src="./axios.min.js"></script>
<script>
const vm = new Vue({
el: '#app',
data: {
// 资产列表数据
list: []
},
created () {
// vue实例后执行(回调函数,钩子函数),在某个时机会被调用的函数。
this.get()
},
methods: {
// 获取列表数据
get() {
axios
.get("http://localhost:3000/assets")
.then(res => {
this.list = res.data
})
this.name = ""
}
}
})
</script>
总结:一般在created函数中获取页面需要的初始化数据。
删除资产
目标:点击页面上的删除按钮,把对应的数据删除掉。
步骤:
-
给a标签绑定点击事件(注意阻止默认行为)
-
定义处理函数,给函数传入资产ID
-
在函数中
-
弹出确认框提示
-
点击确认框之后,发删除请求
-
如果成功之后,更新列表(移除删除的行)
-
<td><a @click.prevent="hDel(item.id)" href="#">删除</a></td>
// 删除资产函数
hDel(id) {
if (confirm("你确定要删除" + id)) {
axios.delete("http://localhost:3000/assets/" + id).then(res => {
this.get()
})
}
},
添加资产
目标:在点击页面上的添加时,可以实现添加新资产的功能
步骤:
- 双向数据绑定,输入资产的表单元素
- 绑定表单的提交事件,阻止默认行为
- 在methods中定义一个事件函数,在函数中:
- 组织需要提交给后台的数据,{name,price},id后台不需要,自动自增。
- 提交数据前,对name进行校验
- 发起添加请求,如果成功,重新获取后台列表数据即可。
- 之前输入的内容清空
在案例中落地的代码:
<!-- 添加资产 -->
<form class="form-inline">
<div class="form-group">
<div class="input-group">
<input type="text" v-model.trim="name" class="form-control" placeholder="资产名称">
</div>
</div>
<div class="form-group">
<div class="input-group">
<input type="text" v-model.number="price" class="form-control" placeholder="价格">
</div>
</div>
<button class="btn btn-primary" @click.prevent="hAdd">添加</button>
</form>
data: {
list: [],
+ name: "",
+ price: 0,
},
// 添加资产函数
hAdd() {
if (!this.name) {
return
}
if (!this.price) {
return
}
axios
.post("http://localhost:3000/assets", {
name: this.name,
price: this.price,
})
.then(res => {
this.get()
})
this.name = ""
}