Vue(十) 过渡动画、配置代理服务器,解决请求跨域的问题

一、Vue封装的过渡与动画

作用:在插入、更新或移除 DOM元素时,在合适的时候给元素添加样式类名。

1. 案例展示

需求:实现这样的效果:点击显示与隐藏,会呈现滑动的效果
在这里插入图片描述
原来的写法:

<!-- isShow在data里设为true -->
<button @click="isShow = !isShow">显示/隐藏</button>
<h1 class="come" v-show="isShow">你好</h1>
<style>
.come {
  animation: move 0.5s linear;
}
.go {
  animation: move 0.5s linear reverse;
}
@keyframes move {
  from {
    transform: translateX(-300px);
  }
  to {
    transform: translateX(0px);
  }
}
</style>

但是这样只能手动更改h1的class类,使其有不同的动画效果;

2. Vue动画

添加transition标签。点击按钮,isShow为false, h1隐藏之前,会先执行一个离开的动画。isShow为true时,会先执行一个进入的动画,然后展示h1。

<button @click="isShow = !isShow">显示/隐藏</button>
<transition appear>
    <h1 v-show="isShow">你好</h1>
</transition>
<style>
   /* 进入的动画 */
   .v-enter-active {
       animation: move 0.5s linear;
   }
   /* 离开的动画 */
   .v-leave-active {
       animation: move 0.5s linear reverse;
   }

   @keyframes move {
       from {
           transform: translateX(-300px);
       }
       to {
           transform: translateX(0px);
       }
   }
</style>
  • 谁要加动画效果,transition标签就包裹谁。
  • transition会自动把v-enter-activev-leave-active加在标签上。
  • name属性。transition如果加上name属性,指定了名称,就不能用默认的v-...
  • appear属性。让页面一打开就有进入的动画效果。值为布尔值。:appear="true",如果不加冒号,true就是一个字符串,加上冒号才是表达式,表示一个布尔值。简单写法就是写一个appear即可。
<transition appear name="hello">
    <h1 v-show="isShow">你好</h1>
</transition>
<style>
/* 进入的动画 */
.hello-enter-active {
  animation: move 0.5s linear;
}
/* 离开的动画 */
.hello-leave-active {
  animation: move 0.5s linear reverse;
}
</style>

3. 过渡

<transition name="hello">
    <h1 v-show="isShow">你好</h1>
</transition>
<style>
/* 进入的起点,离开的终点 */
.hello-enter,
.hello-leave-to {
  transform: translateX(-300px);
}
/* 中间过程 */
.hello-enter-active,
.hello-leave-active {
  transition: 0.5s linear;
}
/* 进入的终点,离开的起点 */
.hello-enter-to,
.hello-leave {
  transform: translateX(0px);
}
</style>

进入或隐藏时,都会给h1添加对应的三个类;起点+终点+中间过程的类
比如隐藏时:
在这里插入图片描述
可以看出有离开的过程和离开的终点这两个类。离开的起点这个类也添加了,但是变化的太快,截不上图。效果执行完之后。h1的样式变为:
在这里插入图片描述
备注:若有多个元素需要过度,则需要使用:<transition-group>,且每个元素都要指定key值。

   <transition-group appear name="hello">
     <h1 class="come" v-show="isShow" key="1">你好</h1>
     <!-- 多个元素过度 transition-group-->
     <h1 class="come" v-show="isShow" key="2">你好</h1>
   </transition-group>

4. 第三方动画库

  • https://animate.style/

(1) 安装: npm install animate.css
(2) 引入:import 'animate.css'
(3) 使用:添加这三个属性,在网站上选择合适的效果,点击后边的复制,粘贴即可
在这里插入图片描述

二、Todo案例应用动画

在添加和删除时,添加一个柔和的效果。
在这里插入图片描述

给哪个组件加动画,这部分css代码就写在哪个组件里

