type transaction = {
uid: string,
paymentMode : string,
}
I want to get key name from type for e.g.
function getFieldName(input) {
returns a string with key name
}
const tran : transaction = {}
getKeyName(tran.paymentMode) returns 'paymentMode'
I read many articles and tried some solutions, so I understand we will at least need to create object for that type which is fine.
I want to do this because key name of a type is required and I also want to keep auto-complete for key name when we type something like obj.key to avoid mistakes.
I know, I can create a separate object from type with constant key, value pair. I want to avoid this, because then we will need to change same thing at 2 places whenever there is a change in type, which can be missed.
Edit:
I was looking for exactly this - nameof in c#. #Aleksey L. told this in comments.
This seems to be the type of function signature that you want:
Note that it meets your criteria of compatibility with IntelliSense auto-complete/suggest:
TS Playground
type Transaction = {
uid: string;
paymentMode: string;
};
function getFieldName <T extends object, K extends keyof T>(o: T, key: K): K {
return key;
}
const transaction: Transaction = {
uid: window.crypto.randomUUID(),
paymentMode: 'unknown',
};
getFieldName(transaction, 'uid'); // "uid"
getFieldName(transaction, 'paymentMode'); // "paymentMode"
getFieldName(transaction, 'asdf'); /*
~~~~~~
Argument of type '"asdf"' is not assignable to parameter of type 'keyof Transaction'.(2345) */
It is not possible to derive a string property name in JavaScript (or TypeScript) by passing a reference to the property value. JavaScript simply doesn't have this kind of meta-introspection capability at present.
It appears that your question is not just about type safety, but about DX/ergonomics. The above solution does require providing the property name, but the number of extra characters to type before getting the IntelliSense prompt is only 1 (2 if you consider whitespace):
// For: obj.prop:
// desired:
fn(obj.p_)
// 012^
// proposed:
fn(obj, 'p_')
// 01234^
// The closing quote is auto-inserted by IntelliSense, just like the closing parenthesis.
I was reading the changes in typescript 4.0, and it has a section called Labeled Tuple Elements. My question isn't about this feature, I get it and I get its value. My question is about the example they used:
function foo(...args: [string, number]): void { // ... }
…should appear no different from the following function…
function foo(arg0: string, arg1: number): void { // ... }
The question is, what is the point of defining a function that first way? As far as I can tell, the second way is objectively better: The same signature, easier to access elements inside the body, self-documenting (the parameters have names), etc. What am I missing? What value does the first example have?
The only advantage I can think of for the top, is that you can refer to args. That might be useful if you just plan to pass this off to another function? Is that why you can do this; for that scenario?
I get that this can be looked at as opinion based, but the answers I'm looking for aren't: Is there anything else you can do with this syntax that I'm not understanding besides what I've mentioned?
The rest tuple syntax lets you abstract over function parameter lists in a way that a regular call signature does not. Abstraction is rarely useful if you're only going to use it once. Imagine if you are asked "what's the point of functions in JavaScript?" and being given this example:
// why do this:
const val = ((x: number) => x * x)(5);
// when the following is simpler:
const val = 5 * 5;
Surely, in isolation, there is no reason to create a function like x => x * x, call it once, and then throw it away. The direct multiplication is obviously superior. The problem here is not the functionality, though, but the example.
So let's look at rest tuples and some of the different things they can be used for once you're allowed to abstract over parameter lists:
Re-use of parameter lists in different function types
Let's say you have a set of functions that all take the same list of parameters, but do not have the same return type:
declare function foo(x: string, y: number, z: boolean): string;
declare function bar(x: string, y: number, z: boolean): number;
declare function baz(x: string, y: number, z: boolean): boolean;
// ... and maybe more
Assuming this is not merely coincidence, and the functions are related in some way, you might want to change the list of parameters at some point and would prefer to do it once and not once for each function. With rest tuples you can do this:
type Params = [x: string, y: number, z: boolean];
declare function foo(...args: Params): string;
declare function bar(...args: Params): number;
declare function baz(...args: Params): boolean;
which is equivalent. If I want to, say, add a fourth parameter, I can do it in Params.
Manipulation of lists of parameters
Let's say I have related functions which have some parameters :
declare function qux(x: string, y: number, z: boolean): number
declare function quux(w: Date, x: string, y: number, z: boolean): string;
Possibly because quux()'s implementation calls qux(). You can rewrite quux()'s signature to use a rest tuple instead:
declare function quux(w: Date, ...rest: Parameters<typeof qux>): string;
I'm using the Parameters utility type to extract the tuple type of parameters from the qux function. If qux()'s parameters change, then quux()'s parameters will change automatically.
Unions of parameter lists instead of overloads
Imagine we have the following overloaded frob() function:
function frob(k: "number", val: number): void;
function frob(k: "string", val: string): void;
function frob(k: "number" | "string", val: number | string) {
if (k === "number") {
console.log(val.toFixed(1)); // error, oops
} else {
console.log(val.toUpperCase()); // error, oops
}
}
As a caller, you can call frob("number", 123) or frob("string", "hello") but not frob("number", "hello") for instance. The implementation of frob() is not so great, though, because the compiler really doesn't understand that k === "number" can only happen when val is a number. The parameters k and val in the implementation are just uncorrelated unions. So you get some errors. This is, of course, easy enough to work around with type assertions.
But consider how much more expressive the following version of frob() is:
function frob(...args: [k: "number", val: number] | [k: "string", val: string]) {
if (args[0] === "number") {
console.log(args[1].toFixed(1)); // okay
} else {
console.log(args[1].toUpperCase()); // okay
}
}
From the caller's point of view, this is pretty much the same as before. Unions of rest tuples end up acting like multiple call signatures. But the implementation is different. The compiler now understands that the args rest argument is a discriminated union; if the first element is "number", then the second element will definitely be a number, and the compiler knows it. So it type checks.
Generic functions relating to parameter lists
Consider the following function withConsoleMessage(). It takes a function as input, and returns another function which takes a message string argument first, and then all the arguments of the input function. When you call that function, it logs the message and calls the original function:
function withConsoleMessage<A extends any[], R>(
cb: (...a: A) => R
): (msg: string, ...a: A) => R {
return (msg, ...a) => (console.log(msg), cb(...a));
}
const atan2WithMessage = withConsoleMessage(Math.atan2);
const ret = atan2WithMessage("hello!", 4, 4); // "hello!"
console.log(ret * 4); // 3.141592653589793
Look at that; the compiler knows that atan2WithMessage() takes a string and then two numbers. Without tuple-type rest elements, it would be difficult to begin to give withConsoleMessage() a type signature. I suppose you could give it a bunch of overloads so it accepted callbacks of different arities, up to some arbitrary limit:
function withConsoleMessage<R>(cb: () => R): (msg: string) => R;
function withConsoleMessage<R, A>(cb: (a: A) => R): (msg: string, a: A) => R;
function withConsoleMessage<R, A, B>(cb: (a: A, b: B) => R): (msg: string, a: A, b: B) => R;
function withConsoleMessage<R, A, B, C>(cb: (a: A, b: B, c: C) => R): (msg: string, a: A, b: B, c: C) => R;
function withConsoleMessage(cb: Function) {
return (msg: string, ...a: any[]) => (console.log(msg), cb(...a));
}
That would sort of work, and before TypeScript 3.0, this is what you had to do if you wanted to approximate this behavior. It was ugly and brittle compared to a generic rest tuple.
I think I'll stop there for now; I don't know how much more time I want to spend on an answer to a question that's already accepted a different answer, after all! Suffice it to say that abstractions like rest tuples open up many possibilities.
Playground link to code
I think that one important thing to understand is that typescript's typing is just a type layer for static checking that compiles down into javascript completely unaffected by the typesystem. If everything perfectly adhered to the types they were given, then yes, ...args: [string, number] would be essentially equivalent to arg0: string, arg1: number except for the fact that it puts the arguments in an array.
However, if the types aren't adhered to (maybe due to incorrect casts or calling a typescript library from javascript), then there is a subtle difference in the two in that the first one will handle any number of arguments and the array will be the appropriate length whereas the second will ignore any arguments past the second and fill in unspecified arguments with undefined.
Another even subtler difference is that rest arguments don't count towards Function.prototype.length, which means your first function will have a length of 0 whereas the second correctly reports a length of 2.
Will these differences realistically matter? Probably not. Is there any reason to use the first? Unless you really want an array of the two arguments for whatever reason, probably not. The second is more readable, and allows you to easily add default argument values. Why does typescript allow the first? Because it should be a superset of javascript, and javascript has rest arguments, so typescript should supply a way to type it, even if of finite length.
I have a function that deals with two type of parameters: string and object. There are 3 different object structure expected. That makes up to 4 possibles types:
type URL = string;
type Item = {| href: string |};
type ItemList = {| results: Item[] |};
type Params = {| offset?: number, limit?: number |};
So the function's options type is:
type Options = URL | Item | ItemList | Params;
And here's the actual function:
// No properties of "Params" are required
// so if they're all omitted, Params === {}
function request(opts: Options = {}) {
if (typeof opts === 'string') {
return 'opts is an URL';
}
if (typeof opts.href === 'string') {
return 'opts is an item';
}
if (Array.isArray(opts.results)) {
return 'opts is a list of items';
}
// Three of the four types are caught
// we're left with "Params" which may or may not
// have a "offset" and "limit" property.
// Destructuring to undefined values is fine here.
// Still, flow complains about the type not being met.
const { offset, limit } = opts;
return 'opts are parameters';
}
Flow is complaining about a few things:
opts = {} throws an incompatibility error. Since no properties of Params are required, shouldn't empty objects match it too? Note that opts = { offset: undefined } clears the error.
Properties "offset" and "limit" are declared not found. Since none of them are required, shouldn't undefined be valid values? And thus destructuring fine?
To summarize my question:
How do you define a type that accepts different types of object, with one having no required properties?
Edit: run the flow code in your browser.
Check out Flowtype - making a sealed empty object for an answer to your first question.
For your second, the answer basically is that Flow does not fully support this sort of type refinement. Disjoint unions were designed for this use case, though you do have to add a discriminator property to all of your objects. Obviously, this will require some nontrivial changes to your code. It's up to you to decide if this is feasible.
If it's not feasible, the best thing to do is probably to just cast through any in this function, and make sure you provide a type annotation for the return value. It looks like it's small enough that it's easy for a human to reason about, so the benefit of typechecking here may not be worth the effort. Of course that's a judgement call that is best left to you.
Suppose I have a function that needs to return something of type StringMap<string, boolean>. An example return that is valid is: {"required": true}.
Now, I've read in a tutorial (it's not important which tutorial) you can create a function that has return type of { [s: string]: boolean } and this is the same return type as the StringMap above.
I don't understand how are these two the same return type? And how the second version is even valid?
All the return types I have seen in TypeScript have only included the type in the past i.e. boolean, number, any. For example function (): number {}. In our second version we use s: string which means we give the variable a name, and specify it's type, how are we suddenly allowed to give the variable the name s?
On top of that we put this string inside an array [s: string] as the key in the second version (therefore the key is now an array). While a StringMap has a string as the key.
The syntax is a bit different than you think. It's a unique syntax for defining dictionaries\maps.
{ [s: string]: boolean } means: a map, which has a key with type string, and it's values are boolean. The s means nothing at all, it could have been anything you want.
(Then why give it a name in the first place? my guess is to make the code more clear, when mapping more complex types. Sometimes you'll want to call the index id, sometimes address, etc..)
More info here, indexed types is what you want.
The Typescript handbook online isn't the most friendly documentation ever, but I think it's good enough and I recommend everyone who uses typescript to at least skim through it. Especially since in 2.0+ they added a bunch of crazy\awesome type features like mapped types.
The type { [s: string]: boolean } defines an indexable type interface.
What you see as an array is just the syntax decided to define the index of the interface.
The name of the key, as far as I know, is ignored and only the type is what matters.
This code { [s: string]: boolean } is defining an indexable interface where the indices are strings and the values are booleans.
I assume that the definition of StringMap is as follows:
export interface StringMap<T, U> = { [s: T]: U };
Which is kind of redundant if you ask me (as the name says that it should be a string map, so the keys should be strings). I would have declared the IStringMap interface as:
export interface IStringMap<T> = { [key: string]: T };
Interfaces in TypeScript just define the "shape" of the object. The previous three definitions have equivalent shapes, so this is perfectly valid:
function fn() : IStringMap<boolean> {
let myMap : StringMap<string, bool> = { };
myMap["foo"] = true;
myMap["bar"] = false;
myMap["baz"] = true;
return myMap;
}
let foo: { [bazzinga: string]: boolean } = fn();
I trying to call a a function inside a array. I get an error message : The expression type { function } is not assignable to boolean.
Declaration:
public conditions: { (value: any): bool; }[] = [];
Usage:
var myBool: bool = conditions["myKey"]("someParam");
Whats wrong here ? Do i need to cast here ?
This works in the TypeScript playground, which uses 0.8.3. Are you sure this is actually the code in question? Indexing an array by a string produces a value of type any, so the function invocation there is sort of irrelevant for type purposes since it will just produce an any as well.