目录
1.概述
前端页面中的数据都来自于后台,后台和前端是互不影响的两个程序,前端应该如何从后端获取数据呢?这就涉及到两个程序的交互,就需要用到Ajax技术。
Ajax:全称Asynchronous JavaScript And XML,异步的JavaScript和XML。
作用:
- 与服务器进行数据交换:通过Ajax可以给服务器发送请求,并获取服务器响应的数据。
- 异步交互:可以在不重新加载整个页面的情况下,与服务器交换数据并更新部分网页的技术,如:搜索联想、用户名是否可用的校验等。
2.同步异步
Ajax的局部刷新功能是因为Ajax请求是异步的,与之对应的有同步请求。
同步请求发送过程:
浏览器页面在发送请求给服务器,在服务器处理请求的过程中,浏览器页面不能做其他的操作。只能等到服务器响应结束之后,浏览器页面才能做其他的操作。
异步请求发送过程:
浏览器页面发送请求给服务器,在服务器处理请求的过程中,浏览器页面还可以做其他的操作。
3.原生Ajax
对于Ajax技术有了充分的认知了,我们接下来通过代码来演示Ajax的效果。此处我们先采用原生的Ajax代码来演示。因为Ajax请求是基于客户端发送请求,服务器响应数据的技术。所以为了完成快速入门案例,我们需要提供服服务器端和编写客户端。
1.服务器端:
可以理解为后端服务器(通过Java写的),这里直接提供服务器端的请求地址,我们前端直接通过Ajax请求访问该地址即可。后端服务器地址:https://mock.apifox.cn/m1/3083103-0-default/emps/list
通过浏览器访问之后的结果如下:
2.客户端
客户端的Ajax请求代码有4步:
- 创建XMLHttpRequest对象:用于和服务器交换数据
- 向服务器发送请求
- 获取服务器响应数据
具体代码如下:(资料中已经提供)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>原生Ajax</title>
</head>
<body>
<input id="btn1" type="button" value="获取数据">
<div id="div1"></div>
<script>
document.querySelector('#btn1').addEventListener('click', ()=> {
//1. 创建XMLHttpRequest
var xmlHttpRequest = new XMLHttpRequest();
//2. 发送异步请求
//设置请求方式和请求路径
xmlHttpRequest.open('GET', 'https://mock.apifox.cn/m1/3128855-0-default/emp/list');
xmlHttpRequest.send();//发送请求
//3. 获取服务响应数据
xmlHttpRequest.onreadystatechange = function(){
if(xmlHttpRequest.readyState == 4 && xmlHttpRequest.status == 200){
document.getElementById('div1').innerHTML = xmlHttpRequest.responseText;
}
}
})
</script>
</body>
</html>
4.Axios
4.1Axios的基本使用
Axios的使用主要分为两步:
- 引入Axios文件
- 使用Axios发送请求,并获取响应结果,官方提供的api很多,例举两种:
- 发送get请求
axios({
medthod:'GET',
url:'https://mock.apifox.cn/m1/3083103-0-default/emps/list'
}).then((result)=>{ //成功回调函数
consle.log(result.data);
}).catch((err)=>{ //失败回调函数
alter(err);
})
//也可以采取下面的方式
axios({
medthod:"get",
url:"https://mock.apifox.cn/m1/3083103-0-default/emps/list"
}).then(function (resp){
alter(resp.data);
})
引用Axios文件时,失败回调函数可以省略。
- 发送post请求
axios({
method:'POST',
url:'https://mock.apifox.cn/m1/3083103-0-default/emps/update',
data:'id=1' //封装请求参数
}).then((result) => {
console.log(result.data);
}).catch((err) => {
alter(err);
})
axios()是用来发送异步请求的,小括号中使用 js的JSON对象传递请求相关的参数:
-
method属性:用来设置请求方式的。取值为 get 或者 post。
-
url属性:用来书写请求的资源路径。如果是 get 请求,需要将请求参数拼接到路径的后面,格式为: url?参数名=参数值&参数名2=参数值2。
-
data属性:作为请求体被发送的数据。也就是说如果是 post 请求的话,数据需要作为 data 属性的值。
then() 需要传递一个匿名函数。我们将 then()中传递的匿名函数称为 回调函数,意思是该匿名函数在发送请求时不会被调用,而是在成功响应后调用的函数。而该回调函数中的 result 参数是对响应的数据进行封装的对象,通过 result.data 可以获取到响应的数据。
完整代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Ajax-Axios</title>
</head>
<body>
<input type="button" value="获取数据GET" id="btnGet">
<input type="button" value="删除数据POST" id="btnPost">
<!-- 1.引入js文件 -->
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<!-- <script src="../js/axios.js"></script> -->
<script>
document.querySelector("#btnGet").addEventListener('click',
function () {
// 调用axios发送get请求 https://mock.apifox.cn/m1/3083103-0-default/emps/list
axios({
method: 'GET',
url: 'https://mock.apifox.cn/m1/3083103-0-default/emps/list'
}).then((result) => { //成功回调函数
console.log(result.data);
}).catch((err) => { //失败回调函数
alert(err);
})
}
)
document.querySelector("#btnPost").addEventListener('click',
function () {
// 调用axios发送post请求 https://mock.apifox.cn/m1/3083103-0-default/emps/update
axios({
method: 'POST',
url: 'https://mock.apifox.cn/m1/3083103-0-default/emps/update',
data: 'id=1' //封装请求参数
}).then((result) => { //成功回调函数
console.log(result.data);
}).catch((err) => { //失败回调函数
alert(err);
})
}
)
</script>
</body>
</html>
注意一定要在script标签中先引入js文件 。
打开浏览器之后,按F12抓包,分别点击两个按钮,查看控制台的效果:
4.2请求方法的别名
Axios针对不同的请求,提供了别名方式的api,具体格式如下:
axios.请求方式(url [, data [, config]])
[ ]中的内容代表可以省略。
方法 | 描述 |
---|---|
axios.get(url [, config]) | 发送get请求 |
axios.delete(url [, config]) | 发送delete请求 |
axios.post(url [, data[, config]]) | 发送post请求 |
axios.put(url [, data[, config]]) | 发送put请求 |
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Ajax-Axios</title>
</head>
<body>
<input type="button" value="获取数据GET" id="btnGet">
<input type="button" value="删除数据POST" id="btnPost">
<!-- 1.引入js文件 -->
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<!-- <script src="../js/axios.js"></script> -->
<script>
document.querySelector("#btnGet").addEventListener('click',
function () {
// 调用axios发送get请求 https://mock.apifox.cn/m1/3083103-0-default/emps/list
// axios别名方式
axios.get('https://mock.apifox.cn/m1/3083103-0-default/emps/list').then((result) => { //成功回调函数
// console.log(result.data);
console.log(result.data.data);
})
}
)
document.querySelector("#btnPost").addEventListener('click',
function () {
// 调用axios发送post请求 https://mock.apifox.cn/m1/3083103-0-default/emps/update
// axios别名方式
axios.post('https://mock.apifox.cn/m1/3083103-0-default/emps/update', 'id=1').then((result) => { //成功回调函数
console.log(result.data);
});
}
)
</script>
</body>
</html>
5.案例-Ajax异步获取数据
用Ajax异步获取数据完成员工列表数据加载。
需求:当点击查询按钮时,发送Ajax异步请求,根据传递的查询条件,动态获取数据,渲染列表页面。
服务器端地址: https://web-server.itheima.net/emps/list
具体代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vue3-案例1</title>
<style>
table,
th,
td {
border: 1px solid #000;
border-collapse: collapse;
line-height: 50px;
text-align: center;
}
#center,
table {
width: 60%;
margin: auto;
}
#center {
margin-bottom: 20px;
}
img {
width: 50px;
}
input,
select {
width: 17%;
padding: 10px;
margin-right: 30px;
border: 1px solid #ccc;
border-radius: 4px;
}
.btn {
background-color: #ccc;
}
</style>
</head>
<body>
<div id="app">
<div id="center">
姓名: <input type="text" name="name" v-model="name">
性别:
<select name="gender" v-model="gender">
<option value="1">男</option>
<option value="2">女</option>
</select>
职位:
<select name="job" v-model="job">
<option value="1">讲师</option>
<option value="2">班主任</option>
<option value="3">其他</option>
</select>
<input class="btn" type="button" value="查询" @click="search">
</div>
<table>
<tr>
<th>序号</th>
<th>姓名</th>
<th>头像</th>
<th>性别</th>
<th>职位</th>
<th>入职时间</th>
<th>更新时间</th>
</tr>
<tr v-for="(user, index) in userList" :key="user.id">
<td>{{index + 1}}</td>
<td>{{user.name}}</td>
<td> <img :src="user.image"> </td>
<td>
<span v-if="user.gender == 1">男</span>
<span v-else-if="user.gender == 2">女</span>
<span v-else>妖</span>
</td>
<td>
<!-- v-show -->
<span v-show="user.job == 1">讲师</span>
<span v-show="user.job == 2">班主任</span>
<span v-show="user.job != 1 && user.job != 2">其他</span>
</td>
<td>{{user.entrydate}}</td>
<td>{{user.updatetime}}</td>
</tr>
</table>
</div>
<!-- 1.引入js文件 -->
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script type="module">
import { createApp } from 'https://unpkg.com/vue@3/dist/vue.esm-browser.js'
createApp({
data() {
return {
name: '',
gender: '',
job: '',
userList: []
}
},
methods: {
search() {
// 输出搜索框中的数据
console.log(`姓名: ${this.name}, 性别:${this.gender}, 职位:${this.job}`);
// 通过axios发送异步请求,动态获取数据 https://web-server.itheima.net/emps/list
axios.get(`https://web-server.itheima.net/emps/list?name=${this.name}&gender=${this.gender}&job=${this.job}`).then((result) => {
// console.log(result.data);
console.log(result.data.data);
this.userList = result.data.data;//把查询到的数据给userList(赋值操作)
})
}
},
// 生命周期-钩子函数
mounted() {
// 页面一旦加载完成,就进行数据查询
this.search();
},
}).mount("#app");
</script>
</body>
</html>
浏览器效果如下:
经过上面的测试之后,我们看到,当我们点击 "查询" 按钮时,确实可以发送Ajax异步请求,动态获取数据。但是,我们打开这个页面时,表格内容却是一片空白,只有点击了查询按钮才会展示出数据。 而在正常的系统中,应该是进入页面时,就会展示出表格中的数据的。
那如何在页面打开之后,就自动执行查询呢? 那此时,我们就需要用到Vue中生命周期的相关函数了。那接下来,我们就来学习Vue的声明周期。
6.生命周期
6.1介绍
vue的生命周期:指的是vue对象从创建到销毁的过程。
vue的生命周期包含8个阶段:每触发一个生命周期事件,会自动执行一个生命周期方法,这些生命周期方法也被称为钩子方法。其完整的生命周期如下图所示:
状态 | 阶段周期 |
---|---|
beforeCreate | 创建前 |
created | 创建后 |
beforeMount | 挂载前 |
mounted | 挂载完成 |
beforeUpdate | 更新前 |
updated | 更新后 |
beforeDestroy | 销毁前 |
destroyed | 销毁后 |
下图是 Vue 官网提供的从创建 Vue 到效果 Vue 对象的整个过程及各个阶段对应的钩子函数:
其中我们需要重点关注的是mounted,其他的我们了解即可。
mounted:挂载完成,Vue初始化成功,HTML页面渲染成功。以后我们一般用于页面初始化自动的ajax请求后台数据
6.2 案例-员工列表查询
那我们要想在页面加载完毕,就查询出员工列表,就可以在mounted钩子函数中,发送异步请求查询员工数据了。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vue3-案例1</title>
<style>
table,
th,
td {
border: 1px solid #000;
border-collapse: collapse;
line-height: 50px;
text-align: center;
}
#center,
table {
width: 60%;
margin: auto;
}
#center {
margin-bottom: 20px;
}
img {
width: 50px;
}
input,
select {
width: 17%;
padding: 10px;
margin-right: 30px;
border: 1px solid #ccc;
border-radius: 4px;
}
.btn {
background-color: #ccc;
}
</style>
</head>
<body>
<div id="app">
<div id="center">
姓名: <input type="text" name="name" v-model="name">
性别:
<select name="gender" v-model="gender">
<option value="1">男</option>
<option value="2">女</option>
</select>
职位:
<select name="job" v-model="job">
<option value="1">讲师</option>
<option value="2">班主任</option>
<option value="3">其他</option>
</select>
<input class="btn" type="button" value="查询" @click="search">
</div>
<table>
<tr>
<th>序号</th>
<th>姓名</th>
<th>头像</th>
<th>性别</th>
<th>职位</th>
<th>入职时间</th>
<th>更新时间</th>
</tr>
<tr v-for="(user, index) in userList" :key="user.id">
<td>{{index + 1}}</td>
<td>{{user.name}}</td>
<td> <img :src="user.image"> </td>
<td>
<span v-if="user.gender == 1">男</span>
<span v-else-if="user.gender == 2">女</span>
<span v-else>妖</span>
</td>
<td>
<!-- v-show -->
<span v-show="user.job == 1">讲师</span>
<span v-show="user.job == 2">班主任</span>
<span v-show="user.job != 1 && user.job != 2">其他</span>
</td>
<td>{{user.entrydate}}</td>
<td>{{user.updatetime}}</td>
</tr>
</table>
</div>
<!-- 1.引入js文件 -->
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script type="module">
import { createApp } from 'https://unpkg.com/vue@3/dist/vue.esm-browser.js'
createApp({
data() {
return {
name: '',
gender: '',
job: '',
userList: []
}
},
methods: {
search() {
// 输出搜索框中的数据
console.log(`姓名: ${this.name}, 性别:${this.gender}, 职位:${this.job}`);
// 通过axios发送异步请求,动态获取数据 https://web-server.itheima.net/emps/list
axios.get(`https://web-server.itheima.net/emps/list?name=${this.name}&gender=${this.gender}&job=${this.job}`).then((result) => {
// console.log(result.data);
console.log(result.data.data);
this.userList = result.data.data;//把查询到的数据给userList(赋值操作)
})
}
},
// 生命周期-钩子函数
mounted() {
// 页面一旦加载完成,就进行数据查询
this.search();
},
}).mount("#app");
</script>
</body>
</html>
6.3 案例-省市区
需求:页面加载完毕后,默认加载并展示出第一个省、第一个市、第一个区。
-
获取省份:https://web-server.itheima.net/province
-
获取市:https://web-server.itheima.net/city?pid=xxx
-
获取区:https://web-server.itheima.net/area?cid=xxx
分析:
-
要想在页面加载完成后,默认加载出第一个省、第一个省对应的第一个市、第一个市对应的第一个区。 就应该用到vue的钩子函数 mounted。
-
那要想加载出省市区,就需要发送3次异步请求,第一次获取省。
-
那什么时候发送第二次异步请求,获取市呢 ? 应该是在第一次异步请求完成之后,我们获取到第一个省份,然后再根据省份的ID查询市。
-
那什么时候发送第三次异步请求,获取区呢 ?应该是在第二次异步请求完成之后,我们获取到第一个市,然后再根据市的ID查询区。从获取到区 。
思考:如何在第一次异步请求成功后,再发送第二次异步请求获取数据呢 ?
那么此时,我们就可以在第一个请求的成功回调函数中,来发送第二次请求 。这样就可以保证,是第一个请求成功,才发送的第二次请求。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vue3-案例1</title>
<style>
#center {
margin-bottom: 20px;
}
input,
select {
width: 17%;
padding: 10px;
margin-right: 30px;
border: 1px solid #ccc;
border-radius: 4px;
}
</style>
</head>
<body>
<div id="app">
<div id="center">
省:
<select v-model="province">
<option v-for="p in provinces" :key="p.id" :value="p.id">{{p.name}}</option>
</select>
市:
<select v-model="city">
<option v-for="c in cities" :key="c.id" :value="c.id">{{c.name}}</option>
</select>
区:
<select v-model="area">
<option v-for="a in areas" :key="a.id" :value="a.id">{{a.name}}</option>
</select>
</div>
</div>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script type="module">
import { createApp } from "https://unpkg.com/vue@3/dist/vue.esm-browser.js";
createApp({
data() {
return {
//不能写死数据,要为空才能不断变化
province: '',
city: '',
area: '',
//存储数据的数组
provinces: [],
cities: [],
areas: [],
};
},
methods: {
search() {
//需求: 动态加载出省\市\区
//1.获取所有的省 https://web-server.itheima.net/province
axios.get('https://web-server.itheima.net/province').then((result) => {
// console.log(result.data);
this.provinces = result.data.data;
//为下拉框提供默认选中值
this.province = this.provinces[0].id;
// console.log("========="+this.province);
//2.获取第一个省对应的市 https://web-server.itheima.net/city?pid=1
axios.get(`https://web-server.itheima.net/city?pid=${this.province}`).then((result) => {
this.cities = result.data.data;
this.city = this.cities[0].id;
//3.获取第一个市对应的区 https://web-server.itheima.net/area?cid=xxx
axios.get(` https://web-server.itheima.net/area?cid=${this.city}`).then((result) => {
this.areas = result.data.data;
this.area = this.areas[0].id;
})
})
})
}
},
//钩子函数
mounted() {
this.search();
},
}).mount("#app");
</script>
</body>
</html>
我在这个案例中思考了以下几个问题:
1.<option v-for="p in provinces" :key="p.id" :value="p.id">{{p.name}}</option>中p in provinces是什么意思?
这是v-for的循环指令的一个简写形式 语法:v-for = "(item,index) in items"
index 为索引/下标,从0开始 ;可以省略,省略index语法: v-for = "item in items"
2. :value="p.id"
的作用?
:value
是 Vue 的动态属性绑定语法,等价于 v-bind:value
。它将 <option>
的 value
属性绑定到当前省份的 id
值。具体解释:
-
p.id
:当前省份对象的唯一标识(如1
,2
,3
)。 -
作用:当用户选择某个省份时,
v-model="province"
会将选中项的value
(即p.id
)赋值给 Vue 实例中的province
变量。
3.为什么用 p.id
而不是 p.name
?
-
数据关联:
id
是后端数据库中的唯一标识,用于后续 API 请求(如获取该省份下的城市时,需要传pid=省份id
)。 -
解耦显示与数据:显示给用户的是
name
(如「广东省」),但程序内部使用id
进行数据交互,避免因名称变更导致的问题。
4.province: '' ", city: '' ", area: '' "这段代码的作用是什么?
province
, city
, area
的作用(表单绑定值):
(1)双向绑定机制:
-
视图 → 数据:用户选择下拉框中的选项时,Vue 自动将选中项的
value
值赋给对应的变量(如province
)。 -
数据 → 视图:当变量的值被代码修改时(如初始化赋值),Vue 自动更新下拉框的选中状态。
(2) 存储选中值
-
province
:存储用户当前选择的省份 ID(如"1"
代表北京市)。 -
city
:存储用户当前选择的城市 ID(如"101"
代表北京市下的朝阳区)。 -
area
:存储用户当前选择的区 ID(如"1001"
代表朝阳区下的某个街道)。
(3)响应式更新视图
-
初始加载时,
province
被赋值为第一个省份的 ID,省份下拉框会自动选中该省份。 -
当用户切换省份时,
province
值变化 → 触发城市数据加载 →cities
更新 → 城市下拉框重新渲染。
(4)实现级联选择
-
每个级别的选择(省→市→区)都依赖前一级的 ID 作为参数。
-
例如,获取城市数据的 API 需要
pid
(省份 ID)参数,这个参数就来自province
变量。
但是我们会发现省和市的回调函数都是嵌套在第一个和第二个回调函数里面的,那么如果有很多个呢,这就陷入了一个叫做回调地狱的问题,接下来我们要学习利用一些关键字来解决这个问题。
6.4 async/await
可以通过async、await来解决回调函数地狱问题。async就是来声明一个异步方法,await是用来等待异步任务执行。
await关键字只在async函数内有效,await关键字取代了原来的then成功回调函数,且await会等待获取到请求成功的结果值。
我们会看到,通过async、await修饰的函数,简化了原来 then 成功回调函数的编写,使我们的代码可读性更强了,也更加便于项目的维护。