目录
一、全局事件总线
(一)全局总线介绍
关系图
对图中的中间商 x 的要求
1.所有组件都能看到
2.有 $on $off $emit
(二)案例
发送方 student
接收方
二、消息订阅和发布
(一)介绍
(二)使用消息订阅
(三)案例
发送方语法
接收方语法
三、todoList 编辑功能
(一)MyItem 文件内容
(二)App 文件内容
(三)$nextTick(function(){})
四、过度与动画
(一)用css动画写
(二)用过渡写
(三)设置多个标签动画
(四)使用成型的动画库
(五)总结
五、解决 ajax 请求跨域的问题
(一)复习
(二)跨域问题描述
(三)跨域解决办法
1.cors
2.jsonp
3.代理服务器
介绍
构建代理服务器
方式一 简单的代理服务器
方式二 高级代理
(四)github 搜索案例
1.发送按钮组件
2.列表组件
3.app 组件
一、全局事件总线
(一)全局总线介绍
这是程序员们的经验
可以实现任意组件间的通信
关系图
就是使用 自定义事件 的方式来实现通讯
在接收方的身上经过 x 绑定自定义事件 然后传数据的人经过 x 来触发这个事件并传递数据 回调函数在接收方那里 就能把发送方的数据发送过去
对图中的中间商 x 的要求
1.所有组件都能看到
x 得放到 vue 的 原型对象上, 就是 Vue.prototype.x = {a:1,b:2}
我们起名不叫 x 叫 $bus 为了迎合 Vue 的语法规则自己起的名 只是一个傀儡
写在 main.js 入口函数里面
安装 全局总线
import Vue from 'vue'
import App from './App.vue'Vue.config.productionTip = falsenew Vue({el: '#app',render: h => h(App),beforeCreate() {Vue.prototype.$bus = this}
})
2.有 $on $off $emit
这三个属性都在 Vue 的原型对象上
(二)案例
发送方 student
<button @click="sendStudentName">把学生名给school</button>
使用 emit 触发我们绑定的事件 然后后面传入我们 想传的数据
methods: {sendStudentName(){this.$bus.$emit('hello',666)}},
接收方
最好一挂载就绑定 所以写在生命周期钩子 mounted 里面
mounted(){this.$bus.$on('hello',(data)=>{console.log('我是 school 组件',data)})}
最好在后面加上一句这个
beforeDestroy(){this.$bus.$off('hello')}
二、消息订阅和发布
(一)介绍
消息订阅,就是绑定我们能接收消息的名字 以后有人发送该名字的消息 我们能调用我们内部的函数接收到
发布消息,就是发布的内容
(二)使用消息订阅
原生的 js 没有消息订阅的功能 ,所以我们要下载第三方的库
我们使用 pubsub.js 库进行 sub 是 subscribe 订阅的意思 pub 就是 publish 发布的意思
先下载库 然后在 发送消息 接收消息的组件中 都要引入这个库 语法如下
import pubsub from 'pubsub-js'
(三)案例
发送方语法
点击按钮发送 hello 消息 内容是 666
<button @click="sendStudentName">把学生名给school</button>
methods: {sendStudentName(){// this.x.$emit('hello',666)pubsub.publish('hello',666)}},
接收方语法
接收名字为 hello 的消息 并且执行回调函数
回调函数内部有两个参数 第一个是消息名 第二个是数据
最好写成箭头函数否则 this 的指向会出问题
mounted(){this.pid = pubsub.subscribe('hello',(message,data)=>{console.log('有人发布了hello 消息 回调执行了',message,data)})},
最后加上一句这个
beforeDestroy(){// this.$bus.$off('hello')pubsub.unsubscribe('this.pubid')}
三、todoList 编辑功能
(一)MyItem 文件内容
在 Item 中 先写一个文本框 然后和前面的 span 用 v-show 方式 用里面的 todo.isEdit 数据来决定显示哪一个数据
<span v-show="!todo.isEdit"> {{ todo.title }}</span>
<inputv-show="todo.isEdit"type="text":value="todo.title"@blur="handleBlur(todo,$event)"ref="inputTitle"/>
用一个按钮 来触发编辑,里面调用 我们编辑的方法 handleEdit 并把 todo 传进去
<button v-show="!todo.isEdit" class="btn btn-edit" @click="handleEdit(todo)">编辑</button>
handleEdit 函数内容 我们要添加 isEdit 属性 先判断一下原先存不存在这个 属性 如果存在直接修改这个属性值即可,如果没有我们再添加
用 $set 方法添加
handleEdit(todo) {if (todo.hasOwnProperty("isEdit")) {todo.isEdit = true;} else {this.$set(todo, "isEdit", true);}},
当光标失去焦点时我们改变数据,通过全局总线 传数据
handleBlur(todo,e) {todo.isEdit = false;if(!e.target.value.trim()) return alert('输入不能为空')this.$bus.$emit('updateTodo',todo.id,e.target.value)},
(二)App 文件内容
在 app 中绑定事件 然后 写回调函数
this.$bus.$on("updateTodo", this.updateTodo);
回调函数修改 显示的数据
updateTodo(id, title) {this.todos.forEach((todo) => {if (todo.id === id) todo.title = title;});},
我们想点击编辑时就获得焦点 修改一下handle 函数
handleEdit(todo) {if (todo.hasOwnProperty("isEdit")) {todo.isEdit = true;} else {this.$set(todo, "isEdit", true);}setTimeout(() => {this.$refs.inputTitle.focus();},200);},
(三)$nextTick(function(){})
但是开发中更常用 $nextTick(function(){}) 下一次更新 dom 模板之后才执行
语法:this.$nextTick(回调函数)
这样更改数据 模板重新解析 dom 元素,解析完毕调用函数 获得焦点
handleEdit(todo) {if (todo.hasOwnProperty("isEdit")) {todo.isEdit = true;} else {this.$set(todo, "isEdit", true);}// setTimeout(() => {// this.$refs.inputTitle.focus();// },200);this.$nextTick(function(){this.$refs.inputTitle.focus();})},
四、过度与动画
transition 标签只能设置一个元素的动画 里面不能放两个标签 如果想给多个元素设置动画,就用transition-group
(一)用css动画写
先在 css 中写一个动画效果 进来和出来两个效果,分别写在 come 和 go 里面
.come {animation: hahaha 1s;
}
.go {animation: hahaha 1s reverse;
}
@keyframes hahaha {from {transform: translateX(-100%);}to {trnasform: translateX(0px);}
}
然后 标签中调用动画效果
<h1 v-show="isShow" class="go">你好啊!</h1>
我们可以通过 vue 来切换我们要使用的动画 不用自己写判断 什么时候调用什么动画
这一种过渡的效果 我们就能通过 transition(过渡) 标签来设置
在合适的时候加上 特定的类名就是特定的动画 come 就是来 go 就是去
<transition name="hello" :appear="true"><h1 v-show="isShow">你好啊!</h1></transition>
transition 还能命名 但是css 中的效果对应也得修改 后面的 appear 属性 能让动画一开始就出来
前面的名字得改成一样的
.hello-enter-active {animation: hahaha 1s;
}
.hello-leave-active {animation: hahaha 1s reverse;
}
(二)用过渡写
.hello-enter 是进入的起点
hello-enter-to 是进入的终点
hello-leave 是退出的起点
hello-leave-to 是退出的终点
hello-enter-active 是进入过渡的效果
hello-leave- active 是离开的过渡效果
模板的内容和前面一样
<button @click="isShow = !isShow">显示隐藏</button><transition name="hello" :appear="true"><h1 v-show="isShow"> 你好啊!</h1></transition>
进入的起点
.hello-enter{transform: translateX(-100%);
}
进入的终点
.hello-leave-to{transform: translateX(0);
}
离开的起点 离开的终点
.hello-leave{transform: translateX(0);
}
.hello-leave-to{transform: translateX(-100%);
}
因为离开的起点和离开的终点和前面开始一致 我们直接合在一块儿写,效果是一样的 并且能简化代码的结构
.hello-enter, .hello-leave-to{transform: translateX(-100%);
}
.hello-enter-to, .hello-leave{transform: translateX(0);
}
过渡的效果 可以单独写在
.hello-enter-active,
.hello-leave-active {transition: 0.5s;
}
(三)设置多个标签动画
transition-group
用到 transition-group 时 里面的标签需要给 key 值
<template><div><button @click="isShow = !isShow">显示隐藏</button><transition-group name="hello" :appear="true"><h1 v-show="isShow" key="1">你好啊!</h1><h1 v-show="isShow" key="2">你好啊!</h1></transition-group></div>
</template>
(四)使用成型的动画库
先用 npm 安装这个包 Animate.css
然后引入 import
import 'animate.css'
然后把网站中写好的类名 放到 name 中
然后再多加一个属性
enter-active-class=“” 进入的动画
leave-active-class=“" 退出的动画
后面引号的内容直接去网站引入就行
<transition-groupname="animate__animated animate__bounce":appear="true"enter-active-class="animate__swing"leave-active-class="animate__backOutRight">
(五)总结
当我们操纵 dom 元素时 在合适的时候 给元素添加样式类名
进入退出 都有三个状态
进入的起点 v-enter
进入的终点 v-enter-to
进入的总过程 v-enter-active
写法
如果用动画写直接用 v-enter-active v-leave-active
如果不用的话 就得用 v-enter v-enter-to v-leave v-leave-to
transition 包含要过渡的元素,得配置 name 属性
多个元素时 得用 <transition-group> 并且里面的每一个元素都得有 key 值
五、解决 ajax 请求跨域的问题
vue-cli 开启 8080 服务器 支持脚手架的运行
(一)复习
复习 : 常用的发送 ajax 请求的方式有什么
1.xhr new (鼻祖 太老了 不用他)
XMLHttpRequest()
xhr.open() 配置请求信息
xhr.send() 真正发出去的请求
2.jQuery
因为 xhr 太古老了 我们需要有人封装一下里面的方法 可以是公司的内部人员
也有现成的封装 就是 jQuery 封装过 xhr
$.get $post
3.axios
也是对 xhr 的封装
4.fetch
和 xhr 是平级的 都是在window 的内置对象上能直接使用
兼容性较差,ie 完全用不了
所以建议使用 axios
(二)跨域问题描述
先引入 axios
import axios from ‘axios’
然后写一个向服务器发送发送 ajax 请求的代码
<template><div><button @click="getStudents">获得学生信息</button></div>
</template>
<script>
import axios from 'axios'
export default {name: "app",methods:{getStudents(){axios.get('http://localhost:5000/students').then(response => {console.log('请求成功了',response.data)},error => {console.log('请求失败',error.message)})}}
};
</script>
首先先开启我们的资源服务器 端口为 5000
然后启动 vue 脚手架,点击按钮发送 ajax 请求
发现报错
发现关键词 cors 还有那个一长串的属性 这是我们之前学的 ajax 同源策略 的知识,现在我们的网站是 8080 端口而且是 http 协议 主机名是 localhost
但是资源服务器 也是 http 协议 主机名也是 localhost 但是 端口号是 5000 和8080 不同不符合同源策略,所以报错 必须三个都相同才是 同源
浏览器成功发送请求了
资源服务器也接收服务器的请求了
我们服务器也接收到返回的资源了
但是我们的网站 发现端口号不太对 就不把资源给我们了,但是我们浏览器里面现在手里有资源
(三)跨域解决办法
1.cors
就是写服务器的人在 响应的时候加几个特殊的响应头
我们的网站成功发送请求了
资源服务器也接收请求了
我们网站也接收到返回的资源了 并且这个资源携带着特殊的响应头 我们网站一接收 发现这个响应头就明白我们要这个数据 就把数据给我们了 实现了跨域
特点:能解决跨域但是 容易任何人都能访问我们的资源服务器 获得资源不太好
需要后端写服务器的人员帮忙
2.jsonp
借助了script 标签里面的 src 属性 引入外部资源的时候不受同源策略的限制
使用的非常少 只能解决 get 请求的 跨域问题
需要前后端一块儿帮忙
3.代理服务器
介绍
代理服务器和我们网站所处在的位置相同
我们想向 资源服务器请求资源 我们先向代理服务器 发送请求
代理服务器再把请求转发给资源服务器
资源服务器把资源返回给代理服务器
代理服务器再把资源转发给我们 我们就收到数据了
粉色的是服务器 蓝色的也是服务器 服务器服务器之间 不用发送 ajax 就用原生的 http 协议即可
构建代理服务器
1.使用 nignx
后端了解一些再开启
2.使用 Vue 脚手架开启
方式一 简单的代理服务器
在vue.config.js 文件中编写代码
先在 vue-cli 的文档中 的配置项中 找到 devSever.proxy 后面的单词就是代理的意思
先把第一段代码沾到我们的文件的后面 开启代理服务器
端口号是我们要请求的服务器的端口号 不是8080 是 5000 后面不要写别的路径了
devServer: {proxy: 'http://localhost:5000'}
然后 给我们 代码中的请求对象的端口也改成 8080 因为我们这回请求对象是代理服务器
<template><div><button @click="getStudents">获得学生信息</button></div>
</template>
<script>
import axios from 'axios'
export default {name: "app",methods:{getStudents(){axios.get('http://localhost:8080/students').then(response => {console.log('请求成功了',response.data)},error => {console.log('请求失败',error.message)})}}
};
</script>
最后请求成功了
小问题:
8080 的根路径 在 public 文件夹中 里面的东西 跨=可以通过 localhost:8080/文件名的形式访问
如果 public 中 的文件出现了我们要请求的服务器中的文件名 我们就不会发送ajax 请求了而是直接把本地的东西拿来使用
比如上面的 students 如果我们本来就有就不发送请求了
而且 我们创建的代理服务器只能向一个资源服务器发送请求 不能向多个服务器发送请求
方式二 高级代理
里面有两段代码 第一段是完整代码 第二段是精简后的代码 我们先看前面的
就是我们的请求带着 这个信息我们就向代理发送请求
/api 是请求前缀 代理服务器问我们 的 8080 我们本次请求的请求前缀是什么 如果是 /api 就走代理
target 就是我们要请求的目标 就是我们 url 地址 代理服务器把请求转发给这个地址
ws 是允许 websocket 通信
changeOrigin: true 是资源服务器 查看我们代理服务器位置时 代理服务器做出的应答,为真时 代理服务器就说我在 5000 其实在 8080 端口 ,如果时 false 就实话实说
devServer: {proxy: {'/api': {target: '<url>',ws: true,changeOrigin: true},'/foo': {target: '<other_url>'}}}
初始化之后代码
devServer: {proxy: {'/daili': {target: 'http://localhost:5000',ws: true,changeOrigin: true},}
}
所以我们要使用这个代理服务器 我们得让我们原本的 请求地址带上请求前缀的信息,位置写在端口号的后面 后面的内容不用变化 还是 students
<template><div><button @click="getStudents">获得学生信息</button></div>
</template>
<script>
import axios from 'axios'
export default {name: "app",methods:{getStudents(){axios.get('http://localhost:8080/daili/students').then(response => {console.log('请求成功了',response.data)},error => {console.log('请求失败',error.message)})}}
};
</script>
然后打开网页发送请求 出现 404 的错误 是因为我们的请求信息里面包含 /daili 我们代理服务器将请求转发时 把这个头前缀也转发了,但是资源服务器中没有 /daili 的路径,所以找不到信息报错
所以我们得把转发给资源服务器中的请求中的 /daili 去掉
我们得再加一个属性 pathRewrite:{'/^daili':' '} 匹配有 /daili 前缀的信息然后将这个信息 前缀转成空字符串
devServer: {proxy: {'/daili': {target: 'http://localhost:5000',pathRewrite:{'^/daili':''},ws: true,changeOrigin: true},}}
如果想向多个资源服务器发送请求 就复制一份 然后接着在 proxy 对象中写就行
devServer: {proxy: {'/daili': {target: 'http://localhost:5000',pathRewrite:{'^/daili':''},ws: true,changeOrigin: true},'/demo': {target: 'http://localhost:5001',pathRewrite:{'^/demo':''},ws: true,changeOrigin: true},}}
注意:
由于我们这种写代理的方式 要加一个前缀,所以如果我们本地也有同名文件时 就强制去使用代理服务器 而不是直接使用我们本地的
我们还能通过这种方式实现 同时向多个服务器发送请求‘(能配置多个代理)
很好的解决了第一种方式的问题
如果不想走代理不加前缀即可
(四)github 搜索案例
案例的网站已经解决了跨域的问题 用 cors
返回的数据 incomplete_results;false 说明发送的是不是完整的数据
item 是选出来的数据
第一个属性是 用户的头像 然后是每个人的 githbu 主页 地址
login 是用户的登录名
list 有四种展示 首先是刚进来的欢迎搜索
然后是 搜索时的加载中
然后是 搜索成功后的 用户信息
最后是 搜索请求出错时 的报错
代码结构
1.发送按钮组件
<template><section class="jumbotron"><h3 class="jumbotron-heading">Search Github Users</h3><div><inputtype="text"placeholder="enter the name you search"v-model="keyWord"/> <button @click="searchUsers">Search</button></div></section>
</template><script>
import axios from "axios";
export default {name: "Search",data() {return {keyWord: "",};},methods: {searchUsers() {this.$bus.$emit("updataListData", {isFirst: false,isLoading: true,errMsg: "",users: [],});axios.get(`https://api.github.com/search/users?q=${this.keyWord}`).then((response) => {console.log("请求成功了", response.data.items);this.$bus.$emit("getUsers", response.data.items);this.$bus.$emit("updataListData", {isLoading: false,errMsg: "",users: response.data.items,});},(error) => {console.log("请求失败了", error.message);this.$bus.$emit("updataListData", {isLoading: false,errMsg: error.message,users: [],});});},},
};
</script>
2.列表组件
<template><div class="row"><divv-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: 100px" /></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 scoped>
export default {name: "List",data() {return {info: {isFirst: true,isLoading: false,errMsg: "",users: [],},};},mounted() {this.$bus.$on("updataListData", (dataObj) => {this.info = {...this.info,...dataObj}});},
};
</script><style>
.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: 0.75rem;margin-bottom: 2rem;border: 1px solid #efefef;text-align: center;
}.card > img {margin-bottom: 0.75rem;border-radius: 100px;
}.card-text {font-size: 85%;
}
</style>
3.app 组件
<template><div class="container"><Search /><List /></div>
</template>
<script>
import Search from "./components/Search";
import List from "./components/List";export default {name: "app",components: { Search, List },
};
</script>