一、Vue语法初探
- 本章中,将会通过编写实际例子,带你对 Vue 的语法有个粗浅的认知,让大家结合例子,有一些自己的疑问,从而带着问题继续学习,以便于更好的理解和掌握后面的知识点。
1.初学编写 HelloWorld 和 Counter
本章讲述了通过 cdn 的方式是如何使用 vue3.0。
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>hollo world</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
// 创建vue实例
Vue.createApp({
data(){
return{
content:1 // 定义变量
}
},
mounted(){
setInterval(() => {
// 简写
this.content += 1
// 等同于
// this.$data.content +=1
}, 1000)
},
template: '<div>{{content}}</div>' // 在标签中使用变量
}).mount('#root') // 将template中的内容挂载到id为root的元素中
</script>
</html>
2.编写字符串反转和内容隐藏功能
接下来用vue做两个简单的小 demo ,为了让初学者更好的理解 vue 语法
字符串反转功能
需求 : 用 vue 实现 点击按钮 时, 反转 文字内容,例如: 123 点击后反转成 321 的这么一个小效果。
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>反转字符串</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
// 创建vue实例
Vue.createApp({
data(){
return{
content: 'hello world' // 定义变量
}
},
methods: {
handleBtnClick(){
// 反转过后转换成字符串
const newContent = this.content.split('').reverse().join('')
// 重新赋值给页面元素
this.content = newContent
}
},
template: `
<div>
{{content}}
<button v-on:click="handleBtnClick">反转</button>
</div>
`
// 在标签中使用变量
}).mount('#root') // 将template中的内容挂载到id为root的元素中
</script>
</html>
内容隐藏功能
需求 :通过 点击按钮 实现 显示隐藏 文字的 开关 的一个小功能。 index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>内容隐藏功能</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
// 创建vue实例
Vue.createApp({
data(){
return{
show: true, // 定义变量控制显示隐藏
}
},
methods: {
handleBtnClick(){
this.show = !this.show
}
},
template: `
<div>
<span v-if="show">hello world</span>
<button v-on:click="handleBtnClick">显示/隐藏</button>
</div>
`
// 在标签中使用变量
}).mount('#root') // 将template中的内容挂载到id为root的元素中
</script>
</html>
3.编写TodoList功能了解循环与双向绑定
本章节讲解了,如何使用 v-for 以及 v-model 。
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Todo List</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
// 创建vue实例
Vue.createApp({
data(){
return{
inputValue: '',
list: [
'hello',
'word',
'dell',
'lee'
]
}
},
methods: {
handleAddItem(){
this.list.push(this.inputValue)
this.inputValue = ''
}
},
template: `
<div>
<input v-model="inputValue"/>
<button v-on:click="handleAddItem">增加</button>
<ul>
<li v-for="(item, index) of list">{{item}}{{index}}</li>
</ul>
</div>
`
// 在标签中使用变量
}).mount('#root') // 将template中的内容挂载到id为root的元素中
</script>
</html>
4.组件概念初探,对 TodoList 进行组件代码拆分
本章节讲解了,如何使用 v-bind指令 以及 创建组件、 使用组件 的概念,案例如下:
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Todo List组件封装</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
// 创建vue实例
const app = Vue.createApp({
data(){
return{
inputValue: '',
list: [
'hello',
'word',
'dell',
'lee'
]
}
},
methods: {
handleAddItem(){
this.list.push(this.inputValue)
this.inputValue = ''
}
},
template: `
<div>
<input v-model="inputValue"/>
<button v-on:click="handleAddItem" v-bind:title="inputValue">增加</button>
<ul>
<todo-item v-for="(item, index) of list" v-bind:content="item" v-bind:index="index"/>
</ul>
</div>
`
// 在标签中使用变量
})
// 定义组件同时注册组件
app.component('todo-item', { // 参数1:组件名称,参数2:组件配置
props: ['content', 'index'], // 接收父组件传值
template: '<li>{{index}} -- {{content}}</li>'
})
// 1. 将template中的内容挂载到id为root的元素中
// 2. 如果有组件,必须要先注册组件,不可直接链式写法创建实例(例如:Vue.createApp(...).mount('#root')), 然后直接挂载到页面元素执行mount,载到页面元素执行mount,
// 那样的话页面已经初始化完成了,component就加不进去了,所以最后mount才可以
app.mount('#root')
</script>
</html>
二、Vue基础语法
本章中,将会讲解 生命周期函数,指令,模版,数据,侦听器,事件,循环渲染 等基础语法知识点,帮助大家理解第一章重写过的代码,同时理解数据驱动的编程思想。
1.Vue 中应用和组件的基础概念
本章节讲解了, mvvm 设计模式 ,以及如何 修改外部根组件 。
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>组件的基础概念</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
// createApp 标识创建一个 Vue 应用,存储到 app 常量中
// 传入的参数表示,这个应用最外层的组件,应该如何表示
// mvvm 设计模式 m -> model 数据,v -> view 视图,vm -> viewModel 视图数据连接层
const app = Vue.createApp({
data(){ // model 层
return{
message: 'hello world'
}
},
template: '<div>{{message}}</div>' // view 层
})
// vm 代表的就是 vue 应用的根组件,可以在f12控制台打印出来
const vm = app.mount('#root')
// 如果在f12控制台修改message数据也会发生变化,因为是双向数据绑定
// vm.$data.message = '呵呵哒'
</script>
</html>
用 常量 保存 Vue实例 ,通过 vm. 的方式既可调用该实例下的 变量以及方法 。
2.理解 Vue 中的生命周期函数
本章节讲解了, vue 的 生命周期 的 执行顺序 ,vue 目前总共 8个生命周期 ,大家只需要记住: 生命周期,是在谋一时刻会自动执行的函数
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>vue的生命周期</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
// 生命周期函数:在谋一时刻会自动执行的函数
const app = Vue.createApp({
data(){
return{
message: 'hello world'
}
},
// 1. 在实例生成之【前】会自动执行的函数
beforeCreate(){
console.log('beforeCreate')
},
// 2. 在实例生成之【后】会自动执行的函数
created(){
console.log('created')
},
// 3. 在组件内容被渲染到页面之【前】立即执行的函数
beforeMount(){
console.log(document.getElementById('root').innerHTML, 'beforeMount')
},
// 4. 在组件内容被渲染到页面之【后】立即执行的函数
mounted(){
console.log(document.getElementById('root').innerHTML, 'mounted')
},
// 5. 当数据发生变化时会立即自动执行的函数
beforeUpdate(){
console.log(document.getElementById('root').innerHTML, 'beforeUpdate')
},
// 6. 当数据发生变化,页面重新渲染后,会自动执行的函数
updated(){
console.log(document.getElementById('root').innerHTML, 'updated')
},
// 7. 当 Vue 应用失效时, 自动执行的函数(被销毁时执行的函数,执行app.unmount()),从挂在的#root元素上解开绑定
beforeUnmount(){
console.log(document.getElementById('root').innerHTML, 'beforeUnmount')
},
// 8. 当 Vue 应用失效时,且 dom 完全销毁之后,自动执行的函数
unmounted(){
console.log(document.getElementById('root').innerHTML, 'unmounted')
},
methods: {
handleItemClick(){
alert('click')
}
},
template: '<div v-on:click="handleItemClick">{{message}}</div>'
})
const vm = app.mount('#root')
</script>
</html>
如果 实例化 的 Vue根组件对象 中也可以不写 template 属性,可以直接在 元素内部 使用 表达式 的方式来做到同样效果,如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>vue的生命周期</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root">
<div v-on:click="handleItemClick">{{message}}</div>
</div>
</body>
<script>
// 生命周期函数:在谋一时刻会自动执行的函数
const app = Vue.createApp({
data(){
return{
message: 'hello world'
}
},
// 1. 在实例生成之【前】会自动执行的函数
beforeCreate(){
console.log('beforeCreate')
},
// 2. 在实例生成之【后】会自动执行的函数
created(){
console.log('created')
},
// 3. 在组件内容被渲染到页面之【前】立即执行的函数
beforeMount(){
console.log(document.getElementById('root').innerHTML, 'beforeMount')
},
// 4. 在组件内容被渲染到页面之【后】立即执行的函数
mounted(){
console.log(document.getElementById('root').innerHTML, 'mounted')
},
// 5. 当数据发生变化时会立即自动执行的函数
beforeUpdate(){
console.log(document.getElementById('root').innerHTML, 'beforeUpdate')
},
// 6. 当数据发生变化,页面重新渲染后,会自动执行的函数
updated(){
console.log(document.getElementById('root').innerHTML, 'updated')
},
// 7. 当 Vue 应用失效时, 自动执行的函数(被销毁时执行的函数,执行app.unmount()),从挂在的#root元素上解开绑定
beforeUnmount(){
console.log(document.getElementById('root').innerHTML, 'beforeUnmount')
},
// 8. 当 Vue 应用失效时,且 dom 完全销毁之后,自动执行的函数
unmounted(){
console.log(document.getElementById('root').innerHTML, 'unmounted')
},
methods: {
handleItemClick(){
alert('click')
}
}
})
const vm = app.mount('#root')
</script>
</html>
3.常用模版语法讲解
- v-text 与 {{}} :用来渲染纯文字
- v-html :用来渲染标签
- v-bind:动态属性
- v-once:元素中的变量只使用一次
- v-if :显示/隐藏重新渲染页面元素
- v-show :显示/隐藏元素,不重新渲染dom元素(display:none,来会切换显示隐藏)
- v-on :定义事件,例如:click 之类的
- 简写形式:v-on 以及 v-bind 等等的简写
- 动态属性:动态 给 属性名 或者 事件名 赋值
- 事件修饰符:阻止冒泡事件 等等的一些 语法糖
v-text 与 {{}}
如果我们想在页面渲染一个 data 中的 变量 ,可以使用 v-text 如下:
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>常用模版语法讲解</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
// 生命周期函数:在谋一时刻会自动执行的函数
const app = Vue.createApp({
data(){
return{
message: 'hello world'
}
},
template: '<div v-text="message"></div>'
})
const vm = app.mount('#root')
</script>
</html>
或者使用 {{}}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>常用模版语法讲解</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
// 生命周期函数:在谋一时刻会自动执行的函数
const app = Vue.createApp({
data(){
return{
message: 'hello world'
}
},
template: '<div>{{message}}</div>'
})
const vm = app.mount('#root')
</script>
</html>
v-text 与 {{}} 的区别:v-text :将数据解析为纯文本,不能输出真正的 html ,与 花括号 的区别是在页面加载时不显示 {{}}
v-html
如果我们想 动态 在 字符串中拼接标签 并且 展示在页面上 ,我们可以使用 v-html ,如下:
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>常用模版语法讲解</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
// 生命周期函数:在谋一时刻会自动执行的函数
const app = Vue.createApp({
data(){
return{
message: '<strong>hello world</strong>'
}
},
template: '<div v-html="message"></div>'
})
const vm = app.mount('#root')
</script>
</html>
v-bind
如果我们想 动态给标签添加属性 时,可以使用 v-bind ,代码如下:
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>常用模版语法讲解</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
// 生命周期函数:在谋一时刻会自动执行的函数
const app = Vue.createApp({
data(){
return{
message: 'hello world'
}
},
// template: '<div v-bind:title="message">{{message}}</div>'
// 或者
// template: '<div :title="message">{{message}}</div>'
// 或者可以写一些表达式
template: '<div :title="message">{{ message ? "1" : "0" }}</div>'
})
const vm = app.mount('#root')
</script>
</html>
v-once
需求 :只想展示第一次变量中展示的数据,以后 变量改变了,页面展示的数据也不跟随改变,可以使用 v-once ,它的意思是 元素中的变量只使用一次
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>常用模版语法讲解</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
// 生命周期函数:在谋一时刻会自动执行的函数
const app = Vue.createApp({
data(){
return{
message: 'hello world'
}
},
template: '<div v-once>{{message}}</div>'
})
const vm = app.mount('#root')
</script>
</html>
v-if
应用场景:适用于 不频繁显示隐藏元素 的需求,如果 频繁显示隐藏 ,可使用 v-show 。
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>常用模版语法讲解</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
// 生命周期函数:在谋一时刻会自动执行的函数
const app = Vue.createApp({
data(){
return{
message: 'hello world',
show: true
}
},
template: '<div v-if="show">{{message}}</div>'
})
const vm = app.mount('#root')
</script>
</html>
v-show
应用场景:适用于 频繁显示隐藏
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>常用模版语法讲解</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
// 生命周期函数:在谋一时刻会自动执行的函数
const app = Vue.createApp({
data(){
return{
message: 'hello world',
show: true
}
},
template: '<div v-show="show">{{message}}</div>'
})
const vm = app.mount('#root')
</script>
</html>
v-on
应用场景:给元素添加事件时,例如: 给一个元素,添加click事件,代码如下:
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>常用模版语法讲解</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
// 生命周期函数:在谋一时刻会自动执行的函数
const app = Vue.createApp({
data(){
return{
message: 'hello world',
show: true
}
},
methods: {
handleClick(){
alert('click')
}
},
template: '<div v-on:click="handleClick">{{message}}</div>'
})
const vm = app.mount('#root')
</script>
</html>
这里要注意,方法 一定要写在 methods 中。
简写形式
- v-on:click 可以写成 @click
- v-bind:title 可以写成 :title
动态属性
应用场景:假如说元素身上的 属性名或者事件名不确定 ,可以用一个 变量的形式来定义动态定义属性 ,代码如下:
index.htrml
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>常用模版语法讲解</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
// 生命周期函数:在谋一时刻会自动执行的函数
const app = Vue.createApp({
data(){
return{
message: 'hello world',
show: true,
name: 'title', // 属性名称
event: 'click' // 事件名称
}
},
methods: {
handleClick(){
alert('click')
}
},
template: '<div @[event]="handleClick" :[name]="message">{{message}}</div>'
})
const vm = app.mount('#root')
</script>
</html>
事件修饰符
应用场景 :以前 js 阻止冒泡默认事件 都用 event对象 下的自带方法,例如:e.preventDefault() ,现在 vue 中有了新的方式 语法糖 解决同样问题,代码如下:
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>常用模版语法讲解</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
// 生命周期函数:在谋一时刻会自动执行的函数
const app = Vue.createApp({
data(){
return{
message: 'hello world',
show: true,
name: 'title', // 属性名称
event: 'click' // 事件名称
}
},
methods: {
handleClick(){
alert('click')
}
},
template: `
<form action="https://www.baidu.com" @click.prevent="handleClick">
<button type="submit">提交</button>
</form>
`
})
const vm = app.mount('#root')
</script>
</html>
4.数据,方法,计算属性和侦听器
本章将主要讲解data 、 methods 、 computed 、 watcher
data
之前使用的 data 中的数据,我们可以 通过控制台 来对 data 中的 数据进行修改 ,例如:
vm.$data.message = '123'
这样就可以把 data 中的数据 修改 了,还有一个更简便的方式
vm.message = '123'
这样也是可以修改的。
methods
定义方法 可以在 methods 中定义,但是这里需要注意 methods 中的方法的 this指向 ,都是指向vue的实例 ,定义方法时候不可以用箭头函数的方式定义方法,因为箭头函数的this指向的是外层对象的this 。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>数据,方法,计算属性和侦听器</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
const app = Vue.createApp({
data(){
return{
message: 'hello world'
}
},
methods: {
handleClick(){
console.log('click', this.message)
}
},
template: `
<div @click="handleClick">{{message}}</div>
`
})
const vm = app.mount('#root')
</script>
</html>
methods 也可以这样用,这种用法叫做 插值表达式 ,如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>数据,方法,计算属性和侦听器</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
const app = Vue.createApp({
data(){
return{
message: 'hello world'
}
},
methods: {
formatString(string){
return string.toUpperCase()
}
},
template: `
<div>{{formatString(message)}}</div>
`
})
const vm = app.mount('#root')
</script>
</html>
computed
computed 用来 计算值,最终 返回一个计算后的结果
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>数据,方法,计算属性和侦听器</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
const app = Vue.createApp({
data(){
return{
count: 2,
price: 5
}
},
computed: {
total(){
return this.count * this.price
}
},
template: `
<div>{{ total }}</div>
`
})
const vm = app.mount('#root')
</script>
</html>
那么有人说了用 methods 能实现与 computed 同样的效果,实际上它俩还是有不同的,
computed :当计算属性依赖的内容发生变更时,才会重新执行计算
methods:只要页面重新渲染,才会重新计算
watch
应用场景:监听数据变化时,做一些逻辑处理
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>数据,方法,计算属性和侦听器</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
// computed 和 method 都能实现的一个功能,建议使用 computed,因为有缓存
// computed 和 watcher 都能实现的功能,建议使用 computed 因为更加简洁
const app = Vue.createApp({
data(){
return{
count: 2,
price: 5,
newTotal: 10
}
},
watch:{
// price 发生变化时,函数会执行
price(current, prev){
this.newTotal = current * this.count
}
},
template: `
<div>{{newTotal}}</div>
`
})
const vm = app.mount('#root')
</script>
</html>
5.样式绑定语法
记下来讲 2种 绑定样式的方法,动态class 以及 动态style 。
class绑定
在 vue 中有以下几种方式来 动态绑定class:
- 字符串形式 : 通过字符串来控制样式的显示
- 对象形式 :通过对象来控制样式的显示
- 数组形式 :通过数组来控制样式的显示
- 组件上绑定class :通过 $attrs 来继承父组件样式
字符串形式
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>样式绑定语法</title>
<style>
.red{
color: red;
}
.green{
color: green;
}
</style>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
const app = Vue.createApp({
data(){
return{
classString: 'red'
}
},
template: `
<div :class="classString">hello world</div>
`
})
const vm = app.mount('#root')
</script>
</html>
然后在 f12控制台 输入
vm.classString = 'green'
文字就会变成绿色 。
对象形式
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>样式绑定语法</title>
<style>
.red{
color: red;
}
.green{
color: green;
}
</style>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
const app = Vue.createApp({
data(){
return{
classObject: {red:false, green:true}
}
},
template: `
<div :class="classObject">hello world</div>
`
})
const vm = app.mount('#root')
</script>
</html>
数组形式
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>样式绑定语法</title>
<style>
.red{
color: red;
}
.green{
color: green;
}
</style>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
const app = Vue.createApp({
data(){
return{
classArray: ['red','green', { brown: true }]
}
},
template: `
<div :class="classArray">hello world</div>
`
})
const vm = app.mount('#root')
</script>
</html>
组件上绑定class
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>样式绑定语法</title>
<style>
.red{
color: red;
}
.green{
color: green;
}
</style>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
const app = Vue.createApp({
data(){
return{
classString: 'red',
classObject: {red:false, green:true},
classArray: ['red','green', { brown: true }]
}
},
template: `
<div :class="classString">
hello world
<demo class="green"/>
</div>
`
})
app.component('demo', {
template: `
<div class="green">one</div>
`
})
const vm = app.mount('#root')
</script>
</html>
如果 子组件中的template子级只有一个元素 ,可以在 父组件中子组件标签上添加class 改变 子组件样式 ,子组件template子级有多个元素 ,vue 会不知道到底是把 green 加到 第一个节点 ,还是 第二个节点 ,这种情况可以 直接在子组件标签身上添加class样式 ,如下
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>样式绑定语法</title>
<style>
.red{
color: red;
}
.green{
color: green;
}
</style>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
const app = Vue.createApp({
data(){
return{
classString: 'red',
classObject: {red:false, green:true},
classArray: ['red','green', { brown: true }]
}
},
template: `
<div :class="classString">
hello world
<demo/>
</div>
`
})
app.component('demo', {
template: `
<div class="green">one</div>
<div class="green">two</div>
`
})
const vm = app.mount('#root')
</script>
</html>
再或者可以使用 $attrs,如下代码,这样的写法是什么意思呢,意思:子组件使用父组件属性上的class的值(子组件继承父组件样式)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>样式绑定语法</title>
<style>
.red{
color: red;
}
.green{
color: green;
}
</style>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
const app = Vue.createApp({
data(){
return{
classString: 'red',
classObject: {red:false, green:true},
classArray: ['red','green', { brown: true }]
}
},
template: `
<div :class="classString">
hello world
<demo class="green"/>
</div>
`
})
app.component('demo', {
template: `
<div :class="$attrs.class">one</div>
<div :class="$attrs.class">two</div>
`
})
const vm = app.mount('#root')
</script>
</html>
style绑定
在 vue 中有以下几种方式来 动态绑定style:
- 字符串形式:通过字符串来控制样式的显示
- 对象形式:通过对象形式来控制样式的显示
字符串形式
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>样式绑定语法</title>
<style>
.red{
color: red;
}
.green{
color: green;
}
</style>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
const app = Vue.createApp({
data(){
return{
styleString: 'color:yellow'
}
},
template: `
<div :style="styleString">hello world</div>
`
})
const vm = app.mount('#root')
</script>
</html>
对象形式
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>样式绑定语法</title>
<style>
.red{
color: red;
}
.green{
color: green;
}
</style>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
const app = Vue.createApp({
data(){
return{
styleObject: {
color: 'orange',
background: 'yellow'
}
}
},
template: `
<div :style="styleObject">hello world</div>
`
})
const vm = app.mount('#root')
</script>
</html>
6.条件渲染
如果 频繁的显示隐藏 建议使用 v-show ,因为 v-if 会 重新渲染dom元素 ,以及 v-if 还有配套的 v-else 、v-else-if ,如下 demo :
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>条件渲染</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
const app = Vue.createApp({
data(){
return{
show: false,
condition: true
}
},
template: `
<div v-if="show">if</div>
<div v-else-if="condition">else if</div>
<div v-else>else</div>
`
})
const vm = app.mount('#root')
</script>
</html>
这种写法 if 要跟 else 或 else-if 使用的标签,贴在一起使用,不然会报错。
7.列表循环渲染
接下来介绍如何使用 v-for循环 数组 或 对象 。
循环数组
循环数组用有 2 个参数
参数1:item 每一项的值
参数2:index索引 ,index可选 。
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>列表循环渲染</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
const app = Vue.createApp({
data(){
return{
list: ['dell', 'lee', 'teacher']
}
},
template: `
<div>
<div v-for="(item, index) in list" :key="item">
{{ item }} -- {{ index }}
</div>
</div>
`
})
const vm = app.mount('#root')
</script>
</html>
循环对象
循环对象用有3 个参数
参数1:value每一项 键值对 的值 参数2:key 对象的 key 参数3:index 索引值
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>列表循环渲染</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
const app = Vue.createApp({
data(){
return{
listObject: {
name: 'dell',
lastName: 'lee',
job: 'teacher'
}
}
},
template: `
<div v-for="(value, key, index) in listObject" :key="value">
{{value}} -- {{key}} -- {{index}}
</div>
`
})
const vm = app.mount('#root')
</script>
</html>
循环数字
也可以 循环数字 ,如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>列表循环渲染</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
const app = Vue.createApp({
template: `
<div v-for="item in 10" :key="item">
{{ item }}
</div>
`
})
const vm = app.mount('#root')
</script>
</html>
v-fo 与 v-if 同时使用
需求 : 循环数组 或 对象 时,如果某一项不想展示,你可能会想 v-for 与 v-if 同时使用,这种做法是错误的, v-for 与 v-if 同时使用时,v-for 的权重要比 v-if 高,所以 v-if 不生效
解决 :
- 方案一:在内部添加一个 template标签(该标签渲染时不会占位) ,在 div 元素上使用 v-if 做 判断处理
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>列表循环渲染</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
const app = Vue.createApp({
data(){
return{
listObject: {
name: 'dell',
lastName: 'lee',
job: 'teacher'
}
}
},
template: `
<template v-for="(value, key, index) in listObject" :key="value">
<div v-if="value !== 'dell'">
{{value}} -- {{key}} -- {{index}}
</div>
</template>
`
})
const vm = app.mount('#root')
</script>
</html>
- 方案二:用 计算属性过滤掉隐藏项 ,只返回 显示项
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>列表循环渲染</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
const app = Vue.createApp({
data(){
return{
listObject: {
name: 'dell',
lastName: 'lee',
job: 'teacher'
}
}
},
computed: {
// 把展示项过滤出来
filterLsit(){
let result = {}
for(const [key,value] of Object.entries(this.listObject)){
if(value !== 'dell'){
result[key] = value
}
}
return result
}
},
template: `
<div v-for="(value, key, index) in filterLsit" :key="value">
{{value}} -- {{key}} -- {{index}}
</div>
`
})
const vm = app.mount('#root')
</script>
</html>
注意
使用 v-for循环 时,要添加 key属性 ,因为,向 循环的列表添加了一项数据,vue不会重新渲染整个列表,只会渲染最新添加的那项 ,为了做区分所以 加key属性做数据的区分 ,建议使用 item 或者一些唯一的值来做key ,不推荐使用 index ,因为 index 做 key 渲染列表时,如果有恰巧有 删除功能,删除了一项 item, 删除项 后的兄弟级会 自动顶到前面补位 , 就会 导致程序出现bug 。
8.事件绑定
- event问题 :event 的部分场景,如何使用
- 一个事件执行多个函数
- 事件修饰符 :解决 事件冒泡 等问题
- 按键修饰符: 回车键 、 tab键 等等
- 鼠标修饰符 : 鼠标对应的左中右按键修饰符
- 精确修饰符 : 组合按键一起按才会触发方法
event问题
如果我们要 额外传递参数 ,还要用到原生 event 对象时,在 实参 的地方传入一个 $event , 形参 的地方接收一下即可,如下代码:
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>事件绑定</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
const app = Vue.createApp({
data(){
return {
counter: 0
}
},
methods: {
handleAddBtnClick(num, event){
console.log(event.target)
this.counter += num
}
},
template: `
{{counter}}
<button @click="handleAddBtnClick(2, $event)">button</button>
`
})
const vm = app.mount('#root')
</script>
</html>
一个事件执行多个函数
在 vue 中有时候你想通过一个 点击事件同时执行2个或多个方法 时,你可以向下面代码一样,通过 逗号分隔 的形式来执行多个函数。
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>事件绑定</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
const app = Vue.createApp({
methods: {
handleAddBtnClick(){
alert(1)
},
handleAddBtnClick2(){
alert(2)
}
},
template: `
<button @click="handleAddBtnClick(),handleAddBtnClick2()">button</button>
`
})
const vm = app.mount('#root')
</script>
</html>
事件修饰符
1.stop修饰符
子容器 与 父容器 都有 点击事件 时,点击子容器,父容器的方法也会被触发 ,这种行为被称之为 事件冒泡 ,为了解决该问题, vue 中提供了 stop事件修饰符 来解决该问题。
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>事件绑定</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
const app = Vue.createApp({
data(){
return {
counter: 0
}
},
methods: {
handleAddBtnClick(num, event){
console.log(event.target)
this.counter += num
},
handleDivClick(){
alert('div click')
}
},
template: `
{{counter}}
<div @click="handleDivClick">
<button @click.stop="handleAddBtnClick(2, $event)">button</button>
</div>
`
})
const vm = app.mount('#root')
</script>
</html>
2.self修饰符
在 父子嵌套 的结构中,只想 通过点击父级触发父级的事件,点击子级不触发父级的事件,可以通过 self修饰符 来解决此问题。
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>事件绑定</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
const app = Vue.createApp({
data(){
return {
counter: 0
}
},
methods: {
handleAddBtnClick(num, event){
console.log(event.target)
this.counter += num
},
handleDivClick(){
alert('div click')
}
},
template: `
<div @click.self="handleDivClick">
{{counter}}
<button @click="handleAddBtnClick(2, $event)">button</button>
</div>
`
})
const vm = app.mount('#root')
</script>
</html>
3.prevent修饰符
阻止默认行为修饰符
4.capture修饰符
捕获模式 : 冒泡模式 是从内到外,捕获模式 是从外到内,这个平时用的不多。
5.once修饰符
once: 事件只执行一次。
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>事件绑定</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
const app = Vue.createApp({
methods: {
handleDivClick(){
alert('div click')
}
},
template: `
<div @click.once="handleDivClick">
点击我
</div>
`
})
const vm = app.mount('#root')
</script>
</html>
6.passive修饰符
passive修饰符 在使用 @scroll事件 时使用,性能会得到很大提升。
按键修饰符
vue 中除了 事件修饰符 还有 按键修饰符 ,这里就例举一些, enter 、tab 、delete 、 esc 、 up、 down 、left 、 right 这些修饰符都可以使用,可以通过这些 修饰符 用 keydown 进行一些按键的绑定。
1.enter修饰符
以前用 原生js 想实现 按回车键触发一个方法 ,就需要用 event 中的 keyCode 判断等不等 13 ,如果等于 13 就证明是 回车键 ,再执行该方法,vue 中可以用 enter按键修饰符 来解决此问题。
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>事件绑定</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
const app = Vue.createApp({
methods: {
// 原生写法想实现回车键就只能用event的keyCode === 13 代表是回车键
handleKeyDown(){
console.log('keydown')
}
},
template: `
<div>
<input @keydown.enter="handleKeyDown"/>
</div>
`
})
const vm = app.mount('#root')
</script>
</html>
鼠标修饰符
鼠标修饰符 有 3 个:left、right 、middle ,接下來就拿 middle 举例:
1.middle修饰符
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>事件绑定</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
const app = Vue.createApp({
methods: {
handleClick(){
console.log('handleClick')
}
},
template: `
<div>
<div @click.middle="handleClick">点击</div>
</div>
`
})
const vm = app.mount('#root')
</script>
</html>
精确修饰符
精确修饰符 是什么意思呢,看下面代码,意思是 必须按住 ctrl 再按住鼠标触发 click 键时候才会执行要执行的函数
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>事件绑定</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
const app = Vue.createApp({
methods: {
handleClick(){
console.log('handleClick')
}
},
template: `
<div>
<div @click.ctrl="handleClick">点击</div>
</div>
`
})
const vm = app.mount('#root')
</script>
</html>
按住 ctrl + click ,此时再按住其他按键,也可以触发方法 ,有时候就是想 只按住这两个按键(ctrl + click) 触发方法,那应该如何解决呢,可以再加个 .exact修饰符
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>事件绑定</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
const app = Vue.createApp({
methods: {
handleClick(){
console.log('handleClick')
}
},
template: `
<div>
<div @click.ctrl.exact="handleClick">点击</div>
</div>
`
})
const vm = app.mount('#root')
</script>
</html>
9.表单中双向绑定指令的使用
input的双向数据绑定
下面的代码中,输入框的值改变后,页面上的文字也会改变,文字改变后,输入框中的值也会改变 ,这就是 双向数据绑定 ,如果用 input框的 v-model ,就可以不用写value 属性了,直接用它就可以了。
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>表单中双向绑定指令的使用</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
const app = Vue.createApp({
data(){
return{
message: 'hello'
}
},
template: `
<div>
{{message}}
<input v-model="message"/>
</div>
`
})
const vm = app.mount('#root')
</script>
</html>
textarea的双向数据绑定
向以前 textarea 都要用 双标签 来使用,像这样:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>表单中双向绑定指令的使用</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
const app = Vue.createApp({
template: `
<textarea>
123
</textarea>
`
})
const vm = app.mount('#root')
</script>
</html>
现在,在 vue中可以实现 双向数据绑定 了,而且只需要 单标签 的形式就可以使用。如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>表单中双向绑定指令的使用</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
const app = Vue.createApp({
data(){
return{
message: 'hello'
}
},
template: `
<textarea v-model="message"/>
`
})
const vm = app.mount('#root')
</script>
</html>
checkbox的双向数据绑定
可以用 input 类型改为 checkbox ,然后绑定一个 布尔值的变量 即可实现 双向数据绑定 ,代码如下:
1.单个 checkbox
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>表单中双向绑定指令的使用</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
const app = Vue.createApp({
data(){
return{
message: false
}
},
template: `
{{message}}
<input type="checkbox" v-model="message"/>
`
})
const vm = app.mount('#root')
</script>
</html>
2.多个checkbox
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>表单中双向绑定指令的使用</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
const app = Vue.createApp({
data(){
return{
message: []
}
},
template: `
{{message}}
jack<input type="checkbox" v-model="message" value="jack"/>
dell<input type="checkbox" v-model="message" value="dell"/>
lee<input type="checkbox" v-model="message" value="lee"/>
`
})
const vm = app.mount('#root')
</script>
</html>
checkbox的自定义写法
checkbox 在 选中 以及 取消选中时候, 通过 true-value 、false-value 属性来 动态更改显示的文字 。
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>表单中双向绑定指令的使用</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
const app = Vue.createApp({
data(){
return{
message: 'hello'
}
},
template: `
{{message}}
<input type="checkbox" v-model="message" true-value="hello" false-value="world"/>
`
})
const vm = app.mount('#root')
</script>
</html>
radio的双向数据绑定
radio 储存值只需要用 默认空字符串 储存即可 ,不需要向 checkbox 那样用 数组 ,因为 radio 只能选择一个值 。
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>表单中双向绑定指令的使用</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
const app = Vue.createApp({
data(){
return{
message: ''
}
},
template: `
{{message}}
jack<input type="radio" v-model="message" value="jack"/>
dell<input type="radio" v-model="message" value="dell"/>
lee<input type="radio" v-model="message" value="lee"/>
`
})
const vm = app.mount('#root')
</script>
</html>
select的双向数据绑定
1.单选select
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>表单中双向绑定指令的使用</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
const app = Vue.createApp({
data(){
return{
message: ''
}
},
template: `
{{message}}
<select v-model="message">
<option disable value="">请选择内容</option>
<option value="A">A</option>
<option value="B">B</option>
<option value="C">C</option>
</select>
`
})
const vm = app.mount('#root')
</script>
</html>
2.多选select
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>表单中双向绑定指令的使用</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
const app = Vue.createApp({
data(){
return{
message: [],
// options: [
// {
// text: 'A', value: 'A'
// },
// {
// text: 'B', value: 'B'
// },
// {
// text: 'C', value: 'C'
// }
// ]
// 数据可以写成对象的形式
options: [
{
text: 'A', value: {
value: 'A'
}
},
{
text: 'B', value: {
value: 'B'
}
},
{
text: 'C', value: {
value: 'C'
}
}
]
}
},
template: `
{{message}}
<select v-model="message" multiple>
<option v-for="item in options" :value="item.value">{{item.text}}</option>
</select>
`
})
const vm = app.mount('#root')
</script>
</html>
v-model指令修饰符、form表单标签修饰符
lazy修饰符(懒加载修饰符)
input 框 失去焦点 时候会触发 刷新数据的变化 。
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>表单中双向绑定指令的使用</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
const app = Vue.createApp({
data(){
return{
message: 'hello'
}
},
template: `
{{message}}
<input v-model.lazy="message"/>
`
})
const vm = app.mount('#root')
</script>
</html>
2.number修饰符
number修饰符可以做类型的转换 ,如果默认是 string 类型,输入值后 保存到变量中的值是 number 类型 。
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>表单中双向绑定指令的使用</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
const app = Vue.createApp({
data(){
return{
message: '123'
}
},
template: `
<div>
{{ typeof message}}
<input v-model.number="message" type="number />
</div>
`
})
const vm = app.mount('#root')
</script>
</html>
3.trim修饰符
trim修饰符的意思,就是去掉输入框前后的空格
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>表单中双向绑定指令的使用</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
const app = Vue.createApp({
data(){
return{
message: '123'
}
},
template: `
<div>
{{message}}
<input v-model.trim="message"/>
</div>
`
})
const vm = app.mount('#root')
</script>
</html>
三、探索组件的理念
本章中,将会通过对 组件 概念,以及组件之间代码组织,数据传递的内容讲解,帮助大家完整理解组件的设计理念,让大家能够 合理的拆分管理组件 ,写出易于维护的 Vue 代码。
1.组件的定义及复用性,局部组件和全局组件
上图,左面 是 页面 的 整体结构 ,我们可以把它 拆分成 3 个部分 ,头部 、 左侧部分 、 右侧部分 ,每个部分可以再进行拆分 , 这样我们就会发现一个页面可以进行 层层拆分 ,拆成一个像 右侧 一样的 树状结构 ,这就是 拆分组件的思维 。
组件复用性
组件具备共用性(复用性),每个组件的数据是独享的,而不会跟其他组件共享 , 组件在页面多次引用 ,它们的数据不会共享,每个组件都是独立的
全局组件
全局组件名称 :建议用小写横线连接(例如:counter-parent)
我们可以看到下面的代码中, counter-parent组件 与 counter 组件 是兄弟级组件,那为什么在 counter-parent组件 中又可以引用 counter 组件 呢, 这是因为用 app.component 方法定义的组件都会成为全局组件 ,在哪里都可以使用。
缺点 : 如果定义了组件但是页面中未引用该组件的标签元素,这个组件会占用这个app实例化对象的存储空间,一直挂载在这个app实例化对象上 ,所以说它对性能是有一定的损耗的,你不用它,它也在。
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>组件的定义及复用性,局部组件和全局组件</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
// 父组件
const app = Vue.createApp({
template: `
<div>
<counter-parent />
<counter />
<counter />
<counter />
</div>`
})
// 定义全局组件
app.component('counter-parent', {
template: `<counter />`
})
// 定义全局组件
app.component('counter', {
data(){
return {
count: 1
}
},
template: `<div @click="count += 1">{{count}}</div>`
})
const vm = app.mount('#root')
</script>
</html>
局部组件
局部组件名称 :建议用 大驼峰命名(例如:Name)
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>组件的定义及复用性,局部组件和全局组件</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
// 1. 创建局部组件(局部组件建议首字母大写)
const Counter = {
data(){
return {
count: 1
}
},
template: `<div @click="count += 1">{{count}}</div>`
}
// 父组件
const app = Vue.createApp({
components: { // 2. 引入局部组件
// Counter: Counter // 组件名:组件值
// 简写:
Counter
},
template: `
<div>
<counter />
</div>`
})
const vm = app.mount('#root')
</script>
</html>
2.组件间传值及传值校验
在本节将讲述 组件之间的传值的方式 ,以及传过来的 值得校验 。
组件间传值
1.父传子
父组件 中调用 子组件标签 ,通过在标签上添加 属性 的形式 给子组件传值 ,子组件通过 props 来接收 父组件 传入的值
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title> 组件间传值及传值校验</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
const app = Vue.createApp({
template: `
<div>
<test content="hello world" />
</div>`
})
// 创建全局组件
app.component('test', {
props: ['content'], // 接收父组件传入的值
template: '<div>{{content}}</div>'
})
const vm = app.mount('#root')
</script>
</html>
传值校验
有时候 子组件 props 接收父组件传入值 不是想要的 类型 ,所以在这里讲一下 子组件如何校验父组件传过来的值 。
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title> 组件间传值及传值校验</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
const app = Vue.createApp({
data(){
return { num: 10000 }
},
template: `
<div>
<test :content="num" />
</div>`
})
// 创建全局组件
// type: String, Boolean, Array, Object, Function, Symbol 数据的类型
// required: true / false 必填
// default: 默认值
// validator函数:可以做一些逻辑性的校验,例如:传入的值是否小于1000,返回布尔值结果
app.component('test', {
props: {
content: {
type: Number, // 类型:校验要传入的类型格式Number类型
default: 789, // 默认值:数组、对象时需要是一个函数返回{}或[]
validator: function(value) { // value:父组件传递过来的值
return value < 1000
}
}
},
template: '<div>{{content}}</div>'
})
const vm = app.mount('#root')
</script>
</html>
3.单向数据流的理解
vue中子组件可以使用父组件传递过来的数据,但是绝对不能修改传递过来的数据 。
需求:封装一个 计数器组件 ,根据 父组件传入的数值,在子组件中点击数值后每次自增 + 1 。
上面的需求中, 子组件每次自增 + 1 时,就会 改变父组件的值 ,这样就 违背了vue的单项数据流 , 那么如何解决呢?
解决方案:子组件内用一个变量,复制父组件传入的数值副本 ,通过 修改变量副本 达到想要的效果。
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title> 单向数据流的理解</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
const app = Vue.createApp({
data(){
return {
num: 1
}
},
template: `
<div>
<counter :count="num"/>
</div>`
})
// 创建全局组件
app.component('counter', {
props: ['count'],
data(){
return {
myCount: this.count // 复制一份count
}
},
template: '<div @click="myCount += 1 ">{{myCount}}</div>'
})
const vm = app.mount('#root')
</script>
</html>
为什么 vue是单项数据流 呢,看下面的代码中, 引入了4次子组件 ,并且4个组件中传入的值 都是 num变量,我们思考一下,如果 其中一个子组件修改了num ,那么其他用到num变量的组件也会被同时改变 ,这样就会出现问题,这就是 单项数据流不允许子组件修改父组件传入的数据,避免数据出现高度耦合 。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title> 单向数据流的理解</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
const app = Vue.createApp({
data(){
return {
num: 1
}
},
template: `
<div>
<counter :count="num"/>
<counter :count="num"/>
<counter :count="num"/>
<counter :count="num"/>
</div>`
})
// 创建全局组件
app.component('counter', {
props: ['count'],
data(){
return {
myCount: this.count // 复制一份count
}
},
template: '<div @click="myCount += 1 ">{{myCount}}</div>'
})
const vm = app.mount('#root')
</script>
</html>
注意
- 父组件给子组件传值 ,如果传入的值有很多,可以用 传入对象的形式 。
- 如果 自定义的属性名 很长,可以用 中划线分割单词的形式 ,例如: picker-name ,那样的话,在子组件内 props中接收 就要这样写:pickerName
4.Non-Props 属性是什么
- inheritAttrs :不接收父组件传入的属性
- $attrs: 接收父组件传入的所有属性
- $attrs.传递过来的属性: 按需来显示,要显示的属性
- 在 生命周期 中使用 $attrs
inheritAttrs
如果 父组件传值,子组件不接收的话,它会把父组件传递过来的内容放在子组件最外层的标签上,变成dom标签的一个属性 ,如下代码:
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Non-Props 属性是什么</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
// Non-prop 属性
const app = Vue.createApp({
template: `
<div>
<counter msg="hello"/>
</div>`
})
// 子组件未用props接收msg
app.component('counter', {
template: '<div>Counter</div>'
})
const vm = app.mount('#root')
</script>
</html>
子组件在浏览器上就会被渲染 成这样:
<div id="root" data-v-app="">
<div>
<!-- 这里会多个msg属性这是因为子组件没有接收父组件传过来的属性 -->
<div msg="hello">Counter</div>
</div>
</div>
如果你 不希望渲染时标签上有这样的属性 ,可以 在子组件中添加 inheritAttrs 属性,意思是不继承父组件传过来的props属性 。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Non-Props 属性是什么</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
// Non-prop 属性
const app = Vue.createApp({
template: `
<div>
<counter msg="hello"/>
</div>`
})
// 子组件未用props接收msg
app.component('counter', {
inheritAttrs: false, // 不继承父组件传过来的props属性
template: '<div>Counter</div>'
})
const vm = app.mount('#root')
</script>
</html>
这样写渲染后子组件上就不会有父组件传递过来的属性 了,包括 style,class 也不会被传递过来。
$attrs
父组件传递给子组件的属性(style、class等等) 可以用 指定给子组件的某个元素 ,如下代码:
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Non-Props 属性是什么</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
// Non-prop 属性
const app = Vue.createApp({
data(){
return {
num: 1
}
},
template: `
<div>
<counter style="color:red" class="div-style" msg="hello"/>
</div>`
})
app.component('counter', {
template: `
<div>Counter</div>
<div v-bind="$attrs">Counter</div>
<div>Counter</div>
`
})
const vm = app.mount('#root')
</script>
</html>
渲染后的结果如下:
<div id="root" data-v-app="">
<div>
<div>Counter</div>
因为子组件的v-bind="$attrs"是加到第二个元素身上了,所以渲染时都会添加到该标签身上
<div class="div-style" msg="hello" style="color: red;">Counter</div>
<div>Counter</div>
</div>
</div>
$attrs按需显示
如果你 只想在某个标签身上显示某个传递过来的属性 ,你可以这样写:
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Non-Props 属性是什么</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
// Non-prop 属性
const app = Vue.createApp({
template: `
<div>
<counter style="color:red" class="div-style" msg="hello"/>
</div>`
})
app.component('counter', {
template: `
<div :style="$attrs.style">Counter</div>
<div :class="$attrs.class">Counter</div>
<div :msg="$attrs.msg">Counter</div>
`
})
const vm = app.mount('#root')
</script>
</html>
放到浏览器上渲染的结果就是这样:
<div id="root" data-v-app="">
<div>
<div style="color: red;">Counter</div>
<div class="div-style">Counter</div>
<div msg="hello">Counter</div>
</div>
</div>
在生命周期中使用$attrs
我们除了可以 在子组件内的标签上使用 attrs 代码如下:
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Non-Props 属性是什么</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
// Non-prop 属性
const app = Vue.createApp({
template: `
<div>
<counter style="color:red" class="div-style" msg="hello"/>
</div>`
})
app.component('counter', {
mounted(){
console.log(this.$attrs.style)
},
template: `
<div :style="$attrs.style">Counter</div>
<div :class="$attrs.class">Counter</div>
<div :msg="$attrs.msg">Counter</div>
`
})
const vm = app.mount('#root')
</script>
</html>
5.父子组件间如何通过事件进行通信
需求 :封装一个 计数器组件 ,父给子传一个数字,子组件中每次点击数字时,数字自增 +1 ,这样就涉及到了需要修改父组件传入的值 ,我们之前讲过 vue 是 单向数据流 ,子组件不可以修改父组件传递的数据 ,之前的 解决办法是复制一份副本修改副本 ,那么我们今天讲一下另外一种解决办法, 我们在子组件通过事件告诉父组件来修改这个值 。
1.子传父
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>父子组件间如何通过事件进行通信</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
// 父组件
const app = Vue.createApp({
data(){
return {
count: 1
}
},
methods: {
handleAddOne(param){
this.count += param
}
},
template: `
<div>
<counter :count="count" @add-one="handleAddOne" />
</div>
`
})
// 子组件
app.component('counter', {
props: ['count'],
methods: {
handleClick(){
this.$emit('addOne', 2)
}
},
template: `
<div @click="handleClick">{{count}}</div>
`
})
const vm = app.mount('#root')
</script>
</html>
子组件 $emit('addOne') 时, 事件名可以是 驼峰式命名 ,但是 在标签上使用时应该是中划线方式@add-one="handleAddOne" 。
上面的计算方式是 子组件通过事件来告知父组件,父组件计算后传给子组件 ,也可以在子组件中计算完成后,传入给父组件,父组件再回传回来 ,如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>父子组件间如何通过事件进行通信</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
// 父组件
const app = Vue.createApp({
data(){
return {
count: 1
}
},
methods: {
handleAddOne(param){
this.count = param
}
},
template: `
<div>
<counter :count="count" @add-one="handleAddOne" />
</div>
`
})
// 子组件
app.component('counter', {
props: ['count'],
methods: {
handleClick(){
this.$emit('addOne', this.count + 2)
}
},
template: `
<div @click="handleClick">{{count}}</div>
`
})
const vm = app.mount('#root')
</script>
</html>
emits
如果一个 子组件向外部触发事件 ,可以写一个 emits 的 对象或数组 ,来 存放$emit定义的事件。
emits的作用 : 为了 方便易于维护,写在emits中,一眼就能看到组件中向外传递了哪些事件,很方便, 还可以 校验$emit回传的值的格式
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>父子组件间如何通过事件进行通信</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
// 父组件
const app = Vue.createApp({
data(){
return {
count: 1
}
},
methods: {
handleAddOne(param){
this.count = param
}
},
template: `
<div>
<counter :count="count" @add-one="handleAddOne" />
</div>
`
})
// 子组件
app.component('counter', {
props: ['count'],
// emits: ['addOne'], // 数组形式
emits: { // 对象形式
addOne: (count) => { // count参数:this.$emit('addOne', 对应这个count)
if(count < 0){ // 如果参数大于0,同意事件往外传值
return true
}
// 如果小于0,不允许往外传,并且给予警告
return false
}
}, // 对象形式
methods: {
handleClick(){
this.$emit('addOne', this.count + 2)
}
},
template: `
<div @click="handleClick">{{count}}</div>
`
})
const vm = app.mount('#root')
</script>
</html>
6.组件间双向绑定高级内容
v-model的使用同时也可以参考我的另一篇文章中的 v-model和.sync的用法
- v-model(修改单个值) :v-model绑值,达到子组件可以修改父组件的值的效果
- v-model(修改多个值) :可以绑定多个v-model值,达到修改多个值
v-model(修改单个值)
上一章讲了讲了如果想做到 子组件想修改父组件的值,子组件要通过$emit触发父组件的自定义方法,然后在父组件中修改值 。其实 v-model 同样可以做到 子组件修改父组件传递的数据,写法如下:
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>组件间双向绑定高级内容</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
// 父组件
const app = Vue.createApp({
data(){
return { count: 1 }
},
template: `
<counter v-model="count" />
`
})
// 子组件
app.component('counter', {
props: ['modelValue'],
methods: {
handleClick(){
this.$emit('update:modelValue', this.modelValue + 2)
}
},
template: `
<div @click="handleClick">{{modelValue}}</div>
`
})
const vm = app.mount('#root')
</script>
</html>
上面代码 子组件 中的 modelValue 是 固定写法 ,如果你不想叫做 modelValue ,你可以 自定义名称 ,这样写:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>父子组件间如何通过事件进行通信</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
// 父组件
const app = Vue.createApp({
data(){
return { count: 1 }
},
template: `
<counter v-model:app="count" />
`
})
// 子组件
app.component('counter', {
props: ['app'],
methods: {
handleClick(){
this.$emit('update:app', this.app + 2)
}
},
template: `
<div @click="handleClick">{{app}}</div>
`
})
const vm = app.mount('#root')
</script>
</html>
这样写绑定v-model 的地方必须要这样写: ,拼接成:v-model:组件中props自定义的名称
v-model(修改多个值)
上面的 v-model 例子,看着是简单了很多,但是 只能修改单个属性值 ,那我如果想用 v-model修改多个属性值 该怎么办呢, 代码如下:
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>组件间双向绑定高级内容</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
// 父组件
const app = Vue.createApp({
data(){
return {
count: 1,
count1: 1
}
},
template: `
<counter v-model:count="count" v-model:count1="count1" />
`
})
// 子组件
app.component('counter', {
props: ['count', 'count1'],
methods: {
handleClick(){
this.$emit('update:count', this.count + 2)
},
handleClick1(){
this.$emit('update:count1', this.count1 + 2)
}
},
template: `
<div @click="handleClick">{{count}}</div>
<div @click="handleClick1">{{count1}}</div>
`
})
const vm = app.mount('#root')
</script>
</html>
组件的v-model配合自定义的修饰符使用
在组件的 v-model 上,还可以 自定义修饰符 来使用,只需要 在子组件中判断父组件传入的修饰符是什么,然后执行响应的逻辑来处理v-model绑定的值即可 ,像下面例子中,传入了一个 自定义大写修饰符 ,代码如下:
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>组件间双向绑定高级内容</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
// 父组件
const app = Vue.createApp({
data(){
return {
count: 'a'
}
},
template: `
<counter v-model.uppercase="count" />
`
})
// 子组件
app.component('counter', {
props: {
'modelValue': String,
modelModifiers: { // modelModifiers指传递过来的修饰符
default: () => ({})
}
},
// mounted(){
// // 打印传过来的修饰符
// console.log(this.modelModifiers) // uppercase:true
// },
methods: {
handleClick(){
let newValue = this.modelValue + 'b'
if(this.modelModifiers.uppercase){ // 是否使用了uppercase(全部大写)修饰符
newValue = newValue.toUpperCase()
}
this.$emit('update:modelValue', newValue)
}
},
template: `
<div @click="handleClick">{{modelValue}}</div>
`
})
const vm = app.mount('#root')
</script>
</html>
7.使用匿名插槽和具名插槽解决组件内容传递问题
需求:如图,封装了一个 表单组件 ,但是 按钮不想固定,假如说按钮的位置:我想展示一个div就展示div,想展示button就展示button ,用 slot插槽 就能解决我们的问题。
匿名插槽
匿名插槽 : 就是没有名称的插槽随插随用 ,缺点,只有 使用一个插槽时候可以使用,同时使用多个插槽时候,就不知道插入到哪里了 。
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>使用插槽和具名插槽解决组件内容传递问题</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
// slot 插槽
// slot 中使用的数据,作用域的问题
// 父模板里调用的数据属性,使用的都是父模板里的数据
// 子模板里调用的数据属性,使用的都是子模板里的数据
// 父组件
const app = Vue.createApp({
data(){
return {
text: '提交'
}
},
template: `
<myform>
<div>{{text}}</div>
</myform>
<myform>
<button>{{text}}</button>
</myform>
<myform></myform>
`
})
// 子组件
app.component('myform', {
methods: {
handleClick(){
alert(123)
}
},
template: `
<div>
<input />
<span @click="handleClick">
<slot>默认值占位符</slot>
</span>
</div>
`
})
const vm = app.mount('#root')
</script>
</html>
具名插槽
具名插槽: 明确指定 name ,给对应 name 的 slot 标签中添加对应的内容 。
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>使用插槽和具名插槽解决组件内容传递问题</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
// slot 插槽
// slot 中使用的数据,作用域的问题
// 父模板里调用的数据属性,使用的都是父模板里的数据
// 子模板里调用的数据属性,使用的都是子模板里的数据
// 具名插槽
// 父组件
const app = Vue.createApp({
template: `
<layout>
<template v-slot:header>
<div>header</div>
</template>
<template v-slot:footer>
<div>footer</div>
</template>
</layout>
`
})
// 子组件
app.component('layout', {
template: `
<div>
<slot name="header"></slot>
<div>content</div>
<slot name="footer"></slot>
</div>
`
})
const vm = app.mount('#root')
</script>
</html>
使用 具名插槽 时 , 父组件 中要用 template标签包裹 ,然后写上 v-slot:插槽名称 ,在 template标签包裹 中写要 插入的内容 。
具名插槽语法简写
可以把 template标签 上的 v-slot:header ,简写成 #header 。
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>使用插槽和具名插槽解决组件内容传递问题</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
// slot 插槽
// slot 中使用的数据,作用域的问题
// 父模板里调用的数据属性,使用的都是父模板里的数据
// 子模板里调用的数据属性,使用的都是子模板里的数据
// 具名插槽
// 父组件
const app = Vue.createApp({
template: `
<layout>
<template #header>
<div>header</div>
</template>
<template #footer>
<div>footer</div>
</template>
</layout>
`
})
// 子组件
app.component('layout', {
template: `
<div>
<slot name="header"></slot>
<div>content</div>
<slot name="footer"></slot>
</div>
`
})
const vm = app.mount('#root')
</script>
</html>
注意
- 使用 slot插槽 时,必须要用 双标签包裹
- slot标签 是没有办法直接 绑定事件 的
8.作用域插槽
需求 : 组件内循环 div 标签,这个标签有可能不仅限于 div ,想通过父组件来决定循环的是什么标签 。
解决办法 :只需要在 子组件slot标签 上 定义属性 传给 父组件 , 父组件 只需要在 调用子组件的标签 上写 v-slot="slotProps" 来 接收数据 ,在 插槽内容 中 slotProps.xxx ,就可以取到传递过来的数据了。
作用域插槽应用场景 : 当子组件的渲染内容,要由父组件决定的时候 。
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>使用插槽和具名插槽解决组件内容传递问题</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
// 父组件
const app = Vue.createApp({
template: `
<list v-slot="slotProps">
<div>
{{ slotProps.item }}
</div>
</list>
`
})
// 子组件
app.component('list', {
data(){
return {
list: [1, 2, 3]
}
},
template: `
<div>
<slot v-for="item in list" :item="item" />
</div>
`
})
const vm = app.mount('#root')
</script>
</html>
上面的 v-slot="slotProps" 是一个 对象 实际上这里有 简写形式 ,可以写成对象的 对象解构赋值 ,如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>使用插槽和具名插槽解决组件内容传递问题</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
// 父组件
const app = Vue.createApp({
template: `
<list v-slot="{ item }">
<div>
{{ item }}
</div>
</list>
`
})
// 子组件
app.component('list', {
data(){
return {
list: [1, 2, 3]
}
},
template: `
<div>
<slot v-for="item in list" :item="item" />
</div>
`
})
const vm = app.mount('#root')
</script>
</html>
9.动态组件和异步组件
本章讲解 动态组件 与 异步组件 的使用方法
动态组件
动态组件 : 根据数据的变化,结合 component 这和标签,来随时动态切换组件的显示 。
需求 :有两个组件,一个 input框 ,一个 hello world 文字,想实现效果: input框 显示时, hello world 文字就隐藏, hello world 文字显示时, input框 就隐藏 。
如果用 简单写法 就是像下面这样写:
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>动态组件和异步组件</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
// 父组件
const app = Vue.createApp({
data(){
return {
currentItem: 'input-item'
}
},
methods: {
handleClick(){
if(this.currentItem === 'input-item'){
this.currentItem = 'common-item'
} else {
this.currentItem = 'input-item'
}
}
},
template: `
<input-item v-show="currentItem === 'input-item'" />
<common-item v-show="currentItem === 'common-item'" />
<button @click="handleClick">切换</button>
`
})
// 子组件
app.component('input-item', {
template: `<input />`
})
// 子组件
app.component('common-item', {
template: `<div>hello world</div>`
})
const vm = app.mount('#root')
</script>
</html>
上面的代码看似简单,但是代码量大,下面再看一下用 动态组件 实现的相同效果, 动态组件实际上就是一个 component 标签,通过is属性来决定这个显示的是哪个组件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>动态组件和异步组件</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
// 父组件
const app = Vue.createApp({
data(){
return {
currentItem: 'input-item'
}
},
methods: {
handleClick(){
if(this.currentItem === 'input-item'){
this.currentItem = 'common-item'
} else {
this.currentItem = 'input-item'
}
}
},
template: `
<component :is="currentItem" />
<button @click="handleClick">切换</button>
`
})
// 子组件
app.component('input-item', {
template: `<input />`
})
// 子组件
app.component('common-item', {
template: `<div>hello world</div>`
})
const vm = app.mount('#root')
</script>
</html>
这样就会有一个问题,在 输入框输入内容后, 点击 切换按钮2次 ,又 回到显示输入框的状态下,发现输入框内输入的内容消失了 , 这时候就需要用到keep-alive标签 来解决这个问题, keep-alive 的意思就是 当这个动态组件第一次渲染时,它会把它里面的一些状态,后面变更的一些情况,都记录下来,当你回头再用这个组件时,会从缓存里,把之前的数据拿过来填充上 ,代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>动态组件和异步组件</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
// 父组件
const app = Vue.createApp({
data(){
return {
currentItem: 'input-item'
}
},
methods: {
handleClick(){
if(this.currentItem === 'input-item'){
this.currentItem = 'common-item'
} else {
this.currentItem = 'input-item'
}
}
},
template: `
<keep-alive>
<component :is="currentItem" />
</keep-alive>
<button @click="handleClick">切换</button>
`
})
// 子组件
app.component('input-item', {
template: `<input />`
})
// 子组件
app.component('common-item', {
template: `<div>hello world</div>`
})
const vm = app.mount('#root')
</script>
</html>
动态组件 与 keep-alive 有时候会一起使用,这点注意一下即可。
异步组件
1.同步组件
首先展示一下 同步组件, 同步组件 就是你 调用这个组件时,这个代码就会立即执行 ,这就叫做 同步组件。
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>同步组件</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
// 父组件
const app = Vue.createApp({
template: `
<div>
<common-item />
</div>
`
})
// 子组件
app.component('common-item', {
template: `<div>hello world</div>`
})
const vm = app.mount('#root')
</script>
</html>
2.异步组件
异步组件 : 动态加载 一些组件,这样的好处是,我们可以把一些大型的项目,加一些小的 js文件,在需要用到这些小 js文件 的时候,通过 异步组件 来引入这些 js文件 来使用这些 js文件 。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>同步组件</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
// 父组件
const app = Vue.createApp({
template: `
<div>
<common-item />
<async-common-item />
</div>
`
})
// 子组件
app.component('common-item', {
template: `<div>hello world</div>`
})
// 异步子组件
app.component('async-common-item', Vue.defineAsyncComponent(() => {
return new Promise((reslve, reject) => {
setInterval(() => {
reslve({
template: `<div>这是异步组件</div>`
})
}, 4000)
})
}))
const vm = app.mount('#root')
</script>
</html>
10.基础语法知识点查缺补漏
- ref :获取 dom , 调用组件内部方法
- provide / inject : 多层组件传值
ref
ref 可以获取 dom 元素 ,获取 dom元素尽量在 mounted 中获取,想获取那个 dom 就在哪个 dom 上添加一个 ref 属性然后 定义一个名称 ,在使用时候直接 this.$refs.定义的属性 即可使用,如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ref</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
// 父组件
const app = Vue.createApp({
data(){
return {
count: 1
}
},
mounted(){
console.log(this.$refs.count)
},
template: `
<div>
<div ref="count">
{{count}}
</div>
</div>
`
})
const vm = app.mount('#root')
</script>
</html>
ref 不仅可以获取 dom 元素,还可以用来 调用组件内部方法 ,如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ref</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
// 父组件
const app = Vue.createApp({
data(){
return {
count: 1
}
},
mounted(){
this.$refs.common.sayHello()
},
template: `
<div>
<common-item ref="common" />
</div>
`
})
app.component('common-item', {
methods: {
sayHello(){
alert('hello')
},
},
template: `<div>hello world</div>`
})
const vm = app.mount('#root')
</script>
</html>
provide / inject
如果有这样一个结构, 父亲组件 > 儿子组件 > 孙子组件 , 父组件调用子组件,子组件调用孙子组件 ,如果 父组件 想给 孙子组件传一个值就需要一层一层的传递这个参数,也就是 跨层传参 ,我们通常会这样写:
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>provide / inject</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
// 父亲组件
const app = Vue.createApp({
data(){
return { count: 1 }
},
template: `
<div>
<child :count="count" />
</div>
`
})
// 儿子组件
app.component('child', {
props: ['count'],
template: `<child-child :count="count" />`
})
// 孙子组件
app.component('child-child', {
props: ['count'],
template: `<div>{{count}}</div>`
})
const vm = app.mount('#root')
</script>
</html>
这种需要 层层的传递 ,那假如还有 爷爷组件 ,甚至 太爷爷组件 ,难道要 跨域 4层 / 5层的组件 传递来传递这个值吗,这时候我们可以使用 vue 提供的 provide / inject
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>provide / inject</title>
<!-- 通过cdn方式引入vue -->
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="root"></div>
</body>
<script>
// 父亲组件
const app = Vue.createApp({
data(){
return {
count: 3
}
},
// provide: { // 1. 向下传递值(给孙子传值)
// count: 2
// },
// 如果provide的值想传data中的变量,必须用函数的方式,像下面这样
provide(){ // 1. 向下传递值(给孙子传值)
return {
count: this.count
}
},
template: `
<div>
<child />
<button @click="count += 1">Add</button>
</div>
`
})
// 儿子组件
app.component('child', {
template: `<child-child />`
})
// 孙子组件
app.component('child-child', {
inject: ['count'], // 2. 接收上层组件传过来的值(接收爷爷传过来的值)
template: `<div>{{count}}</div>`
})
const vm = app.mount('#root')
</script>
</html>
其实 provide / inject 目前还有一个问题,他的 数据是一次性传递过去的,数据后续发生变化,孙子组件的数据不会更新 ,那么在后续讲解 vue3.0 中,我们会讲解如何解决这个问题。