Vue完整版(三)
八、Axios异步通信
- 什么是Axios
Axios 是一个开源的可以用在浏览器端和 NodeJS
的异步通信框架,她的主要作用就是实现 AJAX 异步通信,其功能特点如下:
- 从浏览器中创建
XMLHttpRequests
- 从 node.js 创建 http 请求
- 支持 Promise API [JS中链式编程]
- 拦截请求和响应
- 转换请求数据和响应数据
- 取消请求
- 自动转换 JSON 数据
- 客户端支持防御 XSRF(跨站请求伪造)
GitHub:https://github.com/axios/axios
- 为什么要使用Axios
由于Vue.js
是一个 视图层框架 且作者(尤雨溪) 严格准守SoC (关注度分离原则)
,所以Vue.js
并不包含Ajax的通信功能,为了解决通信问题,作者单独开发了一个名为vue-resource
的插件,不过在进入2.0 版本以后停止了对该插件的维护并推荐了Axios
框架。少用jQuery
,因为它操作Dom
太频繁 !
- 导入Axios
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
- 实例 demo
咱们开发的接口大部分都是采用 JSON 格式,可以先在项目里模拟一段 JSON 数据,数据内容如下:创建一个名为 data.json
的文件并填入上面的内容,放在项目的根目录下。
{
"name": "roc",
"url": "https://blog.csdn.net/",
"page": 1,
"isNonProfit": true,
"address": {
"street": "南昌",
"city": "江西",
"country": "中国"
},
"links": [
{
"name": "博客园",
"url": "https://www.cnblogs.com/"
},
{
"name": "GitHub",
"url": "https://github.com/"
},
{
"name": "Gitee",
"url": "https://gitee.com/"
}
]
}
新建一个demo.html,来试试Axios的异步通信:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<!--v-clock 解决闪烁问题-->
<style>
[v-clock] {
display: none;
}
</style>
</head>
<body>
<!--view层 模板-->
<div id="vue" v-clock>
<div>名称:{{info.name}} </div>
<div>地址:{{info.address.country}}-{{info.address.city}}-{{info.address.street}} </div>
<div>博客:<a v-bind:href="info.url">{{info.url}}</a></div>
</div>
<!--导入Vue.js-->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<!--导入axios-->
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script>
var vm = new Vue({
el: "#vue",
//Model:数据
data: {
},
//data : vm的属性
//data() : vm方法
data(){
return{
//请求的返回参数,必须和json字符串一样
info:{
name: null,
url: null,
address: {
street: null,
city: null,
country: null
}
}
}
},
//钩子函数,链式编程,ES6新特性
mounted(){
axios.get("../data.json").then(response => (this.info = response.data))
axios.get("../data.json").then(response => (console.log(response.data)))
}
});
</script>
</body>
</html>
data
: vm的属性data()
: vm方法return{}
: 请求的返回参数,必须和json字符串一样
mounted(){}
: 钩子函数,链式编程,ES6新特性
说明:
- 在这里使用了
v-bind
将a:href
的属性值与 Vue 实例中的数据进行绑定- 使用 Axios 框架的 get 方法请求
AJAX
并自动将数据封装进了 Vue 实例的数据对象中- 我们在data中的数据结构必须要和
Ajax
响应回来的数据格式匹配!- 关于代码中的
v-clock 解决闪烁问题
,可参考 Vue指令:v-clock解决页面闪烁问题
九、计算属性
- 什么是计算属性
- 计算属性的重点突出在
属性
两个字上(属性是名词),首先它是个属性
其次这个属性有计算
的能力(计算是动词),这里的计算
就是个函数; - 简单点说,它就是一个能够将计算结果缓存起来的属性(将行为转化成了静态的属性),仅此而已;可以想象为缓存!
- 实例 demo
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!--view层 模板-->
<div id="app">
<!-- 调用methods里的方法,得用() -->
<p>currentTime1: {{currentTime1()}}</p>
<!-- 调用计算属性computed的方法,不能用() -->
<p>currentTime2: {{currentTime2}}</p>
</div>
<!--1. 导入Vue.js-->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
var vm = new Vue({
el: "#app",
//Model:数据
data: {
message: "Hello,Roc!"
},
methods: {
currentTime1: function () {
return Date.now(); //返回当前时间戳
}
},
//计算属性
computed: { //methods、computed中的方法名不能重名;重名后,只会调用methods中的方法
currentTime2: function () {
return Date.now(); //返回一个时间戳
},
}
});
</script>
</body>
</html>
说明:
- 调用
methods
里的方法,得用()
methods
是方法
- 调用
computed
的方法,不能用()
computed
是计算属性
,是属性
methods、computed
中的方法名不能重名
;重名后,只会调用methods
中的方法
- 进阶
//计算属性
computed: { //methods、computed中的方法名不能重名;重名后,只会调用methods中的方法
currentTime2: function() {
//currentTime1: function() {
this.message;
return Date.now(); //返回一个时间戳
}
}
属性返回前,如果有数据刷新,缓存页也刷新!
- 结论
调用方法时,每次都需要进行计算
,既然有计算过程则必定产生系统开销,那如果这个结果是不经常变化的呢?
此时就可以考虑将这个结果缓存起来
,采用计算属性可以很方便的做到这一点,计算属性的主要特性就是为了将不经常变化的计算结果进行缓存
,以节约我们的系统开销。
十、插槽slot:内容分发
- 什么是插槽
Vue 实现了一套内容分发的 API,这套 API 的设计灵感源自 Web Components 规范草案,将 <slot>
元素作为承载分发内容的出口。
它允许你像这样合成组件:
<navigation-link url="/profile">
Your Profile
</navigation-link>
然后你在 <navigation-link>
的模板中可能会写为:
<a
v-bind:href="url"
class="nav-link"
>
<slot></slot>
</a>
当组件渲染的时候,<slot></slot>
将会被替换为“Your Profile”。插槽内可以包含任何模板代码,包括 HTML:
<navigation-link url="/profile">
<!-- 添加一个 Font Awesome 图标 -->
<span class="fa fa-user"></span>
Your Profile
</navigation-link>
甚至其它的组件:
<navigation-link url="/profile">
<!-- 添加一个图标的组件 -->
<font-awesome-icon name="user"></font-awesome-icon>
Your Profile
</navigation-link>
如果 <navigation-link>
的 template
中 没有 包含一个 <slot>
元素,则该组件起始标签和结束标签之间的任何内容都会被抛弃。
- 内容分发
在 Vue.js
中我们使用<slot>
元素作为承载分发内容的出口,作者称其为 插槽,可以应用在组合组件的场景中。
- 实例 demo
比如准备制作一个待办事项组件(todo),该组件由待办标题(todo-title)和待办内容(todo-items)组成,但这三个组件又是相互独立的,该如何操作呢?
- 第一步: 定义一个待办事项的组件
<!-- 组件:todo -->
<div id="app"">
<todo></todo>
</div>
<!-- 1.导入Vue.js -->
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.21/dist/vue.min.js"></script>
<script>
Vue.component('todo', {
template: '<div>\
<div>待办事项</div>\
<ul>\
<li>Linux</li>\
<li>Spring Boot</li>\
<li>Vue</li>\
</ul>\
</div>'
});
</script>
- 第二步: 我们需要让,待办事项的标题和值实现动态绑定,怎么做呢? 我们可以留出一个插槽!
- 将上面的代码留出一个插槽,即
<slot>
:
Vue.component('todo', {
template: '<div>\
<slot name="todo-title"></slot>\
<ul>\
<slot name="todo-items"></slot>\
</ul>\
</div>'
});
- 定义一个名为
todo-title
的待办标题组件 和todo-items
的待办内容组件:
Vue.component('todo-title', {
props: ['title'],
template: '<div>{{title}}</div>'
});
Vue.component('todo-items', {
//这里的index,就是数组的下标,使用for循环遍历的时候,可以循环出来!
props: ['index', 'item'],
template: '<li>{{index + 1}}. {{item}}</li>',
});
- 实例化 Vue 并初始化数据:
var vm = new Vue({
el: '#app',
data: {
todoTitle: 'Jerry学JavaWeb',
todoItems: ['Linux', 'Spring Boot', 'Vue'],
}
});
- 将这些值,通过插槽插入:
<!-- 组件:todo -->
<div id="app"">
<todo>
<todo-title slot="todo-title"
v-bind:title="todoTitle">
</todo-title>
<todo-items slot="todo-items"
v-for="(item, index) in todoItems"
v-bind:item="item"
v-bind:index="index":key="index"></todo-items>
</todo-items>
</todo>
</div>
说明:
我们的 todo-title
和 todo-items
组件分别被分发到了 todo
组件的 todo-title
和 todo-items
插槽中。
十一、自定义事件
文档:https://cn.vuejs.org/v2/guide/components-custom-events.html
- 什么是自定义事件
通过上面"插槽slot:内容分发"的代码不难发现,数据项在Vue的实例中,但删除操作要在组件中完成,那么组件如何才能删除Vue实例中的数据呢?
此时就涉及到参数传递与事件分发了,Vue为我们提供了自定义事件的功能很好的帮助我们解决了这个问题:
使用this.$emit ('自定义事件名',参数)
this.$emit('myEvent')
则监听这个名字的 kebab-case 版本是不会有任何效果的:
<!-- 没有效果 -->
<my-component v-on:my-event="doSomething"></my-component>
不同于组件和 prop,事件名不会被用作一个 JavaScript 变量名或 property 名,所以就没有理由使用 camelCase 或 PascalCase 了。并且 v-on
事件监听器在 DOM 模板中会被自动转换为全小写 (因为 HTML 是大小写不敏感的),所以 v-on:myEvent
将会变成 v-on:myevent
——导致 myEvent
不可能被监听到。
因此,我们推荐你始终使用 kebab-case 的事件名。
- 自定义组件的v-model
2.2.0+ 新增
一个组件上的 v-model
默认会利用名为 value
的 prop 和名为 input
的事件,但是像单选框、复选框等类型的输入控件可能会将 value
attribute 用于不同的目的。model
选项可以用来避免这样的冲突:
Vue.component('base-checkbox', {
model: {
prop: 'checked',
event: 'change'
},
props: {
checked: Boolean
},
template: `
<input
type="checkbox"
v-bind:checked="checked"
v-on:change="$emit('change', $event.target.checked)"
>
`
})
现在在这个组件上使用 v-model
的时候:
<base-checkbox v-model="lovingVue"></base-checkbox>
这里的 lovingVue
的值将会传入这个名为 checked
的 prop。同时当 <base-checkbox>
触发一个 change
事件并附带一个新的值的时候,这个 lovingVue
的 property 将会被更新。
注意你仍然需要在组件的 props
选项里声明 checked
这个 prop。
- 实例 demo
- 在vue的实例中,增加了
methods
对象并定义了一个名为removeTodoItems
的方法:
var vm = new Vue({
el: '#app',
data: {
todoTitle: 'Jerry学JavaWeb',
todoItems: ['Linux', 'Spring Boot', 'Vue'],
},
methods: {
//该方法可以被模板中自定义事件触发
removeItems: function (index) {
console.log("删除 " + this.todoItems[index] + " 成功");
// splice():方法 向/从 数组中 添加/删除 项目,然后返回被删除的项目,其中 index 为 添加/删除 项目的位置,1 表示删除的数量
this.todoItems.splice(index,1);//一次删除一个元素
}
}
});
- 修改 todo-items待办内容组件的代码,增加一个删除按钮,并且绑定事件:
Vue.component('todo-items', {
//这里的index,就是数组的下标,使用for循环遍历的时候,可以循环出来!
props: ['index', 'item'],
// “:” 是指令 “v-bind”的缩写,“@”是指令“v-on”的缩写;“.”是修饰符
//只能绑定当前组件的方法
template: '<li>{{index + 1}}. {{item}} <button @click="remove">删除</button></li>',
methods: {
remove: function (index) {
//这里的 remove 是自定义事件的名称,需要在 HTML 中使用 v-on:remove 的方式指派
this.$emit('remove', index);
}
}
});
- 修改 todo-items 待办内容组件的 HTML 代码,增加一个自定义事件,比如叫 remove,可以和组件的方法绑定,然后绑定到vue的方法中:
<!--增加了 v-on:remove="removeItems(index)" 自定义事件,该事件会调用 Vue 实例中定义的名为 removeItems 的方法-->
<todo-items slot="todo-items"
v-for="(item, index) in todoItems"
v-bind:item="item"
v-bind:index="index"
v-on:remove="removeItems(index)"></todo-items>
</todo-items>
- 完整代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!-- 组件:todo -->
<div id="app">
<todo>
<todo-title slot="todo-title"
v-bind:title="todoTitle">
</todo-title>
<!--增加了 v-on:remove="removeItems(index)" 自定义事件,该事件会调用 Vue 实例中定义的名为 removeItems 的方法-->
<todo-items slot="todo-items"
v-for="(item, index) in todoItems"
v-bind:item="item"
v-bind:index="index"
v-on:remove="removeItems(index)"></todo-items>
</todo-items>
</todo>
</div>
<!--1. 导入Vue.js-->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
Vue.component('todo', {
template: '<div>\
<slot name="todo-title"></slot>\
<ul>\
<slot name="todo-items"></slot>\
</ul>\
</div>'
});
Vue.component('todo-title', {
props: ['title'], //props传递参数不能使用驼峰命名法则,不然数据没法渲染到页面上
template: '<div>{{title}}</div>'
});
Vue.component('todo-items', {
//这里的index,就是数组的下标,使用for循环遍历的时候,可以循环出来!
props: ['index', 'item'],
// “:” 是指令 “v-bind”的缩写,“@”是指令“v-on”的缩写;“.”是修饰符
//只能绑定当前组件的方法
template: '<li>{{index + 1}}. {{item}} <button @click="remove">删除</button></li>',
methods: {
remove: function (index) {
//这里的 remove 是自定义事件的名称,需要在 HTML 中使用 v-on:remove 的方式指派
this.$emit('remove', index);
}
}
});
//再实例化
var vm = new Vue({
el: '#app',
data: {
todoTitle: 'Jerry学JavaWeb',
todoItems: ['Linux', 'Spring Boot', 'Vue'],
},
methods: {
//该方法可以被模板中自定义事件触发
removeItems: function (index) {
console.log("删除 " + this.todoItems[index] + " 成功");
// splice():方法 向/从 数组中 添加/删除 项目,然后返回被删除的项目,其中 index 为 添加/删除 项目的位置,1 表示删除的数量
this.todoItems.splice(index, 1);//一次删除一个元素
}
}
});
</script>
</body>
</html>