.todo-enter-active {
  animation: move 0.3s linear;
}
.todo-leave-active {
  animation: move 0.3s linear reverse;
}

@keyframes move {
  from {
    transform: translateX(100%);
  }
  to {
    transform: translateX(0px);
  }
}

方式一:MyItem

在这里插入图片描述
方式二:MyList里

在这里插入图片描述

三、配置代理服务器

现有两台服务器
server1: http://localhost:5000/students
server2: http://localhost:5001/cars

1.总结发送请求的方式

  1. xhr (XMLHTTPRequest)。 比如有方法xhr.open(),xhr.send();这是原生的发送请求的方式。

    jquery和axios都是对xhr进行二次封装的第三方库。

  2. Jquery,比如$.get,$.post。但是jquery中80%的内容是操作dom,而使用vue和react就是为了不直接操作dom,因为发送请求并不常用jquery。

  3. axios,与jquery相比体积更小,支持请求拦截器和响应拦截器,是Promise风格的。更常用。

  4. fetch,与xhr是平级的,也是Promise风格。但是返回的数据包了两层Primose,得两个then才能获取数据,主要问题是兼容性较差。

2. 发送请求、跨域问题

采用axios发送请求:

// App.vue文件
showStu () {
    axios.get('http://localhost:5000/students').then(
        response => {
            console.log('请求成功', response.data)
        },
        error => {
            console.log('请求失败', error);
        }
    )
}

出现跨域错误,跨域即违背同源策略(同源策略是指协议名(http)、主机名(localhost)、端口号(8080)要一致)
在这里插入图片描述
出现跨域错误时,5000收到了8080的请求,并返回了请求结果。但是本地服务器8080收到结果后并未传递给前端,所以出错了。
在这里插入图片描述
跨域的时候,请求发了,服务器收到请求并返回了,且本地服务器也收到了,但是本地服务器并没给我们,所以出错了。

解决跨域的方式:

  • cors,需要配置响应头(但不能轻易配置响应头)
  • jsonp(利用script标签里的src属性,在引入外部资源时不受同源策略的限制)但开发中用的很少,只能解决get请求的跨域,且需要前后端都进行操作。
  • 配置代理服务器,代理服务器的位置(协议号、主机号、端口号)与前端一致。
    在这里插入图片描述
    同源策略是浏览器的一个安全机制,本地服务器8080和5000之间需要ajax进行交流,所以受同源策略的影响。代理服务器和5000服务器都是服务器,服务器与服务器之间用http即可互相发送请求,不受同源策略的限制。
    (打交道不用ajax.有浏览器才有window,才有xhr。)

3. 开启代理服务器

开启代理服务器的方式有两种:nginx,vue-cli。本文只看vue脚手架如何开启代理服务器

方式一:

  // vue.config.js
  devServer: {
      proxy: 'http://localhost:5000' // 发送请求的地址
  }
  // App.vue
  showStu () {
      // 这里如果写5000,则代理服务器不起任何作用。
      // 写8080是向代理服务器发请求,由代理服务器再去将请求转交给后台服务器 
      axios.get('http://localhost:8080/students').then(
          response => {
              console.log('请求成功', response.data)
          },
          error => {
              console.log('请求失败', error);
          }
      )
  }

在这里插入图片描述
缺点:

  1. 只能配置一个代理,也就是只能向5000服务器发送请求。
  2. 不能控制发送的请求是否走代理。当请求了前端不存在的资源时,那么该请求会转发给服务器 。当本身(public文件夹里的资源)就有请求的资源时,代理服务器就不会把请求转发给5000服务器,即不走代理。(优先匹配前端资源)

比如当public文件夹里有一个students文件,发送请求时,获取到的是该students文件里的内容。如果仍旧想要5000的students数据,这种方式行不通
在这里插入图片描述

方式二:

