总结下自己在vue2中的重难点(组件篇)
自定义组件
说到组件,我们都知道组件可以高效率的复用,完美的提升了开发效率,而如果想使用组件肯定是要先进行注册的,而注册又有全局注册和局部注册。
- 全局注册:第一步肯定还是要先创建一个
Vue
格式的文件,需要写在main.js
,先引入组件文件,再使用Vue.component
对组件进行注册,如下:
// main.js
// 引入组件文件
import NowTime from './components/NowTime/now-time.vue'
// 直接使用Vue.component 对组件进行注册, 该组件名就是 Vue.component 的第一个参数
Vue.component('now-time', NowTime)
//注册成功后可在任意页面进行使用了。
- 局部注册:首先还是要创建一个
Vue
格式的文件,在需要使用组件的Vue文件APP中引入组件,并且要在App的components中注册组件,最后就可以在App中的template中使用组件了。如下图:
组件中的数据
另外,自定义中的组件内的数据data不同于App.vue中,前者必须是一个函数,而后者既可以是函数,也可以是对象。
组件单数据流
我们知道组件的作用就是复用,但是我们每个组件肯定都是要改变里面的内容的,即我们是要将父组件里面的内容传递给子组件。
prop实现父组件传递子组件
我们就先举个最简单的例子,将父组件的title1
和title2
这两个变量赋值给子组件的title
中。
<template>
<div id="app">
<!-- 注意!title1 和 title2 是父组件的 data 中定义的数据,title 则是子组件中接收数据时的变量名 -->
<HelloVue :title="title1"></HelloVue>
<HelloVue :title="title2"></HelloVue>
</div>
</template>
在子组件中的title
变量不是写在data
函数里面的,而是用一个props的数组来接收。
<template>
<div class="hello">
<!-- 第二步:在页面上显示 title 的值,写法和显示 data 里定义的数据一样 -->
<h1>{{ title }}</h1>
</div>
</template>
<script>
export default {
name: 'HelloVue',
// 第一步:在 prop 属性中接收 title
props: ['title']
};
</script>
另外,上面之所以将props属性定义为数组,是因为传过来的肯定不止一个变量,但是我们也可以进阶使用将数组改为对象。而且,类型首字母必须大写。
props: {
title: String,
// 多类型
likes: [String, Number],
// 带有默认值
isPublished: {
type: Boolean,
default: true
},
// 下面的required的属性意味着这个变量必填
commentIds: {
type: Array,
required: true
},
author: Object,
callback: Function,
contactsPromise: Promise
}
单向数据流
为什么这样子说呢,因为props里面的属性是单向的,只能从父组件传递到子组件,而不能反过来。但是问题来了,从父组件得到的数据不是我想要的,我需要修改一下,那么就没办法了吗?
当然不是,我在这里提供两个方案:
- 使用计算属性
computed
对数据进行处理。
props: ['initialTitle'],
computed: {
normalizedTitle: function () {
// 对传入的 initialTitle 进行去空格、字母小写化处理
return this.initialTitle.trim().toLowerCase()
}
}
- 可以将传入的数据放在本地data()中使用。
props: ['initialTitle'],
data: function () {
return {
// 要读取 prop 里的 initialTitle,要在前面加 “this.”
// 将传入的 initialTitle 作为本地属性 title 的初始值
title: this.initialTitle
}
}
这样子接下来我们只需要改变子组件里面的title就可以了。
在父组件中使用列表渲染子组件
下面的非常好理解,子组件中的article
是一个对象,接收的就是父组件中articleList
中的成员。
<Article
v-for="article in articleList"
:key="article.title"
:article="article"
></Article>
自定义组件绑定原生事件
在组件里面也可以绑定修饰符,这里主要就是事件修饰符
和按键修饰符
,举个例子就明白了。
- 事件修饰符:就如同之前说的
.stop
阻止冒泡、.capture
捕获、prevent
阻止默认事件等等。 - 按键修饰符:典型写法就是
@keyup.13
,里面的13是enter
键的ASCII码,这样子只要按下enter键就会执行方法了。
我们看下面这个例子就会明白为什么需要绑定原生事件。
<!-- 给自定义组件添加点击事件 print -->
<Article
v-for="article in articleList"
:key="article.title"
:article="article"
@click="print(article)"
></Article>
Article.vue 中:
<div class="article-title" @click="printTitle">{{ article && article.title }}</div>
// 在 `methods` 对象中定义方法
methods: {
printTitle() {
alert("cilcked a title");
}
}
我们可以看出,如果我们点击子组件并不会触发在父组件中定义的方法
print(article)
,只会触发子组件中定义的printTitle
方法,那么我如果想执行父组件的方法该怎么办呢,这时候就要用到.native
事件修饰符了,如下例子所示:
//这时候我们再点击组件Article就会执行父组件中定义的事件了。
<Article
v-for="article in articleList"
:key="article.title"
:article="article"
@click.native="print(article)"
></Article>
自定义事件
我们也可以给组件添加我们自己写的事件,即——自定义事件。看下面的例子吧:
<!-- 自定义事件 upVote,调用该事件时会执行 handleLikes 方法 -->
<article v-on:upVote="handleLikes"></article>
// 在 `methods` 对象中定义方法
methods: {
handleLikes(article) {
article.likes++
}
}
大家在上面看到了一个名为upvote
的自定义事件,别混淆啊,这个不是我们的@click
点击事件,我们想触发事件前提是调用它。
我们可以在组件里面定义一个按钮然后添加点击事件,并使用$emit()
来调用自定义事件
(注意,是调用自定义事件不是父类方法)。
<button @click="childEvent">点赞</button>
另外,$emit方法还可以往里面添加参数
,里面的参数对应自定义事件里面的参数位置。
methods: {
childEvent: function() {
// 调用自定义事件 upVote,这里的第二个参数最后会传到父组件中的 handleLikes 方法里
this.$emit('upVote', this.article);
// do other things
}
}
所以总结,自定义事件无非就两点:
- 在子组件中调用父组件的方法。
- 把子组件的数据通过自定义事件参数的形式传给父组件。
那么,以上就是自定义事件的基本使用了,我其实想说的是自定义事件的双向绑定
。
.sync事件修饰符
先声明下,在vue3中.sync已经被移除,这里我们了解一下,会用就得了。
//里面的.sync修饰符使得父类data函数中的全局变量count同子组件中props数组中的count变量双向绑定了。
<MyCount class="count" :count.sync="count"></MyCount>
// 在 `methods` 对象中定义方法
data: function() {
return {
count: 0
}
}
在下面我们使用update:propname(在props数组里面的变量)
更新事件,后面的参数就是将count+1
赋值给count
。
<div class="my-count">
<button @click="$emit('update:count', count+1)">加一</button>
{{ count }}
</div>
props: ['count'],
这样一来,我们不管在子组件的props
数组中更改count
,还是在父类中修改data函数中的count
,他俩都会发生变化。即——双向绑定。
ref访问子组件实例
我们已经学会使用自定义事件和双向绑定来通过子组件改变父类的变量了,那么我们还可以通过设置ref
属性在父类中改变子组件的实例。
1.ref调用子组件的方法
先给子组件添加ref
属性。
<template>
<Modal ref="modal"></Modal>
</template>
然后再通过this.$refs.ref属性名称
来调用子组件中的方法。
<script>
export default {
methods: {
showModal() {
// 调用子组件中的 show 方法
this.$refs.modal.show();
}
}
};
</script>
如果有多个子组件,那么里面定义的ref属性也是不同的,我们就是通过不同的属性名称调用各个组件的方法。
2.ref调用子元素
首先需要给要访问的子元素设置ref
属性。
<template>
<div id="app">
<input ref="input" type="text" />
<button @click="focusInput">点击使输入框获取焦点</button>
</div>
</template>
和上面一样,也是通过this.$refs.ref属性名称
来调用子元素。
<script>
export default {
name: 'app',
methods: {
focusInput() {
// this.$refs.input 访问输入框元素,并调用 focus() 方法使其获取焦点
this.$refs.input.focus();
}
}
}
</script>
组件slot插槽的使用
插槽的使用非常简单,我们可以先在子组件中输入slot标签,标签里面的内容可以为空,也可以是备用内容,看例子吧。
<div class="modal-content">
<slot>这是个弹框</slot>
<div class="footer">
<button @click="close">close</button>
<button @click="confirm">confirm</button>
</div>
</div>
里面是备用内容,即如果我们调用子组件什么也不输入的情况下显示的默认内容。下面两个例子区别非常明显:
//这个子组件里面什么也没输入。
<Modal :visible.sync="visible"></Modal>
//这个子组件里面则输入了个性化内容。
<Modal :visible.sync="visible">个性化内容</Modal>
slot插槽进阶使用
上面的使用非常基础,如果我需要多个插槽怎么办,答案很简单:
<div class="modal" v-if="visible">
<div class="modal-content">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
</div>
上面的有些slot
插槽里面设置了name
属性,并赋予了不同的值,那么我们看父类怎么写的。
上面也有没有设置
name
属性的slot
,这些就是默认的,相对应的,如果我们在父类中没有使用v-slot
来对应想要的slot
插槽,那么就是调用默认的插槽。
<Modal :visible.sync="visible">
<template v-slot:header>
<h1>Modal title</h1>
</template>
<div>main content</div>
<div>main content</div>
<template v-slot:footer>
<p>Modal footer</p>
</template>
</Modal>
可以看出,使用v-slot:属性值
,我们就可以往我们想插入的插槽里面插入内容了。
总而言之,记住了,slot
插槽就是写在子组件中的,记住这个就得了。
转载自:https://juejin.cn/post/7348721869087326243