vue_base
# 一、Vue核心
# 1. 初识 Vue
① 要用vue必须拥有一个vue实例且需要传入配置对象
② html中容器需要与一个vue实例绑定且为一 一对应关系,容器中的代码为【vue模板】
<html lang="en">
<head>
<meta charset="UTF-8">
<title>初始vue</title>
// 引入vue
<script src="./js/vue.js"></script>
</head>
<body>
<div id="root">
<h1>hello,{{name}}</h1>
</div>
<script>
Vue.config.productionTip = false;
// 创建vue实例
let vueConf = {
// 绑定对应容器
el: "#root",
data() {
return {
name: "ming"
}
},
};
const vm = new Vue(vueConf);
</script>
</body>
</html>
# 2. 模板语法
html 中包含了一些 JS 语法代码,语法分为两种,分别为:
- 插值语法(双大括号表达式)
- 指令(以 v-开头)
插值语法:
- 功能: 用于解析标签体内容
- 语法: ,xxxx 会作为 js 表达式解析
指令语法:
- 功能: 解析标签属性、解析标签体内容、绑定事件
- 举例:v-bind:href = 'xxxx' ,xxxx 会作为 js 表达式被解析
- 说明:Vue 中有有很多的指令,此处只是用 v-bind 举个例子
<html lang="en">
<head>
<meta charset="UTF-8">
<title>初始vue</title>
<script src="./js/vue.js"></script>
</head>
<body>
<div id="root">
<h1>插值语法:</h1>
<h2>hello,{{name}}</h2>
<hr/>
<h1>指令语法:</h1>
<a :href="url">百度</a>
</div>
<script>
Vue.config.productionTip = false;
// 创建vue实例
let vueConf = {
// 绑定对应容器
el: "#root",
data() {
return {
name: "ming",
url: "https://www.baidu.com"
}
},
};
const vm = new Vue(vueConf);
</script>
</body>
</html>
# 3. 数据代理
<script>
// 被代理对象
let obj = {
name: "ming",
url: "https://www.baidu.com",
};
// 代理对象
let age = 12;
// 数据代理
Object.defineProperty(obj, "age", {
get() {
console.log("obj.age进行读取")
return age;
},
set(value) {
console.log("obj.age进行写入")
age = value;
}
});
</script>
# 4. 事件处理
# 1. 绑定监听
- v-on:xxx="fun"
- @xxx="fun"
- @xxx="fun(参数)"
- 默认事件形参: event
- 隐含属性对象: $event
# 2. 事件修饰符
例:@click.stop.prevent(按顺序执行)
.stop
:等同于JavaScript中的event.stopPropagation()
,防止事件冒泡.prevent
:等同于JavaScript中的event.preventDefault()
,防止执行预设的行为(如果事件可取消,则取消该事件,而不停止事件的进一步传播).capture
:与事件冒泡的方向相反,事件捕获由外到内.self
:只会触发自己范围内的事件,不包含子元素.once
:只会触发一次
# 3. 按键修饰符
在JavaScript事件中除了前面所说的事件,还有键盘事件,也经常需要监测常见的键值。在Vue中允许v-on
在监听键盘事件时添加关键修饰符。记住所有的keyCode
比较困难,所以Vue为最常用的键盘事件提供了别名:
例:@keyup.enter
键盘
:
.enter
:回车键.tab
:制表键.delete
:含delete
和backspace
键.esc
:返回键.space
: 空格键.up
:向上键.down
:向下键.left
:向左键.right
:向右键
鼠标
:
.left
:鼠标左键.middle
:鼠标中间滚轮.right
:鼠标右键
# 5. 属性监听与计算
<html lang="en">
<head>
<meta charset="UTF-8">
<title>天气案例_监视属性</title>
<script src="./js/vue.js"></script>
</head>
<body>
<div id="root">
<h2>现在季节为{{weather}}</h2>
<button @click="changeWeather()">切换季节</button>
</div>
<script>
Vue.config.productionTip = false;
const vm = new Vue({
el: "#root",
data() {
return {
status: true
}
},
// 计算
computed: {
/* weather: {
// 调用weather或者用来计算的属性status变化时执行
get() {
console.log("weather被使用了")
return this.status ? '春天' : '冬天';
},
set(value) {
console.log("weather被修改了")
this.status = value;
}
}, */
// 简写不包含set()
weather() {
return this.status ? '春天' : '冬天';
}
},
// 监听
watch: {
weather: {
// 是否马上执行
//immediate: true,
// 开启深度监视,VUE在watch中默认不监视对象里面的值的改变
//deep: true,
handler(newValue, oldValue) {
console.log("季节变化了", oldValue + "-->" + newValue)
}
}
},
methods: {
changeWeather() {
this.status = !this.status;
}
},
})
</script>
</body>
</html>
# 6. 数据监视原理
vue会监视data中所有层次的数据。
如何监测对象中的数据? 通过setter实现监视,且要在new Vue时就传入要监测的数据。 (1).对象中后追加的属性,Vue默认不做响应式处理 (2).如需给后添加的属性做响应式,请使用如下API: Vue.set(target,propertyName/index,value) 或 vm.$set(target,propertyName/index,value)
如何监测数组中的数据? 通过包裹数组更新元素的方法实现,本质就是做了两件事: (1). 调用原生对应的方法对数组进行更新。 (2). 重新解析模板,进而更新页面。
在Vue修改数组中的某个元素一定要用如下方法: (1). 使用这些API:push()、pop()、shift()、unshift()、splice()、sort()、reverse() (2). Vue.set() 或 vm.$set()
<script>
let data = {
name: 'a',
age: 1,
person: {
name: "chs"
}
}
// 用来监视data属性变化
function Observer(obj) {
// 获取data中所有属性
const keys = Object.keys(obj);
keys.forEach(key => {
// 属性值
let val = data[key];
Object.defineProperty(this, key, {
get() {
console.log(`data中${key}被读取了,值为${val}`)
return val;
},
set(newVal) {
console.log(`data中${key}被修改了从${val}-->${newVal}`)
val = newVal;
}
})
})
}
const obj = new Observer(data);
let vm = {};
vm._data = data = obj;
</script>
# 7. 列表渲染
# 1. 列表显示
v-for:
用于展示列表数据
- 语法:v-for="(item, index) in xxx" :key="yyy"
- 可遍历:数组、对象、字符串(用的很少)、指定次数(用的很少)
- 数组: (item, index)
- 对象: (value, key)
- 字符串:(char, index)
- 数字:(number, index)
<html lang="en">
<head>
<meta charset="UTF-8">
<title>基本列表</title>
<script src="./js/vue.js"></script>
<style>
* {
margin-top: 20px;
}
</style>
</head>
<body>
<div id="root">
<input type="text" name="" v-model="keyWords">
<button @click="sortType = 0">原顺序</button>
<button @click="sortType = 1">年龄升序</button>
<button @click="sortType = 2">年龄降序</button>
<ul>
<li v-for="(item, index) in filPerson" :key="item.id">
{{item.name}} - {{item.age}}
</li>
</ul>
</div>
<script>
Vue.config.productionTip = false;
const vm = new Vue({
el: "#root",
data() {
return {
keyWords: "",
// 排序类型 0:原顺序,1:升序,2:降序
sortType: 0,
person: [
{ id: "01", name: "张三", age: 31 },
{ id: "02", name: "王二", age: 22 },
{ id: "03", name: "李二", age: 13 },
{ id: "04", name: "张飞", age: 45 },
{ id: "05", name: "许飞", age: 23 },
]
}
},
computed: {
filPerson() {
// 人员过滤
const arr = this.person.filter((item) => {
return item.name.indexOf(this.keyWords) !== -1;
})
// 年龄排序
if (this.sortType) {
arr.sort((a, b) => {
return this.sortType === 1 ? a.age - b.age : b.age - a.age;
})
}
return arr;
}
},
})
</script>
</body>
</html>
# 2. key原理
虚拟DOM中key的作用: key是虚拟DOM对象的标识,当数据发生变化时,Vue会根据【新数据】生成【新的虚拟DOM】, 随后Vue进行【新虚拟DOM】与【旧虚拟DOM】的差异比较,比较规则如下:
对比规则: (1). 旧虚拟DOM中找到了与新虚拟DOM相同的key: ①若虚拟DOM中内容没变, 直接使用之前的真实DOM ②若虚拟DOM中内容变了, 则生成新的真实DOM,随后替换掉页面中之前的真实DOM
(2). 旧虚拟DOM中未找到与新虚拟DOM相同的key创建新的真实DOM,随后渲染到到页面。
用index作为key可能会引发的问题: (1). 若对数据进行:逆序添加、逆序删除等破坏顺序操作: 会产生没有必要的真实DOM更新 ==> 界面效果没问题, 但效率低 (2). 如果结构中还包含输入类的DOM: 会产生错误DOM更新 ==>界面有问题
开发中如何选择key: (1). 最好使用每条数据的唯一标识作为key, 比如id、手机号、身份证号、学号等唯一值。 (2). 如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表用于展示,使用index作为key是没有问题的。
# 8. Vue指令
# 1.常用内置指令
v-text
: 更新元素的 textContentv-html
: 更新元素的 innerHTMLv-if
: 如果为true, 当前标签才会输出到页面v-else
: 如果为false, 当前标签才会输出到页面v-show
: 通过控制display样式来控制显示/隐藏v-for
: 遍历数组/对象v-on
: 绑定事件监听, 一般简写为@v-bind
: 强制绑定解析表达式, 可以省略v-bindv-model
: 双向数据绑定v-text
: 更新元素的 textContent 作用:向其所在的节点中渲染文本内容。 与插值语法的区别:v-text会替换掉节点中的内容,则不会
v-html
: 更新元素的 innerHTML 作用:向指定节点中渲染包含html结构的内容。 与插值语法的区别: (1). v-html会替换掉节点中所有的内容,则不会。 (2). v-html可以识别html结构。 严重注意:v-html有安全性问题!!!! (1). 在网站上动态渲染任意HTML是非常危险的,容易导致XSS攻击。 (2). 一定要在可信的内容上使用v-html,永不要用在用户提交的内容上!**
v-once
😗*所在节点在初次动态渲染后,就视为静态内容了。以后数据的改变不会引起v-once所在结构的更新,可以用于优化性能。**
v-pre
:**跳过其所在节点的编译过程。ref
: 为某个元素注册一个唯一标识, vue对象通过$refs属性访问这个元素对象v-cloak
: 使用它防止闪现表达式, 与css配合: [v-cloak] { display: none } 本质是一个特殊属性,Vue实例创建完毕并接管容器后,会删掉v-cloak属性。 使用css配合v-cloak可以解决网速慢时页面展示出的问题
# 2. 自定义指令
自定义指令directives中的this为windows并不是Vue
<script>
// 局部指令
new Vue({
el: '#root',
data: {},
directives: {
// 指令名: v-fbind 如果指令名有多个单词则使用'-'连接不能使用驼峰形式 例:fbind-text-or-number
'fbind': {
// 指令与元素成功绑定时调用
bind(element, binding) {
element.value = binding.value
},
// 指令所在元素被插入页面时调用
inserted(element, binding) {
element.focus()
},
// 指令所在的模板被重新解析时调用
update(element, binding) {
element.value = binding.value
}
}
}
})
// 全局指令
Vue.directive('fbind', {
// 指令与元素成功绑定时
bind(element, binding) {
element.value = binding.value
},
// 指令所在元素被插入页面时
inserted(element, binding) {
element.focus()
},
// 指令所在的模板被重新解析时
update(element, binding) {
element.value = binding.value
}
})
</script>
# 9. 生命周期
# 1. 定义
- 生命周期回调函数、生命周期函数、生命周期钩子。
- Vue在关键时刻帮我们调用的一些特殊名称的函数。
- 生命周期函数的名字不可更改,但函数的具体内容是程序员根据需求编写的。
- 生命周期函数中的this指向是vm 或 组件实例对象。
# 2. 周期类别
**beforeCreate
😗*初始化数据监测和数据代理之前调用
此时data中的数据和methods中的方法不能访问
**created
😗*初始化数据监测和数据代理之后调用
此时data中的数据和methods中的方法可以访问,在created后beforeMount之前会开始解析Vue模板生成虚拟DOM
**beforeMount
😗*挂载完成之前
此时Vue模板已经被解析且生成了虚拟DOM所以此时修改DOM是无效的
**mounted
😗*挂载完成后
Vue完成模板解析并把初始的真实DOM中的元素放入页面后(挂载完毕)调用mounted()
**beforeUpdate
😗*页面更新之前
此时model已经被更新了但是view还没有更新,在更新前到更新后之间会生成新的虚拟DOM然后与旧的DOM进行diff并对view进行渲染
**updated
😗*页面更新后
此时页面与数据都更新完成
beforeDestroy
: 销毁之前
**destroyed
😗*销毁后
**activated
😗*路由组件被激活时触发
**deactivated
😗*路由组件失活时触发
# 3. 执行情况
- 在页面一加载就会调用beforeCreate --> created --> beforeMount --> mounted此时会完成第一次的模板解析并进行view的渲染。
- 此后每更新一次data都会调用beforeCreate和created完成model对view的更新

