实验介绍
为什么需要组件?组件是为了方面复用而产生的。
基本实例
比如我们想开发一个可以点击加一的计算按钮,而且这个按钮是到处可以使用的,所以我们需要将他封装成一个组件的方式,这样就可以在各个地方引入,在 src/main.js
写下如下代码:
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
let app = createApp(App)
app.use(store).use(router).mount('#app')
app.component('button-counter', {
template: `<button @click="count">{{counter}}</button>`,
data() {
return {
counter: 0,
}
},
methods: {
count() {
this.counter++
}
}
})
然后在 src/views/TemplateM.vue
:
<template>
<div class="template-m-wrap">
<button-counter></button-counter>
</div>
</template>
<script>
export default {
name: "TemplateM",
data() {
return {
};
},
};
</script>
查看浏览效果如下:
组件复用
你可以将组件进行任意次数使用:
<template>
<div class="template-m-wrap">
<button-counter></button-counter>
<button-counter></button-counter>
<button-counter></button-counter>
</div>
</template>
<script>
export default {
name: "TemplateM",
data() {
return {
};
},
};
</script>
查看浏览效果如下:
值得注意的是,此时组件的 counter
是各自独立的,不会互相影响的,因为你每用一次组件,就会有一个它的「新实例」被创建。
通过 Prop 向子组件传递数据
早些时候,我们提到了创建一个博文组件的事情。问题是如果你不能向这个组件传递某一篇博文的标题或内容之类的我们想展示的数据的话,它是没有办法使用的。这也正是 prop 的由来。
Prop 是你可以在组件上注册的一些自定义 attribute。当一个值传递给一个 prop attribute 的时候,它就变成了那个组件实例的一个 property。为了给博文组件传递一个标题,我们可以用一个 props 选项将其包含在该组件可接受的 prop
列表中:
在 src/main.js
,我们再来定义一个全局组件:
import { createApp } from 'vue/dist/vue.esm-bundler.js'
import App from './App.vue'
import router from './router'
import store from './store'
let app = createApp(App)
app.use(store).use(router).mount('#app')
app.component('button-counter', {
template: `<button @click="count">{{counter}}</button>`,
data() {
return {
counter: 0,
}
},
methods: {
count() {
this.counter++
}
}
})
// 我们再来定义一个全局组件
app.component('blog-title', {
props: {
title: {
type: String,
default: ''
}
},
template: `<div>{{title}}</div>`
})
然后我们在 src/views/TemplateM.vue
来使用组件,一个组件默认可以拥有任意数量的 prop,任何值都可以传递给任何 prop。在上述模板中,你会发现我们能够在组件实例中访问这个值,就像访问 data
中的值一样。
一个 prop 被注册之后,你就可以像这样把数据作为一个自定义 attribute 传递进来:
<template>
<div class="template-m-wrap">
<button-counter></button-counter>
<button-counter></button-counter>
<button-counter></button-counter>
<blog-title :title="title"></blog-title>
</div>
</template>
<script>
export default {
name: "TemplateM",
data() {
return {
title: "这是博客标题"
};
},
};
</script>
监听子组件事件
在我们开发 <blog-title>
组件时,它的一些功能可能要求我们和父级组件进行沟通。例如我们可能会引入一个辅助功能来放大博文的字号,同时让页面的其它部分保持默认的字号。
在其父组件中,我们可以通过添加一个 postFontSize
数据 property 来支持这个功能:
<template>
<div class="template-m-wrap">
<button-counter></button-counter>
<button-counter></button-counter>
<button-counter></button-counter>
<div class="blog-wrap" :style="{ 'font-size': titleFontSize + 'em' }">
<blog-title :title="title" @change-size="changeSize"></blog-title>
</div>
</div>
</template>
<script>
export default {
name: "TemplateM",
data() {
return {
title: "这是博客标题",
titleFontSize: 1,
};
},
methods: {
changeSize() {
this.titleFontSize++;
},
},
};
</script>
我们还是在 src/main.js
来定义组件,子组件分发 change-size
给父组件来控制 title
字体的大小变化:
// 我们再来定义一个全局组件
app.component('blog-title', {
props: {
title: {
type: String,
default: ''
}
},
template: `<div>
<h4>{{title}}</h4>
<button @click="emitFn">changeSize</button>
</div>`,
methods: {
emitFn() {
this.$emit('change-size')
}
}
})
我们看到的页面效果如下:
我们可以在组件的 emits
选项中列出已抛出的事件:
// 我们再来定义一个全局组件
app.component('blog-title', {
props: {
title: {
type: String,
default: ''
}
},
template: `<div>
<h4>{{title}}</h4>
<button @click="emitFn">changeSize</button>
</div>`,
methods: {
emitFn() {
this.$emit('change-size')
}
},
emits: ['change-size']
})
使用事件抛出一个值
有的时候用一个事件来抛出一个特定的值是非常有用的。例如我们可能想让 <blog-title>
组件决定它的文本要放大多少。这时可以使用 $emit
的第二个参数来提供这个值:
// 我们再来定义一个全局组件
app.component('blog-title', {
props: {
title: {
type: String,
default: ''
}
},
template: `<div>
<h4>{{title}}</h4>
<button @click="emitFn">changeSize</button>
</div>`,
methods: {
emitFn() {
this.$emit('change-size', 10) // 第二个参数抛出值
}
},
emits: ['change-size']
})
然后我们在组件中使用时,需要接收组件抛出来的值:
methods: {
changeSize(val) {
this.titleFontSize = this.titleFontSize + val;
},
},
总体代码如下:
<template>
<div class="template-m-wrap">
<button-counter></button-counter>
<button-counter></button-counter>
<button-counter></button-counter>
<div class="blog-wrap" :style="{ 'font-size': titleFontSize + 'em' }">
<blog-title :title="title" @change-size="changeSize"></blog-title>
</div>
</div>
</template>
<script>
export default {
name: "TemplateM",
data() {
return {
title: "这是博客标题",
titleFontSize: 1,
};
},
methods: {
changeSize(val) {
this.titleFontSize = this.titleFontSize + val;
},
},
};
</script>
查看浏览效果如下:
在组件上使用 v-model
自定义事件也可以用于创建支持 v-model
的自定义输入组件。记住:
<template>
<div class="template-m-wrap">
<input v-model="searchText" />
</div>
</template>
<script>
export default {
name: "TemplateM",
data() {
return {
searchText: ''
};
},
};
</script>
上面这段代码等价于下面这段代码:
<template>
<div class="template-m-wrap">
<input :value="searchText" @input="searchText = $event.target.value" />
</div>
</template>
<script>
export default {
name: "TemplateM",
data() {
return {
searchText: ''
};
},
};
</script>
那么如果写成组件,会是什么样展示的呢?
为了让它正常工作,这个组件内的 <input>
必须:
- 将其
value
attribute 绑定到一个名叫 `modalVal 的 prop 上 - 在其
input
事件被触发时,将新的值通过自定义的update:modalVal
事件抛出
写成代码之后是这样的:
app.component('custom-input', {
props: ['modalVal'],
template: `
<input
:value="modalVal"
@input="$emit('update:modalVal', $event.target.value)"
>
`
})
我们在使用组件时:
<template>
<div class="template-m-wrap">
<custom-input v-model="searchText"></custom-input>
</div>
</template>
<script>
export default {
name: "TemplateM",
data() {
return {
searchText: "",
};
},
};
</script>
v-model 结合计算属性
在自定义组件中创建 v-model
功能的另一种方法是使用 computed
property 的功能来定义 getter 和 setter。
在下面的示例中,我们使用计算属性重构 <custom-input>
组件。
请记住,get
方法应返回 modalVal
property,或用于绑定的任何 property,set
方法应为该 property 触发相应的 $emit
。
app.component('custom-input', {
props: ['modalVal'],
template: `
<input v-model="value">
`,
computed: {
value: {
get() {
return this.modalVal
},
set(val) {
this.$emit('update:modalVal', val)
}
}
}
})
通过插槽分发内容
和 HTML 元素一样,我们经常需要向一个组件传递内容,像这样:
<template>
<div class="template-m-wrap">
<content-box> Something bad happened. </content-box>
</div>
</template>
<script>
export default {
name: "TemplateM",
data() {
return {
searchText: "",
};
},
};
</script>
幸好,Vue 自定义的 <slot>
元素让这变得非常简单:
app.component('content-box', {
template: `<div>
这是插槽内容
<slot></slot>
</div>`
})