feat(types): deny unknown attributes on component by default (#1614)
close #1519
This commit is contained in:
parent
77659fa037
commit
5d8a64d53a
@ -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<
|
||||
|
@ -197,7 +197,7 @@ function patchSuspense(
|
||||
}
|
||||
|
||||
export interface SuspenseBoundary {
|
||||
vnode: VNode
|
||||
vnode: VNode<RendererNode, RendererElement, SuspenseProps>
|
||||
parent: SuspenseBoundary | null
|
||||
parentComponent: ComponentInternalInstance | null
|
||||
isSVG: boolean
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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'
|
||||
|
@ -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,
|
||||
|
@ -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 {
|
||||
|
@ -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'))
|
||||
}
|
||||
}
|
||||
})
|
57
test-dts/componentTypeExtensions.test-d.tsx
Normal file
57
test-dts/componentTypeExtensions.test-d.tsx
Normal 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" />)
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user