-
Notifications
You must be signed in to change notification settings - Fork 36
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
TypeScript-ify #92
TypeScript-ify #92
Conversation
Some comments:
|
Thanks @mbest — what's the reason for not using |
It is not typed, you don't provide types for input parameters and output type. It's like there can be a function that get any parameters of `any` and might or might not return anything. It does not document anything. If you want to accept a function that does not accept anything and does not return anything you wouldn't use Function either. This is almost never what you want.
|
Thanks @Sebazzz — I'm not sure I understand. Do you mean that |
Yes, but you can model it as a type alias or interface for reusability and clarity if suitable. For instance:
|
@@ -96,8 +96,7 @@ export function cleanNode (node: Node) { | |||
|
|||
// ... then its descendants, where applicable | |||
if (cleanableNodeTypesWithDescendants[node.nodeType]) { | |||
// cleanNodesInList((node as CleanableNodeWithDescendants).getElementsByTagName("*")) | |||
cleanNodesInList((node as CleanableNodeWithDescendants).childNodes) | |||
cleanNodesInList((node as any).getElementsByTagName('*')); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not use childNodes? How are these two options here different?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@brianmhunt the latter is recursive (grandchildren nodes). I changed it to childNodes under the assumption it'd be the same, but it broke the unit tests.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah I see. That’s unfortunate since childNodes is much faster .... but evidently incorrect 😀
import options from '../options'; | ||
|
||
// @ts-ignore | ||
import _let from '../../../tko.binding.core/src/let'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn’t see where this was used. I’m sure it is, I just didn’t see where.
|
||
export const useSymbols = typeof Symbol === 'function'; | ||
|
||
export function createSymbolOrString(identifier: any) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think identifier has to be a string
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we just use ES2015 Symbols directly and polyfill?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes definitely. That’s been on my todo forever
export const {isArray} = Array; | ||
|
||
// export function arrayForEach<T, TThis>(array: T[], action: (this: TThis, item: T, index: number, array: T[]) => void, thisArg: TThis): void; | ||
export function arrayForEach<T>(array: T[], action: (item: T, index: number, array: T[]) => void, thisArg?: any) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see a lot of the same callbacks. It would be nice for DRY-ness to make a seperate type for this (even if only used internally):
type ArrayUtilCallback<T, TResult=void> = (item: T, index: number, array: T[]) => TResult;
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Sebazzz Where should these sorts of types be defined?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since it is specialized to the utility functions in this file, I'd say in this file at the top.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Got it.
Related question: In the case of JSX, we convert / accept a number of types in anyToNode
, with appropriate delegation by a switch
e.g. https://github.com/knockout/tko/blob/master/packages/utils.jsx/src/JsxObserver.js#L233
I was thinking of rewriting that switch in TS with something like the following (with most specific arguments to most general):
anyToNode(value: Node) : Node { ... }
anyToNode(value: null|undefined|Error|Symbol) : Node { ... }
anyToNode(value: object) : Node { ... }
anyToNode(value: function) : Node { ... }
anyToNode(value: string) : Node { ... }
anyToNode(value: boolean|number|bigint) : Node { ... }
If I understand it correctly, Typescript essentially is going to compile this down to a switch
anyway. Is there a better way to write this, or is this a sound approach? (Even this fairly naive TS version looks much better to me, certainly, than the JS function with a switch).
Also, the anyToNode
returns one of HTMComment
, HTMLElement
, or Text
- what's the right return type? The more general Node
? Or the most specific in each case? Or a combination with e.g. HTMLElement|HTMLComment|Text
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not familiar enough with the compiler-side of things regarding JSX, perhaps @mbest knows?
As I understand it, Typescript does not support overloading in the sense that compiler figures out the checks to make, and the handbook does not express it explicitly.
Regarding the last: Yes, a union is the way to go since you know what types might be returned.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe a stupid question, why do we have these in the first place if TKO doesn't target IE8?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@caseyWebb The anyToNode
function needs to handle these parameters because it's taking user-based input and turning it into JSX; that input may be in the form of a JSX-like object ({ elementName: string, attributes: object, children: array }
) or text, or something else that we nevertheless want to handle gracefully such as an observable or promise.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In order for the compiler to pick the correct typecheck, it follows a similar process to the underlying JavaScript. It looks at the overload list, and proceeding with the first overload attempts to call the function with the provided parameters. If it finds a match, it picks this overload as the correct overload. For this reason, it’s customary to order overloads from most specific to least specific.
^ not sure if that would affect the order you've currently got them in.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@avickers Turns out that section of the docs is only about function type descriptions; Typescript doesn't actually do the overloading implementation. microsoft/TypeScript#5658 . It should. But it doesn't.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That wouldn't be easy. Only for the most trivial cases overloading could be implemented automatically be the Typescript compiler.
import { safeSetTimeout } from './error'; | ||
|
||
// tslint:disable-next-line:ban-types | ||
export function throttle(callback: Function, timeout: number) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Need to check whether we can make this more specific than Function?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like lodash
has a generic Function
as well: https://github.com/lodash/lodash/blob/master/throttle.js (though I'm not sure the jsdoc is used for the type checking, I noted that the throttle type at DefinitelyTyped just re-exports)
}); | ||
} else if (typeof (node.className as any).baseVal === 'string') { | ||
// SVG tag .classNames is an SVGAnimatedString instance | ||
toggleObjectClassPropertyString(node.className, 'baseVal', classNames, shouldHaveClass); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IE < 10 isn't a target anymore right? Perhaps redirect directly to classlist
instead of attempting to feature-detect and workaround?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, there's a ton of pruning we could do for non-old-IE, including this.
I'm looking into converting export function observable (initialValue) {
function Observable () {
if (arguments.length > 0) {
// Write
// Ignore writes if the value hasn't changed
if (Observable.isDifferent(Observable[LATEST_VALUE], arguments[0])) {
Observable.valueWillMutate()
Observable[LATEST_VALUE] = arguments[0]
Observable.valueHasMutated()
}
return this // Permits chained assignments
} else {
// Read
dependencyDetection.registerDependency(Observable) // The caller only needs to be notified of changes if they did a "read" operation
return Observable[LATEST_VALUE]
}
}
overwriteLengthPropertyIfSupported(Observable, { value: undefined })
Observable[LATEST_VALUE] = initialValue
subscribable.fn.init(Observable)
// Inherit from 'observable'
Object.setPrototypeOf(Observable, observable.fn)
if (options.deferUpdates) {
deferUpdates(Observable)
}
return Observable
} On the TS side: type Observable = (...args: any[]) => void
export function observable (initialValue: any) : Observable {
function Observable (...args: any[]) : Observable {
if (args.length > 0) {
if (Observable.isDifferent(Observable[LATEST_VALUE], arguments[0])) { // Property 'isDifferent' does not exist on type '(...args: any[]) => Observable'
Observable.valueWillMutate()
Observable[LATEST_VALUE] = arguments[0]
Observable.valueHasMutated()
}
return this // this' implicitly has type 'any' because it does not have a type annotation.ts(2683)
} else {
// Read
dependencyDetection.registerDependency(Observable) // The caller only needs to be notified of changes if they did a "read" operation
return Observable[LATEST_VALUE]
}
}
overwriteLengthPropertyIfSupported(Observable, { value: undefined })
Observable[LATEST_VALUE] = initialValue
subscribable.fn.init(Observable)
// Inherit from 'observable'
Object.setPrototypeOf(Observable, observable.fn)
if (options.deferUpdates) {
deferUpdates(Observable)
}
return Observable
} These issues seem to mostly stem from the |
Are you looking for this? Any reason why |
Just because I don't yet know what generic means :) |
Ah, right, excuse me 😉 So currently any value can be set into the |
That's unbelievably satisfying. 🤣 |
Is the TS code in this MR functional? It's been over 3 months since a comment was made so I guess everyone got busy with life. |
@skewty This isn't yet functional (as far as I can tell), but in the interim i've written around 50,000 lines of TS, so my skills are almost up to where they probably need to be to deliver on this PR. 👍 Alongside documentation, converting to TS is my ambition for TKO this year. |
Includes #62, rebased w/ latest tko master, build process fixed.
Note: I haven't actually reviewed the work by @Sebazzz, nor tried anything in the browser. This is just bringing that PR up to date and adding build support.
TODO