# 二、Vue组件化编程
# 1. 非单文件组件
步骤:
- 定义组件(创建组件)
- 注册组件
- 使用组件(写组件标签)
组件命名:
- kebab-case命名: my-school
- CamelCase命名: MySchool
# 1. 组件创建
<html>
<head>
<meta charset="UTF-8" />
<title>组件使用</title>
<script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
<!-- 准备好一个容器-->
<div id="root">
<hello></hello>
<hr>
<h1>{{msg}}</h1>
<hr>
<!-- 第三步:编写组件标签 -->
<school></school>
<hr>
<!-- 第三步:编写组件标签 -->
<student></student>
</div>
<div id="root2">
<hello></hello>
</div>
</body>
<script type="text/javascript">
Vue.config.productionTip = false
// 创建school组件 Vue.extend({})与new Vue({})里面配置对象基本类似
const school = Vue.extend({
// 不需要绑定el:"",因为组件可能要为多个容器服务
template: `
<div class="demo">
<h2>学校名称:{{schoolName}}</h2>
<h2>学校地址:{{address}}</h2>
<button @click="showName">点我提示学校名</button>
</div>
`,
// 数据对象必须为函数返回的对象,防止不同容器数据对象指向同一地址
data() {
return {
schoolName: '尚硅谷',
address: '北京昌平'
}
},
methods: {
showName() {
alert(this.schoolName)
}
},
})
const student = Vue.extend({
template: `
<div>
<h2>学生姓名:{{studentName}}</h2>
<h2>学生年龄:{{age}}</h2>
</div>
`,
data() {
return {
studentName: '张三',
age: 18
}
}
})
// 创建hello组件
const hello = Vue.extend({
template: `
<div>
<h2>你好啊!{{name}}</h2>
</div>
`,
data() {
return {
name: 'Tom'
}
}
})
//创建vm
new Vue({
el: '#root',
data: {
msg: '你好啊!'
},
// 局部注册组件
components: {
school,
student,
hello
}
})
// 全局注册组件
Vue.component('hello', hello)
new Vue({
el: '#root2',
})
</script>
</html>
# 2.组件嵌套
<script type="text/javascript">
Vue.config.productionTip = false
// 1.创建student组件作为子组件
const student = Vue.extend({
template: `
<div>
<h2>学生姓名:{{studentName}}</h2>
<h2>学生年龄:{{age}}</h2>
</div>
`,
data() {
return {
studentName: '小明',
age: 18
}
}
})
// 2.创建school组件作为父组件
const school = Vue.extend({
template: `
<div>
<h2>学校名称:{{schoolName}}</h2>
<h2>学校地址:{{address}}</h2>
<student></student>
</div>
`,
data() {
return {
schoolName: '希望小学',
address: '中国'
}
},
// 组件中嵌套子组件
components: {
student
}
})
// 创建hello组件
const hello = Vue.extend({
template: `
<div>
<h2>你好啊!{{name}}</h2>
</div>
`,
data() {
return {
name: '小明'
}
}
})
// 3.创建一个app组件来管理所有组件
const app = Vue.extend({
template: `
<div>
<school></school>
<hello></hello>
</div>
`,
components: {
school,
hello
}
})
new Vue({
template:`<app></app>`,
el: '#root',
data: {
msg: '你好啊!'
},
// 注册组件(局部注册)
components: {
app
}
})
</script>
# 3. 组件本质
- 组件实质是一个VueComponent构造函数
- 使用Vue.extend(options)创建组件时Vue会定义一个VueComponent并返回,所以每个VueComponent都是不同的
- Vue解析模板使用组件时会new VueComponent(options)
Vue.extend = function (extendOptions) {
......
var Sub = function VueComponent (options) {
this._init(options);
};
......
return Sub
};
}
# 4. 内置关系
**prototype
: ** 显式原型对象存在于类中
__proto__
: 隐式原型对象存在于对象中
每个对象都有一个原型对象,通过函数创建的对象也将拥有这个原型对象。原型是一个指向对象的指针。
- 可以将原型理解为对象的父亲,对象从原型对象继承来属性
- 所有函数的原型默认是
Object
的实例,所以这是可以使用toString/toValues/isPrototypeOf
等方法的原因 - 使用原型对象为多个对象共享属性或方法
- 如果对象本身不存在属性或方法将到原型上查找
- 使用原型可以解决,通过构造函数创建对象时复制多个函数造成的内存占用问题
- 原型包含
constructor
属性,指向构造函数 - 对象包含
__proto__
指向他的原型对象
# 2. 单文件组件
以.vue后缀结尾的文件就是一个组件
School.vue
<template>
<div>
<h2>学校名:{{ name }}</h2>
<h2>学校地址:{{ address }}</h2>
<button @click="flag = !flag">点击修改地址</button>
<br>
<input v-show="flag" @keyup.enter="hideText()" v-model="address" type="text">
<hr>
<student></student>
</div>
</template>
<script>
import student from "./Student.vue"
export default {
name: "school",
components: {
student,
},
data() {
return {
name: "希望小学",
address: "中国",
flag: false,
};
},
methods: {
hideText() {
this.flag = !this.flag;
},
},
};
</script>
Student.vue
<template>
<div>
<h2>学生姓名:{{ name }}</h2>
<h2>学生年龄:{{ age }}</h2>
</div>
</template>
<script>
export default {
name: "student",
data() {
//这里存放数据
return {
name: "小明",
age: 13,
};
},
};
</script>
App.vue: 管理所有组件
<template>
<div>
<school></school>
</div>
</template>
<script>
import school from "./School.vue";
export default {
name: "app",
components: {
school,
},
};
</script>
main.js
import app from './App.vue'
new Vue({
el: '#root',
component: {app},
})
# 3. 相关小知识
# 1. ref标签属性
- 被用来给元素或子组件注册引用信息(id的替代者)
- 应用在html标签上获取的是真实DOM元素,应用在组件标签上是组件实例对象
- 使用方式:
打标识:
.....
或获取:this.$refs.xxx
# 2. props配置项
作用:接受父组件传过来的数据
说明: props中的数据是只读的不能进行修改,如果需要修改可以用data中的属性来接收props中需要修改的属性
<!-- 传递数据 -->
<school name="aa" address="bb"></school>
<script>
// 第一种:简单接收
props: ['name','address']
// 第二种:限制类型
props: {
name:String,
address: String
}
// 第三种:限制类型,必要性,默认值
props: {
name: {
type: String,
required: true,
},
address: {
type: String,
default: "xxx",
}
}
</script>
# 3. mixin配置项
作用:将多个组件共用配置提取成一个混入对象
// 1.定义混入(外部定义一个x.js)
default const mx = {
data() {
return {
xxx:xxx
}
},
...
}
// 2.引入混入
// (1)局部引入
import {mx} from 'xxx/x.js '
// 使用
mixins:[mx]
// (2)全局引入
Vue.mixin(mx)
# 4. 插件
- 功能:用于增强Vue
- 本质:包含install方法的一个对象,install的第一个参数是Vue,后面的参数是插件使用者传递的数据
- 使用:
Vue.use(pl,args...)
const pl = {
install(Vue,args...) {
....
}
}
# 5. scoped-样式
作用:让样式在局部生效,防止冲突。
<style scoped>
</style>
# 6. nextTick
作用:在下一次DOM更新结束后执行指定回调
说明:当数据改变后想要基于更新后的DOM进行操作时使用
语法:this.$nextTick(回调)
# 7. slot-插槽
作用:接受父组件传过来的HTML结构
分类:默认插槽、具名插槽、作用域插槽
使用:
默认插槽
<!-- 父组件 --> <template> <Search> <div>xxx</div> </Search> </template> <!-- 子组件 --> <template> <div> <slot>默认内容</slot> </div> </template>
具名插槽
<!-- 父组件 --> <template> <Search> <div slot="center">xxx</div> <template slot="footer"> <div>xxx</div> </template> </Search> </template> <!-- 子组件 --> <template> <div> <slot name="center">默认内容</slot> <slot name="footer">默认内容</slot> </div> </template>
作用域插槽
<!-- 父组件 --> <template> <Search> <div slot="center" slot-scope="data">{{data.msg}}</div> <template slot="footer"> <div>xxx</div> </template> </Search> </template> <!-- 子组件 --> <template> <div> <slot name="center" msg="向父组件传递的数据">默认内容</slot> <slot name="footer">默认内容</slot> </div> </template>
# 4. 自定义事件
<!-- 方式一 通过v-on绑定一个custom事件触发deleteName方法 -->
<School @custom="deleteName"></School>
<!-- 方式二 通过this.$refs.xxx.$on -->
<School ref="st"></School>
mounted() {
this.$refs.st.$on('custom', this.deleteName)
}
<!-- 子组件中触发事件 -->
this.$emit('custom', studentName)
局限:
- 此方式只用于子组件向父组件发送消息(数据)
- 问题: 隔代组件或兄弟组件间通信此种方式不合适
# 5. 全局事件总线
new Vue({
......
beforeCreate() {
// 安装全局事件总线,$bus就是当前应用的vm
Vue.prototype.$bus = this
},
......
})
使用:
mounted() {
// 父组件在总线上绑定事件
this.$bus.$on('xxxx',this.demo)
}
// 子组件触发事件
methods: {
xx() {
this.$bus.$emit('xxxx',param)
}
},
beforeDestroy() {
// 组件销毁时用$off解绑当前组件所用到的事件。
this.$bus.$off("xxxx")
}
# 三、Vue脚手架使用
npm yarn
npm init yarn init // 初始化
npm i | install yarn (install) // 安装依赖包
npm i x --S | --save yarn add x // 安装生产依赖并保存包名
npm i x --D | --save-dev yarn add x -D // 安装开发依赖并保存包名
npm un | uninstall x yarn remove // 删除依赖包
npm i -g | npm -g i x yarn global add x // 全局安装
npm un -g x yarn global remove x // 全局下载
npm run dev yarn dev | run dev // 运行命令
# 1. 安装
npm install -g @vue/cli
# OR
yarn global add @vue/cli
检查其版本是否正确:
vue --version
# 2. 创建一个项目
vue create hello-world
帮助命令
vue create --help
-p, --preset <presetName> 忽略提示符并使用已保存的或远程的预设选项
-d, --default 忽略提示符并使用默认预设选项
-i, --inlinePreset <json> 忽略提示符并使用内联的 JSON 字符串预设选项
-m, --packageManager <command> 在安装依赖时使用指定的 npm 客户端
-r, --registry <url> 在安装依赖时使用指定的 npm registry
-g, --git [message] 强制 / 跳过 git 初始化,并可选的指定初始化提交信息
-n, --no-git 跳过 git 初始化
-f, --force 覆写目标目录可能存在的配置
-c, --clone 使用 git clone 获取远程预设选项
-x, --proxy 使用指定的代理创建项目
-b, --bare 创建项目时省略默认组件中的新手指导信息
-h, --help 输出使用帮助信息
使用图形化界面
你也可以通过 vue ui
命令以图形化界面创建和管理项目:
vue ui
上述命令会打开一个浏览器窗口,并以图形化界面将你引导至项目创建的流程。
# 3. 组件化编码流程
(1).拆分静态组件:组件要按照功能点拆分,命名不要与html元素冲突。
(2).实现动态组件:考虑好数据的存放位置,数据是一个组件在用,还是一些组件在用:
1).一个组件在用:放在组件自身即可。
2). 一些组件在用:放在他们共同的父组件上(状态提升)。
(3).实现交互:从绑定事件开始。
props适用于: (1).父组件 ==> 子组件 通信
(2).子组件 ==> 父组件 通信(要求父先给子一个函数)
使用v-model时要切记:v-model绑定的值不能是props传过来的值,因为props是不可以修改的!
props传过来的若是对象类型的值,修改对象中的属性时Vue不会报错,但不推荐这样做。
# 4. 配置代理
vue.config.js中配置代理规则:
// 这会告诉开发服务器将任何未知请求 (没有匹配到静态文件的请求) 代理到`http://localhost:4000`。
module.exports = {
devServer: {
proxy: 'http://localhost:4000'
}
}
- 优点:配置简单
- 缺点:不能配置多个代理以及是否走代理
module.exports = {
devServer: {
proxy: {
'/api': {
target: 'http://localhost:4000',
// 是否启用websocket
ws: true,
changeOrigin: true,
// 匹配正则的路径替换为''
pathRewrite: {'^api': ''}
},
'/foo': {
target: 'http://localhost:5000'
}
}
}
}
# 四、Vuex
概念:在Vue中实现集中式状态(数据)管理的一个插件,对多个组件的共享数据进行集中式管理,是一种组件间通信的方式
状态自管理应用包含以下几个部分:
- state,驱动应用的数据源;
- view,以声明方式将 state 映射到视图;
- actions,响应在 view 上的用户输入导致的状态变化。
“单向数据流”理念的简单示意:

