前言:一定要注意这套笔记使用的Vue3,许多Vue2的技术在Vue3中已经被淘汰了。故使用Vue2的小伙伴一定不可生搬硬套。
Vue3是2020年09月18日正式发布的,现在许多Vue的教程都是基于Vue2来讲解的。但是Vue3到Vue2版本之间存在较大差异,博主也是一个刚入门Vue的小白,推荐大家想要快速上手Vue的话,可以先去看以下菜鸟的Vue教程(Vue2与Vue3都有)菜鸟教程 Vue3,视频可以去看b站ilovecoding的 Vue入门到精通,讲的Vue2的实战,主要目的是了解Vue的应用。我们也可以使用Vue3的技术,当然语法可能会不同,可以自行查看官方的文档,或查看上面推荐的菜鸟教程。
Vue的概述
Vue是一套用于构建用户界面的渐进式框架。渐进式的指的是主张最少。每个框架都不可避免会有自己的一些特点,从而会对使用者有一定的要求,这些要求就是主张,主张有强有弱,它的强势程度会影响在业务开发中的使用方式。
本文不是在去讲Vue的基础语法及应用,基础的东西在Vue官网已经讲的很通俗易懂,故这里只记录Vue在平常项目中的应用与技巧。
1、理解Vue中的MVVM是什么
- View层:视图层,在我们前端开发中,通常就是DOM层。主要的作用是给用户展示各种信息。
- Model层:数据层,数据可能是固定的死数据,更多的是来自我们服务器,从网络上请求下来的数据。
- VueModel层:视图模型层,视图模型层是View和Model沟通的桥梁。一方面它实现了Data Binding,也就是数据绑定,将Model的改变实时的反应到View中,也就是我们常说的响应式。另一方面它实现了DOM Listener,也就是DOM监听,当DOM发生一些事件(点击,滚动,touch等)时,可以监听到,并在需要的情况下改变对应的Data。
2、表单元素的绑定
以下是官方的原话:
你可以用 v-model 指令在表单 <input>、<textarea> 及 <select> 元素上创建双向数据绑定。它会根据控件类型自动选取正确的方法来更新元素。尽管有些神奇,但 v-model 本质上不过是语法糖。它负责监听用户的输入事件来更新数据,并在某种极端场景下进行一些特殊处理。
v-model 在内部为不同的输入元素使用不同的 property 并抛出不同的事件:
text 和 textarea 元素使用 value property 和 input 事件;
checkbox 和 radio 使用 checked property 和 change 事件;
select 字段将 value 作为 prop 并将 change 作为事件。
一、表单元素绑定与值绑定的综合案例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="v-model-radiobutton">
<h2>文本框 Text</h2>
<input v-model="text" placeholder="edit me" />
<p>Text is: {{ text }}</p>
<h2>多行文本 Textarea</h2>
<p style="white-space: pre-line;">textarea is:{{ textarea }}</p>
<br />
<textarea v-model="textarea" placeholder="add multiple lines"></textarea>
<h2>复选框 Checkbox</h2>
<input type="checkbox" id="checkbox" v-model="checked" true-value="yes" false-value="no"/>
<p>Checked: {{ checked }}</p>
<h2>单选框 Radio</h2>
<input type="radio" id="one" value="One" v-model="picked" />
<label for="one">One</label>
<br />
<input type="radio" id="two" value="Two" v-model="picked" />
<label for="two">Two</label>
<br />
<span>Picked: {{ picked }}</span>
<h2>选择框 Select</h2>
<div id="v-model-select" class="demo">
<select v-model="selected">
<option disabled value="">Please select one</option>
<option>A</option>
<option>B</option>
<option>C</option>
</select>
<p>Selected: {{ selected }}</p>
</div>
</div>
</body>
<script>
Vue.createApp({
data() {
return {
text:'文本',
textarea:'文本域',
checked:'yes',
picked:'',//没有默认值
// picked: 'One,'//默认值为One
selected:'A'
}
}
}).mount('#v-model-radiobutton')
</script>
</html>
二、使用修饰符(限定词)
.lazy
在默认情况下,v-model 在每次 input 事件触发后将输入框的值与数据进行同步 (除了上述输入法组织文字时)。你可以添加 lazy 修饰符,从而转为在 change 事件之后进行同步。
<input v-model.lazy="msg" />
.number
如果想自动将用户的输入值转为数值类型,可以给 v-model 添加 number 修饰符:
<input v-model.number="age" type="text" />
当输入类型为 text 时这通常很有用。如果输入类型是 number,Vue 能够自动将原始字符串转换为数字,无需为 v-model 添加 .number 修饰符。如果这个值无法被 parseFloat() 解析,则返回原始的值。
.trim
如果要自动过滤用户输入的首尾空白字符,可以给 v-model 添加 trim 修饰符:
<input v-model.trim="msg" />
以上可见 官方文档
3、组件化开发
组件化是Vue.js中的重要思想,它提供了一种抽象,让我们可以开发出一个个独立可复用的小组件来构造我们的应用。任何的应用都会被抽象成一颗组件树。
如果我们将一个页面中的所有逻辑放在一起,处理起来会变得非常复杂,而且不利于后续的管理以及扩展。但如果,我们将一个页面拆分成一个个小的功能块,每个功能块完成属于自己这部分独立的功能,那么之后整个页面的管理和维护就变得非常容易了。
案例1:创建一个绑定Vue实例的组件
<div id="app">
<my-cpn></my-cpn>
</div>
<script>
const vm = Vue.createApp({
data(){
return{
message:'gjkhkjh'
}
}
});
//全局组件,
vm.component('my-cpn',{
template:`<div>
{{a}}
</div>`,
data(){
return{
a:'自定义组件属性'
}
}
});
vm.mount('#app');
</script>
案例2:使用JavaScript对象自定义组件(更加灵活)
<div id="app">
<component-a></component-a>
<component-b></component-b>
</div>
<script>
var A = {
template: '<h1>自定义组件A!</h1>'
}
var B = {
template: '<h1>自定义组件B!</h1>'
}
const app = Vue.createApp({
components: {
'component-a': A,
'component-b': B
}
}).mount('#app')
</script>
组件模板分离的写法
<div id="app">
<component-c></component-c>
</div>
<template id="cpn">
<h2>我是组件分离模板</h2>
</template>
<script>
const app = Vue.createApp({
components: {
'component-c': {
template:`#cpn`
}
}
})
app.mount('#app')
</script>
4、组件之间的传值与访问方式
(1)父组件传递数据到子组件(父 =[data]=> 子)#
(2)子组件传递数据给父组件(子 =[data]=> 父)#
(3)父组件的访问子组件 #
(4)子组件的访问父组件(了解即可) #
(5)子父组件之间的双向绑定(了解即可)#
父组件传递数据到子组件
父向子组件传递数据是需要通过props属性传值。
<div id="app">
<component-a :cmovies="movies"></component-a>
</div>
<template id="componentA">
<ul>
<li v-for="m in cmovies">{{m}}</li>
</ul>
</template>
<script>
var componentA = {
template: `#componentA`,
// props:['cmovies'],
props: {
cmovies:Array, //要求cmovies属性必须是一个Array类型
message:{
type:String,
default:'aaaaa' //提供默认值
},
message2:{//类型是对象或数组时,默认值必须是一个函数
type:Array,
default(){
return [];
}
},
//自定义验证函数
propF:{//这个值必须匹配下列字符串中的一个
validator:function(value){
return ['success','warning','danger']
}
}
},
data(){
return{
}
}
}
const app = Vue.createApp({
data(){
return{
movies:['海尔兄弟','海王','海贼王']
}
},
components: {
'component-a': componentA,//注意组件名不能使用大写字符
}
}).mount('#app')
</script>
子组件传递数据给父组件
子组件要将数据传递给父组件要用到$emit方法,它的语法格式为$emit(‘发射事件名称’,[参数])。
<div id="app">
<cpn @item-click="cpnClick"></cpn>
</div>
<template id="cpn">
<button v-for="item in categories" @click="$emit('item-click',item)">{{item.name}}</button>
<!-- <button v-for="item in categories" @click="btnClick(item)">{{item.name}}</button>-->
</template>
<script>
let cpn = {
emits:['item-click'],
template:`#cpn`,
data(){
return {
categories:[
{id:'aa',name:'热门推荐'},
{id:'bb',name:'手机数码'},
{id:'cc',name:'家用家电'},
{id:'dd',name:'电脑办公'}
]
}
},
methods:{
// btnClick(item){
// this.$emit('item-click',item);
// }
}
}
let vm = Vue.createApp({
data(){
return{
}
},
components:{
cpn
},
methods:{
cpnClick(item){
console.log(item);
}
}
}).mount('#app');
</script>
父组件的访问子组件
父组件要访问子组件的数据要使用两个全局对象$children $refs,$children用于取所有的子组件,$refs取ref=’'的子组件。因为在开发中几乎都是使用$refs的方式访问子组件,$children在Vue3中被弃用了。
$refs用法如下:
<div id="app">
<cpn ref="aaa"></cpn>
<button @click="btnClick">按钮</button>
</div>
<template id="cpn">
<h2>子组件</h2>
</template>
<script>
let cpn = {
template:`#cpn`,
methods:{
showRef(){
console.log('引用方法')
}
}
}
let vm = Vue.createApp({
data(){
return{
}
},
methods:{
btnClick(){
this.$refs.aaa.showRef()
}
},
components:{
cpn
}
}).mount('#app')
</script>
子组件访问父组件
子组件访问父组件使用$parent对象,但在我们的开发中基本不会这样用。
<div id="app">
<cpn></cpn>
</div>
<template id="cpn">
<div>
<h2>c组件</h2>
<ccpn></ccpn>
</div>
</template>
<template id="ccpn">
<h2>cc组件</h2>
<button @click="btnClick">按钮</button>
</template>
<script>
let ccpn = {
template:`#ccpn`,
methods:{
btnClick(){
console.log(this.$parent.name)
console.log(this.$root)
}
}
}
let cpn = {
template:`#cpn`,
data(){
return{
name:'c组件'
}
},
components:{
ccpn
}
}
let vm = Vue.createApp({
data(){
return{
}
},
methods:{
},
components:{
cpn
}
}).mount('#app')
</script>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="app">
<cpn :cpropsnum1="num1" :cpropsnum2="num2" @pnum1change="num1change" @pnum2change="num2change" />
</div>
</body>
<template id="cpn">
num1 <br>
props值: {{cpropsnum1}}<br>
data值:{{cnum1}}<br>
<input type="text" v-model="cnum1" @input="cnum1change"><br><br>
num2 <br>
props:{{cpropsnum2}}<br>
data:{{cnum2}}<br>
<input type="text" v-model="cnum2" @input="cnum2change">
</template>
<script>
let cpn = {
emits:['pnum1change','pnum2change'],
template:`#cpn`,
props:{
cpropsnum1:Number,
cpropsnum2:Number
},
data(){
return{
cnum1:this.cpropsnum1,
cnum2:this.cpropsnum2
}
},
methods:{
cnum1change(event){
this.cnum1 = event.target.value;
this.$emit('pnum1change',this.cnum1);
},
cnum2change(event){
this.cnum2 = event.target.value;
this.$emit('pnum2change',this.cnum2);
}
}
}
let vm = Vue.createApp({
data(){
return{
num1:1,
num2:2
}
},
methods:{
num1change(num1){
this.num1 = parseFloat(num1);
},
num2change(num2){
this.num2 = parseFloat(num2);
}
},
components:{
cpn
}
}).mount('#app')
</script>
</html>
5、插槽的应用
看一下官方的原话:Vue 实现了一套内容分发的 API,这套 API 的设计灵感源自 Web Components 规范草案,将 <slot> 元素作为承载分发内容的出口。
<slot>元素主要用于分发内容的接口,它可以有自己的内部实现,也可以由父组件自定义实现来覆盖其内部实现。
下面是插槽的两种使用方式:
1、具名插槽的基本使用
<div id="app">
<!--slot插槽-->
<cpn><div>插槽值</div></cpn>
<!--slot具名插槽-->
<!--注意,v-slot 只能添加在 <template> 上 (只有一种例外情况)。-->
<cpn>
<template v-slot:header>
<h1>Here might be a page title</h1>
</template>
<template v-slot:default>
<p>A paragraph for the main content.</p>
<p>And another one.</p>
</template>
<template v-slot:footer>
<p>Here's some contact info</p>
</template>
</cpn>
</div>
<template id="cpn">
<!--普通插槽-->
<!-- <slot>-->
<!-- <div>插槽的默认值</div>-->
<!-- </slot>-->
<!--具名插槽-->
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
</template>
<script>
let cpn = {
template:`#cpn`,
methods:{
}
}
let vm = Vue.createApp({
data(){
return{
}
},
methods:{
},
components:{
cpn
}
}).mount('#app')
</script>
2、自定义组件的渲染方式
<div id="app">
<h2>使用子组件的默认渲染方式(slot的默认值)</h2>
<cpn></cpn>
<h2>父组件自定义子组件的渲染方式</h2>
<cpn>
<!--slotProps为自定义名称,它是一个封装了slot属性的Object-->
<template v-slot:myslot="slotProps">
<span>{{slotProps.data.join(" >>> ")}}</span>
</template>
</cpn>
</div>
<template id="cpn">
<div>
<slot name="myslot" :data="planguage"> <!--data为自定义属性名-->
<ul>
<li v-for="item in planguage">{{item}}</li>
</ul>
</slot>
</div>
</template>
<script>
let cpn = {
template:`#cpn`,
data(){
return{
planguage:['Java','C++','C#','Go','Javascript']
}
},
methods:{
}
}
let vm = Vue.createApp({
data(){
return{
message:'你哈'
}
},
methods:{
},
components:{
cpn
}
}).mount('#app')
</script>
6、Vue中有哪些响应式操作数组的方法
在Vue的实例中操作Data Model中的数组,且View视图模型也会跟着改变,即要做到数组的响应式操作有哪些方法。例如下面对数组的某个下标直接赋值的操作,数据改变了,但是前端展示的数据是不会变的。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="app">
<ul>
<template v-for="item in hobby">
<li>{{item}}</li>
</template>
</ul>
<button v-on:click="btnClick">按钮</button>
</div>
</body>
<script>
let vm = Vue.createApp({
data(){
return{
hobby:['a','b','c','d']
}
},
methods:{
btnClick(){
this.hobby[0] = 'aaaaa';
console.log(this.hobby);
}
}
}).mount('#app')
</script>
</html>
那么在Vue中有哪些实用的操作数组响应式的方法呢
方法 | 描述 |
---|---|
push() | 在数组后面添加新元素 |
pop() | 删除数组中的最后一个元素 |
shift() | 删除数组中的第一个元素 |
unshift() | 在数组最前面添加新元素 |
splice() | 可用于删除、插入、替换数组元素 |
sort() | 给数组排序 |
reverse() | 数组元素反转 |
我们来用用看
let vm = Vue.createApp({
data(){
return{
hobby:['a','b','c','d']
}
},
methods:{
btnClick(){
// 1.push:在数组最后面添加元素
this.hobby.push('aaa');
}
}
}).mount('#app');
splice()方法使用详解
格式:splice(起始元素下标, [删除元素个数],[替换元素...])
splice(0,1);删除下标0开始往后1个元素
splice(0,1,‘a’);将下标为0的元素替换为a。(实际上是先删除了下标0的元素,再将a插入)
splice(index,0,‘a’,‘b’,‘c’);将a,b,c插入到下标为index的元素前面
splice(index+1,0’a’;‘b’,‘c’)将a,b,c插入到下标为index的元素后面
注意:以上响应式的问题在Vue3中实际上已经得到了解决。
7、Vue3的组合式API
1、setup函数,setup 选项在组件创建之前执行,一旦 props 被解析,就将作为组合式 API 的入口。
export default {
name: 'App',
setup(){
const name = ref('马云');
function f1() {
this.name = '马化腾';
}
}
2、ref函数,在 Vue 3.0 中,我们可以通过一个新的 ref 函数使任何响应式变量在任何地方起作用,如下所示:
import {ref} from 'vue'
export default {
name: 'App',
setup(){
const name = ref('马云');
function f1() {
this.name = '马化腾';
}
return {name,f1};
}
}
Vue案例
购物车
购物车
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="https://unpkg.com/vue@next"></script>
<style>
table{
border-collapse: collapse;
width: 100%;
text-align: center;
}
</style>
</head>
<body>
<div id="app">
<fieldset>
<legend>商店</legend>
<table border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<th>序号</th>
<th>书籍名称</th>
<th>出版日期</th>
<th>购买数量</th>
<th>价格</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<template v-for="(v,k) in books">
<tr>
<td>{{k}}</td>
<td>{{v.name}}</td>
<td>{{v.publishDate}}</td>
<td>¥{{v.price.toFixed(2)}}</td>
<td>
<button @click="reduce(k)" :disabled="v.count<=1">-</button>
{{v.count}}
<button @click="increment(k)">+</button>
</td>
<td>
<button @click="removeBooks(k)">删除</button>
</td>
</tr>
</template>
</tbody>
</table>
<h2 v-show="books.length>0">总价{{getTotal}}</h2>
<h2 v-show="books.length<=0">购物车为空</h2>
</fieldset>
</div>
</body>
<script src="../js/cart.js"></script>
</html>
js文件
let vm = Vue.createApp({
data(){
return{
books:[
{
name:'《算法导论》',
publishDate:'2006-9',
price: 85.00,
count:1
},
{
name:'《UNIX编程艺术》',
publishDate:'2006-2',
price: 59.00,
count:1
},
{
name:'《编程珠玑》',
publishDate:'2008-9',
price: 39.00,
count:1
},
{
name:'《代码大全》',
publishDate:'2006-3',
price: 128.00,
count:1
}
]
}
},
methods:{
increment(index){
this.books[index].count++;
},
reduce(index){
this.books[index].count--;
},
removeBooks(index){
this.books.splice(index,1);
}
},
computed:{
getTotal(){
let total = 0;
for (let i = 0; i < this.books.length; i++) {
total +=this.books[i].count * this.books[i].price;
}
return '¥' + total.toFixed(2);
}
}
})
vm.mount('#app')