From 8f9b8298a581af6bba10a15f8bf1d9d795763c1d Mon Sep 17 00:00:00 2001 From: Alex Macleod Date: Fri, 10 Aug 2018 23:01:24 +0100 Subject: [PATCH] Enable Typescript to understand JSX default props See https://github.com/Microsoft/TypeScript/pull/24422 This addition allows a Component to use defaultProps without having to declare them as optional, in a nutshell: Before class Before extends Component<{ prop?: string }> { static defaultProps = { prop: "default value" }; render() { // this.props.prop is string|undefined } } const element = ; After class After extends Component<{ prop: string }> { static defaultProps = { prop: "default value" }; render() { // typeof this.props.prop is string } } const element = ; The definition isn't perfect, it doesn't quite understand type unions where the type of a single property changes, e.g. { type: "number"; value: number } | { type: "string"; value: string } But this case doesn't break, it just would require you to still provide a property. There might be some more things we can do with LibraryManagedAttributes, it could allow the children property to be correct within Components (always an Array) whilst still allowing components to specify the type of children they accept. --- package.json | 2 +- src/preact.d.ts | 14 ++++++++++ test/ts/preact.tsx | 65 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 80 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index f6a33caef0..ffc4de9d9c 100644 --- a/package.json +++ b/package.json @@ -119,7 +119,7 @@ "rollup-plugin-node-resolve": "^3.0.0", "sinon": "^4.4.2", "sinon-chai": "^3.0.0", - "typescript": "^2.9.0-rc", + "typescript": "^3.0.1", "uglify-js": "^2.7.5", "webpack": "^4.3.0" }, diff --git a/src/preact.d.ts b/src/preact.d.ts index 6b1fc74f2a..2f98a50aa9 100644 --- a/src/preact.d.ts +++ b/src/preact.d.ts @@ -124,6 +124,15 @@ declare namespace preact { }; } +type Defaultize = + // Distribute over unions + Props extends any + ? // Make any properties included in Default optional + & Partial>> + // Include the remaining properties from Props + & Pick> + : never; + declare global { namespace JSX { interface Element extends preact.VNode { @@ -140,6 +149,11 @@ declare global { children: any; } + type LibraryManagedAttributes = + Component extends { defaultProps: infer Defaults } + ? Defaultize + : Props; + interface SVGAttributes extends HTMLAttributes { accentHeight?: number | string; accumulate?: "none" | "sum"; diff --git a/test/ts/preact.tsx b/test/ts/preact.tsx index 8ac748f4c9..5f3a4b4d66 100644 --- a/test/ts/preact.tsx +++ b/test/ts/preact.tsx @@ -125,3 +125,68 @@ class ComponentWithLifecycle extends Component { console.log("componentDidUpdate", previousProps, previousState, previousContext); } } + +// Default props: JSX.LibraryManagedAttributes + +class DefaultProps extends Component<{text: string, bool: boolean}> { + static defaultProps = { + text: "hello" + }; + + render() { + return
{this.props.text}
; + } +} + +const d1 = ; +const d2 = ; + +class DefaultPropsWithUnion extends Component< + { default: boolean } & ( + | { + type: "string"; + str: string; + } + | { + type: "number"; + num: number; + }) +> { + static defaultProps = { + default: true + }; + + render() { + return
; + } +} + +const d3 = ; +const d4 = ; +const d5 = ; +const d6 = ; + +class DefaultUnion extends Component< + | { + type: "number"; + num: number; + } + | { + type: "string"; + str: string; + } +> { + static defaultProps = { + type: "number", + num: 1 + }; + + render() { + return
; + } +} + +const d7 = ; +const d8 = ; +const d9 = ; +const d10 = ;