TSX (JSX for TypeScript) support library for Vue
- BREAKING CHANGES
- Install and enable
- Using intrinsic elements
- Using custom component
- Options
- Utility
- LICENSE
-
V2.2.0
-
Disallow meaningless combination of modifiers(undocumented api).
import { modifiers as m } from "vue-tsx-support"; /* * Below combinations are all disallowed */ // repeating same modifier <div onClick={m.enter.enter(/* snip */)} />; <div onClick={m.prevent.prevent(/* snip */)} />; <div onClick={m.enter.ctrl.enter(/* snip */)} />; // multiple key names. // # what you want may be `m.keys("enter", "asc")` <div onKeydown={m.enter.esc(/* snip */)} />; // multiple buttons <div onMousedown={m.left.middle(/* snip */)} /> // using key name and button togetter <div onKeydown={m.enter.middle(/* snip */)} /> // xxx and noxxx <div onClick={m.ctrl.noctrl(/* snip */)} />
-
-
V2.1.0
-
When event type is function, vue-tsx-support treat it as event handler itself (to support events with multiple parameters).
type Events = { onOk: string, // equivalent to `(arg: string) => void` onError: (target: any, detail: string) => void };
if you want to use function as a parameter, you must fix code like below.
type Wrong = { onOk: () => void }; type Right = { onOk: (callback: (() => void)) => void }
-
-
V2.0.0
- Support Vue >= 2.5.13 only
- Support TypeScript >= 2.8 only
-
v1.0.0
- Support Vue >= 2.5 only.
createComponent
is deprecated. use componentFactory or component instead.
-
v0.5.0:
- Rename
extend
toextendFrom
(undocumented api)
- Rename
-
v0.4.0:
- The way to enable compiler check has changed. See Install and enable
Install from npm:
npm install vue-tsx-support -S
And refer vue-tsx-support/enable-check.d.ts
from somewhere to enable compiler check. (CHANGED since v0.4.0)
///<reference path="node_modules/vue-tsx-support/enable-check.d.ts" />
// or
import "vue-tsx-support/enable-check"
or in tsconfig.json
{
"compilerOptions": {
"...snip...": "...snip..."
},
"include": [
"node_modules/vue-tsx-support/enable-check.d.ts",
"...snip..."
]
}
Standard HTML elements are defined as intrinsic elements. So, compiler can check attribute names and attribute types of them:
// OK
<div id="title" />;
// OK
<input type="number" min={ 0 } max={ 100 } />;
// OK
<a href={ SOME_LINK } />;
// NG: because `href` is not a valid attribute of `div`
<div href={ SOME_LINK } />;
// NG: because `id` must be a number
<div id={ 1 } />;
By default, vue-tsx-support
does not allow unknown props.
For example, if you have this component :
import Vue from "vue";
const MyComponent = Vue.extend({
props: {
text: { type: String, required: true },
important: Boolean,
},
computed: {
className() {
return this.important ? "label-important" : "label-normal";
}
},
methods: {
onClick(event) { this.$emit("ok", event); }
},
template: "<span :class='className' @click='onClick'>{{ text }}</span>"
});
Below code will cause compilation error because compiler does not know
MyComponent
has prop text
.
// Compilation error(TS2339): Property `text` does not exist on type '...'
<MyComponent text="foo" />;
You must add types to the component by apis memtions below, or enable allow-unknown-props
option.
Create tsx-supported component from component options. (Partially compatible with Vue.extend
)
import * as tsx from "vue-tsx-support";
const MyComponent = tsx.componentFactory.create({
props: {
text: { type: String, required: true },
important: Boolean,
},
computed: {
className(): string {
return this.important ? "label-important" : "label-normal";
}
},
methods: {
onClick(event) { this.$emit("ok", event); }
},
render(): VNode {
return <span class={this.className} onClick={this.onClick}>{this.text}</span>;
}
});
componentFactory.create
can infer types of props from component options same as Vue.extend
.
In the above example, props type will be { text?: string, important?: boolean }
.
required: true
specified.
// both `text` and `important` are regarded as optional
// So below 3 cases are all valid.
<MyComponent />;
<MyComponent text="foo" />;
<MyComponent important={true} />;
But text
is required actually, you may think compilation should be failed when text is not specified.
There are sevaral ways to achieve it.
- Instead of
required: true
, specifyrequired: true as true
. This turns type ofrequired
boolean to 'true', and vue-tsx-support can know it is required in compile time.
import * as tsx from "vue-tsx-support";
const MyComponent = tsx.componentFactory.create({
props: {
text: { type: String, required: true as true },
important: Boolean,
},
/* snip */
});
FYI, vue-strict-prop make this easy.
import * as tsx from "vue-tsx-support";
import p from "vue-strict-prop";
const MyComponent = tsx.componentFactory.create({
props: {
text: p(String).required,
important: Boolean,
},
/* snip */
});
- Specify required prop names as second argument
import * as tsx from "vue-tsx-support";
const MyComponent = tsx.componentFactory.create({
props: {
text: { type: String, required: true },
important: Boolean,
},
/* snip */
}, ["text"]);
In above examples, props type will be { text: string, important?: boolean }
.
// NG: `text` is required
<MyComponent />;
<MyComponent important={true} />;
props: ["foo", "bar"]
) is currently not supported.
// Does not work
import * as tsx from "vue-tsx-support";
const MyComponent = tsx.componentFactory.create({
props: ["text", "important"],
/* snip */
});
Shorthand of componentFactory.create
import * as tsx from "vue-tsx-support";
const MyComponent = tsx.component({
props: {
text: { type: String, required: true },
important: Boolean,
},
/* snip */
});
When you want to extend your component from other than Vue
, you can use extendFrom
import * as tsx from "vue-tsx-support";
// This is equivalent to `const MyComponent = Base.extend({ /* snip */ });`
const MyComponent = tsx.extendFrom(Base).create({
/* snip */
});
You can use mixin
to add mixin type-safely.
import * as tsx from "vue-tsx-support";
const StorageMixin = {
methods: {
getItem(string name): string {
return localStorage.getItem(name);
},
setItem(string name, string value): void {
localStorage.setItem(name, value);
}
}
}
const MyComponent = tsx.componentFactory.mixin(StorageMixin).create(
// You can use this.getItem and this.setItem here
{
props: {
name: String
},
data() {
return { value: "" }
},
mounted() {
this.value = this.getItem(this.name);
},
render(): VNode {
return (
<button onClick={() => this.setItem(this.name, this.value)}>
SAVE
</button>
);
}
}
);
// You can add 2 or more mixins by method chain
const tsx.componentFactory.mixin(FirstMixin).mixin(SecondMixin).create({
/* snip */
})
Return componentFactory with additional types (events and scoped slots)
If your component has custom events, you may want to specify event listener. But below example does not work.
import * as tsx from "vue-tsx-support";
const MyComponent = tsx.component({
render(): VNode {
return <button onClick={this.$emit("ok")}>OK</button>;
}
});
// Compilation error: 'onOK' is not a property of MyComponent
<MyComponent onOk={() => console.log("ok")} />;
In such situations, you must specify event types by componentFactoryOf
import * as tsx from "vue-tsx-support";
interface Events {
// all memebers must be prefixed by 'on'
onOk: () => void;
// If event handler has only one parameter, you can specify parameter type as a shorthand.
// For example, this is equivalent to `onError: (arg: { code: number, detail: string }) => void`
onError: { code: number, detail: string };
}
const MyComponent = tsx.componentFactoryOf<Events>().create({
render(): VNode {
return (
<div>
<button onClick={() => this.$emit("ok")}>OK</button>
<button onClick={() => this.$emit("error", { code: 9, detail: "unknown" })}>Raise Error</button>
</div>
);
}
});
// OK
<MyComponent onOk={() => console.log("ok")} />;
<MyComponent onError={p => console.log("ng", p.code, p.detail)} />;
You can also specify types of scoped slots if your component uses it.
import * as tsx from "vue-tsx-support";
interface ScopedSlots {
default: { text: string };
}
const MyComponent = tsx.componentFactoryOf<{}, ScopedSlots>().create({
props: {
text: String
},
render(): VNode {
// type of `$scopedSlots` is checked statically
return <div>
{ this.$scopedSlots.default({ text: this.text || "default text" }) }
</div>;
}
});
// type of `scopedSlots` is checked statically, too
<MyComponent scopedSlots={{
default: p => [<span>p.text</span>]
}}
/>;
// NG: 'default' is missing in scopedSlots
<MyComponent scopedSlots={{
defualt: p => [<span>p.text</span>]
}}
/>;
Base class of class base component
If you write your component with vue-class-component
,
you can it tsx-supported by extending from this class.
import component from "vue-class-component";
import * as tsx from "vue-tsx-support";
interface MyComponentProps {
text: string;
important?: boolean;
}
@component({
props: {
text: { type: String, required: true },
important: Boolean
},
/* snip */
})
class MyComponent extends tsx.Component<MyComponentProps> {
/* snip */
}
Unfortunately, you must write props interface and props definition separately.
If you want, you can specify event types and scoped slot types as 2nd and 3rd type parameter
import component from "vue-class-component";
import * as tsx from "vue-tsx-support";
interface MyComponentProps {
text: string;
important?: boolean;
}
interface Events {
onOk: void;
onError: { code: number, detail: string };
}
interface ScopedSlots {
default: { text: string };
}
@component({
props: {
text: { type: String, required: true },
important: Boolean
},
/* snip */
})
class MyComponent extends tsx.Component<MyComponentProps, Events, ScopedSlots> {
/* snip */
}
Make existing component tsx-supported.
If you can't modify existing component definition, wrap it by ofType
and convert
import ThirdPartyComponent from "third-party-library";
import * as tsx from "vue-tsx-support";
interface MyComponentProps { /* ... */ }
const MyComponent = tsx.ofType<MyComponentProps>().convert(ThirdPartyComponent);
Of course you can specify event types and scoped slot types if you want.
const MyComponent = tsx.ofType<MyComponentProps, Events, ScopedSlots>().convert(ThirdPartyComponent);
Sometimes you may want to specify native event listener or dom property to the component like below.
But unfortunately, vue-tsx-support
does not support this.
// NG: because `nativeOnClick` is not a prop of MyComponent
<MyComponent text="foo" nativeOnClick={ ... } />
// NG: because `domPropInnerHTML` is not a prop of MyComponent
<MyComponent text="foo" domPropInnerHTML={ ... } />
To avoid compilation error, you must use kebab-case attribute name.
// OK
<Component nativeOn-click={ ... } />
// OK
<Component domProp-innerHTML={ ... } />
Or use JSX-spread style.
// OK
<Component { ...{ nativeOn: { click: ... } } } />
// OK
<Component { ...{ domProps: { innerHTML: ... } } } />
For native events, there is an another solution. See enable-nativeon
option.
And sometimes, you may want to specify HTML attributes to the component like below.
But unfortunately, vue-tsx-support
does not support this, too.
// NG: because `min` and `max` are not props of SomeInputComponent
<SomeInputComponent min={ 0 } max={ 100 } />
To avoid compilation error, you must use JSX-spread style.
// OK
<SomeInputComponent { ...{ attrs: { min: 0, max: 100 } } } />
Or enable enable-html-attributes
option.
vue-tsx-support
has some options which change behaviour globally.
See under the options
directory.
To enable each options, import them somewhere
// enable `allow-unknown-props` option
import "vue-tsx-support/options/allow-unknown-props";
Make enabled to specify unknown attributes to intrinsic elements
// OK:`foo` is unknown attribute, but can be compiled
<div foo="foo" />;
Make enabled to specify unknown props to Vue component.
const MyComponent = vuetsx.createComponent<{ foo: string }>({ /* ... */ });
// OK: `bar` is unknown prop, but can be compiled
<MyComponent foo="foo" bar="bar" />;
Make enabled to specify HTML attributes to Vue component.
const MyComponent = vuetsx.createComponent<{ foo: string }>({ /* ... */ });
// OK: `min` and `max` are valid HTML attributes
<MyComponent foo="foo" min={ 0 } max={ 100 } />;
// NG: compiler checks type of `min` (`min` must be number)
<MyComponent foo="foo" min="a" />;
Make enabled to specify native event listeners to Vue component.
const MyComponent = vuetsx.createComponent<{ foo: string }>({ /* ... */ });
// OK
<MyComponent foo="foo" nativeOnClick={ e => ... } />; // and `e` is infered as MouseEvent
Add definitions of router-link
and router-view
Event handler wrappers which work like some event modifiers available in template
import { modifiers as m } from "vue-tsx-support";
// Basic usage:
// Equivalent to `<div @keydown.enter="onEnter" />`
<div onKeydown={m.enter(this.onEnter)} />;
// Use multiple modifiers:
// Equivalent to `<div @keydown.enter.prevent="onEnter" />`
<div onKeydown={m.enter.prevent(this.onEnter)} />;
// Use without event handler:
// Equivalent to `<div @keydown.esc.prevent />`
<div onKeydown={m.esc.prevent} />;
// Use multiple keys:
// Equivalent to `<div @keydown.enter.esc="onEnterOrEsc" />`
<div onKeydown={m.keys("enter", "esc")(this.onEnterOrEsc)} />;
// Use exact modkey combination:
// Equivalent to `<div @keydown.65.ctrl.alt.exact="onCtrlAltA" />`
<div onKeydown={m.keys(65).exact("ctrl", "alt")(this.onCtrlAltA)} />;
-
esc
,tab
,enter
,space
,up
,down
,del
,left
,right
Execute event handler only when specified key is pressed.
⚠️ del
allows not only DELETE, but also BACKSPACE.
⚠️ left
andright
have another behavior when specified to mouse event
⚠️ combination of key modifiers (e.g.m.enter.esc
) does not work. See keys -
left
,right
,middle
Execute event handler only when specified mouse button is pressed.
⚠️ left
andright
have another behavior when specified to keyboard event -
ctrl
,shift
,alt
,meta
Execute event handler only when specified system modifier key is pressed.
-
noctrl
,noshift
,noalt
,nometa
Execute event handler only when specified system modifier key is not pressed.
-
self
Execute event handler only when event.target is the element itself (not from children).
-
prevent
,stop
Call
preventDefault
orstopPropagation
of event object before executing event handler.
-
keys(...args)
Execute event handler only when one of specified key is pressed.
Known key name("esc", "tab", "enter", ...) or number can be specified.// when enter or esc pressed <div onKeydown={m.keys("enter", "esc")(handler)} />; // when 'a' pressed <div onKeydown={m.keys(65)(handler)} />;
-
exact(...args)
Execute event handler only when specified system modifier keys are all pressed, and others are not pressed.
// when CTRL, SHIFT are both pressed, and ALT, META are both not pressed <div onClick={m.exact("ctrl", "shift")(handler)} />;
MIT