VUE学习笔记-基础语法及其应用-2-深入组件开发

(转)本文笔记&源码地址_JS胖

了解组件,什么是组件概念?

在这里插入图片描述

从图中我们可以看到,总共的大区域分为了三个板块

上面一个Header,下面左边一个LeftSide以及右边的RightSide

根据每个区域,又可以分为两个(最下层的绿色中间)、以及三个(最下层的绿色右边)

组件定义:

所谓组件,我们就可以看成是:一个页面中的某一个Section,通过不同的组件共同拼凑

从而,形成一个完整的组件,这也是目前前端最流行的开发方式:

vue_component.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="app"></div>
</body>
</html>

<script>
const app=Vue.createApp({ })
const vm=app.mount("#app")
</script>

我们可以看到,现在我们将一个新的VUE实例挂载到页面ID为app的区域

定义全局组件并使用

现在我们的app可以注册全局的组件,注册好后可以直接在根组件上进行使用

比如我们要显示两个信息(紧接着上方的基本代码)

  1. 这是我的网站

  2. https://www.vincent990413.gitee.io/myblog

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    //第一个全局组件
    app.component('my-component-describe',{
    template:` <h1> 这是我的网站 </h1> `
    })

    //第二个全局组件
    app.component('my-component-website',{
    template:` <h1> https://www.vincent990413.gitee.io/myblog </h1>`
    })

以上就完成了对组件的定义,接下来我们要做的就是使用这些组件

1
2
3
4
5
6
7
//在创建实例的同时 使用自己的组件
const app = Vue.createApp({
template:`
<my-component-describe />
<my-component-website />
`
})

可以将这些步骤想象成 “初始化数据” 初始化数据之后 -> 最后一步才是挂载在网页中

全局组件的可复用性

什么叫可复用性,就是可重用性,我们将一个组件封装好之后,

可以在网页中的各个区域去重复引用这个组件

全局组件的弊端?!

全局组件是有弊端的,因为你处处都可以使用,但是性能不高,应用打开就开始复杂的初始化

局部组件

针对与局部组件:我现在不注册,我要用的时候才注册这个组件,才进行使用

而全局组件是,我不用之前一直都被注册了,一直都存在在那里!

我们可以把局部组件,想象成,一个变量!我定义这个变量,我用的时候再注册它。

创建局部组件

1
2
3
4
5
6
7
8
const myComponentCounter = {
data(){
return{
count: 0
}
},
template:` <div> {{count}} <button @click=" count++ ">增加1</button> </div> `
}

通过这种定义“变量”的方式,我们就已经定义好一个组件了,接下来我们需要在vue.CreateApp()方法里去注册

直接用component: { myComponentCounter }, 声明即可

1
2
3
4
5
6
7
const app = Vue.CreateApp({
components: {'my-component-counter':myComponentCounter}, //声明我的组件 前面的别名可以去掉
template: `
<h1>这是我的网站</h1>
<my-component-counter/>
` //在父组件中的任何位置 都可以调用已经注册了的组件
})

我们的局部组件一般使用驼峰命名法

但多个单词,我们应该遵守规范,

1. const变量用驼峰myComponentCounter

2. 起别名时 单引号且中间加- 小写每个字母: my-component-counter

(具体见上方代码)

父组件与子组件以及他们之间的静态、动态传值

什么是父组件,什么是子组件?

我们在父组件中,定义子组件并使用子组件

被注册,被使用的那个组件叫做:子组件

使用 “定义好的组件” 的组件叫做父组件

定义全局组件Son 并在父组件中使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
app.component('Son',{
template: `
<div> 这是子组件 Son div </div>
`
})

const app = Vue.CreateApp({
template: `
<h1>下面调用子组件</h1>
<Son />
`
// 调用Son 子组件
})

const vm=app.mount("#app")

父组件的参数传给子组件(静态传值)