多个组件共享状态时,单向数据流的简洁性很容易被破坏:
多个组件依赖于同一数据
不同组件的行为需要改变同一数据
引入:
./store/index.js
/**
* 创建Vuex中的核心store
*/
import Vue from 'vue';
import Vuex from 'vuex';
// 使用插件
Vue.use(Vuex);
// 响应组件中的动作
const actions = {};
// 加工数据(state)
const mutations = {};
// 存储数据
const state = {};
// 当state中的数据需要经过加工后再使用时,可以使用getters加工。
const getters = {};
export default new Vuex.Store({
actions,
mutations,
state,
getters
})
main.js
import Vue from 'vue'
import App from './App'
import store from './store/index'
Vue.config.productionTip = false
new Vue({
render: h => h(App),
store,
}).$mount('#app')
使用:
methods: {
increment() {
// 分发 Action
this.store.dispatch('increment',args)
// 直接处理 this.$store.commit('increment')
console.log(this.$store.state.count)
}
}
const store = new Vuex.Store({
state: {
count: 0
},
// mutation 必须是同步函数
mutations: {
increment (state,value) {
state.count++
}
},
actions: {
// context为上下文相当于mini的store
increment (context,value) {
// 提交载荷(Payload)
context.commit('increment')
}
}
})
# 1. 四个map方法的使用
mapState方法:用于帮助我们映射
state
中的数据为计算属性computed: { //借助mapState生成计算属性:sum、school、subject(对象写法) ...mapState({sum:'sum',school:'school',subject:'subject'}), //借助mapState生成计算属性:sum、school、subject(数组写法) ...mapState(['sum','school','subject']), },
mapGetters方法:用于帮助我们映射
getters
中的数据为计算属性computed: { //借助mapGetters生成计算属性:bigSum(对象写法) ...mapGetters({bigSum:'bigSum'}), //借助mapGetters生成计算属性:bigSum(数组写法) ...mapGetters(['bigSum']) },
mapActions方法:用于帮助我们生成与
actions
对话的方法,即:包含$store.dispatch(xxx)
的函数methods:{ //靠mapActions生成:incrementOdd、incrementWait(对象形式) ...mapActions({incrementOdd:'jiaOdd',incrementWait:'jiaWait'}) //靠mapActions生成:incrementOdd、incrementWait(数组形式) ...mapActions(['jiaOdd','jiaWait']) }
mapMutations方法:用于帮助我们生成与
mutations
对话的方法,即:包含$store.commit(xxx)
的函数methods:{ //靠mapActions生成:increment、decrement(对象形式) ...mapMutations({increment:'JIA',decrement:'JIAN'}), //靠mapMutations生成:JIA、JIAN(对象形式) ...mapMutations(['JIA','JIAN']), }
备注:mapActions与mapMutations使用时,若需要传递参数需要:在模板中绑定事件时传递好参数,否则参数是事件对象。
# 2. 模块化+命名空间
目的:让代码更好维护,让多种数据分类更加明确。
修改
store.js
const countAbout = { namespaced:true,//开启命名空间 state:{x:1}, mutations: { ... }, actions: { ... }, getters: { bigSum(state){ return state.sum * 10 } } } const personAbout = { namespaced:true,//开启命名空间 state:{ ... }, mutations: { ... }, actions: { ... } } const store = new Vuex.Store({ modules: { countAbout, personAbout } })
开启命名空间后,组件中读取state数据:
//方式一:自己直接读取 this.$store.state.personAbout.list //方式二:借助mapState读取: ...mapState('countAbout',['sum','school','subject']),
开启命名空间后,组件中读取getters数据:
//方式一:自己直接读取 this.$store.getters['personAbout/firstPersonName'] //方式二:借助mapGetters读取: ...mapGetters('countAbout',['bigSum'])
开启命名空间后,组件中调用dispatch
//方式一:自己直接dispatch this.$store.dispatch('personAbout/addPersonWang',person) //方式二:借助mapActions: ...mapActions('countAbout',{incrementOdd:'jiaOdd',incrementWait:'jiaWait'})
开启命名空间后,组件中调用commit
//方式一:自己直接commit this.$store.commit('personAbout/ADD_PERSON',person) //方式二:借助mapMutations: ...mapMutations('countAbout',{increment:'JIA',decrement:'JIAN'}),
# 五、Vue Router
# 1. 基本使用
安装vue-router,命令:
yarn add vue-router
应用插件:
Vue.use(VueRouter)
编写router配置项:
// 引入VueRouter import VueRouter from 'vue-router' // 引入组件 import About from '../components/About' import Home from '../components/Home' // 创建router实例对象,去管理一组一组的路由规则 const router = new VueRouter({ routes:[ { path:'/about', component:About }, { path:'/home', component:Home } ] }) // 暴露router export default router
实现切换(active-class可配置高亮样式)
<router-link active-class="active" to="/about">About</router-link>
指定展示位置
<router-view></router-view>
# 2. 几个注意点
- 路由组件通常存放在
pages
文件夹,一般组件通常存放在components
文件夹。 - 通过切换,“隐藏”了的路由组件,默认是被销毁掉的,需要的时候再去挂载。
- 每个组件都有自己的
$route
属性,里面存储着自己的路由信息。 - 整个应用只有一个router,可以通过组件的
$router
属性获取到。
# 3. 嵌套路由
配置路由规则,使用children配置项:
routes:[ { path:'/about', component:About, }, { path:'/home', component:Home, children:[ //通过children配置子级路由 { path:'news', //此处一定不要写:/news component:News }, { path:'message',//此处一定不要写:/message component:Message } ] } ]
跳转(要写完整路径):
<router-link to="/home/news">News</router-link>
# 4. 路由的query参数
传递参数
<!-- 跳转并携带query参数,to的字符串写法 --> <router-link :to="/home/message/detail?id=666&title=你好">跳转</router-link> <!-- 跳转并携带query参数,to的对象写法 --> <router-link :to="{ path:'/home/message/detail', query:{ id:666, title:'你好' } }" >跳转</router-link>
接收参数:
$route.query.id $route.query.title
# 5. 命名路由
作用:可以简化路由的跳转。
如何使用
给路由命名:
{ path:'/demo', component:Demo, children:[ { path:'test', component:Test, children:[ { name:'hello' //给路由命名 path:'welcome', component:Hello, } ] } ] }
简化跳转:
<!-- 简化前,需要写完整的路径 --> <router-link to="/demo/test/welcome">跳转</router-link> <!-- 简化后,直接通过名字跳转 --> <router-link :to="{name:'hello'}">跳转</router-link> <!-- 简化写法配合传递参数 --> <router-link :to="{ name:'hello', query:{ id:666, title:'你好' } }" >跳转</router-link>
# 6. 路由的params参数
配置路由,声明接收params参数
{ path:'/home', component:Home, children:[ { path:'news', component:News }, { component:Message, children:[ { name:'xiangqing', path:'detail/:id/:title', //使用占位符声明接收params参数 component:Detail } ] } ] }
传递参数
<!-- 跳转并携带params参数,to的字符串写法 --> <router-link :to="/home/message/detail/666/你好">跳转</router-link> <!-- 跳转并携带params参数,to的对象写法 --> <router-link :to="{ name:'xiangqing', params:{ id:666, title:'你好' } }" >跳转</router-link>
特别注意:路由携带params参数时,若使用to的对象写法,则不能使用path配置项,必须使用name配置!
接收参数:
$route.params.id $route.params.title
# 7.路由的props配置
作用:让路由组件更方便的收到参数
{
name:'xiangqing',
path:'detail/:id',
component:Detail,
// 第一种写法:props值为对象,该对象中所有的key-value的组合最终都会通过props传给Detail组件
// props:{a:900}
// 第二种写法:props值为布尔值,布尔值为true,则把路由收到的所有params参数通过props传给Detail组件
// props:true
// 第三种写法:props值为函数,该函数返回的对象中每一组key-value都会通过props传给Detail组件
props(route){
return {
id:route.query.id,
title:route.query.title
}
}
}
# 8. replace属性
- 作用:控制路由跳转时操作浏览器历史记录的模式
- 浏览器的历史记录有两种写入方式:分别为
push
和replace
,push
是追加历史记录,replace
是替换当前记录。路由跳转时候默认为push
- 如何开启
replace
模式:<router-link replace .......>News</router-link>
# 9. 编程式路由导航
作用:不借助
<router-link>
实现路由跳转,让路由跳转更加灵活具体编码:
//$router的两个API this.$router.push({ name:'xiangqing', params:{ id:xxx, title:xxx } }) this.$router.replace({ name:'xiangqing', params:{ id:xxx, title:xxx } }) this.$router.forward() //前进 this.$router.back() //后退 this.$router.go() //可前进也可后退
# 10. 缓存路由组件
作用:让不展示的路由组件保持挂载,不被销毁。
具体编码:
<keep-alive include="News"> <router-view></router-view> </keep-alive>
# 11. 路由守卫
作用:对路由进行权限控制
分类:全局守卫、独享守卫、组件内守卫
完整的导航解析流程:
- 导航被触发。
- 在失活的组件里调用
beforeRouteLeave
守卫。 - 调用全局的
beforeEach
守卫。 - 在重用的组件里调用
beforeRouteUpdate
守卫 (2.2+)。 - 在路由配置里调用
beforeEnter
。 - 解析异步路由组件。
- 在被激活的组件里调用
beforeRouteEnter
。 - 调用全局的
beforeResolve
守卫 (2.5+)。 - 导航被确认。
- 调用全局的
afterEach
钩子。 - 触发 DOM 更新。
- 调用
beforeRouteEnter
守卫中传给next
的回调函数,创建好的组件实例会作为回调函数的参数传入。
# 全局守卫
每个守卫方法接收三个参数:
to: Route
: 即将要进入的目标 路由对象from: Route
: 当前导航正要离开的路由next: Function
: 一定要调用该方法来 resolve 这个钩子。执行效果依赖next
方法的调用参数next()
: 进行管道中的下一个钩子。如果全部钩子执行完了,则导航的状态就是 confirmed (确认的)。next(false)
: 中断当前的导航。如果浏览器的 URL 改变了 (可能是用户手动或者浏览器后退按钮),那么 URL 地址会重置到from
路由对应的地址。next('/')
或者next({ path: '/' })
: 跳转到一个不同的地址。当前的导航被中断,然后进行一个新的导航。你可以向next
传递任意位置对象,且允许设置诸如replace: true
、name: 'home'
之类的选项以及任何用在router-link
的to
prop (opens new window) 或router.push
(opens new window) 中的选项next(error)
: (2.4.0+) 如果传入next
的参数是一个Error
实例,则导航会被终止且该错误会被传递给router.onError()
(opens new window) 注册过的回调
// 1.全局前置守卫:初始化时执行、每次路由切换前执行
router.beforeEach((to, from, next) => {
if (to.name !== 'Login' && !isAuthenticated) next({ name: 'Login' })
else next()
})
// 2.全局后置守卫:初始化时执行、每次路由切换后执行
router.afterEach((to,from)=>{
console.log('afterEach',to,from)
if(to.meta.title){
document.title = to.meta.title //修改网页的title
}else{
document.title = 'vue_test'
}
})
// 3.全局解析守卫 2.5.0 新增
// 在 2.5.0+ 你可以用 router.beforeResolve 注册一个全局守卫。这和 router.beforeEach 类似,区别是在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后,解析守卫就被调用。
# 独享守卫
在路由配置上直接定义 beforeEnter
守卫:
const router = new VueRouter({
routes: [
{
path: '/foo',
component: Foo,
// 这些守卫与全局前置守卫的方法参数是一样的
beforeEnter: (to, from, next) => {
// ...
}
}
]
})
# 组件内守卫
可以在路由组件内直接定义以下路由导航守卫:
beforeRouteEnter
beforeRouteUpdate
(2.2 新增)beforeRouteLeave
const Foo = {
template: `...`,
beforeRouteEnter(to, from, next) {
// 在渲染该组件的对应路由被 confirm 前调用
// 不!能!获取组件实例 `this`
// 因为当守卫执行前,组件实例还没被创建
},
beforeRouteUpdate(to, from, next) {
// 在当前路由改变,但是该组件被复用时调用
// 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
// 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
// 可以访问组件实例 `this`
},
beforeRouteLeave(to, from, next) {
// 导航离开该组件的对应路由时调用
// 可以访问组件实例 `this`
}
}
beforeRouteEnter
守卫 不能 访问 this
,因为守卫在导航确认前被调用,因此即将登场的新组件还没被创建。
不过,你可以通过传一个回调给 next
来访问组件实例。在导航被确认的时候执行回调,并且把组件实例作为回调方法的参数。
beforeRouteEnter (to, from, next) {
next(vm => {
// 通过 `vm` 访问组件实例
})
}
注意 beforeRouteEnter
是支持给 next
传递回调的唯一守卫。对于 beforeRouteUpdate
和 beforeRouteLeave
来说,this
已经可用了,所以不支持传递回调,因为没有必要了。
beforeRouteUpdate (to, from, next) {
// just use `this`
this.name = to.params.name
next()
}
这个离开守卫通常用来禁止用户在还未保存修改前突然离开。该导航可以通过 next(false)
来取消。
beforeRouteLeave (to, from, next) {
const answer = window.confirm('Do you really want to leave? you have unsaved changes!')
if (answer) {
next()
} else {
next(false)
}
}