I have several objects and types:
interface Car {
make: string;
model: string;
year: number;
}
const car: Car = {make: "Tesla", model: "Cybertruck", year: 2023 }
interface Book {
title: string;
author: string;
}
const book: Book = {title: "Cosmos", author: "Carl Sagan"}
interface Stuff {
cars: Car[],
books: Book[]
}
const stuff: Stuff = {
cars: [car],
books: [book]
}
I want to define a function that takes two arguments:
- dynamic
key
("cars" or "books"), and - an
item
(which will respectively be of ICar or IBook)
and pushes item to stuff[key]
.
Also when calling the function I want it to infer the type of an item based on the key and autosuggest keys.
What I've tried is this:
const addToStuff = ({key, item}: {
key: keyof Stuff,
item: Stuff[typeof key][number] // infers item as Car | Book
}) => stuff[key].push(item)
// .push(item) errors and says
// Argument of type 'Car | Book' is not assignable to parameter of type 'Car & Book'.
// Type 'Car' is not assignable to type 'Car & Book'.
// Type 'Car' is missing the following properties from type 'Book': title, author
here is the link of the ts playground
CodePudding user response:
In order to make it work, you need to create higher order function which is context agnostic. COnsider this example:
interface Car {
make: string;
model: string;
year: number;
}
const car: Car = { make: "Tesla", model: "Cybertruck", year: 2023 }
interface Book {
title: string;
author: string;
}
const book: Book = { title: "Cosmos", author: "Carl Sagan" }
type Stuff = {
cars: Car[],
books: Book[]
}
const stuff: Stuff = {
cars: [car],
books: [book]
}
const withStuff = <
Prop extends PropertyKey,
Value, Data extends Record<Prop, Value[]>
>(data: Data) =>
<Key extends keyof Data>({ key, item }: {
key: Key,
item: Data[Key][number]
}) => {
data[key].push(item)
}
const addToStuff = withStuff(stuff)
addToStuff({ key: 'cars', item: { make: "Tesla", model: "Cybertruck", year: 2023 } }) // ok
addToStuff({ key: 'books', item: { make: "Tesla", model: "Cybertruck", year: 2023 } }) // expected error
As you might have noticed, I have added higher order function to addToStuff
. Not it is withStuff
which returns addTuStuff
. You can also use any other data structure where each value is an array, not only Stuff
data shape.
You can find more examples with currying
in my article