Typescript Definitions for Mithril 1.x
Types are maintained at DefinitelyTyped. Submit PRs there but you can submit issues here.
Requires TypeScript 2.x or later.
Install from npm with:
npm install -D @types/mithril
Please note that while Mithril polyfills Promise support, this type definition does not include a type declaration for Promises. You may see an error like:
'Promise' only refers to a type, but is being used as a value here.
To use promises, you should add the "es2015.promise"
library option to your compiler options. In tsconfig.json
:
{
"compilerOptions": {
"target": "es5",
"lib": [
"es2015.promise",
...
]
}
}
In order to import Mithril's commonjs export in the form:
import m from 'mithril';
you may need to set the "esModuleInterop"
option in your tsconfig.json
.
{
"compilerOptions": {
"esModuleInterop": true,
...
}
}
For Rollup, instead you should enable "allowSyntheticDefaultImports"
:
{
"compilerOptions": {
"allowSyntheticDefaultImports": true,
...
}
}
These settings may depend on the bundler you're using.
import m from 'mithril';
interface Attrs {
name: string;
count: number;
}
const MyComp: m.Component<Attrs> = {
view (vnode) {
return m('span', `name: ${vnode.attrs.name}, count: ${vnode.attrs.count}`);
}
};
If you prefer the convenience of destructuring, you could rewrite MyComp
like:
const MyComp: m.Component<Attrs> = {
view ({attrs: {name, count}}) {
return m('span', `name: ${name}, count: ${count}`);
}
};
The easiest way to annotate a stateful component and to make best use of inference is by holding state in a closure:
import m from 'mithril';
interface Attrs {
name: string;
initialValue: number;
}
function Counter(): m.Component<Attrs> {
let count = 0;
function increment() {
count++;
}
function decrement() {
count--;
}
return {
oninit ({attrs}) {
count = attrs.initialValue;
},
view ({attrs}) {
return m('.counter',
m('span', `name: ${attrs.name}, count: ${count}`),
m('button', {onclick: increment}, '+'),
m('button', {onclick: decrement}, '-')
);
}
};
}
In the above example, local state types can usually be inferred at declaration time and you don't need to worry about how this
may be bound since you never need to write this
.
In the following example, we want to use the initial Vnode
. Here we annotate the whole function rather than just its return type, and the initial vnode type is inferred.
interface Attrs {
name: string;
initialValue: number;
}
const Counter: m.ClosureComponent<Attrs> = vnode => {
let count = vnode.attrs.initialValue
function increment() {
count++;
}
function decrement() {
count--;
}
return {
view ({attrs}) {
return m('.counter',
m('span', `name: ${attrs.name}, count: ${count}`),
m('button', {onclick: increment}, '+'),
m('button', {onclick: decrement}, '-')
);
}
};
};
Note that Typescript cannot infer types for class methods. When using classes you must annotate the incoming Vnode
type for component hook methods.
import m from 'mithril';
interface Attrs {
name: string;
initialValue: number;
}
class Counter implements m.ClassComponent<Attrs> {
count = 0;
// Use arrow functions so `this` is bound as expected
increment = () => {
this.count++;
};
decrement = () => {
this.count--;
};
// The constructor can be used in place of oninit
constructor({attrs}: m.CVnode<Attrs>) {
this.count = attrs.initialValue;
}
// Note that class methods cannot infer parameter types
view ({attrs}: m.CVnode<Attrs>) {
return m('.counter',
m('span', `name: ${attrs.name}, count: ${this.count}`),
m('button', {onclick: this.increment}, '+'),
m('button', {onclick: this.decrement}, '-')
);
}
}
Another way to hold state is in vnode.state
.
import m from 'mithril';
interface Attrs {
name: string;
initialValue: number;
}
interface State {
count: number;
increment(): void;
decrement(): void;
}
const Counter: m.Component<Attrs, State> = {
oninit ({state}) {
state.count = 0;
state.increment = () => {state.count++};
state.decrement = () => {state.count--};
},
view ({attrs, state}) {
return m('.counter',
m('span', `name: ${attrs.name}, count: ${state.count}`),
m('button', {onclick: state.increment}, '+'),
m('button', {onclick: state.decrement}, '-')
);
}
};
In a POJO component hook, this
is a reference to vnode.state
. To have this
inferred correctly, use the m.Comp
type.
import m from 'mithril';
interface Attrs {
name: string;
initialValue: number;
}
interface State {
count: number;
increment(): void;
decrement(): void;
}
const Counter: m.Comp<Attrs, State> = {
count: 0,
increment() {
this.count++;
},
decrement() {
this.count--;
},
oninit ({attrs}) {
this.count = attrs.initialValue;
},
view ({attrs}) {
return m('.counter',
m('span', `name: ${attrs.name}, count: ${this.count}`),
m('button', {onclick: () => {this.increment()}}, '+'),
m('button', {onclick: () => {this.decrement()}}, '-')
);
}
};
Sometimes you can just as easily use functions in place of components. Usually the return type will be inferred as being compatible with m.Children
, or you can annotate it specifically if you prefer:
function titleView(title: string): m.Children {
return m('h1', title);
}
import stream, {Stream} from 'mithril/stream';
const num = stream(1);
const text = stream<string>();
let s: Stream<Foo>;
s = stream(new Foo());
Library support is required for full TSX support and cannot be accomplished with types alone. See the NPM package mithril-tsx-component.
If you are adding mithril to your page as a separate script, then you can install the global version of these types.
For more example usage see the test
folder.
NOTE This repo is not guaranteed to be in sync with DefinitelyTyped. Please use the DT types for your applications. This repo is maintained primarily for documentation and issues.
npm install
npm run lint
You can also try compiling with npm test
(which runs tsc) however there are intentional errors in the tests to ensure type checks will catch those errors.