From 67b9da71eb808c39692c9408b856946d1f96d2cf Mon Sep 17 00:00:00 2001 From: ajuner <106791576@qq.com> Date: Sun, 11 Apr 2021 09:57:20 +0800 Subject: [PATCH] refactor(switch): use composition api (#3885) * refactor(switch): use composition api * test: fix * fix: lint * fix: remove prefixCls * fix: use getPropsSlot * fix: use emits --- components/_util/props-util/index.js | 5 + components/switch/__tests__/index.test.js | 12 +- components/switch/index.tsx | 133 +++++++++--------- components/vc-switch/Switch.jsx | 124 ---------------- components/vc-switch/{index.js => index.ts} | 2 +- .../{PropTypes.js => src/PropTypes.ts} | 2 +- components/vc-switch/src/Switch.tsx | 99 +++++++++++++ 7 files changed, 182 insertions(+), 195 deletions(-) delete mode 100644 components/vc-switch/Switch.jsx rename components/vc-switch/{index.js => index.ts} (57%) rename components/vc-switch/{PropTypes.js => src/PropTypes.ts} (91%) create mode 100644 components/vc-switch/src/Switch.tsx diff --git a/components/_util/props-util/index.js b/components/_util/props-util/index.js index 21a3c10d7..86eb35642 100644 --- a/components/_util/props-util/index.js +++ b/components/_util/props-util/index.js @@ -389,6 +389,10 @@ function isValidElement(element) { return element && element.__v_isVNode && typeof element.type !== 'symbol'; // remove text node } +function getPropsSlot(slots, props, prop = 'default') { + return slots[prop]?.() ?? props[prop]; +} + export { splitAttrs, hasProp, @@ -411,5 +415,6 @@ export { getAllChildren, findDOMNode, flattenChildren, + getPropsSlot, }; export default hasProp; diff --git a/components/switch/__tests__/index.test.js b/components/switch/__tests__/index.test.js index 810343dc5..0742de8e0 100644 --- a/components/switch/__tests__/index.test.js +++ b/components/switch/__tests__/index.test.js @@ -9,7 +9,11 @@ describe('Switch', () => { mountTest(Switch); it('should has click wave effect', async () => { - const wrapper = mount(Switch); + const wrapper = mount({ + render() { + return ; + }, + }); wrapper.find('.ant-switch').trigger('click'); await new Promise(resolve => setTimeout(resolve, 0)); expect(wrapper.html()).toMatchSnapshot(); @@ -19,7 +23,11 @@ describe('Switch', () => { resetWarned(); const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); - mount(Switch, { props: { value: '' } }); + mount({ + render() { + return ; + }, + }); expect(errorSpy).toHaveBeenCalledWith( 'Warning: [antdv: Switch] `value` is not validate prop, do you mean `checked`?', ); diff --git a/components/switch/index.tsx b/components/switch/index.tsx index 4a388e1eb..d97797517 100644 --- a/components/switch/index.tsx +++ b/components/switch/index.tsx @@ -1,86 +1,85 @@ -import { defineComponent, inject } from 'vue'; +import { defineComponent, inject, onBeforeMount, ref, ExtractPropTypes, computed } from 'vue'; import LoadingOutlined from '@ant-design/icons-vue/LoadingOutlined'; import PropTypes from '../_util/vue-types'; -import hasProp, { getOptionProps, getComponent } from '../_util/props-util'; import VcSwitch from '../vc-switch'; import Wave from '../_util/wave'; import { defaultConfigProvider } from '../config-provider'; import warning from '../_util/warning'; import { tuple, withInstall } from '../_util/type'; +import { getPropsSlot } from '../_util/props-util'; +import Omit from 'omit.js'; + +export const SwitchSizes = tuple('small', 'default', 'large'); + +const switchProps = { + prefixCls: PropTypes.string, + size: PropTypes.oneOf(SwitchSizes), + disabled: PropTypes.looseBool, + checkedChildren: PropTypes.any, + unCheckedChildren: PropTypes.any, + tabindex: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), + // defaultChecked: PropTypes.looseBool, + autofocus: PropTypes.looseBool, + loading: PropTypes.looseBool, + checked: PropTypes.looseBool, +}; + +export type SwitchProps = Partial>; const Switch = defineComponent({ name: 'ASwitch', __ANT_SWITCH: true, inheritAttrs: false, - props: { - prefixCls: PropTypes.string, - // size=default and size=large are the same - size: PropTypes.oneOf(tuple('small', 'default', 'large')), - disabled: PropTypes.looseBool, - checkedChildren: PropTypes.any, - unCheckedChildren: PropTypes.any, - tabindex: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), - checked: PropTypes.looseBool, - defaultChecked: PropTypes.looseBool, - autofocus: PropTypes.looseBool, - loading: PropTypes.looseBool, - onChange: PropTypes.func, - onClick: PropTypes.func, - 'onUpdate:checked': PropTypes.func, - }, - // emits: ['change', 'click', 'update:checked'], - setup() { - return { - refSwitchNode: undefined, - configProvider: inject('configProvider', defaultConfigProvider), - }; - }, - created() { - warning( - hasProp(this, 'checked') || !('value' in this.$attrs), - 'Switch', - '`value` is not validate prop, do you mean `checked`?', - ); - }, - methods: { - focus() { - this.refSwitchNode?.focus(); - }, - blur() { - this.refSwitchNode?.blur(); - }, - saveRef(c) { - this.refSwitchNode = c; - }, - }, + props: switchProps, + setup(props: SwitchProps, { attrs, slots, expose }) { + const configProvider = inject('configProvider', defaultConfigProvider); + const refSwitchNode = ref(); - render() { - const { prefixCls: customizePrefixCls, size, loading, disabled, ...restProps } = getOptionProps( - this, - ); - const { getPrefixCls } = this.configProvider; - const prefixCls = getPrefixCls('switch', customizePrefixCls); - const { $attrs } = this; - const classes = { - [$attrs.class as string]: $attrs.class, - [`${prefixCls}-small`]: size === 'small', - [`${prefixCls}-loading`]: loading, + const focus = () => { + refSwitchNode.value?.focus(); }; - const loadingIcon = loading ? : null; - const switchProps = { - ...restProps, - ...$attrs, - prefixCls, - loadingIcon, - checkedChildren: getComponent(this, 'checkedChildren'), - unCheckedChildren: getComponent(this, 'unCheckedChildren'), - disabled: disabled || loading, - class: classes, - ref: this.saveRef, + const blur = () => { + refSwitchNode.value?.blur(); }; - return ( + + expose({ focus, blur }); + + onBeforeMount(() => { + if ('defaultChecked' in attrs) { + console.warn( + `[antdv: Switch]: 'defaultChecked' will be obsolete, please use 'v-model:checked'`, + ); + } + warning( + !('value' in attrs), + 'Switch', + '`value` is not validate prop, do you mean `checked`?', + ); + }); + const { getPrefixCls } = configProvider; + const prefixCls = computed(() => { + return getPrefixCls('switch', props.prefixCls); + }); + return () => ( - + : null + } + checkedChildren={getPropsSlot(slots, props, 'checkedChildren')} + unCheckedChildren={getPropsSlot(slots, props, 'unCheckedChildren')} + disabled={props.disabled || props.loading} + class={{ + [attrs.class as string]: attrs.class, + [`${prefixCls.value}-small`]: props.size === 'small', + [`${prefixCls.value}-loading`]: props.loading, + }} + ref={refSwitchNode} + /> ); }, diff --git a/components/vc-switch/Switch.jsx b/components/vc-switch/Switch.jsx deleted file mode 100644 index c3ac8a6f4..000000000 --- a/components/vc-switch/Switch.jsx +++ /dev/null @@ -1,124 +0,0 @@ -import { switchPropTypes } from './PropTypes'; -import BaseMixin from '../_util/BaseMixin'; -import { hasProp, getOptionProps, getComponent } from '../_util/props-util'; -import Omit from 'omit.js'; -import { defineComponent } from 'vue'; - -// function noop () { -// } -export default defineComponent({ - name: 'VcSwitch', - mixins: [BaseMixin], - inheritAttrs: false, - props: { - ...switchPropTypes, - prefixCls: switchPropTypes.prefixCls.def('rc-switch'), - // onChange: switchPropTypes.onChange.def(noop), - // onClick: switchPropTypes.onClick.def(noop), - }, - data() { - let checked = false; - if (hasProp(this, 'checked')) { - checked = !!this.checked; - } else { - checked = !!this.defaultChecked; - } - return { - stateChecked: checked, - }; - }, - watch: { - checked(val) { - this.stateChecked = val; - }, - }, - mounted() { - this.$nextTick(() => { - const { autofocus, disabled } = this; - if (autofocus && !disabled) { - this.focus(); - } - }); - }, - methods: { - saveRef(c) { - this.refSwitchNode = c; - }, - setChecked(checked, e) { - if (this.disabled) { - return; - } - if (!hasProp(this, 'checked')) { - this.stateChecked = checked; - } - this.__emit('update:checked', checked); - this.__emit('change', checked, e); - }, - handleClick(e) { - const checked = !this.stateChecked; - this.setChecked(checked, e); - this.__emit('click', checked, e); - }, - handleKeyDown(e) { - if (e.keyCode === 37) { - // Left - this.setChecked(false, e); - } else if (e.keyCode === 39) { - // Right - this.setChecked(true, e); - } - }, - handleMouseUp(e) { - this.refSwitchNode?.blur(); - - this.__emit('mouseup', e); - }, - focus() { - this.refSwitchNode?.focus(); - }, - blur() { - this.refSwitchNode?.blur(); - }, - }, - render() { - const { prefixCls, disabled, loadingIcon, ...restProps } = getOptionProps(this); - const checked = this.stateChecked; - const { $attrs } = this; - const switchClassName = { - [$attrs.class]: $attrs.class, - [prefixCls]: true, - [`${prefixCls}-checked`]: checked, - [`${prefixCls}-disabled`]: disabled, - }; - const spanProps = { - ...Omit(restProps, [ - 'checkedChildren', - 'unCheckedChildren', - 'checked', - 'autofocus', - 'defaultChecked', - ]), - ...$attrs, - onKeydown: this.handleKeyDown, - onClick: this.handleClick, - onMouseup: this.handleMouseUp, - type: 'button', - role: 'switch', - 'aria-checked': checked, - disabled, - class: switchClassName, - ref: this.saveRef, - }; - - return ( - - ); - }, -}); diff --git a/components/vc-switch/index.js b/components/vc-switch/index.ts similarity index 57% rename from components/vc-switch/index.js rename to components/vc-switch/index.ts index bfde65dd6..dc4db4c32 100644 --- a/components/vc-switch/index.js +++ b/components/vc-switch/index.ts @@ -1,4 +1,4 @@ // base rc-switch 1.9.0 -import Switch from './Switch'; +import Switch from './src/Switch'; export default Switch; diff --git a/components/vc-switch/PropTypes.js b/components/vc-switch/src/PropTypes.ts similarity index 91% rename from components/vc-switch/PropTypes.js rename to components/vc-switch/src/PropTypes.ts index 17bc7ddf9..70ed02ad4 100644 --- a/components/vc-switch/PropTypes.js +++ b/components/vc-switch/src/PropTypes.ts @@ -1,4 +1,4 @@ -import PropTypes from '../_util/vue-types'; +import PropTypes from '../../_util/vue-types'; export const switchPropTypes = { prefixCls: PropTypes.string, diff --git a/components/vc-switch/src/Switch.tsx b/components/vc-switch/src/Switch.tsx new file mode 100644 index 000000000..4064553b8 --- /dev/null +++ b/components/vc-switch/src/Switch.tsx @@ -0,0 +1,99 @@ +import { switchPropTypes } from './PropTypes'; +import Omit from 'omit.js'; +import { defineComponent, nextTick, onMounted, ref } from 'vue'; +import KeyCode from '../../_util/KeyCode'; +import { getPropsSlot } from '../../_util/props-util'; + +export default defineComponent({ + name: 'VcSwitch', + inheritAttrs: false, + props: { + ...switchPropTypes, + prefixCls: switchPropTypes.prefixCls.def('rc-switch'), + }, + emits: ['update:checked', 'mouseup', 'change', 'click'], + setup(props, { attrs, slots, emit, expose }) { + const checked = ref('checked' in props ? !!props.checked : !!props.defaultChecked); + + const refSwitchNode = ref(); + + onMounted(() => { + nextTick(() => { + if (props.autofocus && !props.disabled) { + refSwitchNode.value.focus(); + } + }); + }); + + const setChecked = (check: boolean, e: MouseEvent | KeyboardEvent) => { + if (props.disabled) { + return; + } + checked.value = !checked.value; + emit('update:checked', checked); + emit('change', check, e); + }; + + const handleClick = (e: MouseEvent) => { + setChecked(checked.value, e); + emit('click', checked.value, e); + }; + + const handleKeyDown = (e: KeyboardEvent) => { + if (e.keyCode === KeyCode.LEFT) { + setChecked(false, e); + } else if (e.keyCode === KeyCode.RIGHT) { + setChecked(true, e); + } + }; + + const handleMouseUp = (e: MouseEvent) => { + refSwitchNode.value?.blur(); + emit('mouseup', e); + }; + + const focus = () => { + refSwitchNode.value?.focus(); + }; + const blur = () => { + refSwitchNode.value?.blur(); + }; + + expose({ focus, blur }); + + return () => ( + + ); + }, +});