如何设计一个组件
需求分析
布局
- content
- left-icon
- body
- input-control
- right-icon
- action
功能
使用 defineEmits 定义组件的事件
在组件的script setup 里如何定义事件
- 使用defineEmits()定义
- 先声明事件接口
<script setup lang="ts"> interface IProps { showAction?: boolean background?: string placeholder?: string shape?: string modelValue?: string | number } const props = defineProps<IProps>() // 声明事件接口 interface IEmits { (e: 'search', v?: string | number): void (e: 'cancel'): void (e: 'clear'): void (e: 'update:modelValue', v?: string | number): void } // 定义事件变量 const emits = defineEmits<IEmits>() const onKeyPress = (e: KeyboardEvent) => { const ENTER_CODE = 13 if (ENTER_CODE === e.keyCode) { e.preventDefault() //使用定义事件里声明事件接口中的某个事件 emits('search', props.modelValue) } } const onClear = () => { //使用定义事件里声明事件接口中的某个事件 emits('clear') //使用定义事件里声明事件接口中的某个事件 emits('update:modelValue', '') } </script> <template> <div class="op-search" :class="{ 'op-search--show-action': showAction }" :style="{ background }"> <div class="op-search__content" :class="shape ? `op-search__content--${shape}` : ''"> <div class="op-cell op-search__field"> <div class="op-field__left-icon"> <VanIcon name="search" /> </div> <div class="op-cell__value"> <div class="op-field__body"> <input type="search" class="op-field__control" :value="modelValue" :placeholder="placeholder" @keypress="onKeyPress" //使用定义事件里声明事件接口中的某个事件, 以及传递input输入框里的值 @input="(e) => emits('update:modelValue', (e.target as HTMLInputElement).value)" /> <div v-if="$slots['right-icon']" class="op-field__right-icon"> <slot name="right-icon"></slot> </div> <VanIcon v-else-if="modelValue" name="clear" class="op-field__clear-icon" //使用定义的函数事件,触发里面的定义事件里声明事件接口中的某个事件 @click="onClear" /> </div> </div> </div> </div> <div v-if="showAction" class="op-search__action"> <slot name="action"> //定义事件里声明事件接口中的某个事件 <div @click="emits('cancel')">取消</div> </slot> </div> </div> </template>
如何定义组件的 v-model
在组件的script setup 里如何定义 v-model 参数
- 声明接口属性,定义v-model双向绑定的变量参数。
- 使用定义的v-model双向绑定的变量参数
<script setup lang="ts"> //声明变量参数的接口属性 interface IProps { showAction?: boolean background?: string placeholder?: string shape?: string modelValue?: string | number } // 定义v-model双向绑定使用的变量参数 const props = defineProps<IProps>() interface IEmits { (e: 'search', v?: string | number): void (e: 'cancel'): void (e: 'clear'): void (e: 'update:modelValue', v?: string | number): void } const emits = defineEmits<IEmits>() const onKeyPress = (e: KeyboardEvent) => { const ENTER_CODE = 13 if (ENTER_CODE === e.keyCode) { e.preventDefault() //在script setup中使用定义v-model双向绑定使用的变量参数 emits('search', props.modelValue) } } const onClear = () => { emits('clear') emits('update:modelValue', '') } </script> <template> //在template中使用定义v-model双向绑定使用的变量参数 showAction background shape <div class="op-search" :class="{ 'op-search--show-action': showAction }" :style="{ background }"> <div class="op-search__content" :class="shape ? `op-search__content--${shape}` : ''"> <div class="op-cell op-search__field"> <div class="op-field__left-icon"> <VanIcon name="search" /> </div> <div class="op-cell__value"> <div class="op-field__body"> //在template中使用定义v-model双向绑定使用的变量参数 modelValue placeholder <input type="search" class="op-field__control" :value="modelValue" :placeholder="placeholder" @keypress="onKeyPress" @input="(e) => emits('update:modelValue', (e.target as HTMLInputElement).value)" /> <div v-if="$slots['right-icon']" class="op-field__right-icon"> <slot name="right-icon"></slot> </div> //在template中使用定义v-model双向绑定使用的变量参数 modelValue <VanIcon v-else-if="modelValue" name="clear" class="op-field__clear-icon" @click="onClear" /> </div> </div> </div> </div> //在template中使用定义v-model双向绑定使用的变量参数 showAction <div v-if="showAction" class="op-search__action"> <slot name="action"> <div @click="emits('cancel')">取消</div> </slot> </div> </div> </template>
是的
如何使用 CSS 变量
定义css变量,使用var(--van-padding-xs)格式来书写vue3的css变量,可以在script setup中进行自定义css变量值
- 在组件中使用css变量
<template> <div class="op-search" :class="{ 'op-search--show-action': showAction }" :style="{ background }"> <div class="op-search__content" :class="shape ? `op-search__content--${shape}` : ''"> <div class="op-cell op-search__field"> <div class="op-field__left-icon"> <VanIcon name="search" /> </div> <div class="op-cell__value"> <div class="op-field__body"> <input type="search" class="op-field__control" :value="modelValue" :placeholder="placeholder" @keypress="onKeyPress" @input="(e) => emits('update:modelValue', (e.target as HTMLInputElement).value)" /> <div v-if="$slots['right-icon']" class="op-field__right-icon"> <slot name="right-icon"></slot> </div> <VanIcon v-else-if="modelValue" name="clear" class="op-field__clear-icon" @click="onClear" /> </div> </div> </div> </div> <div v-if="showAction" class="op-search__action"> <slot name="action"> <div @click="emits('cancel')">取消</div> </slot> </div> </div> </template> <style lang="scss"> :root { // 定义css变量var(变量格式),可以让引用组件的父组件自定义css值 --op-search-padding: 10px var(--van-padding-sm); --op-search-background-color: var(--van-background-color-light); --op-search-content-background: var(--van-gray-1); --op-search-left-icon-color: var(--van-gray-6); --op-search-action-padding: 0 var(--van-padding-xs); --op-search-action-text-color: var(--van-text-color); --op-search-action-font-size: var(--van-font-size-md); --op-search-input-height: 34px; } .op-search { display: flex; align-items: center; box-sizing: border-box; padding: var(--op-search-padding); background: var(--op-search-background-color); &--show-action { padding-right: 0; } &__content { display: flex; flex: 1; padding-left: var(--van-padding-sm); background: var(--op-search-content-background); border-radius: var(--van-border-radius-sm); &--round { border-radius: var(--van-radius-max); } } &__action { padding: var(--op-search-action-padding); color: var(--op-search-action-text-color); font-size: var(--op-search-action-font-size); line-height: var(--op-search-input-height); cursor: pointer; user-select: none; } &__field { flex: 1; padding: 5px var(--van-padding-xs) 5px 0; background-color: transparent; .op-field__left-icon { color: var(--op-search-left-icon-color); margin-right: var(--van-padding-base); .van-icon { font-size: var(--van-field-icon-size); } } } } .op-cell { display: flex; box-sizing: border-box; width: 100%; color: var(--van-cell-text-color); font-size: var(--van-cell-font-size); line-height: var(--van-cell-line-height); &__value { flex: 1; color: var(--van-cell-text-color); vertical-align: middle; word-wrap: break-word; } } .op-field { &__control { display: block; box-sizing: border-box; width: 100%; min-width: 0; margin: 0; padding: 0; border: 0; color: var(--van-field-input-text-color); line-height: inherit; text-align: left; background-color: transparent; resize: none; user-select: none; &::placeholder { color: var(--van-field-placeholder-text-color); } } &__body { display: flex; align-items: center; } &__right-icon { color: var(--van-field-right-icon-color); padding: 0 var(--van-padding-xs); line-height: inherit; flex-shrink: 0; } &__clear { color: var(--van-field-clear-icon-color); font-size: var(--van-field-clear-icon-size) !important; cursor: pointer; } } input { &::-webkit-search-decoration, &::-webkit-search-cancel-button, &::-webkit-search-results-button, &::-webkit-search-results-decoration { display: none; } } </style>
BEM 命名规范
定义
- Bem是块(block) 、元素(element) 、修饰符(modifier)的简写
- - 中划线:仅作为连字符使用,表示某个块或者某个子元素的多单词之间的连接记号
- __ 双下划线:双下划线用来连接块和块的子元素
- -- 双中划线:双中划线用来描述一个块或者块的子元素的一种状态