// vue.config.js
  devServer: {
    proxy: {
      //向server1发送请求
      '/school': {
        target: 'http://localhost:5000',
        pathRewrite: { '^/school': '' }, // 键值对,正则表达式。意思是将所有/school改为空字符串
        ws: true, // 用于支持websocket
        changeOrigin: false // 用于控制请求头中的host值
      },
      //向server2发送请求
      '/demo': {
        target: 'http://localhost:5001',
        pathRewrite: { '^/demo': '' },
        changeOrigin: false
      }
    }
  }
// App.vue
axios.get('http://localhost:8080/school/students').then(
    ...
)
  • 配置请求前缀
    假设规定 /school是请求前缀,想走代理就加上这个请求前缀,不走代理就不加请求前缀。 加上请求前缀后,请求路径是:http://localhost:8080/school/students

  • pathRewrite:重写路径。
    加上请求前缀之后,发送的请求是…/school/students,5000服务器收到的也是这个路径,但是5000服务器并没有这个路径下的资源。所以代理服务器接收到本地8080的请求后,需要将前缀去掉。否则会报404Not Found错误
    在这里插入图片描述

  • ws:用于支持websocket,默认值是true

  • changeOrigin:
    changeOrigin设置为true时,服务器收到的请求头中的host为:localhost:5000
    changeOrigin设置为false时,服务器收到的请求头中的host为:localhost:8080
    changeOrigin默认值为true
    更详细的说明见博客:原来我误会了 changeOrigin 这么多年-CSDN博客

在这里插入图片描述
说明:
1. 优点:可以配置多个代理,且可以灵活的控制请求是否走代理。
2. 缺点:配置略微繁琐,请求资源时必须加前缀。

四、Vue-resource(了解)

vue-resource是vue中的插件,用来发送请求。vue-resource也是对xhr的封装。
安装: npm install vue-resource
引入:import VueResource from 'vue-resource',Vue.use(VueResource)
引入之后vm和vc上就多了个$http。vue-resource的用法与axios十分类似,唯一的区别就是axios…改成了this.$http。

this.$http.get('http://localhost:8080/students').then(
    response => {
        console.log('请求成功', response.data)
    },
    error => {
        console.log('请求失败', error);
    }
)

五、github搜索案例

需求:输入关键词,点击Search按钮,搜索用户名里有这个关键词的用户,展示在页面上。

1、拆分静态组件

拆为App、Search、List。(也可以把每一项拆为item组件,这个案例简单写一下,就不拆了)
在这里插入图片描述
引入外部css文件

这个案例里引入需要bootstrap样式
1、方式一:在src下创建一个assets文件夹(分析脚手架里),创建css文件夹,粘贴第三方css,然后在app.vue里引入

在这里插入图片描述
报错,提示这个字体文件找不到:
在这里插入图片描述
但是我们在使用过程中,并未用到这个字体。这个错误是因为bootstrap源码里用到了这个字体。
通过import的方式引入样式,vue会对assets文件夹里的资源进行严格的检查,确保所有提到的资源都要有。有不存在的资源就会报错。但是第三方的字体在实际应用中并没有用到,所以此处不推荐这种方式。

2、方式二:在public文件里创建css文件,将第三方css粘贴进来,然后在index.html里引入css
在这里插入图片描述

2、动态数据

(1) 给Search组件的按钮添加点击事件

 <div>
   <input type="text" placeholder="enter the name you search" v-model="keyword" />&nbsp;
   <button @click="searchUsers">Search</button>
 </div>
 <script>
  data () {
    return {
      keyword: ''
    }
  },
  methods: {
    searchUsers () {
    // 这个接口github设计的免费接口,github已经通过cors的方式解决了跨域问题
      axios.get(`https://api.github.com/search/users?q=${this.keyword}`).then(
        response => {
         console.log('请求成功了', response.data.items);
        },
        error => {
         console.log('请求失败', error.message);
        }
      )
    }
  }
</script>

