Vue基础
VUE简介
1.什么是VUE
是一套用于构建用户界面的前端框架。

2.vue框架的主要特性

2.1 数据驱动试图
在使用了 vue 的页面中,vue 会监听数据的变化,从而自动重新渲染页面的结构


2.2 双向数据绑定
在填写表单时,双向数据绑定可以辅助开发者在不操作 DOM 的前提下,自动把用户填写的内容同步到数据源中。


好处:开发者不再需要手动操作 DOM 元素,来获取表单元素最新的值!
2.3 MVVM
MVVM 是 vue 实现数据驱动视图和双向数据绑定的核心原理。MVVM 指的是 Model、View 和 ViewModel,它把每个 HTML 页面都拆分成了这三个部分


MVVM工作原理

VUE的基本使用

VUE的指令与过滤器
1.指令的概念
指令(Directives)是 vue 为开发者提供的模板语法,用于辅助开发者渲染页面的基本结构。
- vue 中的指令按照不同的用途可以分为如下 6 大类:
① 内容渲染指令
② 属性绑定指令
③ 事件绑定指令
④ 双向绑定指令
⑤ 条件渲染指令
⑥ 列表渲染指令
1.1 内容渲染指令
内容渲染指令用来辅助开发者渲染 DOM 元素的文本内容。
常用的内容渲染指令有如下 3 个:
-
v-text
<div class="app">
<p v-text="username">姓名:</p>
<!-- 页面显示为 zhangsan -->
</div>
<script>
// 创建 Vue 的实例对象
const vm = new Vue({
// el 属性是固定的写法,表示当前 vm 实例要控制页面上的哪个区域,接收的值是一个选择器
el: '.app',
// data 对象就是要渲染到页面上的数据
data: {
username: 'zhangsan'
}
})
</script>
v-text指令的缺点:会覆盖元素内部原有的内容!
-
{{ }}
vue 提供的 {{ }} 语法,专门用来解决 v-text 会覆盖默认文本内容的问题。
这种 {{ }} 语法的专业名称是插值表达式(英文名为:Mustache)
<p>姓名:{{username}}</p>
<!-- 页面显示为姓名:zhangsan -->
-
v-html
v-html 指令的作用:可以把带有标签的字符串,渲染成真正的 HTML 内容!
v-html会替换掉节点中所有的内容
严重注意:v-html有安全性问题!!!!
在网站上动态渲染任意HTML是非常危险的,容易导致XSS攻击。
一定要在可信的内容上使用v-html,永不要用在用户提交的内容上!
<div class="app">
<p v-html="info">欢迎语:</p>
<!-- 页面显示为 带样式的你好 -->
</div>
<script>
// 创建 Vue 的实例对象
const vm = new Vue({
// el 属性是固定的写法,表示当前 vm 实例要控制页面上的哪个区域,接收的值是一个选择器
el: '.app',
// data 对象就是要渲染到页面上的数据
data: {
username: 'zhangsan',
info: '<h4 style=color:red;>你好</h4>'
}
})
</script>
el属性的使用注意点
el: 'p'
这样写只会控制第一个 p标签
1.2 属性绑定指令
如果需要为元素的属性动态绑定属性值,则需要用到 v-bind 属性绑定指令(v-bind: + 属性名)
<div class="app">
<input type="text" v-bind:placeholder="tips">
<img v-bind:src="photo" alt="">
</div>
<script>
// 创建 Vue 的实例对象
const vm = new Vue({
// el 属性是固定的写法,表示当前 vm 实例要控制页面上的哪个区域,接收的值是一个选择器
el: '.app',
// data 对象就是要渲染到页面上的数据
data: {
tips: '请输入用户名',
photo: 'https://cn.vuejs.org/images/logo.svg'
}
})
</script>
注:v-bind: 也可以简写为:
在插值和属性中编写js表达式的运算
{{number + 1}}
{{ok ?'YES':'NO' }}
{{message.split('').reverse().join('')}}
<div v-bind:id="'list-' + id"></div>
<!-- id=1,即list-1,id=2即list-2 -->
1.3 事件绑定指令
绑定事件
vue 提供了 v-on 事件绑定指令,用来辅助程序员为 DOM 元素绑定事件监听
格式为:v-on:事件="函数名"
<div class="app">
<p>count的数量是:{{ count }}</p>
<button v-on:click="add">+1</button>
</div>
注:v-on:可以简写为@
事件处理函数
通过 v-on 绑定的事件处理函数,需要在 methods 节点中进行声明
<script>
// 创建 Vue 的实例对象
const vm = new Vue({
el: '.app',
data: {
count: 0
},
//methods 的作用是定义事件的处理函数
methods: {
//add: function () {}
// 可以简写为
add(){}
}
})
</script>
通过this访问data中的数据
在上述代码中 data 里的 count 是属于实例对象 vm 的一个属性
因此想要让每次事件触发时 count+1,可以做如下操作:
add() {
/* vm.count++
因为 this 是指向实例对象 vm 的,所以可以替换为 this.count */
this.count++;
}
绑定事件并传参
<button v-on:click="add(1)">+1</button>
//将 1 传给参数n
add(n) {
this.count+=n
}
$event
-
v-on:click="add"没有传参,则默认函数接收到的是事件对象 e
即:add(e) { this.count+=1 e.target.style.backgroundColor='red' }e.target是事件对象
![]()
-
如果有传参, vue 提供的内置变量
$event,用来表示原生的事件对象 e
可以将它作为参数传递给函数<button v-on:click="add(1,$event)">+1</button> add(n, e) { this.count +=n e.target.style.backgroundColor = 'red' }
事件修饰符
| 事件修饰符 | 说明 |
|---|---|
| .prevent | 阻止默认行为(例如:阻止 a 连接的跳转、阻止表单的提交等 |
| .stop | 阻止事件冒泡 |
| .capture | 以捕获模式触发当前的事件处理函数 |
| .once | 绑定的事件只触发1次 |
| .self | 只有在 event.target 是当前元素自身时触发事件处理函数 |
<a href="http://www.baidu.com" @click.prevent="show">百度</a>
按键修饰符
在监听键盘事件时,我们经常需要判断详细的按键。此时,可以为键盘相关的事件添加按键修饰符

1.4 双向绑定指令
vue 提供了 v-model 双向数据绑定指令,用来辅助开发者在不操作 DOM 的前提下,快速获取表单的数据
<input type="text" v-model="username">
v-model是双向绑定。v-bild是单向绑定
注:v-model 只应用于表单元素
<select v-model="chance">
<option value="1">check</option>
<option value="2">2</option>
<option value="3">3</option>
<option value="4">4</option>
</select>
data: {
count: 0,
chance: 0
},
v-model的修饰符
| 修饰符 | 作用 | 示例 |
|---|---|---|
| .number | 自动将用户的输入值转为数值类型 | <input v-model.number="age" /> |
| .trim | 自动过滤用户输入的首尾空白字符 | <input v-model.trim="msg" /> |
| .lazy | 在文本框失焦时才更新数据 | <input v-model.lazy="msg" /> |
1.5 条件渲染指令
条件渲染指令用来辅助开发者按需控制 DOM 的显示与隐藏。条件渲染指令有如下两个,分别是:
-
v-if: 指令会动态地创建或移除DOM 元素,从而控制元素在页面上的显示与隐藏;
-
v-show: 指令会动态为元素添加或移除 style="display: none;" 样式,从而控制元素的显示与隐藏;

v-else
v-else 指令必须配合 v-if 指令一起使用,否则它将不会被识别!

v-else-if
注意:v-else-if 指令必须配合 v-if 指令一起使用,否则它将不会被识别!

1.6 列表渲染指令
v-for
- vue 提供了 v-for 列表渲染指令,用来辅助开发者基于一个数组来循环渲染一个列表结构。v-for 指令需要使用
i in items形式的特殊语法,其中:-
i 是被循环的每一项
-
items 是待循环的数组
-
<ul>
<li v-for="i in list">id:{{i.id}}   姓名:{{i.name}}</li>
</ul>
data: {
list: [
{ id: 1, name: 'zhangsan' },
{ id: 2, name: 'lisi' }
]
}
- v-for 指令还支持一个可选的第二个参数,即当前项的索引。语法格式为
(item, index) in items
<tbody v-for="(i,index) in list">
<tr>
<td>{{index}}</td>
<td>{{i.id}}</td>
<td>{{i.name}}</td>
</tr>
</tbody>
注:item 和 index 除了可以被子元素引用,还可以被自身引用
key
-
官方建议:只要用到了 v-for 指令,那么一定要绑定一个 :key 属性
-
而且,尽量把 id 作为 key 的值
-
官方对 key 的值类型,是有要求的:字符串或数字类型,且具有唯一性(索引index不具有唯一性)
-
key 的值是千万不能重复的,否则会终端报错:Duplicate keys detected
<tbody v-for="(i,index) in list" :key="i.id">
<tr>
<td>{{index}}</td>
<td>{{i.id}}</td>
<td>{{i.name}}</td>
</tr>
</tbody>
2.过滤器
vue3没有过滤器
-
过滤器(Filters)是 vue 为开发者提供的功能,常用于文本的格式化。
-
过滤器可以用在两个地方:插值表达式和 v-bind 属性绑定
过滤器应该被添加在 JavaScript 表达式的尾部,由“管道符”进行调用

2.1 定义过滤器
在创建 vue 实例期间,可以在 filters 节点中定义过滤器
<!-- 在{{}}中通过 管道符‘|’ 调用 过滤器 capi,对message的值进行格式化 -->
<!-- 这里值是以过滤器的返回值为准 -->
<!-- 例如要将 message的值 的首字母大写 -->
<p>message的值:{{message | capi}}</p>
// filters与 data,el 平级
// 过滤器函数必须被定义到filters节点之下
// 过滤器本质上是函数
filters: {
// 定义过滤器函数
// 这里的参数val是管道符前面的值
capi(val) {
var first = val.slice(0, 1).toUpperCase()//截取字符串,变为大写字母
val = first + val.slice(1)
// 过滤器中一定要return一个返回值,渲染到插值的位置
return val
}
}
2.2 私有过滤器和全局
-
在 filters 节点下定义的过滤器,称为“私有过滤器”,因为它只能在当前vm 实例所控制的 el 区域内使用
-
如果希望在多个 vue 实例之间共享过滤器,则可以按照如下的格式定义全局过滤器:
(此方法应该写在实例前,写在实例后会报错)
// 全局过滤器-独立于每个 vm 实例之外
/* Vue.filter()方法接收两个参数:
第一个参数 是全局过滤器的名字
第二个参数 是全局过滤器的“处理函数”,即管道符前面的值 */
Vue.filter('capi', function (val) {
var first = val.slice(0, 1).toUpperCase()//截取字符串,变为大写字母
val = first + val.slice(1)
// 过滤器中一定要return一个返回值,渲染到插值的位置
return val
})
注:如果全局过滤器和私有过滤器名字一致,此时调用的是全局过滤器
2.3 连续调用多个过滤器

2.4 过滤器传参

vue基础入门
1.侦听器
watch 侦听器允许开发者监视数据的变化,从而针对数据的变化做特定的操作
1.1 方法格式的侦听器
// 与data,el同级
// 所有的侦听器,都应该被定义到 watch 节点下
watch: {
// 监听 username值的变化
// 侦听器本质上是一个函数,要侦听哪个数据变化,就将那个数据名作为函数名
// newVal 是变化后的新值,oldVal 是变化之前的旧值
username(newVal, oldVal) {
console.log(newVal, oldVal);
}
}
查询用户名是否被占用
<input type="text" v-model.lazy="username">
watch: {
// 监听 username值的变化
// 侦听器本质上是一个函数,要侦听哪个数据变化,就将那个数据名作为函数名
// newVal 是变化后的新值,oldVal 是变化之前的旧值
username(newVal) {
if (newVal === '') return
// 调用ajax发起请求,判断 username 是否被占用
$.ajax({
type: 'get',//请求的方式,GET或 POST
url: 'https://www.escook.cn/api/finduser/' + newVal,//请求的url地址
success: function (result) {
console.log(result);
}//请求成功后的回调函数
})
}
-
缺点1:无法在刚进入页面的时候,自动触发!!!
-
缺点2:如果侦听的是一个对象,如果对象中的属性发生了变化,不会触发侦听器!!!
1.2 对象格式的侦听器
-
好处1:可以通过 immediate 选项,让侦听器自动触发!!!
默认情况下,组件在初次加载完毕后不会调用 watch 侦听器。如果想让 watch 侦听器立即被调用,则需要使用 immediate 选项
//定义对象格式的监听器
username: {
// 监听器的处理函数,只要username发生变化 就执行函数
handler(newVal) {
if (newVal === '') return
// 调用ajax发起请求,判断 username 是否被占用
$.ajax({
type: 'get',//请求的方式,GET或 POST
url: 'https://www.escook.cn/api/finduser/' + newVal,//请求的url地址
success: function (result) {
console.log(result);
}//请求成功后的回调函数
})
},
// 表示页面初次渲染好之后,就立即触发当前的 watch 侦听器
immediate: true
}
- 好处2:可以通过 deep 选项,让侦听器深度监听对象中每个属性的变化!!!
//如果侦听的是一个对象,如果对象中的属性发生了变化,不会触发侦听器
data: {
info:{
username: 'zhangsan'
}
},
info: {
// 监听器的处理函数,只要username发生变化 就执行函数
handler(newVal) {
console.log(newVal);
},
deep: true
}
另一种写法:
// 如果侦听的是一个对象的属性,不用深度监听,也可以有如下写法
'info.username'(newVal) {
console.log(newVal);
}
补充:模板嵌套字
在普通字符串中嵌入表达式
console.log('Fifteen is ' + (a + b) + ' and \n not ' + (2 * a + b) + '.');
// "Fifteen is 15 and
// not 20."
现在通过模板字符串,${表达式}
console.log(`Fifteen is ${a + b} and
not ${2 * a + b}.`);
// "Fifteen is 15 and
// not 20."
2.计算属性
-
计算属性指的是通过一系列运算之后,最终得到一个属性值。
-
这个动态计算出来的属性值可以被模板结构或 methods 方法使用
<div class="box" :style="{backgroundColor:rgb}">
{{rgb}}
</div>
// 所有的计算属性,都要定义到 computed 节点之下
// 计算属性在定义的时候,要定义成“方法格式”
computed: {
// rgb 作为一个计算属性,被定义成了方法格式,
// 最终,在这个方法中,要返回一个生成好的 rgb(x,x,x) 的字符串
rgb() {
return `rgb(${this.r}, ${this.g}, ${this.b})`
}
}
优点:
1.实现了某些代码得到复用
2.只要计算属性中依赖的数据源变化了,则计算属性会自动重新求值!
3.vue-cil
3.1 什么是单页面应用程序
单页面应用程序(英文名:Single Page Application)简称 SPA,顾名思义,指的是一个 Web 网站中只有唯一的一个HTML 页面,所有的功能与交互都在这唯一的一个页面内完成
3.2 什么是 vue-cli
vue-cli 是 Vue.js 开发的标准工具。它简化了程序员基于 webpack 创建工程化的 Vue 项目的过程。
中文官网:https://cli.vuejs.org/zh/
3.3 基于 vue-cli 快速生成工程化的 Vue 项目:
vue create 项目的名称
( * ) Babel:解决js兼容性
( ) TypeScript:微软的一套脚本语言,现在建议不选
( ) Progressive Web App (PWA) Support:渐进式的web框架
( ) Router:路由
( ) Vuex
( * ) CSS Pre-processors:CSS预处理器
( ) Linter / Formatter:约束代码风格
( ) Unit Testing:单元测试
( ) E2E Testing:e2e测试
选择第一项
预设名称 first_demo
3.4 项目目录

src目录
-
assets 文件夹:存放项目中用到的静态资源文件,例如:css 样式表、图片资源
-
components 文件夹:程序员封装的、可复用的组件,都要放到 components 目录下
-
main.js 是项目的入口文件。整个项目的运行,要先执行 main.js
-
App.vue 是项目的根组件。
3.5 vue 项目的运行流程
在工程化的项目中,vue 要做的事情很单纯:通过 main.js 把 App.vue 渲染到 index.html 的指定区域中。
- 其中:
① App.vue 用来编写待渲染的模板结构
② index.html 中需要预留一个 el 区域
③ main.js 把 App.vue 渲染到了 index.html 所预留的区域中
import Vue from 'vue'
//导入要渲染的vue文件
import App from './App.vue'
Vue.config.productionTip = false
new Vue({
// 把 render 函数指定的组件,渲染到HTML页面中
// h(要渲染的文件名)
render: h => h(App),
}).$mount('#app')
// $mount('#app')的作用等同于 el:'#app'
// 将 id='app'中的内容用 vue文件 替换掉
4.vue组件
组件化开发指的是:根据封装的思想,把页面上可重用的 UI 结构封装为组件,从而方便项目的开发和维护。
4.1 vue 中的组件化开发
-
vue 是一个支持组件化开发的前端框架。
-
vue 中规定:组件的后缀名是.vue。
-
之前接触到的 App.vue 文件本质上就是一个 vue 的组件。
4.2 vue 组件的三个组成部分
每个 .vue 组件都由 3 部分构成,分别是:
- template -> 组件的模板结构
- template 是 vue 提供的容器标签,只起到包裹性质的作用,它不会被渲染为真正的 DOM 元素
- template 中只能包含一个的根节点(即 最外层代码折起来后,template中只有包含一个元素)
- script -> 组件的 JavaScript 行为
vue 规定:.vue 组件中的 data 必须是一个函数,不能直接指向一个数据对象。否则会导致多个组件实例共用同一份数据的问题,请参考官方给出的示例:
https://cn.vuejs.org/v2/guide/components.html#data-必须是一个函数
- style -> 组件的样式
要想启用less,结构应为:
<style lang="less">
</style>
其中,每个组件中必须包含 template 模板
<template>
<div>
<p>---{{ username }}</p>
<button @click="change">按钮</button>
</div>
</template>
// script -> 组件的 JavaScript 行为
<script>
// 默认导出。这是固定写法
export default {
// data数据源
// 注意:vue组件中的data不能指向对象
// 组件中的data必须是一个函数
data() {
// 这个return出去的{ }中,可以定义数据
return {
username: "admin",
};
},
// 定义函数
methods: {
change() {
//在组件中,this就表示当前组件的实例对象
this.username = "zhangsan";
},
},
// 当前组件中的侦听器
watch: {},
// 当前组件中的计算属性
computed: {},
// 当前组件中的过滤器
filter: {},
};
</script>
4.3 组件之间的父子关系

使用组件的三个步骤
-
步骤1:使用 import 语法导入需要的组件
![]()
-
步骤2:使用 components 节点注册组件
![]()
-
步骤3:以标签形式使用刚才注册的组件
![]()
通过 components 注册的是私有子组件
例如:
-
在组件 A 的 components 节点下,注册了组件 F。
-
则组件 F 只能用在组件 A 中;不能被用在组件C 中。
可以使用 name配置项 指定 组件在 开发者工具 中呈现的名字。
4.4 注册全局组件
在 vue 项目的 main.js 入口文件中,通过 Vue.component() 方法,可以注册全局组件

- 建议注册组件的名称都是大写开头
4.5 组件的props
props 是组件的自定义属性

注:
-
props属性是只读的
-
要想修改 props 的值,可以把 props 的值转存到 data 中,因为 data 中的数据都是可读可写的!
<template>
<div>
<p>这是count组件</p>
<p>age的值是:{{ age }}</p>
<button @click="age += 1">+1</button>
</div>
</template>
<script>
export default {
// props是自定义属性,允许使用者通过自定义属性 为当前组件指定初始值
props: ["init"],
data() {
return {
age: this.init,
};
},
};
</script>
//自定义属性的赋值是字符串属性,想要变为数字型 应该在属性前加 :(即v-bind属性)
应用于其他组件:<Count :init="3"></Count>
props 的 default 默认值
在声明自定义属性时,可以通过 default 来定义属性的默认值

props 的 type 值类型
在声明自定义属性时,可以通过 type 来定义属性的值类型

props 的 required 必填项
在声明自定义属性时,可以通过 required 选项,将属性设置为必填项,强制用户必须传递属性的值

4.6 组件之间的样式冲突问题
-
默认情况下,写在 .vue 组件中的样式会全局生效,因此很容易造成多个组件之间的样式冲突问题。
-
导致组件之间样式冲突的根本原因是:
-
① 单页面应用程序中,所有组件的 DOM 结构,都是基于唯一的 index.html 页面进行呈现的
-
② 每个组件中的样式,都会影响整个 index.html 页面中的 DOM 元素
-
4.7 如何解决组件样式冲突的问题
-
1.为每个组件分配唯一的自定义属性,在编写组件样式时,通过属性选择器来控制样式的作用域
-
2. style 节点的 scoped 属性
为了提高开发效率和开发体验,vue 为 style 节点提供了 scoped 属性,从而防止组件之间的样式冲突问题
![]()
<style lang="less" scoped> </style>
scoped 属性的缺点:如果给当前组件的 style 节点添加了 scoped 属性,则当前组件的样式对其子组件是不生效的。
如果想让某些样式对子组件生效,可以使用 /deep/ 深度选择器
<style lang="less" scoped>
//加deep之前 h5[data-v-3c83f0b7]
//加deep之后 [data-v-3c83f0b7] h5
// 当使用第三方组件库的时候,如果有修改第三方组件默认样式的需求,需要用到 /deep/
/deep/ p {
color: pink;
}
</style>
vue组件库:
4.8 vue组件的实例对象

vue文件是 经过上图的包,将每个vue文件解析转换为js文件,再在浏览器执行
6.组件之间的数据共享
6.1父子组件之间的数据共享
父子组件之间的数据共享又分为:
① 父 -> 子共享数据
-
父组件向子组件共享数据需要使用自定义属性(props)
-
父组件定义数据,子组件通过自定义属性来接收数据


//父组件
<template>
<div>
<h1>App根组件</h1>
<Son :msg="message" :init="info"></Son>
</div>
</template>
<script>
export default {
data() {
return {
message: "2022年",
info: { mon: "九月", day: "22日" },
};
},
};
</script>
//子组件
<template>
<div>
<h2>son组件</h2>
<p>今天是:{{ msg }}-{{ init.mon }}-{{ init.day }}</p>
</div>
</template>
<script>
export default {
props: ["msg", "init"],
};
</script>
页面显示结果:


② 子 -> 父共享数据
- 子组件向父组件共享数据使用自定义事件

//子组件
methods: {
add() {
this.count += 1;
//修改数据时,通过 $emit(自定义事件名,要传的数据)触发自定义事件
this.$emit("numchange", this.count);
},
},
//父组件
<p>{{ countFromSon }}</p>
<!-- 绑定自定义事件 -->
<!-- 这个自定义事件是通过子组件的点击事件触发的 -->
<Son2 @numchange="getnewcount"></Son2>
export default {
data() {
return {
// 定义countFromSon来接收从子组件传来的数据
countFromSon: 0,
};
},
methods: {
// 定义自定义事件函数
// val形参表示新值
getnewcount(val) {
this.countFromSon = val;
console.log(val);
},
},
- 过程:点击子组件的按钮 -> 触发点击事件 -> 执行点击事件函数 -> 执行
$emit() -> 触发自定义事件 -> 父组件绑定自定义事件 -> 执行父组件中自定义事件 -> 将子组件中的新值this.count通过val传给父组件
6.2 兄弟组件之间的数据共享
在 vue2.x 中,兄弟组件之间数据共享的方案是 EventBus

EventBus 的使用步骤
-
① 创建 eventBus.js 模块,并向外共享一个 Vue 的实例对象
-
② 在数据发送方,调用 bus.$emit('事件名称', 要发送的数据) 方法触发自定义事件
-
③ 在数据接收方,调用 bus.$on('事件名称', 事件处理函数) 方法注册一个自定义事件
7.ref 引用
-
ref 用来辅助开发者在不依赖于 jQuery 的情况下,获取 DOM 元素或组件的引用。
-
每个 vue 的组件实例上,都包含一个 $refs 对象,里面存储着对应的 DOM 元素或组件的引用。默认情况下,组件的 $refs 指向一个空对象。
使用 ref 引用 DOM 元素
如果想要使用 ref 引用页面上的 DOM 元素,则可以按照如下的方式进行操作
<h1 ref="myh111">App根组件</h1>
使用格式:this.$refs.myh111
<template>
<div>
<h1 ref="myh111">App根组件</h1>
<button @click="btn1">点击</button>
</div>
</template>
<script>
export default {
methods: {
btn1() {
this.$refs.myh111.style.color = "red";
},
},
};
</script>
使用 ref 引用组件实例
使用ref属性,为相应的‘组件’添加引用名称
控制文本框和按钮的按需切换
通过布尔值 inputVisible 来控制组件中的文本框与按钮的按需切换
尚硅谷
Object.defineProperty
let number = 18;
let person = {
name: "zhangsan",
sex: "男",
};
// 使 person和 number产生了联系,用 get 读取,用 set 修改
Object.defineProperty(person, "age", {
// value=18,
// enumerable:true,//控制属性是否可以枚举
// writable:true,//控制属性是否可以被修改
// configurable:true//控制属性是否可以被删除
// 当有人读取person的age属性时,get函数(getter)就会被调用,且返回值就是age的值
get() {
console.log("读取了get属性");
return number;
},
set() {
console.log("修改了get属性,age=" + value);
number = value;
},
});
console.log(person);
数据代理
-
通过Object.defineProperty()把data对象中所有属性添加到vm上。
-
为每一个添加到vm上的属性,都指定一个getter/setter。
-
在getter/setter内部去操作(读/写)data中对应的属性。

事件处理
<button @click="showInfo2($event,66)">点我提示信息2(传参)</button>
methods:{
// 如果vue模板没有写event,会自动传 event 给函数
showInfo1(event){
// console.log(event.target.innerText)
// console.log(this) //此处的this是vm
alert('同学你好!')
},
showInfo2(event,number){
console.log(event,number)
// console.log(event.target.innerText)
// console.log(this) //此处的this是vm
alert('同学你好!!')
}
两个重要的小原则:
-
1.所被Vue管理的函数,最好写成普通函数,这样this的指向才是vm 或 组件实例对象
-
2.所有不被Vue所管理的函数(定时器的回调函数、ajax的回调函数等、Promise的回调函数),最好写成箭头函数,这样this的指向才是vm 或 组件实例对象
绑定样式
class
写法::class=“xxx” xxx可以是字符串、对象、数。
所以分为三种写法,字符串写法,数组写法,对象写法
<div class="app">
<!-- 字符串写法适用于:类名不确定,要动态获取。 -->
<div class="basic" :class="a">
{{name}}
</div>
<br>
<!-- 数组写法适用于:要绑定多个样式,个数不确定,名字也不确定。 -->
<div class="basic" :class="classArr">
好好学习
</div>
<br>
<!-- 对象写法适用于:要绑定多个样式,个数确定,名字也确定,但不确定用不用。 -->
<div class="basic" :class="objClass">
天天向上
</div>
</div>
data: {
name: 'zhangsan',
a: 'normal',
classArr: ['color1', 'color2', 'color3'],
objClass: {
color1: false,
color2: false
}
},
style
通过动态绑定 style,可以通过参数动态的改变元素的样式。
对象写法,数组写法
<!-- 绑定style样式--对象写法 -->
<div class="basic" :style="styleObj">{{name}}</div>
<!-- 绑定style样式--数组写法 -->
<div class="basic" :style="styleArr">{{name}}</div>
模板字符串写法:
<h2 :style="{css属性名:值}"> {<!-- \-->{message}} </h2>
<p>font-size:</p>
<input type="text" v-model="ifname" />
<input type="range" v-model="ifname" />
<p :style="{ fontSize: ifname + 'px' }">文本</p>
<script>
export default {
data() {
return {
ifname: 18,
};
},
methods: {
btn1() {
this.$refs.myh111.style.color = "red";
},
},
};
</script>
数组方式:
<div id="app">
<h2 :style="[fontSize,bgc]">测试文本</h2>
</div>
<script>
let vm = new Vue({
el: "#app",
data: {
fontSize: { fontSize: "48px" },
bgc: { backgroundColor: "red" },
},
});
</script>


详情可以查看这位朋友的:https://blog.csdn.net/hangao233/article/details/123990192?spm=1001.2014.3001.5502
vue监视数据
Vue监视数据的原理:
1. vue会监视data中所有层次的数据。
2. 如何监测对象中的数据?
通过setter实现监视,且要在new Vue时就传入要监测的数据。
(1).对象中后追加的属性,Vue默认不做响应式处理
(2).如需给后添加的属性做响应式,请使用如下API:
Vue.set(target,propertyName/index,value) 或
vm.$set(target,propertyName/index,value)
3. 如何监测数组中的数据?
通过包裹数组更新元素的方法实现,本质就是做了两件事:
(1).调用原生对应的方法对数组进行更新。
(2).重新解析模板,进而更新页面。
4.在Vue修改数组中的某个元素一定要用如下方法:
(1).使用这些API:push()、pop()、shift()、unshift()、splice()、sort()、reverse()
(2).Vue.set() 或 vm.$set()
特别注意:Vue.set() 和 vm.$set() 不能给vm 或 vm的根数据对象 添加属性!!!

收集表单数据:
1.若:<input type="text"/>,则v-model收集的是value值,用户输入的就是value值。
2.若:<input type="radio"/>,则v-model收集的是value值,且要给标签配置value值。
3.若:<input type="checkbox"/>
(1).没有配置input的value属性,那么收集的就是checked(勾选 or 未勾选,是布尔值)
(2).配置input的value属性:
① v-model的初始值是非数组,那么收集的就是checked(勾选 or 未勾选,是布尔值)
② v-model的初始值是数组,那么收集的的就是value组成的数组
备注:v-model的三个修饰符:
lazy:失去焦点再收集数据
number:输入字符串转为有效的数字
trim:输入首尾空格过滤

v-cloak指令(没有值):
-
本质是一个特殊属性,Vue实例创建完毕并接管容器后,会删掉v-cloak属性。
-
使用css配合v-cloak可以解决网速慢时页面展示出{{xxx}}(未经解析的模板)的问题。
<style>
[v-cloak]{
display:none;
}
</style>
<!-- 准备好一个容器-->
<div id="root">
<h2 v-cloak>{{name}}</h2>
</div>
<script type="text/javascript" src="http://localhost:8080/resource/5s/vue.js"></script>
<script type="text/javascript">
console.log(1)
Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。
new Vue({
el:'#root',
data:{
name:'尚硅谷'
}
})
</script>
v-pre指令:(比较没用)
跳过其所在节点的编译过程
可利用它跳过:没有使用指令语法、没有使用插值语法的节点,会加快编译
自定义指令
局部指令
使用 directives 选项完成了指令的局部注册。
<!-- 定义一个v-big指令,和v-text功能类似,但会把绑定的数值放大10倍。 -->
<h1>放大10倍的值:</h1>
<p v-big="n"></p>
<script>
const vm = new Vue({
data: {
n: 1
},
directives: {
//big函数何时会被调用?(没有配置对象时)
//1.指令与元素成功绑定时(一上来)。2.指令所在的模板被重新解析时。
big(element, binding) {
element.innerText = binding.value * 10
}
}
})
</script>
全局指令
将一个自定义指令全局注册到应用层级也是一种常见的做法:
使用Vue.directive()定义
//定义全局指令
//第一个参数为指令名,第二个参数为 配置对象 或 函数
Vue.directive('fbind', {
// 指令与元素成功绑定时(一上来)
bind(element, binding){
element.value = binding.value
},
// 指令所在元素被插入页面时
inserted(element, binding){
element.focus()
},
// 指令所在的模板被重新解析时
update(element, binding){
element.value = binding.value
}
})
Vue.directive('big', function (element, binding) {
element.innerText = binding.value * 10
})
自定义指令总结:
一、定义语法:
(1).局部指令:
new Vue({
directives:{指令名:配置对象} 或 directives{指令名:回调函数}
})
(2).全局指令:
Vue.directive(指令名,配置对象) 或 Vue.directive(指令名,回调函数)
二、配置对象中常用的3个回调:
(1).bind:指令与元素成功绑定时调用。
(2).inserted:指令所在元素被插入页面时调用。
(3).update:指令所在模板结构被重新解析时调用。
三、备注:
1.指令定义时不加v-,但使用时要加v-;
2.指令名如果是多个单词,要使用kebab-case命名方式,不要用camelCase命名。
生命周期
5.1 生命周期 & 生命周期函数
-
生命周期(Life Cycle)是指一个组件从创建 -> 运行 -> 销毁的整个阶段,强调的是一个时间段。
-
生命周期函数:是由 vue 框架提供的内置函数,会伴随着组件的生命周期,自动按次序执行。
注意:生命周期强调的是时间段,生命周期函数强调的是时间点
debugger 断点,放到哪个函数中,就停在哪里

创建前 刚创建 渲染前 刚渲染
- 1. beforeCreate(创建前):
数据监测(getter和setter)和初始化事件还未开始,此时 data 的响应式追踪、event/watcher 都还没有被设置,也就是说不能访问到data、computed、watch、methods上的方法和数据。
- 2. created(创建后):
实例创建完成,实例上配置的 options 包括 data、computed、watch、methods 等都配置完成,但是此时渲染得节点还未挂载到 DOM,所以不能访问到 $el属性。
- 3. beforeMount(挂载前):
在挂载开始之前被调用,相关的render函数首次被调用。此阶段Vue开始解析模板,生成虚拟DOM存在内存中,还没有把虚拟DOM转换成真实DOM,插入页面中。所以网页不能显示解析好的内容。
- 4. mounted(挂载后):(重)
在el被新创建的 vm.$el(就是真实DOM的拷贝)替换,并挂载到实例上去之后调用(将内存中的虚拟DOM转为真实DOM,真实DOM插入页面)。此时页面中呈现的是经过Vue编译的DOM,这时在这个钩子函数中对DOM的操作可以有效,但要尽量避免。一般在这个阶段进行:开启定时器,发送网络请求,订阅消息,绑定自定义事件等等

- 5. beforeUpdate(更新前):
响应式数据更新时调用,此时虽然响应式数据更新了,但是对应的真实 DOM 还没有被渲染(数据是新的,但页面是旧的,页面和数据没保持同步呢)。
- 6. updated(更新后) :
在由于数据更改导致的虚拟DOM重新渲染和打补丁之后调用。此时 DOM 已经根据响应式数据的变化更新了。调用时,组件 DOM已经更新,所以可以执行依赖于DOM的操作。然而在大多数情况下,应该避免在此期间更改状态,因为这可能会导致更新无限循环。该钩子在服务器端渲染期间不被调用。
当调用vm.$destroy()时,组件会进入销毁阶段
- 7. beforeDestroy(销毁前):(重)
实例销毁之前调用。这一步,实例仍然完全可用,this 仍能获取到实例。在这个阶段一般进行关闭定时器,取消订阅消息,解绑自定义事件。
- 8. destroyed(销毁后):
实例销毁后调用,调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。该钩子在服务端渲染期间不被调用。
注:可以参考 vue 官方文档给出的“生命周期图示”,进一步理解组件生命周期执行的过程:
https://cn.vuejs.org/v2/guide/instance.html#生命周期图示

总结:
常用的生命周期钩子:
1.mounted: 发送ajax请求、启动定时器、绑定自定义事件、订阅消息等【初始化操作】。
2.beforeDestroy: 清除定时器、解绑自定义事件、取消订阅消息等【收尾工作】。
关于销毁Vue实例
1.销毁后借助Vue开发者工具看不到任何信息。
2.销毁后自定义事件会失效,但原生DOM事件依然有效。
3.一般不会在beforeDestroy操作数据,因为即便操作数据,也不会再触发更新流程了。
VueComponent
关于VueComponent:
1.school组件本质是一个名为VueComponent的构造函数,且不是程序员定义的,是Vue.extend生成的。
2.使用组件时,创建构造函数
我们只需要写<school />或<school></school>,Vue解析时会帮我们创建school组件的实例对象,即Vue帮我们执行的:new VueComponent(options)。
3.特别注意:每次调用Vue.extend,返回的都是一个全新的VueComponent!!!!
4.关于this指向:
(1).组件配置中:
data函数、methods中的函数、watch中的函数、computed中的函数 它们的this均是【VueComponent实例对象】。
(2).new Vue(options)配置中:
data函数、methods中的函数、watch中的函数、computed中的函数 它们的this均是【Vue实例对象】。
5.VueComponent的实例对象,以后简称vc(也可称之为:组件实例对象)。
Vue的实例对象,以后简称vm。
一个重要的内置关系
一个重要的内置关系:VueComponent.prototype.proto === Vue.prototype
为什么要有这个关系:让组件实例对象(vc)可以访问到 Vue原型上的属性、方法。

脚手架
使用前置:
第一步(没有安装过的执行):全局安装 @vue/cli
npm install -g @vue/cli
第二步:切换到要创建项目的目录,然后使用命令创建项目
vue create xxxxx
第三步:启动项目
npm run serve
脚手架文件结构
├── node_modules
├── public
│ ├── favicon.ico: 页签图标
│ └── index.html: 主页面
├── src
│ ├── assets: 存放静态资源
│ │ └── logo.png
│ │── component: 存放组件
│ │ └── HelloWorld.vue
│ │── App.vue: 汇总所有组件
│ │── main.js: 入口文件
├── .gitignore: git版本管制忽略的配置
├── babel.config.js: babel的配置文件
├── package.json: 应用包配置文件
├── README.md: 应用描述文件
├── package-lock.json:包版本控制文件
index.html:

修改脚手架的默认配置
使用vue inspect > output.js可以查看到Vue脚手架的默认配置。
使用vue.config.js可以对脚手架进行个性化定制,详情见:https://cli.vuejs.org/zh
ref属性
-
1.被用来给 元素 或 子组件 注册引用信息(id的替代者)
-
2.应用在 html标签上 获取的是真实DOM元素,
应用在 组件标签上 是组件实例对象(vc)
使用方式:
打标识:<h1 ref="xxx">.....</h1>或 <School ref="xxx"></School>
获取:this.$refs.xxx
props配置项
1.功能:让组件接收外部传过来的数据
2.传递数据:<Demo name="xxx"/>
<school name="西安石油大学" address="西安"></school>
3.组件 接收数据:
-
第一种方式(只接收):
props: ["name", "address", "num"], -
第二种方式(限制类型):
props: { name: String, address: String, num: Number, }, -
第三种方式(限制类型、限制必要性、指定默认值):
props: { name: { type: String, //name的类型 require: true, //是否必要 }, address: { type: String, //类型 default: "西安", //如果不传,就给默认值 }, num: { type: String, //类型 require: true, }, },
备注:props是只读的
Vue底层会监测你对props的修改,如果进行了修改,就会发出警告.
若业务需求确实需要修改,那么请复制props的内容到data中一份,然后去修改data中的数据。
mixin(混入)
-
混入 (mixin) 提供了一种非常灵活的方式,来分发 Vue 组件中的可复用功能。
-
功能:可以把多个组件共用的配置提取成一个混入对象
例子:
//定义混合对象
export const mixin = {
methods: {
showName() {
alert(this.name);
},
},
}
<h2 @click="showName">学校名称:{{ name }}</h2>
<script>
// 引入混合
import { mixin } from "../mixin";
export default {
name: "School",
data() {
return {
name: "西安石油大学",
address: "西安",
};
},
mixins: [mixin],
};
</script>
选项合并
当组件和混入对象含有同名选项时,这些选项将以恰当的方式进行“合并”。
比如,数据对象在内部会进行递归合并,并在发生冲突时以组件数据优先。
var mixin = {
data: function () {
return {
message: 'hello',
foo: 'abc'
}
}
}
new Vue({
mixins: [mixin],
data: function () {
return {
message: 'goodbye',
bar: 'def'
}
},
created: function () {
console.log(this.$data)
// => { message: "goodbye", foo: "abc", bar: "def" }
}
})
同名钩子函数将合并为一个数组,因此都将被调用。另外,混入对象的钩子将在组件自身钩子之前调用。
var mixin = {
created: function () {
console.log('混入对象的钩子被调用')
}
}
new Vue({
mixins: [mixin],
created: function () {
console.log('组件钩子被调用')
}
})
// => "混入对象的钩子被调用"
// => "组件钩子被调用"
值为对象的选项,例如 methods、components 和 directives,将被合并为同一个对象。两个对象键名冲突时,取组件对象的键值对。
var mixin = {
methods: {
foo: function () {
console.log('foo')
},
conflicting: function () {
console.log('from mixin')
}
}
}
var vm = new Vue({
mixins: [mixin],
methods: {
bar: function () {
console.log('bar')
},
conflicting: function () {
console.log('from self')
}
}
})
vm.foo() // => "foo"
vm.bar() // => "bar"
vm.conflicting() // => "from self"
全局混入不建议使用
插件
-
插件通常用来为 Vue 添加全局功能。插件的功能范围没有严格的限制。
-
本质:包含install方法的一个对象,
install 的第一个参数是Vue,第二个以后的参数是插件使用者传递的数据。
-
定义插件:
对象.install = function (Vue, options) { // 1. 添加全局过滤器 Vue.filter(....) // 2. 添加全局指令 Vue.directive(....) // 3. 配置全局混入(合) Vue.mixin(....) // 4. 添加实例方法 Vue.prototype.$myMethod = function () {...} Vue.prototype.$myProperty = xxxx } -
使用插件:
Vue.use()它需要在你调用 new Vue() 启动应用之前完成
例:
plugin.js
export default {
install(Vue, x, y, z) {
console.log(x, y, z)
//全局过滤器
Vue.filter('mySlice', function (value) {
return value.slice(0, 4)
})
//定义全局指令
Vue.directive('fbind', {
//指令与元素成功绑定时(一上来)
bind(element, binding) {
element.value = binding.value
},
//指令所在元素被插入页面时
inserted(element, binding) {
element.focus()
},
//指令所在的模板被重新解析时
update(element, binding) {
element.value = binding.value
}
})
//定义混入
Vue.mixin({
data() {
return {
x: 100,
y: 200
}
},
})
//给Vue原型上添加一个方法(vm和vc就都能用了)
Vue.prototype.hello = () => { alert('你好啊aaaa') }
}
}
main.js
// 引入插件
import plugin from './plugin'
// 使用插件
Vue.use(plugin)
然后就可以在别的组件使用插件里的功能了。
scoped样式
子组件的 style样式 最终是会汇总在一起的,因此会有 类名相同 导致 样式冲突 的情况
1. 作用:让样式在局部生效,防止冲突。
2. 写法:<style scoped>
注:App (根) 组件不适用
总结TodoList案例
1.组件化的编码流程
(1).拆分静态组件:组件要按照功能点拆分,命名不要与html元素冲突。
(2).实现动态组件:考虑好数据的存放位置,数据是一个组件在用,还是一些组件在用:
-
一个组件在用:放在组件自身即可。
-
一些组件在用:放在他们共同的父组件上(状态提升)。
(3).实现交互:从绑定事件开始。
2.props适用于:
(1).父组件 ==> 子组件 通信
(2).子组件 ==> 父组件 通信(要求父先给子一个函数)
3.使用v-model时要切记:
v-model绑定的值不能是props传过来的值,因为props是不可以修改的!
注:props传过来的若是对象类型的值,修改对象中的属性时Vue不会报错,但不推荐这样做。
组件自定义事件
一种组件间通信的方式,适用于:子组件 ===> 父组件
父组件中绑定:
第一种方式:
<!-- 通过自定义事件实现,,子给父传递数据 -->
<Student @getName="getStudentName"></Student>
getStudentName(studentName) {
console.log("收到学生名", studentName);
},
第二种方式(灵活性强)
使用this.$refs.xxx.$on()这样写起来更灵活,比如可以加定时器啥的。
<Student ref="student"></Student>
export default {
methods: {
getStudentName(studentName) {
console.log("收到学生名", studentName);
},
},
mounted() {
//绑定自定义事件
//获取 student的DOM元素 给其绑定事件 $on('事件名',回调函数)
this.$refs.student.$on("getName", this.getStudentName);
// 设置定时器,三秒之后才绑定事件
setTimeout(()=>{
this.$refs.student.$on("getName", this.getStudentName);
},3000)
},
};
在子组件中触发:
methods: {
postName() {
// 触发student组件上的自定义组件
// this.$emit("组件名",传递的数据);
this.$emit("getName", this.name);
},
},
解绑自定义事件this.$off()
//解绑一个自定义事件
this.$off("getName");
//解绑多个自定义事件
this.$off(["getName",["demo1"]]);
//解绑全部自定义事件
this.$off();
注意:通过this.$refs.xxx.$on('事件名',回调)绑定自定义事件时,回调要么配置在methods中,要么用箭头函数,否则this指向会出问题
(普通函数 this 指向的是触发 mounted函数 的实例对象,即绑定自定义事件的组件)
组件上也可以绑定原生DOM事件(click,keyup等),需要使用 native 修饰符。
<Student ref="student" @click.native="show"/>
全局事件总线(GlobalEventBus)
-
一种组件间通信的方式,适用于任意组件间通信。
-
安装全局事件总线:
new Vue({ ...... beforeCreate() { Vue.prototype.$bus = this //安装全局事件总线,$bus就是当前应用的vm }, ...... }) -
使用事件总线:
1. 接收数据:
A组件想接收数据,则在A组件中给$bus绑定自定义事件,事件的回调留在A组件自身。
methods(){ demo(data){......} } ...... mounted() { this.$bus.$on('xxxx',this.demo) }或直接使用箭头函数
mounted() { this.$bus.$on("getSchoolName", (schoolName) => { console.log("收到了学校传的数据", schoolName); }); },2. 提供数据:
this.$bus.$emit('xxxx',数据)
注:最好在beforeDestroy钩子中,用$off去解绑当前组件所用到的事件。(只解绑当前,千万不能全部解绑)
消息订阅与发布(pubsub)
-
一种组件间通信的方式,适用于任意组件间通信。
-
使用步骤:
-
安装pubsub:
npm i pubsub-js -
引入:
import pubsub from 'pubsub-js' -
接收数据:A组件想接收数据,则在A组件中订阅消息,订阅的回调留在A组件自身。
methods(){ demo(data){......} } ...... mounted() { this.pid = pubsub.subscribe('xxx',this.demo) //订阅消息 } -
提供数据:
pubsub.publish('xxx',数据) -
最好在beforeDestroy钩子中,用
PubSub.unsubscribe(pid)去取消订阅。
-
nextTick
-
语法:this.$nextTick(回调函数)
-
作用:在下一次 DOM 更新结束后执行其指定的回调。
-
什么时候用:当改变数据后,要基于更新后的新DOM进行某些操作时,要在nextTick所指定的回调函数中执行。
this.$nextTick(function(){
this.$refs.inputTitle.focus()
}
Vue封装的过度与动画
-
作用:在插入、更新或移除 DOM元素时,在合适的时候给元素添加样式类名。
-
图示:
![]()
-
写法
-
准备好样式:
- 元素进入的样式:
- v-enter:进入的起点
- v-enter-active:进入过程中
- v-enter-to:进入的终点
- 元素离开的样式:
- v-leave:离开的起点
- v-leave-active:离开过程中
- v-leave-to:离开的终点
- 元素进入的样式:
-
使用
<transition>包裹要过度的元素,并配置name属性:<transition name="hello"> <h1 v-show="isShow">你好啊!</h1> </transition> -
备注:若有多个元素需要过度,则需要使用:
<transition-group>,且每个元素都要指定key值。
<template> <div> <button @click="isShow = !isShow">显示/隐藏</button> <transition-group name="hello" appear> <h1 v-show="!isShow" key="1">你好啊!</h1> <h1 v-show="isShow" key="2">尚硅谷!</h1> </transition-group> </div> </template> -
案例:
<template>
<div>
<button @click="isShow = !isShow">显示与隐藏</button>
<transition name="Show">
<h1 v-show="isShow">"来是come"</h1>
</transition>
</div>
</template>
动画:
<style scoped>
.Show-enter-active {
animation: go 2s ease reverse;
}
.Show-leave-active {
animation: go 2s ease;
}
@keyframes go {
/* 定义动画 */
from {
transform: translateX(0);
}
to {
transform: translateX(-500px);
}
}
</style>
过渡:
<style scoped>
h1{
background-color: orange;
}
/* 进入的起点、离开的终点 */
.hello-enter,.hello-leave-to{
transform: translateX(-100%);
}
.hello-enter-active,.hello-leave-active{
transition: 0.5s linear;
}
/* 进入的终点、离开的起点 */
.hello-enter-to,.hello-leave{
transform: translateX(0);
}
</style>
动画效果可以使用第三方的库:
库的名称:Animate.css
安装:npm i animate.css
引入:import ‘animate.css’
vue脚手架配置代理

方法一
在 vue.config.js 中添加如下配置:
devServer:{
proxy:"http://localhost:5000"
}
说明:
-
优点:配置简单,请求资源时直接发给前端(8080)即可。
-
缺点:不能配置多个代理,不能灵活的控制请求是否走代理。
-
工作方式:若按照上述配置代理,当请求了前端不存在的资源时,那么该请求会转发给服务器 (优先匹配前端资源)
方法二
编写 vue.config.js 配置具体代理规则:
module.exports = {
devServer: {
proxy: {
'/api1': {// 匹配所有以 '/api1'开头的请求路径
target: 'http://localhost:5000',// 代理目标的基础路径
changeOrigin: true,
pathRewrite: {'^/api1': ''}
},
'/api2': {// 匹配所有以 '/api2'开头的请求路径
target: 'http://localhost:5001',// 代理目标的基础路径
changeOrigin: true,
pathRewrite: {'^/api2': ''}
}
}
}
}
/*
changeOrigin设置为true时,服务器收到的请求头中的host为:localhost:5000
changeOrigin设置为false时,服务器收到的请求头中的host为:localhost:8080
changeOrigin默认值为true
*/
在App中使用axios请求数据:
methods: {
getStudents(){
//加前缀api就走代理服务器,不加就不走
axios.get('http://localhost:8080/api1/students').then(
response => {
console.log('请求成功了',response.data)
},
error => {
console.log('请求失败了',error.message)
}
)
},
getCars(){
axios.get('http://localhost:8080/api2/cars').then(
response => {
console.log('请求成功了',response.data)
},
error => {
console.log('请求失败了',error.message)
}
)
}
},
说明:
-
优点:可以配置多个代理,且可以灵活的控制请求是否走代理。
-
缺点:配置略微繁琐,请求资源时必须加前缀。
导入bootstrap样式
方式一
在 src => assets => css 导入bootstrap样式,然后在 App 中引用
import './assets/css/bootstrap.css'
缺点:bootstrap中会引入一些没有下载的资源,如果暂时不使用这些资源,不想下载的话,就不适用于这种方法
方式二
在 public=>css 导入bootstrap样式,然后在 index.html 中引入
<link rel="stylesheet" href="<%= BASE_URL %>css/bootstrap.css">
slot插槽
-
作用:让父组件可以向子组件指定位置插入html结构,也是一种组件间通信的方式,适用于 父组件 ===> 子组件 。
-
分类:默认插槽、具名插槽、作用域插槽
- 默认插槽
父组件中: <Category> <div>html结构1</div> </Category> 子组件中: <template> <div> <!-- 定义插槽 --> <slot>插槽默认内容...</slot> </div> </template> - 具名插槽
父组件中: <List> <img slot="foodimg" src="" alt=""/> //多个同名插槽可以用 template包裹, //v-slot: 这种结构只能用在 template中 <template v-slot:footmore> <div>html结构</div> <div>html结构</div> </template> </List> 子组件中: <div> <!-- 定义插槽 --> <slot name="foodimg">插槽默认内容...</slot> <slot name="foodmore">插槽默认内容...</slot> </div> - 作用域插槽
理解:数据在组件的自身(子组件),但根据数据生成的结构需要组件的使用者(父组件)来决定。
(games数据在Category(子)组件中,但使用数据所遍历出来的结构由App(父)组件决定)父组件中: <Category> //这里不允许使用template <template scope="scopeData"> <!-- 生成的是ul列表 --> <ul> <li v-for="g in scopeData.games" :key="g">{{g}}</li> </ul> </template> </Category> <Category> <template slot-scope="scopeData"> <!-- 生成的是h4标题 --> <h4 v-for="g in scopeData.games" :key="g">{{g}}</h4> </template> </Category> 子组件中: <template> <div> <!-- 通过数据绑定就可以把子组件的数据传到父组件 --> <slot :games="games"></slot> </div> </template> <script> export default { name:'Category', props:['title'], //数据在子组件自身 data() { return { games:['红色警戒','穿越火线','劲舞团','超级玛丽'] } }, } </script>
vuex

-
概念:
在Vue中实现集中式状态(数据)管理的一个Vue插件,对vue应用中多个组件的共享状态进行集中式的管理(读/写),也是一种组件间通信的方式,且适用于任意组件间通信。
-
何时使用?
多个组件需要共享数据时
-
原理图

-
构建环境
-
安装
npm i vuex -
创建文件
src/store/index.js
//引入Vue核心库
import Vue from 'vue'
//引入Vuex
import Vuex from 'vuex'
//应用Vuex插件
Vue.use(Vuex)
//准备actions对象——响应组件中用户的动作
const actions = {}
//准备mutations对象——修改state中的数据
const mutations = {}
//准备state对象——保存具体的数据
const state = {}
//创建并导出store
export default new Vuex.Store({
actions,
mutations,
state
})
- 在
main.js中创建 vm 时传入 store配置项
//引入store
import store from './store'
new Vue({
render: h => h(App),
store,
beforeCreate() {
Vue.prototype.$bus = this
},
}).$mount('#app')
-
组件中读取vuex中的数据:
$store.state.sum注:这里如果不在index.js中处理数据 而是在store中新建了js文件来处理写数据
读取数据格式应该为:$store.state.存放数据的文件名.sum -
组件中修改vuex中的数据:
$store.dispatch('action中的方法名',数据)或$store.commit('mutations中的方法名',数据)备注:若没有网络请求或其他业务逻辑,组件中也可以越过actions,即不写
dispatch,直接编写commit
案例:
index.js
//引入Vue核心库
import Vue from 'vue'
//引入Vuex
import Vuex from 'vuex'
//应用Vuex插件
Vue.use(Vuex)
//准备actions对象——响应组件中用户的动作
// 业务逻辑(判断,定时器 等)写在 actions 中
const actions = {
addodd(context, value) {
//context为上下文对象(部分的store) value为传递的参数值
if (state.sum % 2) {
context.commit('ADD', value)
}
},
addwait(context, value) {
setTimeout(() => {
context.commit('ADD', value)
}, 1000);
}
}
//准备mutations对象——修改state中的数据
const mutations = {
ADD(state, value) {
// console.log('mutations中的jia被调用了')
state.sum += value
},
DEL(state, value) {
state.sum -= value
},
}
//准备state对象——保存具体的数据
const state = {
sum: 0
}
//创建并暴露store
export default new Vuex.Store({
actions,
mutations,
state
})
组件:
<template>
<div>
<h1>当前求和为{{ $store.state.sum }}</h1>
<select v-model.number="n">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<button @click="add">+</button>
<button @click="del">-</button>
<button @click="addOdd">当前和为奇数时加</button>
<button @click="addWait">等一会加</button>
</div>
</template>
<script>
export default {
data() {
return {
n: 1,
};
},
methods: {
// 直接连接mutations
add() {
this.$store.commit("ADD", this.n);
},
del() {
this.$store.commit("DEL", this.n);
},
addOdd() {
this.$store.dispatch("addodd", this.n);
},
addWait() {
this.$store.dispatch("addwait", this.n);
},
},
};
</script>
getters的使用
-
概念:当state中的数据需要经过加工后再使用时,可以使用getters加工
-
在store.js中追加getters配置
const getters = {
bigSum(state){
return state.sum * 10
}
}
//创建并导出store
export default new Vuex.Store({
......
getters
})
- 组件中读取数据:
$store.getters.bigSum
map
-
导入
import {mapState, mapGetters, mapActions, mapMutations} from 'vuex' -
mapState方法:用于帮助我们映射state中的数据为计算属性
computed: { /* // 普通的计算属性 sum() { return this.$store.state.sum; }, address() { return this.$store.state.address; }, day() { return this.$store.state.day; }, */ //借助mapState生成计算属性:sum、school、subject(对象写法) ...mapState({ sum: "sum", address: "address", day: "day" }), //计算属性名 和 state中的数据名 相同,使用数组写法 ...mapState(["sum", "address", "day"]), } -
mapGetters方法:用于帮助我们映射getters中的数据为计算属性
computed: { //借助mapGetters生成计算属性:bigSum(对象写法) ...mapGetters({bigSum:'bigSum'}), //计算属性名 和 state中的数据名 相同,使用数组写法 ...mapGetters(['bigSum']) }, -
mapMutations方法:用于帮助我们生成与mutations对话的方法,即:包含
$store.commit(xxx)的函数methods:{ /*普通事件回调函数 add() { this.$store.commit("ADD", this.n); }, del() { // this.sum -= this.n; this.$store.commit("DEL", this.n); },*/ //靠mapActions生成:increment、decrement(对象形式) ...mapMutations({ add: "ADD", del: "DEL" }), //函数名与 mutations中的函数名相同,使用数组写法 ...mapMutations(['ADD','DEL']), } -
mapActions方法:用于帮助我们生成与actions对话的方法,即:包含
$store.dispatch(xxx)的函数methods: { /* addOdd() { this.$store.dispatch("addodd", this.n); }, addWait() { this.$store.dispatch("addwait", this.n); }, */ //靠mapActions生成:incrementOdd、incrementWait(对象形式) ...mapActions({ addOdd: "addodd", addWait: "addwait" }), //函数名与 actions中的函数名 相同,使用数组写法 ...mapActions(['addodd','addwait']) },
备注:mapActions与mapMutations使用时,若需要传递参数需要:在模板中绑定事件时传递好参数,否则传的参数是事件对象(event)。
<button @click="add(n)">+</button>
<button @click="del(n)">-</button>
<button @click="addOdd(n)">当前和为奇数时加</button>
<button @click="addWait(n)">等一会加</button>
7.模块化+命名空间
-
目的:让代码更好维护,让多种数据分类更加明确。
-
修改
store.jsconst 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'}),
路由
理解: 一个路由(route)就是一组映射关系(key - value),多个路由需要路由器(router)进行管理。
前端路由:key是路径,value是组件。
- 基本使用
-
安装vue-router,命令:
npm i vue-router -
应用插件:(
main.js)// 引入VueRouter import VueRouter from "vue-router"; // 引入路由器 import router from "./router"; Vue.use(VueRouter) new Vue({ render: h => h(App), router: router, }).$mount('#app') -
编写 router 配置项:(
router/index.js)//引入VueRouter import VueRouter from 'vue-router' //引入Luyou 组件 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>
几个注意点
- 路由组件通常存放在
pages文件夹,一般组件通常存放在components文件夹。 - 通过切换,“隐藏”了的路由组件,默认是被销毁掉的,需要的时候再去挂载。
- 每个组件都有自己的
$route属性,里面存储着自己的路由信息。 - 整个应用只有一个router,可以通过组件的
$router属性获取到。
多级路由
- 配置路由规则,使用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>
路由的query参数
- 传递参数
<template>
<div>
<ul>
<li v-for="m in messageList" :key="m.id">
<!-- 跳转并携带query参数,to的字符串写法 -->
<!-- <router-link :to="`/home/message/detail?id=${m.id}&title=${m.title}`">
{{ m.title }}</router-link> -->
<!-- 跳转并携带query参数,to的对象写法 -->
<router-link
:to="{
path: '/home/message/detail',
query: {
id: m.id,
title: m.title,
},
}"
>{{ m.title }}</router-link
>
</li>
</ul>
<hr />
<router-view></router-view>
</div>
</template>
- 接收参数
<template>
<div>
<li>消息编号:{{ $route.query.id }}</li>
<li>消息标题:{{ $route.query.title }}</li>
</div>
</template>
命名路由
-
作用:可以简化路由的跳转。
-
如何使用
- 给路由命名:
routes: [ { path: '/about', component: About }, { path: '/home', component: Home, //二级路由 children: [ { path: 'news', component: News, }, { path: 'message', component: Message, children: [ { name: 'hello', //给路由命名 path: 'detail', component: Detail, }, ] } ] }, ]-
简化跳转:
<!--简化前,需要写完整的路径 --> <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>
路由的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
路由的props配置
作用:让路由组件更方便的收到参数
- 路由:
children: [
{
name: 'hello', //给路由命名
path: 'detail/:id/:title',
component: Detail,
//第一种写法:props值为对象,该对象中所有的key-value的组合最终都会通过props传给Detail组件
// 只能传固定参数
// props: { a: 900 }
//第二种写法:props值为布尔值,布尔值为true,则把路由收到的所有params参数通过props传给Detail组件
// 只适用于params传参
// props: true
//第三种写法:props值为函数,该函数返回的对象中每一组 key-value 都会通过 props 传给Detail组件
props($route) {
return {
id: $route.query.id,
title: $route.query.title
}
}
},
]
- 跳转去的组件
<template>
<div>
<li>消息编号:{{ id }}</li>
<li>消息标题:{{ title }}</li>
</div>
</template>
<script>
export default {
name: "Detail",
props: ["id", "title"],
};
</script>
<router-link>的replace属性
-
作用:控制路由跳转时操作浏览器历史记录的模式
-
浏览器的历史记录有两种写入方式:分别为
push和replace,
push是追加历史记录,
replace是替换当前记录。路由跳转时候默认为push -
如何开启
replace模式:<router-link replace .......>News</router-link>
编程式路由导航
-
作用:不借助
<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() //可前进也可后退,传具体参数,负数表示后退,正数表示前进
缓存路由组件
-
作用:让不展示的路由组件保持挂载,不被销毁。
-
具体编码:
<!-- 缓存多个路由组件 --> <keep-alive :include="['News','Message']"> <router-view></router-view> </keep-alive> <!-- 缓存一个路由组件 --> <keep-alive include="News"> <router-view></router-view> </keep-alive>注:
-
include="组件名"
-
如果没有 include 那么所有的路由组件都缓存(子路由组件)
-
两个新的生命周期钩子
-
作用:路由组件所独有的两个钩子,用于捕获路由组件的激活状态。
-
具体名字:
-
activated路由组件被激活时触发。 -
deactivated路由组件失活时(切换到别的组件)触发。
-
路由守卫
- 作用:对路由进行权限控制
-
全局守卫:
全局前置守卫:
router.beforeEach((to,from,next)全局后置守卫:
router.afterEach((to,from)//全局前置守卫:初始化时执行、每次路由切换前执行 //to:到哪去,from:从哪来,next:放行 router.beforeEach((to,from,next)=>{ //判断当前路由是否需要进行权限控制 if(to.meta.isAuth){ //权限控制的具体规则 if(localStorage.getItem('school') === 'atguigu'){ next() //放行 }else{ alert('暂无权限查看') } }else{ next() //放行 } }) //全局后置守卫:初始化时执行、每次路由切换后执行 router.afterEach((to,from)=>{ console.log('afterEach',to,from) if(to.meta.title){ document.title = to.meta.title //修改网页的title }else{ document.title = 'vue_test' } })const router = new VueRouter({ routes: [ { path: '/home', component: Home, meta: { title: '主页' },//网页标题 children: [ { path: 'news', component: News, //可以将自定义的一些属性放入 //isAuth:组件是否有权限设置 meta: { isAuth: true, title: '新闻' } }, { path: 'message', name: 'mess', component: Message, meta: { isAuth: true, title: '消息' }, children: [{...}] } ] } ] }) -
独享守卫:
独属于某一个路由的,写在路由组件里
beforeEnter(to, from, next){}const router = new VueRouter({ routes: [ { path: '/home', component: Home, meta: { title: '主页' },//网页标题 children: [ { path: 'news', component: News, //可以将自定义的一些属性放入 //isAuth:组件是否有权限设置 meta: { isAuth: true, title: '新闻' } }, { path: 'message', name: 'mess', component: Message, meta: { isAuth: true, title: '消息' }, //独享路由守卫,在路由组件里 beforeEnter(to, from, next) { if (to.meta.isAuth) { //判断当前路由是否需要进行权限控制 if (localStorage.getItem('school') === 'atguigu') { next() } else { alert('暂无权限查看') // next({name:'guanyu'}) } } else { next() } }, children: [{}] } ] } ] })注:可以和全局后置守卫配合使用 -
组件内守卫:
在具体组件内写守卫
//进入守卫:通过路由规则,进入该组件时被调用 beforeRouteEnter (to, from, next) { }, //离开守卫:通过路由规则,离开该组件时被调用 beforeRouteLeave (to, from, next) { }<script> export default { name: "Home", beforeRouteEnter(to, from, next) { if (to.meta.isAuth) { //判断当前路由是否需要进行权限控制 if (localStorage.getItem("school") === "atguigu") { next(); } else { alert("暂无权限查看"); // next({name:'guanyu'}) } } else { next(); } }, beforeRouteEnter(to, from, next) { next(); }, }; </script>
路由器的两种工作模式
-
对于一个url来说,什么是hash值?—— #及其后面的内容就是hash值。
-
hash值不会包含在 HTTP 请求中,即:hash值不会带给服务器。
-
hash模式:
-
. 地址中永远带着#号,不美观 。
-
. 若以后将地址通过第三方手机app分享,若app校验严格,则地址会被标记为不合法。
-
. 兼容性较好。
-
-
history模式:
- . 地址干净,美观 。
- . 兼容性和hash模式相比略差。
- . 应用部署上线时需要后端人员支持,解决刷新页面服务端404的问题。
Vue UI组件库
移动端:
vant: http://vant3.uihtm.com/#/zh-CN
Cube UI:https://didi.github.io/cube-ui/#/zh-CN
Mint UI:https://mint-ui.github.io/#!/zh-cn
PC端:
Element UI:https://element.eleme.cn/
IView UI:https://www.iviewui.com/









浙公网安备 33010602011771号