这是我参与11月更文挑战的第28天,活动详情查看:2021最后一次更文挑战
背景
由于个人的项目需要用的 Switch 组件,又想保持项目的轻量级引用,放弃了组件库的选择,选择了自己手撸一个组件。同时也给暂时还不会在vue3写组件的同学当个模版参考。
环境: vue3 + typescript
最终结果:
结构
其实我们可以看到样式结构其实非常简单,就是一个div里面包了一个小球。
于是我们可以很简单的写出 html
模版
<div
class="switch border-$default"
rounded-full
overflow-hidden
border
relative
>
<div absolute rounded-full class="switch__button bg-[#fff]"></div>
</div>
外层即为我们的容器,里面的div即为我们的小球 button。
我们在给它 给上我们的样式
.switch {
width: v-bind(_width);
height: v-bind(_height);
transition: background-color 0.3s cubic-bezier(0.4, 0, 0.2, 1),
box-shadow 0.3s cubic-bezier(0.4, 0, 0.2, 1);
background-color: v-bind(_bgColor);
&:focus {
box-shadow: 0 0 0 2px rgba(24, 160, 88, 0.2);
}
&.active {
background-color: v-bind(_bgActiveColor);
.switch__button {
left: calc(100% - $dotWidth - 1px);
}
}
&__button {
left: 1px;
top: 1px;
width: $dotWidth;
height: $dotHeight;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
}
为了保证组件样式可配置的最大化 其实可以看到,我组件样式的主题其实是痛过外部变量控制的。 于是我进行样式的属性定义。
$dotWidth: calc(v-bind(_height) - 4px);
$dotHeight: calc(v-bind(_height) - 4px);
props
组件最大的可配置性,还是在于组件对外扩展的属性是什么样。
为了保证高自由度,我提供了一下熟悉对外扩展
const props = withDefaults(
defineProps<{
size?: 'small' | 'medium' | 'large'
value?: string | number | boolean
loading?: boolean
defaultValue?: string | number | boolean
disabled?: boolean
bgColor?: string
bgActiveColor?: string
}>(),
{
size: 'medium',
defaultValue: false,
disabled: false,
bgColor: 'rgba(0, 0, 0, 0.14)',
bgActiveColor: '#60bc95'
}
)
然后将我的属性样式可以传到css
中供样式使用(在css中通过v-bind
使用)
于是就有了上文出现的变量。
const { _width, _height } = useSize(props.size)
const _bgColor = computed(() => props.bgColor)
const _bgActiveColor = computed(() => props.bgActiveColor)
v-model
在组件中,我们需要对外暴露我们的状态值供外部使用。
为了代码简单,推荐大家使用Vueuse
这个库,里面集成了很多种实用的工具方法。
于是我们有:
const emits = defineEmits<{
(event: 'change', status: boolean): void
(event: 'update:value', status: boolean): void
}>()
const status = useVModel(props, 'value', emits)
在vue3 的 setup 环境中,我们需要通过defineEmits
api 提供组件对外暴露的属性和方法。
例如上面,我就向外抛出了一个 v-model
和change
事件。
我们在对组件的禁用规则作出调整。当时disable
为 true 时,我们需要切换禁用的样式和进行状态值的更改。
于是我们可以在点击事件中作出判断
const handleClick = () => {
if (!props.disabled) {
status.value = !status.value
emits('change', status.value)
}
}
然后再对html
模版进行优化。
<template>
<div
class="switch border-$default"
:class="[disabled && 'op-50', status && 'active']"
rounded-full
overflow-hidden
border
relative
@click="handleClick"
>
<div absolute rounded-full class="switch__button bg-[#fff]"></div>
</div>
</template>
这样我们就大功告成了。
尝试引入
const testRef = ref(true)
<Switch v-model:value="testRef" size="small" />
<p>{{ testRef }}</p>
结果:
这样就完美实现了。
具体实现可以参考我的项目:具体源码