所谓父组件向子组件传值,就是在调用子组件的时候,静态绑定props参数

1
2
3
4
5
6
7
8
9
//父组件传入name参数 那么子组件就要有一个name处于prop数组中
<Son name = "vincent"/>

app.component('Son',{
prop:['name']
template: `
<div> 这是子组件,父组件传过来的参数name值为:{{name}} </div>
`
})

这种方法是静态传输,一旦运行起来,就无法改变了

父组件的参数传给子组件(动态传值)

一般我们利用:将父组件中data中的参数与传入子组件的参数进行绑定

从而使得传入的值,是动态的,是可变的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const app = Vue.CreateApp({
data(){
return{
name: 'Beatrix'
}
},
template: `
<h1>下面调用子组件</h1>
<Son :name="name" />
`
// 此时是动态传入给子组件的参数
})

app.component('Son',{
prop:['name']
template: `
<div> 这是子组件,父组件传过来的参数name值为:{{name}} </div>
`
})

这样通过修改data中的参数 完成动态绑定

父组件的参数传给子组件(传入函数参数)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
//父组件
const app=Vue.createApp({
data(){
return{
name: 'Beatrix'
}
},
methods:{
talk(){
return "你好,我是Vincent."
}
},
template:`
<h1>下面调用子组件并传入函数参数talk</h1>
<Son :talk = "talk" />
`
})

//子组件
app.component('Son', {
props:['talk'],
methods:{
buttonTalk(){
//弹窗显示 父组件传来的函数参数
alert(this.talk())
}
},
template:`
<div> 这是子组件Beatrix,我想知道你是谁?</div>
<div> <button @click="buttonTalk">点我就知道了</button> </div>
`
})

const vm=app.mount("#app")

父组件的参数传给子组件(Slot插槽)

这种情况适用于,父组件传入给子组件一段HTML代码,欲在部分区域显示:

  1. 在子组件想要显示的区域,声明插槽

  2. 在父组件使用子组件的双标签形式,并插入HTML代码/其他子组件 <Son> <div> {{counter}}: <my-component-others /></div> </Son>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
const app = Vue.createApp({
data(){
return{
counter:1
}
},
template:`
<h1>父组件调用子组件并传入HTML代码/其他组件</h1>
<Son> <div style="background-color:red;"> {{ counter }}</div> </Son>
`
})

app.component('Son', {
data(){
return{

}
},
methods:{

},
template:`
<h1>子组件的标题</h1>
<slot></slot>
`
})

const vm = app.mount("#app")

值得注意的是,当父组件与子组件具有同名的变量时,我们父组件传入参数时,子组件的slot中会显示父组件的属性。

  • 父模板里调用的数据属性,使用的都是父模板里的数据。

  • 子模板里调用的数据属性,使用的都是子模板里的数据。

{{counter}} 是在父模板中调用的,那么使用的是父模板的数据。

slot中的默认数据

当slot中无数据传递过来是,写在slot中的数据就是默认显示数据。

1
<slot> 这是默认数据 </slot>

指定若干个插槽的位置(指定不同的name)

1
2
3
4
5
6
7
app.component('Son',{
template:`
<slot name="first"></slot>
<div>这是第二个</div>
<slot name="third"></slot>
`
})

我们可以看到,指定了name显示区域,在父组件传值的时候,也指定显示在对应name的区域即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const app = Vue.createApp({
data(){
return{

}
},
methods:{

},
template:`
<Son>
<template v-slot:first> <div>这是First的参数值</div> </template>
<template v-slot:third> <div>这是third的参数值</div> </template>
</Son>
`
})

在代码中,我们可以看到,利用 v-slot:XX 可以指定显示在对应子组件name为XX的区域

也可以简写成 #XX

子组件

子组件向父组件传递数据

  • 子组件调用父组件事件

  • 子组件向父组件事件中传递参数

  • 子组件传递参数时,如何通过emits进行校验

