Vue正式篇
🍓Vue脚手架
🍒搭建Vue脚手架
- 下载安装
node.js
:https://nodejs.org/en/download/
- 配置
node.js
环境变量 - 在
cmd
窗口输入node --version
- 配置
npm
淘宝镜像:npm config set registry https://registry.npm.taobao.org
- 按照官方文档执行以下命令
https://cli.vuejs.org/zh/
npm install -g @vue/cli
- 关掉
cmd
,重新打开cmd
,输入命令vue
,查看是否安装成功 - 在桌面创建一个
Vue
项目模板- 输入:
vue create
项目名 Please pick a preset: (Use arrow keys)
选择Vue2
版本- 在项目的根目录下执行以下命令启动项目:
npm run serve
- 前往浏览器输入
http://localhost:8080/
,成功访问
- 输入:
🍒脚手架目录结构
|—— node_modules
|—— public
| |—— favicon.ico:页签图标(名字和路径不要随便修改)
| |—— index.html:主页面(名字和路径不要随便修改)
|—— src
| |—— assets:存放静态资源
| | |—— logo.png
| |—— component:存放组件
| | |—— HelloWorld.vue
| |—— App.vue:汇总所有组件
| |—— main.js:入口文件(名字和路径不要随便修改)
|—— .gitignore:git版本管制忽略的配置
|—— babel.config.js:babel的配置文件
|—— pakage.json:应用包配置文件
|—— README.md:应用描述文件
|—— package-lock.json:包版本控制文件
|—— vue.config.js:vue脚手架配置文件(自己添加,名字固定,不能随便取)
🍒脚手架文件执行顺序
- 执行
npm run serve
- 程序来到
src/main.js
文件(入口文件),将App
组件放入容器 - 程序来到
public/index.html
🍒入口文件main.js
涉及技术:
ES6
模块化暴露方式:- 默认暴露
export default xxx
(暴露一个的推荐使用)
引入方式:import xxx from xxx
- 分别暴露
export const xxx = xxx
引入方式:import {xxx} from xxx
- 统一暴露
export {xxx}
引入方式:import {xxx} from xxx
- 默认暴露
render
:render
是一个有返回值的函数render
里的参数是一个函数- 通过
render
参数的函数来注册App
组件,解析模板 - 脚手架默认引入的是一个阉割版的
/vue/dist/vue.runtime.esm.js
,没有模板解析的功能,只能通过render
属性来解决
//引入Vue
import Vue from 'vue'
//引入App组件,它是所有组件的父组件
import App from './App.vue'
//关闭vue生产提示
Vue.config.productionTip = false
//创建Vue实例对象
new Vue({
el:'#app',
//render简写
//render: h => h(App),
//render完整写法:
render(createElement){
return createElement(App)
}
})
🍒Vue文件
<!--组件的模板,注意只有一个根标签,并且根标签上不能使用Vue指令-->
<template>
</template>
<script>
//组件交互相关的代码
export default {
name:'School',//组件名字,一般与Vue文件的名字相同
data(){ //组件管理的数据,必须是函数式的
return {
name:'vue大学',
address:'北京'
}
}
}
</script>
<!--
设置组件的样式
scoped:让样式在局部生效,防止组件之间的样式相互冲突,否则不加则是全局样式
-->
<style scoped>
</style>
🍒App.vue
<template>
<div>
<School></School>
<hr>
<Student></Student>
</div>
</template>
<script>
import School from './components/School.vue' //import引入组件文件,需要再前面加上./
import Student from './components/Student.vue'
export default {
name:'App',
components:{ //注册组件
School,
Student
}
}
</script>
<style>
</style>
🍓Vue文件的命名规则
- 全小写
schoolinfo
- 每个单词开头大写:
SchoolInfo
-
衔接:school-info
🍓ref属性
- 注意:
ref
是vm
特有的属性,不是指令,所以前面不需要加v-
- 用于给元素或者子组件注册引用信息
- 如果注册的是
HTML
标签,则返回真实DOM
元素 - 如果注册的是
VC
组件,则返回的是组件的vc实例
(这里可以参数实现子传父)
🌷🌷🌷
<template>
<div>
<h3 ref="title">{{title}}</h3>
<School ref="school"></School>
<button ref="btn" type="button" @click="showDOM">点我获取DOM元素</button>
</div>
</template>
<script>
import School from './components/School.vue'
export default {
name:'App',
data(){
return {
title:'学校信息'
}
},
methods:{
showDOM(){
console.log(this.$refs.title)
console.log(this.$refs.school)
console.log(this.$refs.btn)
}
},
components:{
School
}
}
</script>
打印信息:
🍓props属性
-
作用:用于组件动态接收外部传递的参数(可以传对象、数组、函数等等)(父传子)
-
传递参数:
<School studentName="张三" :studentAge="23" schoolName="Vue大学" schoolAddress="北京"> </School>
注意:
- 对于非字符串类型的参数,需要在参数前面加上
:
- 对于值是由
Vue
管理的变量,需要Vue
去解析的,需要在参数前面加上:
- 对于非字符串类型的参数,需要在参数前面加上
-
接收参数:
- 第一种:没有任何限制
props:['studentName','studentAge','schoolName','schoolAddress']
- 第二种:限制参数类型
props:{ studentName:String, studentAge:Number, schoolName:String, schoolAddress:String }
- 第三种:限制参数类型;参数是否必传;参数默认值
props:{ studentName:{ type:String, required:true //是否必传,默认非必传 }, studentAge:{ type:Number, default:18 //设置参数默认值 }, schoolName:{ type:String, required:true }, schoolAddress:{ type:String, required:true } }
- 第一种:没有任何限制
-
备注:
props
与data
的执行顺序是:先执行props
,再执行data
,所以可以将props
的值赋予给data
props
的参数,也是会放到vc实例
上,模板可以直接使用,但是props
参数是只读,不能更改props
参数的值,如果想要更改,可以将其赋予给data
,再对data
做操作- 对于引用类型的数据是可以更改值的,因为引用类型的值发生变化,但是引用类型指定的地址没有发生改变,所以是允许的(但不推荐这样做)
- 参数名:参数名不能是
vue
的保留字,比如key、ref
- 因为可以传递函数,所以如果想要实现子传父,父组件只要把函数传给子组件,子组件通过调用函数传递参数,实现子传父
🌷🌷🌷
Student.vue
<template>
<div>
<h3>学生姓名:{{showName}}</h3>
<h3>学生年龄:{{showAge}}</h3>
</div>
</template>
<script>
export default {
name:'Student',
data(){
return {
showName:this.name,
showAge:this.age
}
},
props:{
name:String,
age:Number
}
}
</script>
School.vue
<template>
<div>
<h3>学校名称:{{name}}</h3>
<h3>学校地址:{{address}}</h3>
<hr>
<Student :name="studentName" :age="studentAge"></Student>
</div>
</template>
<script>
import Student from './Student.vue'
export default {
name:'School',
data(){
return {
name:this.schoolName,
address:this.schoolAddress
}
},
components:{
Student
},
props:{
studentName:String,
studentAge:Number,
schoolName:String,
schoolAddress:String
}
}
</script>
App.vue
<template>
<div>
<h3 ref="title">{{title}}</h3>
<School studentName="张三" :studentAge="23" schoolName="Vue大学" schoolAddress="北京"></School>
</div>
</template>
<script>
import School from './components/School.vue'
export default {
name:'App',
data(){
return {
title:'学校信息'
}
},
components:{
School
}
}
</script>
三个组件的关系:
🍓组件自定义事件
- 绑定自定义事件:
<School propBtn="none" v-on:自定义事件名称="回调函数"></School> <School propBtn="none" @自定义事件名称="回调函数"></School> this.$refs.实例名.$on('自定义事件名称',this.回调函数) //这个绑定的回调函数this是App this.$refs.实例名.$once('自定义事件名称',this.回调函数) //这个绑定的回调函数this是App,事件只触发一次 this.$refs.实例名.$on('自定义事件名称',function(){}) //这个回调函数的this是绑定事件的组件实例 this.$refs.实例名.$on('自定义事件名称',()=>{}) //这个回调箭头函数的this是App(箭头函数没有自己的this,往外找)
- 触发自定义绑定事件:
this.$emit('自定义事件名称',事件参数...)
- 销毁自定义事件
- 销毁该实例下所有绑定的自定义事件
this.$off()
- 销毁指定的一个自定义事件
this.$off('自定义事件名称')
- 销毁指定的多个自定义事件
this.$off(['自定义事件名称1','自定义事件名称2'...])
- 自定义事件与原始事件的区别:
vc或vm实例
被销毁后,原始事件依然有效,自定义事件与实例一起被销毁了- 自定义事件只能绑定
vc组件实例
,原始事件如果想要绑定到vc组件实例
上,需要加上事件修饰符native
@click.native="回调函数"
- 销毁该实例下所有绑定的自定义事件
🌷🌷🌷
School.vue
<template>
<div>
<h3>学校名称:{{name}}</h3>
<h3>学校地址:{{address}}</h3>
<button type="button" @click="sendSchoolNamebyProps(name)" :style="{display: propBtn}">通过props点我向App传输学校名称</button>
<button type="button" @click="sendSchoolNamebycustomEvent(name)" :style="{display: customEventBtn}">通过自定义事件点我向App传输学校名称</button>
<button type="button" @click="destroyCustomEvent">销毁当前School实例的自定义事件</button>
</div>
</template>
<script>
export default {
name:location.href.slice(location.href.lastIndexOf('/')+1,location.href.lastIndexOf('.')),
data(){
return {
name:'vue大学',
address:'北京'
}
},
props:['sendSchoolNamebyProps','propBtn','customEventBtn'],
methods:{
sendSchoolNamebycustomEvent(name){
//触发事件
this.$emit('sendMsg',name)
},
destroyCustomEvent(){
//销毁自定义事件
this.$off()
}
}
}
</script>
App.vue
<template>
<div>
<h3 ref="title">{{title}}</h3>
<h5>通过props属性向App传递参数</h5>
<School :sendSchoolNamebyProps="sendSchoolNamebyProps" customEventBtn="none"></School>
<hr>
<h5>通过ref属性向App传递参数</h5>
<School ref="school" propBtn="none" customEventBtn="none"></School>
<hr>
<h5>通过组件自定义绑定事件向App传递参数(全写)</h5>
<School propBtn="none" v-on:sendMsg="sendSchoolNamebycustomEvent"></School>
<hr>
<h5>通过组件自定义绑定事件向App传递参数(简写)</h5>
<School propBtn="none" @sendMsg="sendSchoolNamebycustomEvent">更</School>
<h5>通过组件自定义绑定事件向App传递参数(与ref属性结合使用,绑定事件更加灵活)</h5>
<School ref="sch" propBtn="none"></School>
<hr>
<h5>通过组件自定义绑定事件向App传递参数(简写2)</h5>
<School propBtn="none" @sendMsg="sendSchoolNamebycustomEvent">更</School>
<h5>通过组件自定义绑定事件向App传递参数(与ref属性结合使用,绑定事件更加灵活)</h5>
<School ref="sch2" propBtn="none"></School>
<hr>
<h5>通过组件自定义绑定事件向App传递参数(简写3)</h5>
<School propBtn="none" @sendMsg="sendSchoolNamebycustomEvent">更</School>
<h5>通过组件自定义绑定事件向App传递参数(与ref属性结合使用,绑定事件更加灵活)</h5>
<School ref="sch3" propBtn="none"></School>
</div>
</template>
<script>
import School from './components/School.vue'
export default {
name:'App',
data(){
return {
title:'学校信息'
}
},
components:{
School
},
methods:{
sendSchoolNamebyProps(name){
console.log('通过props传递的name:' + name)
},
sendSchoolNamebycustomEvent(name){
console.log('通过自定义事件传递的name:' + name)
console.log(this)
}
},
mounted(){
console.log('通过ref传递的name:' + this.$refs.school.name)
//绑定回调函数
setTimeout((item)=>{
this.$refs.sch.$on('sendMsg',this.sendSchoolNamebycustomEvent)
},3000)
this.$refs.sch2.$on('sendMsg',function(){
console.log('通过自定义事件传递的name:' + this.name)
console.log(this)
})
this.$refs.sch3.$on('sendMsg',()=>{
console.log('通过自定义事件传递的name:' + this.name)
console.log(this)
})
}
}
</script>
🍓全局事件总线
- 作用:一般用于两个没有任何关系的组件之间进行通信,因为全局资源是比较紧张的,能不用就不要用,实在没办法就使用它
- 步骤:
- 在
main.js
里面定义全局事件总线beforeCreate(){ Vue.prototype.$bus = this }
- 在需要接收数据的组件里给
$bus
绑定自定义事件和回调函数 - 在发送数据的组件里触发
$bus
的事件 - 在
Vue
实例即将被销毁之前解绑事件,节省资源
- 在
🌷🌷🌷
main.js
(定义全局事件总线$bus
)
import Vue from 'vue'
import App from './App.vue'
Vue.config.productionTip = false
new Vue({
el:'#app',
render:h => h(App),
beforeCreate(){
Vue.prototype.$bus = this
},
})
App.vue
(省略掉部分旁支末节,想要看全部可以去看源码)(定义全局事件)
export default {
name:'App',
methods:{
//创建全局事件总线的回调函数
addTodo(content){
const newTodo = {
id:'005',
content,
isCompleted :false
}
this.todoList.unshift(newTodo)
},
updateTodo(id){
this.todoList.forEach((item)=>{
if(item.id === id){
item.isCompleted = !item.isCompleted
}
})
},
clearTodo(){
if(confirm('是否清除已完成任务?')){
this.todoList = this.todoList.filter((item)=>{
return !item.isCompleted
})
}
},
deleteTodo(id){
if(confirm('是否删除该任务?')){
this.todoList = this.todoList.filter((item)=>{
return item.id != id
})
}
},
allHandle(val){
this.todoList.forEach((item)=>{item.isCompleted=val})
}
},
//绑定全局事件
mounted(){
this.$bus.$on('addTodo',this.addTodo)
this.$bus.$on('clearTodo',this.clearTodo)
this.$bus.$on('allHandle',this.allHandle)
this.$bus.$on('updateTodo',this.updateTodo)
this.$bus.$on('deleteTodo',this.deleteTodo)
},
beforeDestroy(){
//销毁全局事件总线
this.$bus.$off(['addTodo','clearTodo','allHandle','updateTodo','deleteTodo'])
}
}
TodoItem.vue
(与App.vue
没有任何关系,利用全局总线事件向App.vue
传递参数id
)
export default {
name:'TodoItem',
methods:{
updateTodo(id){
this.$bus.$emit('updateTodo',id)
},
deleteTodo(id){
this.$bus.$emit('deleteTodo',id)
}
}
}
🍓消息的订阅与发布
- 作用:实现组件之间的通信
- 步骤:
- 下载依引入
pubsub-js
(第三方插件,与vue
无关)
npm i pubsub-js
- 在需要接收消息的组件订阅消息(注意:订阅消息的回调函数的第一个参数是订阅名称,后面的参数就是你自己定义的回调函数)
this.pid = pubsub.subscribe('订阅名称',回调函数)
- 在发送消息的组件发布消息
pubsub.publish('订阅名称',参数)
- 下载依引入
🌷🌷🌷
App.vue
(订阅消息,创建回调函数,销毁消息)
import pubsub from 'pubsub-js'
export default {
name:'App',
methods:{
//定义回调函数
addTodo(msgName,content){
const newTodo = {
id:'005',
content,
isCompleted :false
}
this.todoList.unshift(newTodo)
},
updateTodo(msgName,id){
this.todoList.forEach((item)=>{
if(item.id === id){
item.isCompleted = !item.isCompleted
}
})
},
clearTodo(msgName){
if(confirm('是否清除已完成任务?')){
this.todoList = this.todoList.filter((item)=>{
return !item.isCompleted
})
}
},
deleteTodo(msgName,id){
if(confirm('是否删除该任务?')){
this.todoList = this.todoList.filter((item)=>{
return item.id != id
})
}
},
allHandle(msgName,val){
this.todoList.forEach((item)=>{item.isCompleted=val})
}
},
mounted(){
//订阅消息
this.addTodoPid = pubsub.subscribe('addTodo',this.addTodo)
this.clearTodoPid = pubsub.subscribe('clearTodo',this.clearTodo)
this.allHandlePid = pubsub.subscribe('allHandle',this.allHandle)
this.updateTodoPid = pubsub.subscribe('updateTodo',this.updateTodo)
this.deleteTodoPid = pubsub.subscribe('deleteTodo',this.deleteTodo)
},
beforeDestroy(){
//销毁
pubsub.unsubscribe(this.addTodoPid)
pubsub.unsubscribe(this.clearTodoPid)
pubsub.unsubscribe(this.allHandlePid)
pubsub.unsubscribe(this.updateTodoPid)
pubsub.unsubscribe(this.deleteTodoPid)
}
}
TodoItem.vue
,(发布消息,注意: 回调函数只能传递一个参数)
import pubsub from 'pubsub-js'
export default {
name:'TodoItem',
data(){
return {
}
},
props:['todoItem'],
methods:{
updateTodo(id){
//发布消息
pubsub.publish('updateTodo',id)
},
deleteTodo(id){
pubsub.publish('deleteTodo',id)
}
}
}
🍓组件之间的参数传递
🍒子传父
props
(需要父先向子传递一个函数)ref
- 组件自定义绑定事件
- 全局事件总线(不推荐,浪费资源)
- 消息的订阅与发布
🍒父传子
props
- 全局事件总线(不推荐,浪费资源)
- 消息的订阅与发布
🍒两个无任何关系的组件
- 全局事件总线
- 消息的订阅与发布
Vuex
🍓mixins属性
- 作用:将组件中的一些公共部分提取出来写到一个
js
文件中复用 - 使用方式:
- 全局引入:
Vue.mixin(xxx)
- 局部引入:
minins:[xxx]
(推荐)
- 全局引入:
- 备注:
如果引入mixins
属性的公共配置有部分配置在组件中已经配置过了,则以组件配置的为主,如果是周期函数,则全部执行,并且minxins
配置的周期函数先执行
🌷🌷🌷
公共部分js
(定义公共方法和生命周期函数)
export const mixin = { //需要使用export暴露这个对象
methods:{
showInfo(){
alert(this.showName)
}
},
mounted(){
console.log('Hello world!')
}
}
组件引入并使用这个公共部分js
import {mixin} from '../assets/mixin.js' //引入
export default {
name:Student,
mixins:[mixin] //使用
}
🍓插件
- 功能:用于增强
Vue
- 本质:包含
install
方法的一个对象,install
的第一个参数是Vue
,第二个以后的参数是插件使用者传递的参数 - 定义插件:
对象.install = function(Vue,options){}
- 使用插件:
Vue.use(插件名,options)
🌷🌷🌷
涉及技术:Vue
过滤器(可以前往本博主下的Vue
入门去看)
定义插件
import dayjs from './dayjs.min.js'
export default {
install(Vue){
Vue.filter('nowTime',function(val,format='YYYY-MM-DD HH:mm:ss'){
return dayjs(val).format(format)
})
}
}
引入插件main.js
import Vue from 'vue'
import App from './App.vue'
import plugins from './assets/plugins.js'
Vue.config.productionTip = false
Vue.use(plugins) //引入插件
new Vue({
el:'#app',
render:h => h(App)
})
使用插件
<template>
<div>
<h3>现在是:{{time | nowTime}}</h3>
</div>
</template>
<script>
export default {
name:'School',
data(){
return {
time:Date.now()
}
}
}
</script>
🍓$set方法
作用:向
Vue
已经管理的对象中添加属性,需要使用this.$set(对象,'属性',值)
,否则不会产生数据代理,则不会引起页面的变化
🌷🌷🌷
添加性别属性
export default {
name:'App',
data(){
return {
student:{
name:"张三",
age:23
}
}
},
methods:{
addSex(){
this.$set(this.student,'sex','男')
}
}
}
🍓$nextTick方法
作用:对于需要对编译挂载后的DOM进行操作,比如input获取焦点,这部分代码需要放到vue属性$nextTick的回调函数中去,否则会因为代码执行顺序时机不对,而不生效
🌷🌷🌷
点击获得焦点的事件函数
editTodo(){
this.$nextTick(function(){
this.$refs.inputTodo.focus()
})
}
🍓动画和过渡
🍒动画
步骤分析:
- 给需要做动画的元素加上条件渲染属性
v-show
- 给需要做动画的元素外面包裹
transition
或者transition-group
标签 - 定义一个
css
动画 - 绑定动画,设置动画执行效果
css
固定类名v[name]-enter-active
:进入v[name]-leave-active
:离开
🌷🌷🌷
<template>
<div>
<h2>动画</h2> <br>
<button type="button" @click="isShow = !isShow">显示/隐藏</button>
<!--step 2-->
<transition appear name="hello">
<!--step 1-->
<h1 v-show="isShow">你好啊!</h1>
</transition>
</div>
</template>
<script>
export default {
name:'App',
data(){
return {
isShow:true
};
}
}
</script>
<style scoped>
h1 {
background-color: orange;
}
/**
* 进入
* step 4-1
*/
.hello-enter-active {
animation: donghua 1s linear;
}
/**
* 离开
* step 4-2
*/
.hello-leave-active {
animation: donghua 1s linear reverse;
}
/**
* step 3
*/
@keyframes donghua {
from {
transform: translateX(-100%);
}
to {
transform:translateX(0)
}
}
</style>
transition
与transition-group
的区别:
transition
下面只能有一个根元素transition-group
下面可以有多个根元素,每个根元素必须有一个唯一的key
- 他们的属性:
1.name
:根据name
的不同,css
选择器也不同,以name-
开头,没有name
则css
选择器以默认v-
开头
2.appear
:是否需要初始化显示,默认不需要
3.enter-active-class
:进入动画类(第三方库使用)
4.leave-active-class
:离开动画类(第三方库使用)
🍒过渡
步骤分析:
- 给需要做动画的元素加上条件渲染属性
v-show
- 给需要做动画的元素外面包裹
transition
或者transition-group
标签 - 定义
css
过渡起点和终点 - 定义
css
过渡方式
css
固定类名v-enter
:进入起点v-enter-to
:进入终点v-leave
:离开起点v-leave-to
:离开终点
🌷🌷🌷
<template>
<div>
<h2>过渡</h2> <br>
<!--step 2-->
<transition-group appear name="hello2">
<!--step 1-->
<h1 v-show="isShow" :key="1">你好啊!</h1>
<h1 v-show="isShow" :key="2">Vue大学!</h1>
</transition-group>
</div>
</template>
<script>
export default {
name:'App',
data(){
return {
isShow:true
};
}
}
</script>
<style scoped>
h1 {
background-color: orange;
}
/**
* 定义过渡效果
* step 4
*/
.hello2-enter-active,.hello2-leave-active {
transition: 1s linear;
}
/**
* 过渡进入起点、过渡离开终点step 1
* step 3
*/
.hello2-enter , .hello2-leave-to{
transform: translateX(-100%);
}
/**
* 过渡进入终点、过渡离开起点
* step 3
*/
.hello2-enter-to ,.hello2-leave{
transform: translateX(0);
}
</style>
🍒动画库animate.css
步骤分析:
animate.css
引入下载:npm install animate.css
animate
官网:https://animate.style/
- 步骤:
- 给需要做动画的元素加上条件渲染属性
v-show
- 给需要做动画的元素外面包裹
transition
或者transition-group
标签 - 引入动画库
import 'animate.css'
- 设置
transition
属性:name="animate__animated animate__bounce" enter-active-class="animate__backInLeft" leave-active-class="animate__backOutRightnpm"
- 给需要做动画的元素加上条件渲染属性
🌷🌷🌷
<template>
<div>
<h2>第三方动画库animate.css</h2> <br>
<!--step 3-2,3-4-->
<transition appear name="animate__animated animate__bounce" enter-active-class="animate__backInLeft" leave-active-class="animate__backOutRight">
<!--step 3-1-->
<h1 v-show="isShow" :key="1">你好啊!</h1>
</transition>
</div>
</template>
<script>
//step 3-3
import 'animate.css'
export default {
name:'App',
data(){
return {
isShow:true
};
}
}
</script>
🍓代理
跨域产生的原因:
违背了三个原则:同协议;同主机;同端口号
跨域的请求发出去了,服务器收到了请求,并且把数据发送到了浏览器上,但是浏览器发现你违背了三个原则,所以没有给你
解决跨域的办法:
cors
(后端人员解决,一劳永逸)
由服务器在请求上加几个响应头jsonp
(需要前后端一起协调才行)- 代理服务器(前端)
配置代理方式一(简易):
- 在
Vue
脚手架的配置文件配置vue.config.js
下配置
js devServer:{ proxy:'http://后端服务器IP:后端服务器端口' }
- 发送请求时,填写代理服务器的ip和端口
不完美的地方:
- 代理服务器接收到请求后,先去本服务器上寻找资源,当本服务器没有时,才去被代理的后端服务器寻找资源(不能灵活的控制请求是否需要走代理)
- 一个前端项目只能配置一个代理
完美配置代理方式二:
devServer:{
proxy:{
'/代理前缀':{
target:'http://后端服务器IP:后端服务器端口',
pathRewrite:{'^/代理前缀':''}, //代理服务器请求真实服务器时将代理前缀去掉
ws:true,//用于支持websocket //默认true
changeOrigin:true //true:告诉服务器我的请求来自于他自己;false:告诉服务器我的请求真实所来自的地方,默认true,用于控制请求头中的host值
}
}
}
🍓插槽
步骤分析:
- 在需要插入的组件里加上
<slot></slot>
告诉Vue
需要把东西插到哪里 - 在引入的组件标签写入你需要插入的东西
🌷🌷🌷
配置需要插槽的组件
<template>
<div class="category">
<h3>{{title}}分类</h3>
<!--step 1-->
<slot></slot>
</div>
</template>
<script>
export default {
name:'Category',
props:['title']
}
</script>
向此插槽插入代码
<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="(itme,index) in games" :key="index">{{itme}}</li>
</ul>
</Category>
<Category title="电影">
<video controls src="http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4"></video>
</Category>
</div>
</template>
<script>
import Category from './components/Category.vue'
export default {
name:'App',
data(){
return {
games:['王者荣耀','斗罗大陆','英雄联盟','剑侠情缘','部落冲突']
}
},
components:{
Category
}
}
</script>
🍒作用域插槽
作用域插槽:
App
向category
传递结构,category
向App
传递数据category
传递数据
<slot :param1="val1" :param2="val2"></slot>
App
接收数据
方式1:
方式2:<template scope="param"> <div>{{param.val1}}</div> <div>{{param.val2}}</div> </template>
结构赋值:(此为ES6
的结构赋值,不知道的童鞋可以去看下ES6
)<template scope="{param1}"> <div>{{param1}}</div> <div>{{param}}</div> </template>
🌷🌷🌷
配置需要插槽的组件
<template>
<div class="category">
<h3>{{title}}分类</h3>
<!--传递参数,可以传递多个-->
<slot :games="games" :msg="msg"></slot>
</div>
</template>
<script>
export default {
name:'Category',
data(){
return {
games:['王者荣耀','斗罗大陆','英雄联盟','剑侠情缘','部落冲突'],
msg:'海岛奇兵'
}
},
props:['title'],
}
</script>
向此插槽插入代码
<template>
<div class="container">
<Category title="游戏">
<!--赋值方式1-->
<template scope="games">
<ul>
<!--与结构赋值的区别:games.games-->
<li v-for="(itme,index) in games.games" :key="index">{{itme}}</li>
</ul>
</template>
</Category>
<Category title="游戏">
<!--赋值方式2:结构赋值-->
<template scope="{games}">
<ol>
<li style="color: red;" v-for="(itme,index) in games" :key="index">{{itme}}</li>
</ol>
</template>
</Category>
<Category title="游戏">
<!--赋值方式2:多个参数结构赋值-->
<template scope="{games,msg}">
<h4 v-for="(item,index) in games" :key="index">{{item}}</h4>
<h4>{{msg}}</h4>
</template>
</Category>
</div>
</template>
<script>
import Category from './components/Category.vue'
export default {
name:'App',
components:{
Category
}
}
</script>
🍓Vuex
Vuex
是专门在Vue
中实现集中式
状态(数据)管理的一个插件
。
Vuex
作用:存放多个组件都要使用的数据,实现数据共享(读写)。
Vuex
的工作原理:
Actions
:·vuex
实例通过调用Dispatch
访问Actions
里面的函数。Actions
所管理的函数都包含一个参数content
(上下文),函数里通过content
调用commits
访问Mutations
里的函数。- 一般会在
Actions
里面发送Ajax
请求,Actions
负责与服务端进行交互。 Actions
里面一般负责做一些业务逻辑判断。
Mutations
:Mutations
里面所管理的函数都包含一个参数state
,是Vuex
的State
。Mutations
负责加工State
里面的数据,如果组件传递来的操作不需要与服务端进行交互,且没有任何复杂的业务逻辑,也可以直接用vuex
实例通过commit
跳过Actions
直接调用Mutations
函数。
State
:- 用于管理共享数据的,相当于
Vue
里的data
,当State
发生了变化,Vuex
会帮你重新渲染使用到State
数据的组件。
- 用于管理共享数据的,相当于
getters
:- 非必须要使用的配置项。
getters
所管理的函数,只有一个参数state
,是一种相当于Vue
里的计算属性的作用。
- 非必须要使用的配置项。
使用步骤:
- 下载
Vuex
插件
npm i vuex
- 创建
/src/store/index.js
,写入以下代码
注意:必须创建import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex) export default new Vuex.Store({ actions:{}, mutations:{}, state:{} })
store
对象之前使用插件,即创建之前执行Vue.use(Vuex)
- 在
main.js
中引入store
,并把store
注入到Vue
实例中去
🌷🌷🌷
index.js
创建vuex
实例store
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
actions:{
oddNUMADD(content,data){
if(content.state.sum % 2){
content.commit('addNum', data)
}
},
rankADD(content,data){
setTimeout(()=>{
content.commit('addNum',data)
},1000)
}
},
mutations:{
addNum(state,data){
state.sum += data
},
cutNUM(state,data){
state.sum -= data
},
},
state:{
sum:0
},
getters:{
bigSUM(state){
return state.sum * 10
}
}
})
main.js
注入store
到vue
import Vue from 'vue'
import App from './App.vue'
import store from './store/index.js'
Vue.config.productionTip = false
const vm = new Vue({
el:'#app',
render:h => h(App),
store,
beforeCreate(){
Vue.prototype.$bus = this
}
})
Count.vue
组件引用和修改共享属性sum
<template>
<div>
<h1>当前求和为:{{sum}}</h1>
<h1>放大十倍的求和为:{{bigSUM}}</h1>
<select v-model.number="selectNUM">
<option value ="1">1</option>
<option value ="2">2</option>
<option value ="3">3</option>
</select>
<button type="button" @click="addNUM">+</button>
<button type="button" @click="cutNUM">-</button>
<button type="button" @click="oddNUMADD">当前求和为奇数再加</button>
<button type="button" @click="rankADD">等一等再加</button>
</div>
</template>
<script>
import {mapState,mapGetters} from 'vuex'
export default {
name:'Count',
data(){
return {
selectNUM:1
}
},
methods:{
addNUM(){
this.$store.commit('addNum',this.selectNUM)
},
cutNUM(){
this.$store.commit('cutNUM',this.selectNUM)
},
oddNUMADD(){
this.$store.dispatch('oddNUMADD',this.selectNUM)
},
rankADD(){
this.$store.dispatch('rankADD',this.selectNUM)
},
},
computed:{
sum(){
return this.$store.state.sum
},
bigSUM(){
return this.$store.getters.bigSUM
}
}
}
</script>
Count.vue
精简版
计算属性的优化:
- 使用
mapState
映射生成获取state
里面的值的计算属性的代码- 使用
mapGetters
映射生成获取getters
里面的值的计算属性的代码- 两种写法:对象写法和数组写法;数组写法要求对象写法的
key
和val
相同
commit
和dispatch
的优化:
commit
的优化:
使用mapMutations
优化dispatch
的优化:
使用mapActions
优化
<template>
<div>
<h1>当前求和为:{{sum}}</h1>
<h1>放大十倍的求和为:{{bigSUM}}</h1>
<select v-model.number="selectNUM">
<option value ="1">1</option>
<option value ="2">2</option>
<option value ="3">3</option>
</select>
<button type="button" @click="addNUM(selectNUM)">+</button>
<button type="button" @click="cutNUM(selectNUM)">-</button>
<button type="button" @click="oddNUMADD(selectNUM)">当前求和为奇数再加</button>
<button type="button" @click="rankADD(selectNUM)">等一等再加</button>
</div>
</template>
<script>
import {mapState,mapGetters,mapMutations,mapActions} from 'vuex'
export default {
name:'Count',
data(){
return {
selectNUM:1
}
},
methods:{
//写法一
// addNUM(){
// this.$store.commit('addNum',this.selectNUM)
// },
// cutNUM(){
// this.$store.commit('cutNUM',this.selectNUM)
// },
//写法二(对象写法)
//...mapMutations({addNUM:'addNUM',cutNUM:'cutNUM'}),
//写法三(数组写法)
...mapMutations(['addNUM','cutNUM']),
//写法一
// oddNUMADD(){
// this.$store.dispatch('oddNUMADD',this.selectNUM)
// },
// rankADD(){
// this.$store.dispatch('rankADD',this.selectNUM)
// },
//写法二(对象写法)
//...mapActions({oddNUMADD:'oddNUMADD',rankADD:'rankADD'})
//写法三(数组写法)
...mapActions(['oddNUMADD','rankADD'])
},
computed:{
//写法一
// sum(){
// return this.$store.state.sum
// },
// bigSUM(){
// return this.$store.getters.bigSUM
// }
//写法二:对象写法
// ...mapState({sum:'sum'}),
// ...mapGetters({bigSUM:'bigSUM'})
//写法三:数组写法
...mapState(['sum']),
...mapGetters(['bigSUM'])
}
}
</script>
🍒Vuex的模块化开发
- 将对应的模块数据拉取出来
//注意:引入state是默认是当前模型自己局部的state,所以不需要加名称空间去指定,如果不是,则需要标注state是属于哪个名称空间的。 const countModel = { namespaced:true, //这边必须写,否则名称空间不能识别 actions:{ }, mutations:{ }, state:{ }, getters:{ } }
- 导入模块到
vuex
import Vue from 'vue' import Vuex from 'vuex' import countModel from './Count.js' Vue.use(Vuex) export default new Vuex.Store({ modules:{ countModel } })
- 获取对应模块的数据
- 原始写法
addNUM(){ this.$store.commit('countModel/addNum',this.selectNUM) } oddNUMADD(){ this.$store.dispatch('countModel/oddNUMADD',this.selectNUM) } sum(){ return this.$store.state.countModel.sum } bigSUM(){ return this.$store.getters['countModel/bigSUM'] }
- 对象
...mapMutations('countModel',{addNUM:'addNUM',cutNUM:'cutNUM'}), ...mapActions('countModel',{oddNUMADD:'oddNUMADD',rankADD:'rankADD'}) ...mapState('countModel',{sum:'sum'}), ...mapGetters('countModel',{bigSUM:'bigSUM'})
- 数组
...mapMutations('countModel',['addNUM','cutNUM']), ...mapActions('countModel',['oddNUMADD','rankADD']), ...mapState('countModel',['sum']), ...mapGetters('countModel',['bigSUM'])
- 原始写法
🍓路由
- 使用步骤:
- 下载
vue-router
npm i vue-router
- 应用插件:
import VueRouter from 'vue-router' Vue.use(VueRouter)
- 编写
router
配置项export default new VueRouter({ routes:[ { path:'/About', component:About } ] })
- 实现切换
<router-link class="list-group-item" active-class="active" to="/About">About</router-link> <router-link class="list-group-item" active-class="active" to="/Home">Home</router-link>
- 指定显示位置
<router-view></router-view>
(最好与router-link
写在同一个组件里面,这样对应关系不会乱套)
- 下载
- 由路由管理的组件叫路由组件,放在
/pages
目录下,其他的组件叫一般组件,放在componenets
目录下 - 被切换掉的组件会被默认销毁,切换过来的组件会重新挂载
- 每个路由组件都有自己的
$route
属性,里面存储着自己的路由信息 - 整个应用只有一个
router
,可以通过路由组件的$router
属性获取到
🌷🌷🌷
main.js
加载路由插件
import Vue from 'vue'
import App from './App.vue'
import VueRouter from 'vue-router'
import router from './router/index.js'
Vue.config.productionTip = false
Vue.use(VueRouter)
const vm = new Vue({
el:'#app',
render:h => h(App),
router,
beforeCreate(){
Vue.prototype.$bus = this
}
})
index.js
配置路由规则
import VueRouter from 'vue-router'
import About from '../pages/About.vue'
import Home from '../pages/Home.vue'
export default new VueRouter({
routes:[
{
path:'/About',
component:About
},
{
path:'/Home',
component:Home
}
]
})
Navigation.vue
切换路由
<template>
<div class="row">
<div class="col-xs-2 col-xs-offset-2">
<div class="list-group">
<!--路由切换-->
<router-link class="list-group-item" active-class="active" to="/About">About</router-link>
<router-link class="list-group-item" active-class="active" to="/Home">Home</router-link>
</div>
</div>
<!--切换后的页面展示-->
<router-view></router-view>
</div>
</template>
<script>
export default {
name:'Navigation',
}
</script>
🍒路由嵌套
index.js
配置多级路由
import VueRouter from 'vue-router'
import About from '../pages/About.vue'
import Home from '../pages/Home.vue'
import Message from '../pages/home/Message.vue'
import News from '../pages/home/News.vue'
export default new VueRouter({
routes:[
{
path:'/about',
component:About
},
{
path:'/home',
component:Home,
children:[ //多级路由
{
path:'message', //注意:子级路由不需要加/
component:Message
},
{
path:'news',
component:News
}
]
}
]
})
实现切换
<!--子级路由跳转需要带上父级路由的完整路径-->
<router-link class="list-group-item" active-class="active" to="/Home/News">News</router-link>
<router-link class="list-group-item" active-class="active" to="/Home/Message">Message</router-link>
🍒路由传参
query
传参:- 写法1(字符串写法)
<router-link :to="`/home/message/detail?id=${item.id}&title=${item.title}`">{{item.title}}</router-link>
- 写法2(对象写法)
<router-link :to="{ path:'/home/message/detail', query:{ id:item.id, title:item.title } }"> {{item.title}} </router-link>
- 写法1(字符串写法)
param
传参:
步骤:- 在路由里面声名参数占位符:
{ name:'messageDetail' path:'detail', component:Detail/:id/:title }
- 传参:
字符串写法:
对象传参:<router-link :to="`/home/message/detail/${item.id}/${item.title}`">{{item.title}}</router-link>
<router-link :to="{ name:'messageDetail', //注意:携带params参数是不允许用path的,只能用name params:{ id:item.id, title:item.title } }"> {{item.title}} </router-link>
- 在路由里面声名参数占位符:
- 接收参数:
$route.query.id $route.query.title
- 接收参数简写:
- 在路由中添加属性
props
://写法一 { path:'detail', component:Detail, props({query}){ return query } } //写法二 { path:'detail', component:Detail, props({params}){ return params } }
- 在目标组件
Detail
使用props
接收对应的参数props:['id','title']
- 在路由中添加属性
🍒路由命名
作用:用于解决路径相对复杂的路由简写形式
步骤:
- 在路由配置里面先命名:
{ name:'homeMessage', path:'message', component:Message }
- 使用对象来标识路由
//原先写法: to="/home/message" //使用路由名称写法: :to="{name:'homeMessage'}"
🍒路由replace和路由push
作用:控制浏览器的浏览记录;默认是追加push
,replace
模式是替换,开启replace
后,无法使用浏览器倒退键,倒退到上一个浏览记录
push
模式:默认模式。可以使用浏览器倒退键,倒退到上一个浏览记录replace
模式:在router-link
添加属性replace
可以把push
转为replace
模式。不可以使用浏览器倒退键,倒退到上一个浏览记录
<router-link replace :to="`/home/message/detail?id=${item.id}&title=${item.title}`">{{item.title}}</router-link>
🍒编程式路由导航
编程式相比router-link
模式来说要更加灵活,可以写一定的业务逻辑。
<template>
<div>
<ul>
<li v-for="item in message" :key="item.id">
<!--router-link模式-->
<router-link :to="`/home/message/detail?id=${item.id}&title=${item.title}`">{{item.title}}</router-link>
<!--编程式-->
<button type="button" @click="push(item.id,item.title)">push</button>
<button type="button" @click="replace(item.id,item.title)">replace</button>
</li>
</ul>
<hr>
<router-view></router-view>
</div>
</template>
<script>
export default {
name:'Message',
data(){
return {
message:[
{
id:'001',
title:'消息001'
},
{
id:'002',
title:'消息002'
},
{
id:'003',
title:'消息003'
}
]
}
},
methods:{
push(id,title){
this.$router.push({
path:'/home/message/detail',
query:{
id,
title
}
})
},
replace(id,title){
this.$router.replace({
path:'/home/message/detail',
query:{
id,
title
}
})
}
}
}
</script>
🍒路由浏览记录前进后退
- 后退:
this.$router.back()
- 前进:
this.$router.forward()
- 前进3格:
this.$router.go(3)
- 后退3格:
this.$router.go(-3)
🍒缓存路由技术
作用:阻止组件在进行路由切换的时候被销毁,用于防止在页面上填写了某些form
表单,在切换后被清理了。
实现方式:
<!--指定单个组件不能被销毁-->
<keep-alive include="组件Name">
<router-view></router-view>
</keep-alive>
<!--指定多个组件不能被销毁-->
<keep-alive :include="['组件Name1','组件Name2']">
<router-view></router-view>
</keep-alive>
<!--所有组件都不会被销毁-->
<keep-alive>
<router-view></router-view>
</keep-alive>
🍒路由组件的生命周期
由路由管理的组件叫路由组件,路由组件有两个独有的生命周期函数
activated
:组件被激活时调用deactivated
:组件销毁时调用
🍒meta属性
路由之中用来存放每个路由组件独有的信息,一般与下面的路由守卫配合使用。
🍒路由守卫
🍅全局路由守卫
全局路由守卫作为全局,写在路由配置文件index.js
中
全局路由守卫分前置路由守卫和后置路由守卫。
-
前置路由守卫:跳转之前调用
- 参数:
to
:目标路由from
:当前路由next
:放行函数
- 实现代码
router.beforeEach((to,from,next)=>{ })
- 参数:
-
后置路由守卫:跳转成功之后调用
- 参数:
to
:目标路由from
:当前路由
- 实现代码
router.afterEach((to,from)=>{ })
- 参数:
🌷🌷🌷
index.js
配合meta
属性设置跳转组件页面的标题和是否需要权限校验。
import VueRouter from 'vue-router'
import About from '../pages/About.vue'
import Home from '../pages/Home.vue'
import Message from '../pages/home/Message.vue'
import News from '../pages/home/News.vue'
import Detail from '../pages/home/message/Detail.vue'
const router = new VueRouter({
routes:[
{
path:'/about',
component:About,
meta:{
title:'关于'
}
},
{
path:'/home',
component:Home,
children:[
{
name:'homeMessage',
path:'message',
component:Message,
children:[
{
path:'detail',
component:Detail,
props({query}){
return query
}
}
],
meta:{ //自定义信息
isAuth:true, //自定义一个属性是否需要鉴权
title:'消息'
}
},
{
name:'homeNews',
path:'news',
component:News,
meta:{ //自定义信息
isAuth:true,
title:'新闻'
}
}
],
meta:{
title:'主页'
}
}
]
})
//全局前置路由守卫
// to:目标路由
// from:当前路由
// next:放行函数
router.beforeEach((to,from,next)=>{
if(to.meta.isAuth){
if(localStorage.getItem('user') === 'admin'){
//满足条件放行
next()
}else{
alert('权限不足')
}
}else{
//满足条件放行
next()
}
})
//后置路由守卫
// to:目标路由
// from:当前路由
router.afterEach((to,from)=>{
document.title = to.meta.title || 'Vue路由守卫'
})
export default router
🍅独享路由守卫
独享路由守卫作为局部,写在路由配置文件index.js
的路由配置规则routes
中。
独享路由守卫没有独享的后置路由守卫,只有独享的前置路由守卫。
代码实现:
{
name:'homeNews',
path:'news',
component:News,
beforeEnter(to,from,next){ //独享路由守卫
next() //放行
}
}
🍅组件内路由守卫
组件内路由守卫作为局部,写在路由组件之中。
代码实现:
//通过路由规则,进入该组件时被调用
beforeRouteEnter(to,from,next){
next()
},
//通过路由规则,离开该组件之前被调用
beforeRouteLeave(to,from,next){
next()
}
🍒路由的工作模式
使用mode
属性指定路由的工作模式:hash
和history
hash
模式:(推荐)
http://localhost:8080/#/home/message
在地址栏后面加#
,#
后面的路径都是hash
值,他们不会发送到浏览器,只在前端内部运行history
模式:
http://localhost:8080/home/message
每一步操作的路由路径都会直接拼在地址栏的后面,这样当你刷新的时候,浏览器就会根据这个路径去请求服务器,而服务器并没有这个路径的静态资源,那就会404