js发ajax请求的几种方式

- xhr(原生js自带): new XMLHttpRequest() xhr.open() xhr.send()

- jQuery: $.get() $.post

- axios(推荐)

- fetch(windows对象自带): 有致命缺陷...
  • 安装 axios

    num i axios
    
  • demo演示

    - 后端接口的地址: http://127.0.0.1:8888/demo/
    
    - 返回正常的数据是这样:
    	{
        "data": [
            {
                "id": 1,
                "name": "JimGreen",
                "age": 20
            },
            {
                "id": 2,
                "name": "KateGreen",
                "age": 18
            },
            {
                "id": 3,
                "name": "LiLei",
                "age": 20
            },
            {
                "id": 4,
                "name": "HanMeiMei",
                "age": 19
            }
        ]
    }
    
    
### Student.vue(一个按钮发送请求,控制台打印返回信息)
<template>
	<button type="button" @click="getStudentName">获取学生信息</button>
</template>

<script>
	import axios from 'axios'
	export default {
		name:'Student',
		methods:{
			getStudentName(){
				axios.get('http://127.0.0.1:8888/demo/').then(
					response => {console.log('请求成功',response.data)},
					error => {console.log('请求失败',error.message)}
				)
			}
		}
	}
</script>

<style>
</style>

- 结果: 由于浏览器的同源策略,跨域失败(服务器有收到请求,但是返回的数据被浏览器阻止了)

	- 前端运行的地址: http://localhost:8080/
	- 后端运行的地址: http://localhost:8000/demo/
	
	- 区别: 协议&&域名 都一样,但是端口不一样,所以不同源,被拒绝

	...been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

跨域的解决方案

- cors # 真正解决跨域问题(后端处理)

- jsonp: 利用 <script src>,只能发送get请求(前后端一起配合...)

- 代理服务器
  • 本次讲的就是使用'代理服务器'解决问题

  • 代理服务器

    - nginx
    
    - vue-cli: 架了一个和客户端 端口一样的服务器
    
  • vue-cli 代理服务器解决跨域问题

    ### vue.config.js(配置完,一定要重启脚手架服务器,否则不生效)
    const { defineConfig } = require('@vue/cli-service')
    module.exports = defineConfig({
      ......
      // 代理服务器
      devServer: {
    	  // 配置目标服务器地址
          proxy: 'http://localhost:8000'
        }
    })
    
    ### Student.vue
    <template>
    	<button type="button" @click="getStudentName">获取学生信息</button>
    </template>
    
    <script>
    	import axios from 'axios'
    	export default {
    		name:'Student',
    		methods:{
    			getStudentName(){
    				// 注意: 不再是向目标服务器发起请求
    				// 而是向本机的代理服务器发起请求+目标服务器的路径
    				axios.get('http://localhost:8080/demo/').then(
    					response => {console.log('请求成功',response.data)},
    					error => {console.log('请求失败',error.message)}
    				)
    			}
    		}
    	}
    </script>
    
    - 返回结果: 正常返回数据了
    
    	ata: Array(4)
        0: {id: 1, name: 'JimGreen', age: 20}
        1: {id: 2, name: 'KateGreen', age: 18}
        2: {id: 3, name: 'LiLei', age: 20}
        3: {id: 4, name: 'HanMeiMei', age: 19}
        length: 4
        
    - 注意事项: 若脚手架的代理服务器本身就有 demo路径的资源,那么请求会优先得到代理服务器的响应
      代理服务器不再进行转发
      
    - 这种配置方式的缺陷有两点
    
    	- 不能配置多个代理
    	- 代理服务器有的资源,就不再进行转发
    
    
  • 配置多个代理服务器演示

    - 后端接口的地址1: http://127.0.0.1:8888/demo/test1
    - 后端接口的地址2: http://127.0.0.1:8888/demo/test2
    
    - 地址1返回数据
    	{
        "data": [
            {
                "id": 1,
                "name": "JimGreen",
                "age": 20
            },
            {
                "id": 2,
                "name": "KateGreen",
                "age": 18
            },
            {
                "id": 3,
                "name": "LiLei",
                "age": 20
            },
            {
                "id": 4,
                "name": "HanMeiMei",
                "age": 19
            }
        ]
    }
    
    - 地址2返回数据
    
    {
        "data": [
            {
                "id": 1,
                "name": "安宁",
                "age": 20
            },
            {
                "id": 2,
                "name": "云帆",
                "age": 18
            },
            {
                "id": 3,
                "name": "东东",
                "age": 20
            },
            {
                "id": 4,
                "name": "林林",
                "age": 19
            }
        ]
    }
    
