feat(types): deny unknown attributes on component by default (#1614)

close #1519
This commit is contained in:
HcySunYang 2020-07-17 23:43:28 +08:00 committed by GitHub
parent 77659fa037
commit 5d8a64d53a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 100 additions and 58 deletions

View File

@ -15,7 +15,11 @@ import {
import { ExtractPropTypes, ComponentPropsOptions } from './componentProps'
import { EmitsOptions } from './componentEmits'
import { isFunction } from '@vue/shared'
import { VNodeProps } from './vnode'
import {
VNodeProps,
AllowedComponentProps,
ComponentCustomProps
} from './vnode'
// defineComponent is a utility that is primarily used for type inference
// when declaring components. Type inference is provided in the component
@ -40,7 +44,7 @@ export function defineComponent<Props, RawBindings = object>(
{},
{},
// public props
VNodeProps & Props
VNodeProps & Props & AllowedComponentProps & ComponentCustomProps
>
> &
FunctionalComponent<Props>
@ -80,7 +84,7 @@ export function defineComponent<
Mixin,
Extends,
E,
VNodeProps & Props
VNodeProps & Props & AllowedComponentProps & ComponentCustomProps
>
> &
ComponentOptionsWithoutProps<
@ -131,7 +135,8 @@ export function defineComponent<
M,
Mixin,
Extends,
E
E,
AllowedComponentProps & ComponentCustomProps
>
> &
ComponentOptionsWithArrayProps<
@ -182,7 +187,7 @@ export function defineComponent<
Mixin,
Extends,
E,
VNodeProps
VNodeProps & AllowedComponentProps & ComponentCustomProps
>
> &
ComponentOptionsWithObjectProps<

View File

@ -197,7 +197,7 @@ function patchSuspense(
}
export interface SuspenseBoundary {
vnode: VNode
vnode: VNode<RendererNode, RendererElement, SuspenseProps>
parent: SuspenseBoundary | null
parentComponent: ComponentInternalInstance | null
isSVG: boolean

View File

@ -11,6 +11,8 @@ import { VNode, VNodeArrayChildren, VNodeProps } from '../vnode'
import { isString, ShapeFlags } from '@vue/shared'
import { warn } from '../warning'
export type TeleportVNode = VNode<RendererNode, RendererElement, TeleportProps>
export interface TeleportProps {
to: string | RendererElement
disabled?: boolean
@ -55,8 +57,8 @@ const resolveTarget = <T = RendererElement>(
export const TeleportImpl = {
__isTeleport: true,
process(
n1: VNode | null,
n2: VNode,
n1: TeleportVNode | null,
n2: TeleportVNode,
container: RendererElement,
anchor: RendererNode | null,
parentComponent: ComponentInternalInstance | null,
@ -85,10 +87,7 @@ export const TeleportImpl = {
insert(placeholder, container, anchor)
insert(mainAnchor, container, anchor)
const target = (n2.target = resolveTarget(
n2.props as TeleportProps,
querySelector
))
const target = (n2.target = resolveTarget(n2.props, querySelector))
const targetAnchor = (n2.targetAnchor = createText(''))
if (target) {
insert(targetAnchor, target)
@ -165,7 +164,7 @@ export const TeleportImpl = {
// target changed
if ((n2.props && n2.props.to) !== (n1.props && n1.props.to)) {
const nextTarget = (n2.target = resolveTarget(
n2.props as TeleportProps,
n2.props,
querySelector
))
if (nextTarget) {
@ -267,7 +266,7 @@ interface TeleportTargetElement extends Element {
function hydrateTeleport(
node: Node,
vnode: VNode,
vnode: TeleportVNode,
parentComponent: ComponentInternalInstance | null,
parentSuspense: SuspenseBoundary | null,
optimized: boolean,
@ -284,7 +283,7 @@ function hydrateTeleport(
) => Node | null
): Node | null {
const target = (vnode.target = resolveTarget<Element>(
vnode.props as TeleportProps,
vnode.props,
querySelector
))
if (target) {

View File

@ -50,7 +50,7 @@ type RawProps = VNodeProps & {
__v_isVNode?: never
// used to differ from Array children
[Symbol.iterator]?: never
}
} & { [key: string]: any }
type RawChildren =
| string

View File

@ -18,7 +18,7 @@ import {
SuspenseBoundary,
queueEffectWithSuspense
} from './components/Suspense'
import { TeleportImpl } from './components/Teleport'
import { TeleportImpl, TeleportVNode } from './components/Teleport'
export type RootHydrateFunction = (
vnode: VNode<Node, Element>,
@ -202,7 +202,7 @@ export function createHydrationFunctions(
} else {
nextNode = (vnode.type as typeof TeleportImpl).hydrate(
node,
vnode,
vnode as TeleportVNode,
parentComponent,
parentSuspense,
optimized,

View File

@ -54,7 +54,7 @@ export { h } from './h'
// Advanced render function utilities
export { createVNode, cloneVNode, mergeProps, isVNode } from './vnode'
// VNode types
export { Fragment, Text, Comment, Static } from './vnode'
export { Fragment, Text, Comment, Static, ComponentCustomProps } from './vnode'
// Built-in components
export { Teleport, TeleportProps } from './components/Teleport'
export { Suspense, SuspenseProps } from './components/Suspense'

View File

@ -53,7 +53,7 @@ import {
queueEffectWithSuspense,
SuspenseImpl
} from './components/Suspense'
import { TeleportImpl } from './components/Teleport'
import { TeleportImpl, TeleportVNode } from './components/Teleport'
import { isKeepAlive, KeepAliveContext } from './components/KeepAlive'
import { registerHMR, unregisterHMR, isHmrUpdating } from './hmr'
import {
@ -477,8 +477,8 @@ function baseCreateRenderer(
)
} else if (shapeFlag & ShapeFlags.TELEPORT) {
;(type as typeof TeleportImpl).process(
n1,
n2,
n1 as TeleportVNode,
n2 as TeleportVNode,
container,
anchor,
parentComponent,

View File

@ -71,8 +71,14 @@ export type VNodeHook =
| VNodeMountHook[]
| VNodeUpdateHook[]
export interface VNodeProps {
[key: string]: any
export interface ComponentCustomProps {}
export interface AllowedComponentProps {
class?: unknown
style?: unknown
}
// https://github.com/microsoft/TypeScript/issues/33099
export type VNodeProps = {
key?: string | number
ref?: VNodeRef
@ -104,7 +110,11 @@ export type VNodeNormalizedChildren =
| RawSlots
| null
export interface VNode<HostNode = RendererNode, HostElement = RendererElement> {
export interface VNode<
HostNode = RendererNode,
HostElement = RendererElement,
ExtraProps = { [key: string]: any }
> {
/**
* @internal
*/
@ -114,7 +124,7 @@ export interface VNode<HostNode = RendererNode, HostElement = RendererElement> {
*/
__v_skip: true
type: VNodeTypes
props: VNodeProps | null
props: (VNodeProps & ExtraProps) | null
key: string | number | null
ref: VNodeNormalizedRef | null
scopeId: string | null // SFC only
@ -597,7 +607,7 @@ export function mergeProps(...args: (Data & VNodeProps)[]) {
const incoming = toMerge[key]
if (existing !== incoming) {
ret[key] = existing
? [].concat(existing as any, toMerge[key])
? [].concat(existing as any, toMerge[key] as any)
: incoming
}
} else {

View File

@ -1,30 +0,0 @@
import { defineComponent, expectError, expectType } from './index'
declare module '@vue/runtime-core' {
interface ComponentCustomOptions {
test?(n: number): void
}
interface ComponentCustomProperties {
state: 'stopped' | 'running'
}
}
export const Custom = defineComponent({
data: () => ({ counter: 0 }),
test(n) {
expectType<number>(n)
},
methods: {
aMethod() {
// @ts-expect-error
expectError(this.notExisting)
this.counter++
this.state = 'running'
// @ts-expect-error
expectError((this.state = 'not valid'))
}
}
})

View File

@ -0,0 +1,57 @@
import { defineComponent, expectError, expectType } from './index'
declare module '@vue/runtime-core' {
interface ComponentCustomOptions {
test?(n: number): void
}
interface ComponentCustomProperties {
state: 'stopped' | 'running'
}
interface ComponentCustomProps {
custom?: number
}
}
export const Custom = defineComponent({
props: {
bar: String,
baz: {
type: Number,
required: true
}
},
data: () => ({ counter: 0 }),
test(n) {
expectType<number>(n)
},
methods: {
aMethod() {
// @ts-expect-error
expectError(this.notExisting)
this.counter++
this.state = 'running'
// @ts-expect-error
expectError((this.state = 'not valid'))
}
}
})
expectType<JSX.Element>(<Custom baz={1} />)
expectType<JSX.Element>(<Custom custom={1} baz={1} />)
expectType<JSX.Element>(<Custom bar="bar" baz={1} />)
// @ts-expect-error
expectType<JSX.Element>(<Custom />)
// @ts-expect-error
expectError(<Custom bar="bar" />)
// @ts-expect-error
expectError(<Custom baz="baz" />)
// @ts-expect-error
expectError(<Custom baz={1} notExist={1} />)
// @ts-expect-error
expectError(<Custom baz={1} custom="custom" />)

View File

@ -171,8 +171,9 @@ describe('with object props', () => {
eee={() => ({ a: 'eee' })}
fff={(a, b) => ({ a: a > +b })}
hhh={false}
// should allow extraneous as attrs
// should allow class/style as attrs
class="bar"
style={{ color: 'red' }}
// should allow key
key={'foo'}
// should allow ref