-
Notifications
You must be signed in to change notification settings - Fork 12.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Suggestion: introduce UniveralElement to eliminate most type assertions in DOM programming (prototype created) #3304
Comments
The problem is these typings are not true. These typings claim that document.querySelectorAll will return something that is both an HTMLElement and an SVGElement; where the reality is it returns either an HTMLElement or an SVGElement. The reason why you need to cast is not because the typings are not correct, but because the DOM structure, that exist at run-time, does not treat SVGElement as an HTMLElement, and there is nothing we can do about that. faking the typings to say all elements have all properties makes you loose type safety, and forces users to check at runtime whether these properties exist or not before using them, which is probably the main reason you would use TypeScript. I understand that SVGElements are not common vs. HTMLElement, but they do exist. For your cases, you can always pass |
This is not the case. Previously, we usually write code like this: (<HTMLInputElement>document.querySelector('input[type=file]')).files[0]... But how does TS comipler know the return value of In most cases, users are sure about what type of If the actual return type is uncertain, we have to do runtime checking anyway, and nothing TS can do for us. Previously we do it like this: var foo = document.querySelector('#foo');
if ((<HTMLInputElement>foo).files) {
let fileInput = <HTMLInputElement>foo;
fileInput.files[0]...
} Do you see a small issue here? Before we can check the existance of On the other hand, with var foo = document.querySelector('#foo');
if (foo.files) {
let fileInput = <HTMLInputElement>foo;
fileInput.files[0]...
} Or simply var foo = document.querySelector('#foo');
if (foo.files) {
foo.files[0]...
} It is equally type-safe, and more natural to read in my opinion. Of cause if we can mark interface UniversalElement extends Element, HTMLInputElement?, ..., SVGLineElement? {
} So users need to know that
Hope I have made myself clear. |
I grepped the source of the codebase I've been working on and found about one If you have a lot of casts, this may be due to programming in a type-unfriendly style. Here's a simple example of a class that uses the DOM:
Here we've thrown away the references to the
No casts needed. The code is also more robust: suppose we add a third element between I'd say that frequent use of |
It depends on what type of code you are writing. If you are using a UI framework that generates all DOM on the fly, then you are right (and I'm actually using one in my project). But if you are writing codes in progress enhancement way, or codes manipulating free format document (e.g. a rich text editor), then there are plenty of valid use cases of Also note that we almost always need to cast |
This really does not seems right to me because I already has
This anyway requires developer knowledge about
Why? It's still JS and web platform. TypeScript does not limits your to any specific development or use cases. You still can and should be able to develop with it whatever you can with JS. |
The compiler does not treat the library any differently than any other file. |
@mhegazy I think I've answered your concern above ( #3304 (comment) ), maybe you are too busy to reply in detail. I'd like to stress:
|
@duanyao, I would follow the advice of @jeffreymorlan above and encapsulate the interaction with the DOM. (I just did a search of our large code-base and we have just 3 references to It doesn't make sense to me to define this composite interface called |
@NoelAbrahams If your project rarely uses I'm not sure what do you mean by "TypeScript-specific" and "indirection". Actually I think |
JQuery is an anti-pattern 😄 and mimicking that behaviour is not entirely advisable. "TypeScript-specific" and "indirection" because we are attempting to replace well-known interfaces with something new. |
@NoelAbrahams , I don't think most web developers are familiar with all those DOM interfaces, because they don't use them directly in JS. How do you know you need a cast or not? You have to know in which interface a property is defined first -- this is a big burden. In contrast, with UniversalElement, developers don't have to remember those details, IDE can help them. Additionally, the client code is very similar to its equivalent JS code, so I'd say UniversalElement make the code less "TypeScript-specific", not more. |
TS heavily relies on type assertion in DOM programming. In my experiences, type assertion is a major factor of produtivity-reduction in font-end TS. There is another user expressed similar concern (#3263).
So I try to eliminate most type assertions in DOM programming by adding a
UniveralElement
interface tolib.d.ts
, see my fork oflib.es6.d.ts
.In my fork,
querySelector()
,getElementById()
,getElementsByClassName()
,children
,firstElementChild
etc. returnUniversalElement
or a list ofUniversalElement
, which inherits all known sub interfaces ofElement
;UIEvent::target
is typed asUniversalUIEventTarget
, which extendsUniversalElement
,Document
, andWindow
. So in most case so you don't have to cast the result before use anymore. Additionally you don't loose the power of type check and autocompletion.A snapshot of autocompletion using my prototype:
And some sample codes:
@NekR and @kitsonk, what do you think?
The text was updated successfully, but these errors were encountered: