Vue笔记04-Vue中的Ajax

解决开发环境中Ajax跨域问题

在使用jQuery的时候,前后端交互通常使用$.ajax,但是会存在跨域问题。在jQuery中,有大量操作DOM的方法,但是在Vue中,我们不会主动操作DOM,所以配合Vue使用的时候,大多使用axios,使用npm i axios进行安装。
首先使用命令node server1.jsnode server2.js启动两个本地服务。
在axios中,使用代理的方式解决了跨域问题。通过Vue-CLI的devServer.proxy配置可以看到两种配置方式。需要手动创建vue.config.js配置文件,在这里添加代理服务器的配置。

module.exports = {
    pages: {
        index: {
            // 入口
            entry: 'src/main.js',
        }
    },
    // 关闭语法检查
    lintOnSave:false,
    // 开启代理服务器
    devServer: {
        proxy: 'http://localhost:5000'
    }
}

在App.vue中使用axios请求数据。

<template>
	<div>
		<button @click="getData">获取数据button>
	div>
template>
<script>
	import axios from 'axios'
	export default {
		name:'App',
		methods:{
			getData(){
				// 下一行会提示跨域问题,跨域的时候,请求是发送到服务器了,服务器也返回了,但是被浏览器拦截了
				// axios.get('http://localhost:5000/students').then(
				axios.get('http://localhost:8080/students').then(
					response => {
						console.log('请求成功', response.data);
					},
					error => {
						console.log('请求失败', error.message);
					}
				);
			}
		}
	}
script>

整个流程是这样的:点击按钮后,通过axios向本机的代理服务器发送/students请求,代理服务器接收到/students请求后,先检查自身public文件夹下,是否有students,如果有,直接返回,如果没有,向proxy配置的地址转发/students请求获取数据,在localhost:5000上有一个/students的服务,可以拿到students的数据。
上面这种配置方式有局限性:不能配置多个代理,不能控制具体请求是否走代理。
下面介绍第二种代理配置方式。

module.exports = {
    pages: {
        index: {
            // 入口
            entry: 'src/main.js',
        }
    },
    // 关闭语法检查
    lintOnSave:false,
    // 开启代理服务器
    // devServer: {
    //     proxy: 'http://localhost:5000'
    // }
    devServer: {
        proxy: {
            '/proxy1': {
                target: 'http://localhost:5000',
                pathRewrite:{'^/proxy1': ''},// 正则替换,去掉代理前缀
                ws: true,// 支持WebSocket,默认为true,可忽略
                changeOrigin: true// 控制请求头host的值,默认为true,可忽略
            },
            'proxy2': {
                target: 'http://localhost:5001',
                pathRewrite:{'^/proxy2': ''}// 正则替换,去掉代理前缀
            }
        }
    }
}
<template>
	<div>
		<button @click="getData1">获取学生数据button>
		<button @click="getData2">获取汽车数据button>
	div>
template>
<script>
	import axios from 'axios'
	export default {
		name:'App',
		methods:{
			getData1(){
				axios.get('http://localhost:8080/proxy1/students').then(
					response => {
						console.log('请求成功', response.data);
					},
					error => {
						console.log('请求失败', error.message);
					}
				);
			},
			getData2(){
				axios.get('http://localhost:8080/proxy2/cars').then(
					response => {
						console.log('请求成功', response.data);
					},
					error => {
						console.log('请求失败', error.message);
					}
				);
			}
		}
	}
script>

在本地的5000和5001端口上分别有一个处理/students和一个处理/cars的服务。在App.vue中,使用axios请求的时候,如果需要走代理,那么请求路径的最前面要带上代理前缀,如果不走代理,就不需要带了,控制更加灵活了,而且支持配置多个代理。
关于changeOrigin,如果是true,服务器收到请求头的host是localhost:5000,如果是false,服务器收到请求头的host是localhost:8080。

GitHub用户搜索案例

这个案例,把页面分成两个组件:搜索组件和列表组件。通过变量控制请求中不同时刻显示的内容不同。
当首次进入页面的时候,显示“欢迎词”,点击搜索发送请求后,显示“加载中”,请求收到后,显示具体信息,如果请求报错了,显示错误信息。这就要在搜索组件搜索方法中控制遍历的值,来动态切换列表组件的展示状态,因此这里用到了组件间传值,这里使用全局事件总线的写法。
main.js

// 引入Vue
import Vue from 'vue'
// 引入App
import App from './App.vue'
// 关闭Vue的生产提示
Vue.config.productionTip = false;
// 创建vm
new Vue({
	el:'#app',
	render: h => h(App),
	beforeCreate() {
		// 引入全局事件总线
		Vue.prototype.$bus = this;
	},
});

