【react】受控/非受控模式

0 阅读1分钟

受控模式

  • 数据在组件外
  • 能通过业务代码改变数据
function App() {
  const [value, setValue] = useState('guang');

  function onChange(event: ChangeEvent<HTMLInputElement>) {
    setValue(event.target.value);
  }

  return <input value={value} onChange={onChange}/>
}

非受控模式

  • 数据在组件内
  • 不能通过业务代码改变数据
function App() {
  function onChange(event: ChangeEvent<HTMLInputElement>) {
    console.log(event.target.value);
  }

  return <input defaultValue={'guang'} onChange={onChange}/>
}

useControllableValue

对组件数据的封装,兼容了受控模式与非受控模式,来自ahooks

function useControllableValue(defaultProps, options = {}) {
  const props = defaultProps ?? {};

  const {
    defaultValue, // 既没有 value 也没有 defaultValue 时使用的值
    defaultValuePropName = 'defaultValue',
    valuePropName = 'value',
    trigger = 'onChange',
  } = options;

  const value = props[valuePropName];

  // 根据是否有 value 判断是否为受控模式
  const isControlled = Object.prototype.hasOwnProperty.call(props, valuePropName);

  // 组件初始值,后续不会改变
  const initialValue = useMemo(() => {
    if (isControlled) {
      return value;
    }
    if (Object.prototype.hasOwnProperty.call(props, defaultValuePropName)) {
      return props[defaultValuePropName];
    }
    return defaultValue;
  }, []);

  // 组件状态,受控模式/非受控模式共用
  const stateRef = useRef(initialValue);
  if (isControlled) {
    stateRef.current = value;
  }

  // 强制组件更新的 hook
  const update = useUpdate();

  function setState(v: SetStateAction<T>, ...args: any[]) {
    const r = isFunction(v) ? v(stateRef.current) : v;

    if (!isControlled) {
      stateRef.current = r;
      update();
    }
    if (props[trigger]) {
      props[trigger](r, ...args);
    }
  }

  return [stateRef.current, useMemoizedFn(setState)] as const;
}
interface compProps {
  value?: string,
  defaultValue?: string,
  onChange?: (val: string) => void
}

function BizComp(props: any) {
  const [state, setState] = useControllableValue<string>(props);

  return <input value={state} onChange={(e) => setState(e.target.value)} />;
};

function App() {
  const [val, setVal] = useState('');

  return <BizComp value={val} onChange={(val) => {
    setVal(val);
  }}></BizComp>
}