How does TypeScript handle JSX
Use the React JSX as an example to see how TypeScript handles React JSX.
<div id="main">
<MyComponent title="my-component"></MyComponent>
</div>Copy the code
This code covers all aspects of JSX use in React: host elements, host element attributes, custom components, custom component attributes, and Children. These are the only concepts in the seemingly complex JSX fragment. To recognize these concepts, TypeScript defines several interfaces and allows users to choose their own implementations to map TypeScript to React JSX.
TypeScript defines several interfaces as follows:
React JSX |
TypeScript JSX definition |
---|---|
The host element |
IntrinsicElements |
Host element attributes |
IntrinsicAttributes IntrinsicClassAttributes |
Custom Components |
Element ElementClass About the difference: www.typescriptlang.org/docs/handbo… |
Customize component properties |
Function component: The first argument to a function The Class components: ElementAttributesProperty |
Children |
ElementChildrenAttribute |
How does TypeScript handle React JSX
Use the TypeScript JSX definition provided by React as an example:
declare global {
namespace JSX {
// tslint:disable-next-line:no-empty-interface
interface Element extends React.ReactElement<any, any> { }
interface ElementClass extends React.Component<any> {
render(): React.ReactNode;
}
interface ElementAttributesProperty { props: {}; }
interface ElementChildrenAttribute { children: {}; }
// tslint:disable-next-line:no-empty-interface
interface IntrinsicAttributes extends React.Attributes { }
// tslint:disable-next-line:no-empty-interface interface IntrinsicClassAttributes<T> extends React.ClassAttributes<T> { } interface IntrinsicElements { // HTML ... form: React.DetailedHTMLProps<React.FormHTMLAttributes<HTMLFormElement>, HTMLFormElement>; h1: React.DetailedHTMLProps<React.HTMLAttributes<HTMLHeadingElement>, HTMLHeadingElement>; . }}}Copy the code
Using this JSX configuration, you can tell TypeScript:
-
When Element is encountered, use react. ReactElement to type check
-
For instances of the Class component, this, use react.componentfor type checking
-
When an Element of Class type is encountered, the properties above it are checked using the props property of ElementClass
-
When you encounter an Element of a function type, the attribute above it is checked directly with the first argument to the function
-
Note that this rule is a TypeScript default and is not derived from the configuration file above
-
-
When encountering Element, its Children, go to the Children property of ElementClass props to check
-
When encountering IntrinsicElement, check directly with the IntrinsicElements definition, namely this code:
{ // HTML ... form: React.DetailedHTMLProps<React.FormHTMLAttributes<HTMLFormElement>, HTMLFormElement>; h1: React.DetailedHTMLProps<React.HTMLAttributes<HTMLHeadingElement>, HTMLHeadingElement>; . }Copy the code
-
Encountered IntrinsicElement, its normal Attributes and class Attributes are checked using react. Attributes and react. ClassAttributes, respectively
How does TypeScript know whether it is Element (custom component) or IntrinsicElement (host component)?
By the case of the first letter after “<“, Element in uppercase, Element in lowercase.
How does TypeScript handle Vue’s JSX
Take a look at the JSX configuration of TypeScript that we often use in vUE projects:
import Vue, { VNode } from 'vue'
declare global {
namespace JSX {
// tslint:disable no-empty-interface
interface Element extends VNode {}
// tslint:disable no-empty-interface
interface ElementClass extends Vue {}
interface IntrinsicElements {
[elem: string]: any
}
}
}Copy the code
Using this JSX configuration, you can tell TypeScript:
-
When Element is encountered, use Vue’s VNode for type checking
-
For instances of the Class component, namely this, Vue is used for type checking
-
An Element of type Class was encountered, and its property is undefined
-
When you encounter an Element of a function type, the attribute above it is checked directly with the first argument to the function
-
Note that this rule is a TypeScript default and is not derived from the configuration file above
-
-
Encounter Element, its Children, undefined
-
When encountering IntrinsicElement, check directly with the IntrinsicElements definition
-
While IntrinsicElements are defined here, it is equivalent to allowing any host tags even if they are not fully defined in HTML, such as
.
-
-
Encountered IntrinsicElement, its general and class attributes are undefined
In contrast, the JSX definition of TypeScript used in Vue is much less detailed than that of React. We can even draw the following conclusions from this configuration:
-
The Class type of JSX components in Vue do not have the functions verification capability
-
Children of JSX in Vue do not have verification capability
-
Neither the host component of JSX in Vue nor the properties of the host component have verification capabilities
This is completely consistent with our practical results.
How to make TypeScript perfectly compatible with Vue2. X
We just need to fill in the above undefined or incomplete parts. Let’s think briefly:
-
When we encounter an Element of type Class, the attribute on it naturally uses the $props attribute in Vue
-
When encountering Element, its Children automatically use the $slots attribute of Vue
-
IntrinsicElement adds completion
It works for point 3, just a bit of work. But for points 1 and 2, it’s not so simple.
Vue $props is not generic at all. Even if you specify $props, TypeScript is still a function.
readonly $props: Record<string, any>;Copy the code
For point 2: Vue’s $slots not only has no generic concept, it also conflicts with the TypeScript children definition. TypeScript defines children as an Element or an array of elements, but $slots is a key:value object in Vue.
readonly $slots: { [key: string]: VNode[] | undefined };
readonly $scopedSlots: { [key: string]: NormalizedScopedSlot | undefined };Copy the code
Therefore, we cannot simply supplement the JSX definition of Vue. We need to give Vue props the ability to generify, and we also need to smooth out the differences defined between Vue slots and TypeScript children. We have to do some hacks.
Vue-tsx-support solution
By introducing the JSX definition file provided by VUE-TX-Support, we can achieve our demands, which is defined as follows:
import * as base from "./types/base";
import * as builtin from "./types/builtin-components";
import "./types/vue";
declare global {
namespace JSX {
interface Element extends base.Element {}
interface ElementClass extends base.ElementClass {}
interface ElementAttributesProperty extends base.ElementAttributesProperty {}
interface IntrinsicElements extends base.IntrinsicElements {
// allow unknown elements
[name: string]: any;
// builtin components
transition: base.TsxComponentAttrs<builtin.TransitionProps>;
"transition-group": base.TsxComponentAttrs<builtin.TransitionGroupProps>;
"keep-alive": base.TsxComponentAttrs<builtin.KeepAliveProps>; }}}Copy the code
As you can see, the vue TSX – supoort the key Element, ElementClass, ElementAttributesProperty and IntrinsicElements the four definitions, use your own definition of JSX type do the replacement.
By looking at the code, we found that the base Element and bae ElementClass actually still VNode and Vue, IntrinsicElements also is better understood, and so here we are mainly analyzes ElementAttributesProperty.
ElementAttributesProperty are defined as follows:
export interface ElementAttributesProperty {
_tsxattrs: any;
}Copy the code
That is, vue-tsX-support tells TypeScript to go to ElementClass’s _tsxattrs for type checking when it encounters a Class component attribute. So where did the _tsxattrs attribute come from?
As can be seen from the official example of VUE-TSX-support:
import * as tsx from "vue-tsx-support";
const MyComponent = tsx.componentFactory.create({
props: {
text: { type: String, required: true },
important: Boolean,
},
...
});Copy the code
We had to use vuE-Tx-support to create vUE instances, so VUe-TX-support was bound to do something about our VUE instances. Along the way, we eventually found this code:
exportclass Component< Props, EventsWithOn = {}, ScopedSlotArgs = {} > extends Vue { _tsxattrs! : TsxComponentAttrs<Props, EventsWithOn, ScopedSlotArgs>;$scopedSlots! : { [Kin keyof ScopedSlotArgs]: InnerScopedSlot<ScopedSlotArgs[K]>
};
}Copy the code
That is, vue-tsx-supoort extends our vue instance with its own Component, adding the _tsxattrs attribute to it and modifying the $scopedSlots attribute. More importantly, we see here that Component supports generics, so this gives us the ability to specify props and scopedSlots types when writing Vue components.
Finally, for OO development, we’ll use a combination of vue-class-Component and vue-tx-supoort:
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 */
}Copy the code
The only pity is that we had to declare props twice, once to vue-class-Component and once to vue-tsx-Component.
At this point, we have basically solved the type verification of Vue’s JSX Class component properties.
Still unresolved questions — slots
Vue-tsx-support, though, gives Vue props the ability to generalize, allowing TypeScript to parse Class component properties. But there was another problem that we still had, and that was the conflicting definition between slots and Children. That is, even with Vue-tx-supoort, TypeScript can’t check Element’s Children.
ScopedSlots is actually React renderProps, which is essentially still props. It doesn’t have anything to do with slots.
digression
We might wonder: what’s the point of struggling with TypeScript for Vue2 when Vue3 is coming out?
It’s common to hear people say that Vue3 is written in TypeScript, so TypeScript support must be fine. But as I’ve seen in this article and our analysis of Why vue2.x Sucks in TypeScript, this is not a serious statement.
So it makes sense to play around with TypeScript, not only to deepen our understanding of TypeScript, but also to help us think for ourselves.
reference
github.com/vuejs/vue
Github.com/wonderful-p…
To the end.