### vue.config.js
......
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
  ......
  // 代理服务器
  // devServer: {
	 //  // 配置目标服务器地址
  //     proxy: 'http://localhost:8888'
  //   }
  
  devServer: {
	  proxy: {
		'/test1': { // 加前缀
		  target: 'http://localhost:8888', // 目标服务器地址
		  pathRewrite:{'^/test1':''} // 正则匹配,去掉这个前缀
		  // ws: true, // 用于支持 websocket
		  // changeOrigin: true // 用于控制请求头中的host值(是否隐藏客户端地址)
		},
		'/test2': {
		  target: 'http://localhost:8888',
		  pathRewrite:{'^/test2':''}
		 ......
		},
	  }
    }
  
})

### Student.vue
<template>
	<div>
		<button type="button" @click="getStudentName">获取学生信息</button>
		<button type="button" @click="getChineseName">获取中文信息</button>
	</div>
	
</template>

<script>
	import axios from 'axios'
	export default {
		name:'Student',
		methods:{
			getStudentName(){
				// axios.get('http://localhost:8080/demo/').then(
				// 代理服务器地址+目标服务器资源路径
				axios.get('http://localhost:8080/demo/test1/').then(
					response => {console.log('请求成功',response.data)},
					error => {console.log('请求失败',error.message)}
				)
			},
			getChineseName(){
				// axios.get('http://localhost:8080/demo/').then(
				axios.get('http://localhost:8080/demo/test2/').then(
					response => {console.log('请求成功',response.data)},
					error => {console.log('请求失败',error.message)}
				)
			},
			
		}
	}
</script>

<style>
</style>

vue脚手架配置代理

  • 方法一

    ### vue.config.js
    ......
    devServer: {
    	  // 配置目标服务器地址
          proxy: 'http://localhost:8888'
        }
    
    - 优点: 配置简单,请求资源时直接发给前端代理服务器即可
    - 缺点: 不能配置多个代理,不能灵活的控制请求是否走代理
    
  • 方法二

    ### vue.config.js
    ......
    devServer: {
    	  proxy: {
    		'/test1': { // 取个名字
    		  target: 'http://localhost:8888', // 目标服务器地址
    		  pathRewrite:{'^/test1':''} // 正则匹配,去掉这个名字
    		  // ws: true, // 用于支持 websocket
    		  // changeOrigin: true // 用于控制请求头中的host值(是否隐藏客户端地址)
    		},
    		'/test2': {
    		  target: 'http://localhost:8888',
    		  pathRewrite:{'^/test2':''}
    		 ......
    		},
    	  }
        }
    
    - 优点: 可以配置多个代理,且可以灵活控制请求是否走代理
    - 缺点: 配置稍繁琐,请求资源必须加前缀
    

    github搜索案例(调用github API渲染数据)

  • 一个页面,就是一个'App.vue'

  • 引入bootstrap.css(public新建css目录,把bootstrap.css丢里面)

    ### index.html
    <!DOCTYPE html>
    <html lang="">
      <head>
       ......
        <link rel="icon" href="<%= BASE_URL %>favicon.ico">
        <!--模仿上面的引入方式,变量和css之间,无需'/'-->
    	<link rel="stylesheet" type="text/css" href="<%= BASE_URL %>css/bootstrap.css"/>
        ......
      </head>
    
  • demo组件搭建(App+2个组件)

    ### Search.vue
    <template>
      <section class="jumbotron">
        <h3 class="jumbotron-heading">Search Github Users</h3>
        <div>
          <input type="text" placeholder="enter the name you search"/>&nbsp;<button>Search</button>
        </div>
      </section>
    </template>
    
    <script>
    	export default {
    	  name: "Search"
    	}
    </script>
    
    ### List.vue
    <template>
      <div class="row">
        <div class="card">
          <a href="" target="_blank">
            <img src="https://img1.baidu.com/it/u=1629479163,3719818209&fm=253&fmt=auto&app=138&f=JPEG?w=473&h=399" style="width: 100%"/>
          </a>
        </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>
    
    ### App.vue
    <template>
      <div class="container">
        <Search/>
        <List/>
      </div>
    </template>
    
    <script>
    
    import Search from './components/GitHub/Search.vue'
    import List from './components/GitHub/List.vue'
    
    export default {
      name: 'App',
      components: {
    	  Search,
    	  List
      }
      
    }
    </script>
    
    
    
  • 引入第三方css注意点: 如果采用以下方式引用,一旦库依赖的第三方资源没有被引入,就会报错