根据组件之间的单向数据流,子组件是没办法直接更改父组件传递过来的参数

解决办法

1. 将父组件传递过来的参数赋给子组件的数据 在子组件中更改自己的数据(本段稍后一点内容)

2. 我们可以调用父组件的 “可以改变这个参数值” 的方法,本质上是父组件来改变(紧接着读下去)

比如:我们用父组件更改传递给子组件的counter变量(用一个方法)

在子组件中调用这个方法

仔细看懂,接下来的代码!

仔细看懂,接下来的代码!

仔细看懂,接下来的代码!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
//父组件
const app=Vue.createApp({
data(){
return{
counter: 1
}
},
methods:{
parentUpdateCounter(){
this.counter++;
}
},
template:`
<h1>下面调用子组件传入参数counter</h1>

<h1>子组件通过父组件的方法,更改这个传递过去的参数(注意区分直接赋给子组件中的data参数)</h1>

<Son :counter="counter" @parentUpdateCounter="parentUpdateCounter" />
`
})

//子组件
app.component('Son', {
props: ['counter'],
emits:['parentUpdateCounter'],
data(){
return{

}
},
methods:{
childUpdate(){
this.$emit('parentUpdateCounter')
}
},
template:`
<div> 这是子组件,我想看到数据变化!</div>
<div> {{ childCounter }}
{{ counter }} <button @click=" childUpdate ">点我就知道了</button>
</div>
`
//可以看到 上面是{{ counter }} 直接引用父组件传过去的参数
})

const vm=app.mount("#app")

关键代码:

1. 首先在父组件中定义传递过去的变量counter data(){return{ counter:1 }}

2. 父组件传递过去 <Son :counter="counter" />

3. 子组件中声明props用来接收 props:[ 'counter' ]

4. 父组件中写可以更改counter变量的方法 methods:{parentUpdateCounter(){this.counter++}}

5. 父组件传递过去 <Son :counter="counter" @parentUpdateCounter="parentUpdateCounter"/> (@引用名=“父组件方法名”)

6. 子组件中声明emits用来接收 emits:['parentUpdateCounter'](与父组件传递过来的引用名一致)

7. 子组件在需要的时候(一般在子组件自己的方法内部),调用这个父组件的引用名(代表对应的父组件方法)this.$emit('parentUpdateCounter')

子组件利用slot向父组件传递List中的数据

在Javascript中的list中,我们视为对象,因此调用属性是需要 obj.item

  1. 为子组件中添加list数据
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    app.component('Son',{
    data(){
    return{
    names: ['Vincent', 'Beatrix', 'James']
    }
    },
    template:`
    <div>
    <slot v-for="name in names" :item="name"> </slot>
    </div>
    `
    })
  • 最重要的就是我们为这个插槽,绑定了item属性,这样父组件可以调用到这个属性。
  1. 父组件调用这个子组件,并得到绑定的item值
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    const app=Vue.createApp({
    data(){
    return{

    }
    },
    template:`
    <Son v-slot="props">
    <div> {{ props.item }} </div>
    </Son>
    `
    })
  • props可以任意取名字,但是一定是要绑定item这个属性

校验器(引入 ElementUI)

点我,见图书管理系统的前端项目地址

组件之间的单向数据流

一句话说明,就是数据从父级组件传递给子组件,只能这样单向(从父到子)绑定

子组件内部不能直接修改从父组件传递过来的数据

比如我们在父组件中定义一个计数器counter,传给子组件后,在子组件中更改这个数据

结论是,我们没办法看到这个数据的变化。

既然我们没办法,直接改变父组件传过来的变量,不如我们只更改子组件data中的变量

但将这个子组件data中的变量 赋值为父组件传过来的变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

比如父组件传过来的变量为counter

//父组件
const app=Vue.createApp({
data(){
return{
counter: 1
}
},
methods:{

},
template:`
<h1>下面调用子组件并静态传入函数参数talk</h1>
<Son :counter = "counter" />
`
})

