tko
tko copied to clipboard
TypeScript-ify
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
- [ ] get tests passing
- [ ] finish refactoring into TS
- [ ] replace build process with something simpler
Some comments:
- All functions should have a typed return value.
- Don't use
Functionas a type. - For compatibility with Knockout 3.5.0, exported types should match https://github.com/knockout/knockout/blob/master/build/types/knockout.d.ts
Thanks @mbest — what's the reason for not using Function as a type?
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 Function is untyped, but the correct thing to do in TS is to have types for the function (e.g. someFunction: (n: number) => any)?
Yes, but you can model it as a type alias or interface for reusability and clarity if suitable.
For instance:
type SubscriptionCallback<T> = (newValue : T) => void;
// […]
interface Xxx<T> {
void subscribe(callback: SubscriptionCallback<T>);
}
I'm looking into converting @tko/observable and I'm not sure the best way to add types to an Observable, but there's a lot of questions about things that aren't in the handbook. Here's what I've got for the JS:
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 observable function returning an Observable function / instance. It isn't apparent how Typescript is set up to deal with function() { this }, leaving me with a bunch of questions to bridge the gaping chasm that is my TS knowledge. Any thoughts and assistance is most welcome and appreciated.
Any reason why
Observableis not generic?
Just because I don't yet know what generic means :)
Any reason why
Observableis not generic?Just because I don't yet know what generic means :)
Ah, right, excuse me 😉
So currently any value can be set into the Observable, but if it were an Observable<T> with accepting input and output values of type T instead of any you get a strongly typed observable: If you create a Observable<string> you can't put a Person in it accidently.
Any reason why
Observableis not generic?Just because I don't yet know what generic means :)
Ah, right, excuse me 😉
So currently any value can be set into the
Observable, but if it were anObservable<T>with accepting input and output values of typeTinstead ofanyyou get a strongly typed observable: If you create aObservable<string>you can't put aPersonin it accidently.
That's unbelievably satisfying. 🤣
See knockout/knockout#2458
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.