<script>
    import './assert/css/bootstrap.css'
    ......
</script>

  • github接口地址

    https://api.github.com/search/users?q=xxx
    
  • 测试该接口能否正常返回数据(正常返回数据)

    ### List.vue
    <template>
      <section class="jumbotron">
        <h3 class="jumbotron-heading">Search Github Users</h3>
        <div>
        														<!--v-model收集用户输入-->
          <input type="text" placeholder="enter the name you search" v-model="keyWord" />&nbsp;
    	  <button @click="searchUser">Search</button> <!--绑定事件-->
        </div>
      </section>
    </template>
    
    <script>
    	import axios from 'axios'
    	
    	export default {
    	  name: "Search",
    	  data(){
    		  return {
    			  keyWord:''
    		  }
    	  },
    	  methods:{
    		  searchUser(){
    			 // 返回的数据: {total_count: 117470, incomplete_results: false, items: Array(30)}
    			  axios.get(`https://api.github.com/search/users?q=${this.keyWord}`).then(
    				response => {
    					console.log('请求成功',response.data)
    				},
    				error => {
    					console.log('请求失败',error.message)
    				}
    			  )
    		  }
    	  }
    	}
    </script>
    
  • 现在调用github api接口,获取数据并渲染出来(这里使用$bus实现组件之间通讯)

    ### Search.vue(发数据,触发事件并传参)
    <template>
     ......
    </template>
    
    <script>
    	import axios from 'axios'
    	
    	export default {
    	  name: "Search",
    	 ......
    	  methods:{
    		  searchUser(){
    			  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.vue(收数据,绑定事件并接收数据)
    <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: 100%"/>
          </a>
    	  <p class="card-text">{{user.login}}</p>
        </div>
      </div>
    </template>
    
    <script>
    	export default {
    	  name: "List",
    	  data(){
    		  return {
    			  users:[] // 预存储
    		  }
    	  },
    	  mounted(){
    		  this.$bus.$on('getUsers',(items)=>{
    			  this.users = items // 接收数据并保存起来
    		  })
    	  }
    	}
    </script>
    
    <style scoped>
    	......
    </style>
    
  • List组件的四种状态切换,我们新增布尔值变量,来标识这种状态,再依据值来决定是否展示

    - 刚进入页面,展示欢迎信息: Welcome
    - 用户点击搜索(网速比较慢): loading
    - 搜索成功: users
    - 搜索失败: error
    
  • 基础写法(可以实现 功能,缺点是函数参数很业余,没有实现'语义化')

    ### List.vue
    <template>
      <div class="row">
        ......
        <!--增加html显示内容(依据标识值决定是否展示)-->
    	<h1 v-show="isFirst">欢迎使用</h1>
    	<h1 v-show="isLoading">加载中......</h1>
    	<h1 v-show="errMsg">{{errMsg}}</h1>
      </div>
    </template>
    
    <script>
    	export default {
    	  name: "List",
    	  data(){
    		  return {
    			  isFirst:true, // 新增3个标记
    			  isLoading:false,
    			  errMsg:'',
    			  users:[]
    		  }
    	  },
    	  mounted(){
    		  // this.$bus.$on('getUsers',(items)=>{
    			 //  this.users = items
    		  // })
    		  // 接收 Search组件传过来的值
    		  this.$bus.$on('updateListData',(isFirst,isLoading,errMsg,users)=>{
    			  this.isFirst = isFirst
    			  this.isLoading = isLoading
    			  this.errMsg = errMsg
    			  this.users = users
    		  })
    	  }
    	}
    </script>
    
    ### Search.vue
    ......
    searchUser(){
    			  console.log('xxx')
    			  axios.get(`https://api.github.com/search/users?q=${this.keyWord}`).then(
    				response => {
    					console.log('请求成功',response.data)
    					// this.$bus.$emit('getUsers',response.data.items)
    					// 传参
    					this.$bus.$emit('updateListData',false,false,'',response.data.items)
    				},
    				error=>{
    					console.log('请求失败',error.message)
    					// 失败就返回错误信息和空list
    					this.$bus.$emit('updateListData',false,true,error.message,[])
    				}
    			  )
    		  }
    
  • 改进版: 把那些零碎的函数参数,打包成一个对象,传入

    ### Search.vue
    searchUser(){
    			  console.log('xxx')
    			  axios.get(`https://api.github.com/search/users?q=${this.keyWord}`).then(
    				response => {
    					
    					// this.$bus.$emit('updateListData',false,false,'',response.data.items)
    					// 传入一个对象(由于isFirst只显示一次,从此以后就是false,故这里不传,在List组件写死即可)
    					this.$bus.$emit('updateListData',{isLoading:false,errMsg:'',users:response.data.items})
    					
    				},
    				error=>{
    					console.log('请求失败',error.message)
    					// this.$bus.$emit('updateListData',false,true,'网络错误',[])
    					// 同样的写法,传入一个对象
    					this.$bus.$emit('updateListData',{isLoading:false,errMsg:error.message,users:[]})
    				}
    			  )
    		  }
    		  
    ### List.vue
    <template>
      <div class="row">
        <!--根据length值是否展示用户信息-->
        <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: 100%"/>
          </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',(isFirst,isLoading,errMsg,users)=>{
    			 //  this.isFirst = isFirst
    			 //  this.isLoading = isLoading
    			 //  this.errMsg = errMsg
    			 //  this.users = users
    		  // })
    		  // 接收对象
    		  this.$bus.$on('updateListData',(dataObj)=>{
    			  // this._data = dataObj // 这样写,_data就被写死了,不再是响应式对象
    			 this.info = {...this.info,...dataObj} // 拼接
    			 this.info.isFirst = false; // 不再显示欢迎消息
    		  })
    	  }
    	}
    </script>
    
    <style scoped>
    	......
    </style>
    

    vue-resource(旧版本vue使用的ajax库)

  • 属于vue插件

  • 使用方法很简单,和axios一模一样(API都一模一样)

    ### 安装
    npm i vue-resource
    
    ### main.js(引入插件)
    import xxx from 'vue-resource' // 这里可以随便取名
    Vue.use(xxx) // 使用插件
    
    ### Search.vue
    earchUser(){
    			  // 一模一样的API用法...
    			  // axios.get(`https://api.github.com/search/users?q=${this.keyWord}`).then(
    			  this.$http.get(`https://api.github.com/search/users?q=${this.keyWord}`).then(
    				response => {
    					......
    				},
    				error=>{
    					......
    				}
    			  )
    		  }