From 5257b366fd31406fb97e60e240893f0f268d8a7f Mon Sep 17 00:00:00 2001 From: Evan You Date: Fri, 12 Oct 2018 19:49:41 -0400 Subject: [PATCH] types: improve createElement typing --- .vscode/settings.json | 1 - .../core/__tests__/attrsFallthrough.spec.ts | 34 +++------ packages/core/src/component.ts | 4 +- packages/core/src/componentOptions.ts | 12 ++-- packages/core/src/h.ts | 72 +++++++++++++++---- packages/core/src/optional/mixin.ts | 12 +--- packages/core/src/vdom.ts | 10 ++- packages/renderer-dom/src/patchData.ts | 5 +- 8 files changed, 85 insertions(+), 65 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 50f76ba3..bb531a85 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -22,7 +22,6 @@ "rust", "scss", "text", - "typescriptreact", "yml" ] } diff --git a/packages/core/__tests__/attrsFallthrough.spec.ts b/packages/core/__tests__/attrsFallthrough.spec.ts index 41bf51bd..68740cc2 100644 --- a/packages/core/__tests__/attrsFallthrough.spec.ts +++ b/packages/core/__tests__/attrsFallthrough.spec.ts @@ -6,12 +6,8 @@ describe('attribute fallthrough', () => { const click = jest.fn() const childUpdated = jest.fn() - class Hello extends Component<{}, { count: number }> { - data() { - return { - count: 0 - } - } + class Hello extends Component { + count: number = 0 inc() { this.count++ click() @@ -27,7 +23,7 @@ describe('attribute fallthrough', () => { } } - class Child extends Component { + class Child extends Component<{ [key: string]: any }> { updated() { childUpdated() } @@ -73,19 +69,15 @@ describe('attribute fallthrough', () => { const click = jest.fn() const childUpdated = jest.fn() - class Hello extends Component<{}, { count: number }> { - data() { - return { - count: 0 - } - } + class Hello extends Component { + count = 0 inc() { this.count++ click() } render() { return h(Child, { - foo: 1, + foo: 123, id: 'test', class: 'c' + this.count, style: { color: this.count ? 'red' : 'green' }, @@ -94,7 +86,7 @@ describe('attribute fallthrough', () => { } } - class Child extends Component<{ foo: number }> { + class Child extends Component<{ [key: string]: any; foo: number }> { static props = { foo: Number } @@ -148,12 +140,8 @@ describe('attribute fallthrough', () => { const childUpdated = jest.fn() const grandChildUpdated = jest.fn() - class Hello extends Component<{}, { count: number }> { - data() { - return { - count: 0 - } - } + class Hello extends Component { + count = 0 inc() { this.count++ click() @@ -169,7 +157,7 @@ describe('attribute fallthrough', () => { } } - class Child extends Component { + class Child extends Component<{ [key: string]: any; foo: number }> { updated() { childUpdated() } @@ -178,7 +166,7 @@ describe('attribute fallthrough', () => { } } - class GrandChild extends Component<{ foo: number }> { + class GrandChild extends Component<{ [key: string]: any; foo: number }> { static props = { foo: Number } diff --git a/packages/core/src/component.ts b/packages/core/src/component.ts index 66fc4bb0..394bf984 100644 --- a/packages/core/src/component.ts +++ b/packages/core/src/component.ts @@ -13,9 +13,9 @@ import { nextTick } from '@vue/scheduler' import { ErrorTypes } from './errorHandling' import { initializeComponentInstance } from './componentUtils' -export interface ComponentClass extends ComponentClassOptions { +export interface ComponentClass

extends ComponentClassOptions { options?: ComponentOptions - new

(): MergedComponent + new

(props?: P): MergedComponent } export type MergedComponent = D & P & ComponentInstance diff --git a/packages/core/src/componentOptions.ts b/packages/core/src/componentOptions.ts index 690daab1..08cf83c8 100644 --- a/packages/core/src/componentOptions.ts +++ b/packages/core/src/componentOptions.ts @@ -1,18 +1,18 @@ -import { ComponentInstance } from './component' +import { ComponentInstance, MergedComponent } from './component' import { Slots } from './vdom' export type Data = Record -export interface ComponentClassOptions { - props?: ComponentPropsOptions +export interface ComponentClassOptions

{ + props?: ComponentPropsOptions

computed?: ComponentComputedOptions watch?: ComponentWatchOptions displayName?: string } -export interface ComponentOptions - extends ComponentClassOptions { - data?(): object +export interface ComponentOptions

> + extends ComponentClassOptions { + data?(): D render?: (this: This, props: Readonly, slots: Slots, attrs: Data) => any // TODO other options readonly [key: string]: any diff --git a/packages/core/src/h.ts b/packages/core/src/h.ts index 29c7d235..c461017d 100644 --- a/packages/core/src/h.ts +++ b/packages/core/src/h.ts @@ -2,7 +2,6 @@ import { ChildrenFlags } from './flags' import { ComponentClass, FunctionalComponent, - Component, ComponentInstance } from './component' import { ComponentOptions } from './componentOptions' @@ -14,7 +13,8 @@ import { createFragment, createPortal, VNodeData, - BuiltInProps + BuiltInProps, + Key } from './vdom' import { isObservable } from '@vue/observer' import { warn } from './warning' @@ -22,6 +22,14 @@ import { warn } from './warning' export const Fragment = Symbol() export const Portal = Symbol() +type RawChildType = VNode | string | number | boolean | null | undefined + +export type RawSlots = { + [name: string]: () => RawChildrenType +} + +export type RawChildrenType = RawChildType | RawChildType[] + export type ElementType = | string | FunctionalComponent @@ -30,10 +38,6 @@ export type ElementType = | typeof Fragment | typeof Portal -type RawChildType = VNode | string | number | boolean | null | undefined - -export type RawChildrenType = RawChildType | RawChildType[] - interface VNodeFactories { c: typeof createComponentVNode e: typeof createElementVNode @@ -42,20 +46,60 @@ interface VNodeFactories { p: typeof createPortal } -interface createElement { +// This is used to differentiate the data object from +// vnodes and arrays +type Differ = { _isVNode?: never; [Symbol.iterator]?: never } + +type OptionsComponent

= + | (ComponentOptions

& { template: string }) + | (ComponentOptions

& { render: Function }) + +interface createElement extends VNodeFactories { // element - (tag: string, data?: VNodeData, children?: any): VNode + ( + tag: string, + // TODO support native element properties + data?: VNodeData & Differ | null, + children?: RawChildrenType | RawSlots + ): VNode + (tag: string, children?: RawChildrenType): VNode + // fragment + ( + tag: typeof Fragment, + data?: ({ key?: Key } & Differ) | null, + children?: RawChildrenType | RawSlots + ): VNode + (tag: typeof Fragment, children?: RawChildrenType): VNode + // portal + ( + tag: typeof Portal, + data?: ({ target: any } & BuiltInProps & Differ) | null, + children?: RawChildrenType | RawSlots + ): VNode + (tag: typeof Portal, children?: RawChildrenType): VNode + // object +

( + tag: OptionsComponent

, + data?: (P & BuiltInProps & Differ) | null, + children?: RawChildrenType | RawSlots + ): VNode +

(tag: OptionsComponent

, children?: RawChildrenType): VNode // functional

( tag: FunctionalComponent

, - data?: P & BuiltInProps | null, - children?: any + data?: (P & BuiltInProps & Differ) | null, + children?: RawChildrenType | RawSlots ): VNode - // stateful +

(tag: FunctionalComponent

, children?: RawChildrenType): VNode + // class >( tag: new () => T & { $props: P }, - data?: P & BuiltInProps | null, - children?: any + data?: (P & BuiltInProps & Differ) | null, + children?: RawChildrenType | RawSlots + ): VNode + >( + tag: new () => T & { $props: P }, + children?: RawChildrenType ): VNode } @@ -138,7 +182,7 @@ export const h = ((tag: ElementType, data?: any, children?: any): VNode => { ref ) } -}) as createElement & VNodeFactories +}) as createElement h.c = createComponentVNode h.e = createElementVNode diff --git a/packages/core/src/optional/mixin.ts b/packages/core/src/optional/mixin.ts index 902d9f2e..605075c0 100644 --- a/packages/core/src/optional/mixin.ts +++ b/packages/core/src/optional/mixin.ts @@ -1,16 +1,6 @@ -import { ComponentInstance, ComponentType } from '../component' +import { ComponentInstance } from '../component' import { ComponentOptions } from '../componentOptions' -import { RawVNodeChildren, VNodeData } from '../vdom' export interface Mixin extends ComponentOptions {} export function applyMixins(Component: ComponentInstance, mixins: Mixin[]) {} - -export function h(tag: ComponentType | string, data: RawVNodeChildren): object -export function h( - tag: ComponentType | string, - data: VNodeData, - children: RawVNodeChildren -): object { - return {} -} diff --git a/packages/core/src/vdom.ts b/packages/core/src/vdom.ts index f54fe3b1..811f7b26 100644 --- a/packages/core/src/vdom.ts +++ b/packages/core/src/vdom.ts @@ -6,7 +6,7 @@ import { import { VNodeFlags, ChildrenFlags } from './flags' import { createComponentClassFromOptions } from './componentUtils' import { normalizeClass, normalizeStyle, handlersRE, EMPTY_OBJ } from './utils' -import { ElementType, RawChildrenType } from './h' +import { RawChildrenType, RawSlots } from './h' // Vue core is platform agnostic, so we are not using Element for "DOM" nodes. export interface RenderNode { @@ -47,7 +47,9 @@ export interface BuiltInProps { slots?: RawSlots | null } -export type VNodeData = Record & BuiltInProps +export type VNodeData = { + [key: string]: any +} & BuiltInProps export type VNodeChildren = | VNode[] // ELEMENT | PORTAL @@ -65,10 +67,6 @@ export type Slots = Readonly<{ [name: string]: Slot }> -export type RawSlots = { - [name: string]: () => RawChildrenType -} - export function createVNode( flags: VNodeFlags, tag: string | FunctionalComponent | ComponentClass | RenderNode | null, diff --git a/packages/renderer-dom/src/patchData.ts b/packages/renderer-dom/src/patchData.ts index d13e8057..f6244cd4 100644 --- a/packages/renderer-dom/src/patchData.ts +++ b/packages/renderer-dom/src/patchData.ts @@ -7,8 +7,9 @@ import { patchEvent } from './modules/events' export const onRE = /^on/ -// value, checked, selected & muted are always patched as properties -const domPropsRE = /^domProps|^(?:value|checked|selected|muted)$/ +// value, checked, selected & muted +// plus anything with upperCase letter in it are always patched as properties +const domPropsRE = /\W|^(?:value|checked|selected|muted)$/ export function patchData( el: Element,