javascript - Hur man korrekt typar Object.entries till min anpassade typ?
Jag har dessa 3 fält i mitt formulär (sku, sku_variation, namn) och jag vill använda dem för att skapa en ny produkt.
Jag kom på denna lösning genom att först omvandla parsedData till okänt, men det ser helt enkelt ut som dålig praxis.
export type TProduct = {
id: string,
sku: number,
skuVariation: number,
name: string,
}
export type TCreateProduct = Omit<TProduct, 'id'>;
const createProduct = async(parsedData:TCreateProduct) => {
fetch('http://localhost:3333/api/products/',{
method: 'POST',
body: JSON.stringify(parsedData)
})
return '';
}
const handleSubmit = (e:React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
const form = new FormData(e.target as HTMLFormElement);
const parsedData = Object.fromEntries(form.entries()) as unknown as TCreateProduct;
createProduct(parsedData);
}
Utan as unknown as TCreateProduct-omvandlingen skulle felet som kastas i createProduct-anropet bli:
Argument of type '{ [k: string]: FormDataEntryValue; }' is not assignable to parameter of type 'TCreateProduct'.
Type '{ [k: string]: FormDataEntryValue; }' is missing the following properties from type 'TCreateProduct': sku, skuVariation, name
Svar #1
TypeScript har rätt att tvinga dig att göra den hackiga dubbla typen eftersom din typomvandling är ogiltig. FormData kommer endast att ha värden av typen string eller File, men typen TCreateProduct har egenskaper med number-värden. Detta innebär att om en nyckel matchar en av dessa egenskaper med ett nummervärde, skulle typen på egenskapen i det resulterande objektet vara felaktig.
Om du ändrar din typ till att vara en post av strängar, kan du ta bort den extra omvandlingen till unknown. Naturligtvis kommer du förmodligen att ha ett problem med createProduct eftersom begärans kropp kommer att serialiseras med värden för sku och skuVariation kodade som strängar.
export type TCreateProduct = Record<Exclude<keyof TProduct, 'id'>, string>;
const handleSubmit = (e:React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
const form = new FormData(e.target as HTMLFormElement);
const parsedData = Object.fromEntries(form.entries()) as TCreateProduct;
createProduct(parsedData);
}
Alternativt kan du mappa värdena och göra lämpliga omvandlingar för numeriska värden. Det kommer inte se lika snyggt ut som att göra Object.fromEntries(), men det blir mest exakt.
export type TCreateProduct = Omit<TProduct, 'id'>;
const handleSubmit = (e:React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
const form = new FormData(e.target as HTMLFormElement);
const parsedData: TCreateProduct = {
name: form.get('name') as string,
sku: parseInt(form.get('sku') as string),
skuVariation: parseInt(form.get('sku') as string)
};
createProduct(parsedData);
}
Svar #2
Du kan använda ett tredjepartsbibliotek för att fungera som en TypeGuard och värdevalidator. Det finns några, som Joy och Zod. Jag kommer att ge dig ett exempel med Zod
const createProductSchema = z.object({
sku: z.number(),
skuVariation: z.number(),
name: z.string().min(3),
});
const productSchema = createProductSchema.extend({
id: z.number(),
});
export type TProduct = z.infer<typeof productSchema>;
export type TCreateProduct = z.infer<typeof createProductSchema>;
const createProduct = async (parsedData: TCreateProduct) => {
fetch('http://localhost:3333/api/products/', {
method: 'POST',
body: JSON.stringify(parsedData),
});
return '';
};
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
const form = new FormData(e.target as HTMLFormElement);
const parsedData = createProductSchema.parse(
Object.fromEntries(form.entries())
);
createProduct(parsedData);
};
Ha i åtanke att createProductSchema.parse kommer att kasta ett fel om data inte är korrekt
