Is there any way to reference an inferred type in TypeScript?
In the following example we get nice inferred types.
function Test() {
return {hello:"world"}
}
var test = Test()
test.hello // works
test.bob // 'bob' doesn't exist on inferred type
But what if I want to define a function that takes a parameter of the type: "Whatever Test returns", without explicitly defining the interface?
function Thing(test:???) {
test.hello // works
test.bob // I want this to fail
}
This is a workaround, but it gets hairy if Test has parameters of its own.
function Thing(test = Test()) {} // thanks default parameter!
Is there some way to reference the inferred type of whatever Test returns? So I can type something as "Whatever Test returns", without making an interface?
The reason I care is because I usually use a closure/module pattern instead of classes. Typescript already lets you type something as a class, even though you can make an interface that describes that class. I want to type something as whatever a function returns instead of a class. See Closures in Typescript (Dependency Injection) for more information on why.
The BEST way to solve this is if TypeScript added the abilty to define modules that take their dependencies as parameters, or to define a module inside a closure. Then I could just use the spiffy export syntax. Anyone know if there are any plans for this?
It's now possible:
function Test() {
return { hello: "world" }
}
function Thing(test: ReturnType<typeof Test>) {
test.hello // works
test.bob // fails
}
https://www.typescriptlang.org/docs/handbook/advanced-types.html#type-inference-in-conditional-types
You can use the body of an interface as a type literal:
function Thing(test: { hello: string; }) {
test.hello // works
test.bob // I want this to fail
}
is equivalent to
interface ITest {
hello: string;
}
function Thing(test: ITest) {
test.hello // works
test.bob // I want this to fail
}
Just don't forget the ; at the end of each member.
There is no syntax for naming or referring to inferred types. The closest you can get is using interfaces or type-literals for the members you are going to use. Interfaces and type-literals will match whatever type which at least has the defined members. "Duck-typing"
Related
I am trying to use commands.ts to implify repeating actions, like asking for email and password.
but when I try to use this, it gives me the rror for the login(Argument of type '"login"' is not assignable to parameter of type 'keyof Chainable)
`
Cypress.Commands.add("login", (email, password) => {
some codes....
})
`
I didn't try anything else
create a file index.ts on the same level as commands.ts
put inside
export {}
declare global {
namespace Cypress {
interface Chainable {
login(email:string, password:string): Chainable<void>;
}
}
}
enjoy
UPDATE - WHY IT WORKS
When we use Typescript everything needs an interface to be described.
In your example, Cypress has its Chainable interface but you wish it to have a 'login' property too.
With this code, you are saying that you want to extend 'Cypress.Chainable' to have a login property.
Let me explain it to you with another real example
interface String {
myOwnChainableFunction: Function
}
String.prototype.myOwnChainableFunction = function (this: string){
console.log(this)
}
'ciao'.myOwnChainableFunction();
In this example, we are extending the String prototype to accept our own function in chain.
In Javascript, we have just to attach it to the prototype but with .ts the Typescript checker pretends this property exists in the String interface, so we added it to the interface too.
I'm in the process of converting a JavaScript project to Typescript.
I have an old module file that was require()-d from the main.js file of the project.
Here is the simplified example:
//required_module.js
const foo = require("bar");
module.exports = {
start(myvar) {
this.myvar = myvar;
this.myfunc('str1');
this.myfunc('str2');
},
myfunc(type) {
const other = new foo.stuff;
if (type === 'str1') {
other.fn1(this.xbar(type));
}
else if (type === 'str2') {
other.fn2('blahblah', this.xbar(type));
}
other.go();
},
xbar(type) {
return this.myvar.asd(type);
}
};
As you can see, this is just a simple JS module file (not a class), that uses this many times, and it just works as expected.
However as I try to convert this module to a TypeScript module without creating a class from it, I get different kind of errors at the this references as I try different approaches, like:
Object is possibly 'undefined'.ts(2532)
'this' implicitly has type 'any' because it does not have a type annotation.ts(2683)
An outer value of 'this' is shadowed by this container.
Is the only solution to create a class or TypeScript also allows using this outside of classes in a specific way?
Based on the helpful comments, the answer is:
You probably don't have to convert it to a class, but you'd be better off: you are going to run into issues dynamically adding properties (like myvar) or calling methods on untyped objects. Typescript doesn't want you treating JS objects as a random grab-bag of properties: there's already another data structure for that- Jared Smith
I'd focus less on converting to typescript and more on converting to modern module syntax. If you used named exports instead of object methods, you wouldn't have these issues. You would however have a stateful, static, module-scoped myvar variable (as you currently do), which should be avoided. Instead of the singleton, a class that can be instantiated multiple times might be the better approach.- Alex Wayne
The example, simply converted code would look like something like this in TypeScript:
import foo from 'bar';
export default {
myvar: 0,
start(myvar: number) {
this.myvar = myvar;
this.myfunc('str1');
this.myfunc('str2');
},
myfunc(type: 'str1' | 'str2') {
const other = new foo.stuff;
if (type === 'str1') {
other.fn1(this.xbar(type));
}
else if (type === 'str2') {
other.fn2('blahblah', this.xbar(type));
}
other.go();
},
xbar(type: string) {
//...
}
};
I am using VueJS with typescrpit but this could be in any js framework.
I have componenets with some methods I want to expose globally so they can be used via the browser console. First idea is to attach them to the window object. This could be done from a lifecycle method such us mounted in my case, but I prefered a cleaner easier solution to write and to use, using decorators.
I've tried something like:
mycomponenet.ts :
function makeGlobal() {
return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
(window as any)[propertyKey] = () => target[propertyKey]();
};
}
Then this decorator will be used easily like :
#makeGlobal()
myMethodToGoGloabl(){
// do stuff ...
}
Till now everything works fine, until the function uses "this" like :
#makeGlobal()
myMethodToGoGloabl(){
this.firstName = "";
}
Then I get a error firstName of undefined. I understood by searching and reading that the decorator is executed before class instantiation, therefor what I exposed (correct me if I m mistaken) is a prototype method and not a method of the instance that why we have no access to this. I've tried to bind(this) but I failed for the same reason that I don't expose globally the method of the instance.
Is there a way to use decorators in my case or they are useless here?
Guess the issue is your decorator. You do not take over the this instance at all...
Also the way you do it, you won't be able to pass parameters.
Suggestion:
function makeGlobal() {
return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
var currentThis = this;
(window as any)[propertyKey] = () => target[propertyKey].apply(currentThis, args);
};
}
Also keep in mind what Christian says in the comment. It basically can only work for singletons, as it will override the method for multiple instances
In Angular 2 testing utility I do something like this:
fixture = TestBed.createComponent(EditableValueComponent);
where EditableValueComponent is a normal component class.
I wonder how it works:
static createComponent<T>(component: Type<T>): ComponentFixture<T>;
Beceause I wanna do something similar (I want to simplify some testing stuff):
export class SuperFixture<T>
{
fixture: ComponentFixture<T>;
component: T;
constructor()
{
this.fixture = TestBed.createComponent(T); // <--- problem here!
this.component = this.fixture.componentInstance;
}
}
The problem is:
'T' only refers to a type, but is being used as a value here.'
EDIT #1
I solved the problem this way:
constructor(component)
{
this.fixture = TestBed.createComponent<T>(component);
But I still don't know how it works..
You still need to pass actual class (constructor function that creates instances of a class) into constructor function of SuperFixture. Under the hood TestBed.createComponent calls provided constructor function with new to create instance of provided class. So SuperClass signature might look like this:
class SuperFixture<T>
{
fixture: ComponentFixture<T>;
component: T;
// passing in the constructor for instances of T
constructor(componentConstructor: new () => T)
{
this.fixture = TestBed.createComponent<T>(componentConstructor);
this.component = this.fixture.componentInstance;
}
}
Was working on that answer, but had to step out for coffee. ¯_(ツ)_/¯
The language feature you are using is called a Generic in TypeScript. It allows defining types at runtime with "type variables" (like <T>), which are separate from function arguments.
Before, the type variable was being passed as a function argument, when the function expected an instance of type T. That's what the error means.
The change you made works because you are passing the type variable and the instance in their correct positions in the call.
The SuperFixture object gets the value of T when you create it, and then it will pass that type variable to createComponent in the constructor, along with the value of component.
Is it possible to get stricter compile time checking when using interfaces for arguments to constructors? The default behavior seems to be too lenient.
For example given the following class:
// #flow
'use strict';
import type { Bar } from './bar';
export default class Foo {
_bar: Bar;
_name: string;
constructor (bar: Bar, name: string) {
this._bar = bar;
this._name = name;
}
}
And the following interface defined in another place:
// #flow
'use strict';
export interface Bar {
doSomething(someArg: string);
}
If I create an invalid instance of Foo with some kind of primitive type I'll get an error:
// In any of these flowtype checking works and fails because
// it knows those things are not Bar.
new Foo('bar', 'someName');
new Foo(1, 'someName');
new Foo({}, 'someName');
But then if I do something silly like this:
new Foo(new Function(), 'someName');
flowtype is perfectly happy with this and that sort of defeats the purpose of even having defined an interface in the first place. If I can just pass in any kind of instance object and flowtype doesn't see that what's passed in does not match the interface it should throw an error just like it did for {}.
Is there some configuration I need to change or something I am doing wrong?
Edit:
I think this may be a bug and have filed an issue.
Apparently it's a known issue.
As it is now function instances properties are type any even if they're undefined. :(
My issue was closed as duplicate. This explains the problem in my question.