成功获取到数据:
在这里插入图片描述

(2) 数据展示在List组件里

组件间通信,此处采用全局事件总线。(二连问:谁提供数据,谁接收数据)
1、安装全局事件总线

// 创建vm实例
new Vue({
  // 将App组件放入容器中
  render: h => h(App),
  beforeCreate () {
    Vue.prototype.$bus = this
  }
}).$mount('#app') // 挂载容器

2、组件发送数据与接收数据,渲染页面

List.vue接收数据:

// 页面html结构就不写了
  data () {
    return {
      users: [],
    }
  },
  mounted () {
    this.$bus.$on('getUsers', (users) => {
      this.users = users
    })
  },
  beforeDestroy () {
    this.$bus.$off('getUsers')
  }

Serch.vue发送数据

  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);
        }
      )
    }
  }

页面渲染成功:

在这里插入图片描述

3、完善页面

List组件不应只展示用户数据。总的来说,List组件应该包括
(1)、刚进入界面时—欢迎
(2)、获取数据时----加载中
(3)、数据获取完—展示数据
(4)、数据加载失败----数据获取失败

数据驱动页面展示,页面有不同的变化,所以最好是创建新的数据。

List.vue
在这里插入图片描述
这样写的问题是 ,一个属性一个属性的写太麻烦。且Search.vue触发该事件时

// 不够语义化,但看false,true,不明白是什么含义
this.$bus.$emit('updateListData',  false,  true,  '', [] })

应该将这些属于用一个对象包装起来。

改进

Search.vue

 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 => {
        // 这里不需要再强调isFirst的值了,也不需要改变,所以就不写了
        this.$bus.$emit('updateListData', { isLoading: false, errMsg: '', users: response.data.items })
      },
      error => {
        this.$bus.$emit('updateListData', { isLoading: false, errMsg: error, users: [] })
      }
    )
  }
}

List.vue
在这里插入图片描述
这个地方需要注意,如果用38行代码的方式给info赋值,则会出现这样的情况:
在未发送请求之前,info包含四个属性
在这里插入图片描述
但是搜索之后,属性变成三个了,破坏数据结构(但是对功能没啥影响):
在这里插入图片描述
解决办法:
一、List组件中采用解构赋值的方式,即40行代码
二、Search组件传递数据时,四个属性的数据都写全。不省略isFirst

List和Search的完整代码

CSS样式不贴了
List:

<template>
  <div>
    <!-- 展示欢迎词 -->
    <h1 v-show="info.isFirst">欢迎!</h1>
    <!-- 展示加载中 -->
    <h1 v-show="info.isLoading">加载中</h1>
    <!-- 展示用户列表 用v-show控制 -->
    <div class="row" v-show="info.users.length">
      <div class="card" v-for="user in info.users" :key="user.id">
        <!-- 冒号动态绑定,动态绑定后,“js表达式” -->
        <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>
    <!-- 展示错误信息 -->
    <div v-show="info.errMsg">{{ info.errMsg }}</div>
  </div>
</template>

<script>
export default {
  name: 'List',
  data () {
    return {
      info: {
        users: [],
        isFirst: true, // 是否为初次加载
        isLoading: false, // 是否正在加载
        errMsg: '' // 请求页面错误
      }
    }
  },
  mounted () {
    // 绑定事件
    this.$bus.$on('updateListData', (data) => {
       this.info = { ...this.info, ...data }
    })
  },
  beforeDestroy () {
    this.$bus.$off('updateListData')
  }
}
</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"
        />&nbsp;<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 => {
          // 这里不需要再强调isFirst的值了,也不需要改变,所以就不写了
          this.$bus.$emit('updateListData', { isLoading: false, errMsg: '', users: response.data.items })
        },
        error => {
          this.$bus.$emit('updateListData', { isLoading: false, errMsg: error, users: [] })
        }
      )
    }
  }
}
</script>
  • 21
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值