reactjs - Hur kan jag skriva HTML select-element så att event.target.value refererar till alternativets typvärde?
Jag har en komponent som heter Select och den har options, value och onChange props. options är en array av objekt med attributen label och value. här är min Select.tsx kod
import * as React from "react";
import type { ChangeEventHandler, ReactElement } from "react";
export interface OptionType {
value: string | number | null;
label: string;
}
export const Select = ({
value,
onChange,
options
}: {
value: string | number | null;
onChange: ChangeEventHandler<HTMLSelectElement>;
options: OptionType[];
}): ReactElement => (
<select value={value ?? ""} onChange={onChange}>
{options.map((option) => (
<option value={option.value ?? ""} key={option.value}>
{option.label}
</option>
))}
</select>
);
Jag använde Select-komponenten så här, problemet ligger i onChange-händelsehanteraren nedan. Jag vill att event.target.value ska ha typen UnitType.
import * as React from "react";
import { Select } from "./Select";
const { useState } = React;
type UnitType = "cm" | "m" | "km";
interface UnitOptionType {
label: string;
value: UnitType;
}
// Select options array with a custom type
const timeOptions: UnitOptionType[] = [
{ label: "Centimeter", value: "cm" },
{ label: "Meter", value: "m" },
{ label: "Kilometre", value: "km" }
];
export const App = () => {
const [value, setValue] = useState<UnitType | null>(null);
return (
<div>
<Select
options={timeOptions}
value={value}
onChange={(event) => {
// I WANT this to have the type of UnitType
// but it is string
setValue(event.target.value)
}}
/>
</div>
);
};
Du kan se min kod på denna Codesandbox-länk:
https://codesandbox.io/s/xenodochial-matan-i3p9z?file=/src/App.tsx:0-765
Svar #1
Värdet av HTMLSelectElement kommer alltid att vara en string. Det är typat på det sättet i lib.dom-standardtypbiblioteket som följer med TypeScript. Det finns inte mycket du kan göra för att ändra på det.
I det här fallet skulle jag göra <Select>-komponenten generisk så att den vet typerna på sina alternativ.
Gör sedan så att onChange-callbacken tar emot denna typ, istället för hela händelsen.
Slutligen kastar du värdet till den typen, eftersom du vet att värdet kommer att vara värdet av ett av alternativen.
Sätter man ihop det får man:
export interface OptionType<T extends string | number | null> {
value: T;
label: string;
}
export const Select = <T extends string | number | null>({
value,
onChange,
options
}: {
value: T;
onChange: (newValue: T) => void;
options: OptionType<T>[];
}): ReactElement => (
<select value={value ?? ""} onChange={(event) => onChange(event.target.value as T)}>
{options.map((option) => (
<option value={option.value ?? ""} key={option.value}>
{option.label}
</option>
))}
</select>
);
Nu tillhandahåller onChange T, vilket innebär unionen av värdena för alternativen, så du kan skicka det värdet direkt till setState, utan att behöva gå via händelseobjektet alls.
<Select
options={timeOptions}
value={value}
onChange={setValue}
/>
Typescript-lekplats med ovanstående kod och inga typefel.
En sista notering: Du använder typen för värdena som string | number | null, men som nämnts ovan kommer .value alltid att returnera en sträng, så du kommer aldrig få null eller number från det. Så att stödja dessa typer blir mer komplicerat och du kan inte göra det enbart i typvärlden eftersom du behöver veta hur man konverterar string till den typ du vill ha vid körning.
