tko icon indicating copy to clipboard operation
tko copied to clipboard

TypeScript-ify

Open caseyWebb opened this issue 6 years ago • 13 comments

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

caseyWebb avatar Mar 15 '19 04:03 caseyWebb

Some comments:

  • All functions should have a typed return value.
  • Don't use Function as 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

mbest avatar Mar 15 '19 17:03 mbest

Thanks @mbest — what's the reason for not using Function as a type?

brianmhunt avatar Mar 15 '19 18:03 brianmhunt

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.

Sebazzz avatar Mar 15 '19 18:03 Sebazzz

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)?

brianmhunt avatar Mar 15 '19 19:03 brianmhunt

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>);
}

Sebazzz avatar Mar 15 '19 19:03 Sebazzz

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.

brianmhunt avatar Mar 17 '19 19:03 brianmhunt

Are you looking for this?

Any reason why Observable is not generic?

Sebazzz avatar Mar 17 '19 20:03 Sebazzz

Any reason why Observable is not generic?

Just because I don't yet know what generic means :)

brianmhunt avatar Mar 18 '19 02:03 brianmhunt

Any reason why Observable is 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.

Sebazzz avatar Mar 18 '19 06:03 Sebazzz

Any reason why Observable is 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.

That's unbelievably satisfying. 🤣

brianmhunt avatar Mar 18 '19 12:03 brianmhunt

See knockout/knockout#2458

mbest avatar Mar 19 '19 17:03 mbest

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 avatar Jul 13 '19 05:07 skewty

@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.

brianmhunt avatar Jul 19 '19 16:07 brianmhunt