refactor(checkbox-group): rewrite with hook (#27147)

This commit is contained in:
Tom Xu 2020-10-18 23:31:20 +08:00 committed by GitHub
parent e234a71ecd
commit 261018053f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 105 additions and 193 deletions

View File

@ -2,7 +2,7 @@ import * as React from 'react';
import classNames from 'classnames';
import omit from 'omit.js';
import Checkbox, { CheckboxChangeEvent } from './Checkbox';
import { ConfigConsumer, ConfigConsumerProps } from '../config-provider';
import { ConfigContext } from '../config-provider';
export type CheckboxValueType = string | number | boolean;
@ -29,11 +29,6 @@ export interface CheckboxGroupProps extends AbstractCheckboxGroupProps {
onChange?: (checkedValue: Array<CheckboxValueType>) => void;
}
export interface CheckboxGroupState {
value: CheckboxValueType[];
registeredValues: CheckboxValueType[];
}
export interface CheckboxGroupContext {
toggleOption?: (option: CheckboxOptionType) => void;
value?: any;
@ -42,135 +37,119 @@ export interface CheckboxGroupContext {
export const GroupContext = React.createContext<CheckboxGroupContext | null>(null);
class CheckboxGroup extends React.PureComponent<CheckboxGroupProps, CheckboxGroupState> {
static defaultProps = {
options: [],
};
const CheckboxGroup: React.FC<CheckboxGroupProps> = ({
defaultValue,
children,
options = [],
prefixCls: customizePrefixCls,
className,
style,
onChange,
...restProps
}) => {
const { getPrefixCls, direction } = React.useContext(ConfigContext);
static getDerivedStateFromProps(nextProps: CheckboxGroupProps) {
if ('value' in nextProps) {
return {
value: nextProps.value || [],
};
const [value, setValue] = React.useState<CheckboxValueType[]>(
restProps.value || defaultValue || [],
);
const [registeredValues, setRegisteredValues] = React.useState<CheckboxValueType[]>([]);
React.useEffect(() => {
if ('value' in restProps) {
setValue(restProps.value || []);
}
return null;
}
}, [restProps.value]);
constructor(props: CheckboxGroupProps) {
super(props);
this.state = {
value: props.value || props.defaultValue || [],
registeredValues: [],
};
}
getOptions() {
const { options } = this.props;
// https://github.com/Microsoft/TypeScript/issues/7960
return (options as Array<CheckboxOptionType>).map(option => {
const getOptions = () => {
return options.map(option => {
if (typeof option === 'string') {
return {
label: option,
value: option,
} as CheckboxOptionType;
};
}
return option;
});
}
cancelValue = (value: string) => {
this.setState(({ registeredValues }) => ({
registeredValues: registeredValues.filter(val => val !== value),
}));
};
registerValue = (value: string) => {
this.setState(({ registeredValues }) => ({
registeredValues: [...registeredValues, value],
}));
const cancelValue = (val: string) => {
setRegisteredValues(prevValues => prevValues.filter(v => v !== val));
};
toggleOption = (option: CheckboxOptionType) => {
const { registeredValues } = this.state;
const optionIndex = this.state.value.indexOf(option.value);
const value = [...this.state.value];
const registerValue = (val: string) => {
setRegisteredValues(prevValues => [...prevValues, val]);
};
const toggleOption = (option: CheckboxOptionType) => {
const optionIndex = value.indexOf(option.value);
const newValue = [...value];
if (optionIndex === -1) {
value.push(option.value);
newValue.push(option.value);
} else {
value.splice(optionIndex, 1);
newValue.splice(optionIndex, 1);
}
if (!('value' in this.props)) {
this.setState({ value });
if (!('value' in restProps)) {
setValue(newValue);
}
const { onChange } = this.props;
if (onChange) {
const options = this.getOptions();
const opts = getOptions();
onChange(
value
newValue
.filter(val => registeredValues.indexOf(val) !== -1)
.sort((a, b) => {
const indexA = options.findIndex(opt => opt.value === a);
const indexB = options.findIndex(opt => opt.value === b);
const indexA = opts.findIndex(opt => opt.value === a);
const indexB = opts.findIndex(opt => opt.value === b);
return indexA - indexB;
}),
);
}
};
renderGroup = ({ getPrefixCls, direction }: ConfigConsumerProps) => {
const { props, state } = this;
const { prefixCls: customizePrefixCls, className, style, options, ...restProps } = props;
const prefixCls = getPrefixCls('checkbox', customizePrefixCls);
const groupPrefixCls = `${prefixCls}-group`;
const prefixCls = getPrefixCls('checkbox', customizePrefixCls);
const groupPrefixCls = `${prefixCls}-group`;
const domProps = omit(restProps, ['children', 'defaultValue', 'value', 'onChange', 'disabled']);
const domProps = omit(restProps, ['value', 'disabled']);
let { children } = props;
if (options && options.length > 0) {
children = this.getOptions().map(option => (
<Checkbox
prefixCls={prefixCls}
key={option.value.toString()}
disabled={'disabled' in option ? option.disabled : props.disabled}
value={option.value}
checked={state.value.indexOf(option.value) !== -1}
onChange={option.onChange}
className={`${groupPrefixCls}-item`}
style={option.style}
>
{option.label}
</Checkbox>
));
}
if (options && options.length > 0) {
children = getOptions().map(option => (
<Checkbox
prefixCls={prefixCls}
key={option.value.toString()}
disabled={'disabled' in option ? option.disabled : restProps.disabled}
value={option.value}
checked={value.indexOf(option.value) !== -1}
onChange={option.onChange}
className={`${groupPrefixCls}-item`}
style={option.style}
>
{option.label}
</Checkbox>
));
}
const context = {
toggleOption: this.toggleOption,
value: this.state.value,
disabled: this.props.disabled,
name: this.props.name,
const context = {
toggleOption,
value,
disabled: restProps.disabled,
name: restProps.name,
// https://github.com/ant-design/ant-design/issues/16376
registerValue: this.registerValue,
cancelValue: this.cancelValue,
};
const classString = classNames(
groupPrefixCls,
{
[`${groupPrefixCls}-rtl`]: direction === 'rtl',
},
className,
);
return (
<div className={classString} style={style} {...domProps}>
<GroupContext.Provider value={context}>{children}</GroupContext.Provider>
</div>
);
// https://github.com/ant-design/ant-design/issues/16376
registerValue,
cancelValue,
};
render() {
return <ConfigConsumer>{this.renderGroup}</ConfigConsumer>;
}
}
const classString = classNames(
groupPrefixCls,
{
[`${groupPrefixCls}-rtl`]: direction === 'rtl',
},
className,
);
return (
<div className={classString} style={style} {...domProps}>
<GroupContext.Provider value={context}>{children}</GroupContext.Provider>
</div>
);
};
export default CheckboxGroup;
export default React.memo(CheckboxGroup);

View File

@ -14,25 +14,13 @@ describe('CheckboxGroup', () => {
const wrapper = mount(
<Checkbox.Group options={['Apple', 'Pear', 'Orange']} onChange={onChange} />,
);
wrapper
.find('.ant-checkbox-input')
.at(0)
.simulate('change');
wrapper.find('.ant-checkbox-input').at(0).simulate('change');
expect(onChange).toHaveBeenCalledWith(['Apple']);
wrapper
.find('.ant-checkbox-input')
.at(1)
.simulate('change');
wrapper.find('.ant-checkbox-input').at(1).simulate('change');
expect(onChange).toHaveBeenCalledWith(['Apple', 'Pear']);
wrapper
.find('.ant-checkbox-input')
.at(2)
.simulate('change');
wrapper.find('.ant-checkbox-input').at(2).simulate('change');
expect(onChange).toHaveBeenCalledWith(['Apple', 'Pear', 'Orange']);
wrapper
.find('.ant-checkbox-input')
.at(1)
.simulate('change');
wrapper.find('.ant-checkbox-input').at(1).simulate('change');
expect(onChange).toHaveBeenCalledWith(['Apple', 'Orange']);
});
@ -47,15 +35,9 @@ describe('CheckboxGroup', () => {
const groupWrapper = mount(
<Checkbox.Group options={options} onChange={onChangeGroup} disabled />,
);
groupWrapper
.find('.ant-checkbox-input')
.at(0)
.simulate('change');
groupWrapper.find('.ant-checkbox-input').at(0).simulate('change');
expect(onChangeGroup).not.toHaveBeenCalled();
groupWrapper
.find('.ant-checkbox-input')
.at(1)
.simulate('change');
groupWrapper.find('.ant-checkbox-input').at(1).simulate('change');
expect(onChangeGroup).not.toHaveBeenCalled();
});
@ -68,15 +50,9 @@ describe('CheckboxGroup', () => {
];
const groupWrapper = mount(<Checkbox.Group options={options} onChange={onChangeGroup} />);
groupWrapper
.find('.ant-checkbox-input')
.at(0)
.simulate('change');
groupWrapper.find('.ant-checkbox-input').at(0).simulate('change');
expect(onChangeGroup).toHaveBeenCalledWith(['Apple']);
groupWrapper
.find('.ant-checkbox-input')
.at(1)
.simulate('change');
groupWrapper.find('.ant-checkbox-input').at(1).simulate('change');
expect(onChangeGroup).toHaveBeenCalledWith(['Apple']);
});
@ -105,10 +81,10 @@ describe('CheckboxGroup', () => {
];
const wrapper = mount(<Checkbox.Group options={options} />);
expect(wrapper.instance().state.value).toEqual([]);
expect(wrapper.find('.ant-checkbox-checked').length).toBe(0);
wrapper.setProps({ value: ['Apple'] });
expect(wrapper.instance().state.value).toEqual(['Apple']);
wrapper.update();
expect(wrapper.find('.ant-checkbox-checked').length).toBe(1);
});
// https://github.com/ant-design/ant-design/issues/12642
@ -119,10 +95,7 @@ describe('CheckboxGroup', () => {
<Checkbox value="my" onChange={onChange} />
</Checkbox.Group>,
);
wrapper
.find('.ant-checkbox-input')
.at(0)
.simulate('change');
wrapper.find('.ant-checkbox-input').at(0).simulate('change');
expect(onChange).toHaveBeenCalled();
expect(onChange.mock.calls[0][0].target.value).toEqual('my');
});
@ -141,10 +114,7 @@ describe('CheckboxGroup', () => {
children: [<Checkbox key={2} value={2} />],
});
wrapper
.find('.ant-checkbox-input')
.at(0)
.simulate('change');
wrapper.find('.ant-checkbox-input').at(0).simulate('change');
expect(onChange).toHaveBeenCalledWith([2]);
});
@ -160,12 +130,7 @@ describe('CheckboxGroup', () => {
wrapper.setProps({
children: [<Checkbox key={1} value={2} />],
});
expect(
wrapper
.find('.ant-checkbox-input')
.at(0)
.prop('checked'),
).toBe(false);
expect(wrapper.find('.ant-checkbox-input').at(0).prop('checked')).toBe(false);
});
// https://github.com/ant-design/ant-design/issues/17297
@ -180,25 +145,13 @@ describe('CheckboxGroup', () => {
</Checkbox.Group>,
);
wrapper
.find('.ant-checkbox-input')
.at(0)
.simulate('change');
wrapper.find('.ant-checkbox-input').at(0).simulate('change');
expect(onChange).toHaveBeenCalledWith([1]);
wrapper
.find('.ant-checkbox-input')
.at(1)
.simulate('change');
wrapper.find('.ant-checkbox-input').at(1).simulate('change');
expect(onChange).toHaveBeenCalledWith([1, 2]);
wrapper
.find('.ant-checkbox-input')
.at(0)
.simulate('change');
wrapper.find('.ant-checkbox-input').at(0).simulate('change');
expect(onChange).toHaveBeenCalledWith([2]);
wrapper
.find('.ant-checkbox-input')
.at(0)
.simulate('change');
wrapper.find('.ant-checkbox-input').at(0).simulate('change');
expect(onChange).toHaveBeenCalledWith([1, 2]);
});
@ -215,30 +168,10 @@ describe('CheckboxGroup', () => {
</Collapse>
</Checkbox.Group>,
);
wrapper
.find('.ant-collapse-item')
.at(0)
.find('.ant-collapse-header')
.simulate('click');
wrapper
.find('.ant-checkbox-input')
.at(0)
.simulate('change');
expect(
wrapper
.find(Checkbox.Group)
.at(0)
.state('value'),
).toEqual(['1']);
wrapper
.find('.ant-checkbox-input')
.at(0)
.simulate('change');
expect(
wrapper
.find(Checkbox.Group)
.at(0)
.state('value'),
).toEqual([]);
wrapper.find('.ant-collapse-item').at(0).find('.ant-collapse-header').simulate('click');
wrapper.find('.ant-checkbox-input').at(0).simulate('change');
expect(wrapper.find('.ant-checkbox-checked').length).toBe(1);
wrapper.find('.ant-checkbox-input').at(0).simulate('change');
expect(wrapper.find('.ant-checkbox-checked').length).toBe(0);
});
});