Vue - Ajax
本章主要讲解一下Vue中的跨域问题。
1. 开发环境Ajax跨域
注意,我这里明确指出来开发环境中的跨域,并不是生产环境。
现在我有一个后端环境,用于响应前端请求的数据,现在编写前端代码:
首先需要在
Vue
中安装Axios
:npm i axios
编写
App.vue
代码发送请求:<template> <div class="app"> <button @click="getArticleTagInfo">获取文章标签信息</button> </div> </template> <script> import axios from "axios"; export default { name: 'App', components: {}, methods: { getArticleTagInfo() { const params = { "pageNum": 1, "pageSize": 10 }; axios.post( 'http://localhost:8080/personal_blog/article/admin/queryArticleTagByAdmin', params, { headers: { token: 'VxvbPKB87OC3Op4IBIp66oxJzYTtQ1cSr8HVzfcbXijt77CxpjXvVOOgui05JQaKhZZnlbbbKtwi1CLBnO1imnf8m3DjvdFmxGMIkukI0GM8I71G4QQFfJd2aLj4L4KI' } } ).then(res => { console.log(res.data); }).catch(err => { console.log(err); }) } } } </script> <style> .app { background-color: gray; } </style>
注意:上面请求地址中,不能够直接写后端的地址以及端口,只能写前端的地址和端口,所以只能是8080,不能是后端的23121
执行结果:
凡是出现了
CORS
、Access-Control-Allow-Origin
等字样,基本上都是出现了跨域问题。
跨域(Cross-Origin)是指在Web开发中,当前前端页面的请求和后端API的响应不属于同一个域(域名、端口、协议的组合)时,就会发生跨域。这种情况可能会导致一些安全性问题,因此浏览器通常会限制跨域请求。
以下是导致跨域问题的主要情况:
- 不同域名: 当前前端页面的域名与后端API的域名不一致时,会发生跨域。例如,前端页面在 “
http://example.com
“ 上,而后端API在 “http://api.example.com
“ 上。- 不同端口: 即使域名相同,但端口不同也会被浏览器视为跨域。例如,前端页面在 “
http://example.com:8080
“ 上,而后端API在 “http://example.com:3000
“ 上。- 不同协议: 当前前端页面使用的是 HTTP 协议,而后端 API 使用的是 HTTPS 协议时,也属于跨域。
- 子域名不同: 当前前端页面的子域名与后端API的子域名不一致时,也可能发生跨域。例如,前端页面在 “
http://www.example.com
“,而后端API在 “http://api.example.com
“。在正常的Web开发中,为了安全起见,浏览器会实施同源策略(Same-Origin Policy),防止未经授权的跨域请求。同源策略限制了通过脚本(如JavaScript)发起的跨域HTTP请求。为了在前端进行跨域请求,可以通过一些方式来处理,例如使用CORS(跨域资源共享)头、JSONP、代理等技术。
为什么出现跨域呢?
我的前端使用的协议是HTTP
,主机名(域名)为localhost
,端口是8080
;
我的后端使用的协议是HTTP
,主机名(域名)为localhost
,端口是23121
;
这里因为端口不同,所以出现了跨域,如果要想解决跨域,就得使前后端使用的协议、主机(域名)、端口三者一致才行,也就是我们说的同源。
解决跨域的三种方式:
- 后端配置
CORS
来解决跨域 - 使用
JSONP
,不过这个技术只能解决get方式的请求,其余的请求方式均无法解决,有点鸡肋 - 使用代理:例如
nginx
不过这里我主要讲解在Vue中使用代理的方式,只不过只能在开发环境中有效,到生产环境中,一般是使用nginx
来代理了。
查看Vue-CLI
部分内容:
先参考它的配置一来配置Vue
,在 vue.config.js
中进行配置 :
const {defineConfig} = require('@vue/cli-service')
module.exports = defineConfig({
transpileDependencies: true,
lintOnSave: false, //关闭eslint检查
devServer: { //跨域配置
proxy: 'http://localhost:23121/'
}
})
注意:上诉配置的代理路径只是配置到了端口出就停止了,并且配置之后不能立即生效,必须重启前端服务才可以生效
执行结果:
可见请求数据成功了。
对于上诉的配置来说,官网说的很清楚了:这会告诉开发服务器将任何未知请求 (没有匹配到静态文件的请求) 代理到http://localhost:23121
;
反过来说,如果有匹配的静态文件,也就是Vue项目中的public文件夹下的文件,那么它就不会将请求转发到后端,而是直接请求了前端的静态文件。
所以,配置一是有缺陷的:
- 不能配置多个代理
- 不能灵活的控制请求是否走代理。
现在来看看配置二的写法:
const {defineConfig} = require('@vue/cli-service')
module.exports = defineConfig({
transpileDependencies: true,
lintOnSave: false, //关闭eslint检查
devServer: { //跨域配置
proxy: {
'/personal_blog': {
target: 'http://localhost:23121',
ws: true, //websocket 默认开启
changeOrigin: true, //默认开启
//pathRewrite: {'^/personal_blog': ''}
},
'/test':{
//...
}
}
}
})
配置二的意思就是,当检测到端口号后面的第一层路径为/personal_blog
的时候,就走这层代理,如果为test
的时候,就走test配置对象中的代理。
changeOrigin
设置为true
时,服务器收到的请求头中的host为:localhost:23121
changeOrigin
设置为false
时,服务器收到的请求头中的host为:localhost:8080
changeOrigin
默认值为true
;用大白话说,就是Vue会不会撒谎的问题,如果为true表示撒谎,告诉后端服务器我的Host(域名和端口)和你一样,反之告诉后端服务器自己真实的Host,一般我们都设置为true。
注意:
在我的配置中还有
pathRewrite
这个配置项,对于上诉配置来说,他的作用就是将请求中的/personal_blog使用空字符串代替,也就是路径重写。一般我的请求会有一个统一前缀开头的,比如
http://example.com/api/.....
,这个/api就是路径的前缀,如果你的后端没有配置统一前缀,那么你请求的时候就要将这个/api
使用空字符串代替,才能请求到后端地址,如果后端也配置了统一路径前缀,那么你就无需配置pathRewrite
这个配置项了。
2. 插槽
我现在的代码有App
组件和Category
组件,App组件如下:
<template>
<div class="container">
<Category :dataList="foods" title="美食"/>
<Category :dataList="games" title="游戏"/>
<Category :dataList="films" title="电影"/>
</div>
</template>
<script>
import Category from "@/components/Category.vue";
export default {
name: 'App',
components: {Category},
data() {
return {
foods: ['火锅', '烧烤', '小龙虾', '牛排'],
games: ['红色警戒', '穿越火线', '劲舞团', '超级玛丽'],
films: ['《教父》', '《拆弹专家》', '《你好,李焕英》']
}
},
}
</script>
<style>
.container{
display: flex;
justify-content: space-around;
}
</style>
Category
组件如下:
<template>
<div class="category">
<h3>{{ title }}分类</h3>
<ul v-for="(item,index) in dataList" :key="index">
<li>{{ item }}</li>
</ul>
</div>
</template>
<script>
export default {
name: "Category",
props: ['dataList', 'title']
}
</script>
<style scoped>
.category {
background-color: skyblue;
width: 200px;
height: 300px;
}
h3{
text-align: center;
background-color: orange;
}
</style>
执行结果:
2.1 默认插槽
现在我有如下需求:要求在没事分类中添加一个图片,电影分类中添加一个视频,游戏分类不变,要求的效果如下:
那么你可能会这样来做,我们上诉代码中的App
组件传递了一个分类信息的title,你可能会根据不同的分类,然后使用v-show
来判断什么分类展示什么结构,这样写确实可以,但是分类一多,需求一多,那么你的代码就特别混乱,特别难以维护。
那么我们在App中不是使用了Category组件吗?我们知道,使用组件有两种方式,一种是自闭和:<Categroy/>
,一种是双标签:<Categroy></Categroy>
,那么我在双标签中写结构是否可以呢?我们来试试看:
App组件修改部分代码:
<Category :dataList="foods" title="美食">
<img src="https://s3.ax1x.com/2021/01/16/srJlq0.jpg" alt="">
</Category>
执行结果:
可见是没有什么效果,控制台也没有报错,难道是Vue没有解析这里img标签吗?答案是否定的,肯定解析了的。那为什么会出现这种情况呢?
这里我就不墨迹了,直接说:因为你在Category标签中写了其他标签,然后Vue肯定也会去解析的,但是Vue解析到组件标签体里面的其他标签的时候,他会将解析到内容放入你的组件中,例如你这里写了<Category></Category>
组件标签,那么Vue会将这个组件标签中的解析的内容放到你的Category
组件中去,虽然Vue虽然会给你放到对应的组件中去,但是它并不知道应该放到什么位置,所以这里你需要指定一个Vue放入解析内容的位置,这也就是插槽的由来。
插槽的作用:让父组件可以向子组件指定位置插入html结构,也是一种组件间通信的方式,适用于父组件 ===> 子组件。
对应的标签为:<slot></slot>
继续改造代码,我们来指定Vue将解析的内容存放的位置。
Category
组件:
<template>
<div class="category">
<h3>{{ title }}分类</h3>
<slot>我是一些默认值,当使用者没有传递具体结构时,我会出现</slot>
</div>
</template>
<script>
export default {
name: "Category",
props: ['title']
}
</script>
App
组件(使用者):
<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="(item,index) in games" :key="index">{{ item }}</li>
</ul>
</Category>
<Category title="电影">
<video src="http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4"></video>
</Category>
<Category title="未知"></Category>
</div>
</template>
<script>
import Category from "@/components/Category.vue";
export default {
name: 'App',
components: {Category},
data() {
return {
foods: ['火锅', '烧烤', '小龙虾', '牛排'],
games: ['红色警戒', '穿越火线', '劲舞团', '超级玛丽'],
films: ['《教父》', '《拆弹专家》', '《你好,李焕英》']
}
},
}
</script>
<style>
.container{
display: flex;
justify-content: space-around;
}
img{
width: 100%;
}
video{
width: 100%;
}
</style>
可见,改造的结果是,我将Category组件里面的ul和li结构移除了,因为要传递的数据形式是使用者决定的,不一定是列表结构,所以将结构移到使用者那边去,然后使用<slot></slot>
标签来接收使用者传递的结构,也就是组件标签中的其他标签内容,并且<slot></slot>
标签中还写了一些内容,这<slot></slot>
标签中的内容出现的时机是:当使用者没有传递结构过来的时候展示的。
执行结果:
可见传递过去的内容都被slot标签给收到了,并且如果没有传递html结构的时候,展示的也是默认内容。
上图的展示结果中,视频video没有加载出来,可能是视频源没了。不用过多关心。
上诉代码的写法其实也被称为默认插槽的写法。
2.2 具名插槽
上一小节我们知道了默认插槽的写法,这一小节看看具名插槽,顾名思义就是有名称的插槽。
现在我有这样的需求:
要求在图片或视频或列表下方有一些超链接,且使用插槽来完成。
你可能会这样写:
Categroy.vue
:
<template>
<div class="category">
<h3>{{ title }}分类</h3>
<slot>我是一些默认值,当使用者没有传递具体结构时,我会出现</slot>
<slot>我是一些默认值,当使用者没有传递具体结构时,我会出现</slot>
</div>
</template>
在第一个插槽中,你想要存放图片,第二个插槽中想要存放超链接。
在App.vue对应位置加上超链接:
<Category title="美食">
<img src="https://s3.ax1x.com/2021/01/16/srJlq0.jpg" alt="">
<a href="blog.cqwulyj.cn" target="_blank">更多美食</a>
</Category>
执行结果:
可见它并不是你想象中的效果,而是将图片和超链接一起存放在了插槽1和插槽2中,并没有插槽1存图片,插槽2存超链接,那如何让他们分开存放呢?
这时候,你就可以给插槽取名。
Category.vue中:
<template>
<div class="category">
<h3>{{ title }}分类</h3>
<slot name="img">我是一些默认值,当使用者没有传递具体结构时,我会出现</slot>
<slot name="link">我是一些默认值,当使用者没有传递具体结构时,我会出现</slot>
</div>
</template>
使用name标签属性来给slot插槽取名。
对应App.vue的修改:
<Category title="美食">
<img src="https://s3.ax1x.com/2021/01/16/srJlq0.jpg" alt="" slot="img">
<a href="blog.cqwulyj.cn" target="_blank" slot="link">更多美食</a>
</Category>
使用的时候,在对应的结构上加上slot标签属性,并且值为取名的值。
不过现在slot和slot-scope标签属性好像已经过时了,不过还能使用。
这里面对应使用者(App)来说还有还有另外一种写法,只不过这种写法要和template
标签配合起来使用,且使用template
标签的时候,要使用v-slot
来指定名称了,**v-slot
标签属性目前还没有过时**:
<Category title="美食">
<template v-slot:img>
<img src="https://s3.ax1x.com/2021/01/16/srJlq0.jpg" alt="" >
</template>
<template v-slot:link>
<a href="blog.cqwulyj.cn" target="_blank">更多美食</a>
</template>
</Category>
注意:v-slot只能用到template标签上,并且v-slot是用
:
并不是用=""
的形式,并且template标签在被解析之后是不呈现这个标签的。
2.3 作用域插槽
作用域插槽从名称上看和作用域相关,这里的作用域和JavaScript中的作用域其实有异曲同工之妙。
什么时候使用作用域插槽呢?当需要的数据并没有在组件的使用者身上,而在组件本身上的时候使用。
例如:数据在组件的自身,但根据数据生成的结构需要组件的使用者来决定。(games数据在Category组件中,但使用数据所遍历出来的结构由App组件决定)
Category.vue代码:
<template>
<div class="category">
<h3>{{ title }}分类</h3>
<slot name="img">我是一些默认值,当使用者没有传递具体结构时,我会出现</slot>
<slot name="link">我是一些默认值,当使用者没有传递具体结构时,我会出现</slot>
</div>
</template>
<script>
export default {
name: "Category",
props: ['title'],
data() {
return {
foods: ['火锅', '烧烤', '小龙虾', '牛排'],
games: ['红色警戒', '穿越火线', '劲舞团', '超级玛丽'],
films: ['《教父》', '《拆弹专家》', '《你好,李焕英》']
}
}
}
</script>
<style scoped>
.category {
background-color: skyblue;
width: 200px;
height: 300px;
}
h3{
text-align: center;
background-color: orange;
}
</style>
现在数据在Category组件本身上。
对应的App.vue:
<template>
<div class="container">
<Category title="游戏">
<ul>
<li v-for="(item,index) in games" :key="index">{{ item }}</li>
</ul>
</Category>
</div>
</template>
<script>
import Category from "@/components/Category.vue";
export default {
name: 'App',
components: {Category},
}
</script>
<style>
.container{
display: flex;
justify-content: space-around;
}
img{
width: 100%;
}
video{
width: 100%;
}
</style>
现在我的组件的使用者想要使用games数据,显然是得不到的。
现在的场景:组件的使用者App拿不到数据,数据只能传递到组件本身上,组件的使用者又想根据使用者自己来决定传递数据的格式是怎样的,所以也只能采用插槽来传递结构,不能将结构写在组件本身上,这时候似乎就陷入了死局。
这时候可以使用作用域插槽来传递数据:
组件本身像props那样传递数据:
<template> <div class="category"> <h3>{{ title }}分类</h3> <slot :youxi="games"></slot> </div> </template> <script> export default { name: "Category", props: ['title'], data() { return { foods: ['火锅', '烧烤', '小龙虾', '牛排'], games: ['红色警戒', '穿越火线', '劲舞团', '超级玛丽'], films: ['《教父》', '《拆弹专家》', '《你好,李焕英》'] } } } </script> <style scoped> .category { background-color: skyblue; width: 200px; height: 300px; } h3{ text-align: center; background-color: orange; } </style>
可见传递的数据为
youxi
。组件使用者接收数据:
这里要使用
slot-scope
,对应的App.vue
:<Category title="游戏"> <ul slot-scope="receiveData"> <li v-for="(item,index) in receiveData" :key="index">{{ item }}</li> </ul> </Category>
你要先使用slot-scope接收到传递过来的数据之后,才能够使用youxi来遍历。这里接收数据的时候,名字可以随便写,不一定非要和传递数据时使用的一致
注意:
slot-scope
标签属性也过时了。结果:
可见接收到的数据时整个数组,我们要取出每一项,怎么取呢?receiveData.youxi
即可。也就是取数据的时候,格式:任意名称.传递的名称
<Category title="游戏">
<ul slot-scope="receiveData">
<li v-for="(item,index) in receiveData.youxi" :key="index">{{ item }}</li>
</ul>
</Category>
结果:
注意:千万不能是receiveData.games,我传递数据的时候专门写的不一样。
不过在实际开发中最好写一样,这样就不会考虑这么多问题了。