I'd like to use JSX syntax in TypeScript, but I don't want to use React.
I have seen answers to other related questions here on SO, but nothing was complete or detailed enough to be of any help. I've read this guide and the JSX chapter of the handbook, and it didn't help much. I don't understand how the language-feature itself really works.
I've tried to examine the React declarations, but it's just too huge for me to absorb - I need a minimal, working example demonstrating type-checked elements, components and attributes. (It doesn't need to have a working implementation of the factory-function, I'm mostly interested in the declarations.)
What I'd like, minimally, is an example that makes the following work, with type-safety:
var el = <Ping blurt="ya"></Ping>;
var div = <div id="foo">Hello JSX! {el}</div>;
I assume I'll need to declare JSX.IntrinsicElements
at least, and a createElement()
factory-function of some sort, but that's about as far as I got:
declare module JSX {
interface IntrinsicElements {
div: flurp.VNode<HTMLDivElement>;
}
}
module flurp {
export interface VNode<E extends Element> {
id: string
}
export function createElement<T extends Element>(type: string, props?: any, ...children: (VNode<Element>|string)[]): VNode<Element> {
return {
id: props["id"]
};
}
}
class Ping {
// ???
}
var el = <Ping blurt="ya"></Ping>;
var div = <div id="foo">Hello JSX! {el}</div>;
I compile this with tsconfig.json
like:
{
"compilerOptions": {
"target": "es5",
"jsx": "react",
"reactNamespace": "flurp"
}
}
Hovering over the <div>
element and id="foo"
attribute, I can see the compiler understands my declaration of intrinsic elements - so far, so good.
Now, to get the <Ping blurt="ya">
declaration to compile, with type-checking for the blurt
attribute, what do I do? Should Ping
even be a class
or is it maybe a function
or something?
Ideally, elements and components should have a common ancestor of some sort, so I can have a common set of attributes that apply to both.
I'm looking to create something simple and lightweight, perhaps along the lines of monkberry, but with a JSX syntax, e.g. using components like <If>
and <For>
rather than inline statements. Is that even feasible? (Is there any existing project along those lines I can look at for reference?)
Please, if anyone can provide a working, minimal example of how to use the language feature itself, that would be a huge help!
Indeed, the JSX section of the TypeScript handbook is a great resource; for those of you who haven't, check it out.
If you want tsc
to compile your JSX, you will need to do the following (extremely broad overview):
IntrinsicElements
interface/type in the JSX
namespace (enable type-safety)."react"
code generation and point to your JSX Factory (using the "jsxFactory"
option).Here's a more in-depth look at this. Also, you might like to see a repository or maybe a working example/playground.
Here, you tell TypeScript how to interpret your JSX code. At a minimum, you need to declare IntrinsicElements
, but there are other types you can declare that will give you better type-hinting, enable component features, and generally improve/tweak how your JSX is understood by TypeScript.
Here's an example declaration of the JSX namespace:
/// <reference lib="DOM" />
declare namespace JSX {
// The return type of our JSX Factory: this could be anything
type Element = HTMLElement;
// IntrinsicElementMap grabs all the standard HTML tags in the TS DOM lib.
interface IntrinsicElements extends IntrinsicElementMap { }
// The following are custom types, not part of TS's known JSX namespace:
type IntrinsicElementMap = {
[K in keyof HTMLElementTagNameMap]: {
[k: string]: any
}
}
interface Component {
(properties?: { [key: string]: any }, children?: Node[]): Node
}
}
A few notes:
HTMLElement
s and Node
s, the DOM lib (lib.dom.d.ts) is a great resource to get better type checking. In this example, I've used it to declare all real HTML tags as valid Intrinsic elements. You could further extend this, for example, to preload EventHandler
s like onclick
via GlobalEventHandlers
.Element
is another type TypeScript looks for in the JSX namespace. This is optional (defaulting to any
). Use this to specify the return type of your factory function.Component
interface.JSX.ElementClass
interface. See here for more details.The JSX
namespace helps us define how we intend to use JSX in our code, but we still need to implement a function that can handle the code generated by tsc
. Our factory function should follow the form (tag: string, properties: { [k: string]: any }, ...children: any[]): any
, or something more specific. Here's an example that will enable functional components (N.B. it's big and ugly):
function jsx(tag: JSX.Tag | JSX.Component,
attributes: { [key: string]: any } | null,
...children: Node[])
{
if (typeof tag === 'function') {
return tag(attributes ?? {}, children);
}
type Tag = typeof tag;
const element: HTMLElementTagNameMap[Tag] = document.createElement(tag);
// Assign attributes:
let map = (attributes ?? {});
let prop: keyof typeof map;
for (prop of (Object.keys(map) as any)) {
// Extract values:
prop = prop.toString();
const value = map[prop] as any;
const anyReference = element as any;
if (typeof anyReference[prop] === 'undefined') {
// As a fallback, attempt to set an attribute:
element.setAttribute(prop, value);
} else {
anyReference[prop] = value;
}
}
// append children
for (let child of children) {
if (typeof child === 'string') {
element.innerText += child;
continue;
}
if (Array.isArray(child)) {
element.append(...child);
continue;
}
element.appendChild(child);
}
return element;
}
Now, all that's left is to use our jsx
function. Our tsconfig should configure at least the following:
{
"compilerOptions": {
"jsx": "react",
"jsxFactory": "jsx",
}
}
Now, we can write JSX in any .tsx
file as long as we import our jsxFactory
:
import jsx from './jsxFactory';
function Ping({ blurt }: { blurt: string }) {
return <div>{blurt}</div>
}
var el = <Ping blurt="ya"></Ping>;
// var div: HTMLElement
var div = <div id="foo">Hello JSX! {el}</div>;
document.body.appendChild(div)
Thanks to JSX.Element
, TypeScript knows the result of JSX is an HTMLElement
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With