六、异步请求
6.1 跨域问题
- 跨域
安装 Axios:npm install axios
App running at - Local: http://localhost:8080/
- 编写异步请求
<template>
<div>
<button @click="getStudents">获取学生信息</button>
</div>
</template>
<script>
import axios from 'axios'
export default {
name: 'App',
methods: {
getStudents() {
axios.get("http://localhost:5000/students").then(
response => {
console.log('请求成功了 ', response.data)
},
error => {
console.log('请求失败了 ', error.message)
}
)
}
}
}
</script>
- 准备服务端 API
GET http://127.0.0.1:5000/students
[
{
"id": "001",
"name": "tom",
"age": 18
},
{
"id": "002",
"name": "jerry",
"age": 19
},
{
"id": "003",
"name": "tony",
"age": 120
}
]
GET http://127.0.0.1:5001/cars
[
{
"id": "001",
"name": "奔驰",
"price": 199
},
{
"id": "002",
"name": "马自达",
"price": 109
},
{
"id": "003",
"name": "捷达",
"price": 120
}
]
- 由于端口不一致出现跨域问题
- 跨域问题解决
Vue 解决跨域参考:https://cli.vuejs.org/zh/config/#devserver-proxy
- 配置 vue.config.js
module.exports = {
// 可以在 '/vue.config.js' 下覆盖 Vue 的默认配置,如配置则必须指定 'entry' 入口文件
pages: {
index: {
entry: 'src/main.js'
}
},
lintOnSave: false, // 关闭语法检查
// 开启代理服务器
devServer: {
proxy: {
'/school': {
target: 'http://localhost:5000',
pathRewrite: {'^/school': ''},
ws: true, // 用于支持 websocket
changeOrigin: true // 用于控制请求头中的 host 值(false = localhost:8080, true = localhost:5000)
},
'/store': {
target: 'http://localhost:5001',
pathRewrite: {'^/store': ''},
ws: true,
changeOrigin: true
}
}
}
}
- 修改请求路径
<template>
<div>
<button @click="getStudents">获取学生信息</button>
<button @click="getCars">获取汽车信息</button>
</div>
</template>
<script>
import axios from 'axios'
export default {
name: 'App',
methods: {
getStudents() {
axios.get("http://localhost:8080/school/students").then(
response => {
console.log('请求成功了 ', response.data)
},
error => {
console.log('请求失败了 ', error.message)
}
)
},
getCars() {
axios.get("http://localhost:8080/store/cars").then(
response => {
console.log('请求成功了 ', response.data)
},
error => {
console.log('请求失败了 ', error.message)
}
)
}
}
}
</script>
- 不再出现跨域问题
6.2 Github 用户搜索案例
- 静态组件拆分
- App.vue
<template>
<div class="container">
<Search/>
<List/>
</div>
</template>
<script>
import Search from './components/Search.vue'
import List from './components/List.vue'
export default {
name: 'App',
components: {Search, List}
}
</script>
- Search.vue
<template>
<div>
<section class="jumbotron">
<h3 class="jumbotron-heading">Search Github Users</h3>
<div>
<input type="text" placeholder="enter the name you search"/> <button>Search</button>
</div>
</section>
</div>
</template>
<script>
export default {
name: 'Search'
}
</script>
- List.vue
<template>
<div>
<div class="row">
<div class="card">
<a href="https://github.com/xxxxxx" target="_blank">
<img src="https://cn.vuejs.org/images/logo.svg" style='width: 100px'/>
</a>
<p class="card-text">xxxxxx</p>
</div>
<div class="card">
<a href="https://github.com/xxxxxx" target="_blank">
<img src="https://cn.vuejs.org/images/logo.svg" style='width: 100px'/>
</a>
<p class="card-text">xxxxxx</p>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'List'
}
</script>
<style scoped>
.album {
min-height: 50rem; /* Can be removed; just added for demo purposes */
padding-top: 3rem;
padding-bottom: 3rem;
background-color: #f7f7f7;
}
.card {
float: left;
width: 33.333%;
padding: .75rem;
margin-bottom: 2rem;
border: 1px solid #efefef;
text-align: center;
}
.card > img {
margin-bottom: .75rem;
border-radius: 100px;
}
.card-text {
font-size: 85%;
}
</style>
- 外部样式引入
- 静态效果
- 实现动态请求
- Search 接收关键词发送请求并通过消息总线回调传值
<template>
<div>
<section class="jumbotron">
<h3 class="jumbotron-heading">Search Github Users</h3>
<div>
<input type="text" placeholder="enter the name you search" v-model="keyWord"/>
<button @click="searchUsers">Search</button>
</div>
</section>
</div>
</template>
<script>
import axios from 'axios'
export default {
name: 'Search',
data() {
return {
keyWord: ''
}
},
methods: {
searchUsers() {
axios.get(`https://api.github.com/search/users?q=${this.keyWord}`).then(
response => {
this.$bus.$emit('getUsers', response.data.items)
},
error => {
console.log('请求失败了', error.message)
}
)
}
}
}
</script>
- List 动态展示数据
<template>
<div class="row">
<div class="card" v-for="user in users" :key="user.login">
<a :href="user.html_url" target="_blank">
<img :src="user.avatar_url" style='width: 100px'/>
</a>
<p class="card-text">{{user.login}}</p>
</div>
</div>
</template>
<script>
export default {
name: 'List',
data() {
return {
users: []
}
},
mounted() {
this.$bus.$on('getUsers', (users) => {
this.users = users
})
}
}
</script>
- 细节调优(欢迎信息、请求中、请求失败信息)
- List 改造
<template>
<div class="row">
<!-- 展示用户列表 -->
<div v-show="listInfo.users.length" class="card" v-for="user in listInfo.users" :key="user.login">
<a :href="user.html_url" target="_blank">
<img :src="user.avatar_url" style='width: 100px'/>
</a>
<p class="card-text">{{user.login}}</p>
</div>
<!-- 展示欢迎词 -->
<h1 v-show="listInfo.isFirst">欢迎使用!</h1>
<!-- 展示加载中 -->
<h1 v-show="listInfo.isLoading">加载中...</h1>
<!-- 展示错误信息 -->
<h1 v-show="listInfo.errMsg">{{listInfo.errMsg}}</h1>
</div>
</template>
<script>
export default {
name: 'List',
data() {
return {
// 将 List 中的数据作为一个整体
listInfo: {
isFirst: true,
isLoading: false,
errMsg: '',
users: []
}
}
},
mounted() {
this.$bus.$on('updateListData', (dataObj) => {
// 将 dataObj 里有的数据更新到 listInfo
this.listInfo = {...this.listInfo, ...dataObj}
})
}
}
</script>
- Search 改造
<template>
<div>
<section class="jumbotron">
<h3 class="jumbotron-heading">Search Github Users</h3>
<div>
<input type="text" placeholder="enter the name you search" v-model="keyWord"/>
<button @click="searchUsers">Search</button>
</div>
</section>
</div>
</template>
<script>
import axios from 'axios'
export default {
name: 'Search',
data() {
return {
keyWord: ''
}
},
methods: {
searchUsers() {
// 请求前更新 List 的数据
this.$bus.$emit('updateListData', {isFirst: false, isLoading: true, errMsg: '', users: []})
axios.get(`https://api.github.com/search/users?q=${this.keyWord}`).then(
response => {
// 请求成功后更新 List 的数据
this.$bus.$emit('updateListData', {isLoading: false, errMsg: '', users: response.data.items})
},
error => {
// 请求失败后更新 List 的数据
this.$bus.$emit('updateListData', {isLoading: false, errMsg: error.message, users: []})
}
)
}
}
}
</script>
6.3 插槽 slot
作用:让父组件可以向子组件指定位置插入html结构,也是一种组件间通信的方式,适用于 父组件 ===> 子组件。
6.3.1 不使用插槽
- App.vue
<template>
<div class="container">
<Category title="美食" :listData="foods"/>
<Category title="游戏" :listData="games"/>
<Category title="电影" :listData="films"/>
</div>
</template>
<script>
import Category from './components/Category.vue'
export default {
name: 'App',
data() {
return {
foods: ['火锅', '烧烤', '小龙虾', '牛排'],
games: ['红色警戒', '穿越火线', '劲舞团', '超级玛丽'],
films: ['《教父》', '《拆弹专家》', '《你好,李焕英》', '《尚硅谷》']
}
},
components: {Category}
}
</script>
<style>
.container {
display: flex;
justify-content: space-around;
}
</style>
- Category.vue
<template>
<div class="category">
<h3>{{title}}分类</h3>
<ul>
<li v-for="(item, index) in listData" :key="index">{{item}}</li>
</ul>
</div>
</template>
<script>
export default {
name: 'Category',
props: ['title', 'listData']
}
</script>
<style>
.category {
background-color: skyblue;
width: 200px;
height: 300px;
}
h3 {
text-align: center;
background-color: orange;
}
</style>
6.3.2 默认插槽
不同的数据页展示不同类型的数据。
<template>
<div class="container">
<Category title="美食">
<img src="https://s3.ax1x.com/2021/01/16/srJlq0.jpg" alt="">
</Category>
<Category title="游戏">
<ul>
<li v-for="(game,index) in games" :key="index">{{game}}</li>
</ul>
</Category>
<Category title="电影">
<video controls src="http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4"></video>
</Category>
</div>
</template>
<template>
<div class="category">
<h3>{{title}}分类</h3>
<!-- 定义一个插槽(挖个坑,等着组件的使用者进行填充) -->
<slot>我是一些默认值,当使用者没有传递具体结构时,我会出现</slot>
</div>
</template>
6.3.3 具名插槽
同个组件需要传递多个插槽时。
<template>
<div class="container">
<Category title="美食" >
<img slot="center" src="https://s3.ax1x.com/2021/01/16/srJlq0.jpg" alt="">
<a slot="footer" href="http://www.atguigu.com">更多美食</a>
</Category>
<Category title="游戏" >
<ul slot="center">
<li v-for="(g,index) in games" :key="index">{{g}}</li>
</ul>
<div class="foot" slot="footer">
<a href="http://www.atguigu.com">单机游戏</a>
<a href="http://www.atguigu.com">网络游戏</a>
</div>
</Category>
<Category title="电影">
<video slot="center" controls src="http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4"></video>
<template v-slot:footer>
<div class="foot">
<a href="http://www.atguigu.com">经典</a>
<a href="http://www.atguigu.com">热门</a>
<a href="http://www.atguigu.com">推荐</a>
</div>
<h4>欢迎前来观影</h4>
</template>
</Category>
</div>
</template>
<template>
<div class="category">
<h3>{{title}}分类</h3>
<!-- 定义一个插槽(挖个坑,等着组件的使用者进行填充) -->
<slot name="center">我是一些默认值,当使用者没有传递具体结构时,我会出现1</slot>
<slot name="footer">我是一些默认值,当使用者没有传递具体结构时,我会出现2</slot>
</div>
</template>
6.3.4 作用域插槽
理解:数据在组件的自身,但根据数据生成的结构需要组件的使用者来决定。(games数据在Category组件中,但使用数据所遍历出来的结构由App组件决定)
<template>
<div class="container">
<Category title="游戏">
<template scope="atguigu">
<ul>
<li v-for="(g,index) in atguigu.games" :key="index">{{g}}</li>
</ul>
</template>
</Category>
<Category title="游戏">
<template scope="{games}">
<ol>
<li style="color:red" v-for="(g,index) in games" :key="index">{{g}}</li>
</ol>
</template>
</Category>
<Category title="游戏">
<template slot-scope="{games}">
<h4 v-for="(g,index) in games" :key="index">{{g}}</h4>
</template>
</Category>
</div>
</template>