Vue2Ajax和插槽


Vue - Ajax

本章主要讲解一下Vue中的跨域问题。

1. 开发环境Ajax跨域

注意,我这里明确指出来开发环境中的跨域,并不是生产环境。

现在我有一个后端环境,用于响应前端请求的数据,现在编写前端代码:

  1. 首先需要在Vue中安装Axios

    npm i axios
    
  2. 编写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

  3. 执行结果:

    凡是出现了CORSAccess-Control-Allow-Origin等字样,基本上都是出现了跨域问题。

跨域(Cross-Origin)是指在Web开发中,当前前端页面的请求和后端API的响应不属于同一个域(域名、端口、协议的组合)时,就会发生跨域。这种情况可能会导致一些安全性问题,因此浏览器通常会限制跨域请求。

以下是导致跨域问题的主要情况:

  1. 不同域名: 当前前端页面的域名与后端API的域名不一致时,会发生跨域。例如,前端页面在 “http://example.com“ 上,而后端API在 “http://api.example.com“ 上。
  2. 不同端口: 即使域名相同,但端口不同也会被浏览器视为跨域。例如,前端页面在 “http://example.com:8080“ 上,而后端API在 “http://example.com:3000“ 上。
  3. 不同协议: 当前前端页面使用的是 HTTP 协议,而后端 API 使用的是 HTTPS 协议时,也属于跨域。
  4. 子域名不同: 当前前端页面的子域名与后端API的子域名不一致时,也可能发生跨域。例如,前端页面在 “http://www.example.com“,而后端API在 “http://api.example.com“。

在正常的Web开发中,为了安全起见,浏览器会实施同源策略(Same-Origin Policy),防止未经授权的跨域请求。同源策略限制了通过脚本(如JavaScript)发起的跨域HTTP请求。为了在前端进行跨域请求,可以通过一些方式来处理,例如使用CORS(跨域资源共享)头、JSONP、代理等技术。

为什么出现跨域呢?

我的前端使用的协议是HTTP,主机名(域名)为localhost,端口是8080

我的后端使用的协议是HTTP,主机名(域名)为localhost,端口是23121

这里因为端口不同,所以出现了跨域,如果要想解决跨域,就得使前后端使用的协议、主机(域名)、端口三者一致才行,也就是我们说的同源。

解决跨域的三种方式

  1. 后端配置CORS来解决跨域
  2. 使用JSONP,不过这个技术只能解决get方式的请求,其余的请求方式均无法解决,有点鸡肋
  3. 使用代理:例如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文件夹下的文件,那么它就不会将请求转发到后端,而是直接请求了前端的静态文件。

所以,配置一是有缺陷的:

  1. 不能配置多个代理
  2. 不能灵活的控制请求是否走代理

现在来看看配置二的写法:

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拿不到数据,数据只能传递到组件本身上,组件的使用者又想根据使用者自己来决定传递数据的格式是怎样的,所以也只能采用插槽来传递结构,不能将结构写在组件本身上,这时候似乎就陷入了死局。

这时候可以使用作用域插槽来传递数据:

  1. 组件本身像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

  2. 组件使用者接收数据:

    这里要使用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标签属性也过时了。

  3. 结果:

可见接收到的数据时整个数组,我们要取出每一项,怎么取呢?receiveData.youxi即可。也就是取数据的时候,格式:任意名称.传递的名称

<Category title="游戏">
  <ul slot-scope="receiveData">
    <li v-for="(item,index) in receiveData.youxi" :key="index">{{ item }}</li>
  </ul>
</Category>

结果:

注意:千万不能是receiveData.games,我传递数据的时候专门写的不一样。

不过在实际开发中最好写一样,这样就不会考虑这么多问题了。


文章作者: 念心卓
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 念心卓 !
  目录