自定义钩子实现入门(如何实现一个 useRadioSelect 钩子)

351 阅读3分钟

如何实现一个 useRadioSelect 钩子

useRadioSelect是什么

useRadioSelect 是一个管理单选控件radio数据流的React通用钩子,radio这种控件在h5中为某种特殊的input控件,在表单中用于单选输入。

我们为什么需要useRadioSelect

  1. 在大部分的场景下我们直接使用原生的 <input type="radio"/> 控件就可以满足我们的业务需求,或者在很多中台项目中使用 Antd-Radio 组件, 但有时候我们需要完全脱离原生的ui或者 antd 的 ui实现一个类似的交互逻辑控件的时候该数据流管理组件便可以发挥他的作用。

  2. 另外还有一个值得商榷的问题是原生和antd的radio都是不支持选中之后的反选的,该文章也对这样的交互逻辑做了相关解释,但在某些特定的场景需求中允许反选也存在一定的合理性,将这样相关的特殊需求自己实现的通用钩子也可以做相关的适配提供额外的能力

如何实现useRadioSelect

该钩子需要用户提供什么

  1. radioList列表(必须)
  2. 是否需要提供反选radio的能力(可选)

该钩子需要给用户提供什么

  1. 点击radio-ui需要出发的回调方法(必须)
  2. 当前选中的radio-value(必须)
  3. 原始的radioList(必须)
  4. 脱离UI的一些直接控制数据的方法(非必须)

设计API限定入参和出参

此处使用TS

type OptionItem = string | number | undefined;

type Result = {
  originalOptionList: OptionItem[];
  selectedValue: OptionItem;
  optionClick: (item: OptionItem) => void;
  // 受控的一些外部操作方法
  setValue: (item: OptionItem) => void;
  clear: () => void;
};

type useRadioSelect = (
  originalOptionList: OptionItem[],
  options: Options = { optionCancelable: false }
): Result

实现内部的通用数据管理方法

这里主要的方法是optionClick方法,需要在用户开启反选 option 的时候做特殊处理,以下是该钩子的源码

mport React, { useState, useEffect } from "react";

export type OptionItem = string | number | undefined;

export type Result = {
  originalOptionList: OptionItem[];
  selectedValue: OptionItem;
  optionClick: (item: OptionItem) => void;
  // 受控的一些外部操作方法
  setValue: (item: OptionItem) => void;
  clear: () => void;
};

export type Options = {
  defaultSelectedValue?: OptionItem;
  optionCancelable?: boolean;
};

function useRadioSelect(
  originalOptionList: OptionItem[],
  options?: Options
): Result {
  const { optionCancelable, defaultSelectedValue } = {
    optionCancelable: false,
    ...(options || {}),
  };

  const [selectedValue, setSelectedValue] = useState<OptionItem | undefined>(
    defaultSelectedValue && originalOptionList.includes(defaultSelectedValue)
      ? defaultSelectedValue
      : originalOptionList[0]
  );

  const clear = () => {
    setSelectedValue(undefined);
  };

  const optionClick = (item) => {
    if (optionCancelable && selectedValue === item) {
      setSelectedValue(undefined);
    } else {
      setSelectedValue(item);
    }
  };

  return {
    originalOptionList,
    setValue: setSelectedValue,
    clear,
    optionClick,
    selectedValue: selectedValue,
  };
}

export default useRadioSelect;

如何使用 useRadioSelect

使用 useRadioSelect 封装自定义的样式组件

const RadioGroup = ({
  optionList,
  onChange,
  containerStyle,
  containerClassName,
  defaultSelectedValue = undefined,
  optionCancelable = false,
}: {
  optionList: OptionList;
  onChange: (value: OptionItem) => void;
  containerStyle?: CSSProperties;
  containerClassName?: string;
  defaultSelectedValue?: OptionItem;
  optionCancelable?: boolean;
}) => {
  const optionValueList = optionList.map((item) => item.value);

  const { selectedValue, optionClick } = useRadioSelect(optionValueList, {
    defaultSelectedValue,
    optionCancelable,
  });

  useEffect(() => {
    onChange(selectedValue);
  }, [selectedValue]);

  return (
    <div style={containerStyle} className={containerClassName}>
      {optionList.map(({ label, value }) => (
        <div
          onClick={() => {
            optionClick(value);
          }}
          key={value}
          style={
            value === selectedValue
              ? {
                  display: "inline-block",
                  marginRight: "10px",
                  border: "1px solid black",
                  color: "white",
                  backgroundColor: "black",
                  cursor: "pointer",
                }
              : {
                  display: "inline-block",
                  marginRight: "10px",
                  border: "1px solid black",
                  color: "black",
                  backgroundColor: "#fff",
                  cursor: "pointer",
                }
          }
          className={
            value === selectedValue
              ? "selected-option-item option-item"
              : "option-item"
          }
        >
          {label}
        </div>
      ))}
    </div>
  );
};

使用该组件

const Demo = () => {
  const onChange = (value) => {
    console.log("value", value);
  };

  return (
    <RadioGroup
      optionList={[
        { label: "不限", value: -1 },
        { label: "", value: 1 },
        { label: "", value: 0 },
      ]}
      onChange={onChange}
      defaultSelectedValue={1}
      optionCancelable={true}
    />
  );
};