App.vue

<template>
	<div class="container">
		<Search/>
		<List/>
	div>
template>
<script>
	import Search from './components/Search'
	import List from './components/List'
	export default {
		name:'App',
		components:{Search,List}
	}
script>

Search.vue

<template>
	<section class="jumbotron">
		<h3 class="jumbotron-heading">Search Github Usersh3>
		<div>
			<input type="text" placeholder="enter the name you search" v-model="keyWord"/> 
			<button @click="searchUsers">Searchbutton>
		div>
	section>
template>
<script>
	import axios from 'axios'
	export default {
		name:'Search',
		data() {
			return {
				keyWord:''
			}
		},
		methods: {
			searchUsers(){
				// 请求前更新List的数据
				this.$bus.$emit('updateListData',{isLoading:true,errMsg:'',users:[],isFirst:false});
				// github的这个接口携带了特殊的响应头,通过CORS解决了跨域
				// 这里是ES6的模板字符串写法
				axios.get(`https://api.github.com/search/users?q=${this.keyWord}`).then(
					response => {
						console.log('请求成功了');
						// 请求成功后更新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>

List.vue

<template>
	<div class="row">
		
		<div v-show="info.users.length" class="card" v-for="user in info.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="info.isFirst">欢迎使用!h1>
		
		<h1 v-show="info.isLoading">加载中....h1>
		
		<h1 v-show="info.errMsg">{{info.errMsg}}h1>
	div>
template>
<script>
	export default {
		name:'List',
		data() {
			return {
				info:{
					isFirst:true,
					isLoading:false,
					errMsg:'',
					users:[]
				}
			}
		},
		mounted() {
			this.$bus.$on('updateListData',(dataObj)=>{
				// 这里的语法是ES6的解构赋值
				this.info = {...this.info,...dataObj};
			});
		}
	}
script>
<style scoped>
	.album {
		min-height: 50rem;
		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>

Vue中两个Ajax库

axios

vue-resource

使用npm i vue-resource安装,使用vue-resource后,会在组件或Vue实例上添加一个$http的属性。

// 引入Vue
import Vue from 'vue'
// 引入App
import App from './App.vue'
// 引入vue-resource插件
import vueResource from 'vue-resource'
// 关闭Vue的生产提示
Vue.config.productionTip = false;
// 使用插件
Vue.use(vueResource);
//创建vm
new Vue({
	el:'#app',
	render: h => h(App),
	beforeCreate() {
		Vue.prototype.$bus = this
	},
})

之前axios.get()的写法换成this.$http.get()写法,目前不怎么维护了,还是使用axios吧。

slot插槽

父组件向子组件传递带数据的标签。子组件中的结构不确定时,需要使用slot技术,在父组件中定义好结构,传给子组件,子组件通过插槽接收父组件传过来的标签和数据,并放在插槽的位置上。插槽内容是由父组件编译后传给子组件的。

默认插槽

App.vue

<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="(g,index) in games" :key="index">{{g}}li>
			ul>
		Category>
		<Category title="电影">
			<video controls src="http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4">video>
		Category>
	div>
template>
<script>
	import Category from './components/Category'
	export default {
		name:'App',
		components:{Category},
		data() {
			return {
				foods:['火锅','烧烤','小龙虾','牛排'],
				games:['红色警戒','穿越火线','劲舞团','超级玛丽'],
				films:['《教父》','《拆弹专家》','《你好,李焕英》','《尚硅谷》']
			}
		}
	}
script>
<style scoped>
	.container{
		display: flex;
		justify-content: space-around;
	}
style>

Category.vue

<template>
	<div class="category">
		<h3>{{title}}分类h3>
		
		<slot>我是一些默认值,当使用者没有传递具体结构时,我会出现slot>
	div>
template>

<script>
	export default {
		name:'Category',
		props:['title']
	}
script>
<style scoped>
	.category{
		background-color: skyblue;
		width: 200px;
		height: 300px;
	}
	h3{
		text-align: center;
		background-color: orange;
	}
	video{
		width: 100%;
	}
	img{
		width: 100%;
	}
style>

具名插槽

当有多个插槽的时候,就需要给插槽指定名称了,不同的内容可以根据插槽的名称,指定放到哪个插槽内。
Catagory.vue中的模板部分,给slot添加了name。

<template>
	<div class="category">
		<h3>{{title}}分类h3>
		
		<slot name="center">我是一些默认值,当使用者没有传递具体结构时,我会出现1slot>
		<slot name="footer">我是一些默认值,当使用者没有传递具体结构时,我会出现2slot>
	div>
template>

App.vue中的模板部分,指明填充到哪个插槽中,使用