[IMP] types: do not make Env an indexed type

Before this commit, Env was an indexed type, this means that one could
write env.anything, and it would accept it as a valid type. This is
actually quite dangerous, because we lose the typing advantages for all
keys that are properly defined.

For example, if a component is defined as:

class MyComponent extends Component<Props> {
 ...
}

Then Typescript will let it use anything from the environment, even if
it is wrong.  So, most properly typed Typescript applications should use
instead a sub environment:

interface MyAppEnv extends Env {
  someKey: someValue
}

Then, the component should be defined this way:

class MyComponent extends Component<Props, MyAppEnv> {
 ...
}

Before this commit, any typos in the environment accesses would not be
noticed by typescript.
This commit is contained in:
Géry Debongnie
2020-04-17 11:30:25 +02:00
committed by aab-odoo
parent 94c8bce810
commit 142b69823f
6 changed files with 26 additions and 11 deletions
-1
View File
@@ -34,7 +34,6 @@ import { activateSheet } from "./styles";
*/
export interface Env {
qweb: QWeb;
[key: string]: any;
}
export type MountPosition = "first-child" | "last-child" | "self";
+2 -1
View File
@@ -1,7 +1,8 @@
import { Component } from "../component/component";
import { xml } from "../tags";
import { EnvWithRouter } from "./router";
export class RouteComponent extends Component {
export class RouteComponent extends Component<{}, EnvWithRouter> {
static template = xml`
<t>
<t
+9 -1
View File
@@ -49,6 +49,10 @@ interface Options {
mode: Router["mode"];
}
export interface EnvWithRouter extends Env {
router: Router;
}
const paramRegexp = /\{\{(.*?)\}\}/;
export class Router {
@@ -60,7 +64,11 @@ export class Router {
routeIds: string[];
env: RouterEnv;
constructor(env: Env, routes: Partial<Route>[], options: Options = { mode: "history" }) {
constructor(
env: Partial<EnvWithRouter>,
routes: Partial<Route>[],
options: Options = { mode: "history" }
) {
env.router = this;
this.mode = options.mode;
this.env = env as RouterEnv;
+7 -3
View File
@@ -24,6 +24,10 @@ import { onWillUpdateProps } from "./hooks";
// Store Definition
//------------------------------------------------------------------------------
export interface EnvWithStore extends Env {
store: Store;
}
export type Action = ({ state, dispatch, env, getters }, ...payload: any) => any;
export type Getter = ({ state: any, getters }, payload?) => any;
@@ -83,7 +87,7 @@ interface SelectorOptions {
const isStrictEqual = (a, b) => a === b;
export function useStore(selector, options: SelectorOptions = {}): any {
const component: Component = Component.current!;
const component = Component.current as Component<any, EnvWithStore>;
const componentId = component.__owl__.id;
const store = options.store || (component.env.store as Store);
if (!(store instanceof Store)) {
@@ -149,11 +153,11 @@ export function useStore(selector, options: SelectorOptions = {}): any {
}
export function useDispatch(store?: Store): Store["dispatch"] {
store = store || (Component.current!.env.store as Store);
store = store || (Component.current!.env as EnvWithStore).store;
return store.dispatch.bind(store);
}
export function useGetters(store?: Store): Store["getters"] {
store = store || (Component.current!.env.store as Store);
store = store || (Component.current!.env as EnvWithStore).store;
return store.getters;
}
+5 -2
View File
@@ -3484,11 +3484,14 @@ describe("environment and plugins", () => {
});
test("can define specific env for root components", async () => {
class App1 extends Component {
interface AppEnv extends Env {
test: number;
}
class App1 extends Component<{}, AppEnv> {
static template = xml`<span></span>`;
}
App1.env = { test: 1 };
class App2 extends Component {
class App2 extends Component<{}, AppEnv> {
static template = xml`<span></span>`;
}
App2.env = { test: 2 };
+3 -3
View File
@@ -1,5 +1,5 @@
import { Component, Env } from "../src/component/component";
import { Store, useStore, useDispatch, useGetters } from "../src/store";
import { Store, useStore, useDispatch, useGetters, EnvWithStore } from "../src/store";
import { useState } from "../src/hooks";
import { xml } from "../src/tags";
import { shallowEqual } from "../src/utils";
@@ -885,7 +885,7 @@ describe("connecting a component to store", () => {
actions
});
class TodoItem extends Component {
class TodoItem extends Component<{}, EnvWithStore> {
static template = xml`
<div class="todo">
<t t-esc="state.todo.title"/>
@@ -958,7 +958,7 @@ describe("connecting a component to store", () => {
actions
});
class TodoItem extends Component {
class TodoItem extends Component<{}, EnvWithStore> {
static template = xml`
<div class="todo">
<t t-esc="state.todo.title"/>