-
Notifications
You must be signed in to change notification settings - Fork 35
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
How do I use this with TypeScript? #19
Comments
I think you're after a declaration like this: declare namespace JSX {
interface IntrinsicElements {
[elemName: string]: any;
}
} See https://www.typescriptlang.org/docs/handbook/jsx.html#intrinsic-elements. |
Although the above works - if any of your code imports something that imports React (like storybook), those global JSX types will be included, and those will be prioritised over And anything that's imported explicitly (even through something else) cannot be ignored/excluded in the Hoping someone else has a solution for this :) |
@types/vhtml is now public (see #25 (comment)). I think we can close this issue. |
The same issue @ThaNarie mentioned about global JSX occurs in the DefinitelyTyped version as well right? Perhaps an issue for that can be opened there for something like the preact types approach. Either way I think this is resolved. Thanks @pastelmind |
@AndrewLeedham Probably. I suppose we could split the /// <reference types="vhtml/jsx" />
// This can also work
import {} from "vhtml/jsx"; @types/react seems to be using similar approach. See Since I am unfamiliar with this trick, I am hesitant to work on it myself. Feel free to submit a PR to DefinitelyTyped. |
I've been experimenting with enabling children type-checking with TypeScript. I was unsuccessful. It turns out TypeScript has some strict assumptions about how child components are passed around in JSX. function MyComponent(props: { children: any }): JSX.Element {
/* ... */
}
const noChild = <MyComponent/>;
const oneChild = <MyComponent><div>the child</div></MyComponent>;
const manyChildren = (
<MyComponent>
<div>child 1</div>
<div>child 2</div>
<div>child 3</div>
</MyComponent>
); When TypeScript examines the
However, vhtml actually does this:
Only the last assumptions match. Because of this, TypeScript will happily accept the following code: const WantString = (props: { children: string }) => {
// Should be fine, right?
return props.children.toLowerCase();
}
// TypeScript: I think you're receiving props.children === "asdf"
// vhtml: Nope, I will pass props.children === ["asdf"] and your component will die trying to lowercase an array
const result = <WantString>{"asdf"}</WantString> The only way of guaranteeing type safety is to type all children as I suspect that TypeScript's assumptions are largely based on how
Edit: Looks like Preact has got into this as well. See preactjs/preact#1008 and preactjs/preact#1116 where they hacked their way around the discrepancy between Preact's |
This reverts commit 19aa2a9. This won't work, because vhtml handles the `props.children` attribute in a way different from how TypeScript expects things. TypeScript believes that: - A component with no child receives props.children === undefined - A component with one child receives props.children === typeof child - A component with multiple children receives [...children] However, vhtml always wraps children in an array, even if there is only 0 or 1 child. Because of this, enforcing strict type checking on children would result in incorrect type checks that could be actively harmful. See more discussion and example at: - developit/vhtml#19 (comment)
@types/vhtml 2.2.1 supports strict children type checks! preactjs/preact#1116 (comment) gave me a hint to look into ExamplesFor example, given the following component: function Component(props: { children: string[] }): JSX.Element {
/* ... */;
} TypeScript will allow any number of children, as long as they all evaluate to a string: <Component>Foo{"bar"}<div>baz</div></Component>; // OK
<Component>{1}</Component>; // Error! Childless componentIf you omit const NoChildren() => <div>No children!</div>;
let result1 = <NoChildren/>; // OK
let result2 = <NoChildren>This won't work</NoChildren>; // Compile error Single-child componentIf you use a tuple literal with exactly one element, TypeScript will check that too. // vhtml will flatten and concatenate arrays in JSX, so this is fine
const OneChild(props: { children: [string] }) => <div>{props.children}</div>;
let result1 = <OneChild/>; // Compile error
let result2 = <OneChild>Yes</OneChild>; // OK
let result3 = <OneChild><div>1</div><div>2</div></OneChild>; // Compile error Rejecting invalid children typevhtml always passes // props.children is not an array!
const BadComponent(props: { children: string }) => <div>{props.children}</div>;
let result1 = <BadComponent/>; // Compile error
let result2 = <BadComponent>Yes</BadComponent>; // Compile error
let result3 = <BadComponent><div>1</div><div>2</div></BadComponent>; // Compile error All this is made possible by using a series of conditional checks. Should one need to support more type checks, all we need is to expand those checks. LimitationsUnfortunately, these tricks aren't as flexible as I would like them to be. For example, you can't enforce type checks for N-length tuple types (N > 1)--TypeScript will simply treat them as arbitrary-length arrays // I want exactly three children, in the order of boolean, string, number
const Imperfect(props: { children: [boolean, string, number]; }) => { /* ... */ };
// ...but TypeScript will treat the above as (boolean | string | number)[]
// so this compiles just fine:
<Imperfect>{true}</Imperfect>; I haven't bothered to tackle this, but it seems we would need variadic tuples (introduced in TypeScript 4.0) to make it happen. Since TypeScript 3.x is still going strong, it would be prudent not to use bleeding edge features in our type definitions. (If you can make it happen in TypeScript 3.x, please submit a PR to DefinitelyTyped! We would appreciate it very much.) Extending intrinsic attributesAnother caveat: To ensure that plain HTML tags accept anything as children, they internally have a // I want my component to act like a <div> with benefits.
// I know: I'll extend the built-in interface used for type checking <div>
type Props = JSX.IntrinsicAttributes['div'] & { someProp?: number };
const BetterDiv = (props: Props) => {
const { children, ...rest } = props;
return <div {...rest}>{ /* ... */ }</div>;
}
<BetterDiv someProp={12}>foo bar</BetterDiv>; // Compile error To work around the issue, always define your own children prop: type DivProps = JSX.IntrinsicAttributes['div'];
interface Props extends DivProps {
children: any[];
someProp?: number;
}
const BetterDiv = (props: Props) => {
const { children, ...rest } = props;
return <div {...rest}>{ /* ... */ }</div>;
}
<BetterDiv someProp={12}>foo bar</BetterDiv>; // OK |
Instead of injecting the `JSX` namespace as a global symbol, move it under a new namespace named `vhtml`. This `vhtml` namespace is merged with the `vhtml()` function. Library consumers can access it as `.JSX`: ```ts import h from "vhtml"; type A = h.JSX.Element; ``` The `JSX` namespace can also be imported directly: ``` import h, {JSX} from "vhtml"; type A = JSX.Element; ``` This should allow vhtml-powered JSX code to be used together with other JSX-based libraries, such as React. It _should_ solve the issue: - developit/vhtml#19 (comment) Background: TypeScript 2.8 supports locally scoping the `JSX` namespace. This allows a package to use multiple libraries that expose different variations of the `JSX` namespace without conflicts. See: - https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-8.html#locally-scoped-jsx-namespaces - microsoft/TypeScript#22207 Note: This does not allow you to use two JSX-based libraries together in a single module. No transpiler that I know of supports this use case.
Instead of injecting the `JSX` namespace as a global symbol, move it under a new namespace named `vhtml`. This `vhtml` namespace is merged with the `vhtml()` function. Library consumers can access it as `.JSX`: import h from "vhtml"; type A = h.JSX.Element; The `JSX` namespace can also be imported directly: import h, {JSX} from "vhtml"; type A = JSX.Element; This should allow vhtml-powered JSX code to be used together with other JSX-based libraries, such as React. It _should_ solve the issue: - developit/vhtml#19 (comment) Background: TypeScript 2.8 supports locally scoping the `JSX` namespace. This allows a package to use multiple libraries that expose different variations of the `JSX` namespace without conflicts. See: - https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-8.html#locally-scoped-jsx-namespaces - microsoft/TypeScript#22207 Note: This does not allow you to use two JSX-based libraries together in a single module. No transpiler that I know of supports this use case.
@pastelmind BREAKING CHANGE: Instead of injecting the `JSX` namespace as a global symbol, move it under a new namespace named `vhtml`. This `vhtml` namespace is merged with the `vhtml()` function. Library consumers can access it as `.JSX`: import h from "vhtml"; type A = h.JSX.Element; The `JSX` namespace can also be imported directly: import h, {JSX} from "vhtml"; type A = JSX.Element; This should allow vhtml-powered JSX code to be used together with other JSX-based libraries, such as React. It _should_ solve the issue: - developit/vhtml#19 (comment) Background: TypeScript 2.8 supports locally scoping the `JSX` namespace. This allows a package to use multiple libraries that expose different variations of the `JSX` namespace without conflicts. See: - https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-8.html#locally-scoped-jsx-namespaces - microsoft/TypeScript#22207 Note: This does not allow you to use two JSX-based libraries together in a single module. No transpiler that I know of supports this use case.
I can figure out the:
But I can't figure out how to get the typings going for
h
and things likechildren
,Specifically the error I'm getting on any JSX element is:
The text was updated successfully, but these errors were encountered: