Improve Typescript definition for MithrilRoutes

The previous definition of MithrilRoutes was generic over <T extends
MithrilController>, with the MithrilRoutes containing a collection of
MithrilComponent<T>.

However, this presents problems with TypeScript's type inference in many common
situations.  For example, consider this usage of m.route():

```
m.route(document.body, "/a", {
    "/a": ComponentA,
    "/b": ComponentB,
})
```
Both `ComponentA` and `ComponentB` implement `MithrilComponent<T extends
MithrilController>`, with each component having a different concrete controller
type (`ControllerA` and `ControllerB`).

However, unless ControllerA's type is a subset of ControllerB's type (or
vice-versa), TypeScript will be unable to infer the type of the MitrilRoutes<T>
returned by m.route(). To get this to compile, a third type which *is* a
subset of both ControllerA and ControllerB would need to be explicity provided:

```
// Both ControllerA and ControllerB implement MithrilController
m.route<MithrilController>(document.body, "/a", {
  "/a": ComponentA,
  "/b": ComponentB,
})
```

This commit makes MithrilRoute non-generic, and instead of containing
MithrilComponent<T extends MithrilRoute>, it contains
MithrilComponent<MithrilController>, which will match all valid
MithrilComponents.

The motivation for this change is twofold:

1. In the case where m.route() does not compile due to type-inference failure,
the resulting syntax error from Typescript is very unhelpful because m.route()
has a number of overloads. Leaving this as is will result in confusion for
downstream users.
2. An assumption that vanishingly little utility is provided by defining
`MithrilRoutes<T extends MithrilController>` over a type more specific than
MithrilController. At most, it could be used to assert that all of your routed
Components meet a more specialized interface, but that same type check could be
accomplished without having to augment MithrilRoutes with that information.
This commit is contained in:
Matt Tracy 2015-09-29 14:05:53 -04:00
parent 6f15a9b148
commit 26f68744b5

10
mithril.d.ts vendored
View file

@ -431,10 +431,10 @@ declare module _mithril {
* @param defaultRoute The route to start with. * @param defaultRoute The route to start with.
* @param routes A key-value mapping of pathname to controller. * @param routes A key-value mapping of pathname to controller.
*/ */
<T extends MithrilController>( (
rootElement: Element, rootElement: Element,
defaultRoute: string, defaultRoute: string,
routes: MithrilRoutes<T> routes: MithrilRoutes
): void; ): void;
/** /**
@ -447,7 +447,7 @@ declare module _mithril {
* m("a[href='/dashboard/alicesmith']", {config: m.route}); * m("a[href='/dashboard/alicesmith']", {config: m.route});
* ``` * ```
*/ */
<T extends MithrilController>( (
element: Element, element: Element,
isInitialized: boolean, isInitialized: boolean,
context?: MithrilContext, context?: MithrilContext,
@ -876,12 +876,12 @@ declare module _mithril {
/** /**
* This represents a key-value mapping linking routes to components. * This represents a key-value mapping linking routes to components.
*/ */
interface MithrilRoutes<T extends MithrilController> { interface MithrilRoutes {
/** /**
* The key represents the route. The value represents the corresponding * The key represents the route. The value represents the corresponding
* component. * component.
*/ */
[key: string]: MithrilComponent<T>; [key: string]: MithrilComponent<MithrilController>;
} }
/** /**