在Typescript / React中,我如何创建一个具有通用类型的组件?

83qze16e  于 2023-08-08  发布在  TypeScript
关注(0)|答案(2)|浏览(138)

我有一个相对通用的组件。它的定义如下所示:

export type CheckboxItem = {
  label: string,
  code: string,
};

export type CheckboxesProps = {
  items: CheckboxItem[],
  handleStateChange: (selected: (CheckboxItem['code'])[]) => void,

};

export interface CheckboxState<T>  {
  [key: CheckboxItem['code']]: boolean,
}

export default function Checkboxes({items, handleStateChange}: CheckboxesProps) {
    const [state, setState] = useState<CheckboxState>(items.reduce((stateObj: CheckboxState, item: CheckboxItem) => {
      stateObj[item.code] = false;
      return stateObj;
    }, {}));

  const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
      setState({
        ...state,
        [event.target.name]: event.target.checked,
      });
      handleStateChange(Object.keys(state).filter(key => state[key]));
  };
...
}

字符串
直到我尝试调用带有对象常量项的复选框(不确定是否正确的类型脚本术语)时,这才有效:

// within another component
export const PrimaryNeeds = {
  foodAllergies: 'foodAllergies',
  foodRestrictions: 'foodRestrictions',
  medicineAllergies: 'medicineAllergies',
  medicalConditions: 'medicalConditions',
  phobias: 'phobias',
  other: 'other',
} as const;

// type checkboxItems = {
//    label: PrimaryNeeds[keyof PrimaryNeeds]
//    code: keyof PrimaryNeeds, 
// }
// The above type isn't actually in my code, I simply do the following instead:

export type PrimaryNeedsCheckboxesProps = {
  langCode: keyof typeof LanguageCodes,
  handlePrimaryNeedsChanged: (selected: (keyof typeof PrimaryNeeds)[]) => void,
}

export default function PrimaryNeedsCheckboxes({langCode, handlePrimaryNeedsChanged}: PrimaryNeedsCheckboxesProps) {

   const checkboxItems = (Object.keys(PrimaryNeeds) as (keyof PrimaryNeeds)[]).map((needKey: keyof PrimaryNeeds) => {
        return {
          label: primaryNeeds[needKey],
          code: needKey,
        };
      });
    // ...
   return (
     <Checkboxes items={checkboxItems} handleStateChange={handlePrimaryNeedsChanged} />
   )


现在我得到了一个关于类型字符串之间的类型不匹配的错误,我猜是PrimaryNeeds键的“union type”:

Type '(selected: ("foodAllergies" | "foodRestrictions" | "medicineAllergies" | "medicalConditions" | "phobias" | "other")[]) => void' is not assignable to type '(selected: string[]) => void'.
  Types of parameters 'selected' and 'selected' are incompatible.
    Type 'string[]' is not assignable to type '("foodAllergies" | "foodRestrictions" | "medicineAllergies" | "medicalConditions" | "phobias" | "other")[]'.
      Type 'string' is not assignable to type '"foodAllergies" | "foodRestrictions" | "medicineAllergies" | "medicalConditions" | "phobias" | "other"'.


我试图设置一个泛型类型,以便Checkbox组件CheckboxItem对象属性类型可以依赖于传入的类型,但后来我遇到了typescript不允许将泛型类型用作索引:

export type CheckboxItem<T> = {
  label: keyof T,
  code: T[keyof T],
};
export type CheckboxesProps<T>= {
  items: CheckboxItem<T>[],
  handleStateChange: (selected: (CheckboxItem<T>['code'])[]) => void,
};
// error points to "key" below
export interface CheckboxState<T>  {
  [key: CheckboxItem<T>['code']]: boolean,
}

An index signature parameter type cannot be a literal type or generic type. Consider using a mapped object type instead.


我读了mapped object文档,但没有看到它有什么帮助,尽管我也不完全理解它们应该如何帮助的机制。我也不理解使用泛型类型作为索引签名参数的问题--这似乎是我想要的,尽管我想这是有原因的。
我尝试使用Key Remapping中推荐的位:

export interface CheckboxState<T>  {
  [K in T]?: boolean,
}


但是Typescript也不喜欢这样:

A mapped type may not declare properties or methods.

**如何为这些props创建泛型类型?”””这是我应该做的吗?

zdwk9cvp

zdwk9cvp1#

TypeScript中的typeinterface基本上是等价的,但Map类型是个例外--它们必须是 types
所以呢

export interface CheckboxState<T> {
  [key in keyof T]: boolean
}

字符串
抛出错误,但是

export type CheckboxState<T> = {
  [key in keyof T]: boolean
}


工作。
(It看起来你的示例代码可能会混淆代码和标签-你的注解掉的checkboxItems使用代码作为键,标签作为值(我假设这是你需要的),但你的CheckboxItems泛型类型使用标签作为键,代码作为值。

iqjalb3h

iqjalb3h2#

我想你可以从一个useCheckboxes钩子开始-

type CheckboxesState = Record<string, boolean>

type UseCheckboxes<T extends CheckboxesState> = {
    keys: Array<keyof T>,
    get: (key: keyof T) => boolean,
    set: (key: keyof T, value: boolean) => void,
}

function useCheckboxes<T extends CheckboxesState>(init: (() => T) | T): UseCheckboxes<T> {
    const [state, setState] = useState(init)
    return useMemo(
        () => ({
            keys: Object.keys(state),
            get: (key) => state[key],
            set: (key, value) => { setState(s => ({ ...s, [key]: value })) },
        }),
        [state],
    )
}

字符串
useCheckboxes的每个示例返回一个唯一的类型-

const mydata = useCheckboxes({
    turbo: true,
    debug: false,
})

// mydata : UseCheckboxes<{ turbo: true, debug: false }>


在使用setget方法时,我们得到了有用的类型自动完成-
x1c 0d1x的数据
现在您可以编写Checkboxes组件了。UseCheckboxes为我们提供了迭代和显示复选框所需的一切,并为每个复选框创建了一个onChange处理程序-

function Checkboxes<T extends CheckboxesState>(props: { items: UseCheckboxes<T> }) {
    const { items } = props
    return (
        <div>
            {items.keys.map(key => (
                <label key={key}>
                  <input
                    type="checkbox"
                    checked={items.get(key)}
                    value={key}
                    onChange={() => items.set(key, !items.get(key))}
                  />
                  {key}
                </label>
            ))}
        </div>
    )
}


这里有一个完整的演示,你可以在浏览器中运行-

const { useMemo, useState } = React

function useCheckboxes(init) {
    const [state, setState] = React.useState(init);
    return React.useMemo(() => ({
        keys: Object.keys(state),
        get: (key) => state[key],
        set: (key, value) => { setState(s => (Object.assign(Object.assign({}, s), { [key]: value }))); },
    }), [state]);
}

function Checkboxes(props) {
    const { items } = props
    return (
        <div>
            {items.keys.map(key => (
                <label key={key}>
                  <input
                    type="checkbox"
                    checked={items.get(key)}
                    value={key}
                    onChange={() => items.set(key, !items.get(key))}
                  />
                  {key}
                </label>
            ))}
        </div>
    )
}

function App() {
    const checkboxes = useCheckboxes({
        foo: true,
        bar: false,
    });
    return <Checkboxes items={checkboxes} />
}

ReactDOM.createRoot(document.querySelector("#app")).render(<App />)
<script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<div id="app"></div>

的字符串

相关问题