I have heard endless good things about TypeScript in the last couple years, but I've never really had a chance to use it. So when I was tasked with...
For further actions, you may consider blocking this person and/or reporting abuse
If you look closely to the problems you are having, they are pretty much all class oriented. This is exactly what I found when picking up typescript and eventually abandoned classes and shift to a more functional programming oriented coding style.
I just feel that functions are first class citizen and works better with Typescript then classes. With the way of packaging nowadays, there is not much practical you cannot do without classes.
For example, I would rather use "knex" to build the queries wrapped with functions instead of using an ORM (in your case objection) because I feel like it is more straight forward and faster to get things done, and you have more control over what you want to return.
I definitely get this perspective, and often I do prefer a functional approach over a class based approach. However, in my experience using both query builders like knex and ORMs like Objection, I find the mental mapping between my code and the database much easier to keep straight when I have a class for each table, especially the way that Objection uses static properties like
idColumnandrelationshipMappingsto represent database constraints. Others may feel differently, but it's how my brain works.This doesn't have to do with classes vs functions. If you have a place that accepts
stringtype values, then later you're trying to pass something that is of typestring | string[], that's simply wrong and TS is doing a good job telling you that this is a problem. You might know what you're passing, but the next developer, and the next one after that, won't know. This is about scaling code bases in a way that prevents accidents, classes or not.To understand this more specifically, if someone write some piece of code that accepts objects of type
Modelbased on the above class in the article, f.e. some code likethis code is going to fail if it unexpectedly receives
string | string[]because it is expecting to have astringonly, and may call string-specific methods on that string, etc.As you can see here, this doesn't have to do with classes vs functions at all. Definitely use only functions if you like that, but this problem still needs to be taken into account, and it is simply about assignment.
@trusktr Which thing are you referencing? This is a pretty long thread at this point lol
MDN explains the Class construct internals nicely. They imply the differences between a Class and Function are minimal.
With the Class, hoisting is not done and the class body is always in strict mode.
developer.mozilla.org/en-US/docs/W...
For the first one
function handleRelations(modelClass: extends Model) ...here is a very simple TypeScript Playground example example.
TLDR;
In terms of readability for
see this TypeScript Playground example
TLDR;
When I have a chance, I'll check out the other issues you're having.
Huh,
Record<T>is a pretty handy trick. Definitely nicer than the bare syntax. And yeah, in cases where it gets too complicated I definitely do declare little types, but it seems like a lot of overhead to have to declare a local type for every function that uses Maps or Sets of objects like that... I'll have to think about that one.Regarding passing around classes and subclasses, in your example,
modelClass: Modelis still and instance of some class extendingModel, rather than a class itself. I need to reference the classes themselves to get at static methods and properties. Here's a playground example (in a broken state) showing what I mean.Thanks for the responses!
Ahh, I see what you mean. My bad. You can do this
TLDR;
Does that handle your use case?
Oh man yes it does, that's exactly what I was looking for! I didn't realize that was a thing! Thanks!
Or even better using generics:
Generics for sure, but it still needs to be
typeof Modelas he doesn’t want an instance of the class.1)
function handleRelations<T extends Model>(modelClass: T) {Look up Generics Constraints in the typescript manual.
2) That's not a Typecript question, that's an object-oriented question. The answer is "you can't, and shouldn't".
If I have an Animal[] and .map it to call a function on each return value...
animals.map(a => a.idColumn().whatIsValidHere)... then what is always safe for
whatisvalidhereif it's sometimes a string sometimes an array sometimes an Invoice, etc.3)
is a bit more useful. Stick in /shared/ or wherever you keep such things.
You beat me to it!
typeof ModelandT extends Modelare the useful features here, but I also agree with Edward Tam - you should probably eschew subclassing in general. "Is a" object relationships are the tightest form of coupling that exists. Instead use functions or even mixins, if you must attach "methods" to your objects. You probably don't need a base class when you have interfaces and mixins available to you.is ais strong, but can be loosened with Factories and Bridge PatternHaven't found a good use for the Bridge Pattern, but I've often wondered why more ORMs aren't using Factories/mixins
It can help to be able to ask quick questions on gitter.im/Microsoft/TypeScript or launchpass.com/fullstacktypescript
Awesome, thanks! I'll keep those groups handy
JS lover too until I met TS here. I've led a big full-stack app project using TypeScript so I probably can give you some insight.
I agree with this comment by Edward Tam. I suggest not to rely much on class and class extensions as TypeScript doesn't have good support for OOP extensions. Even though TS was born from OOP paradigm, it's actually easier to just use
typeand namespaced-functions is the form ofstatic methods in a class. I use class only if I need a long-running data scope which seemingly has "a separate runtime" and I use it rarely.I would recommend watching this youtube.com/watch?v=yy8jQgmhbAU and youtube.com/watch?v=aKLntZcp27M. Even though it's rust and c++ it gave a lot of insight on how to make code that is development-scalable, meaning adding/changing/removing features in the future will not be as hard as when you use by the book OOP, which I mistakenly use in my first projects.
Also, check this out, TypeScript 3.5's feature higher-order type inference which is super neat.
It's nice to see more people embracing TypeScript. Welcome to the ecosystem, good luck and have fun.
Out of interest, do you use an ORM with TS? If so which one? I have yet to find one that doesn't seem like it wants to work strictly in the OOP paradigm. I need an ORM that properly supports relational data structures like ternary relationships. The only one that looks bearable is Objection which essentially seems to be a query builder that holds a representation of your schema in code to provide helpers to do easy queries and eager loading etc. Unfortunately as you've pointed out, it doesn't really have support for TS.
So, I wrote this article a couple months ago now, and since then I've developed my project into a pretty sizable codebase, still using Objection with TypeScript. IMHO, it works fine. I understand what a few other commenters have said about TypeScript and classes, but after digging in a bit deeper, I haven't encountered any problems I couldn't pretty easily work around. I like Objection a lot; I think it occupies a pretty nice middle ground between query builders and ORMs where it gives you some very nice class-based features out of the box, but still provides low-level access to SQL queries when you need it in a way that many Hibernate-style ORMs do not.
Interfaces are not for reinventing the wheel.
Your interfaces should be defined first and should contain the minimum common behaviour between similar objects that will implement them.
Think of an interface as a contract that needs to be fulfilled by the classes that implement it.
This means any method that only needs to use a specific behaviour that multiple objects contain, only needs to accept any object that implements it.
The point is that the base classes are already defined in the library I'm using, and I was only using the interface as a hack to be able to pass around the classes in the way I needed to. I understand the purpose and usefulness of interfaces as a construct, but that wasn't my situation.
Making this a typed language you need to expect that it makes more limitations on the code you write to help you avoid any bugs or errors at runtime. I think the first two points you mentioned are designed in Typescript to help you code in a way that doesn't violate Liskov Substitution Principle which is one of the SOLID design principles.
en.m.wikipedia.org/wiki/Liskov_sub...
The second point, absolutely. Not the first point though; what I was reaching for but couldn't find was
typeof Model, as infunction foo(model: typeof Model).I think the issue when you try to change the signature of a subclass, is that any subclass should be assignable to their superclass.
See in the example: Playground
You can run it and see the error. I understand that in your point of view you could perfectly handle that case, but that's a sample use case.
Very good question. When moving to Typescript and the concept of the Classes and ability to extend a base class, one rule must be absolutely followed. The is-a relationship is paramount. The parent must be a type of base.
Base classes don't really return anything (other than hosting properties and funtions) rather, they morph all things public to be a part of the parent.
This is where intellisense shines because no API is needed as the editor discovers all props and functions as you type.
Interface defintions are not strictly needed. They only provide the abilty for other users to inject their own implementation of a pattern. This stops the need to always use just one concrete class and is favored mostly by testers.
In your example you do not need to override the getter for Id. Why? Because it's in the base class. When referring to static properties one must call out the class that contains them.
If each of the static methods are to return the same thing you only need one in the base class. DRY is good.
Each class's
idColumngetter returns a different thing, as shown in the example code. That's why I was overriding them.Instead of this you can just use the
objecttype?I am dealing with Typescript for a while now but this sure demystified certain things ^ pretty cool!
Not sure if someone mentioned it, but do you know about the 'object' (typescriptlang.org/docs/handbook/b...) type?
Change the string or string[] returns to type of any. To fix complaints. Just put in checks when used.
We disallow type any in our code. It removes any benefits you get from using a typed language