From f3f2ee6f37f9c82ccdac915da01fcf6bd9fe8301 Mon Sep 17 00:00:00 2001 From: ajuner <106791576@qq.com> Date: Thu, 30 Sep 2021 14:13:21 +0800 Subject: [PATCH 1/5] refactor(radio): use composition api --- components/radio/Group.tsx | 212 +++++++++--------- components/radio/Radio.tsx | 121 +++++----- components/radio/RadioButton.tsx | 43 ++-- .../__tests__/__snapshots__/demo.test.js.snap | 9 +- .../__snapshots__/group.test.js.snap | 2 +- components/radio/demo/radioGroup-more.vue | 2 +- components/radio/demo/radioGroup-options.vue | 13 +- components/radio/index.en-US.md | 8 +- components/radio/index.zh-CN.md | 9 +- components/radio/interface.ts | 8 +- components/radio/style/index.less | 58 +++-- components/radio/style/rtl.less | 61 +++++ 12 files changed, 311 insertions(+), 235 deletions(-) create mode 100644 components/radio/style/rtl.less diff --git a/components/radio/Group.tsx b/components/radio/Group.tsx index e3d039f52d..c879c2d317 100644 --- a/components/radio/Group.tsx +++ b/components/radio/Group.tsx @@ -1,133 +1,133 @@ -import { provide, inject, nextTick, defineComponent } from 'vue'; +import { provide, nextTick, defineComponent, ref, watch, onBeforeMount } from 'vue'; +import type { PropType, ExtractPropTypes } from 'vue'; import classNames from '../_util/classNames'; import PropTypes from '../_util/vue-types'; import Radio from './Radio'; -import { getOptionProps, filterEmpty, hasProp, getSlot } from '../_util/props-util'; -import { defaultConfigProvider } from '../config-provider'; +import useConfigInject from '../_util/hooks/useConfigInject'; import { tuple } from '../_util/type'; import type { RadioChangeEvent } from './interface'; import { useInjectFormItemContext } from '../form/FormItemContext'; +const RadioGroupSizeTypes = tuple('large', 'default', 'small'); + +export type RadioGroupSize = typeof RadioGroupSizeTypes[number]; + +const RadioGroupOptionTypes = tuple('default', 'button'); + +export type RadioGroupOption = typeof RadioGroupOptionTypes[number]; + +export type RadioGroupChildOption = { + label: string; + value: string; + disabled?: boolean; +}; + +const radioGroupProps = { + prefixCls: PropTypes.string, + value: PropTypes.any, + size: PropTypes.oneOf(RadioGroupSizeTypes).def('default'), + options: { + type: Array as PropType>, + }, + disabled: PropTypes.looseBool, + name: PropTypes.string, + buttonStyle: PropTypes.string.def('outline'), + id: PropTypes.string, + optionType: PropTypes.oneOf(RadioGroupOptionTypes).def('default'), +}; + +export type RadioGroupProps = Partial>; + export default defineComponent({ name: 'ARadioGroup', - props: { - prefixCls: PropTypes.string, - defaultValue: PropTypes.any, - value: PropTypes.any, - size: PropTypes.oneOf(tuple('large', 'default', 'small')).def('default'), - options: PropTypes.array, - disabled: PropTypes.looseBool, - name: PropTypes.string, - buttonStyle: PropTypes.string.def('outline'), - onChange: PropTypes.func, - id: PropTypes.string, - }, + props: radioGroupProps, emits: ['update:value', 'change'], - setup() { + setup(props, { slots, emit }) { const formItemContext = useInjectFormItemContext(); - return { - formItemContext, - updatingValue: false, - configProvider: inject('configProvider', defaultConfigProvider), - radioGroupContext: null, - }; - }, - data() { - const { value, defaultValue } = this; - return { - stateValue: value === undefined ? defaultValue : value, - }; - }, - watch: { - value(val) { - this.updatingValue = false; - this.stateValue = val; - }, - }, - // computed: { - // radioOptions() { - // const { disabled } = this; - // return this.options.map(option => { - // return typeof option === 'string' - // ? { label: option, value: option } - // : { ...option, disabled: option.disabled === undefined ? disabled : option.disabled }; - // }); - // }, - // }, - created() { - this.radioGroupContext = provide('radioGroupContext', this); - }, - methods: { - onRadioChange(ev: RadioChangeEvent) { - const lastValue = this.stateValue; + const { prefixCls } = useConfigInject('radio', props); + const stateValue = ref(props.value); + const updatingValue = ref(false); + watch( + () => props.value, + val => { + stateValue.value = val; + updatingValue.value = false; + }, + ); + + onBeforeMount(() => { + provide('radioGroupContext', { + onRadioChange, + stateValue: stateValue, + props, + }); + }); + + const onRadioChange = (ev: RadioChangeEvent) => { + const lastValue = stateValue.value; const { value } = ev.target; - if (!hasProp(this, 'value')) { - this.stateValue = value; + + if (!('value' in props)) { + stateValue.value = value; } // nextTick for https://github.com/vueComponent/ant-design-vue/issues/1280 - if (!this.updatingValue && value !== lastValue) { - this.updatingValue = true; - this.$emit('update:value', value); - this.$emit('change', ev); - this.formItemContext.onFieldChange(); + if (!updatingValue.value && value !== lastValue) { + updatingValue.value = true; + emit('update:value', value); + emit('change', ev); + formItemContext.onFieldChange(); } nextTick(() => { - this.updatingValue = false; + updatingValue.value = false; }); - }, - }, - render() { - const props = getOptionProps(this); - const { - prefixCls: customizePrefixCls, - options, - buttonStyle, - id = this.formItemContext.id.value, - } = props; - const { getPrefixCls } = this.configProvider; - const prefixCls = getPrefixCls('radio', customizePrefixCls); + }; - const groupPrefixCls = `${prefixCls}-group`; - const classString = classNames(groupPrefixCls, `${groupPrefixCls}-${buttonStyle}`, { - [`${groupPrefixCls}-${props.size}`]: props.size, - }); + return () => { + const { options, optionType, buttonStyle, id = formItemContext.id.value } = props; - let children = filterEmpty(getSlot(this)); + const groupPrefixCls = `${prefixCls.value}-group`; - // 如果存在 options, 优先使用 - if (options && options.length > 0) { - children = options.map(option => { - if (typeof option === 'string') { + const classString = classNames(groupPrefixCls, `${groupPrefixCls}-${buttonStyle}`, { + [`${groupPrefixCls}-${props.size}`]: props.size, + }); + + let children = slots.default?.(); + if (options && options.length > 0) { + const optionsPrefixCls = + optionType === 'button' ? `${prefixCls.value}-button` : prefixCls.value; + children = options.map(option => { + if (typeof option === 'string') { + return ( + + {option} + + ); + } + const { value, disabled, label } = option as RadioGroupChildOption; return ( - {option} + {label} ); - } - return ( - - {option.label} - - ); - }); - } - - return ( -
- {children} -
- ); + }); + } + return ( +
+ {children} +
+ ); + }; }, }); diff --git a/components/radio/Radio.tsx b/components/radio/Radio.tsx index e3908a5b6b..b948556eea 100644 --- a/components/radio/Radio.tsx +++ b/components/radio/Radio.tsx @@ -1,16 +1,14 @@ import type { ExtractPropTypes } from 'vue'; -import { defineComponent, inject } from 'vue'; +import { defineComponent, inject, ref } from 'vue'; import PropTypes from '../_util/vue-types'; import VcCheckbox from '../vc-checkbox'; import classNames from '../_util/classNames'; -import { getOptionProps } from '../_util/props-util'; -import { defaultConfigProvider } from '../config-provider'; -import type { RadioChangeEvent } from './interface'; +import useConfigInject from '../_util/hooks/useConfigInject'; +import type { RadioChangeEvent, RadioGroupContext } from './interface'; import { useInjectFormItemContext } from '../form/FormItemContext'; export const radioProps = { prefixCls: PropTypes.string, - defaultChecked: PropTypes.looseBool, checked: PropTypes.looseBool, disabled: PropTypes.looseBool, isGroup: PropTypes.looseBool, @@ -30,72 +28,67 @@ export default defineComponent({ name: 'ARadio', props: radioProps, emits: ['update:checked', 'update:value', 'change', 'blur', 'focus'], - setup() { + setup(props, { emit, expose, slots }) { const formItemContext = useInjectFormItemContext(); - return { - configProvider: inject('configProvider', defaultConfigProvider), - radioGroupContext: inject('radioGroupContext', null), - formItemContext, + const vcCheckbox = ref(); + const radioGroupContext = inject('radioGroupContext'); + const { prefixCls } = useConfigInject('radio', props); + + const focus = () => { + vcCheckbox.value.focus(); }; - }, - methods: { - focus() { - (this.$refs.vcCheckbox as HTMLInputElement).focus(); - }, - blur() { - (this.$refs.vcCheckbox as HTMLInputElement).blur(); - }, - handleChange(event: RadioChangeEvent) { + + const blur = () => { + vcCheckbox.value.blur(); + }; + + expose({ focus, blur }); + + const handleChange = (event: RadioChangeEvent) => { const targetChecked = event.target.checked; - this.$emit('update:checked', targetChecked); - this.$emit('update:value', targetChecked); - this.$emit('change', event); - this.formItemContext.onFieldChange(); - }, - onChange2(e: RadioChangeEvent) { - this.$emit('change', e); - if (this.radioGroupContext && this.radioGroupContext.onRadioChange) { - this.radioGroupContext.onRadioChange(e); + emit('update:checked', targetChecked); + emit('update:value', targetChecked); + emit('change', event); + formItemContext.onFieldChange(); + }; + + const onChange = (e: RadioChangeEvent) => { + emit('change', e); + if (radioGroupContext && radioGroupContext.onRadioChange) { + radioGroupContext.onRadioChange(e); } - }, - }, + }; - render() { - const { $slots, radioGroupContext: radioGroup } = this; - const props = getOptionProps(this); - const { - prefixCls: customizePrefixCls, - id = this.formItemContext.id.value, - ...restProps - } = props; - const { getPrefixCls } = this.configProvider; - const prefixCls = getPrefixCls('radio', customizePrefixCls); + return () => { + const radioGroup = radioGroupContext; + const { prefixCls: customizePrefixCls, id = formItemContext.id.value, ...restProps } = props; - const rProps: RadioProps = { - prefixCls, - id, - ...restProps, - }; + const rProps: RadioProps = { + prefixCls: prefixCls.value, + id, + ...restProps, + }; - if (radioGroup) { - rProps.name = radioGroup.name; - rProps.onChange = this.onChange2; - rProps.checked = props.value === radioGroup.stateValue; - rProps.disabled = props.disabled || radioGroup.disabled; - } else { - rProps.onChange = this.handleChange; - } - const wrapperClassString = classNames({ - [`${prefixCls}-wrapper`]: true, - [`${prefixCls}-wrapper-checked`]: rProps.checked, - [`${prefixCls}-wrapper-disabled`]: rProps.disabled, - }); + if (radioGroup) { + rProps.name = radioGroup.props.name; + rProps.onChange = onChange; + rProps.checked = props.value === radioGroup.stateValue.value; + rProps.disabled = props.disabled || radioGroup.props.disabled; + } else { + rProps.onChange = handleChange; + } + const wrapperClassString = classNames({ + [`${prefixCls.value}-wrapper`]: true, + [`${prefixCls.value}-wrapper-checked`]: rProps.checked, + [`${prefixCls.value}-wrapper-disabled`]: rProps.disabled, + }); - return ( - - ); + return ( + + ); + }; }, }); diff --git a/components/radio/RadioButton.tsx b/components/radio/RadioButton.tsx index 90e88caa22..c70b6fabe7 100644 --- a/components/radio/RadioButton.tsx +++ b/components/radio/RadioButton.tsx @@ -1,35 +1,28 @@ import { defineComponent, inject } from 'vue'; import type { RadioProps } from './Radio'; import Radio, { radioProps } from './Radio'; -import { getOptionProps, getSlot } from '../_util/props-util'; -import { defaultConfigProvider } from '../config-provider'; +import useConfigInject from '../_util/hooks/useConfigInject'; +import type { RadioGroupContext } from './interface'; export default defineComponent({ name: 'ARadioButton', - props: { - ...radioProps, - }, - setup() { - return { - configProvider: inject('configProvider', defaultConfigProvider), - radioGroupContext: inject('radioGroupContext', {}), - }; - }, - render() { - const props = getOptionProps(this) as RadioProps; - const { prefixCls: customizePrefixCls, ...otherProps } = props; - const { getPrefixCls } = this.configProvider; - const prefixCls = getPrefixCls('radio-button', customizePrefixCls); + props: radioProps, + setup(props: RadioProps, { slots }) { + const { prefixCls } = useConfigInject('radio-button', props); + const radioGroupContext = inject('radioGroupContext'); + + return () => { + const rProps: RadioProps = { + ...props, + prefixCls: prefixCls.value, + }; - const rProps: RadioProps = { - prefixCls, - ...otherProps, + if (radioGroupContext) { + rProps.onChange = radioGroupContext.onRadioChange; + rProps.checked = rProps.value === radioGroupContext.stateValue.value; + rProps.disabled = rProps.disabled || radioGroupContext.props.disabled; + } + return {slots.default?.()}; }; - if (this.radioGroupContext) { - rProps.onChange = this.radioGroupContext.onRadioChange; - rProps.checked = props.value === this.radioGroupContext.stateValue; - rProps.disabled = props.disabled || this.radioGroupContext.disabled; - } - return {getSlot(this)}; }, }); diff --git a/components/radio/__tests__/__snapshots__/demo.test.js.snap b/components/radio/__tests__/__snapshots__/demo.test.js.snap index 82ad5fbf72..d96ccc23d1 100644 --- a/components/radio/__tests__/__snapshots__/demo.test.js.snap +++ b/components/radio/__tests__/__snapshots__/demo.test.js.snap @@ -41,13 +41,16 @@ exports[`renders ./components/radio/demo/radioGroup.vue correctly 1`] = ` `; -exports[`renders ./components/radio/demo/radioGroup-more.vue correctly 1`] = `
`; +exports[`renders ./components/radio/demo/radioGroup-more.vue correctly 1`] = `
`; exports[`renders ./components/radio/demo/radioGroup-options.vue correctly 1`] = `

-

-
+

+

+

+

+

`; diff --git a/components/radio/__tests__/__snapshots__/group.test.js.snap b/components/radio/__tests__/__snapshots__/group.test.js.snap index fa25c29be8..1cfc6747b5 100644 --- a/components/radio/__tests__/__snapshots__/group.test.js.snap +++ b/components/radio/__tests__/__snapshots__/group.test.js.snap @@ -2,7 +2,7 @@ exports[`Radio all children should have a name property 1`] = `
`; -exports[`Radio fire change events when value changes 1`] = `
`; +exports[`Radio fire change events when value changes 1`] = `
`; exports[`Radio fire change events when value changes 2`] = `
`; diff --git a/components/radio/demo/radioGroup-more.vue b/components/radio/demo/radioGroup-more.vue index 32ac8a7c58..efe5df8882 100644 --- a/components/radio/demo/radioGroup-more.vue +++ b/components/radio/demo/radioGroup-more.vue @@ -32,7 +32,7 @@ export default defineComponent({ setup() { const value = ref(1); const radioStyle = reactive({ - display: 'block', + display: 'flex', height: '30px', lineHeight: '30px', }); diff --git a/components/radio/demo/radioGroup-options.vue b/components/radio/demo/radioGroup-options.vue index f30ca502ec..fd057d26fd 100644 --- a/components/radio/demo/radioGroup-options.vue +++ b/components/radio/demo/radioGroup-options.vue @@ -19,9 +19,16 @@ Render radios by configuring `options`.

- + +
+ +
+ +
+ +
+
-
-``` From 8cd9c1c8e712639d52021518626ab9f0bdc8beb0 Mon Sep 17 00:00:00 2001 From: ajuner <106791576@qq.com> Date: Thu, 30 Sep 2021 14:25:33 +0800 Subject: [PATCH 3/5] chore: update --- components/radio/Group.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/radio/Group.tsx b/components/radio/Group.tsx index c879c2d317..3e07ceb45d 100644 --- a/components/radio/Group.tsx +++ b/components/radio/Group.tsx @@ -58,7 +58,7 @@ export default defineComponent({ onBeforeMount(() => { provide('radioGroupContext', { onRadioChange, - stateValue: stateValue, + stateValue, props, }); }); From 231558659330ab9e57b9e21876f341112dc0e5cb Mon Sep 17 00:00:00 2001 From: ajuner <106791576@qq.com> Date: Thu, 30 Sep 2021 14:28:12 +0800 Subject: [PATCH 4/5] docs: update --- components/radio/index.en-US.md | 14 +++++++------- components/radio/index.zh-CN.md | 14 +++++++------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/components/radio/index.en-US.md b/components/radio/index.en-US.md index 57990f0954..0dc91adbce 100644 --- a/components/radio/index.en-US.md +++ b/components/radio/index.en-US.md @@ -27,15 +27,15 @@ Radio. Radio group can wrap a group of `Radio`。 -| Property | Description | Type | Default | +| Property | Description | Type | Default | Version | | --- | --- | --- | --- | --- | -| buttonStyle | style type of radio button | `outline` \| `solid` | `outline` | -| disabled | Disable all radio buttons | boolean | false | -| name | The `name` property of all `input[type="radio"]` children | string | - | -| options | set children optional | string\[] \| Array<{ label: string value: string disabled?: boolean }> | - | +| buttonStyle | style type of radio button | `outline` \| `solid` | `outline` | | +| disabled | Disable all radio buttons | boolean | false | | +| name | The `name` property of all `input[type="radio"]` children | string | - | | +| options | set children optional | string\[] \| Array<{ label: string value: string disabled?: boolean }> | - | | | optionType | Set Radio optionType | `default` \| `button` | `default` | 3.0.0 | -| size | size for radio button style | `large` \| `default` \| `small` | `default` | -| value(v-model) | Used for setting the currently selected value. | any | - | +| size | size for radio button style | `large` \| `default` \| `small` | `default` | | +| value(v-model) | Used for setting the currently selected value. | any | - | | ### RadioGroup Events diff --git a/components/radio/index.zh-CN.md b/components/radio/index.zh-CN.md index e9eb5435d9..4687fdcce6 100644 --- a/components/radio/index.zh-CN.md +++ b/components/radio/index.zh-CN.md @@ -28,15 +28,15 @@ cover: https://gw.alipayobjects.com/zos/alicdn/8cYb5seNB/Radio.svg 单选框组合,用于包裹一组 `Radio`。 -| 参数 | 说明 | 类型 | 默认值 | +| 参数 | 说明 | 类型 | 默认值 | 版本 | | --- | --- | --- | --- | --- | -| buttonStyle | RadioButton 的风格样式,目前有描边和填色两种风格 | `outline` \| `solid` | `outline` | -| disabled | 禁选所有子单选器 | boolean | false | -| name | RadioGroup 下所有 `input[type="radio"]` 的 `name` 属性 | string | - | -| options | 以配置形式设置子元素 | string\[] \| Array<{ label: string value: string disabled?: boolean }> | - | +| buttonStyle | RadioButton 的风格样式,目前有描边和填色两种风格 | `outline` \| `solid` | `outline` | | +| disabled | 禁选所有子单选器 | boolean | false | | +| name | RadioGroup 下所有 `input[type="radio"]` 的 `name` 属性 | string | - | | +| options | 以配置形式设置子元素 | string\[] \| Array<{ label: string value: string disabled?: boolean }> | - | | | optionType | 用于设置 Radio `options` 类型 | `default` \| `button` | `default` | 3.0.0 | -| size | 大小,只对按钮样式生效 | `large` \| `default` \| `small` | `default` | -| value(v-model) | 用于设置当前选中的值 | any | - | +| size | 大小,只对按钮样式生效 | `large` \| `default` \| `small` | `default` | | +| value(v-model) | 用于设置当前选中的值 | any | - | | ### RadioGroup 事件 From d96b2883cfea8b374a110d2b9bc112762275453a Mon Sep 17 00:00:00 2001 From: tangjinzhou <415800467@qq.com> Date: Fri, 1 Oct 2021 14:49:02 +0800 Subject: [PATCH 5/5] Update Group.tsx --- components/radio/Group.tsx | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/components/radio/Group.tsx b/components/radio/Group.tsx index 3e07ceb45d..3d098aae2a 100644 --- a/components/radio/Group.tsx +++ b/components/radio/Group.tsx @@ -45,7 +45,7 @@ export default defineComponent({ setup(props, { slots, emit }) { const formItemContext = useInjectFormItemContext(); const { prefixCls } = useConfigInject('radio', props); - const stateValue = ref(props.value); + const stateValue = ref(props.value === undefined ? props.defaultValue : props.value); const updatingValue = ref(false); watch( () => props.value, @@ -55,14 +55,6 @@ export default defineComponent({ }, ); - onBeforeMount(() => { - provide('radioGroupContext', { - onRadioChange, - stateValue, - props, - }); - }); - const onRadioChange = (ev: RadioChangeEvent) => { const lastValue = stateValue.value; const { value } = ev.target; @@ -81,6 +73,12 @@ export default defineComponent({ updatingValue.value = false; }); }; + + provide('radioGroupContext', { + onRadioChange, + stateValue, + props, + }); return () => { const { options, optionType, buttonStyle, id = formItemContext.id.value } = props; @@ -91,7 +89,7 @@ export default defineComponent({ [`${groupPrefixCls}-${props.size}`]: props.size, }); - let children = slots.default?.(); + let children = null; if (options && options.length > 0) { const optionsPrefixCls = optionType === 'button' ? `${prefixCls.value}-button` : prefixCls.value; @@ -122,6 +120,8 @@ export default defineComponent({ ); }); + } else { + children = slots.default?.(); } return (