Vue(十) 过渡动画、配置代理服务器,解决请求跨域的问题
文章目录
- 一、Vue封装的过渡与动画
- 1. 案例展示
- 2. Vue动画
- 3. 过渡
- 4. 第三方动画库
- 二、Todo案例应用动画
- 三、配置代理服务器
- 1.总结发送请求的方式
- 2. 发送请求、跨域问题
- 3. 开启代理服务器
- 四、Vue-resource(了解)
- 五、github搜索案例
- 1、拆分静态组件
- 2、动态数据
- (1) 给Search组件的按钮添加点击事件
- (2) 数据展示在List组件里
- 3、完善页面
- List和Search的完整代码
一、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-active
和v-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.总结发送请求的方式
-
xhr (XMLHTTPRequest)。 比如有方法xhr.open(),xhr.send();这是原生的发送请求的方式。
jquery和axios都是对xhr进行二次封装的第三方库。
-
Jquery,比如
$.get
,$.post
。但是jquery中80%的内容是操作dom,而使用vue和react就是为了不直接操作dom,因为发送请求并不常用jquery。 -
axios,与jquery相比体积更小,支持请求拦截器和响应拦截器,是Promise风格的。更常用。
-
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);
}
)
}
缺点:
- 只能配置一个代理,也就是只能向5000服务器发送请求。
- 不能控制发送的请求是否走代理。当请求了前端不存在的资源时,那么该请求会转发给服务器 。当本身(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" />
<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"
/> <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>