I want to call a function inside another function and use its type parameter as "default" type when calling child function.
Is it possible in Typescript?
// Parent interface
interface IParent {
id: number;
name: string;
}
// Child interface with foreign key
interface IChild {
id: number;
name: string;
ParentId: number;
}
// Parent function declaration:
function select<T>(query: {
select: string,
join: string,
}) {
return `${query.select} ${query.join}`;
}
// Child function declaration (F type is optional):
function buildJoin<T, F = Record<any, any>>(foreignKey: keyof T, otherColumn: keyof F): string {
return 'JOIN f on t.foreignKey = f.otherColumn';
}
// Strong typing:
select<IParent>({
select: 'select * from Parent',
join: buildJoin<IChild, IParent>('ParentId', 'id'), // explicitly typed "ParentType"
});
// Requested behaviour:
select<IParent>({
select: 'select * from Parent',
join: buildJoin<IChild>('ParentId', 'id'), // if 2nd type parameter omitted it should be taken from parent function
});
CodePudding user response:
You're trying to infer a type argument to buildJoin
based on where it's called, which you can't do. So in that sense, the answer to your question is "no."
As an alternative, you might write a fluent interface in which select
returns an object with a join
method, which can then inherit the type parameter's value. Here's a rough example (not intended to be an actual robust implementation [though it does work for this limited example], just an example):
// Parent interface
interface IParent {
id: number;
name: string;
}
// Child interface with foreign key
interface IChild {
id: number;
name: string;
ParentId: number;
}
// Parent function declaration:
function select<ParentType>(select: string) {
return {
query: select,
join<ChildType>(foreignKey: keyof ChildType, otherColumn: keyof ParentType) {
this.query = "\nJOIN f on t.foreignKey = f.otherColumn";
return this;
},
build() {
return this.query;
},
};
}
// Strong typing:
const sql = select<IParent>("select * from Parent")
.join<IChild>("ParentId", "id")
.build();
CodePudding user response:
There is another option relying on tagged primitive types, currying and function composition.
const select = <Parent>(select: string) =>
select as string & { _parent: Parent };
const join = <Child>(foreignKey: keyof Child) => <Parent>(otherColumn: keyof Parent) =>
(select: string & { _parent: Parent }) =>
`${select}\nJOIN f on t.foreignKey = f.otherColumn`;
You can use it like so
import { pipe } from 'fp-ts/lib/function'
const ok1 = pipe(
select <IParent>("select * from Parent"),
join <IChild>("ParentId") ("id")
)
const ok2 = pipe(
select <IParent>("select * from Parent"),
join <IChild>("ParentId") <IParent>("id")
)
CodePudding user response:
yes it is possible. By calling the super() method in the constructor method, we call the parent's constructor method and gets access to the parent's properties and methods. Inheritance is useful for code reusability: reuse properties and methods of an existing class when you create a new class.