第一章节 初始 Vue.js
- Vue 是一套用于构建用户界面的渐进式前端框架
- 易学易用,性能出色,适用场景丰富的 Web 前端框架
Vue 的特点:
- 采用组件化模式,提高代码复用性,且让代码更好维护
- 声明式编码,让编码人员无需直接直接操作 DOM,提高开发效率
- 在使用了 vue 的页面中, vue 会监听数据的变化,从而自动重新渲染页面的结构
- 注意: 数据驱动视图是单向的数据绑定
- 双向数据绑定,可以辅助开发者在不操作 DOM 的情况下,自动把用户填写的内容同步到数据源中
- MVVM 是 vue 实现数据驱动视图和数据双向绑定的核心原理
- Model 表示当前页面渲染时所依赖的数据源 --- 【data中的数据】
- View 表示当前页面所渲染的 DOM 结构 ---【模板视图】
- ViewModel 表示 vue 的实例,它是 MVVM 的核心 ---【Vue实例对象】
- ViewModel 作为 MVVM的核心,它把当前页面的数据源【Model】和页面的结构【View】连接在了一起
- 当数据源发生变化时,会被 ViewModel 监听到, 它会根据最新的数据源自动更新页面中的结构
- 当表单元素的值发生改变时,也会被ViewModel监听到,会把变化过后的最新值自动同步到 Model 数据源中
- vue 中的数据代理,通过 VM 实例对象来进行代理 data 对象中属性的操作【读/写】
- 它的基本原理,通过 Object.defineProperty() 把 data 对象中所有属性添加到 VM 实例上
为每一个添加到 vm 实例上的属性,都指定一个 getter/setter
在 getter/setter 内部去操作【读/写】data 中对应的属性
第二章: 走入Vue.js 的世界
- 使用步骤
- 在Vue官网下载好 vue.js 的脚本文件 【开发版本】
- 导入 vue.js 的文件
- 在页面中声明一个将要被 vue 所控制的 DOM 区域
- 创建 VM 实例对象【vue 实例对象】
<body>
<!-- vue 控制的区域内容 -->
<div id="app">
<h1>{{username}}</h1>
</div>
<!-- 导入 vue.js 文件 -->
<script src="./lib/vue.js"></script>
<!-- 创建 Vue 的实例对象 -->
<script>
Vue.config.productionTip = false // 阻止 vue 启动时产生的提示
const vm = new Vue({
// el 表示是需要挂载的页面中的哪个区域
el: "#app",
// data 对象表示就是需要渲染早页面上的数据
data: {
username: '好好努力,必将成功!!!'
}
})
</script>
</body>
指令的概念:
- 指令(Directives)是 vue 为开发者提供的模板语法,用于辅助开发者渲染页面的基本结构
内部渲染指令:
- 内部渲染指令用来辅助开发者渲染 DOM 元素的文本内容
- v-text 文本渲染指令,它只会进行文本的渲染,会覆盖元素内部的原有内容
- {{ }} 插值表达式,专门用来解决v-text会覆盖默认文本的问题,可以写任何合法的JS表达式
- 注意:在使用插值表达式的时候会有一个小小的问题,在插值表达式渲染内容的时候会存储内容闪烁的问题,因此可以使用 v-cloak 指令进行解决
- 最后需要在样式表中写入: [v-cloak] {display:none} 即可
- v-html 渲染 HTML 标签中的内容,注意:有安全性的问题
属性绑定指令
- 如果需要为元素的属性动态绑定属性值,则需要用的 v-bind 指令绑定属性【可以简写为 :】
事件绑定指令
- vue 提供了v-on 事件绑定指令,用来为 DOM 元素绑定事件监听【可以简写为 @】
- 事件修饰符
- vue 提供了 事件修饰符的概念,对事件的触发进行控制,常见的事件修饰符如下:
- .prevent 阻止默认行为
- .stop 阻止事件冒泡
- .capture 以捕获模式触发当前的事件处理函数
- .once 绑定的事件只触发一次
- .self 只有在 event.target 是当前元素自身时触发事件处理函数
- 按键修饰符
- 1、.enter,可捕获enter键
- 2、.tab,可捕获tab键;
- 3、.delete,可捕获“删除”和“退格”按键;
- 4、.esc,可捕获取消键;
- 5、.space,可捕获空格键
- 6、.up等
- Vue.config.keyCodes.自定义按键名 = keycode码, 可以控制按键的别名
双向绑定指令
- vue 提供了 **v-model** 双向数据绑定指令,用于在不操作 DOM 的前提下,快速获取表单数据
- v-model 指令修饰符
- .number 自动将用户输入的值转为数值类型
- .trim 自动过滤用户输入的首尾空白字符
- .lazy 在"change"时,而非"input"时更新
- 注意:只有表单元素才可以使用 v-model 指令
条件渲染指令
- 条件渲染指令用来辅助开发者按需控制 DOM 的显示和隐藏
-
v-if 每次动态创建或移除元素,来实现元素的显示或移除 - 如果刚进入页面时,某些元素默认不需要被展示,而且后期也很少会进行展示,那么使用 v-if 性能会更好
-
v-else-if 顾名思义,用于充当 v-if 的“else-if”块 指令必须结合 v-if 指令进行使用
-
v-show 动态的为元素添加或移除 display:none 样式,来实现元素的显示或隐藏
- 如果需要频繁的切换元素的显示状态,则用 v-show 性能可能会更好
-
列表渲染指令
- vue 提供了v-for列表渲染指令,用于对列表进行渲染,指令需要使用 item in items 形式的特殊语法
items 是待循环的数组
item 是被循环每一项元素
- 注意: 在使用 v-for 列表渲染的时候必须要绑定 key 属性,它的值必须是唯一的 - key 的值可以是字符串或数字类型 - 建议把数据项的 id 属性作为 key 的唯一标识
章节练习:【指令练习】
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>指令学习</title>
<link rel="stylesheet" href="./css/bootstrap.css">
<style>
[v-cloak] {
display: none;
}
</style>
</head>
<body>
<div id="app">
<h1 v-cloak>{{userName}}</h1>
<input type="text" v-model="userName">
<h2 v-text="text"></h2>
<div v-html="info"></div>
<input type="text" :placeholder="tips">
<p v-cloak>count: {{count}}</p>
<button @click="add($event,1)">点击+1</button>
<p v-if="flag">这是被v-if控制的元素</p>
<p v-show="flag">这是被v-show控制的元素</p>
<hr />
<p v-if="score === 'A'">优秀</p>
<p v-else-if="score === 'B'">良好</p>
<p v-else-if="score === 'C'">一般</p>
<p v-else=>差劲</p>
<hr />
<table class="table table-bordered table-hover table-striped">
<thead>
<th>id</th>
<th>名称</th>
<th>价格</th>
</thead>
<tbody>
<tr v-for="item in list" :key="item.id">
<td>{{item.id}}</td>
<td>{{item.name}}</td>
<td>{{item.price}}</td>
</tr>
</tbody>
</table>
</div>
<script src="./lib/vue.js"></script>
<script>
Vue.config.productionTip = false // 阻止 vue 启动时产生的提示
const vm = new Vue({
el: '#app', // 用来挂载页面上 vue 用于操作的区域容器节点
data: { // data 对象用于存储渲染在页面上数据
userName: '富贵',
text: '花开富贵',
info: '<h2>好远连连,花开富贵</h2>',
tips: '输入用户名',
count: 0,
// 如果 flag 为 true,则显示元素,为false则隐藏元素
flag: true,
score: "A",
list: [
{id: '001', name: '华为', price: '¥6888.88'},
{id: '002', name: '小米', price: '¥1988.88'},
{id: '003', name: '苹果', price: '¥9999.99'}
]
},
methods: { // methods 用于定义事件的处理函数
add(event,n) {
this.count += n
event.target.style.backgroundColor = 'red'
}
}
})
</script>
</body>
</html>
自定义指令
- 在 Vue 中可以通过 directives 节点下声明定义私有自定义指令
- 有以下回调函数:【参数:element, binding】
- bind:只调用一次,指令第一次绑定到元素时调用
- inserted: 当绑定的元素插入到父节点时调用
- update: 所在组件的 VNode 更新时调用
- 有以下回调函数:【参数:element, binding】
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
[v-cloak] {
display: none;
}
</style>
</head>
<body>
<!--
1. 需求1:自定义一个指令,v-big 和 v-text 功能类似,但是会把绑定的数值放大10倍
-->
<div id="app">
<h1 v-cloak>当前值:<span v-text="n"></span></h1>
<h1 v-cloak>放大10倍后:<span v-big="n"></span></h1>
<button @click="n++">点击+1</button>
<hr />
<input type="text" v-fbind:value="n">
</div>
<script src="./lib/vue.js"></script>
<script>
Vue.config.productionTip = false
// 全局 自定义指令
/* Vue.directive('指令名称', function(element,binding){
}) */
const vm = new Vue({
el: "#app",
data: {
n: 1
},
// 定义私有 自定义指令
directives: {
// 指定绑定成功时调用, 指定所在的模板被重新解析时
big(element, binding) {
element.innerText = binding.value * 10
},
fbind: {
// 指令与元素绑定成功时调用
bind(element, binding) {
element.value = binding.value
},
// 指令所在元素被插入页面时调用
inserted(element, binding) {
element.focus()
},
// 指令所在的模板被重新解析时调用
update(element, binding) {
element.value = binding.value
}
}
}
})
</script>
</body>
</html>
章节三 生命周期【过滤器,计算属性】学习
过滤器
- 过滤器【filters】 是 vue 为开发者提供的功能,常用于 文本的格式化操作,可以用在【差值表达式、属性绑定】
- 过滤器被添加在 JS 表达式的尾部,通过管道符进行调用,用法如下: 【过滤器中需要有返回值】
- 注意: 在 Vue3.x 版本中剔除了过滤器的相关功能,官方建议使用【计算属性、方法】代替
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
[v-cloak] {
display: none;
}
</style>
</head>
<body>
<div id="app">
<!--使用过滤器 | 过滤器名称 -->
<p v-cloak>{{message | capital}}</p>
</div>
<script src="./lib/vue.js"></script>
<script>
Vue.config.productionTip = false
// 定义全局过滤器
Vue.filter('capital', (str) => {
const first = str.charAt(0).toUpperCase()
const other = str.slice(1)
return first + other
})
const vm = new Vue({
el: '#app',
data: {
message: 'hello vue.js'
},
methods: {},
// 过滤器函数必须定义在 filters 节点之下
/* filters: {
// 过滤器中,一定要有返回值
// 过滤器中的新式参数,永远都是“管道符”前面的那个值
capital(val) {
const first = val.charAt(0).toUpperCase()
const other = val.slice(1)
return first + other
}
} */
})
</script>
</body>
</html>
侦听器【watch】
- watch 侦听器 允许开发者监视数据的变化,从而针对数据的变化做特定的操作
- 当被侦听的属性变化时,回调函数自动调用,进行相关操作
- 侦听的属性必须存在,才能进行监视!!
- 侦听器的两种写法:
- new Vue 实例时传入 watch 配置
- 通过 vm.$watch 监视
- immediate 选项,控制侦听器是否自动触发一次【false、true】
- Vue 中的 watch 默认是不检测对象内部值的改变
- 可以通过 deep 选项,让侦听器监听对象中每个属性的变化
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
[v-cloak] {
display: none;
}
</style>
</head>
<body>
<div id="app">
<h2 v-cloak>今天天气很{{info}}</h2>
<button @click="changeWeather">切换天气</button>
</div>
<script src="./lib/vue.js"></script>
<script>
Vue.config.productionTip = false
new Vue({
el: "#app",
data:{
isHot: true
},
methods: {
changeWeather() {
this.isHot = !this.isHot
}
},
computed: {
info() {
return this.isHot ? '炎热' : '凉爽'
}
},
watch: {
isHot: {
immediate: true, // 初始化时让 handler 调用一下
// handler 什么时候调用? 当 isHot 发生改变时
handler(newValue,oldValue) {
console.log('isHot被修改了:', newValue, oldValue)
}
}
/*
简写
isHot(newValue,odlValue) {
console.log('isHot被修改了:', newValue, oldValue)
}
*/
}
})
</script>
</body>
</html>
计算属性【computed】
- 定义: 要用的属性不存在,要通过已有属性计算得来
- 原理: 底层借助了 Object.defineProperty() 方法提供的 getter 和 setter
- get 函数什么时候被执行?
- 初次读取时会执行一次
- 当依赖的数据发生变化时会被再次调用
- 优势: 与 methods 实现相比,内部有缓存机制, 效率更高【调用方便】
- 计算属性最终会被渲染到 vm 实例上,直接读取使用即可
- 如果计算属性要被修改,那么必须写 set 函数去响应修改后的结果,且 set 中要引起计算时依赖的数据发生改变
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
[v-cloak] {
display: none;
}
</style>
</head>
<body>
<div id="app">
值1: <input type="text" v-model="num1"><br/><br/>
值2: <input type="text" v-model="num2"><br/><br/>
结果: <span v-cloak>{{count}}</span>
</div>
<script src="./lib/vue.js"></script>
<script>
Vue.config.productionTip = false
const vm = new Vue({
el: "#app",
data: {
num1: '',
num2: ''
},
computed: {
count: {
// get 有什么作用? 当 count 被读取时,get 就会被触发调用,且返回值就作为 count的值
// get 什么时候被调用? 1. 初次读取 count 时, 2. 所依赖的数据发生变化时
get() {
console.log('被调用了')
let result = parseFloat(this.num1) + parseFloat(this.num2) || ''
return result
},
// set 什么时候调用? 当 count 被修改时
set(value) {
console.log('set',value)
const arr = value.split('-')
this.num1 = arr[0]
this.num2 = arr[1]
}
}
/*
简写版
count() {
console.log('被调用了')
let result = parseFloat(this.num1) + parseFloat(this.num2) || ''
return result
}
*/
}
})
</script>
</body>
</html>
样式绑定
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
[v-cloak] {
display: none;
}
.red {
color: red;
}
.italic {
font-style: italic;
}
.small {
font-size: 16px;
}
</style>
</head>
<body>
<div id="app">
<!-- 为元素绑定数组的类样式 -->
<h1 :class="['red','small']" v-cloak>{{msg}}</h1>
<hr />
<!-- 在数组中使用三元表达式 -->
<h1 v-cloak :class="['red',flag ? 'italic':'']">{{msg}}</h1>
<hr />
<!-- 传递对象作为类样式 -->
<h1 v-cloak :class="classObj">{{msg}}</h1>
<hr/>
<!-- 动态绑定 style 行内样式 -->
<h1 v-cloak :style="{color: 'red'}">{{msg}}</h1>
</div>
<script src="./lib/vue.js"></script>
<script>
Vue.config.productionTip = false
const vm = new Vue({
el: "#app",
data: {
msg: '花开富贵',
flag: false,
classObj: { red: true, italic: true }
}
})
</script>
</body>
</html>
生命周期说明
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
[v-cloak] {
display: none;
}
</style>
</head>
<body>
<div id="app">
<h2 v-cloak>当前的值是:{{n}}</h2>
<button @click="n++">点击+1</button>
<button @click="bye">销毁vm</button>
</div>
<script src="./lib/vue.js"></script>
<script>
Vue.config.productionTip = false
new Vue({
el: "#app",
data: {
n: 1
},
methods: {
bye() {
console.log('bye~')
this.$destroy()
}
},
// 将要创建 vm 实例之前调用
beforeCreate() {
console.log('beforeCreate')
},
// 创建完毕 vm 实例后调用
created() {
console.log('created')
},
// 将要挂载实例时调用
beforeMount() {
console.log('beforeMount')
},
// 实例挂载完毕时调用
mounted() {
console.log('mounted')
},
// 将要更新实例之前调用
beforeUpdate() {
console.log('beforeUpdate')
},
// 实例更新完毕后调用
updated() {
console.log('updated')
},
// 实例将要销毁前调用
beforeDestroy() {
console.log('beforeDestroy')
},
// 实例销毁后调用
destroyed() {
console.log('destroyed')
},
})
</script>
</body>
</html>
创建阶段的生命周期钩子函数:
- 在 new Vue() 实例之前 会进行 初始化操作:【init Events & Lifecycle】事件、生命周期,但是数据代理并未开始
- beforeCrate: 实例刚在内存中被创建出来,此时并未初始完毕【没有 data 和 methods 属性】
- 此时会进入【init injections & reactivity】初始化:数据检测、数据代理
- created: 实例已经在内存中创建完毕了,此时 vm 中有了data 和 methods 属性也准备就绪,
【此阶段 Vue 实例开始解析模板,生成虚拟DOM,但是页面还不能显示解析好的内容】
- beforeMount: 此时已经完成了对模板的编译,但是并未挂载到页面中
【页面中呈现的是未经过Vue编译的DOM结构,所有对DOM的操作,最终都不会生效】
【将内存中的虚拟DOM转为真实的DOM插入页面】
- mounted: 此时,已经将编译好的模板,挂载到了页面指定的容器中进行显示
【至此初始化过程结束,一般在此执行: 开启定时器,发送网络请求,订阅消息,绑定自定义事件等】
- 更新期间的生命周期钩子函数:
- beforeUpdate: 状态更新之前被执行,此时 data 中的状态值是最新的,但是界面上的数据显示还是之前的
【还没有进行重新渲染DOM操作,根据新的数据,生成新的虚拟DOM进行比较,最终完成页面的更新[Model--->View 的更新]】
- updated: 实例更新完毕之后调用此函数,此时 data 中的状态值和界面上显示的数据,都已经完成了更新操作【最新值】
- 销毁期间的生命周期钩子函数:
- beforeDestroy: 实例销毁之前调用,此时Vue的实例仍然是可以使用的
【在此阶段:一般用于做, 关闭定时器、取消订阅消息、解绑自定义事件等收尾操作】
- destroy: Vue 实例销毁后调用
- 常用的生命周期钩子函数:
- mounted: 发送 Ajax 请求,启动定时器、绑定自定义事件、发布订阅消息 等初始化操作
- beforeDestroy: 清除定时器、解绑自定义事件、取消发布订阅消息 等收尾操作
- 注意: 一般是不会在 beforeDestroy 操作数据,因为在此阶段是不会触发更新流程了
章节四 组件化编程
- 组件的定义:实现应用中局部功能代码和资源的集合
- 组件的本质是一个名为:VueComponent 的构造函数,且不是被程序员定义的,是 Vue.extend 生成的
- 我们只需要写上组件名称,Vue 解析时会帮我们创建此组件的实例对象,既 Vue 帮我们执行的 new VueComponent(options)
- 特别注意: 每次调用 Vue.extend 返回的都是一个全新的 VueComponent
- 关于 this 指向:
- 组件配置中:
- data 函数、methods 中的函数、watch 中的函数、computed 中的函数,它们的 this 指向均是【VueComponent】
- new Vue() 配置中:
- data 函数、methods 中的函数、watch 中的函数、computed 中的函数,它们的 this 指向均是【Vue实例】
- 组件配置中:
脚手架工具进行Vue的项目创建
-
Vue 脚手架是 Vue官方提供的标准化开发工具【VueCLI】
-
使用步骤:
-
- 如果说下的速度比较慢,可以使用 npm 淘宝镜像 npm config set registry registry.npm.taobao.org
-
- 全局安装 @vue/cli npm install -g @vue/cli【安装完毕后使用=命令: vue-V 检测是否安装ok】
-
- 切换到需要创建项目的目录,使用命令创建项目: vue create 项目名称
-
- 选择使用的 Vue 的版本,进行创建得到构建
-
ref 属性 学习
- 被用来给元素或子组件注册引用信息(id的替代者)
- 应用在 HTML 标签上获取的是真实 DOM 元素,应用在组件标签上就是该组件的实例对象【VueComponent】
- 使用方式:
- 打标识: <h1 ref="xxx">内容</h1> 或者 <School ref="xxx" />
- 获取: this.$refs.xxx
-
ref 案例
<template>
<div>
<h1 v-text="msg" ref="title"></h1>
<button @click="show">点击显示上方的DOM元素</button>
<School />
</div>
</template>
<script>
export default {
name: 'App',
data() {
return {
msg: '欢迎学习Vue'
}
},
methods: {
show() {
console.log(this.$refs.title)
}
}
}
</script>
<style>
</style>
props 学习
- props 它的功能,让该组件接收外部传递过来的数据
- 1. 传递数据:
- <Demo name="xxx"/>
- 2. 接收数据:
- 第一种方式:【只接受】
- props: ['name']
- 第二种方式:【限制类型】
props: {
name: String
}
- 第三种方式:【限制类型、限制必要性、指定默认值】
props: {
name: {
type: String,
required: true,
default: 8
}
}
- 注意: props 是只读的,不允许修改, Vue 底层会检测到对象props的修改,如若进行修改,就会发出警告
-
props 案例
- 在你的 components 文件中 新建一个 .vue 的文件【组件】---Student.vue
- 在你认为是父组件中进行 注册导入
// Student.vue
<template>
<div class="demo">
<h1>{{msg}}</h1>
<h2>学生姓名: {{name}}</h2>
<h2>学生性别: {{sex}}</h2>
<h2>学生年龄: {{age}}</h2>
</div>
</template>
<script>
export default {
name: "School",
data() {
return {
msg: '奇缘课堂,自学者的快乐'
}
},
// 简单声明接收
// props: ['name', 'sex', 'age']
// 接收的同时对类型进行限制
/* props: {
name:Number,
age: Number,
sex: String
} */
// 接收的同时对数据: 进行类型限制 + 默认值的指定 + 必要性的限制
props: {
name: {
type: String, // 数据类型
required: true // 必填项
},
age: {
type: Number,
default: 18
},
sex: {
type: String,
required: true
}
}
}
</script>
<style>
.demo {
background-color: pink;
}
</style>
// 父组件中 【这里我用了 App.vue】做为父组件
<template>
<div>
<!--
使用注册的组件,并进行传值,这个过程叫做父向子传值
注意: :agg='22' 使用 :绑定的属性此时这个属性是一个 number 类型的数据
-->
<School name="小娟儿" sex="女" :age="22"/>
</div>
</template>
<script>
// 导入 子组件
import School from './components/School'
export default {
name: 'App',
components: { // 注册组件
School
}
}
</script>
<style>
</style>
组件间的通信
-
父子组件之间的数据共享
-
父向子传递数据
- 父组件向子组件传递数据需要使用自定义属性
-
- 父组件引入子组件,在子组件上绑定属性传入数据
-
- 子组件通过 props: ['xxx'] 进行接收传递的数据
-
- 父组件向子组件传递数据需要使用自定义属性
<!--父组件-->
<template>
<div class="app-container">
<h1>App 根组件</h1>
<!-- 绑定自定义属性,向子组件传递数据 -->
<Student :msg="message" :user="users"/>
</div>
</template>
<script>
import Student from './components/School'
export default {
data() {
return {
message: 'Hello Vue',
users: { name: '小娟儿', sex: '女', age: 21 }
}
},
components: {Student}
}
</script>
<style lang="less">
.app-container {
padding: 1px 20px 2px;
background-color: #efefef;
}
</style>
<!--子组件-->
<template>
<div class="demo">
<h3>Student组件</h3>
<p>msg 的值是: {{msg}}</p>
<p>user 的值是: {{user}}</p>
</div>
</template>
<script>
export default {
// 子组件通过 props 接收到父组件创建过来的数据
props: ['msg', 'user']
}
</script>
<style>
.demo {
background-color: orangered;
}
</style>
- 子向父传递数据
-
第一种方式: 父组件需要定义一个回调函数,并把这个回调函数传递给子组件,子组件进行接收
-
第二种方式: 子组件向父组件共享数据需要使用自定义事件
- 父组件需要定义一个事件,在子组件需要绑定该事件
- 子组件需要通过, this.$emit('事件名称', 需要传递的数据)
-
<!--父组件-->
<template>
<div class="app-container">
<h1>App 根组件 ---- {{countFromSon}}</h1>
<!-- 自定义 numchange 事件 -->
<Student @numchange="getNewCount"/>
</div>
</template>
<script>
import Student from './components/School'
export default {
data() {
return {
// 用于接收子组件传递过来的数据
countFromSon: 0
}
},
components: {Student},
methods: {
// 获取子组件传递过来的数据
getNewCount(value) {
this.countFromSon = value
}
},
}
</script>
<style lang="less">
.app-container {
padding: 1px 20px 2px;
background-color: #efefef;
}
</style>
<!--子组件-->
<template>
<div class="demo">
<h3>Student组件----- {{count}}</h3>
<button @click="add">点击+1</button>
</div>
</template>
<script>
export default {
data() {
return {
count: 0
}
},
methods: {
add() {
this.count += 1
/* 接收到自定义 numchange 事件,把要进行传递的数据,传递给父组件 */
this.$emit('numchange',this.count)
}
},
}
</script>
<style>
.demo {
background-color: orangered;
}
</style>
- 全局事件总线 【GlobalEventBus】
- 定义全局事件总线: 【在 main.js 文件中】
new Vue({
render: h => h(App),
beforeCreate() {
Vue.prototype.$bus = this // 安装全局事件总线
}
}).$mount('#app')
- 兄弟组件之间的数据传递
- 在 vue2.x 中,兄弟组价之间的数据共享的方案是: 【GlobalEventBus】
- 【GlobalEventBus】 的使用步骤:
- 在数据发送方,调用 bus.$emit('事件名称', 要发送的数据) 方法触发自定义事件
- 在数据接收方,调用 bus.$on('事件名称', 事件处理函数) 方法去注册一个自定义事件
- 【GlobalEventBus】 的使用步骤:
- 注意: 最好是在 beforeDestroy() {} 声明周期函数中 进行自定义事件的解绑 beforeDestroy() { this.$off('自定义事件名称') }
- 在 vue2.x 中,兄弟组价之间的数据共享的方案是: 【GlobalEventBus】
-
兄弟之间数据传递 案例
- 首先准备两个组件【用于作为兄弟组件】
// StudentA.vue
<template>
<div class="demo">
<h2>StudentA组件</h2>
<hr/>
<p>StudentB组件发送的数据是: {{msgFromShool}}</p>
</div>
</template>
<script>
export default {
data() {
return {
msgFromShool: ''
}
},
created() {
// 为 bus 绑定自定义事件
bus.$on('share', (val) => {
this.msgFromShool = val
})
}
}
</script>
<style lang="less" scoped>
.demo {
background-color: aliceblue;
}
</style>
// StudentB.vue
<template>
<div class="demo">
<h2>StudentB组件</h2>
<button @click="send">点击发送数据给Student组件</button>
</div>
</template>
<script>
export default {
data() {
return {
str: '发送给兄弟组件的数据!!!'
}
},
methods: {
send() {
// 通过 eventBus 发送数据
bus.$emit('share', this.str)
}
}
}
</script>
<style lang="less" scoped>
.demo {
background-color: orangered;
}
</style>
nextTick 学习
- 语法: this.$nextTick(callback)
- 作用: 在下一次DOM更新结束后执行其指定的回调【需要借助于 ref】
- 什么时候使用: 当改变数据后,要基于更新后的新DOM进行某些操作时,要在nextTick所指定的回调函数中执行
-
nextTick 场景演示以及使用 案例
<template>
<div class="app-container">
<h1>App 根组件</h1>
<input type="text" v-if="inputVisible" @blur="showButton" ref="iptRef">
<button v-else @click="showInput">展示数据框</button>
</div>
</template>
<script>
export default {
data() {
return {
inputVisible: false
}
},
methods: {
showInput() {
this.inputVisible = true
this.$nextTick(() => {
this.$refs.iptRef.focus()
})
},
showButton() {
this.inputVisible = false
}
}
}
</script>
<style lang="less">
.app-container {
padding: 1px 20px 2px;
background-color: #efefef;
}
</style>
第五章节 学习
- 配置代理 【解决请求跨域问题】
- 在 vue.config.js 中写入以下代码即可
module.exports = {
// 关闭语法检查
lintOnSave: false,
// 开启代理服务器【方式一】
/* devServer: {
proxy: 'http://localhost:6000'
} */
// 开启代理服务器【方式二】
devServer: {
proxy: {
'/api': { // 配置以 '/api' 开头的请求路径
target: 'http://localhost:6000', // 代理目标的基础路径
pathRewrite: {'^/api': ''}, // 重写目录路径
ws: true, // 是否开启 websocket 服务
changeOrigin: true // 是否代理
}
}
}
}
动态路由和动态组件
-
vue 提供了一个内置的 component 组件,专门用来实现动态组件的渲染
- 用法:
<component :is="表示要渲染的组件名字"></component>
- 用法:
-
使用 keep-alive 保持状态,可以把内部的组件进行缓存操作
- 用法:
<keep-alive><component :is="表示要渲染的组件名字"></component></keep-alive>
- 用法:
-
keep-alive 对应的生命周期函数:
-
当组件被缓存时,会自动触发组件的 deactivated 生命周期函数
-
当组件被激活时,会自动触发组件的 activated 生命周期函数
-
keep-alive 的 include 属性 和 exclude 属性
- include 属性用来指定: 只有名称匹配的组件才会被缓存
- exclude 属性是用来排除不被缓存的组件
- 注意: 这两个属性是不能一起使用的,【二选一】
-
<keep-alive include="School,Student">
<component :is="" />
</keep-alive>
-
案例
// Student.vue 组件
<template>
<div class="Student-container">
<h2>Student组件</h2>
</div>
</template>
<script>
export default {
}
</script>
<style scoped>
.Student-container {
background-color: orangered;
}
</style>
// School.vue 组件
<template>
<div class="school-container">
<h2>School组件</h2>
<p>{{num}}</p>
<button @click="num++">+1</button>
</div>
</template>
<script>
export default {
data() {
return {
num: 0
}
},
activated() {
console.log('组件被激活了, activated')
},
deactivated() {
console.log('组件被缓存了,deactivated')
},
};
</script>
<style scoped>
.school-container {
background-color: beige;
}
</style>
// App.vue
<template>
<div class="app-container">
<h1>App组件...</h1>
<hr />
<button @click="comName='School'">展示 School 组件</button>
<button @click="comName='Student'">展示 Student 组件</button>
<div class="box">
<!-- 渲染 School 和 Student 组件 -->
<keep-alive>
<component :is="comName"></component>
</keep-alive>
</div>
</div>
</template>
<script>
import School from "./components/School.vue";
import Student from "./components/Student.vue";
export default {
components: {
School,
Student,
},
data() {
return {
comName: "School",
};
},
};
</script>
<style scoped>
.app-container {
padding-left: 20px;
padding-top: 20px;
padding-right: 20px;
border: 1px solid #efefef;
background-color: aliceblue;
}
</style>
插槽的学习和使用
- 什么是插槽?
- 插槽【Slot】是 vue 为组件的封装者提供的能力,允许开发者在封装组件时,为了以后的组件扩展部分定为插槽
- 作用: 让父组件可以向子组件位置插入HTML结构,也是一种组件之间的通信方式【父组件向子组件】
- 插槽分类: 默认插槽、具名插槽、作用域插槽
- 默认插槽
// 父组件中
<template>
<Game>
<div>html结构1</div>
</Game>
</template>
// 子组件中
<template>
<div>
<!-- 定义插槽 -->
<slot></slot>
</div>
</template>
- 具名插槽
// 父组件中
<template>
<Game>
<template v-slot:game>
<div>html结构2</div>
</template>
</Game>
</template>
// 子组件中
<template>
<div>
<!-- 定义插槽 -->
<slot name="game"></slot>
</div>
</template>
- 作用域插槽
<!-- 父组件中-->
<template>
<Game>
<template scope="{games}">
<!--生成有序列表-->
<ol>
<li v-for="(item,index) in games" v-bind:key="index">{{item}}</li>
</ol>
</template>
</Game>
</template>
<!--子组件中-->
<template>
<div>
<!-- 定义插槽 -->
<slot v-bind:games="games"></slot>
</div>
</template>
<script>
export default {
name: "Category",
data() {
return {
games: ["王者荣耀", "英雄联盟", "穿越火线"]
}
}
}
</script>
第六章 路由学习
-
什么是路由?
- 一个路由就是一组映射关系【key - value】
- key 为路径,value 可能是 function 或 component
-
前端路由的工作方式:
-
- 用户点击了页面上的路由连接
-
- 导致了 URL 地址栏中的 Hash 值的变化
-
- 前端路由监听到了 Hash 地址的改变
-
- 前端路由把当前 Hash 地址对应的组件 渲染到浏览器中
-
vue-router
- vue-router 是 Vue.js 官方提供的路由解决方案,它只能结合 vue 项目进行使用
- 能够轻松的实现出 SPA 项目中的组件切换
- 安装 vue-router: npm install vue-router@3 -S
- 注意:在 Vue2.x 中安装的是 3版本的,否则会出错
- 在 main.js 中导入并使用
import Vue from 'vue'
import App from './App.vue'
// 引入 VueRouter
import VueRouter from 'vue-router'
// 引入路由对象
import router from './router'
Vue.config.productionTip = false
// 使用 VueRouter
Vue.use(VueRouter)
new Vue({
render: h => h(App),
router,
}).$mount('#app')
- 创建 router 文件【src/router/index.js】配置路由
// 引入 VueRouter
import VueRouter from "vue-router"
// 引入组件
import About from '../components/About.vue'
import Home from '../components/Home.vue'
// 创建并暴露路由对象
export default new VueRouter({
routes: [
{ path: '/about', component: About },
{ path: '/home', component: Home }
]
})
-
在需要使用的组件中注册路由实现组件切换:
<router-link to="/要跳转的路径" />
-
指定展示的组件位置
-
<router-view></router-view>
-
嵌套路由
- 嵌套路就是在路由的基础上又进行了一层的嵌套【使用属性: children】
- 在scr文件目录中找到 router 文件夹、index.js
// 引入 VueRouter
import VueRouter from "vue-router"
// 引入组件
import About from '../components/About.vue'
import Home from '../components/Home.vue'
import News from '../components/News.vue'
import Message from '../components/Message.vue'
// 创建并暴露路由对象
export default new VueRouter({
routes: [
{
path: '/about',
component: About
},
{
path: '/home',
component: Home,
// 使用多级路由【路由嵌套】
children: [
{
path: 'news',
component: News
},
{
path: 'message',
component: Message
}
]
}
]
})
路由命名
- 作用: 可以简化路由的跳转
- 使用: 需要给路由命名
// 引入 VueRouter
import VueRouter from "vue-router"
export default new VueRouter({
name: 'demo' // 命名路由
path: '/demo',
component: Demo
})
- 简化跳转
<router-link :to="{name: 'demo'}">跳转内容</router-link>
路由传递参数
- 路由的 query 参数传递
<!-- 跳转路由,并携带 query 参数 [字符串写法]-->
<router-link :to="/home/message/detail?id=8$msg=hello">跳转内容</router-link>
<!-- 跳转路由,并携带 query 参数 [对象写法] -->
<router-link :to="{path: '/home/message/detail', query: id: 888, msg:'hello'}">跳转的内容</router-link>
<!-- 接收参数 -->
$route.query.id
$route.query.msg
<template>
<ul>
<li>消息编号: {{$route.query.id}}</li>
<li>消息标题: {{$route.query.title}}</li>
</ul>
</template>
- 路由的 params 参数传递
-
- 配置路由,需要 params 参数
// 引入 VueRouter
import VueRouter from "vue-router"
export default new VueRouter({
name: 'detail'
path: '/detail/:id/:title', // 使用占位符声明接收 params 参数
component: Detail
})
-
- 传递参数
<!-- 跳转路由,并携带 params 参数 [字符串写法]-->
<!-- <router-link :to="`/home/message/detail/${item.id}/${item.title}`">{{item.title}}</router-link> -->
<!-- 使用 对象形式的时候,需要使用, name 属性进行 路径跳转 -->
<router-link :to="{
name: 'detail',
params: {
id: item.id,
title: item.title
}
}">
{{item.title}}
</router-link>
- 路由的 props 配置
- 作用: 让路由组件更加方便的接收到参数
// 引入 VueRouter
import VueRouter from "vue-router"
export default new VueRouter({
name: 'detail'
path: '/detail',
component: Detail,
// props 的第一种写法,值为对象,改对象中所有的key - value 都会以 props 的形式传递给 Detail 组件
/* props: {
a:1,
b: 'hello'
// props 的第二种写法,值为 布尔值,若值为true,就会把该路由组件接收到的所有params参数,以prop Detail组件
// props 的第三种写法,值为函数
/* props($route) {
return {id: $route.params.id, title: $route.params.title}
} */
props({params:{id,title}}) {
return {id,title}
}
})
- 组件接收 使用 props
<template>
<ul>
<li>消息编号: {{ id }}</li>
<li>消息标题: {{ title }}</li>
</ul>
</template>
<script>
export default {
name: "Detail",
props: ["id", "title"],
};
</script>
- router-link 的replace 属性
- 作用: 控制路由跳转时操作浏览器历史记录的模式
- 浏览器的历史记录有两种: push、replace, push 是追加历史记录,replace 是替换当前记录
编程式路由导航
- 作用: 不借助 router-link 实现路由的跳转,让路由更加的灵活
// $router 的 API
this.$router.push('hash 地址')
this.$router.replace('hash 地址')
this.$router.back() // 后退
this.$router.forward() // 前进
this.$router.go(n) // 前进/后退
路由导航守卫
-
作用: 对路由进行权限控制
-
分类: 全局守卫、独享守卫、组件内守卫
-
全局守卫【前置、后置】 -- 写到 router文件下的,index.js 中
// 全局前置路由守卫【初始化、每次路由切换之前被调用】
router.beforeEach((to,from,next) => {
// to 是将要访问的路由信息对象
// from 是将要离开时路由的信息对象
// next 是一个函数, 调用 next() 表示放行,允许这次路由导航
/*
当前用户没有访问权限,强制其跳转到登录页面: next('/login')
当前用户没有访问权限,不允许进入后台管理主页: next(false)
*/
console.log('前置路由守卫',to,from)
if (to.meta.isAuth) { // 判断是否鉴权
if (localStorage.getItem('Le') === 'Le168168') {
next()
}else {
alert("身份认证失败,您无权限查看!!!")
}
}else {
next()
}
})
// 后置路由守卫【初始化、每次路由切换之后被调用】
router.afterEach((to,from) => {
console.log('后置路由守卫',to,from)
document.title = to.meta.title || '奇缘课堂'
})
章节七 Vuex 学习
-
Vuex 是什么?
- 它是专门在 Vue 中实现集中式状态【数据】管理的一个Vue插件,对于vue应用中多个组件的共享状态进行集中管理【读/写】
- 也是一种组件间的通信方式,且使用于任意组件间通信
-
什么时候使用 Vuex?
- 多个组件依赖于同一状态
- 来自不同组件的行为需要变更为同一状态
-
安装:
npm install vuex@next --save -
搭建 vuex 环境
-
- 创建文件: src/store/index.js
-
// 引入 vue
import Vue from 'vue'
// 引入 vuex
import Vuex from 'vuex'
// 使用 Vuex
Vue.use(Vuex)
// 创建 actions 对象 响应于组件中用户动作
const actions = {
increment(context, value) {
// 通过 commit 触发 mutations
context.commit('Add', value)
}
}
// 创建 mutations 对象 响应于 修改 state 中的数据
const mutations = {
Add(state, value) {
state.num += value
}
}
// 创建 state 对象 响应于具体的数据
const state = {
num: 0
}
// 准备一个 getters 用于将 state 中的数据进行加工
// 当 state 中的数据需要经过加工够在进行使用时,可以使用 getters
// - 组件中读取: $store.getters.bigSum
const getters = {
bigSum(state) {
return state.sum * 10
}
}
// 创建并暴露 Store
export default new Vuex.Store({
actions,
mutations,
state,
getters
})
-
- 在 main.js 中创建 vm 时传入 store 配置项
import Vue from 'vue'
import App from './App.vue'
// 引入 store
import store from './store'
Vue.config.productionTip = false
new Vue({
render: h => h(App),
store,
}).$mount('#app')
- 3. 组件中读取Vuex中的数据: $store.state.sum
- 4. 组件中修改Vuex中的数据: $store.dispatch('actions中的方法名', 数据)/$store.commit('actions中的方法名', 数据)
-
案例
- 在store文件下的 index.js 中
// 该文件用于创建 Vuex 中最为核心的 store
// 引入 Vue
import Vue from 'vue'
// 引入 Vuex
import Vuex from 'vuex'
// 使用插件
Vue.use(Vuex)
// 准备 actions 用于响应组件中的动作
const actions = {
increment(context, value) {
context.commit('increment',value)
},
decrement(context, value) {
if (context.state.sum === 0) return
context.commit('decrement', value)
}
}
// 准备 mutations 用于操作数据
const mutations = {
increment(state, value) {
state.sum += value
},
decrement(state, value) {
state.sum -= value
}
}
// 准备 state 用于存储数据
const state = {
sum: 0,
school: 'ABC',
subject: 'Web前端'
}
// 准备一个 getters 用于将 state 中的数据进行加工
const getters = {
bigSum(state) {
return state.sum * 10
}
}
// 创建并暴露 store
export default new Vuex.Store({
actions,
mutations,
state,
getters
})
<template>
<div class="count-container">
<h1>请求求和为:{{$store.state.sum}}</h1>
<h2>当前求和放大十倍为:{{$store.getters.bigSum}}</h2>
<select v-model.number="n">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<button @click="increment">+</button>
<button @click="decrement">-</button>
<button @click="incrementOdd">请求求和为奇数再加</button>
<button @click="incrementWait">等一等在加</button>
</div>
</template>
<script>
export default {
name: "Count",
data() {
return {
n: 1
}
},
methods: {
increment() {
this.$store.dispatch('increment',this.n)
},
decrement() {
this.$store.dispatch('decrement',this.n)
},
incrementOdd() {
if (this.$store.state.sum %2 != 0) {
this.$store.dispatch('increment',this.n)
}
},
incrementWait() {
setTimeout(() => {
this.$store.dispatch('increment',this.n)
}, 500)
}
}
}
</script>
<style scoped>
button {
margin-left: 8px;
}
.count-container {
background-color: orange;
}
</style>
- mapState 和 mapGetters 方法的使用
// 引入 mapState 和 maoGetters
import {mapState, mapGetters} from 'vuex'
computed: {
// 借助 mapState 生成计算属性,从 state 中读取数据【对象写法】
// ...mapState({sum:'sum', school:'school', subject:'subject'})
...mapState(['sum','school','subject']),
// 借助 mapGetters 生成计算属性,从 getters 中读取数据
...mapGetters(['bigSum'])
}
<template>
<div class="count-container">
<h1>请求求和为:{{sum}}</h1>
<h2>当前求和放大十倍为:{{bigSum}}</h2>
<h3>在{{school}}, 学习{{subject}}</h3>
<select v-model.number="n">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<button @click="increment">+</button>
<button @click="decrement">-</button>
<button @click="incrementOdd">请求求和为奇数再加</button>
<button @click="incrementWait">等一等在加</button>
</div>
</template>
<script>
import {mapState,mapGetters} from 'vuex'
export default {
name: "Count",
data() {
return {
n: 1
}
},
methods: {
increment() {
this.$store.dispatch('increment',this.n)
},
decrement() {
this.$store.dispatch('decrement',this.n)
},
incrementOdd() {
if (this.$store.state.sum %2 != 0) {
this.$store.dispatch('increment',this.n)
}
},
incrementWait() {
setTimeout(() => {
this.$store.dispatch('increment',this.n)
}, 500)
}
},
computed: {
// 借助 mapState 生成计算属性,从 state 中读取数据【对象写法】
// ...mapState({sum:'sum', school:'school', subject:'subject'})
...mapState(['sum','school','subject']),
// 借助 mapGetters 生成计算属性,从 getters 中读取数据
...mapGetters(['bigSum'])
}
}
</script>
<style scoped>
button {
margin-left: 8px;
}
.count-container {
background-color: orange;
}
</style>
- mapActions 和 mapMutations 方法的使用
// 引入 mapState 和 maoGetters
import {mapState, mapGetters, mapActions, mapMutations} from 'vuex'
methods: {
// 借助 mapMutations 生成对应的方法,方法中会去调用 commit 去联系 mutations 对象
// ...mapMutations({increment:'increment', decrement:'decrement'}),
...mapMutations(['increment','decrement']),
// 借助 mapActions 生成对应的方法,方法中会去调用 dispatch 联系 actions 对象
...mapActions(['incrementOdd', 'incrementWait'])
/* incrementOdd() {
if (this.$store.state.sum %2 != 0) {
this.$store.dispatch('increment',this.n)
}
},
incrementWait() {
setTimeout(() => {
this.$store.dispatch('increment',this.n)
}, 500)
} */
},
- 注意: mapActions 和 mapMutations 使用时,若需要传递参数,需要在模板中绑定事件时传递好参数,否则就是事件对象