From 346393f81e596633c3de25a637b9a9c63f564a93 Mon Sep 17 00:00:00 2001 From: tangjinzhou <415800467@qq.com> Date: Tue, 28 Dec 2021 16:41:58 +0800 Subject: [PATCH] refactor: checkbox --- components/checkbox/Checkbox.tsx | 228 +++++++----------- components/checkbox/Group.tsx | 194 +++++++-------- .../__tests__/__snapshots__/demo.test.js.snap | 13 +- components/checkbox/__tests__/group.test.js | 5 +- components/checkbox/demo/check-all.vue | 4 +- components/checkbox/demo/group.vue | 4 +- components/checkbox/demo/layout.vue | 5 +- components/checkbox/index.ts | 7 +- components/checkbox/interface.ts | 78 ++++++ components/checkbox/style/index.less | 2 + .../checkbox/style/{index.ts => index.tsx} | 0 components/checkbox/style/mixin.less | 35 ++- components/checkbox/style/rtl.less | 28 +++ components/components.ts | 1 + components/style/themes/default.less | 2 + components/vc-checkbox/Checkbox.tsx | 2 +- 16 files changed, 334 insertions(+), 274 deletions(-) create mode 100644 components/checkbox/interface.ts rename components/checkbox/style/{index.ts => index.tsx} (100%) create mode 100644 components/checkbox/style/rtl.less diff --git a/components/checkbox/Checkbox.tsx b/components/checkbox/Checkbox.tsx index c1a9aea99..f08ce38f2 100644 --- a/components/checkbox/Checkbox.tsx +++ b/components/checkbox/Checkbox.tsx @@ -1,36 +1,15 @@ -import type { ExtractPropTypes } from 'vue'; -import { defineComponent, inject, nextTick } from 'vue'; -import PropTypes from '../_util/vue-types'; +import { watchEffect, onMounted, defineComponent, inject, onBeforeUnmount, ref } from 'vue'; import classNames from '../_util/classNames'; import VcCheckbox from '../vc-checkbox/Checkbox'; -import hasProp, { getOptionProps, getSlot } from '../_util/props-util'; -import { defaultConfigProvider } from '../config-provider'; +import { flattenChildren } from '../_util/props-util'; import warning from '../_util/warning'; import type { RadioChangeEvent } from '../radio/interface'; import type { EventHandler } from '../_util/EventInterface'; import { useInjectFormItemContext } from '../form/FormItemContext'; -function noop() {} +import useConfigInject from '../_util/hooks/useConfigInject'; -export const checkboxProps = () => { - return { - prefixCls: PropTypes.string, - defaultChecked: PropTypes.looseBool, - checked: PropTypes.looseBool, - disabled: PropTypes.looseBool, - isGroup: PropTypes.looseBool, - value: PropTypes.any, - name: PropTypes.string, - id: PropTypes.string, - indeterminate: PropTypes.looseBool, - type: PropTypes.string.def('checkbox'), - autofocus: PropTypes.looseBool, - onChange: PropTypes.func, - 'onUpdate:checked': PropTypes.func, - skipGroup: PropTypes.looseBool, - }; -}; - -export type CheckboxProps = Partial>>; +import type { CheckboxProps } from './interface'; +import { CheckboxGroupContextKey, checkboxProps } from './interface'; export default defineComponent({ name: 'ACheckbox', @@ -38,124 +17,91 @@ export default defineComponent({ __ANT_CHECKBOX: true, props: checkboxProps(), emits: ['change', 'update:checked'], - setup() { + setup(props, { emit, attrs, slots, expose }) { const formItemContext = useInjectFormItemContext(); - return { - formItemContext, - configProvider: inject('configProvider', defaultConfigProvider), - checkboxGroupContext: inject('checkboxGroupContext', undefined), - }; - }, + const { prefixCls, direction } = useConfigInject('checkbox', props); + const checkboxGroup = inject(CheckboxGroupContextKey, undefined); + const uniId = Symbol('checkboxUniId'); - watch: { - value(value, prevValue) { - if (this.skipGroup) { - return; + watchEffect(() => { + if (!props.skipGroup && checkboxGroup) { + checkboxGroup.registerValue(uniId, props.value); } - nextTick(() => { - const { checkboxGroupContext: checkboxGroup = {} } = this; - if (checkboxGroup.registerValue && checkboxGroup.cancelValue) { - checkboxGroup.cancelValue(prevValue); - checkboxGroup.registerValue(value); - } - }); - }, - }, - - mounted() { - const { value, checkboxGroupContext: checkboxGroup = {} } = this; - if (checkboxGroup.registerValue) { - checkboxGroup.registerValue(value); - } - - warning( - hasProp(this, 'checked') || this.checkboxGroupContext || !hasProp(this, 'value'), - 'Checkbox', - '`value` is not validate prop, do you mean `checked`?', - ); - }, - beforeUnmount() { - const { value, checkboxGroupContext: checkboxGroup = {} } = this; - if (checkboxGroup.cancelValue) { - checkboxGroup.cancelValue(value); - } - }, - methods: { - handleChange(event: RadioChangeEvent) { - const targetChecked = event.target.checked; - this.$emit('update:checked', targetChecked); - // this.$emit('input', targetChecked); - this.$emit('change', event); - }, - focus() { - (this.$refs.vcCheckbox as HTMLInputElement).focus(); - }, - blur() { - (this.$refs.vcCheckbox as HTMLInputElement).blur(); - }, - }, - - render() { - const props = getOptionProps(this); - const { checkboxGroupContext: checkboxGroup, $attrs } = this; - const children = getSlot(this); - const { - indeterminate, - prefixCls: customizePrefixCls, - skipGroup, - id = this.formItemContext.id.value, - ...restProps - } = props; - const getPrefixCls = this.configProvider.getPrefixCls; - const prefixCls = getPrefixCls('checkbox', customizePrefixCls); - const { - onMouseenter = noop, - onMouseleave = noop, - onInput, - class: className, - style, - ...restAttrs - } = $attrs; - const checkboxProps: any = { - ...restProps, - id, - prefixCls, - ...restAttrs, - }; - if (checkboxGroup && !skipGroup) { - checkboxProps.onChange = (...args) => { - this.$emit('change', ...args); - this.formItemContext.onFieldChange(); - checkboxGroup.toggleOption({ label: children, value: props.value }); - }; - checkboxProps.name = checkboxGroup.name; - checkboxProps.checked = checkboxGroup.sValue.indexOf(props.value) !== -1; - checkboxProps.disabled = props.disabled || checkboxGroup.disabled; - checkboxProps.indeterminate = indeterminate; - } else { - checkboxProps.onChange = this.handleChange; - } - const classString = classNames( - { - [`${prefixCls}-wrapper`]: true, - [`${prefixCls}-wrapper-checked`]: checkboxProps.checked, - [`${prefixCls}-wrapper-disabled`]: checkboxProps.disabled, - }, - className, - ); - const checkboxClass = classNames({ - [`${prefixCls}-indeterminate`]: indeterminate, }); - return ( - - ); + onBeforeUnmount(() => { + if (checkboxGroup) { + checkboxGroup.cancelValue(uniId); + } + }); + onMounted(() => { + warning( + props.checked !== undefined || checkboxGroup || props.value === undefined, + 'Checkbox', + '`value` is not validate prop, do you mean `checked`?', + ); + }); + + const handleChange = (event: RadioChangeEvent) => { + const targetChecked = event.target.checked; + emit('update:checked', targetChecked); + emit('change', event); + }; + const checkboxRef = ref(); + const focus = () => { + checkboxRef.value?.focus(); + }; + const blur = () => { + checkboxRef.value?.blur(); + }; + expose({ + focus, + blur, + }); + return () => { + const children = flattenChildren(slots.default?.()); + const { indeterminate, skipGroup, id = formItemContext.id.value, ...restProps } = props; + const { onMouseenter, onMouseleave, onInput, class: className, style, ...restAttrs } = attrs; + const checkboxProps: CheckboxProps = { + ...restProps, + id, + prefixCls: prefixCls.value, + ...restAttrs, + }; + if (checkboxGroup && !skipGroup) { + checkboxProps.onChange = (...args) => { + emit('change', ...args); + checkboxGroup.toggleOption({ label: children, value: props.value }); + }; + checkboxProps.name = checkboxGroup.name.value; + checkboxProps.checked = checkboxGroup.mergedValue.value.indexOf(props.value) !== -1; + checkboxProps.disabled = props.disabled || checkboxGroup.disabled.value; + checkboxProps.indeterminate = indeterminate; + } else { + checkboxProps.onChange = handleChange; + } + const classString = classNames( + { + [`${prefixCls.value}-wrapper`]: true, + [`${prefixCls.value}-rtl`]: direction.value === 'rtl', + [`${prefixCls.value}-wrapper-checked`]: checkboxProps.checked, + [`${prefixCls.value}-wrapper-disabled`]: checkboxProps.disabled, + }, + className, + ); + const checkboxClass = classNames({ + [`${prefixCls.value}-indeterminate`]: indeterminate, + }); + return ( + + ); + }; }, }); diff --git a/components/checkbox/Group.tsx b/components/checkbox/Group.tsx index 3ee1ecf33..c20e48334 100644 --- a/components/checkbox/Group.tsx +++ b/components/checkbox/Group.tsx @@ -1,134 +1,116 @@ -import type { PropType } from 'vue'; -import { defineComponent, inject, provide } from 'vue'; -import PropTypes from '../_util/vue-types'; +import { computed, ref, watch, defineComponent, provide } from 'vue'; import Checkbox from './Checkbox'; -import hasProp, { getSlot } from '../_util/props-util'; -import { defaultConfigProvider } from '../config-provider'; -import type { VueNode } from '../_util/type'; import { useInjectFormItemContext } from '../form/FormItemContext'; +import useConfigInject from '../_util/hooks/useConfigInject'; +import type { CheckboxOptionType } from './interface'; +import { CheckboxGroupContextKey, checkboxGroupProps } from './interface'; -export type CheckboxValueType = string | number | boolean; -export interface CheckboxOptionType { - label: VueNode; - value: CheckboxValueType; - disabled?: boolean; - indeterminate?: boolean; - onChange?: (e: Event) => void; -} -function noop() {} export default defineComponent({ name: 'ACheckboxGroup', - props: { - name: PropTypes.string, - prefixCls: PropTypes.string, - defaultValue: { type: Array as PropType> }, - value: { type: Array as PropType> }, - options: { type: Array as PropType> }, - disabled: PropTypes.looseBool, - onChange: PropTypes.func, - id: PropTypes.string, - }, + props: checkboxGroupProps(), emits: ['change', 'update:value'], - setup() { + setup(props, { slots, emit, expose }) { const formItemContext = useInjectFormItemContext(); - return { - formItemContext, - configProvider: inject('configProvider', defaultConfigProvider), - }; - }, - - data() { - const { value, defaultValue } = this; - return { - sValue: value || defaultValue || [], - registeredValues: [], - }; - }, - watch: { - value(val) { - this.sValue = val || []; - }, - }, - created() { - provide('checkboxGroupContext', this); - }, - methods: { - getOptions() { - const { options = [], $slots } = this; - return options.map(option => { + const { prefixCls, direction } = useConfigInject('checkbox', props); + const mergedValue = ref((props.value === undefined ? props.defaultValue : props.value) || []); + watch( + () => props.value, + () => { + mergedValue.value = props.value || []; + }, + ); + const options = computed(() => { + return props.options.map(option => { if (typeof option === 'string') { return { label: option, value: option, }; } - let label = option.label; - if (label === undefined && $slots.label) { - label = $slots.label(option); - } - return { ...option, label }; + return option; }); - }, - cancelValue(value: CheckboxValueType) { - this.registeredValues = this.registeredValues.filter(val => val !== value); - }, + }); + const triggerUpdate = ref(Symbol()); + const registeredValuesMap = ref>(new Map()); + const cancelValue = (id: Symbol) => { + registeredValuesMap.value.delete(id); + triggerUpdate.value = Symbol(); + }; + const registerValue = (id: Symbol, value: string) => { + registeredValuesMap.value.set(id, value); + triggerUpdate.value = Symbol(); + }; - registerValue(value: CheckboxValueType) { - this.registeredValues = [...this.registeredValues, value]; - }, - toggleOption(option: CheckboxOptionType) { - const { registeredValues } = this; - const optionIndex = this.sValue.indexOf(option.value); - const value = [...this.sValue]; + const registeredValues = ref(new Map()); + watch(triggerUpdate, () => { + const valuseMap = new Map(); + for (const value of registeredValuesMap.value.values()) { + valuseMap.set(value, true); + } + registeredValues.value = valuseMap; + }); + + const toggleOption = (option: CheckboxOptionType) => { + const optionIndex = mergedValue.value.indexOf(option.value); + const value = [...mergedValue.value]; if (optionIndex === -1) { value.push(option.value); } else { value.splice(optionIndex, 1); } - if (!hasProp(this, 'value')) { - this.sValue = value; + if (props.value === undefined) { + mergedValue.value = value; } - const options = this.getOptions(); const val = value - .filter(val => registeredValues.indexOf(val) !== -1) + .filter(val => registeredValues.value.has(val)) .sort((a, b) => { - const indexA = options.findIndex(opt => opt.value === a); - const indexB = options.findIndex(opt => opt.value === b); + const indexA = options.value.findIndex(opt => opt.value === a); + const indexB = options.value.findIndex(opt => opt.value === b); return indexA - indexB; }); - // this.$emit('input', val); - this.$emit('update:value', val); - this.$emit('change', val); - this.formItemContext.onFieldChange(); - }, - }, - render() { - const { $props: props, $data: state } = this; - const { prefixCls: customizePrefixCls, options, id = this.formItemContext.id.value } = props; - const getPrefixCls = this.configProvider.getPrefixCls; - const prefixCls = getPrefixCls('checkbox', customizePrefixCls); - let children = getSlot(this); - const groupPrefixCls = `${prefixCls}-group`; - if (options && options.length > 0) { - children = this.getOptions().map(option => ( - props.name), + disabled: computed(() => props.disabled), + }); + expose({ + mergedValue, + }); + return () => { + const { id = formItemContext.id.value } = props; + let children = null; + const groupPrefixCls = `${prefixCls.value}-group`; + if (options.value && options.value.length > 0) { + children = options.value.map(option => ( + + {option.label === undefined ? slots.label?.(option) : option.label} + + )); + } + return ( +
- {option.label} - - )); - } - return ( -
- {children} -
- ); + {children || slots.default?.()} +
+ ); + }; }, }); diff --git a/components/checkbox/__tests__/__snapshots__/demo.test.js.snap b/components/checkbox/__tests__/__snapshots__/demo.test.js.snap index 5ed2331a4..01ce052f8 100644 --- a/components/checkbox/__tests__/__snapshots__/demo.test.js.snap +++ b/components/checkbox/__tests__/__snapshots__/demo.test.js.snap @@ -3,8 +3,10 @@ exports[`renders ./components/checkbox/demo/basic.vue correctly 1`] = ``; exports[`renders ./components/checkbox/demo/check-all.vue correctly 1`] = ` -
-
+
+
`; @@ -22,7 +24,7 @@ exports[`renders ./components/checkbox/demo/disabled.vue correctly 1`] = `
-