Skip to content

Commit

Permalink
Revised Component types (children, ref) (#877)
Browse files Browse the repository at this point in the history
* Revised Component types (children, readonly, ref)

* Rename past `Component` type to `ComponentWithChildren`.
  Motivation: Most components don't actually support children.
  This causes a type error upon accidental passing of children /
  forces you to think about which components support children.
* Added second parameter to `ComponentWithChildren`
  to make it easier to give a custom type for `children`
  (but still defaults to useful `JSX.Element`).
* New `Component` type does not have automatic `children` property
  (like React's preferred `VoidFunctionalComponent`),
  offering another natural way to type `props.children`:
  `Component<{children: JSX.Element}>`.
* `props` argument in both `Component` and `ComponentWithChildren`
  automatically cast to `readonly` (shallow one level only),
  to avoid accidental assignment to `props.foo` (usually a getter)
  while still allowing passing mutables in props.
  Add `Props<T>` helper for this transformation.
* Add @lxsmnsyc's `Ref<T>` type so it's easy to type `props.ref`:
  `Component<{ref: Ref<Element>}>`.
  <#778 (comment)>
  Fixes #778.

* Fix test

* Remove Props helper and shallow Readonly for props

* VoidComponent, ParentComponent, FlowComponent

* Use Component<any> for generic component type

* Add default types, Context.Provider require children

Comments from Otonashi

* Default parameter for PropsWithChildren

* Restore missing <any>

* Docs typo fix

* Cleanup server code

* JSDoc improvements

* Improve FlowProps JSDoc

* More FlowComponent JSDoc improvements

Co-authored-by: Ryan Carniato <[email protected]>
  • Loading branch information
edemaine and ryansolid authored May 3, 2022
1 parent ee94b5e commit f9c5675
Show file tree
Hide file tree
Showing 4 changed files with 84 additions and 14 deletions.
9 changes: 5 additions & 4 deletions packages/solid/src/reactive/signal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import { requestCallback, Task } from "./scheduler";
import { setHydrateContext, sharedConfig } from "../render/hydration";
import type { JSX } from "../jsx";
import type { FlowComponent, FlowProps } from "../render";

export const equalFn = <T>(a: T, b: T) => a === b;
export const $PROXY = Symbol("solid-proxy");
Expand Down Expand Up @@ -1047,7 +1048,7 @@ export function serializeGraph(owner?: Owner | null): GraphRecord {
};
}

export type ContextProviderComponent<T> = (props: { value: T; children: any }) => any;
export type ContextProviderComponent<T> = FlowComponent<{ value: T; }>;

// Context API
export interface Context<T> {
Expand All @@ -1061,7 +1062,7 @@ export interface Context<T> {
* ```typescript
* interface Context<T> {
* id: symbol;
* Provider: (props: { value: T; children: any }) => any;
* Provider: FlowComponent<{ value: T }>;
* defaultValue: T;
* }
* export function createContext<T>(defaultValue?: T): Context<T | undefined>;
Expand Down Expand Up @@ -1604,7 +1605,7 @@ function resolveChildren(children: JSX.Element): ResolvedChildren {
}

function createProvider(id: symbol) {
return function provider(props: { value: unknown; children: JSX.Element }) {
return function provider(props: FlowProps<{ value: unknown; }>) {
let res;
createComputed(
() =>
Expand All @@ -1613,7 +1614,7 @@ function createProvider(id: symbol) {
return children(() => props.children);
}))
);
return res as JSX.Element;
return res;
};
}

Expand Down
62 changes: 59 additions & 3 deletions packages/solid/src/render/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,56 @@ export function enableHydration() {
hydrationEnabled = true;
}

export type PropsWithChildren<P = {}> = P & { children?: JSX.Element };
export type Component<P = {}> = (props: PropsWithChildren<P>) => JSX.Element;
/**
* A general `Component` has no implicit `children` prop. If desired, you can
* specify one as in `Component<{name: String, children: JSX.Element>}`.
*/
export type Component<P = {}> = (props: P) => JSX.Element;

/**
* Extend props to forbid the `children` prop.
* Use this to prevent accidentally passing `children` to components that
* would silently throw them away.
*/
export type VoidProps<P = {}> = P & { children?: never };
/**
* `VoidComponent` forbids the `children` prop.
* Use this to prevent accidentally passing `children` to components that
* would silently throw them away.
*/
export type VoidComponent<P = {}> = Component<VoidProps<P>>;

/**
* Extend props to allow an optional `children` prop with the usual
* type in JSX, `JSX.Element` (which allows elements, arrays, functions, etc.).
* Use this for components that you want to accept children.
*/
export type ParentProps<P = {}> = P & { children?: JSX.Element };
/**
* `ParentComponent` allows an optional `children` prop with the usual
* type in JSX, `JSX.Element` (which allows elements, arrays, functions, etc.).
* Use this for components that you want to accept children.
*/
export type ParentComponent<P = {}> = Component<ParentProps<P>>;

/**
* Extend props to require a `children` prop with the specified type.
* Use this for components where you need a specific child type,
* typically a function that receives specific argument types.
* Note that all JSX <Elements> are of the type `JSX.Element`.
*/
export type FlowProps<P = {}, C = JSX.Element> = P & { children: C };
/**
* `FlowComponent` requires a `children` prop with the specified type.
* Use this for components where you need a specific child type,
* typically a function that receives specific argument types.
* Note that all JSX <Elements> are of the type `JSX.Element`.
*/
export type FlowComponent<P = {}, C = JSX.Element> = Component<FlowProps<P, C>>;

/** @deprecated: use `ParentProps` instead */
export type PropsWithChildren<P = {}> = ParentProps<P>;

/**
* Takes the props of the passed component and returns its type
*
Expand All @@ -30,7 +78,15 @@ export type ComponentProps<T extends keyof JSX.IntrinsicElements | Component<any
: T extends keyof JSX.IntrinsicElements
? JSX.IntrinsicElements[T]
: {};
export function createComponent<T>(Comp: (props: T) => JSX.Element, props: T): JSX.Element {

/**
* Type of `props.ref`, for use in `Component` or `props` typing.
*
* @example Component<{ref: Ref<Element>}>
*/
export type Ref<T> = T | ((val: T) => void);

export function createComponent<T>(Comp: Component<T>, props: T): JSX.Element {
if (props == null || typeof props !== "object") props = {} as T;
if (hydrationEnabled) {
if (sharedConfig.context) {
Expand Down
2 changes: 1 addition & 1 deletion packages/solid/src/server/reactive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ export function children(fn: () => any) {
return createMemo(() => resolveChildren(fn()));
}

export function runWithOwner(o: Owner, fn: () => any) {
export function runWithOwner<T>(o: Owner, fn: () => T): T {
const prev = Owner;
Owner = o;
try {
Expand Down
25 changes: 19 additions & 6 deletions packages/solid/src/server/rendering.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,17 @@ import {
} from "./reactive";
import type { JSX } from "../jsx";

type PropsWithChildren<P> = P & { children?: JSX.Element };
export type Component<P = {}> = (props: PropsWithChildren<P>) => JSX.Element;
export type Component<P = {}> = (props: P) => JSX.Element;
export type VoidProps<P = {}> = P & { children?: never };
export type VoidComponent<P = {}> = Component<VoidProps<P>>;
export type ParentProps<P = {}> = P & { children?: JSX.Element };
export type ParentComponent<P = {}> = Component<ParentProps<P>>;
export type FlowProps<P = {}, C = JSX.Element> = P & { children: C };
export type FlowComponent<P = {}, C = JSX.Element> = Component<FlowProps<P, C>>;
export type Ref<T> = T | ((val: T) => void);
export type ComponentProps<T extends keyof JSX.IntrinsicElements | Component> =
T extends Component<infer P> ? P :
T extends keyof JSX.IntrinsicElements ? JSX.IntrinsicElements[T] : {};

type PossiblyWrapped<T> = {
[P in keyof T]: T[P] | (() => T[P]);
Expand Down Expand Up @@ -385,12 +394,16 @@ export function createResource<T, S>(
] as ResourceReturn<T>);
}

export function lazy(fn: () => Promise<{ default: any }>): (props: any) => string {
let resolved: (props: any) => any;
export function lazy<T extends Component<any>>(
fn: () => Promise<{ default: T }>
): T & { preload: () => Promise<{ default: T }> } {
let resolved: T;
const p = fn();
const contexts = new Set<SuspenseContextType>();
p.then(mod => (resolved = mod.default));
const wrap = (props: any) => {
const wrap: Component<ComponentProps<T>> &
{ preload?: () => Promise<{ default: T }> }
= (props) => {
const id = sharedConfig.context!.id.slice(0, -1);
if (resolved) return resolved(props);
const ctx = useContext(SuspenseContext);
Expand All @@ -407,7 +420,7 @@ export function lazy(fn: () => Promise<{ default: any }>): (props: any) => strin
return "";
};
wrap.preload = () => p;
return wrap;
return wrap as T & { preload: () => Promise<{ default: T }> };
}

function suspenseComplete(c: SuspenseContextType) {
Expand Down

0 comments on commit f9c5675

Please sign in to comment.