refactor(switch): use composition api (#3885)

* refactor(switch): use composition api

* test: fix

* fix: lint

* fix: remove prefixCls

* fix: use getPropsSlot

* fix: use emits
pull/3923/head
ajuner 2021-04-11 09:57:20 +08:00 committed by GitHub
parent 4c370edf24
commit 67b9da71eb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 182 additions and 195 deletions

View File

@ -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;

View File

@ -9,7 +9,11 @@ describe('Switch', () => {
mountTest(Switch);
it('should has click wave effect', async () => {
const wrapper = mount(Switch);
const wrapper = mount({
render() {
return <Switch />;
},
});
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 <Switch value="" />;
},
});
expect(errorSpy).toHaveBeenCalledWith(
'Warning: [antdv: Switch] `value` is not validate prop, do you mean `checked`?',
);

View File

@ -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<ExtractPropTypes<typeof switchProps>>;
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 ? <LoadingOutlined class={`${prefixCls}-loading-icon`} /> : 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 () => (
<Wave insertExtraNode>
<VcSwitch {...switchProps} />
<VcSwitch
{...Omit(props, ['prefixCls', 'size', 'loading', 'disabled'])}
{...attrs}
checked={props.checked}
prefixCls={prefixCls.value}
loadingIcon={
props.loading ? <LoadingOutlined class={`${prefixCls.value}-loading-icon`} /> : 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}
/>
</Wave>
);
},

View File

@ -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 (
<button {...spanProps}>
{loadingIcon}
<span class={`${prefixCls}-inner`}>
{checked
? getComponent(this, 'checkedChildren')
: getComponent(this, 'unCheckedChildren')}
</span>
</button>
);
},
});

View File

@ -1,4 +1,4 @@
// base rc-switch 1.9.0
import Switch from './Switch';
import Switch from './src/Switch';
export default Switch;

View File

@ -1,4 +1,4 @@
import PropTypes from '../_util/vue-types';
import PropTypes from '../../_util/vue-types';
export const switchPropTypes = {
prefixCls: PropTypes.string,

View File

@ -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 () => (
<button
{...Omit(props, [
'prefixCls',
'checkedChildren',
'unCheckedChildren',
'checked',
'autofocus',
'defaultChecked',
])}
{...attrs}
onKeydown={handleKeyDown}
onClick={handleClick}
onMouseup={handleMouseUp}
type="button"
role="switch"
aria-checked={checked.value}
disabled={props.disabled}
class={{
[attrs.class as string]: attrs.class,
[props.prefixCls]: true,
[`${props.prefixCls}-checked`]: checked.value,
[`${props.prefixCls}-disabled`]: props.disabled,
}}
ref={refSwitchNode}
>
{props.loadingIcon}
<span class={`${props.prefixCls}-inner`}>
{checked.value
? getPropsSlot(slots, props, 'checkedChildren')
: getPropsSlot(slots, props, 'unCheckedChildren')}
</span>
</button>
);
},
});