Antd Switch

源码地址

Switch 参数

很明显,Switch 的作为一个受控组件, 那么他接受的组件应该有checked, onChange 这两个属性, 查看Switchinterface

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

源码地址

直接看 RcSwitchrender 函数, 其实就是一个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 中经常被使用到