学习一个框架,先去使用其中的知识,如果其中有不会的东西在去补充一些理论,然后再通过一些实践去巩固之前的实践和理论经验,这样最终才能真正掌握一门框架的知识。
- 先使用Vue(其中可能会有很多不同的地方)
- 再去补充其中的理论知识
- 最后再用一个实战项目来巩固所学知识
初识Vue
我的第一个Vue案例
不用安装Vue,直接使用Vue提供的cdn,https://unpkg.com/vue@next
就可以直接编写vue代码了。
注意:如果cdn不够快,这可以使用这个(我在网上找的)
<script src="https://unpkg.com/vue@next"></script>
<script src="https://unpkg.com/vue-router@next"></script>
<script src="https://unpkg.com/vuex@next"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/vant@next/lib/index.css"/>
<script src="https://cdn.jsdelivr.net/npm/vant@next/lib/vant.min.js"></script>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<script src="https://unpkg.com/vue@next"></script>
<title>Hello Vue</title>
</head>
<body>
<div id="root"></div>
</body>
<script type="text/javascript">
Vue.createApp({
data() {
return {
content: 1
}
},
mounted() {
setInterval(() => {
this.$data.content += 1;
}, 1000)
},
template: '<div>hello world, {{content}}</div>'
}).mount('#root');
</script>
</html>
- template:需要渲染的内容
- data()函数:返回需要的数据
- mounted()函数:当页面加载完成后会自动执行的函数
- .mount(‘#root’):需要将渲染的渲染的内容挂载到哪一个节点后面
- {{}}:括号中的内容可以进行数据解析,这叫做差值表达式
初试v-on点击绑定事件
- 功能:点击按钮可以反转hello world字符串
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<script src="https://unpkg.com/vue@next"></script>
<title>Hello Vue</title>
</head>
<body>
<div id="root"></div>
</body>
<script type="text/javascript">
Vue.createApp({
data() {
return {
content: 'hello world',
}
},
methods: {
handleBtnClick() {
this.content = this.content.split('').reverse().join('');
}
},
template: `
<div>
{{content}}
<button v-on:click="handleBtnClick">反转</button>
</div>
`
}).mount('#root');
</script>
</html>
初试v-if
- 功能:点击按钮可以显示或隐藏hello world字符串
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<script src="https://unpkg.com/vue@next"></script>
<title>Hello Vue</title>
</head>
<body>
<div id="root"></div>
</body>
<script type="text/javascript">
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');
</script>
</html>
v-for循环,v-model双向绑定和v-bind初试
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<script src="https://unpkg.com/vue@next"></script>
<title>Hello Vue</title>
</head>
<body>
<div id="root"></div>
</body>
<script type="text/javascript">
Vue.createApp({
data() {
return {
list: [],
inputValue: '',
}
},
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>
<li v-for="(item, index) of list">{{index}} : {{item}}</li>
</ul>
</div>
`
}).mount('#root');
</script>
</html>
- v-model=v-bind+v-on
在表单上使用双向绑定,其实就是将inputValue
和变淡的value属性值进行绑定(v-bind的作用),并且在表单提交的时候,inputValue属性值也会被提交(v-on的作用)。
- v-bind和
{{}}
的区别
要想在将标签中将内容进行数据的绑定替换的话,可可以直接使用{{}}
差值表达式即可。但是如果一个标签的属性要想要和一个数值绑定的话,就需要使用v-bind
v-bind的简写是:
,所以加上了:之后,属性之后的""内就不是单纯的字符串了,而是可以是表达式。
组件的概念初探
其实前端中每一个模块都是一个组件,那么在vue中我们可以将一个自定义一个模块成为一个组件,以供之后的使用。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<script src="https://unpkg.com/vue@next"></script>
<title>Hello Vue</title>
</head>
<body>
<div id="root"></div>
</body>
<script type="text/javascript">
const app = Vue.createApp({
data() {
return {
list: [],
inputValue: '',
}
},
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', {
// props就是在接收外部到当前组件中的属性,组件中可以获得这些组件的属性值
// 本质上其实就是通过组件的标签,来获得外部对组件的传递的数据
props: ['content', 'index'],
template: `
<li>{{index}} -- {{content}}</li>
`
});
app.mount('#root');
</script>
</html>
如果看不同也不要紧,这只是试用一下而已。
在做项目的时候,面向对象的语言会将项目中不同的部分拆分成不同的部分,以此可以将项目模块化,从而使得整个项目框架很清晰。
而vue中组件也是一样,我们将原来的li
标签,写成了一个我们需要的组件起一个名字叫做todo-item
,然后定义这个组件可以干什么。其中template
就是这个组件需要渲染出的内容。
有的人觉得你在组件中又定义了一个li
标签,这不和原来一样嘛,但是要注意的是,我们可以将组件中的内容定义的很复杂,这可以li
标签不能替代的。所以这就是组件的好处。标签组件可以通过props
来接收外部发来的数据,这样我们就可以将组件从原来的项目中抽出来,可以将其功能定义的很复杂,同时和之前的项目也有数据的关联。
Vue基础语法
本章来讲解一下vue的基础语法
Vue应用和组件的基础概念
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<script src="https://unpkg.com/vue@next"></script>
<title>Hello Vue</title>
</head>
<body>
<div id="root"></div>
</body>
<script type="text/javascript">
const app = Vue.createApp({
data() {
return {
message: 'hello world',
}
},
template: "<div>{{message}}</div>"
});
const vm = app.mount('#root');
console.log(vm.$data.message);
</script>
</html>
- 在vue中,可以使用
Vue.createApp()
的方式来创建一个应用,并且可以存放在一个变量中。 - 其中的
date()
和template
都是为了设置该组件在外部前端展示的内容。 - 通过
mount()
可以将该组件挂载到一个dom元素下,并返回值是一个这个应用的根组件 - vue采用的是mvvm设计模式,即m->model(数据),v->view(视图),vm->(viewModel)视图数据连接层。vue的组件通过data传递数据,在template中定义展示的内容,而将两者连接起来的就是应用的根组件也是应用的视图数据连接层。
- 根组件可以通过
$data
的来获得组件的数据。
Vue中声明周期函数
8个声明周期函数:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<script src="https://unpkg.com/vue@next"></script>
<title>Hello Vue</title>
</head>
<body>
<div id="root">
<div>{{message}}!</div>
</div>
</body>
<script type="text/javascript">
// 生命周期函数:在某一个时刻会自动执行的函数
const app = Vue.createApp({
data() {
return {
message: 'hello world',
}
},
// 在应用示例生成之前执行的函数
beforeCreate() {
console.log("beforeCreate");
},
// 在应用示例生成之后执行的函数
created() {
console.log("created");
},
// 在template内容被渲染成render函数之后,在组件被渲染到页面之前执行的函数
beforeMount() {
console.log(document.getElementById('root').innerHTML, "beforeMount");
},
// 在组件被渲染到页面之后执行的函数
mounted() {
console.log(document.getElementById('root').innerHTML, "mounted");
},
// 当数据发生变化,立即执行的函数
beforeUpdate() {
console.log(document.getElementById('root').innerHTML, "beforeUpdate");
},
// 当数据发生变化,页面重新渲染之后立即执行的函数
updated() {
console.log(document.getElementById('root').innerHTML, "updated");
},
// 在Vue应用销毁时,立即执行的函数
beforeUnmount() {
console.log(document.getElementById('root').innerHTML, "beforeUnmount");
},
// 在Vue应用销毁时,并且dom完全销毁之后,立即执行的函数
unmount() {
console.log(document.getElementById('root').innerHTML, "unmount");
},
// template: "<div>{{message}}</div>"
});
const vm = app.mount('#root');
console.log(vm.$data.message);
</script>
</html>
- 生命周期函数:在某一个时刻会自动执行的函数
beforeCreate
:在app示例生成之前立即执行的函数created
:在app示例生成之后立即执行的函数beforeMount
:在组件被转换成render函数之前,组件被渲染到页面之前执行的函数mounted
:在组件被渲染到页面之后立即执行的函数beforeUpdate
:当数据发生变化,立即执行的函数updated
:当数据发生变化,页面重新渲染之后,立即执行的函数beforeUnmount
:当app被销毁时,立即执行的函数unmount
:当app被销毁时,并且dom元素被完全销毁之后立即执行的函数
注意:如果没有template的话,会默认渲染挂载元素的innerHTML,所以template中的内容也可以直接写到挂载元素的下面。
关键提炼:
- ![[Pasted image 20220428220658.png]]
- 生命周期函数也叫作钩子函数
常用模板语法
{{}}
:差值表达式,括号中可以解析data
中的变量。也可以解析js的表达式(注意是表达式而不是语句)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<script src="https://unpkg.com/vue@next"></script>
<title>Hello Vue</title>
</head>
<body>
<div id="root"></div>
</body>
<script type="text/javascript">
const app = Vue.createApp({
data() {
return {
message: "hello world",
}
},
template: "<div>{{message + ', I am student'}}</div>"
});
app.mount('#root');
</script>
</html>
- v-html:可以将一个节点直接以html的方式挂载到一个节点之下
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<script src="https://unpkg.com/vue@next"></script>
<title>Hello Vue</title>
</head>
<body>
<div id="root"></div>
</body>
<script type="text/javascript">
const app = Vue.createApp({
data() {
return {
message: "<strong>hello world</strong>",
}
},
template: `<div v-html='message'></div>`
});
app.mount('#root');
</script>
</html>
- v-bind:将一个标签的属性和数值进行绑定。(注意如果是标签的内容进行绑定的话,可以使用{{}})
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<script src="https://unpkg.com/vue@next"></script>
<title>Hello Vue</title>
</head>
<body>
<div id="root"></div>
</body>
<script type="text/javascript">
const app = Vue.createApp({
data() {
return {
disable: true,
}
},
template: `<input v-bind:disabled="disable" />`
});
app.mount('#root');
</script>
</html>
- v-if:该标签会进行v-if中的判断
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<script src="https://unpkg.com/vue@next"></script>
<title>Hello Vue</title>
</head>
<body>
<div id="root"></div>
</body>
<script type="text/javascript">
const app = Vue.createApp({
data() {
return {
message: "hello world",
show: false,
}
},
template: `<div v-if="show">{{message}}</div>`
});
app.mount('#root');
</script>
</html>
- v-once:保证该组件只会被渲染一次,避免了无用的渲染来消耗性能
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<script src="https://unpkg.com/vue@next"></script>
<title>Hello Vue</title>
</head>
<body>
<div id="root"></div>
</body>
<script type="text/javascript">
const app = Vue.createApp({
data() {
return {
message: "hello world",
}
},
template: `<div v-once>{{message}}</div>`
});
const vm = app.mount('#root');
</script>
</html>
- v-on:绑定事件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<script src="https://unpkg.com/vue@next"></script>
<title>Hello Vue</title>
</head>
<body>
<div id="root"></div>
</body>
<script type="text/javascript">
const app = Vue.createApp({
data() {
return {
message: "hello world",
}
},
methods: {
handleClick() {
alert("finish click");
}
},
template: `
<div v-on:click="handleClick"
v-bind:title="message">
{{message}}
</div>
`
});
const vm = app.mount('#root');
</script>
</html>
注意:
- 其中v-on和v-bind有简写形式,分别用@和:来代替。
- 标签的属性也是可以是动态变化的,即标签的动态属性,可以使用
[]
来绑定标签的属性
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<script src="https://unpkg.com/vue@next"></script>
<title>Hello Vue</title>
</head>
<body>
<div id="root"></div>
</body>
<script type="text/javascript">
const app = Vue.createApp({
data() {
return {
message: "hello world",
event: "click",
name: "title"
}
},
methods: {
handleClick() {
alert("finish click");
}
},
template: `
<div @[event]="handleClick"
:[name]="message">
{{message}}
</div>
`
});
const vm = app.mount('#root');
</script>
</html>
- 事件修饰符
如果想要将标签的默认行为取消掉的话,可以这样写:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<script src="https://unpkg.com/vue@next"></script>
<title>Hello Vue</title>
</head>
<body>
<div id="root"></div>
</body>
<script type="text/javascript">
const app = Vue.createApp({
methods: {
handleClick(e) {
e.preventDefault();
}
},
template: `
<a href="http://www.baidu.com" @click="handleClick">百度一下</a>
`
});
const vm = app.mount('#root');
</script>
</html>
模板语法中可以使用事件的修饰符来对事件进行修饰,通过使用prevent
来修饰的话,也可以取消一个标签的默认行为。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<script src="https://unpkg.com/vue@next"></script>
<title>Hello Vue</title>
</head>
<body>
<div id="root"></div>
</body>
<script type="text/javascript">
const app = Vue.createApp({
template: `
<a href="http://www.baidu.com" @click.prevent>百度一下</a>
`
});
const vm = app.mount('#root');
</script>
</html>
数据,方法,计算属性和侦听器
data,methods,computed,watcher
- data:app中的this指的是app这个实例,可以根据这个this来获取data中的数据
<script type="text/javascript">
const app = Vue.createApp({
data() {
return {
message: "hello world",
}
},
methods: {
handleClick() {
console.log(this); // 打印app实例
console.log(this.$data.message); // hello world
console.log(this.message); // hello world
}
},
template: `
<div @click="handleClick">{{message}}</div>
`
});
const vm = app.mount('#root');
</script>
- methods:方法可以和事件进行绑定,也可以在差值表达式{{}}中使用
<script type="text/javascript">
const app = Vue.createApp({
data() {
return {
message: "hello world",
}
},
methods: {
handleString(str) {
return str.toUpperCase();
}
},
template: `
<div>{{handleString(message)}}</div>
`
});
const vm = app.mount('#root');
</script>
- computed:在computed中可以进行数据的计算。
进行计算的三种方式:
<script type="text/javascript">
const app = Vue.createApp({
data() {
return {
price: 5,
count: 2
}
},
computed: {
total() {
return this.price * this.count;
}
},
methods: {
getTotal() {
return this.price * this.count;
}
},
template: `
<div>{{ parseInt(price) * parseInt(count) }}</div>
<div>{{total}}</div>
<div>{{getTotal()}}</div>
`
});
const vm = app.mount('#root');
</script>
computed和methods中的计算有什么区别?
-
只有当computed中所依赖的数据变化的时候,computed才会重新执行一次。而当页面重新渲染的时候,methods中的方法就会重新执行一次。
-
computed中采用了缓存的机制,所以使得性能更加高效。
-
watch:对一个数据进行监听,当这个数据发生变化的时候,就会被触发。
<script type="text/javascript">
const app = Vue.createApp({
data() {
return {
num : 10
}
},
watch: {
// 当num发生变化后,1秒后就会打印出修改过的新值和之前的旧值
num(cur, prev) {
setTimeout(() => {
console.log(cur, prev);
}, 1000);
}
},
template: "<div>{{num}}</div>"
});
const vm = app.mount('#root');
</script>
computed底层采用的就是watch,所以当计算数据结果来进行同步操作的话,使用封装过后的computed更简单。但是如果想要进行一些异步操作的话,就需要使用到watch。
总结:
- computed和method都能实现的功能,建议使用computed,因为它有缓存性能更高
- computed和watch都能实现的功能,建议使用computed,因为它更简洁
样式绑定语法
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<script src="https://unpkg.com/vue@next"></script>
<title>Hello Vue</title>
</head>
<style>
.red {
color: red;
}
.green {
color: green;
}
.font {
font-size: 30px;
}
</style>
<body>
<div id="root"></div>
</body>
<script type="text/javascript">
const app = Vue.createApp({
data() {
return {
// 使用字符串的方式选择class
classString: "red",
// 使用数组的形式选择class
classArray: ["red", "font", {green: false}],
// 使用键值对的方式选择class
classObject: {
red: true,
font: true,
green: false
},
// 可以使用字符串的方式来定义style样式
styleString:"color: blue; font-size: 30px",
// 也可以写成键值对的形式,效果同上但是更方便查看
styleObject: {
color: "orange",
fontSize: "30px",
}
}
},
template: `
<div :class="classString">Hello World</div>
<div :class="classArray">Hello World</div>
<div :class="classObject">Hello World</div>
<div :style="styleString">Hello World</div>
<div :style="styleObject">Hello World</div>
<demo :class="classObject" :style="styleObject"/>
`
});
app.component("demo", {
template:`
<div :class="$attrs.class">demo1</div>
<div :style="$attrs.style">demo2</div>
`
});
const vm = app.mount('#root');
</script>
</html>
补充:
- 在标签中绑定属性值的话,采用键值对的方式更加数据驱动,从而扩展性更强一些。
- 如果子组件(被调用的一方组件叫做子组件)中的标签超过一个的时候,父组件的class和style属性就需要自己手动继承。如果只有一个的话,可以直接继承父组件的属性。
总结:
- 如果想给一个组件加样式的话,可以使用class或者style。两者都可以在data中定义字符串,也可以定义字典。而class还可以使用数组。并且style定义的字典中写的是添加的样式,而class定义的字典是选择已经写好的class。
条件渲染
v-if和v-show的区别以及v-else-if,v-else
v-if和v-show的条件为true的时候,两者都会将组件显示出来。但是当条件为false的时候,v-if会直接将dom标签移除掉。而v-show会设置dom标签的属性display:none
从而隐藏标签而不是销毁标签。因此在需要频繁显示隐藏标签时,使用v-show可以避免频繁销毁重建dom标签,所以性能会更高一点。
v-if,v-else-if,v-else可以直接像普通的if,elseif,else一样使用,只不过标签之间需要紧挨着,否则会报错。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<script src="https://unpkg.com/vue@next"></script>
<title>Hello Vue</title>
</head>
<body>
<div id="root"></div>
</body>
<script type="text/javascript">
const app = Vue.createApp({
data() {
return {
show: false,
condtionOne: false,
condtionTwo: true
}
},
template: `
<div v-if="show">Hello World</div>
<div v-show="show">Hello World</div>
<div v-if="condtionOne">if</div>
<div v-else-if="condtionTwo">elseif</div>
<div v-else>else</div>
`
});
const vm = app.mount('#root');
</script>
</html>
列表循环渲染
v-for循环数组和对象
<script type="text/javascript">
const app = Vue.createApp({
data() {
return {
listArray: ['zhy', 'man', 18],
listObject: {
name: "zhy",
gender: "man",
age: 18
}
}
},
template: `
<div>
<div v-for="(item, index) in listArray" :key="index">{{index}} -> {{item}}</div>
<div v-for="(value, key, index) in listObject" :key="index">{{index}} -> {{key}} -> {{value}}</div>
</div>
`
});
const vm = app.mount('#root');
</script>
注意:页面中常常会存在一些当页面刷新过后依然不会改变的元素,所以vue为了提高性能会将这些元素不重新刷新,但是需要给这些元素添加上一个可以唯一标识的key。在标签中显式的内容需要使用{{}}来绑定数据,但是标签的属性值中可以直接使用数据。
配合v-for在列表中动态修改数组和对象内容
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<script src="https://unpkg.com/vue@next"></script>
<title>Hello Vue</title>
</head>
<body>
<div id="root"></div>
</body>
<script type="text/javascript">
const app = Vue.createApp({
data() {
return {
listArray: ['zhy', 'male', 18],
listObject: {
name: "zhy",
gender: "male",
age: 18
}
}
},
methods: {
// 动态修改数组的方法
// 1.通过函数来修改数组
handleAddBtnClick() {
this.listArray.push('hello');
},
handlePopBtnClick() {
this.listArray.pop();
},
handleShiftBtnClick() {
this.listArray.shift();
},
handleUnshiftBtnClick() {
this.listArray.unshift('hello');
},
handleReverseBtnClick() {
this.listArray.reverse();
},
// 2.直接替换数组
handleReplace() {
this.listArray = ['孔子', '孟子', '老子'];
},
// 3. 更新数组内容
handleUpdate() {
this.listArray[0] = '孔子';
},
// 在对象中条件元素
handleAddObject() {
this.listObject.school = 'nlg',
this.listObject.hobby = 'programming'
}
},
template: `
<div>
<div v-for="(item, index) in listArray" :key="index">{{index}} -> {{item}}</div>
<button @click="handleAddBtnClick">尾插</button>
<button @click="handlePopBtnClick">尾删</button>
<button @click="handleShiftBtnClick">头删</button>
<button @click="handleUnshiftBtnClick">头插</button>
<button @click="handleReverseBtnClick">反转</button>
<br/>
<button @click="handleReplace">替换</button>
<button @click="handleUpdate">更新</button>
<br/>
<div v-for="(value, key, index) in listObject" :key="index">{{index}} -> {{key}} -> {{value}}</div>
<button @click="handleAddObject">添加</button>
</div>
`
});
const vm = app.mount('#root');
</script>
</html>
注意:
- 如果想要在v-for中使用v-if进行判断的话,不能在同一个标签中同时进行循环和判断,因为循环的优先级比判断要高。所以v-if需要在当前标签中再嵌套一层标签。
- 因为嵌套一层标签会使得我们不想要外层的标签,所以此时外层的标签可以使用template占位符记进行替换,template只是一个占位符,用于对标签进行操作,而不占是实际的标签。
<script type="text/javascript">
const app = Vue.createApp({
data() {
return {
listArray: ['zhy', 'male', 18],
}
},
template: `
<div>
<div
v-for="(item, index) in listArray"
:key="index"
>
<div v-if="item !== 18">
{{index}} -> {{item}}
</div>
</div>
</div>
<hr/>
<div>
<template
v-for="(item, index) in listArray"
:key="index"
>
<div v-if="item !== 18">
{{index}} -> {{item}}
</div>
</template>
</div>
`
});
const vm = app.mount('#root');
</script>
![[Pasted image 20220430110102.png]]
事件绑定
事件绑定的基础语法
- 一般在绑定一个函数的时候,只需要写函数名即可。但是如果想要同时绑定多个函数的话,就需要写上
()
,即function_name()
这样的形式。 - 每一个事件都有一个事件对象,如果没有函数没有参数的话,默认函数的第一个参数就是该事件对象。但是如果函数有参数,那么就需要在调用函数的地方传递一个参数$event才可以在函数使用该对象。
<script type="text/javascript">
const app = Vue.createApp({
methods: {
handleBtnClick(str, event) {
alert(str);
console.log(event);
},
handleBtnClick1() {
alert("hello world!!!");
},
},
template: `
<button @click="handleBtnClick('hello world', $event), handleBtnClick1()">点击</button>
`
});
const vm = app.mount('#root');
</script>
事件绑定中的修饰符
事件在绑定后可以对事件进行修饰,即添加一些在执行事件是会执行的默认操作。
事件有两种修饰符:事件修饰符,按键修饰符,鼠标修饰符和精确修饰符
事件修饰符.stop .prevent .self .capture .once .passive等等
- .stop:停止事件的冒泡传递
- .capture:停止事件的向下传递
- .self:只有自身可以触发事件
- .once:事件只能触发一次
<script type="text/javascript">
const app = Vue.createApp({
methods: {
handleBtnClick() {
alert("button click");
},
handleDivClick() {
alert("div click");
},
},
template: `
<div>
<div @click.once="handleDivClick">
<span>Hello World!!!</span>
<button @click.stop="handleBtnClick">点击</button>
</div>
</div>
`
});
const vm = app.mount('#root');
</script>
按键修饰符:
- enter
- tab
- esc
- delete
- up
- down
- left
- right
鼠标修饰符:
- left:左键
- right:右键
- middle:滚轮
精确修饰符:必须当下点击指定按键或者鼠标才可以触发事件
- exact:精确点击
<script type="text/javascript">
const app = Vue.createApp({
methods: {
handleDivClick() {
console.log("div click");
},
handleInput() {
console.log("keydown");
},
},
template: `
<div>
<div @click.right="handleDivClick">Hello World!!!</div>
<div @click.ctrl.exact="handleDivClick">Hello World!!!</div>
<input @keydown.delete="handleInput"/>
</div>
`
});
const vm = app.mount('#root');
</script>
表单中的双向绑定指令
双向绑定的意思就是将表单中的元素和data中的数据绑定,当表单中的元素进行变化的时候,data也相应变化,当data中的数据变化的时候,表单中的元素也相应变化。
<script type="text/javascript">
const app = Vue.createApp({
data() {
return {
message: 'hello world',
listArray: [],
checkStr: '',
}
},
methods: {
},
template: `
<div>
<span>{{message}}</span>
<input v-model="message"/>
<textarea v-model="message"/>
</div>
<div>
{{listArray}}
hello <input type="checkbox" v-model="listArray" value="hello"/>
world <input type="checkbox" v-model="listArray" value="world"/>
!!! <input type="checkbox" v-model="listArray" value="!!!"/>
<br/>
{{checkStr}}
男<input type="radio" v-model="checkStr" value="男"/>
女<input type="radio" v-model="checkStr" value="女"/>
</div>
`
});
const vm = app.mount('#root');
</script>
注意:
-
checkbox和radio绑定的值只能为true或者false。绑定的值为数组的话,那么数组中就可以拿到勾选的条目的value值。
-
select中的双向绑定
<script type="text/javascript">
const app = Vue.createApp({
data() {
return {
listArray: [],
options: [ {
text: 'A',
value: {value:'A'}
}, {
text: 'B',
value: {value:'B'}
}, {
text: 'C',
value: {value:'C'}
}],
}
},
template: `
<div>
{{listArray}}
<select v-model="listArray">
<option v-for="item in options" :value="item.value">{{item.text}}</option>
</select>
</div>
`
});
const vm = app.mount('#root');
</script>
true-value
false-value
.lazy
.number:类型转化
.trim:去除前后空格
v-model的高级用法和修饰符
在v-model中可以使用true-value
和false-value
来定义值去确定当前是true/false
修饰符:
- lazy:双向绑定的数据不会立即变化,只有当表单失去焦点才会变化
- number:将绑定的数据类型转化为number
- trim:去除掉绑定数据的前后的多余空格
<script type="text/javascript">
const app = Vue.createApp({
data() {
return {
value: "world",
message: "",
num: '',
}
},
template: `
<div>
{{value}}
<input type="checkbox" v-model="value" true-value="hello" false-value="world"/>
<br/>
{{message}}
<input v-model.lazy="message"/>
<input v-model.trim="message"/>
<br/>
{{typeof num}}
<input v-model.number="num" type="number"/>
</div>
`
});
const vm = app.mount('#root');
</script>
组件的理念
组件的定义以及复用性,局部组件和全局组件
组件具有复用性,独立性、
app.component定义的全局组件
- 组件的意义就是让整个页面可以分模块独立去开发,并且每一个组件都可以复用。所以组件具有独立性和复用性
- 组件分为全局组件和局部组件,全局组件可以使用
app.component
挂载到app实例下。局部组件是直接通过定义变量的方式来定义。- 全局组件就算不使用也会挂载到对应的app下,处处都可以使用,所以使用简单,单对性能消耗比较大。一般全局组件名字使用小写字符开头,以
-
作为分隔符。 - 局部组件在不使用的时候,只是一个变量,不会消耗多余的性能。当局部组件在app的
component
中注册后才能使用。一般局部组件的名字是以大驼峰的方式命名。局部组件在注册的时候,要做一个名字和组件名之间的映射。如果不写映射的话,vue也会自动将大驼峰转换成小写字母和-
的组合,所以组件的命名要规范,
- 全局组件就算不使用也会挂载到对应的app下,处处都可以使用,所以使用简单,单对性能消耗比较大。一般全局组件名字使用小写字符开头,以
<script type="text/javascript">
const HelloWorld = {
template: `
<div>Hello World!!!</div>
`
}
const app = Vue.createApp({
components: {
HelloWorld,
// 相当于'hello-world':HelloWorld
},
template: `
<count-down />
<hello-world />
`
});
app.component('count-down',{
data() {
return {
count: 60
}
},
methods: {
handleCount() {
console.log('hello world');
setInterval(() => {
this.count -= 1;
}, 100);
}
},
template:`
<div @click="handleCount">{{count}}</div>
`
});
const vm = app.mount('#root');
</script>
组件间传值及传值校验
光光定义组件使得组件具有了很强的独立性,但是往往子组件要和父组件进行数据的传递,只需要在组件中定义一个属性,并在组件内部通过props
来接收即可。
传递数据分为两种:静态传参和动态传参。
- 静态传参:直接将参数传递给组件,这样的方式往往只能传递一个字符串。
- 动态传参:在父组件中的data()中定义参数,通过v-bind绑定组件的属性,这样就可以传递不同类型的参数了。
<script type="text/javascript">
const app = Vue.createApp({
data() {
return {
num: 123,
}
},
template: `
<test content="123"/>
<test :content="num"/>
`
});
app.component('test', {
props: ['content'],
template:`<div>{{ content }}</div>`
});
const vm = app.mount('#root');
</script>
一般通过props
来接收参数,如果只是接收参数的话,可以使用数组来接收。但是同时想要对传递的参数进行校验,那么就可以使用字典来接收,然后再字典内部进行校验。
不同类型的校验:
- type:String,Boolean,Number,Array,Object,Function,Symbol
- required:必填
- default:默认值
- validator:校验
<script type="text/javascript">
const app = Vue.createApp({
data() {
return {
num: 123,
}
},
template: `
<test :content="num"/>
`
});
app.component('test', {
props: {
content: {
// 校验类型
type: Number,
// 是否一定要传递参数
required: true,
// 默认值
// 也可以写成 default: 456,
default: () => {
return 456;
},
// 数据有效性校验
validator: (num) => {
// 返回一个boolean
return num < 1000;
}
}
},
template:`<div>{{ content }}</div>`
});
const vm = app.mount('#root');
</script>
单向数据流的理解
当父组件需要传递很多参数到子组件中的时候,可以不同v-bind:属性名="数值型"
的方式动态绑定,可以直接将多个参数放在一个对象中,然后使用v-bind="对象"
来绑定一个对象,子组件中会将字典中的键值对一个一个自动展开。
<script type="text/javascript">
const app = Vue.createApp({
data() {
return {
a: 123,
b: 456,
c: 789,
params: {
a: 123,
b: 456,
c: 789,
}
}
},
template: `
<test :a="a" :b="b" :c="c"/>
<test v-bind="params" />
`
});
app.component('test', {
props: ['a', 'b', 'c'],
template:`<div>{{a}} {{b}} {{c}}</div>`
});
const vm = app.mount('#root');
</script>
注意:
- 属性值有一个命名规范:传递的属性名需要以小写字母加
-
的方式,但是在子组件中接收的时候要用大驼峰的方式接收。 - 这个要和定义和接收局部组件名区分,局部组件名需要以大驼峰的形式,但是在
component
中接收组件的时候,要用小写加-
的方式接收。 - 总结:在根组件中,无论是接收组件,还是传递属性值命名都是小写加
-
的方式。在子组件中无论是接收属性值还是定义组件名都要以大驼峰命名。
<script type="text/javascript">
const app = Vue.createApp({
data() {
return {
content: "hello world",
}
},
template: `
<test :content-hello="content"/>
`
});
app.component('test', {
props: ['contentHello'],
template:`<div>{{contentHello}}</div>`
});
const vm = app.mount('#root');
</script>
- 单向数据流:父组件可以给子组件传递数据,但是数据本身是只读的,不能修改。
- 解释:父组件动态传递的数据可以同时给多个子组件使用。而每一次子组件应该是相互独立的,如果所有的子组件可以共用父组件传递的同一个数据的话,那么子组件会耦合起来了。因此父组件传递的数据是只读的,在子组件中不能修改它的值。
<script type="text/javascript">
const app = Vue.createApp({
data() {
return {
num: 1
}
},
template: `
<demo :num="num"/>
`
});
app.component('demo', {
props: ['num'],
data() {
return {
myNum: this.num
}
},
methods: {
handleAddNum() {
// this.num += 1; // 错误写法,子组件中不能使用父组件传递过来的数据
this.myNum += 1;
}
},
template:`<div @click="handleAddNum">{{myNum}}</div>`
});
const vm = app.mount('#root');
</script>
Non-Props属性
- 如果当父组件传递给子组件参数,但是子组件并没有使用
props
接收的话,就会使用到Non-Props属性。- 如果子组件中只有一个标签的话,那么传递的参数会直接放在子组件的那一个标签中当做属性
- 如果子组件中不止一个标签的话,那么传递的参数就不会同时作用于所有的组件。要是想用传递过来的参数的话,可以使用
vue
中自带的$attrs
变量来进行赋值。- 如果想要接收全部的参数,可以直接
v-bind="$attrs"
。 - 如果只想要接收部分参数,可以使用
v-bind:属性名="$attrs.属性名"
来接收。 - 另外如果想要接收传递的参数可以将子组件的
inheritAttrs
设置为false
即可。
- 如果想要接收全部的参数,可以直接
<script type="text/javascript">
const Test1 = {
template:"<div>hello world</div>"
};
const Test2 = {
inheritAttrs: true,// 可以选择不接收任何属性
template:`
<div v-bind="$attrs">hello world</div>
<div :attr1="$attrs.attr1">hello world</div>
<div :attr2="$attrs.attr2" :attr3="$attrs.attr3">hello world</div>
`
};
const app = Vue.createApp({
components: {
Test1,
Test2,
},
data() {
return {
content: "hello world",
}
},
template: `
<test1 :content="content" style="color:red" />
<test2 attr1="a" attr2="b" attr3="c" />
`
});
const vm = app.mount('#root');
</script>
![[Pasted image 20220430192003.png]]
父子组件间如何通过事件通信
前面说过父组件传递给子组件的数据是只读的,不能在子组件中修改。但是子组件可以使用app中的emit
属性来自定义事件去通知父组件修改父组件中的数据,这样父组件再传递给子组件数据的时候,子组件接收到的数据也就变化了。
- 功能:每一次点击数字,都会触发子组件的
handleAddNum
函数,然后子组件中自定义一个事件addOne
,父组件中可以通过@
来接听这个事件(前面说明子组件中的命名格式是大驼峰,而根组件中要改成对应的小写字母加-
的命名风格,所以子组件中的addOne
事件到父组件中就成了add-one
事件),最终父组件触发handleAddOne
函数,让count
加一。
<script type="text/javascript">
const app = Vue.createApp({
data() {
return {
num: 1
}
},
methods: {
handleAddOne() {
this.num += 1;
}
},
template: `
<demo :num="num" @add-one="handleAddOne"/>
`
});
app.component('demo', {
props: ['num'],
methods: {
handleAddNum() {
this.$emit('addOne');
}
},
template:`<div @click="handleAddNum">{{num}}</div>`
});
const vm = app.mount('#root');
</script>
自定义事件也可以传递参数。子组件中传递的参数可以在父组件中监听这个事件的函数中使用,即子组件中给addOne
传递的参数可以在父组件的handleAddOne
中使用。
<script type="text/javascript">
const app = Vue.createApp({
data() {
return {
num: 1
}
},
methods: {
// 使用传递来的参数
handleAddOne(param1, param2) {
this.num += param1 + param2;
}
},
template: `
<demo :num="num" @add-one="handleAddOne"/>
`
});
app.component('demo', {
props: ['num'],
methods: {
handleAddNum() {
// 传递参数
this.$emit('addOne', 2, 4);
}
},
template:`<div @click="handleAddNum">{{num}}</div>`
});
const vm = app.mount('#root');
</script>
- 子组件中可以使用数组来定义emits属性中可以触发哪些事件。也可以在emits中定义字典来检验自定义事件传递的参数的合法性。
<script type="text/javascript">
const app = Vue.createApp({
data() {
return {
num: 1
}
},
methods: {
handleAddOne(num) {
this.num = num;
}
},
template: `
<demo :num="num" @add-one="handleAddOne"/>
`
});
app.component('demo', {
props: ['num'],
// emits: ['addOne'],
emits: {
// 对addOne事件进行数据校验
addOne: (val) => {
if (val > 10) {
return false;
}
return true;
}
},
methods: {
handleAddNum() {
this.$emit('addOne', this.num + 5);
}
},
template:`<div @click="handleAddNum">{{num}}</div>`
});
const vm = app.mount('#root');
</script>
上面这个功能其实就是父组件给子组件传递参数,然后子组件再返回一个数据给子组件,这个功能其实就类似于数据的双向绑定,其实v-model
在这里也是可以适用的。
v-model的高级用法
- 如果使用
v-model
进行数据双向绑定的话,其实写法很固定,父组件中将需要绑定的数据使用v-mode
传递过来,然后子组件中props
中一定要用modelValue
这个名字来接收传递的数据,然后在子组件的this.$emit
中触发的事件的名字一定是update:modelValue
,然后将修改过的数据传递给父组件,传递的数据会自动覆盖父组件中的数据,这样就形成了数据的双向绑定。
<script type="text/javascript">
const app = Vue.createApp({
data() {
return {
num: 1
}
},
methods: {
handleAddOne(num) {
this.num = num;
}
},
template: `
<demo v-model="num" />
`
});
app.component('demo', {
props: ['modelValue'],
methods: {
handleAddNum() {
this.$emit('update:modelValue', this.modelValue + 1);
}
},
template:`
<div @click="handleAddNum">{{modelValue}}</div>
`
});
const vm = app.mount('#root');
</script>
如果想要给双向绑定的数据起一个名字的话,可以以v-model:数据名="传递的数据"
的方式。
<script type="text/javascript">
const app = Vue.createApp({
data() {
return {
num1: 1,
num2: 1
}
},
template: `
<demo v-model:num1="num1" v-model:num2="num2"/>
`
});
app.component('demo', {
// 接收两个双向绑定的数据
props: ['num1', 'num2'],
methods: {
handleAddNum1() {
// 更新num1
this.$emit('update:num1', this.num1 + 1);
},
handleAddNum2() {
// 更新num2
this.$emit('update:num2', this.num2 + 1);
}
},
template:`
<div @click="handleAddNum1">{{num1}}</div>
<div @click="handleAddNum2">{{num2}}</div>
`
});
const vm = app.mount('#root');
</script>
- v-mode除了可以通过其别名的方式传递双向绑定的数据,也可以给传递的数据添加上自定义的修饰符。
组件中使用modelModifiers
来查看修饰符是否存在。
<script type="text/javascript">
const app = Vue.createApp({
data() {
return {
message: 'a'
}
},
template: `
<demo v-model.uppercase="message"/>
`
});
app.component('demo', {
props: {
'modelValue': String,
'modelModifiers': {
// 默认返回一个空对象
default: () => {
return {}
}
}
},
methods: {
handleClick() {
let newVal = this.modelValue + 'a';
// 当双向绑定的事件有uppercase这个修饰符的时候,this.modelModifiers.uppercase=true
if (this.modelModifiers.uppercase) {
newVal = newVal.toUpperCase();
}
this.$emit('update:modelValue', newVal);
}
},
template:`
<div @click="handleClick">{{modelValue}}</div>
`
});
const vm = app.mount('#root');
</script>
使用插槽和具名插槽解决组件内容传递问题
当父组件想要传递一个标签给子组件的时候,通过传递属性值的方式进行传递的话,太过麻烦。此时就可以使用slot插槽,slot插槽可以定制化组件中的内容,slot中可以传递很多东西,可以是字符串,标签,另一个子组件等等。如果slot中没有传递数据的话,可以在slot标签中添加默认值。
但是slot标签在子组件中是无法绑定事件的,一般都是通过在slot标签外面加上一个span标签,然后对span标签进行绑定事件来解决的。
slot中使用的数据的作用域
- 如果slot中传递的标签有一个{{}}包裹的变量,这个变量使用的是父组件中的变量替换,而不是子组件的变量
- 总结:父模板中调用的数据使用的都是父模板中的数据。子模板中调用的数据都是子模板中的数据。
<script type="text/javascript">
const app = Vue.createApp({
data() {
return {
message: "提交"
}
},
template: `
<myform>
Hello Slot~
<button>{{message}}</button>
</myform>
<br/>
<myform>
Hello Slot!
<span>{{message}}</span>
</myform>
<br/>
<myform></myform>
`
});
app.component('myform', {
methods: {
handleClick() {
alert("hello");
}
},
template: `
<span @click="handleClick">
<slot>default value</slot>
</span>
`
});
const vm = app.mount('#root');
</script>
具名插槽
如果想要将父组件中传递的slot拆分成多个部分使用的话,就不能像直接在子组件中直接使用slot标签了,而是要对父组件中的slot进行v-slot指令,并在子组件中指定slot的名字。
注意:
- 在父组件中传递的slot中使用v-slot指令,不能直接在标签上使用,而是要在标签的外面套一层占位符template,在template上使用v-slot执行。
- 在template中使用v-slot传递数据的名字,可以简写成
#数据名
<script type="text/javascript">
const app = Vue.createApp({
template: `
<layout>
<template #header>
<div>header</div>
</template>
<template v-slot:fonter>
<div>fonter</div>
</template>
</layout>
`
});
app.component('layout', {
template: `
<div>
<slot name="header"></slot>
<div> content </div>
<slot name="fonter"></slot>
</div>
`
});
const vm = app.mount('#root');
</script>
作用域插槽
在子组件中使用slot只能决定slot的展示方式(展示的位置,循环展示等等),但是不能决定子组件展示的内容。当slot要展示的内容在子组件中,但此时又想要将这些数据展示在父组件中是就需要使用作用域插槽。即子组件将数据传递给父组件,然后父组件可以通过v-slot获得所有子组件传递的数据,从而可以在父组件中使用子组件中要展示的内容。
<script type="text/javascript">
const app = Vue.createApp({
template: `
<layout v-slot="slotProps">
<div>{{slotProps.item}}</div>
</layout>
<layout v-slot={item}>
<span>{{item}}</span>
</layout>
`
});
app.component('layout', {
data() {
return {
list: [1, 2, 3]
}
},
template: `
<div>
<slot v-for="item in list" :item="item"/>
</div>
`
});
const vm = app.mount('#root');
</script>
注意:
- 因为父组件通过v-slot需要接收全部的数据,所以可以使用
{}
解构的方法,将数据直接获得。这样就不用通过.
的方式一个一个获取了。
动态组件和异步组件
动态组件:使用component标签中的is
属性,可以根据数据的变化,进行动态切换组件。
动态组件其实就是为了简化使用v-show
指令来判断不同组件的写法的。
<script type="text/javascript">
const app = Vue.createApp({
data() {
return {
curItem: 'input-item',
}
},
methods: {
handleClick() {
if (this.curItem === 'input-item') {
this.curItem = 'div-item';
} else {
this.curItem = 'input-item';
}
}
},
template: `
<component :is="curItem"/>
<br/>
<input-item v-show="curItem === 'input-item'"/>
<div-item v-show="curItem === 'div-item'"/>
<button @click="handleClick">切换</button>
`
});
app.component('input-item', {
template: `
<input />
`
});
app.component('div-item', {
template: `
<div>Hello World</div>
`
})
const vm = app.mount('#root');
</script>
异步的概念
- 异步就是当前任务不是立即执行,而是在等待一段时间后才可以执行任务。所以异步就是不同步。
<script>
console.log('a');
console.log('b');
setTimeout(() => {
console.log('异步执行');
}, 1000);
console.log('c');
console.log('d');
</script>
promise实现异步,当promise状态发生改变,就会触发then()中的响应函数。
状态改变有两种:
- pending=>fulfilled
- pending=>rejected
Promise对象中必须要传递一个参数,并且参数一定要是一个函数。
promise有两个参数,如果是成功执行函数就使用resolve
来执行函数,如果是执行函数就使用reject
来执行函数,并且调用的地方需要使用try,catch来捕捉异常,否则会报错。
async/wait也是用于处理异步的,背后的原理也是promise
<script>
// async表示异步函数,等价于f2()函数的写法
async function f1() {
return "hello async";
}
function f2() {
return new Promise((resolve) => {
resolve("hello async");
});
}
async function f3() {
// 因为await,所以函数会阻塞在await这儿
let res = await f1();
console.log(res);
console.log('hello await');
}
f3()
</script>
异步组件就是异步执行某些组件的逻辑。
<script type="text/javascript">
const app = Vue.createApp({
template: `
<async-item/>
`
});
app.component('async-item', Vue.defineAsyncComponent(() => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({
template: `<div>Hello async component</div>`
});
}, 2000);
})
}));
const vm = app.mount('#root');
</script>
注意:
- 所有异步操作中都需要传入一个函数作为参数,例如defineAsyncComponent,Promise,setTimeout中,最后通过resolve返回一个组件,可以在组件的template中定义组件需要渲染的标签。
补充内容
- v-once:让组件只渲染一次,即使组件中的数据发生了变化,组件也只渲染一次
<script type="text/javascript">
const app = Vue.createApp({
data() {
return {
count: 1
}
},
template: `
<div @click="count += 1" v-once>{{count}}</div>
`
});
const vm = app.mount('#root');
</script>
即使触发事件后,count增加了,但是页面只会显示1。
- ref:可以获得dom节点或者组件的引用,相当于拿到dom节点或者组件本身
<script type="text/javascript">
const app = Vue.createApp({
data() {
return {
count: 1
}
},
mounted() {
// 获得div标签
console.log(this.$refs.count);
// 获得子组件的引用,并使用子组件中的函数
this.$refs.item.sayHello();
},
template: `
<div ref="count">{{count}}</div>
<item ref="item"/>
`
});
app.component('item', {
methods: {
sayHello() {
alert('Hello~');
}
},
template: `<div></div>`
});
const vm = app.mount('#root');
</script>
- provide/inject:父组件通过provide可以将数据跨组件传递,需要接收数据的组件使用inject属性接收
<script type="text/javascript">
const app = Vue.createApp({
data() {
return {
count: 1
}
},
provide: {
count: 1
},
template: `
<child/>
`
});
app.component('child', {
template: `
<child-child />
`
});
app.component('child-child', {
inject: ['count'],
template: `
<div>{{count}}</div>
`
});
const vm = app.mount('#root');
</script>
在父组件中使用provide
属性即可传递参数,但是如果使用对象来传递,那么传递的参数是一次性分配的,和父组件中的参数无关。所以要将provide写成一个函数,这样就可以将父组件的参数传递给其他组件了。
const app = Vue.createApp({
data() {
return {
count: 1
}
},
provide() {
return {
count: this.count
}
},
template: `
<child/>
`
});
但是注意即使将父组件的参数传递过去了,但是这个数据不是和父组件绑定的。所以在组件中使用不会影响到父组件中的参数。