//子组件中
app.component('myComponentCounter',{
props: ['counter'],
data(){
return{
//关键一步,我们赋给可以改变的childCounter
childCounter: this.counter
}
},
template:`
{{ childCounter }} <button @click=" childCounter++ "> 点我可以看到数据的变化 </button>
`
})

这样我们就可以看到数据的变化了!

  • 思考,为什么要有单向数据流的概念呢?
  • 因为,如果父组件使用多个相同的子组件,传入不同的参数,我们希望这些子组件互不干扰。

也就是说,子组件不能直接更改父组件的数据,而是更改自己的数据。

异步组件和Promise讲解

异步的概念

所谓异步,就是几个操作不是按严格的顺序执行(真正的执行顺序未知)

与它相反的概念是同步:

  • 举个例子,如何将大象塞到冰箱里?
  1. 打开冰箱

  2. 将大象塞进去

  3. 关掉冰箱门

以上的操作就是同步的,具有严格的前后时序。

一般而言,我们将经常要去后台请求数据的业务/组件,封装成异步的。

Promise

1
2
3
4
5
6
7
8
9
app.component('async-component',Vue.defineAsyncComponent(()=>{
return new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve({
template:`<div>这是一个异步组件</div>`
})
},3000)
})
}))

我们在调用这个组件时,三秒钟后,会出现对应组件中的内容

爷孙级别组件数据传递(多用于多级组件)

顺序依次继承数据

如果有一个爷爷,他现在有一套房子(北京四合院),想过继下去。

我们就可以实现:

  1. 从爷爷过继给父亲,再从父亲过继给孙子 props参数
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    const app=Vue.createApp({
    data(){
    return{
    house: '北京四合院,价值8000万!'
    }
    },
    template:`
    <h1> 这是爷爷,爷爷过继给儿子! </h1>
    <Son :house="house" />
    `
    })

    app.component('Son',{
    props:['house'],
    template:`
    <h2>儿子接受房子,这个房子是: {{ house }} <h2>
    <h2>儿子继续过继给孙子</h2>
    <Grandson :house="house" />
    `
    })

    app.component('Grandson',{
    props:['house'],
    template:`
    <h3>这是孙子,接受房子,房子信息是: {{house}} </h3>
    `
    })

    const vm=app.mount("#app")

这样通过依次继承的方法,的确可以做到将“爷爷”级别的组件数据传递给“孙子”组件

但如果项目中出现5,6次继承层次的孙孙孙…这样代码就太冗余了

指定组件层次接受参数 Provide&Inject

Provide意为提供参数,Inject引入接受、注入参数

也就是说,我们在“祖先”级别的组件提供某个参数,可以跳过中间的若干个组件,直接过继给指定的组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
const app=Vue.createApp({
data(){
return{
house: '北京四合院,价值8000万!'
}
},
provide:{
houseDirectlyToGrandson: '给孙子的海景房!'
},
template:`
<h1> 这是爷爷,爷爷不过继给儿子,直接给孙子! </h1>
<Son/>
`
})

app.component('Son',{
props:['house'],
template:`
<h2>这是儿子,不接受参数,不传递参数!</h2>
<Grandson/>
`
})

app.component('Grandson',{
props:['house'],
inject:['houseDirectlyToGrandson'],
template:`
<h3>这是孙子,接受房子,房子信息是: {{houseDirectlyToGrandson}} </h3>
`
})

const vm=app.mount("#app")

关键代码:

  1. 在爷爷组件中,提供属性 provide:{ newHounse:'新房子' }

  2. 在任意后代组件中,提供注入参数:inject:['newHouse'] 即可引用

  • 如果这种层级关系达到5-6级,再使用props进行传递,那就会非常麻烦,而且会有大量的代码冗余。

  • 使用provide和inject可以解决这个问题。