vue2.0&vue3.0
Vue 介绍
什么是 vue ?
- 构建用户界面
- 用 vue 往 html 页面中填充数据,非常的方便
- 框架
- 框架是一套现成的解决方案,程序员只能遵守框架的规范,去编写自己的业务功能!
- 要学习 vue,就是在学习 vue 框架中规定的用法!
- vue 的指令、组件(是对 UI 结构的复用)、路由、Vuex、vue 组件库
- 只有把上面老师罗列的内容掌握以后,才有开发 vue 项目的能力!
vue 的两个特性
-
数据驱动视图:
- 数据的变化会驱动视图自动更新
- 好处:程序员只管把数据维护好,那么页面结构会被 vue 自动渲染出来!
-
双向数据绑定:
在网页中,form 表单负责采集数据,Ajax 负责提交数据。
- js 数据的变化,会被自动渲染到页面上
- 页面上表单采集的数据发生变化的时候,会被 vue 自动获取到,并更新到 js 数据中
注意:数据驱动视图和双向数据绑定的底层原理是 MVVM(Mode 数据源、View 视图、ViewModel 就是 vue 的实例)
MVVM
MVVM 是 vue 实现数据驱动视图和双向数据绑定的核心原理。
MVVM 的工作原理
ViewModel
作为 MVVM 的核心,是它把当前页面的数据源
(Model)和页面的结构
(View)连接在了一起。
当数据源发生变化时,会被 ViewModel 监听到,VM 会根据最新的数据源自动更新页面的结构。
当单元素的值发生变化表时,也会被 VM 监听到,VM 会把变化过后最新的值自动同步到 Model 数据源中
起步
第一个Vue程序:
- 导入开发版本的 Vue.js
- 创建 Vue实例对象,设置
el
属性和data
属性 - 使用简洁的模板语法
把数据渲染到页面
上
<body>
<div id="app">
{{message}}
</div>
</body>
12345
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
// 创建 Vue 实例对象
var app = new Vue({
// 指定当前 app 实例要控制页面的哪个区域,接收的值是一个选择器
// el 属性是固定的写法
el:'#app',
// 指定 Model 数据源,data对象就是要渲染到页面上的数据
data:{
message:'Hello Vue!'
}
})
</script>
12345678910111213
基本代码与 MVVM 的对应关系
图丢了
插件库
day.js 快速日期格式化
解决格式化Vue文件时 逗号、分号问题 :
在根目录中,添加一个 .prettierrc.json
配置文件,写入:
{
"singleQuote": true,
"semi": false,
"trailingComma": "none"
}
12345
一劳永逸方法:
-
在电脑上的用户目录下新建一个
.prettierrc.json
文件,同理上面的参数。 -
在 **Vs code **的
setting.json
中添加配置路径:// 设置 配置文件,解决格式化vue文件时逗号、分号爆红问题 // 这里注意配置路径,记得使自己计算机上的用户名 "prettier.configPath": "C:/Users/mi/.prettierrc.json", 12345
解决格式化时函数括号前的空格问题 :
- Prettier 格式化插件无法处理函数括号前添加空格问题
解决办法一、在当前项目里的 .eslintrc.js
里 rules 规则里添加忽略加空格爆红提示 :
'space-before-function-paren':['error','never']
1
解决办法二: 使用 Prettier now
插件可以解决此问题。
vue 指令
1. 内容渲染指令
v-text
设置标签的内容, 默认写法会 覆盖元素内部原有的内容 , 内部支持写表达式。{{ }}
插值表达式:在实际开发中用的最多,只是内容的占位符,不会覆盖原有的内容!v-html
指令的作用:可以把带有标签的字符串,渲染成真正的 HTML 内容!
注意:插值表达式只能用在元素的内容节点中,不能用在元素的属性节点中!
2. v-bind 属性绑定属性
- 在 vue 中,可以使用
v-bind:
指令,为元素的属性动态绑定值; - 简写是英文的
:
-
在使用 v-bind 属性绑定期间,如果绑定内容需要进行动态拼接,则字符串的外面应该包裹单引号,例如:
<div :title="'box' + index">这是一个 div</div> 1
-
使用 v-bind 在元素绑定时希望内传入 Number数值时,避免被解析成字符串
<select v-model='value'> <option :value='数字'>...</option> </select> 123
3. v-on 事件绑定属性
-
v-on:
简写是@
-
语法格式为:
<p> count的值是:{{ count }}</p> <button @click="add"></button> methods: { add() { // 如果在方法中要修改 data 中的数据,可以通过 this 访问到 this.count += 1 } } 12345678910
$event
在事件绑定时,会有一个原生DOM 的事件对象 e,如果事件对象传入参数,默认的事件对象会被覆盖。
$event
的应用场景:如果默认的事件对象 e 被覆盖了,则可以手动传递一个 $event。
例如:
// 点击按钮让count值 递增,绑定点击事件并传参
<button @click="add(3, $event)"></button>
methods: {
add(n, e) {
// 如果在方法中要修改 data 中的数据,可以通过 this 访问到
this.count += 1
}
}
12345678910
事件修饰符:
事件绑定期间非常好玩的一个东西,对事件的触发进行控制。
-
.prevent
<a @click.prevent="xxx">链接</a> 1
-
.stop
<button @click.stop="xxx">按钮</button> 1
事件修饰符 | 说明 |
---|---|
.prevent | 阻止默认行为 (如:阻止a链接的跳转,阻止表单提交等) |
.stop | 阻止冒泡事件 |
.capture | 以捕获模式触发当前的事件处理函数 |
.once | 绑定的事件只触发一次 |
.self | 只有在event.target 是当前元素自身时触发事件处理函数 |
按键修饰符
按键 | 键码值 | 使用 |
---|---|---|
Enter | 13 | .enter |
Tab | 9 | .tab |
Delete | 46 | .delete (捕获“删除”和“退格”按键) |
Esc | 27 | .esc |
BackSpace | 8 | .space |
Up Arrow | 38 | .up |
Left Arrow | 37 | .left |
Right Arrow | 39 | .right |
Dw Arrow | 40 | .down |
1.自定义其他的按键别名:
Vue.config.keyCodes.f6 = 118
<input @keyup.f6="xxx" /> // 只有单击f6键才会触发xxx的回调
12345
2.多个按键一并触发该事件
@keyup.ctrl.enter="XXX" // 按下ctrl和enter才触发事件执行
1
【案例应用】:表单输入后按回车键添,按esc则清空表单
<input type="text" v-model="newbrand"
@keyup.enter="add"
@keyup.esc="newbrand=''" />
123
4. v-model 表单绑定
在不操作 DOM 的前提下,实现表单元素和数据的双向绑定。
v-model 指令的修饰符
修饰符 | 作用 | 示例 |
---|---|---|
.number | 自动将用户输入值转为 Number | <input v-model.number =“age” /> |
.trim | 去除首尾空白字符 | <input v-model.trim =“msg” /> |
.lazy | 表单输入后失去焦点时更新页面数据,而非实时更新 | <input v-model.lazy =“msg” /> |
-
input 输入框
-
type=“text”
-
type=“radio”
-
type=“checkbox”
-
type=“xxxx”
-
-
textarea
-
select
5. 条件渲染指令
条件渲染指令用来辅助开发者按需控制 DOM 的显示与隐藏。
v-if 和 v-show
v-show
的原理是:动态为元素添加或移除style= " display: none; " 样式,从而控制元素的显示与隐藏。- 如果要频繁的切换元素的显示状态,用 v-show 性能会更好。
v-if
的原理是:每次动态创建或移除 DOM 元素,实现元素的显示和隐藏。- 如果在运行时条件很少改变,则使用 v-if 较好。
v-if 指令在使用的时候,有两种方式:
-
直接给定一个布尔值 true 或 false
<p v-if="true">被 v-if 控制的元素</p> 1
-
给 v-if 提供一个判断条件,根据判断的结果是 true 或 false,来控制元素的显示和隐藏
<p v-if="type === 'A'">良好</p> 1
v-else-if
<div v-if="type === 'A'">优秀</div>
<div v-else-if="type === 'B'">良好</div>
<div v-else-if="type === 'C'">及格</div>
<div v-else>不及格</div>
1234
6. 列表渲染指令
基于一个数组来循环渲染一个列表结构。
v-for
v-for
指令需要使用 item in items
形式的特殊语法
items
是源数据 (待循环) 的数组,而item
被循环的每一项。
【示例】:动态渲染表单数据:
<link rel="stylesheet" href="./lib/bootstrap.css">
<!-- 希望 Vue 能够控制下面的这个 div,帮我们把数据填充到 div 内部 -->
<div id="app">
<table class="table table-bordered table-hover table-striped">
<thead>
<th>索引</th>
<th>Id</th>
<th>姓名</th>
</thead>
<tbody>
<!-- 官方建议:只要用到了 v-for 指令,那么一定要绑定一个 :key 属性 -->
<!-- 而且,尽量把 id 作为 key 的值 -->
<!-- 官方对 key 的值类型,是有要求的:字符串或数字类型 -->
<!-- key 的值是千万不能重复的,否则会终端报错:Duplicate keys detected -->
<tr v-for="(item, index) in list" :key="item.id">
<td>{{ index }}</td>
<td>{{ item.id }}</td>
<td>{{ item.name }}</td>
</tr>
</tbody>
</table>
</div>
1234567891011121314151617181920212223
使用 key 维护列表的状态
官方推荐, 使用 v-for 指令绑定一个
:key
属性,key的作用是为了高效的更新虚拟DOM。
- key 的值类型,是有要求的:字符串或数字类型
- 把数据项 id 属性的值作为 key 的值(因为 id 属性的值具有唯一性)
- 改变 data 顺序,index 会重新排序,所以 index 的值不具有唯一性。
计算属性 computed
实时监听
data 中数据的变化,并return 一个计算后的新值
, 供组件渲染 DOM 时使用。可以被
模板结构
(插值、v-bind ) 或methods
方法使用。
- 但是在某些情况下,我们可能需要对数据进行一些转化后在显示,或者需要将多个数据结合起来进行显示,这时候我们可以使用计算属性。
实例 1:
<div id="app">
<h2>{{getFullName()}}</h2>
<h2>{{fullName}}</h2>
</div>
1234
const vm = new Vue({
el: '#app',
data: {
firstName: 'lin',
lastName: 'willen'
},
computed: {
fullName () {
return this.firstName + ' ' + this.lastName;
}
},
// 使用 methods: 每次都会调用方法
methods: {
getFullName () {
return this.firstName + ' ' + this.lastName;
}
}
})
123456789101112131415161718
特点:
- 定义的时候,要被定义为 “方法”。
- 在使用计算属性的时候,当普通的属性使用即可
- 实现了代码的复用,只要计算属性中依赖的数据源变化了,则计算属性会自动重新求值。
实例2:
<div id="app">
<h2>总价格:{{totalPrice}}</h2>
</div>
1234
const vm = new Vue({
el: '#app',
data: {
books:[
{id: 1001, name: 'Unix编程艺术',price: 119},
{id: 1002, name: '代码大全',price: 105},
{id: 1003, name: '深入理解计算机原理',price: 99},
{id: 1004, name: '现代操作系统',price: 109}
]
},
computed: {
totalPrice () {
let totalPrice = 0;
for (let i in this.books) {
totalPrice += this.books[i].price;
}
// 也可以使用 for of
for (let book of this.books) {
totalPrice += book.price;
}
return totalPrice;
}
}
})
123456789101112131415161718192021222324252627
计算属性 vs 方法
methods
和computed
看起来都可以实现我们的功能,那么为什么还要多一个计算属性 ?
- methods: 每次使用都会调用方法
- computed:
计算属性会缓存计算的结果
, 不变的情况下只调用一次, 除非原属性发生改变,才会重新调用.
计算属性 vs 侦听器
侧重的应用场景不同侧重的应用场景不同
:
- 计算属性侧重于监听
多个值
的变化,最终计算并返回一个新值
。 - 侦听器侧重于监听单个数据的变化,最终执行
特定的业务处理
,不需要有任何返回值
。
vue组件
什么是组件化开发 ?
根据封装的思想,把页面上可重用的 UI 结构封装为组件,方便项目的开发和维护。
vue 中的组件化开发
- vue 是一个支持组件化开发的前端框架。
- vue 中组件的后缀名是
.vue
组件的构成
每个 .vue 组件都由 3 部分构成,分别是:
template
:组件的模板结构,且每个组件中必须包含template模板结构。script
: 组件的 JavaScript 行为style
:组件的样式
<template>
<!-- 当前组件的 DOM 结构,需要定义到 template 标签内部 -->
</template>
<script>
// 组件相关的 data数据、methods方法等,
// 都要定义到 export default 所导出的对象中
export default {}
</script>
// 标签上添加 lang="less" 属性,即可使用 less 语法编写组件的样式
// scoped 防止样式冲突
<style lang='less' scoped>
</style>
12345678910111213141516
script 节点:
(1)script 中的 name
节点
- 用来定义组件的名称,调试的时候
可以清晰的区分每个组件
。
(2)script 中的 data
节点
- 组件渲染期间需要用到的数据, data
必须是函数
, 不能直接指向对象数据。
(3)script 中的 methods
节点
- 组件中的事件处理函数(
方法
),必须定义到methods
节点中
注册私有组件
通过 components 注册的是私有子组件,被注册的组件只能用在当前组件中。
1.使用 import 语法导入需要的组件
import Left from '@/components/Left.vue'
1
2.在 script 标签中使用 components
节点注册组件
<script>
export default {
comments:{
Left
}
}
</script>
1234567
3.以标签的形式
使用刚才注册的组件
<template>
<Left></Left>
</template>
123
注册全局组件
在 vue 项目的
main.js
入口文件中,通过Vue.component()
方法,可以注册全局组件。
注册:
import Vue from 'vue'
import App from './App.vue'
// 导入需要被全局注册的那个组件
import Count from '@/components/Count.vue'
// 参数一: 组件的 '注册名称',将来以标签形式使用时要求和这个名称一样。
// 参数二: 需要被全局注册的那个组件。
Vue.component('MyCount', Count)
// 消息提示的环境配置,设置为开发环境或者生产环境
Vue.config.productionTip = false
new Vue({
// render 函数中,渲染的是哪个 .vue 组件,那么这个组件就叫做 “根组件”
render: h => h(App)
}).$mount('#app')
123456789101112131415161718
使用:
<template>
// 这里需要注意 '注册名称'
<MyCount ></MyCount>
</template>
1234
组件注册时名称的大小写
在 Vue 定义组件注册名称的方式有两种:
-
短横线命名法,例如
my-swiper
和
my-search
- 使用组件时也必须使用短横线命名
-
大驼峰命名法,例如
MySwiper
和
MySearch
- 既可以按照大驼峰命名法使用,也可以
转化为短横线名称
进行使用。
- 既可以按照大驼峰命名法使用,也可以
注意: 在开发中,推荐使用大驼峰命名法
为组件注册名称,因为它的适用性更强。
组件之间的样式冲突问题
默认情况下,写在 .vue 组件中的样式会全局生效,因此很容易造成多个组件之间的样式冲突问题。
scoped 属性
让当前组件的样式对其子组件是不生效。
Vue 中提供了在style 节点添加 scoped
属性,来防止样式冲突问题:
- 原理是为每个组件分配唯一的自定义属性,在编写组件样式时,通过属性选择器来控制样式的作用域。
<template>
<div class="container" data-v-001>
<h3 data-v-001 > 轮播图组件件</h3>
</div>
</template>
<style>
// 通过中括号'属性选择器',防止样式冲突问题
// 因为每个组件分配的自定义属性是'唯一的'
. container[data-v-001]{
border: 1px solid red;
}
</style>
12345678910111213
为了提高开发效率和开发体验,直接在 style 节点使用 scoped 属性:
<style lang="less" scoped>
</style>
12
/deep/ 样式穿透
让某些样 式对子组件生效。
使用场景: 当使用第三方组件库的时候,需要修改第三方组件默认样式的时候。
<style lang="less" scoped>
/*不加 /deep/ 时,生成的选择器格式为 .title[data-v-052242de]*/
.title{
color: blue;
}
/*加/deep/ 时,生成的选择器格式为 [data-v-052242de] .title*/
/deep/ .title {
color: pink;
}
</style>
123456789101112
Class 与 Style 绑定
通过
v-bind
动态操作元素样式。
1. 动态绑定 HTML 的 class:
通过三元表达式,动态的为元素绑定 class 的类名:
<h3 class="thin" :class="isItalic ?'italic':''">MyDeep 组件</h3>
<button @click="isItalic=!lisItalic"> Toggle Italic </button>
123
data(){
return { isItalic:true }
.thin{
font-weight:200;
.italic{
font-style:italic;
}
12345678
2. 以数组语法绑定 HTML 的 class
如果元素需要动态绑定多个 class 的类名,此时可以使用数组的语法格式
<h3 class="thin":class="[isItalic? 'italic': '',isDelete? 'delete':'']">MyDeep组件</h3>
<button @click="isItalic= !isItalic"> 字体变细 </button>
<button @click="isDelete= !isDelete"> 添加删除线 e</button>
<script>
export default {
data () {
return {
isItalic: true,
isDelete: false,
}
}
}
</script>
123456789101112131415
3. 以对象语法绑定 HTML 的 class:
<h3 class="thin":class="classObj">MyDeep组件</h3>
<button @click="classObj.isItalic = !classObjisItalic"> 字体变细 </button>
<button @click="classObj.isDelete = !classObj.isDelete"> 添加删除线 e</button>
<script>
export default {
data () {
return {
classObj:{
isItalic: true,
isDelete: false,
}
}
}
}
</script>
12345678910111213141516
4. 以对象语法绑定内联的 style
命名可以用驼峰式
或短横线分隔
(记得用引号括起来) 来命名:
<div :style="{color:active, fontSize: fsize +'px','background-color': bgcolor}">
Hello world!!
</div>
<button @click="fsize += 1">字号+1</button>
<button @click="fsize -= 1">字号-1</button>
1234567
data () {
return {
active: 'red',
fsize: 30,
bgcolor: 'pink'
}
}
1234567
自定义属性 props
props
是组件的自定义属性,允许使用者通过自定义属性,为当前组件指定初始值,极大的提高组件的复用性。
在组件中声明 prpos
my-article 组件的定义如下:
<template>
<h3>标题:{{title}}</h3>
<h5>作者:{{author}}</h5>
</template>
123456
父组件传递给my-article
组件的数据,必须在props
节点中声明 :
<script>
export default {
props:['title','author'],
</script>
1234
无法使用未声明的 props
如果父组件给子组件
传递了未声明的 props 属性
,则这些属性会被忽略,无法被子组件使用。
动态绑定 props 的值
使用 v-bind 属性绑定的形式,可以为组件动态绑定 props 的值。
<!--通过V-bind属性绑定,为author动态赋予一个表达式的值
<my-article :title="info.title" :author="'post by'+info.author"></my-article>
1234
props 的大小写命名
组件中如果使用“camelCase (驼峰命名法)
”声明了 props 属性的名称,则有两种方式为其绑定属性的值:
<script>
export default {
props:['pubTime'], // 使用'驼峰命名'法为当前组件声明 pubTime 属性
</script>
1234
使用时既可以用驼峰命名
,亦可以用短横线分隔命名
的形式为组件绑定属性的值 :
<my-article pubTime="2021" ></my-article>
// 等价于
<my-article pub-time="2021" ></my-article>
123
props 验证
-
基础的类型检查
type
props:{ //支持的8种基础类型 propA:String, //字符串类型 propB:Number, //数字类型 propC:Boolean, //布尔值类型 propD:Array, //数组类型 propE:Object, //对象类型 propF:Date, //日期类型 propG:Function, //函数类型 propH:Symbol //符号类型 } 1234567891011121314
-
多个可能的类型
-
必填项校验
required
-
属性默认值
default
props:{ // 通过数组形式,为当前属性定义多个可能的类型 type: [String, Number], required: true, default: 0, } 1234567
自定义验证函数
在封装组件时,可以为 prop 属性指定自定义的验证函数,从而对 prop 属性的值进行更加精确的控制:
props:{
type:{
// 通过 validataor函数,对type 属性进行校验,属性值 通过val形参接收
validataor(val){
// 必须匹配下列字符串中的一个
return ['success','warning','danger'].indexOf(val) !== -1
}
}
}
123456789
prpos 传入不同的初始值
在实际开发中我们经常会碰到下面的情况:
- 在不同组件
使用同一个注册的组件
时候希望赋值一个不同的初始值
。
1.组件的封装者通过 props 允许使用者自定义初始值:
<template>
<div>
<h5>Count是全局组件,将被Left 和 Right组件使用</h5>
<p>count 的值是:{{ init }}</p>
<button @click="count += 1">+1</button>
</div>
</template>
<script>
export default {
// props: ['init'],
props: {
// 自定义属性的名字,是封装者自定义的(只要名称合法即可)
init: {
// 如果外界使用 Count 组件的时候,没有传递 init 属性,则默认值生效
default: 0,
// 指定值类型必须是 Number 数字
type: Number,
// 必填项校验(表示必须传入值)
required: true
}
},
data() {
return {
//props是只读的且不可修改
// 要想修改 props 的值,可以把 props 的值转存到 data中
count: this.init
}
}
}
12345678910111213141516171819202122232425262728293031
2.组件的使用者通过属性节点传入初始值
Left 组件、Right 组件:
<template>
<div class="left-container">
<h3>Left 组件</h3>
<hr />
<MyCount :init="9"></MyCount>
</div>
</template>
1234567
<template>
<div class="right-container">
<h3>Right 组件</h3>
<hr />
<MyCount :init="6"></MyCount>
</div>
</template>
12345678
props 里传参注意点
- props 可以通过
[]
给数据定义多个可能的数据类型 ; - props 传入
Object
默认值必须是一个fn
props: {
commCount: {
type: String,
default: ''
},
pubdate: {
// 通过数组形式,为当前属性定义多个可能的类型
type: [String, Number],
default: ''
},
cover: {
type: Object,
// 通过 default 函数,返回 cover 的默认值
default: function() {
// 这个 return 的对象就是 cover 属性的默认值
return { cover: 0 }
}
}
}
12345678910111213141516171819
- props 是
只读的
,想修改 props 的值,可以把 props 的值转存到 data 中。 - props 的三个属性值:default、type、required。
自定义事件
封装组件时,为了让组件的使用者
可以监听到组件内状态的变化
,此时需要用到组件的自定义事件
。
vue2 中自定义事件的 3 个使用步骤:
- 在封装组件时 (子组件):
触发
自定义事件 - 在使用组件(父组件)时:
监听
自定义事件
触发自定义事件
// 子组件
<button @click="onBtnClick"> +1 </button>
<script>
export default {
data() {
// 子组件自己的数据,将来希望把 count 值传给父组件
return { count: 0 }
},
methods: {
onBtnClick() {
this.count t= 1
//修改数据时,通过 $emit()触发自定义事件
// 当点击 '+1' 按钮时 调用this.$emit 触发自定义的 numchange 事件
this.$emit( 'numchange')}
}
}
</script>
12345678910111213141516171819
监听自定义事件
// 父组件
<Son @numchange="getNewCount"></Son>
methods : {
getNewCount(val) {
console.log('监听到了 count 值的变化', val)
},
123456789
自定义事件传参
在调用 this.$emit() 方法触发自定义事件时,可以通过第 2 个参数为自定义事件传参:
methods: {
onBtnClick() {
this.count t= 1
this.$emit( 'numchange' , this.count)} // 触发自定义事件,通过第二个参数传参
}
12345
filter 过滤器
在 vue 3.x 的版本中剔除了过滤器
相关的功能。
在 vue 3.x 使用计算属性或方法代替被剔除的过滤器功能。
过滤器(Filters)常用于文本的格式化。过滤器可以用在两个地方:
双括号插值表达式
和v-bind
属性绑定。
<!-- 在双花括号中通过 | 调用capitalize过滤器,对message值进行格式化 -->
{{ message | capitalize }}
<!-- 在 `v-bind` 中 -->
<div v-bind:id="rawId | formatId"></div>
12345
私有过滤器
在 filters 节点下定义的过滤器,称为“私有过滤器”,因为它只能在当前 vm 实例所控制的 el 区域内使用。
创建一个私有过滤器,示例代码如下:
const vm = new Vue({
el: '#app',
data: {
message: 'hello world!',
info: 'title info'
},
// 在 filters 节点下定义过滤器
filters: {
// 把首字母转换为大写的过滤器
capitalize (value) {
return value.charAt(0).toUpperCase() + value.slice(1)
}
}
12345678910111213
全局过滤器
如果希望在多个 vue 实例之间共享过滤器,则可以按照如下的格式定义全局过滤器:
// Vue.filters()方法接收两个参数:
// 第一个参数:过滤器名字 第二个参数:过滤器的处理函数
Vue.filter('capitalize',(str)=>{
return value.charAt(0).toUpperCase() + value.slice(1)'
} )
123456
调用多个过滤器:
{{ message | filterA | filterB }}
1
- 先把 message 的值交给 filterA 处理,再把 filterA 处理结果交给 filterB 进行处理,最终把 filterB 的处理结果,作为最终值渲染到页面上。
过滤器传参
过滤器是 JavaScript 函数,因此可以接收参数:
{{ message | filterA('arg1', arg2) }}
// 第一个参数永远是 '管道符' 前面待处理的值,第二个参数开始才是调用过滤器时传递的参数
Vue.filter('filterA',(msg,arg1,arg2)=>{
// 过滤器的逻辑代码
})
1234567
这里,filterA
被定义为接收三个参数的过滤器函数。其中 message
的值作为第一个参数,普通字符串 'arg1'
作为第二个参数,表达式 arg2
的值作为第三个参数。
<p>{{message | cap | maxl(5)}}</p>
Vue.filter('cap', (str) => {
return str.charAt(0).toUpperCase() + str.slice(1) + '----'
})
Vue.filter('maxl', (str, len = 10) => {
if (str.length <= len) return str
return str.slice(0, len) + '....'
})
var app = new Vue({
el: '#app',
data: {
message: 'hello Vue 2021年10月30日00:07:45!'
}
12345678910111213141516
过滤器的注意点
- 在过滤器函数中,一定要有 return 值
- 在过滤器的形参中,可以获取到“管道符”前面待处理的那个值
- 如果全局过滤器和私有过滤器名字一致,此时按照“就近原则”,调用的是”私有过滤器“
label的for属性
使用lable自带的属性进行单选钮的启用和禁用:
<input type="checkbox" :id="'cb' + item.id" v-model="item.status">
<label :for="'cb' + item.id" v-if="item.status">已启用</label>
<label :for="'cb' + item.id" v-else>已禁用</label>
123
watch 侦听器
watch 侦听器
监视数据的变化
,从而针对数据的变化做特定的操作
。
侦听器的格式
- 方法格式的侦听器
- 无法在刚进入页面的时候,自动触发!!
- 如果侦听的是一个对象,如果对象中的属性发生了变化,不会触发侦听器!
- 对象格式的侦听器
- 可以通过 immediate 选项,让侦听器自动触发!
- 可以通过 deep 选项,让侦听器深度监听对象中每个属性的变化!
使用方法格式创建的侦听器:
监听 username 值的变化,并使用 axios 发起 Ajax 请求,检测当前输入的用户名是否可用:
import axios from 'axios'
export default{
data(){
return{ username: ''}
}
},
watch: {
// newVal 是'变化后的新值',oldVal 是'变化之前的旧值'
async username(newVal,oldVal) {
if (newVal === '') return
// 使用 axios 发起请求,判断用户名是否可用
const { data: res } = await axios.get(`https://www.escook.cn/api/finduser/${newVal}` )
console.log(res)
}
}
1234567891011121314151617
使用方法创建时:组件在初次加载完毕后不会调用 watch 侦听器。
immediate 选项
如果想让 watch 侦听器在浏览器打开时立即被调用,则需要使 用
immediate
选项。
使用对象格式创建的侦听器:
watch: {
username: {
// handler 是固定写法,表示当 username 的值变化时,自动调用 handler 处理函数
async handler(newVal,oldVal) {
if (newVal === '') return
const { data: res } = await axios.get('https://www.escook.cn/api/finduser/' + newVal)
console.log(res)
},
// 表示页面初次渲染好之后,就立即触发当前的 watch 侦听器
immediate: true
}
}
123456789101112
- 使用
handler
定义侦听器函数 immediate
控制侦听器是否自动触发, 默认值为false
不立即触发。
deep 选项
如果 watch 侦听的是一个对象,如果对象中的属性值发生了变化,则无法被监听到。此时需要使用 deep 开启 深度监听。
<input type="text" v-model.trim="username"/>
1
data: {
info: {username: 'admin'}
},
watch:{
// 监听info对象的变化
info:{
handler(newVal){
console.log(newVal.username)
},
// 开启深度监听,监听每个属性值的变化,默认值为 false
deep: true
}
}
12345678910111213
监听对象单个属性的变化
如果只想监听对象中单个属性的变化,则可以按照如下的方式定义 watch 侦听器。
const vm = new Vue({
el: '#app',
data: {
info: {username: 'admin',}
},
watch:{
'info.username':{
handler(newVal){
console.log(newVal)
},
}
}
})
12345678910111213
组件的生命周期
生命周期
(Life Cycle)是指一个组件从创建 -> 运行 -> 销毁的整个阶段,强调的是一个时间段。生命周期函数
:是由 vue 框架提供的内置函数,会伴随着组件的生命周期,自动按次序执行。
生命周期
强调的是时间段,生命周期函数
强调的是时间点。
生命周期函数的分类
生命周期图示:
需要注意的三个周期函数:
在实际开发中,
created
是最常用的生命周期函数 !
组件之间的数据共享
父→子
父组件通过
v-bind
属性绑定向子组件共享数据,子组件使用props
接收数据。
// 父组件
// 注册子组件
<Son :msg="message" :user="userinfo"></Son>
<script>
// 导入子组件
import Son from '@component/Son.vue'
export default {
data:{
return{
message:'hello vue.js'
userinfo:{
name: 'lilei',age: 21
},
components: {
Son
}
}
}
}
</script>
12345678910111213141516171819202122
// 子组件
<template>
<div>
<h5>Son组件</h5>
<p>父组件传递过来的 msg值是: {{ msg }</p>
<p>父组件传递过来的user值是: {{ user }}</p>
</div>
</template>
export default {
props: [ 'msg' , 'user ']
}
123456789101112
子 → 父
子组件向父组件共享数据使用自定义事件。
// 子组件
export default {
data() {
// 子组件自己的数据,将来希望把 count 值传给父组件
return { count: 0 }
},
methods: {
add() {
this.count t= 1
//修改数据时,通过 $emit()触发自定义事件
this.$emit( 'numchange' , this.count)}
}
}
1234567891011121314
// 父组件
<h1>App 根组件</h1>
<h3>子组件传过来的数据是 : {{ countFromSon }}</h3>
<Son @numchange="getNewCount"> </Son>
<script>
import Son from '@component/Son.vue'
export default {
data() {
return { countFromSon: 0 }
},
methods : {
getNewCount(val) {
console.log('numchange 事件被触发了!', val)
this.countFromSon = val
},
components: {
Son
}
}
}
</script>
123456789101112131415161718192021222324
兄弟组件数据共享
在 vue2.x 中,兄弟组件之间数据共享的方案是
EventBus
。
EventBus 的使用步骤
- 创建
eventBus.js
模块,并向外共享一个 Vue 的实例对象
- 在数据发送方,调用
bus.$emit('事件名称', 要发送的数据)
方法触发自定义事件 。 - 在数据接收方,调用
bus.$on('事件名称', 事件处理函数)
方法注册一个自定义事件。
ref 引用 操作DOM
不依赖于 jQuery 和调用 DOM API 的情况下,获取
DOM 元素
或组件的引用
。
每个 vue 的组件实例上,都包含一个 $refs 对象
,里面存储着对应的 DOM 元素或组件的引用。默认情况下,组件的 $refs 指向一个空对象。
使用 ref 引用 DOM 元素
<!--使用ref属性,为对应的DOM添加引用名称-->
<h3 ref="myh3">MyRef 组件</h3>
<button@click="getRef">获取$refs 引用</button>
methods:{
getRef(){
//通过this.$refs.引用的名称可以获取到DOM元素的引用
console.log(this.$refs.myh3)
//操作DOM元素,把文本颜色改为红色
this.$refs.myh3.style.color='red'
},
12345678910111213
使用 ref 引用组件实例
需求: 在根组件控制子组件
<!--使用ref属性,为对应的“组件”添加引用名称-->
<my-counter ref="counterRef"> </my-counter>
<button @click="getRef"> 获取$refs 引用 </button>
methods:{
getRef(){
// 通过this.$refs.引用的名称可以引用组件的实例
console.log(this.$refs.counterRef)
// 引用到组件的实例之后,就可以调用 子组件上的 methods 方法
this.$refs.counterRef.add()
},
123456789101112
点击文本框自动获得焦点
添加 ref 引用,并调用原生 DOM 对象的
.focus()
方法即可。
this.$nextTick(cb) 方法
$nextTick(cb)
保证 cb 回调函数可以操作到最新的 DOM 元素(推迟到下一个 DOM 更新周期之后执行)。
- 解决我们在页面没有渲染完成前使用 ref 操作DOM元素报错问题。
控制文本框和按钮的按需切换
点击按钮展示文本框,文本框输入时隐藏按钮:
<template>
<input type="text" v-if="inputVisible" ref="ipt">
<button v-else @click="showInput">展示input输入框</button>
</template>
1234
<script>
export default{
data(){
return{
//控制文本框和按钮的按需切换
inputVisible:false,
},
methods:{
showInput(){
//切换布尔值,显示文本框
this.inputVisible=true
//获取文本框的DOM引用,并调用.focus()使其自动获得焦点
// this.$refs.ipt.focus() 错误,此时页面未渲染完毕,无法获取文本框
//把对input文本框的操作,推迟到下次DOM更新之后。否则页面上根本不存在文本框元素
this.$nextTick(() =>{
this.$refs.ipt.focus()
})
}
},
</script>
1234567891011121314151617181920212223
动态组件
实现不同组件之间的按需展示。( 动态切换组件显示和隐藏 ) ,类似于
Vue-Router
。
动态组件的基本使用
Vue 提供了一个内置的 <component>
组件,专门用来实现动态组件的渲染 :
component
标签是 vue 内置的,作用:组件的占位符- 通过
:is
属性,动态指定要渲染的组件- is 属性的值,表示要渲染的组件的名字。
- is 属性的值,应该是组件在 components 节点下的注册名称。
- 使用
keep-alive
保持组件的状态 (避免组件切换时重新渲染)。- keep-alive 会把内部的组件进行缓存,而不是销毁组件。
- 通过
include
指定哪些组件需要被缓存。 - 通过
exclude
属性指定哪些组件不需要被缓存。
// 点击按钮,动态切换组件的名称
<button @click="comName = 'Left'">展示 Left</button>
<button @click="comName = 'Right'">展示 Right</button>
<div class="box">
<keep-alive exclude="MyRight">
<component :is="comName"></component>
</keep-alive>
</div>
<script>
import Left from '@/components/Left.vue'
import Right from '@/components/Right.vue'
export default {
data(){
return {
// comName 表示要展示的组件的名字
comName: 'Left'
}
},
components: {
// 如果在“声明组件”的时候,没有为组件指定 name 名称,则组件的名称默认就是“注册时候的名称”
// 组件的 “注册名称” 应用场景是:以标签的形式使用渲染到页面。
Left,
Right
}
}
</script>
12345678910111213141516171819202122232425262728293031
在组件中定义 name 名称:
- 当提供了 name 属性之后,组件的名称就是 name 属性的值。
export default {
name: 'MyRight'
}
123
声明 name 应用场景:结合<keep-alive>
标签实现组件缓存功能;以及在调试工具中看到组件的 name 名称。
插槽 slot
在签形式使用的
组件中内容节点
中插入内容
。
- 通过
slot
元素定义插槽
,从而为用户预留内容占位符。 - 封装组件时,没有预留插槽的内容会被丢弃。
后备内容
在
slot
标签内添加的内容会被作为后备内容。
// 子组件 my-com
<template>
<slot>这是后备内容</slot>
</template>
1234
// 使用插槽
<my-com>
// 如果用户没有提供内容,上面 slot标签 内定义的内容会生效,此时页面会有 "这是后备内容"
// 如过提供了,下面的 img 将会被渲染到页面上
<img src="../assets/log.png" alt="">
</my-com>
123456
具名插槽
// MyArticle 组件
<template>
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
</template>
1234567891011121314
- Vue 官方规定,每个
slot
插槽,都要有一个 name名称,一个不带name
的<slot>
出口会带有隐含的名字“default”。
为具名插槽提供内容
如果要把内容填充到指定名称的插槽中,我们可以在一个 <template>
元素上使用 v-slot
指令,并以 v-slot
的参数的形式提供其名称:
<MyArticle>
<template v-slot:header>
// 把内容放在 MyArticle组件的 header 标签内
<h1>如今最好,没有来日方长。</h1>
</template>
// 上一个代码块中,main 标签未指定具名插槽,所以默认渲染到 main标签中
<p>现在是2021年11月7日 22点05分 星期天</p>
<p>今天不仅是周日,也是冬至</p>
<template #footer>
<p>最后,祝大家冬至快乐!</p>
</template>
</MyArticle>
12345678910111213141516
v-slot:
可以简写#
。例如 v-slot:header 可以被重写为 #header。v-slot
属性只能放在 组件标签和<template>
元素上 (否则会报错)。
作用域插槽
在封装组件的过程中,可以为预留的<slot>
插槽绑定 props 数据,这种带有 props 数据的<slot>
叫做“作用域插槽
”。示例代码如下:
<template>
<div>
<h1> 这是 Left 组件</h1>
<!--下面的slot 是一个作用域插槽 -->
<slot v-for="item in list" :user="item"></slot>
</div>
</template>
12345678
接收作用域插槽对外提供的数据, 使用解构赋值
简化数据的接收过程 :
<!-- 使用自定义组件 -->
<Left>
<!--作用域插槽对外提供的数据对象,可以通过“解构赋值”简化接收的过程-->
<template #default="{user}">
<tr>
<td>{{user.id}}</td>
<td>{{user.name}}</td>
<td>{{user.state}}</td>
</tr>
</template>
</Left>
1234567891011
自定义指令
vue 官方提供了 v-text、v-for、v-model、v-if 等常用的指令。除此之外 vue 还允许开发者自定义指令。
私有自定义指令 directives: { }
在每个 vue 组件中,可以在 directives
节点下声明私有自定义指令。
1. 定义一个私有自定义指令:
directives:{
color:{
//为绑定到的HTML元素设置红色的文字
bind(el){
//形参中的el是绑定了此指令的、原生的DOM对象
el.style.color=‘red'
}
1234567
2. 使用自定义指令:
- 使用自定义指令时,需要加上
v-指令前缀
<!--声明自定义指令时,指令的名字是color 使用时就是 v-color -->
<h1 v-color>App组件</h1>
123
3. 为自定义指令动态绑定参数值
在 template
结构中使用自定义指令
时,可以通过等号 =
的方式,为当前指令动态绑定参数值:
<template>
<!--在使用指令时,动态为当前指令绑定参数值 color-->
<h1 v-color="color">App组件</h1>
</template>
data(){
return{
color:'red’
}
1234567891011
4. 通过 binding
获取指令的参数值:
在上面的实例中我们为自定义指令绑定了一个动态的参数值,如何拿到这个值呢 ?
在声明自定义指令时,可以通过形参中的第二个参数,来接收指令的参数值
<h1 v-color="color">App组件</h1>
// 此时传入的是字符串,不会在data 数据中查找
<p v-color="'blue'" ></p>
directives:{
color:{
bind(el,binding){
//通过binding对象的.value属性,获取动态的参数值
el.style.color=binding.value
}
123456789101112
5. 使用 update 函数更新 DOM
update
函数会在每次 DOM 更新时被调用。
注意:在 vue3 的项目中使用自定义指令时, bind 必须改为 mounted
、 update 改为 updated
。
bind 函数只调用 1 次:当指令第一次绑定到元素时调用,当 DOM 更新时 bind 函数不会被触发。
directives:{
color:{
//当指令第一次被绑定到元素时被调用
bind(el,binding){
el.style.color=binding.value
},
//每次DOM更新时被调用
update(el,binding){
el.style.color=binding.value
}
12345678910
6. 同时使用 bind 和 update 函数简写
如果 bind
和 update
函数中的 逻辑完全相同
,则对象格式的自定义指令可以简写成函数格式:
directives:{
//在 bind 和 update 时,会触发相同的业务逻辑
color( el,binding ){
el.style.color=binding.value
}
12345
全局自定义指令 Vue.directive ()
通过“
Vue.directive()
” 进行声明 。
注意:在使用 Vue-cli ( 脚手架) 时,要把全局自定义指令写在main.js
文件中:
//参数1:字符串,表示全局自定义指令的名字
//参数2:对象,用来接收指令的参数值
Vue.directive('color',function(el,binding){
el.style.color=binding.value
})
// 简写
Vue.directive('color',(el,binding) =>{
el.style.color=binding.value
})
1234567891011
vue-cli
Vue CLI 是官方发布的vue.js项目脚手架, 可以快速搭建vue开发环境以及Webpack配置。
安装和使用
一、安装Vue脚手架
npm install -g @vue/cli
1
二、创建项目
vue create project(项目名称)
1
目录详解:
|- public // 静态页面目录
|- index.html // 项目入口
|- src // 源码目录
|- assets // 存放项目中用到的静态资源文件,例如:css 样式表、图片资源
|- components // 封装的、可复用的组件,都要放到 components 目录下
|- App.vue // 根组件
|- main.js // 项目的入口文件。整个项目的运行,要先执行
1234567
vue 项目的运行流程:
通过 main.js
把 App.vue
渲染到index.html
的指定区域中。
- App.vue 用来编写待渲染的
模板结构
- index.html 中需要预留一个
el 区域
- main.js 把 App.vue 渲染到了 index.html 所预留的区域中
axios
axios 是一个专注于网络请求的库, 调用 axios 方法得到的返回值是
Promise 对象
。
axios 的基本使用
-
发起 GET 请求:
axios({ // 请求方式 method: 'GET', // 请求的地址 url: 'http://www.liulongbin.top:3006/api/getbooks', // URL 中的查询参数(GET请求传参) params: { id: 1 } }).then(function (result) { console.log(result) }) 12345678910111213
-
params
表示传递到服务器端的数据,以
url参数
的形式拼接在请求地址后面
- 如 : params: { page:1,per:3 }
- 最终生成:http://jsonplaceholder.typicode.com/?page=1&per=3
-
-
发起 POST 请求:
document.querySelector('#btnPost').addEventListener('click', async function () { // 如果调用某个方法的返回值是 Promise 实例,则前面可以添加 await! // await 只能用在被 async “修饰”的方法中 // 1. 调用 axios 之后,使用 async/await 进行简化 // 2. 使用解构赋值,从 axios 封装的大对象中,把 data 属性解构出来 // 3. 把解构出来的data属性,使用冒号进行重命名,一般都重命名为 { data: res } const { data: res } = await axios({ method: 'POST', url: 'http://www.liulongbin.top:3006/api/post', // POST请求体传参 data: { name: 'zs', age: 20 } }) console.log(res) }) 1234567891011121314151617181920212223
axios 封装的 6 个属性:
axios 在请求到数据之后,在真正的数据之外,套了一层外壳。
{
config:{ },
// data 才是服务器返回的真实数据
data:{
status: 200,
msg: "获取数据成功!",
data: Array(6)
},
headers:{ ... },
request:{ },
status: 200,
statusText: 'OK',
}
12345678910111213
axios直接发起GET和POST请求
document.querySelector('#btnGET').addEventListener('click', async function () {
/* axios.get('url地址', {
// GET 参数
params: {}
}) */
const { data: res } = await axios.get('http://www.liulongbin.top:3006/api/getbooks', {
params: { id: 1 }
})
console.log(res)
})
document.querySelector('#btnPOST').addEventListener('click', async function () {
// axios.post('url', { /* POST 请求体数据 */ })
const { data: res } = await axios.post('http://www.liulongbin.top:3006/api/post', { name: 'zs', gender: '女' })
console.log(res)
})
1234567891011121314151617
在 Vue-cli 中使用 axios
一般我们会直接这么用:
// 在组件内
<template>
<button @click="postInfo">发起 POST 请求</button>
</template>
<script>
// 1. 导入 axios
import axios from 'axios'
export default {
//2. 在 methods 定义 axios请求方法
methods: {
async postInfo() {
const { data: res } = await axios.post('http://www.liaoyia.top:3306/api/post', { name: 'zs', age: 20 })
console.log(res)
}
}
}
</script>
12345678910111213141516171819
缺点: 在每次使用时候都要导入 axios 文件,写请求地址 (对后期维护不友好) 。
把 axios 挂载到 Vue原型上并配置请求根路径
避免重复导入axios 和重复写入完整请求地址。
在main.js
中配置
import Vue from 'vue'
import App from './App.vue'
import axios from 'axios'
Vue.config.productionTip = false
// 全局配置 axios 的请求根路径 (官方提供配置项)
axios.defaults.baseURL = 'http://www.liaoyia.top:3006'
// 把 axios 挂载到 Vue.prototype 上,供每个 .vue 组件的实例直接使用
Vue.prototype.$http = axios
// 今后,在每个 .vue 组件中要发起请求,直接调用 this.$http.xxx
// 但是,把 axios 挂载到 Vue 原型上,有一个缺点:不利于 API 接口的复用!!!
new Vue({
render: h => h(App)
}).$mount('#app')
12345678910111213141516171819
使用:
<template>
<button @click="btnGetBooks">获取图书列表的数据</button>
</template>
<script>
export default {
methods: {
// 点击按钮,获取图书列表的数据
async btnGetBooks() {
const { data: res } = await this.$http.get('/api/getbooks')
console.log(res)
}
}
}
</script>
12345678910111213141516
但是把 axios 挂载到 Vue原型上并配置请求根路径也有缺点:
无法实现API接口的复用
: 在多个组件想使用同一个请求方法(API)的时候,只能在每个组件重新定义一次。
axios 封装
1.如果项目中有多个请求地址,我们可以根据多个地址使用工厂模式封装 js 模块,创建多个 axios
实例对象,并设置请求根路径 (baseURL
) :
步骤如下: 在项目的 src
目录下创建utils
文件夹并新建一个 request.js
文件:
import axiox from 'axios'
const request =axiox.create({
//baseURL会在发送请求的时候拼接在url参数的前面
baseURL:'http://jsonplaceholder.typicode.com/',
timeout:5000
})
// 向外导出
export default request
1234567891011
- 使用这种方法时: 一般我们只会在一个 js 模块创建一个
axios
实例对象,并向外导出。 - 如果有多个服务器地址,那就创建多个 js模块,并在里面创建axios实例对象。
2.为了实现复用性,我们还可以把所有请求,都封装在API模块里,在API模块中,按需导出一个方法,这个方法调用 request.get 或 request.post 来请求一个接口,最后return 一个Promise 对象。
比如想调用接口获取用户相关信息:
在根目录新建 utils
文件夹并在里面新建 userAPI.js
文件
//导入 utils 文件夹下的 request.js
import request from '@/utils/request.js'
export const getArticleListAPI = function(_page, _limit) {
return request.get('/articles', {
params: {
_page,
_limit
}
})
}
123456789101112
【实例】:点击按钮发起 GET 请求并自动调用请求拦截
和添加响应拦截器
:
<template>
<div class="home">
<button @click="getByMineHandle">调用封装的get请求</button>
</div>
</template>
<script>
// 导入 get 方法
import { get } from '../utils/request'
export default {
name: 'Home',
methods:{
getByMineHandle(){
get('',{page:3,per:2}).
then(res=>console.log(res))
}
}
}
</script>
123456789101112131415161718192021
import axiox from 'axios'
const instance =axiox.create({
//baseURL会在发送请求的时候拼接在url参数的前面
baseURL:'http://jsonplaceholder.typicode.com/',
timeout:5000
})
//请求拦截
// 所有的网络请求都会先走这个方法
// 添加请求拦截器,所有的网络请求都会先走这个方法
// 我们可以在它里面为请求添加一些自定义的内容 比如 token 或者在 headers 提供信息
instance.interceptors.request.use(function (config) {
// 在发送请求之前做些什么
console.group('全局请求拦截')
console.log(config)
console.groupEnd()
config.headers.token ='12343'
return config;
}, function (error) {
// 对请求错误做些什么
return Promise.reject(error);
});
// 添加响应拦截器
//此处可以根据服务器的返回状态码做响应的处理
//404 404 500
instance.interceptors.response.use(function (response) {
// 对响应数据做点什么
console.group('全局响应拦截')
console.log(response)
console.groupEnd()
return response;
}, function (error) {
// 对响应错误做点什么
return Promise.reject(error);
});
// 从服务器查看获取数据
export function get(url,params) {
return instance.get(url,{
params
})
}
// 向服务器创建数据
export function post(url,data) {
return instance.post(url,data)
}
// 从服务器删除数据
export function del(url) {
return instance.delete(url)
}
// 向服务器发送更新数据
export function put(url,data) {
return instance.put(url,data)
}
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859
Vue-router
SPA 与前端路由
SPA (单页面网页)
,所有组件的展示与切换都在这唯一的一个页面内完成。
此时,不同组件之间的切换需要依赖 前端路由(router)
来实现。
什么是前端路由?
Hash 地址与组件之间的对应关系
,不同的Hash 展示不同的页面。
前端路由的工作方式:
vue-router 的基本用法
vue-router 是 vue.js 官方给出的路由解决方案。它只能结合 vue 项目进行使用,能够轻松的管理 SPA 项目中组件的切换。
-
安装 vue-router 包
npm i vue-router@3.5.2 -S 1
此时 src 源码目录下会新增一个 router 文件夹。
-
创建路由模块
在 src 源代码目录下,新建
router/index.js
路由模块,并初始化://1.导入Vue和VueRouter的包import Vue from'vue' import VueRouter from‘vue-router' //2.调用Vue.use()函数,把VueRouter 安装为Vue的插件 Vue.use(VueRouter) //3.创建路由的实例对象 const router=new VueRouter() //4.向外共享路由的实例对象 export default router 12345678910111213
-
导入并挂载路由模块
在
src/main.js
入口文件中,导入并挂载路由模块:import Vue from'vue' import App from'./App.vue' //1.导入路由模块 import router from@/router' new Vue ({ render:h=>h(App), //2.挂载路由模块 router:router }).$mount('#app') 12345678910111213
-
声明路由链接和占位符
在 src/App.vue 组件中,使用 vue-router 提供的
<router-link>
和<router-view>
声明路由链接和占位符:<tcmplate> <div class="app-container"> <h1>App组件</h1> <!--1.定义路由链接--> <router-link to="/home">首页</router-link> <router-link to="/movie">电影</router-link> <router-link to="/about">关于</router-link> <!--2.定义路由的占位符--> <router-view></router-view> </div> </template> 123456789101112131415
-
声明路由的
匹配规则
在 src/router/index.js
路由模块中,通过 routes
数组声明路由的匹配规则:
//导入需要使用路由切换展示的组件
import Home from'@/components/Home.vue'
import Movie from'@/components/Movie.vue'
import About from'@/components/About.vue'
//2.创建路由的实例对象
const router=new VueRouter({
//在routes数组中,声明路由的匹配规则
routes:[
//path 表示要匹配的hash地址;component表示要展示的路由组件
{path:'/home',component: Home},
{path:'/movie',component: Movie},
{path:'/about',component: About}
]
})
1234567891011121314151617
路由重定向
当访问地址 A 的时候,
强制用户跳转
到地址C, 从而展示特定的组件页面。
- 通过路由规则的
redirect
属性,指定一个新的路由地址来实现路由的重定向。
下面这个应用场景,当用户访问网页 / 目录时候,跳转到首页:
const router=new VueRouter({
//在routes数组中,声明路由的匹配规则
routes:[
//当用户访问 / 的时候,通过 redirect 属性跳转到 /home 对应的路由规则
{ path: '/',redirect:'/home' },
{ path: '/home',component: Home },
{ path: '/movie',component: Movie },
{ path: '/about',component: About }
})
123456789
路由高亮
可以通过如下的两种方式,将激活的路由链接进行高亮显示:
- 使用
默认的
高亮 class 类 自定义
路由高亮的 class 类
默认的高亮 class 类
被激活的路由链接,默认会应用一个叫做 router-link-active
的类名, 可以使用此类名选择器
,为激活 的路由链接设置高亮的样式:
/*在index.css全局样式表中,重新router-link-active的样式*/
.router-link-active {
color:#ef4238;
font-weight:bold;
border-bottom: 2px #ef4238 solid;
}
1234567
自定义路由高亮的 class 类
在创建路由的实例对象时,开发者可以基于 linkActiveClass
属性,自定义路由链接被激活时所应用的类名:
const router=createRouter({
history:createlebHashHistory(),
// 指定被激活的路由链接,会应用 router-active 这个类名,
// 默认的 router-link-active 类名会被覆盖掉
1inkActiveclass:'router-active',
routes:[
{ path: '/',redirect: '/home' },
{ path: '/home',component: Home },
{ path:'/movie',component: Movie },
{ path: '/about',component: About },
]
})
123456789101112
嵌套路由
通过路由实现组件的嵌套展示,叫做嵌套路由。
1. 声明子路由链接和子路由占位符
上图中,我们在 About.vue
组件中套娃了 tab1 和 tab2 组件 ,如果我们想展示它,则需要声明子路由链接
以及子路由占位符
:
<template>
<div class="about-container">
<h3>About 组件</h3>
<!--1.在关于页面中,声明两个子路由链接-->
<router-link to="/about/tab1">tab1</router-link>
<router-link to="/about/tab2">tab2</router-link>
<hr/>
<!--2.在关于页面中,声明子路由的占位符-->
<router-view></router-view>
</div>
</template>
12345678910111213
到此为止,我们已经有了 子路由链接
、子路由占位符
,以及链接对应的组件
,但是要在页面显示,我们还缺少对应关系,也就是路由规则
。
2.通过 children 属性声明子路由规则
在 src/router/index.js
路由模块中,导入需要的组件,并使用 children
属性声明子路由规则:
// 导入组件
import Tab1 from '@/components/tabs/Tab1.vue'
import Tab2 from '@/components/tabs/Tab2.vue'
const router=new VueRouter({
routes:[
{ //about 页面的路由规则(父级路由规则)
path: '/about',
component: About,
//1.通过children属性,嵌套声明子级路由规则
children:[
{ path: 'tab1',component: Tab1 },//2.访问/about/tab1时,展示Tab1组件
{ path: 'tab2',component: Tab2 } //2.访问/about/tab2时,展示Tab2组件
}]
})
123456789101112131415
- 注意:使用
children
属性嵌套声明子级路由规则时,path 名称不需要加/
。
3. 默认子路由
如果
children
数组中,某个路由规则的 path 值为空字符串,则这条路由规则,叫作“默认子路由”。
// src/router/index.js
const router=new VueRouter({
routes:[
{
path: '/about',
component: About,
// rediect: 'about/table' // 通过重定向设置默认展示页面
children:[
// path值为空字符串,此时Tab1为默认子路由,一进入About页面,默认展示 Tab1
{ path: '',component: Tab1 },
{ path: 'tab2',component: Tab2 }
}]
})
123456789101112131415
- 所以,
默认子路由
和路由重定向
都可以用来设置展示特定的组件页面
。
动态路由匹配
动态路由指的是:把 Hash 地址中
可变的部分
定义为参数项
,从而提高路由规则的复用性
。
有如下3个路由链接:
<router-link to="/movie/1"> 电影1 </router-link>
<router-link to="/movie/2"> 电影2 </router-link>
<router-link to="/movie/3"> 电影3 </router-link>
123
如果定义下面3个路由规则,虽然可行,但太过繁琐且复用性差:
{path:'/movie/1',component:Movie}
{path:'/movie/2',component:Movie}
{path:'/movie/3',component:Movie}
12345
使用动态路由,将上面创建3个路由规则,合并成一个,提高了路由规则的复用性:
// src/router/index.js 文件
//路由中的动态参数以 : 进行声明,冒号后面的是动态参数的名称
{ path:'/movie/:id',component:Movie}
12345
- 在 vue-router 中使用英文的冒号(
:
)来定义路由的参数项 。
$route.params
访问动态匹配的参数值
在 动态路由
渲染出来的组件中,可以使用 this.$route.params
对象访问到动态匹配的参数值:
<template>
<div class="movie-container">
<!--this.$route是路由的“参数对象”-->
<h3> Movie 组件-- {{this.$route.params.id}} </h3>
</div>
</template>
<script>
export default{
name:'Movie'
}
</script>
123456789101112
使用 props 接收路由参数
为了简化路由参数的获取形式,vue-router 允许在路由规则中开启 props
传参, 在定义路由规则时,声明props : true
选项 ,
// router下的 index.js 文件
// 1. 声明 props : true 选项
{ path:'/movie/:id',component: Movie,props:true }
12345
定义好后,即可在Movie组件中,以props的形式接收到路由规则匹配到的参数项。
<template>
<!-- 3、直接使用props中接收的路由参数 -->
<h3> MyMovie组件--{{id}}</h3>
</template>
<script>
export default{
//2、使用props接收路由规则中匹配到的参数项
props:['id']
</script>
12345678910
编程式导航 API
vue-router 提供了许多编程式导航的 API,其中最常用的导航 API 分别是:
-
this.$router.
push
(‘hash 地址’)
- 跳转到指定 hash 地址,并
增加
一条历史记录 。
- 跳转到指定 hash 地址,并
-
this.$router.
replace
(‘hash 地址’)
- 跳转到指定的 hash 地址,并
替换掉当前
的历史记录 。
- 跳转到指定的 hash 地址,并
-
this.$router.
go
(数值 n)
- 实现导航历史前进、后退。
- $router.
back()
,后退到上一个页面。 - $router.
forward()
,前进到下一个页面。
调用 this.$router.push()
或者 $router.replace()
方法,可以跳转到指定的 hash 地址,展示对应的组件页面 :
<template>
<div class="home-container">
<h3> Home组件 </h3>
<button @click="gotoMovie">跳转到Movie页面</button>
</div>
</template>
<script>
export default{
methods:{
gotollovie(){
this.$router.push("/movie/1")
}
}
</script>
12345678910111213141516
push 和 replace 的区别:
- push 会
增加一条历史记录
- replace 不会增加历史记录,而是
替换掉当前的历史记录
。
调用 this.$router.go()
方法,可以在浏览历史中前进和后退:
<template>
<h3> MyMovie组件---{{id}}</h3>
<button @click="goBack" >后退</button>
</template>
<script>
export default{
props:['id'],
methods:{
goBack(){
this.$router.go(-1) //后退到之前的组件页面
}
},
</script>
1234567891011121314
导航守卫
每次发生路由的导航跳转时,都会触发全局前置守卫。因此,在全局前置守卫中,我们可以对每个路由进行访问权限的控制:
//创建路由实例对象
const router = new VueRouter({..…})
//调用路由实例对象的beforeEach方法,即可声明“全局前置守卫"
//每次发生路由导航跳转的时候,都会自动触发这个“回调函数”
router.beforeEach((to,from,next) =>{
/* 必须调 next 函数 */
})
123456789
守卫方法的 3 个形参:
to
是将要访问的路由的信息 (对象)from
是将要离开的路由的信息对象next
是一个函数,一定要调用next ()
才表示放行,允许这次路由导航 。
注意:
- 在守卫方法中如果
不声明 next
形参,则默认允许用户访问每一个路由
! - 在守卫方法中如果
声明了 next 形参
,则必须调用 next() 函数
,否则不允许用户访问任何一个路由!
next 函数的 3 种调用方式
- 直接放行:
next()
强制其停留在当前页面
:next(false
)强制其跳转到登录页面
:next(’/login’)
结合 token 控制后台主页的访问权限
router.beforeEach((to,from,next) => {
//获取浏览器缓存的用户信息
const userInfo = window.localStorage.getItem('token');
if (to.path === '/main' && !userInfo ){ //访问的是 main 页面且 token 不存在
// 访问的是后台主页,但是没有token的值,跳转到登入页
next('/login')
} else{
next()//访问的不是后台主页,直接放行
})
12345678910
上面代码中,如果项目中有多个页面都需要设置访问权限怎么办呢 ?
- 使用路由导航守卫 判断访问的是否是一个有权限的 Hash 地址问题:
// 第一种: 使用 || 多重判断,代码臃肿
router.beforeEach((to,from,next) => {
if (to.path === '/main' || to.path === '/user' || to.path === 'seeting'){
// code....
} else{
// ....
}
})
123456789
我们可以把多个地址存入到一个数组
或者一个json
文件、js
文件:
const patArr = ['/mian', '/home', '/home/users', '/home/rights']
router.beforeEach((to,from,next) => {
if (patArr.indexOf(to.path) !== -1){
// code....
} else{
// ....
}
})
12345678910
// 存入 js 使用
// 1. 定义一个 pathArr.js文件
export default ['/mian', '/home', '/home/users', '/home/rights']
// 2. 使用时导入
import pathArr from '@/router'
1234567
meta 路由原信息
上面的例子中,还是不够方便,vue-router 中给我们提供了 meta
字段,传入一个对象,设置requiresAuth: true
,就相当于开起来跳转验证。
const router = new VueRouter({
routes: [
{
path: '/blog',
component: Blog,
path: 'bar',
component: Bar,
// 设置 meta ,开启跳转验证
meta: { requiresAuth: true }
}
]
})
123456789101112
全局守卫中定义页面权限:
router.beforeEach((to, from, next) => {
console.log(to)
//判断 $route.matched数组 是否中有 meta 字段值
if (to.matched.some(record => record.meta.requiresAuth)) {
如果当前页面的 是否有 user
if (!localStorage.getItem("user")) {
next({
path: '/login',
//传入完整的路径
query: { redirect: to.fullPath }
})
} else {
next()
}
} else {
next() // 确保一定要调用 next() 这次放行说明在白名单
}
})
123456789101112131415161718
更改登入页面的逻辑
// Login.vue
methods:{
handLeLonge(){
// 获取用户名和密码
setTimeout(()=>{
let data = {
user: this.user
}
// 保存用户名到本地
localStorage.setItem('user',JSON.stringify(data))
this.$route.push({
path: this.$route.query.redirect
})
})
}
}
123456789101112131415161718
拓展 - 网页链接详解
// APP 组件
<router-link to="/movie/1"> 洛基 </router-link>
<router-link to="/movie/2?name=zs&age=20"> 雷神< /router-link>
<router-link to="/movie/3"> 复联 </router-link>
<router-link to="/about"> 关于 </router-link>
1234567
<template>
<div class="movie-container">
<!-- this.$route 是路由的“参数对象” -->
<!-- this.$router 是路由的“导航对象” -->
<h3>Movie 组件 --- {{ $route.params.mid }} --- {{ mid }}</h3>
<button @click="showThis"> 打印 this</button>
<!-- 在行内使用编程式导航跳转的时候,this 必须要省略,否则会报错! -->
<button @click="$router.back()">back 后退</button>
<button @click="$router.forward()">forward 前进</button>
</div>
</template>
<script>
export default {
name: 'Movie',
// 接收 props 数据
props: ['mid'],
methods: {
showThis() {
console.log(this)
},
}
}
</script>
12345678910111213141516171819202122232425262728
点击雷神电影打印出的 this 里,有一个 $route
对象, 如下:
-
在 hash 地址中,
/
后面的参数项,叫做
“路径参数”
- 在路由“参数对象”中,需要使用
this.$route.params
来访问路径参数 - 但是我们一般会使用
props
传参来接收路径参数。
- 在路由“参数对象”中,需要使用
-
在 hash 地址中,
?
后面的参数项,叫做**“查询参数”**
- 在路由“参数对象”中,需要使用
this.$route.query
来访问查询参数
- 在路由“参数对象”中,需要使用
-
在
this.$route
中,path 只是路径部分;fullPath 是完整的地址。
开发思路:
- 要想从一个地址跳到另一个地址 (如: 登录后立即跳转到首页,并且首页里设置默认的展示的页面)
- 先要确定
离开谁
、要去哪儿
- 找到 要离开 的页面,给它添加
redirect
指向新地址。
- 先要确定
注意点 :
-
标签节点不平级时无法使用 v-if 和 v-else 结合來判断
-
在 vue 中:
-
在使用组件的时候,如果某个属性名是“小驼峰”形式,则绑定属性的时候,建议改写成“连字符”格式。例如
cmtCount
建议写成cmt-count
-
Vuex
Vuex 是什么
vuex
是终极的组件之间的数据共享方案。
- Vuex 是实现组件全局状态(数据)管理的一种机制,可以方便的实现组件之间数据的共享。
图丢了
优点:
- 集中管理共享的数居,易于开发和维护
- 高效地实现组件之间的数据共享,提高开发效率
- 响应式数据,与页面的同步
什么样的数据适合存储到 Vuex 中 ?
一般情况下,只有组件之间共享的数据,才有必要存储到 vuex 中;对于组件中的私有数据,依旧存储在组件自身的data中即可 !
Vuex 的基本使用:
-
安装 vuex 依赖包
npm install vuex --save 1
-
在
store / index.js
导入包 并创建 store 对象//导入 vuex 包 import Vuex from 'vuex' Vue.use(Vuex) // 创建 store 对象 const store = new Vuex.Store({ // state 中存放的就是全局共享的数据 state: { count: 0 }, mutations: {}, actions: {}, modules: {} }) 12345678910111213
-
将 store 对象挂载到 vue 实例
main.js
中import store from './store/index'; new Vue({ el: '#app', render: h => h(app), router, // 将创建的共享数据对象,挂载到 Vue 实例中 // 所有的组件,就可以直接从 store 中获取全局的数据了 store }) 123456789101112
一张图看懂 vuex:
state 数据源
State 提供唯一的公共数据源,所有共享的数据都要统一放到
Store
的state
中进行存储。
访问 state 中数据两种方式 :
this.$store.state.全局数据名称 访问:
// this.$store.state.全局数据名称
<h3>当前最新的count值为:{{$store.state.count}}</h3>
1234
- 由于在模板字符串中,是不需要写 this 的。
mapState 映射为计算属性:
通过 mapState 函数,将当前组件需要的全局数据,映射为 computed
计算属性:
// 1. 在想使用数据的组件中 从 vuex 中按需导入 mapState 函数
import { mapState } from 'vuex'
// 2. 将全局数据,映射为当前组件的计算属性
computed: {
...mapState(['count'])
}
12345678
mutations 变更数据
mutation
用于变更 store 中的数据。
- 只能通过 mutation 变更 Store 数据,不可以直接操作 Store 中的数据。
- 虽然操作起来稍微繁琐,但可以集中监控所有数据的变化。
this.$store.commit() 触发 mutations
// 定义 Mutation
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
add(state) {
// 变更状态
state.count++
}
}
})
12345678910111213
// 在组件中 触发 mutation
methods: {
handle1() {
// 触发 mutations 的第一种方式
this.$store.commit('add')
}
}
1234567
可以在触发 mutations 时传递参数
:
// 定义Mutation
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
addN(state, step) {
// 变更状态
state.count += step
}
}
})
123456789101112
// 触发mutation
methods: {
handle2() {
// 在调用 commit 函数,
// 触发 mutations 时携带参数
this.$store.commit('addN', 3)
}
}
12345678
mapMutations 映射为方法
-
从vuex中按需导入 mapMutations函数
import { mapMutations } from 'vuex' 1
-
通过刚才按需导入的 mapMutations 函数,映射为当前组件的
methods
函数。// 2. 将指定的 mutations 函数,映射为当前组件的 methods 函数 methods: { ...mapMutations(['add', 'addN']) } 123456
实例:
// store
mutations: {
add(state) {
// 变更状态
state.count++
},
sub(state) {
state.count--
},
addN(state, step) {
// 变更状态
state.count += step
},
subN(state, step) {
state.count -= step
}
},
// 组件A
import { mapState,mapMutations } from 'vuex'
methods:{
...mapMutations(['sub','subN']),
decrement(){
// 调用
this.sub()
},
decrementN(){
this.subN(5)
}
}
123456789101112131415161718192021222324252627282930
Actions 处理异步操作
如果通过异步操作变更数据,必须通过 Action,而不能使用Mutation,但是在 Action中还是要通过触发Mutation的方式间接变更数据。
注意: 在Actions 中不能直接修改 state中的数据,要通过 mutations修改
。
this.$store.dispatch 触发 Actions
// store/index.js 定义 Action
const store = new Vuex.store({
// ...省略其他代码
mutations: {
// 只有 mutations中的函数才有权利修改 state。
// 不能在 mutations里执行异步操作。
add(state) {
state.count++
}
},
actions: {
// 在Actions 中不能直接修改 state中的数据,要通过 mutations修改。
addAsync(context) {
setTimeout(() => {
context.commit('add')
}, 1000);
}
},
})
1234567891011121314151617181920
// 在组件中 触发 Action
methods:{
handle(){
// 触发 actions 的第一种方式
this.$store.dispatch('addAsync')
}
}
12345678
mapActions 映射为方法
// 1. 从Vuex中按需导入 mapActions 函数。
import {mapActions} from 'vuex'
// 2. 将指定的 actions 函数,映射为当前组件 methods 的方法。
methods:{
...mapActions(['subAsync']),
decrementAsync(){
this.subAsync()
}
}
123456789101112
// store/index.js
actions: {
// 在Actions 中不能直接修改 state中的数据,要通过 mutations修改。
subAsync(context){
setTimeout(() => {
context.commit('sub')
}, 1000);
}
}
12345678910
Getter 按需展示数据
Getter 用于对 Store中的数据进行加工处理形成新的数据。筛选或者排序显示。
- Getter 不会修改 Store 中的原数据,它只起到一个包装器的作用,将Store中的数据加工后输出出来。
- Store 中数据发生变化, Getter 的数据也会跟着变化。
//定义 Getter
const store = new Vuex.Store({
state:{
count:0
},
getters: {
showNum(state) {
return '当前最新的数量是【' + state.count + '】'
}
},
})
1234567891011
this.$store.getters.名称 访问
this.$store.getters.名称
1
mapGetters 映射为计算属性
import { mapGetters } from 'vuex'
computed:{
...mapGetters(['showNum'])
}
12345
简写 !!!
其实,通过mapState,mapMutations,mapActions,mapGetters
映射过来的计算属性,或者方法都可以直接调用,不用在 commit
或者 dispatch
。
正常写法:
<button @click="decrementAsync"> -1Async</button>
import {mapActions} from 'vuex'
methods: {
...mapActions(['subAsync']),
decrementAsync(){
this.subAsync()
}
},
1234567891011121314
其实可以简写成:
<button @click="decrementAsync"> -1Async</button>
import {mapActions} from 'vuex'
//...省略一些代码
methods: {
...mapActions(['subAsync']),
},
12345678910
有参数的时候,也可以直接把参数带上,就像这样:
<button @click="subAsync(5)"> +5 </button>
1
import {mapActions} from 'vuex'
//...省略一些代码
methods: {
...mapActions(['addAsync']),
},
12345678
Vue 3
组件之间的数据共享
Vue 3 中的自定义事件
在vu3中使用使用自定义事相较vu2中多了一个 emits
节点声明:
vue 3中使用自定义事件:
在封装组件时:
1. 在`emits`节点声明自定义事件
2. `触发`监听事件
12
在使用组件时: 监听
自定义事件
声明自定义事件:
// 子组件
<button @click="onBtnClick"> +1 </button>
<script>
export default {
// 自定义事件必须声明到 emits 节点中
emits: ['chage']
}
</script>
123456789
触发自定义事件:
// 子组件
<button @click="onBtnClick"> +1 </button>
<script>
export default {
// 自定义事件必须声明到 emits 节点中
emits: ['chage'],
data() {
// 子组件自己的数据,将来希望把 count 值传给父组件
return { count: 0 }
},
methods: {
onBtnClick() {
this.count t= 1
//修改数据时,通过 $emit()触发自定义事件
// 当点击 '+1' 按钮时 调用this.$emit 触发自定义的 numchange 事件
this.$emit( 'numchange')}
}
}
</script>
123456789101112131415161718192021
监听自定义事件:
// 父组件
// 使用 v-on 指令绑定监听事件
<Son @numchange="getNewCount"></Son>
methods : {
getNewCount(val) {
console.log('监听到了 count 值的变化', val)
},
12345678
组件上的 v-model
当需要维护组件内外数据的同步时
,可以在组件上使用 v-model 指令。示意图:
父子组件数据共享
实现父子组件数据双向同步。
子组件向父组件共享数据:
-
父组件通过
v-bind
属性绑定向子组件共享数据,子组件使用props
接收数据。
父组件向子组件共享数据:
-
通过自定义事件
emits
,然后触发$emit()
, 子组件监听。
- 在 v-bind: 指令之前添加 v-model 指令
- 在子组件中声明 emits 自定义事件,格式为必须 update:xxx
- 调用 $emit() 触发自定义事件,更新父组件中的数据
使 v-model 实现子→父组件共享数据好处:
在父组件中 不用再监听自定义事件
了,也不用再额外声明事件处理
函数。
父组件:
// 使用双向数据绑定指令
<my-son v-model:number="count"> </my-son>
data(){
return{
count: 0
}
}
12345678
子组件:
// 子组件
<button @click="add"> +1 </button>
<script>
export default {
prpos:['number']
emits:['update:number']
methods: {
add() {
this.$emit( 'update:number',this.num+1)}
}
}
</script>
1234567891011121314
后代关系组件之间的数据共享
指的是
父节点的组件
向其子孙组件
共享数据。
使用步骤:
-
父节点通过
provide
方法,对子孙组件共享数据。export default { data() { return { color: 'red', // 1. 定义"父组件"要向"子孙组件"共享的数据 } }, provide() { // provide函数 返回要共享的数据对象 return { color: this.color, count: 1, } }, } 12345678910111213
-
子孙节点通过
inject
接收数据。<template> <h5>三级组件 --- {{ color }} --- {{ count }}</h5> </template> <script> export default { // 子孙组件,使用 inject 接收父节点共享的数据 inject: ['color', 'count'], } </script> 123456789101112
父节点对外共享响应式的数据
父节点使用 provide 向下共享数据时并非响应式
,我们可以结合 computed 函数
向下共享响应式的数据
:
import { computed } from 'vue' // 1. 从vue中按需导入 computed 函数
export default {
data() {
return {
color: 'red',
}
},
provide() {
return {
// 2. 使用 computed 函数,把共享数据包装为"响应式"的数据
color: computed(() => this.color),
count: 1,
}
},
}
1234567891011121314151617
子孙节点使用响应式数据, 注意接收的响应式数据必须以 .value
的形式使用:
<template>
// 响应式数据,必须以.value 的形式进行使用
<h5>子孙组件 --- {{ color.value }} --- {{ count }}</h5>
</template>
<script>
export default {
// 子孙组件,使用 inject 接收父节点共享的数据
inject: ['color', 'count'],
}
</script>
123456789101112
全局数据共享
vuex
是终极的组件之间的数据共享方案。
- 解决大量、频繁的共享数据麻烦问题。
- vuex 就是用来管理组件中
需要共享的数据
。
数据共享总结
父子关系:
- 父 → 子 :
自定义属性
- 子 → 父 :
自定义事件
- 父子组件数据共享 使用组件上的
v-model
兄弟关系: EvenBus
后代关系:provide
& inject
全局数据共享: vuex
在vue3中全局配置 axios
import { createApp } from 'vue'
import './assets/css/bootstrap.css'
import './index.css'
import axios from 'axios'
// 创建一个单页面应用程序实例
const app = createApp(App)
// 配置全局请求根路径
axios.defaults.baseURL = 'https://www.escook.cn'
// 全局挂载到app 根路径中
// $http 是模仿 vue封装成员的方式
app.config.globalProperties.$http = axios
// 实现挂载
app.mount('#app')
1234567891011121314151617181920
在组件中使用:
methods: {
async getInfo() {
const { data: res } = await this.$http.get('/api/get', {
// get 请求必须通过 params 传参
params: {
name: 'ls',
age: 33,
},
})
console.log(res)
},
},
123456789101112
vue3 中创建路由
- vue3 中需要安装 4.x的版本
- 在 src 目录下创建
router.js
文件并配置
import { createRouter, createWebHashHistory } from 'vue-router'
import Login from './components/MyLogin.vue'
import Home from './components/Home.vue'
// 创建路由实例对象
const router = createRouter({
history: createWebHashHistory(), // 指定通过 hash 管理路由的切换
routes: [
{ path: '/home', redirect: '/login' },
{ path: '/login', component: Login, name: 'login' },
],
})
export default router //4. 向外共享路由对象
12345678910111213141516
在 src/main.js
入口文件中,导入并挂载路由模块:
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
// 创建 app 实例
const app = createApp(App)
app.use(router)
// 挂载 app 实例
app.mount('#app')
12345678910111213
事件:
// 父组件
// 使用 v-on 指令绑定监听事件
<Son @numchange="getNewCount"></Son>
methods : {
getNewCount(val) {
console.log('监听到了 count 值的变化', val)
},
12345678
组件上的 v-model
当需要维护组件内外数据的同步时
,可以在组件上使用 v-model 指令。示意图:
父子组件数据共享
实现父子组件数据双向同步。
子组件向父组件共享数据:
- 父组件通过
v-bind
属性绑定向子组件共享数据,子组件使用props
接收数据。
父组件向子组件共享数据:
- 通过自定义事件
emits
,然后触发$emit()
, 子组件监听。
- 在 v-bind: 指令之前添加 v-model 指令
- 在子组件中声明 emits 自定义事件,格式为必须 update:xxx
- 调用 $emit() 触发自定义事件,更新父组件中的数据
使 v-model 实现子→父组件共享数据好处:
在父组件中 不用再监听自定义事件
了,也不用再额外声明事件处理
函数。
父组件:
// 使用双向数据绑定指令
<my-son v-model:number="count"> </my-son>
data(){
return{
count: 0
}
}
12345678
子组件:
// 子组件
<button @click="add"> +1 </button>
<script>
export default {
prpos:['number']
emits:['update:number']
methods: {
add() {
this.$emit( 'update:number',this.num+1)}
}
}
</script>
1234567891011121314
后代关系组件之间的数据共享
指的是
父节点的组件
向其子孙组件
共享数据。
使用步骤:
-
父节点通过
provide
方法,对子孙组件共享数据。export default { data() { return { color: 'red', // 1. 定义"父组件"要向"子孙组件"共享的数据 } }, provide() { // provide函数 返回要共享的数据对象 return { color: this.color, count: 1, } }, } 12345678910111213
-
子孙节点通过
inject
接收数据。<template> <h5>三级组件 --- {{ color }} --- {{ count }}</h5> </template> <script> export default { // 子孙组件,使用 inject 接收父节点共享的数据 inject: ['color', 'count'], } </script> 123456789101112
父节点对外共享响应式的数据
父节点使用 provide 向下共享数据时并非响应式
,我们可以结合 computed 函数
向下共享响应式的数据
:
import { computed } from 'vue' // 1. 从vue中按需导入 computed 函数
export default {
data() {
return {
color: 'red',
}
},
provide() {
return {
// 2. 使用 computed 函数,把共享数据包装为"响应式"的数据
color: computed(() => this.color),
count: 1,
}
},
}
1234567891011121314151617
子孙节点使用响应式数据, 注意接收的响应式数据必须以 .value
的形式使用:
<template>
// 响应式数据,必须以.value 的形式进行使用
<h5>子孙组件 --- {{ color.value }} --- {{ count }}</h5>
</template>
<script>
export default {
// 子孙组件,使用 inject 接收父节点共享的数据
inject: ['color', 'count'],
}
</script>
123456789101112
全局数据共享
vuex
是终极的组件之间的数据共享方案。
- 解决大量、频繁的共享数据麻烦问题。
- vuex 就是用来管理组件中
需要共享的数据
。
数据共享总结
父子关系:
- 父 → 子 :
自定义属性
- 子 → 父 :
自定义事件
- 父子组件数据共享 使用组件上的
v-model
兄弟关系: EvenBus
后代关系:provide
& inject
全局数据共享: vuex
在vue3中全局配置 axios
import { createApp } from 'vue'
import './assets/css/bootstrap.css'
import './index.css'
import axios from 'axios'
// 创建一个单页面应用程序实例
const app = createApp(App)
// 配置全局请求根路径
axios.defaults.baseURL = 'https://www.escook.cn'
// 全局挂载到app 根路径中
// $http 是模仿 vue封装成员的方式
app.config.globalProperties.$http = axios
// 实现挂载
app.mount('#app')
1234567891011121314151617181920
在组件中使用:
methods: {
async getInfo() {
const { data: res } = await this.$http.get('/api/get', {
// get 请求必须通过 params 传参
params: {
name: 'ls',
age: 33,
},
})
console.log(res)
},
},
123456789101112
vue3 中创建路由
- vue3 中需要安装 4.x的版本
- 在 src 目录下创建
router.js
文件并配置
import { createRouter, createWebHashHistory } from 'vue-router'
import Login from './components/MyLogin.vue'
import Home from './components/Home.vue'
// 创建路由实例对象
const router = createRouter({
history: createWebHashHistory(), // 指定通过 hash 管理路由的切换
routes: [
{ path: '/home', redirect: '/login' },
{ path: '/login', component: Login, name: 'login' },
],
})
export default router //4. 向外共享路由对象
12345678910111213141516
在 src/main.js
入口文件中,导入并挂载路由模块:
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
// 创建 app 实例
const app = createApp(App)
app.use(router)
// 挂载 app 实例
app.mount('#app')
12345678910111213