solidjs / solid

A declarative, efficient, and flexible JavaScript library for building user interfaces.
https://solidjs.com
MIT License
32.05k stars 914 forks source link

How JSX.Element(children) support generics (component type) ? #573

Closed vnaki closed 3 years ago

vnaki commented 3 years ago

How SolidJS's JSX.Element support generics? (Typescript)

Like React Component (Menu & MenuItem)


interface MenuItemProps {
       children: React.ReactElement
}

function MenuItem(props:MenuItemProps) {
       return  <div className="menu-item">{props.children}</div>
}

interface MenuProps {
       children: React.ReactElement<MenuItemProps>
}

function Menu(props:MenuProps) {
        return  <div className="menu">{props.children}</div>
}

// html 
<div className="container">
<Menu>
    <MenuItem>Menu One</MenuItem>
    <MenuItem>Menu Two</MenuItem>
    <MenuItem>Menu Three</MenuItem>
    {/* Only MenuItem is allowed */}
</Menu>
</div>

The above React. ReactElement supports generics, eg React.ReactElement<MenuItemProps>

How does JSX. Element of SolidJs support generics ?

SolidJS Typescript Code:

import { JSX } from 'solid-js';

interface MenuItemProps {
       children: JSX.Element
}

function MenuItem(props:MenuItemProps) {
       return  <div className="menu-item">{props.children}</div>
}

interface MenuProps {
       children: JSX.Element
}

function Menu(props:MenuProps) {
        return  <div className="menu">{props.children}</div>
}

My English level is general.

atk commented 3 years ago

You could use const Menu: Component<MenuProps> = … to do the same. In Solid, JSX.Element could either be a HTMLElement, a string, null, undefined, a function that returns the other types or an array of the aforementioned, which is not necessarily connected to these Props.

vnaki commented 3 years ago

@atk Thanks! I'll try!

vnaki commented 3 years ago

How can chidlren of the Menu only be MenuItem ?

interface MenuItemProps {
       children: any
}

interface MenuProps {
      // key point
       children: React.ReactElement<MenuItemProps> | React.ReactElement<MenuItemProps>[]
}
interface MenuItemProps {
       children: any
}

interface MenuProps {
       children: JSX.Element
}

export namespace JSX {

    type Element = Node  | ArrayElement  | FunctionElement   | string   | number  | boolean   | null  | undefined;

       ....

}

export declare type Component<P = {}> = (props: PropsWithChildren<P>) => JSX.Element;

export declare type PropsWithChildren<P = {}> = P & {
    //  这里决定了Menu's  children只能是 Node | ArrayElement | ...| null |  undefined 这些类型,   不能是 MenuItem吧。
    //  This determines that the children can only be string、 number、null、Node、..., etc
    //  Is this the defect of children ?
    children?: JSX.Element;
};
atk commented 3 years ago

You can use children: ReturnType<MenuItem>[] | ReturnType<MenuItem> | (() => ReturnType<MenuItem>[] | ReturnType<MenuItem>) to convince the type checker to mark other items as error.

ryansolid commented 3 years ago

Thanks @atk. That makes a lot of sense since we don't have VDOM nodes so it's at the mercy of what is being returned from our Component factory functions. Which is any combination or those. I wonder if this is extractable into a generic type helper in an obvious and useful way.

atk commented 3 years ago

That and an extracted generic type for the signal setters would be rather useful.

I'd suggest the types JSX.Output<C extends Component> and Setter<T>.

atk commented 3 years ago

Unfortunately, that does not work; Component's return type is fixed to JSX.Element. We'd need to change a lot of the types a great deal in order to make this possible. But it would be really cool if it could work.

vnaki commented 3 years ago

@atk Thank you. Your answer is very useful!

vnaki commented 3 years ago

@atk @ryansolid

As follows:

export declare type PropsWithChildren<P = {}> = P & {
   // here
    children?: JSX.Element;
};

export declare type Component<P = {}> = (props: PropsWithChildren<P>) => JSX.Element; // here 

Yeah, that's the problem. Component's return type is fixed to JSX.Element

Is there any way to solve it ?

atk commented 3 years ago

Even if we inferred the output type, we would still not be able to tell two components with possible identical output (e.g. HTMLDivElement) apart. The only possible way to make the result distinguishable would be to add a name attribute manually somehow, but that would also have to be supported by dom expressions.