文章目录
- 基础用法 modelValue
- 尺寸 size
- 文字描述与自定义图标
- 扩展的 value 类型
- isControlled 是否受控
- actualValue 实际值
- checked 开关状态
- 禁用状态、加载状态
- 阻止切换 beforeChange
- 切换 handleChange
看源码时候做的笔记。
Switch 开关 | Element Plus (element-plus.org)
基础用法 modelValue
在vue3中,
v-model
和modelValue
绑定的是同一个值。v-model
是一个语法糖,等同于同时使用:modelValue
和@update:modalValue
,即向子组件传递一个名为modelValue
的prop,同时监听一个名为update:modelValue
的事件。
基础用法:绑定 v-model
到一个 Boolean 类型的变量。 会传到prop的modelValue中。
modelValue: {
type: [Boolean, String, Number],
default: false,
},
尺寸 size
会传到prop的size中,是枚举值。
文字描述与自定义图标
- active-text
- inactive-text
- inline-prompt
分别表示开关文案、控制文本是否显示在点内。源码中是驼峰式的,主要通过条件渲染实现。
- inactive-icon
- active-icon
开关icon。条件渲染实现。
扩展的 value 类型
- activeValue
- inactiveValue
绑定的开/关的对应值,类型可以为:Boolean, String, Number。
/**
* @description switch value when in `on` state
*/
activeValue: {
type: [Boolean, String, Number],
default: true,
},
/**
* @description switch value when in `off` state
*/
inactiveValue: {
type: [Boolean, String, Number],
default: false,
},
switch开关底层是一个vue的input checkbox
控件:
<input
:id="inputId"
ref="input"
:class="ns.e('input')"
type="checkbox"
role="switch"
:aria-checked="checked"
:aria-disabled="switchDisabled"
:aria-label="label || ariaLabel"
:name="name"
:true-value="activeValue"
:false-value="inactiveValue"
:disabled="switchDisabled"
:tabindex="tabindex"
@change="handleChange"
@keydown.enter="switchValue"
/>
aria表示无障碍。
用true-value
的activeValue
和 false-value
的inactiveValue
替换 选中的true 和 未选中的false。
:true-value="activeValue"
:false-value="inactiveValue"
表单输入绑定 | Vue.js (vuejs.org)
因此传入的activeValue
和inactiveValue
属性就为switch开/关的值。
isControlled 是否受控
代码中定义了一个变量isControlled
表示是否受控。
const isControlled = ref(props.modelValue !== false)
定义类型时定义的modelValue
默认为false,若此时modelValue
不为false,说明switch组件是受控的,isControlled
为true,switch组件显示的值为传入值。
监听绑定的modelValue
属性,由于监听的是属性,因此要传入一个getter
。若modelValue发生变化,也说明此switch组件是受控的。
watch(
() => props.modelValue,
() => {
isControlled.value = true
}
)
actualValue 实际值
代码定义了一个变量actualValue
表示实际值。
若switch是受控组件,则它的实际值为绑定的modelValue
,否则为false。
若实际值actualValue
与绑定为开的值activeValue
相同,说明switch状态为开(checked=true)
const actualValue = computed(() => {
return isControlled.value ? props.modelValue : false
})
const checked = computed(() => actualValue.value === props.activeValue)
到这里,actualValue
的值可能是modelValue
或false,可能出现不是activeValue / inactiveValue
的情况(若不受控,则actualValue为false)。下面代码的目的是确保actualValue的值为activeValue / inactiveValue 。向父组件触发事件,入参为inactiveValue
if (![props.activeValue, props.inactiveValue].includes(actualValue.value)) {
emit(UPDATE_MODEL_EVENT, props.inactiveValue)
emit(CHANGE_EVENT, props.inactiveValue)
emit(INPUT_EVENT, props.inactiveValue)
}
这三个事件分别对应:
export const UPDATE_MODEL_EVENT = 'update:modelValue'
export const CHANGE_EVENT = 'change'
export const INPUT_EVENT = 'input'
checked 开关状态
代码定义了一个变量checked
表示开关状态。
const checked = computed(() => actualValue.value === props.activeValue)
参与控制前文所说的文字描述的显示与否。监听checked的变化,同步到input控件。
validateEvent
即文档中的 validate-event
属性,表示是否触发表单验证。如果需要触发的话,就触发。
watch(checked, (val) => {
input.value!.checked = val
if (props.validateEvent) {
formItem?.validate?.('change').catch((err) => debugWarn(err))
}
})
禁用状态、加载状态
定义了表单的禁用状态。若switchDisabled.value
为true,则切换状态的方法switchValue
会直接return。
const switchDisabled = useFormDisabled(computed(() => props.loading))
加载状态直接用条件渲染,不赘述。
阻止切换 beforeChange
设置beforeChange属性,若返回 false 或者返回 Promise 且被 reject,则停止切换。
若设置了beforeChange,则在触发切换switch前要先执行beforeChange。
在props中解构出beforeChange
。若没有设置beforeChange,则直接切换。
beforeChange是一个返回Promise<boolean> | boolean>
的方法。若它通过类型校验isPromiseOrBool
,则执行此方法。若promise.resolve的值能通过if判断,则执行切换:handleChange
。
const switchValue = () => {
if (switchDisabled.value) return
const { beforeChange } = props
if (!beforeChange) {
handleChange()
return
}
const shouldChange = beforeChange()
const isPromiseOrBool = [
isPromise(shouldChange),
isBoolean(shouldChange),
].includes(true)
if (!isPromiseOrBool) {
throwError(
COMPONENT_NAME,
'beforeChange must return type `Promise<boolean>` or `boolean`'
)
}
if (isPromise(shouldChange)) {
shouldChange
.then((result) => {
if (result) {
handleChange()
}
})
.catch((e) => {
debugWarn(COMPONENT_NAME, `some error occurred: ${e}`)
})
} else if (shouldChange) {
handleChange()
}
}
beforeChange: {
type: definePropType<() => Promise<boolean> | boolean>(Function),
},
文档的例子非常清晰:
<template>
<el-switch
v-model="value1"
:loading="loading1"
:before-change="beforeChange1"
/>
<el-switch
v-model="value2"
class="ml-2"
:loading="loading2"
:before-change="beforeChange2"
/>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import { ElMessage } from 'element-plus'
const value1 = ref(false)
const value2 = ref(false)
const loading1 = ref(false)
const loading2 = ref(false)
const beforeChange1 = () => {
loading1.value = true
return new Promise((resolve) => {
setTimeout(() => {
loading1.value = false
ElMessage.success('Switch success')
return resolve(true)
}, 1000)
})
}
const beforeChange2 = () => {
loading2.value = true
return new Promise((_, reject) => {
setTimeout(() => {
loading2.value = false
ElMessage.error('Switch failed')
return reject(new Error('Error'))
}, 1000)
})
}
</script>
切换 handleChange
val 是inactiveValue / activeValue 。在下一个 DOM 更新周期更新 input.value.checked 的值
const handleChange = () => {
const val = checked.value ? props.inactiveValue : props.activeValue
// 向外暴露一个input事件,参数为val
emit(UPDATE_MODEL_EVENT, val)
emit(CHANGE_EVENT, val)
emit(INPUT_EVENT, val)
// 在下一个 DOM 更新周期更新 input.value.checked 的值
nextTick(() => {
input.value!.checked = checked.value
})
}
emit(CHANGE_EVENT, val)
即文档中对外暴露的change事件。
其他内容,如switchKls
这种使用ns
生成动态的类名等,在之前的系列里提到过,不再赘述。