In the 3rd party lib, There's the following:
export interface Event extends Log {
args?: Result;
}
export interface TypedEvent<EventArgs extends Result> extends Event {
args: EventArgs;
}
export type InstallationPreparedEvent = TypedEvent<
[
string,
string
] & {
sender: string;
permissions : string;
}
>;
Then, in my code, I got this.
function prepareInstall(...): Promise<InstallationPreparedEvent> {
const event = ...;
return event;
}
Then, I got these choices, but I like none of them. The reason is, I simplified it, but it actualy contains 8 args, so code becomes ugly.
// choice 1.
function run() {
const event = await prepareInstall();
string sender = event.args.sender;
}
// choice 2
function run() {
const { event : {sender } } = await prepareInstall();
}
// choice 3
function run() {
const { sender } = (await prepareInstall()).args;
}
What I want to achieve is from prepareInstall, I want to return event.args directly so I'd use it like this:
const { sender } = await prepareInstall();
But returning event.args doesn't solve it as typescript in the run still requires me to do: event.args.sender. The important thing is I need returned type in prepareInstall to be InstallationPreparedEvent since in run, I want typescript to tell me what values exist(sender, permissions).
Hope I made sense.
If I understand correctly, you want to return only the arg prop from InstallationPreparedEvent, but with correct typing (right?).
You can specify a prop's type through bracket notation:
function prepareInstall(...): Promise<InstallationPreparedEvent["args"]> {
const event = await ...;
return event.args;
}
With this, you will get type hinting:
function run() {
const eventArgs = await prepareInstall();
const {sender} = eventArgs; // no complaints
const foo = eventArgs.foo; //Error: Property 'foo' does not exist on type '[string, string] & { sender: string; permissions: string; }'
}
Related
I have a function called invoke which currently is typed like this:
export type InvokeType = <T = InvokeResponseTypes, Params = any>(
methodName: InvokeMethodNameTypes,
args?: Params
) => Promise<T>;
// for context, Invoke is a signalR method to call methods on a SignalR server.
InvokeMethodNameTypes are simply strings that call methods, like "GetUser" or "GetEvent" etc.
Each InvokeMethodNameType has corresponding args (Params) or none at all and I would use it like so:
const data = await invoke<GetEventResponseType, GetEventArgs>("GetEvent", { eventId })
This works but I do not feel like it is the correct approach to what I actually want to achieve.
Is it possible to type the invoke function so when passing a string such as "GetEvent" the arguments are automatically inferred to the correct object type?
Based on the code in the sandbox:
function myFunction(stringArg: 'person'): { name: string, age: number }
function myFunction(stringArg: 'car'): { make: string, model: string }
function myFunction(stringArg: any): any
{
if (stringArg === "person") {
return {
name: "John",
age: 20
};
}
if (stringArg === "car") {
return {
make: "Mazda",
model: "6"
};
}
}
let personResult = myFunction("person");
let personName = personResult.name; // Okay
let personAge = personResult.age; // Okay
let personMake = personResult.make; // Error
let personModel = personResult.model; // Error
let carResult = myFunction("car");
let carName = carResult.name; // Error
let carAge = carResult.age; // Error
let carMake = carResult.make; // Okay
let carModel = carResult.model; // Okay
Playground
Consider this:
interface TArguments {
width?: number,
height?: number
}
interface TSomeFunction {
someFunction ({width, height}: TArguments): void
}
const someObject: TSomeFunction = {
someFunction ({width, height}) {
// do something, no return
}
}
Both paremeters are optional, this means that I can call someFunction like that:
someObject.someFunction() // but it is not passing through
I'm geting an Error "Expected 1 arguments, but got 0".
Am I missing something ?
How do I write an Interface when all parameters are optional?
Your interface should not concern about default values or destructuring, as those are implementation details. Just declare the parameter as optional, as if it was a scalar:
interface TSomeFunction {
someFunction (size? : TArguments): void
}
The implementation can then define both:
const someObject: TSomeFunction = {
someFunction({ width, height } = {}) {
// do something, no return
}
}
Your error must be coming from a different place (or try restarting your IDE, that is a source of a lot of frustration :)). Your second approach looks correct:
const someFunction = ({width, height}: TArguments = {}) => { ... }
Here is a working TypeScript playground link
EDIT You need to also specify that the parameter itself, not only its keys, is optional:
interface TSomeFunction {
someFunction (arguments?: TArguments): void; // Notice the question mark after the parameter expression
}
// Now you can either add a default value to your function
const someObject: TSomeFunction = {
someFunction ({width, height}: TArguments = {}) {}
}
// Or leave it optional without specifying a default value
const someObject: TSomeFunction = {
someFunction ({width, height}?: TArguments) {} // Notice the question mark
}
Here's my code to test equality of some class objects. See my other question if you want to know why I'm not just doing
expect(receivedDeals).toEqual(expectedDeals) and other simpler assertions.
type DealCollection = { [key: number]: Deal }; // imported from another file
it("does the whole saga thing", async () => {
sagaStore.dispatch(startAction);
await sagaStore.waitFor(successAction.type);
const calledActionTypes: string[] = sagaStore
.getCalledActions()
.map(a => a.type);
expect(calledActionTypes).toEqual([startAction.type, successAction.type]);
const receivedDeals: DealCollection = sagaStore.getLatestCalledAction()
.deals;
Object.keys(receivedDeals).forEach((k: string) => {
const id = Number(k);
const deal = receivedDeals[id];
const expected: Deal = expectedDeals[id];
for (let key in expected) {
if (typeof expected[key] === "function") continue;
expect(expected[key]).toEqual(deal[key]);
}
});
});
The test passes fine, but I'm getting a Flow error on expected[key]:
Cannot get 'expected[key]' because an index signature declaring the expected key / value type is missing in 'Deal'
I can paste in code from Deal by request, but I think all you need to know is that I haven't declared an index signature (because I don't know how!).
I've searched around a bit but I can't find this exact case.
Update: I can eliminate the errors by changing deal and expected thusly:
const deal: Object = { ...receivedDeals[id] };
const expected: Object = { ...expectedDeals[id] };
And since I'm comparing properties in the loop this isn't really a problem. But I would think that I should be able to do this with Deals, and I'd like to know how I declare the index signature mentioned in the error.
PS. Bonus question: In some world where a mad scientist crossbred JS with Swift, I imagine you could do something like
const deal: Object = { ...receivedDeals[id] where (typeof receivedDeals[id] !== "function" };
const expected = // same thing
expect(deal).toEqual(expected);
// And then after some recombining of objects:
expect(receivedDeals).toEqual(expectedDeals);
Is this a thing at all?
Edit:
Adding a bit of the definition of Deal class:
Deal.js (summary)
export default class Deal {
obj: { [key: mixed]: mixed };
id: number;
name: string;
slug: string;
permalink: string;
headline: string;
// ...other property definitions
constructor(obj?: Object) {
if (!obj) return;
this.id = obj.id;
this.name = obj.name;
this.headline = obj.headline;
// ...etc
}
static fromApi(obj: Object): Deal {
const deal = new Deal();
deal.id = obj.id;
deal.name = obj.name;
deal.slug = obj.slug;
deal.permalink = obj.permalink;
// ...etc
return deal;
}
descriptionWithTextSize(size: number): string {
return this.descriptionWithStyle(`font-size:${size}`);
}
descriptionWithStyle(style: string): string {
return `<div style="${style}">${this.description}</div>`;
}
distanceFromLocation = (
location: Location,
unit: unitOfDistance = "mi"
): number => {
return distanceBetween(this.location, location);
};
distanceFrom = (otherDeal: Deal, unit: unitOfDistance = "mi"): number => {
return distanceBetween(this.location, otherDeal.location);
};
static toApi(deal: Deal): Object {
return { ...deal };
}
static collectionFromArray(array: Object[]) {
const deals: DealCollection = {};
array.forEach(p => (deals[p.id] = Deal.fromApi(p)));
return deals;
}
}
An index signature (or indexer property) is defined as [keyName: KeyType]: ValueType. DealCollection is a great example: the keyName is key, the KeyType is number and the ValueType is Deal. This means that whenever you access a number property of an object of type DealCollection, it will return a Deal. You will want to add a similar expression to the definition of Deal in order to access arbitrary properties on it. More information can be found at the Objects as maps section in the Flow documentation.
I'm in the process of optimizing some code in my library, however, I have a bit of an issue regarding why bracket notation isn't working when trying to call an imported class.
Parameter type accepts a string that is camelCased, such as: myString.
The parameter data can be anything.
import { foo } from './example';
export const find = (type: string, data: any) => {
// This next line effectively deletes the end of the string starting
// from the first capital letter.
const f = type.replace(/[A-Z][a-z]+/, '');
try {
return [f][type](data);
} catch (e) {
return e;
}
};
this is what I expect it to look like if I was to visualize it using dot notation:
foo.fooBar(someRandomData)
This should call the static method fooBar(data) on the imported class foo,
however, I receive an error message:
TypeError: [f][type] is not a function
If I was to revert it back to my if..else if style, it works:
if (type.startsWith('foo')) return foo[type](data);
How can I do what is desired above without getting the defined error message?
Thank you for your help in advance!
EDIT: This is an example I modified from already existing code, therefore, I fixed a few typos.
EDIT #2: as per requested, the imported class foo looks like this:
export class foo{
static fooBar(data){
// Do stuff with data...
}
In the end you need some reference to the classes or object to get started with. Here is a working example of how you could do this type of functionality, but you have start with a map of your class instances so you can get to them:
class foo {
fooBar(data: any) { return { name: 'foo', data } };
}
class example {
exampleFood(data: any) { return { name: 'example', data } };
}
var lookup: { [classes: string]: any; } = { };
lookup['foo'] = new foo();
lookup['example'] = new example();
const find = (encodedType: string, data: any) => {
// This next line effectively deletes the end of the string starting
// from the first capital letter.
const f = encodedType.replace(/[A-Z][a-z]+/, '');
try {
return lookup[f][encodedType](data);
} catch (e) {
return e;
}
};
alert(JSON.stringify(find("fooBar", "Found you foo")));
alert(JSON.stringify(find("exampleFood", "Found you example")));
I would suggest you instead move over to using the nodeJS built-in EventEmitter.
You can do something like:
import * as EventEmitter from 'events';
import { foo } from './example';
import { bar } from './example2';
export const discordEventEmitter = new EventEmitter();
discordEventEmitter.on('fooBar', foo.fooBar);
discordEventEmitter.on('fooToo', foo.fooToo);
discordEventEmitter.on('barBell', bar.barBell);
Then, when you want to fire an event, you can simply:
discordEventEmitter.emit('fooBar', someData);
You can also simplify the event handler registration by writing:
const fooProps = Object.getOwnPropertyNames(foo) as (keyof typeof foo)[];
fooProps.filter(k => typeof foo[k] === 'function').forEach(funcName => {
discordEventEmitter.on(funcName, foo[funcName]);
});
const barProps = Object.getOwnPropertyNames(bar) as (keyof typeof bar)[];
fooProps.filter(k => typeof bar[k] === 'function').forEach(funcName => {
discordEventEmitter.on(funcName, bar[funcName]);
});
How to dynamically extend class with method in TypeScript? Any examples?
I try:
UsersBlocksMyOrders.prototype.myFunc = function():any{
alert('23434');
};
But compiler give me a error.
Most often, you need to do something like:
interface UsersBlocksMyOrders {
myFunc(): any;
}
Otherwise the compiler doesn't know about it.
It even works with existing classes. For example:
interface String {
logit(): void;
}
String.prototype.logit = function () {
console.log(this);
}
let a = "string";
a.logit();
(code in playground)
Because you want to change something in a different module, which is called Module Augmentation, you need to do something like:
Import { UsersBlocksMyOrders } from "../pages/users/blocks/myorders";
declare module "../pages/users/blocks/myorders" {
interface UsersBlocksMyOrders {
logit(): void;
}
}
UsersBlocksMyOrders.prototype.logit = function () { console.log(this); }
Whenever possible (which it seems to be for you), edit the source code directly. Doing it like this should only be done on an as-needed basis.
This may help (from here):
function extend<T, U>(first: T, second: U): T & U {
let result = <T & U>{};
for (let id in first) {
(<any>result)[id] = (<any>first)[id];
}
for (let id in second) {
if (!result.hasOwnProperty(id)) {
(<any>result)[id] = (<any>second)[id];
}
}
return result;
}
class Person {
constructor(public name: string) { }
}
interface Loggable {
log(): void;
}
class ConsoleLogger implements Loggable {
log() {
// ...
}
}
var jim = extend(new Person("Jim"), new ConsoleLogger());
var n = jim.name;
jim.log();