自定义一个customButton
组件
在components目录下创建customButton.vue文件
<template>
<button @click="count++">You clicked me {{ count }} times.</button>
</template>
<script setup>
import {ref} from "vue";
const count = ref(0)
</script>
使用组件
引入组件有两种方法,一个是全局注册组件,另一个是局部引入组件。
全局注册组件
在main.js中创建 app.component()
方法,让组件在当前 Vue 应用中全局可用。
import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App)
import customButton from './components/customButton.vue'
app.component('customButton',customButton)
app.mount('#app')
使用
<custom-button/>
app.component()
方法可以被链式调用:
app
.component('ComponentA', ComponentA)
.component('ComponentB', ComponentB)
.component('ComponentC', ComponentC)
局部注册
全局注册虽然很方便,但有以下几个问题:
-
全局注册,但并没有被使用的组件无法在生产打包时被自动移除,如果你全局注册了一个组件,即使它并没有被实际使用,它仍然会出现在打包后的 JS 文件中。 -
全局注册在大型项目中使项目的依赖关系变得不那么明确,影响应用长期的可维护性。
在使用<script setup>
的单文件组件中,导入的组件可以直接在模板中使用,无需注册:
<template>
<div>
<custom-button></custom-button>
</div>
</template>
<script setup>
import CustomButton from "@/components/customButton";
</script>
如果没有使用 <script setup>
,则需要使用 components 选项来显式注册:
<template>
<div>
<custom-button></custom-button>
</div>
</template>
<script>
import CustomButton from "@/components/customButton";
export default {
components: {
CustomButton
},
setup() {
}
}
</script>
传递 props
props
声明
在使用 <script setup>
的单文件组件中,props
可以使用 defineProps()
宏来声明:
<script setup>
import {ref,defineProps} from "vue";
const count = ref(0)
const props = defineProps(['title','likes'])
console.log(props)
</script>
在没有使用<script setup>
的组件中,prop
可以使用 props
选项来声明:
<script>
import {ref} from "vue";
export default {
props: ['title','likes'],
setup(props) {
console.log(props)
const count = ref(0)
return {
count
}
}
}
</script>
除了使用字符串数组来声明 prop
外,还可以使用对象的形式:
<script setup>
import {ref,defineProps} from "vue";
const count = ref(0)
// const props = defineProps(['title','likes'])
const props = defineProps({
title: String,
likes: Number
})
console.log(props)
</script>
<script>
import {ref} from "vue";
export default {
// props: ['title','likes'],
props: {
title: String,
likes: Number
},
setup(props) {
console.log(props)
const count = ref(0)
return {
count
}
}
}
</script>
传递 prop
自定义组件
<button @click="count++">{{title}}:{{likes}}</button>
向子组件传递 props
<custom-button title="点赞数" likes="5"/>
静态 vs. 动态 Prop
<custom-button title="点赞数" likes="5"/>
以上就是静态值形式的 props。
相应地,还有使用 v-bind 或缩写 : 来进行动态绑定的 props:
<template>
<div>
<!-- 根据一个变量的值动态传入 -->
<custom-button title="点赞数" :likes="likes"/>
<br>
<!-- 根据一个更复杂表达式的值动态传入 -->
<custom-button :title="userName+`点赞数`" :likes="likes+10"/>
</div>
</template>
<script setup>
import {ref} from "vue";
const userName = ref('该用户获得')
const likes = ref(10)
</script>
传递不同的值类型
Number
<!-- 虽然 `42` 是个常量,我们还是需要使用 v-bind -->
<!-- 因为这是一个 JavaScript 表达式而不是一个字符串 -->
<custom-button :likes="42" />
<!-- 根据一个变量的值动态传入 -->
<custom-button :likes="post.likes" />
Boolean
<!-- 仅写上 prop 但不传值,会隐式转换为 `true` -->
<custom-button is-liked />
<!-- 虽然 `false` 是静态的值,我们还是需要使用 v-bind -->
<!-- 因为这是一个 JavaScript 表达式而不是一个字符串 -->
<custom-button :is-liked="false" />
<!-- 根据一个变量的值动态传入 -->
<custom-button :is-liked="post.isPublished" />
Array
<!-- 虽然这个数组是个常量,我们还是需要使用 v-bind -->
<!-- 因为这是一个 JavaScript 表达式而不是一个字符串 -->
<custom-button :comment-ids="[234, 266, 273]" />
<!-- 根据一个变量的值动态传入 -->
<custom-button :comment-ids="post.commentIds" />
Object
<!-- 虽然这个对象字面量是个常量,我们还是需要使用 v-bind -->
<!-- 因为这是一个 JavaScript 表达式而不是一个字符串 -->
<custom-button
:author="{
name: 'Veronica',
company: 'Veridian Dynamics'
}"
/>
<!-- 根据一个变量的值动态传入 -->
<custom-button :author="post.author" />
Prop
校验
要声明对 props
的校验,你可以向 defineProps()
宏提供一个带有 props
校验选项的对象,比如
defineProps({
// 基础类型检查
// (给出 `null` 和 `undefined` 值则会跳过任何类型检查)
likes: Number,
title: String,
// 多种可能类型
propB: [String,Number],
// 必传,且为 String 类型
propC: {
type: String,
required: true
},
// Number 类型的默认值
propD: {
type: Number,
default: 100
},
// 对象类型的默认值
propE: {
type: Object,
// 对象或数组的默认值
// 必须从一个工厂函数返回。
// 该函数接收组件所接收到的原始 prop 作为参数。
default(rawProps) {
return { message: 'hello' }
}
},
// 自定义类型校验函数
propF: {
validator(value) {
// The value must match one of these strings
return ['success', 'warning', 'danger'].includes(value)
}
},
// 函数类型的默认值
propG: {
type: Function,
// 不像对象或数组的默认,这不是一个工厂函数。这会是一个用来作为默认值的函数
default() {
return 'Default function'
}
}
})
触发与监听事件
在组件的模板表达式中,可以直接使用 $emit
方法触发自定义事件 (例如:在 v-on 的处理函数中):
<template>
<button @click="$emit('addEvent')">{{title}}:{{likes}}</button>
</template>
<script setup>
import {defineProps} from "vue";
const props = defineProps({
likes: Number,
title: String,
})
console.log(props)
</script>
父组件可以通过 v-on
(缩写为 @
) 来监听事件:
<template>
<div>
<custom-button @addEvent="callback" title="点赞数" :likes="likes"></custom-button>
</div>
</template>
<script setup>
import {ref} from "vue";
import CustomButton from "@/components/customButton";
const likes = ref(10)
const callback = () => {
likes.value++
}
</script>
组件的事件监听器也支持.once
修饰符:
<custom-button @addEvent.once="callback" title="点赞数" :likes="likes"></custom-button>
事件参数
给 $emit
提供一个额外的参数:
<button @click="$emit('addEvent',2)">{{title}}:{{likes}}</button>
父组件
<template>
<div>
<custom-button @addEvent="callback" title="点赞数" :likes="likes"></custom-button>
</div>
</template>
<script setup>
import {ref} from "vue";
import CustomButton from "@/components/customButton";
const likes = ref(10)
const callback = (n) => {
likes.value += n
}
</script>
所有传入 $emit()
的额外参数都会被直接传向监听器。举例来说,$emit('foo', 1, 2, 3)
触发后,监听器函数将会收到这三个参数值。
声明触发的事件
组件可以显式地通过 defineEmits()
宏来声明它要触发的事件:
<template>
<button @click="addClick">{{title}}:{{likes}}</button>
</template>
<script setup>
import {defineProps,defineEmits} from "vue";
defineProps({
likes: Number,
title: String,
})
const emit = defineEmits(['addEvent'])
const addClick = () => {
// emit('addEvent')
emit('addEvent',2)//传参数
}
</script>
如果你显式地使用了 setup
函数而不是 <script setup>
,则事件需要通过 emits
选项来定义,emit
函数也被暴露在 setup()
的上下文对象上:
<template>
<button @click="addClick">{{title}}:{{likes}}</button>
</template>
<script>
export default {
props: {
title: String,
likes: Number
},
emits: ['addEvent'],
setup(props,ctx) {
const addClick = () => {
// ctx.emit('addEvent')
ctx.emit('addEvent',2)
}
return {
addClick
}
}
}
</script>
事件校验
要为事件添加校验,那么事件可以被赋值为一个函数,接受的参数就是抛出事件时传入 emit
的内容,返回一个布尔值来表明事件是否合法。
比如:
<script setup>
const emit = defineEmits({
// 没有校验
click: null,
// 校验 submit 事件
submit: ({ email, password }) => {
if (email && password) {
return true
} else {
console.warn('Invalid submit event payload!')
return false
}
}
})
function submitForm(email, password) {
emit('submit', { email, password })
}
</script>
组件 v-model
v-model
可以在组件上使用以实现双向绑定。自定一个input组件customInput
。
-
将内部原生 <input>
元素的 value 属性 绑定到modelValue
prop。 -
当原生的 input
事件触发时,触发一个携带了新值的update:modelValue
自定义事件。
<template>
<input :value="modelValue" @input="$emit('update:modelValue',$event.target.value)">
</template>
<script setup>
import {defineProps,defineEmits} from "vue";
defineProps(['modelValue'])
defineEmits(['update:modelValue'])
</script>
现在 v-model
可以在这个组件上正常工作了:
<custom-input v-model="searchText"></custom-input>
另一种在组件内实现 v-model
的方式是使用一个可写的,同时具有 getter
和 setter
的 computed
属性。get
方法需返回 modelValue
prop,而 set
方法需触发相应的事件:
<template>
<!-- <input :value="modelValue" @input="$emit('update:modelValue',$event.target.value)">-->
<input v-model="value" />
</template>
<script setup>
import {defineProps, defineEmits, computed} from "vue";
// defineProps(['modelValue'])
// defineEmits(['update:modelValue'])
const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])
const value = computed({
get() {
return props.modelValue
},
set(value) {
emit('update:modelValue',value)
}
})
</script>
v-model
的参数
默认情况下,v-model
在组件上都是使用 modelValue
作为 prop,并以 update:modelValue
作为对应的事件。我们可以通过给v-model
指定一个参数来更改这些名字。
子组件应声明一个 title
prop,并通过触发 update:title
事件更新父组件值:
<template>
<input :value="title" @input="$emit('update:title',$event.target.value)">
</template>
<script setup>
import {defineProps,defineEmits} from "vue";
defineProps(['title'])
defineEmits(['update:title'])
</script>
父组件
<custom-input @input="input" v-model:title="searchText"></custom-input>
多个 v-model
绑定
在组件实例上创建多个 v-model
双向绑定。 创建一个UserName
组件
<template>
<input type="text" :value="firstName"
@input="$emit('update:firstName',$event.target.value)">
<input type="text" :value="lastName"
@input="$emit('update:lastName',$event.target.value)">
</template>
<script setup>
import {defineProps,defineEmits} from "vue";
defineProps({
firstName: String,
lastName: String
})
defineEmits(['update:firstName','update:lastName'])
</script>
父组件
...
<user-name :first-name="firstName" :last-name="lastName"></user-name>
...
<script setup>
import {ref} from "vue";
const firstName = ref('Lo')
const lastName = ref('Hedy')
</script>
处理 v-model
修饰符
v-model
有一些内置的修饰符,例如 .trim
,.number
和 .lazy
。在某些场景下,你可能想要一个自定义组件的 v-model
支持自定义的修饰符。
我们来创建一个自定义的修饰符 capitalize
,它会自动将 v-model
绑定输入的字符串值第一个字母转为大写:
<MyComponent v-model.capitalize="myText" />
组件的 v-model
上所添加的修饰符,可以通过 modelModifiers
prop 在组件内访问到。在下面的组件中,我们声明了 modelModifiers 这个 prop,它的默认值是一个空对象:
<script setup>
const props = defineProps({
modelValue: String,
modelModifiers: { default: () => ({}) }
})
defineEmits(['update:modelValue'])
console.log(props.modelModifiers) // { capitalize: true }
</script>
<template>
<input
type="text"
:value="modelValue"
@input="$emit('update:modelValue', $event.target.value)"
/>
</template>
注意这里组件的 modelModifiers
prop 包含了 capitalize
且其值为 true,因为它在模板中的 v-model
绑定 v-model.capitalize="myText"
上被使用了。
有了这个 prop
,我们就可以检查 modelModifiers
对象的键,并编写一个处理函数来改变抛出的值。在下面的代码里,我们就是在每次 <input />
元素触发 input
事件时将值的首字母大写:
<script setup>
const props = defineProps({
modelValue: String,
modelModifiers: { default: () => ({}) }
})
const emit = defineEmits(['update:modelValue'])
function emitValue(e) {
let value = e.target.value
if (props.modelModifiers.capitalize) {
value = value.charAt(0).toUpperCase() + value.slice(1)
}
emit('update:modelValue', value)
}
</script>
<template>
<input type="text" :value="modelValue" @input="emitValue" />
</template>
对于又有参数又有修饰符的 v-model
绑定,生成的 prop
名将是 arg + “Modifiers”。举例来说:
<MyComponent v-model:title.capitalize="myText">
相应的声明应该是:
const props = defineProps(['title', 'titleModifiers'])
defineEmits(['update:title'])
console.log(props.titleModifiers) // { capitalize: true }
观看更多相关文章请关注公众号:

原文始发于微信公众号(大前端编程教学):自定义组件(一)
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/224278.html