Reputation: 9664
I'm trying to apply a default value to a generic. But whenever the generic is not provided, the generic is defaulting to the value given to an argument. I want to prevent this from happening. Here's a very simple example:
type Data = Record<string, any>
const computeData = <D extends Data = {}>(input: D): D => {
// Does some stuff to input
return input;
}
const res = computeData<{ name: string }>({ name: 'john' })
In this case the type of "res" is { name: string }
as expected. But if the generic is not provided, I need it to default to {}
which is the type given as the default value for the generic.
const computeData = <D extends Data = {}>(input: D): D => {
// Does some stuff to input
return input;
}
const res = computeData({ name: 'john' })
I have simplified the code for this example. I know in the context of this example this is kind of pointless.
EDIT: The actual use case is a network mocker that lets users add objects to mock graphql requests. I want to give them the option to provide resolver types. However, if they do not provide the types they are inferred from initial data passed in - I want to avoid this.
Type error because the initial call to constructor infers type for generic
This works because an explicit type is given when instantiating the class
Upvotes: 5
Views: 3748
Reputation: 1887
You can get around the type inference by using another type parameter and filtering out the default values. You can even do this without having to make type assertions.
type Data = Record<string, any>
type NoInferData<DataType extends Data | void, InputType extends Data> = (
DataType extends void
// If it was void, there was no argument, so the type must be an empty object, or whatever you'd like
? Record<string, never>
// Otherwise it should match the explcitly added data type
: InputType
);
const computeData = <
// Type arugment for the type of Data you are providing
DataType extends Data | void = void,
// Type argument to store the inferred type of the input
// - If its void, return the default type, in this example it's an empty object
// - Otherwise, return the type of Data explicitly provided
InputType extends Data = DataType extends void ? Record<string, never> : DataType
>(input: NoInferData<DataType, InputType>): NoInferData<DataType, InputType> => {
// Does some stuff to input
return input;
}
// Allows expliclty marking the data type
const explicitData = computeData<{ name: string }>({ name: 'john' })
// Doesn't allow data that doesn't match the explcit data type
const explicitDataError = computeData<{ pizza: string }>({ name: 'john' })
// Doesn't allow data that doesn't meet the default (no inferring)
const implictEmptyObjectError = computeData({ name: 'john' })
// Allows the default type of the empty object
const implictEmptyObject = computeData({})
Upvotes: 4