Antd Switch
Switch 参数
很明显,Switch
的作为一个受控组件, 那么他接受的组件应该有checked
, onChange
这两个属性, 查看Switch
的interface
interface
/** 简化后的interface */
export interface SwitchProps {
/** switch 大小 */
size?: SwitchSize;
/** 开关状态 */
checked?: boolean;
/** 开关状态 */
defaultChecked?: boolean;
/** onChange 事件 */
onChange?: (checked: boolean, event: MouseEvent) => void;
checkedChildren?: React.ReactNode;
unCheckedChildren?: React.ReactNode;
disabled?: boolean;
}
antd
源码中Switch
调用的是 RcSwitch
/** 简化后的代码 */
import * as React from 'react';
import RcSwitch from 'rc-switch';
const Switch = React.forwardRef<unknown, SwitchProps>(props, ref) => {
const { disabled, ...rest } = props;
return (
<RcSwitch
{...props}
disabled={disabled || loading}
ref={ref}
/>
)
}
RcSwitch
直接看 RcSwitch
的render
函数, 其实就是一个button
包裹着两个propsChildren
const RcSwitch = () => {
return (
<button
{...restProps}
aria-checked={innerChecked}
disabled={disabled}
ref={ref}
onClick={onInternalClick}
>
{loadingIcon}
<span className={`${prefixCls}-inner`}>
{innerChecked ? checkedChildren : unCheckedChildren}
</span>
</button>
)
}
值得学习的是,rc-switch 用一个hook 函数
,将value
, defaultValue
, onChange
三者抽离出来,这样就可以封装一个受控组件或者是一个非受控组件了
其实 useMergedState
就是 umi hook
里面的 useControllableValue
const RcSwitch = () => {
const [innerChecked, setInnerChecked] = useMergedState<boolean>(false, {
value: checked,
defaultValue: defaultChecked,
});
/** 就是一个onChange 函数,同时改变自身innerCheck 状态,如果props 有onChange,那么也触发父级事件 */
function triggerChange(
newChecked: boolean,
event: React.MouseEvent<HTMLButtonElement> | React.KeyboardEvent<HTMLButtonElement>,
) {
let mergedChecked = innerChecked;
if (!disabled) {
mergedChecked = newChecked;
setInnerChecked(mergedChecked);
onChange?.(mergedChecked, event);
}
return mergedChecked;
}
return (
<button
aria-checked={innerChecked}
disabled={disabled}
ref={ref}
onClick={(e) => triggerChange(!innerChecked, e)}
>
{loadingIcon}
<span className={`${prefixCls}-inner`}>
{innerChecked ? checkedChildren : unCheckedChildren}
</span>
</button>
)
}
useMergedState 或者 useControllableValue
我们直接查看useControllableValue
这个hooks 是怎么实现的
源码
interface StandardProps<T> {
value: T;
defaultValue?: T;
onChange: (val: T) => void;
}
function useControllableValue<T = any>(props: StandardProps<T>): [T, (val: T) => void];
function useControllableValue<T = any>(
props?: Props,
options?: Options<T>,
): [T, (v: T, ...args: any[]) => void];
function useControllableValue<T = any>(props: Props = {}, options: Options<T> = {}) {
/** 首先获取options 的默认参数, 一般为 value, onChange */
const {
defaultValue,
defaultValuePropName = 'defaultValue',
valuePropName = 'value',
trigger = 'onChange',
} = options;
/** 获取 prop['value'] 的值 */
const value = props[valuePropName] as T;
/** 如果value 在 props 中, 那么我们直接用 props[value] 的值, 不然就是用默认值 */
const [state, setState] = useState<T>(() => {
if (valuePropName in props) {
return value;
}
if (defaultValuePropName in props) {
return props[defaultValuePropName];
}
return defaultValue;
});
/* init 的时候不用执行了, 当我们传入的props[value] 发生变化的时候,重新setState, 保证state 跟 props[value] 同步 */
useUpdateEffect(() => {
if (valuePropName in props) {
setState(value);
}
}, [value, valuePropName]);
/** onChange 事件, 分为非受控组件,与受控组件 */
const handleSetState = useCallback(
(v: T, ...args: any[]) => {
/** 这里判断一下 我们用组件的时候有没有传入value, 有的话就是受控组件, 没有的话就是非受控组件,非受控组件维护内部值 */
if (!(valuePropName in props)) {
setState(v);
}
/** 受控组件, 判断一下是否有onChange, 如果有那么就调用props.onChange */
if (props[trigger]) {
props[trigger](v, ...args);
}
},
[props, valuePropName, trigger],
);
return [valuePropName in props ? value : state, handleSetState] as const;
}
这个hooks
在antd 中经常被使用到