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 { ExtractPropTypes, ComponentPropsOptions } from './componentProps'
|
||||||
import { EmitsOptions } from './componentEmits'
|
import { EmitsOptions } from './componentEmits'
|
||||||
import { isFunction } from '@vue/shared'
|
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
|
// defineComponent is a utility that is primarily used for type inference
|
||||||
// when declaring components. Type inference is provided in the component
|
// when declaring components. Type inference is provided in the component
|
||||||
@ -40,7 +44,7 @@ export function defineComponent<Props, RawBindings = object>(
|
|||||||
{},
|
{},
|
||||||
{},
|
{},
|
||||||
// public props
|
// public props
|
||||||
VNodeProps & Props
|
VNodeProps & Props & AllowedComponentProps & ComponentCustomProps
|
||||||
>
|
>
|
||||||
> &
|
> &
|
||||||
FunctionalComponent<Props>
|
FunctionalComponent<Props>
|
||||||
@ -80,7 +84,7 @@ export function defineComponent<
|
|||||||
Mixin,
|
Mixin,
|
||||||
Extends,
|
Extends,
|
||||||
E,
|
E,
|
||||||
VNodeProps & Props
|
VNodeProps & Props & AllowedComponentProps & ComponentCustomProps
|
||||||
>
|
>
|
||||||
> &
|
> &
|
||||||
ComponentOptionsWithoutProps<
|
ComponentOptionsWithoutProps<
|
||||||
@ -131,7 +135,8 @@ export function defineComponent<
|
|||||||
M,
|
M,
|
||||||
Mixin,
|
Mixin,
|
||||||
Extends,
|
Extends,
|
||||||
E
|
E,
|
||||||
|
AllowedComponentProps & ComponentCustomProps
|
||||||
>
|
>
|
||||||
> &
|
> &
|
||||||
ComponentOptionsWithArrayProps<
|
ComponentOptionsWithArrayProps<
|
||||||
@ -182,7 +187,7 @@ export function defineComponent<
|
|||||||
Mixin,
|
Mixin,
|
||||||
Extends,
|
Extends,
|
||||||
E,
|
E,
|
||||||
VNodeProps
|
VNodeProps & AllowedComponentProps & ComponentCustomProps
|
||||||
>
|
>
|
||||||
> &
|
> &
|
||||||
ComponentOptionsWithObjectProps<
|
ComponentOptionsWithObjectProps<
|
||||||
|
@ -197,7 +197,7 @@ function patchSuspense(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface SuspenseBoundary {
|
export interface SuspenseBoundary {
|
||||||
vnode: VNode
|
vnode: VNode<RendererNode, RendererElement, SuspenseProps>
|
||||||
parent: SuspenseBoundary | null
|
parent: SuspenseBoundary | null
|
||||||
parentComponent: ComponentInternalInstance | null
|
parentComponent: ComponentInternalInstance | null
|
||||||
isSVG: boolean
|
isSVG: boolean
|
||||||
|
@ -11,6 +11,8 @@ import { VNode, VNodeArrayChildren, VNodeProps } from '../vnode'
|
|||||||
import { isString, ShapeFlags } from '@vue/shared'
|
import { isString, ShapeFlags } from '@vue/shared'
|
||||||
import { warn } from '../warning'
|
import { warn } from '../warning'
|
||||||
|
|
||||||
|
export type TeleportVNode = VNode<RendererNode, RendererElement, TeleportProps>
|
||||||
|
|
||||||
export interface TeleportProps {
|
export interface TeleportProps {
|
||||||
to: string | RendererElement
|
to: string | RendererElement
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
@ -55,8 +57,8 @@ const resolveTarget = <T = RendererElement>(
|
|||||||
export const TeleportImpl = {
|
export const TeleportImpl = {
|
||||||
__isTeleport: true,
|
__isTeleport: true,
|
||||||
process(
|
process(
|
||||||
n1: VNode | null,
|
n1: TeleportVNode | null,
|
||||||
n2: VNode,
|
n2: TeleportVNode,
|
||||||
container: RendererElement,
|
container: RendererElement,
|
||||||
anchor: RendererNode | null,
|
anchor: RendererNode | null,
|
||||||
parentComponent: ComponentInternalInstance | null,
|
parentComponent: ComponentInternalInstance | null,
|
||||||
@ -85,10 +87,7 @@ export const TeleportImpl = {
|
|||||||
insert(placeholder, container, anchor)
|
insert(placeholder, container, anchor)
|
||||||
insert(mainAnchor, container, anchor)
|
insert(mainAnchor, container, anchor)
|
||||||
|
|
||||||
const target = (n2.target = resolveTarget(
|
const target = (n2.target = resolveTarget(n2.props, querySelector))
|
||||||
n2.props as TeleportProps,
|
|
||||||
querySelector
|
|
||||||
))
|
|
||||||
const targetAnchor = (n2.targetAnchor = createText(''))
|
const targetAnchor = (n2.targetAnchor = createText(''))
|
||||||
if (target) {
|
if (target) {
|
||||||
insert(targetAnchor, target)
|
insert(targetAnchor, target)
|
||||||
@ -165,7 +164,7 @@ export const TeleportImpl = {
|
|||||||
// target changed
|
// target changed
|
||||||
if ((n2.props && n2.props.to) !== (n1.props && n1.props.to)) {
|
if ((n2.props && n2.props.to) !== (n1.props && n1.props.to)) {
|
||||||
const nextTarget = (n2.target = resolveTarget(
|
const nextTarget = (n2.target = resolveTarget(
|
||||||
n2.props as TeleportProps,
|
n2.props,
|
||||||
querySelector
|
querySelector
|
||||||
))
|
))
|
||||||
if (nextTarget) {
|
if (nextTarget) {
|
||||||
@ -267,7 +266,7 @@ interface TeleportTargetElement extends Element {
|
|||||||
|
|
||||||
function hydrateTeleport(
|
function hydrateTeleport(
|
||||||
node: Node,
|
node: Node,
|
||||||
vnode: VNode,
|
vnode: TeleportVNode,
|
||||||
parentComponent: ComponentInternalInstance | null,
|
parentComponent: ComponentInternalInstance | null,
|
||||||
parentSuspense: SuspenseBoundary | null,
|
parentSuspense: SuspenseBoundary | null,
|
||||||
optimized: boolean,
|
optimized: boolean,
|
||||||
@ -284,7 +283,7 @@ function hydrateTeleport(
|
|||||||
) => Node | null
|
) => Node | null
|
||||||
): Node | null {
|
): Node | null {
|
||||||
const target = (vnode.target = resolveTarget<Element>(
|
const target = (vnode.target = resolveTarget<Element>(
|
||||||
vnode.props as TeleportProps,
|
vnode.props,
|
||||||
querySelector
|
querySelector
|
||||||
))
|
))
|
||||||
if (target) {
|
if (target) {
|
||||||
|
@ -50,7 +50,7 @@ type RawProps = VNodeProps & {
|
|||||||
__v_isVNode?: never
|
__v_isVNode?: never
|
||||||
// used to differ from Array children
|
// used to differ from Array children
|
||||||
[Symbol.iterator]?: never
|
[Symbol.iterator]?: never
|
||||||
}
|
} & { [key: string]: any }
|
||||||
|
|
||||||
type RawChildren =
|
type RawChildren =
|
||||||
| string
|
| string
|
||||||
|
@ -18,7 +18,7 @@ import {
|
|||||||
SuspenseBoundary,
|
SuspenseBoundary,
|
||||||
queueEffectWithSuspense
|
queueEffectWithSuspense
|
||||||
} from './components/Suspense'
|
} from './components/Suspense'
|
||||||
import { TeleportImpl } from './components/Teleport'
|
import { TeleportImpl, TeleportVNode } from './components/Teleport'
|
||||||
|
|
||||||
export type RootHydrateFunction = (
|
export type RootHydrateFunction = (
|
||||||
vnode: VNode<Node, Element>,
|
vnode: VNode<Node, Element>,
|
||||||
@ -202,7 +202,7 @@ export function createHydrationFunctions(
|
|||||||
} else {
|
} else {
|
||||||
nextNode = (vnode.type as typeof TeleportImpl).hydrate(
|
nextNode = (vnode.type as typeof TeleportImpl).hydrate(
|
||||||
node,
|
node,
|
||||||
vnode,
|
vnode as TeleportVNode,
|
||||||
parentComponent,
|
parentComponent,
|
||||||
parentSuspense,
|
parentSuspense,
|
||||||
optimized,
|
optimized,
|
||||||
|
@ -54,7 +54,7 @@ export { h } from './h'
|
|||||||
// Advanced render function utilities
|
// Advanced render function utilities
|
||||||
export { createVNode, cloneVNode, mergeProps, isVNode } from './vnode'
|
export { createVNode, cloneVNode, mergeProps, isVNode } from './vnode'
|
||||||
// VNode types
|
// VNode types
|
||||||
export { Fragment, Text, Comment, Static } from './vnode'
|
export { Fragment, Text, Comment, Static, ComponentCustomProps } from './vnode'
|
||||||
// Built-in components
|
// Built-in components
|
||||||
export { Teleport, TeleportProps } from './components/Teleport'
|
export { Teleport, TeleportProps } from './components/Teleport'
|
||||||
export { Suspense, SuspenseProps } from './components/Suspense'
|
export { Suspense, SuspenseProps } from './components/Suspense'
|
||||||
|
@ -53,7 +53,7 @@ import {
|
|||||||
queueEffectWithSuspense,
|
queueEffectWithSuspense,
|
||||||
SuspenseImpl
|
SuspenseImpl
|
||||||
} from './components/Suspense'
|
} from './components/Suspense'
|
||||||
import { TeleportImpl } from './components/Teleport'
|
import { TeleportImpl, TeleportVNode } from './components/Teleport'
|
||||||
import { isKeepAlive, KeepAliveContext } from './components/KeepAlive'
|
import { isKeepAlive, KeepAliveContext } from './components/KeepAlive'
|
||||||
import { registerHMR, unregisterHMR, isHmrUpdating } from './hmr'
|
import { registerHMR, unregisterHMR, isHmrUpdating } from './hmr'
|
||||||
import {
|
import {
|
||||||
@ -477,8 +477,8 @@ function baseCreateRenderer(
|
|||||||
)
|
)
|
||||||
} else if (shapeFlag & ShapeFlags.TELEPORT) {
|
} else if (shapeFlag & ShapeFlags.TELEPORT) {
|
||||||
;(type as typeof TeleportImpl).process(
|
;(type as typeof TeleportImpl).process(
|
||||||
n1,
|
n1 as TeleportVNode,
|
||||||
n2,
|
n2 as TeleportVNode,
|
||||||
container,
|
container,
|
||||||
anchor,
|
anchor,
|
||||||
parentComponent,
|
parentComponent,
|
||||||
|
@ -71,8 +71,14 @@ export type VNodeHook =
|
|||||||
| VNodeMountHook[]
|
| VNodeMountHook[]
|
||||||
| VNodeUpdateHook[]
|
| VNodeUpdateHook[]
|
||||||
|
|
||||||
export interface VNodeProps {
|
export interface ComponentCustomProps {}
|
||||||
[key: string]: any
|
export interface AllowedComponentProps {
|
||||||
|
class?: unknown
|
||||||
|
style?: unknown
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://github.com/microsoft/TypeScript/issues/33099
|
||||||
|
export type VNodeProps = {
|
||||||
key?: string | number
|
key?: string | number
|
||||||
ref?: VNodeRef
|
ref?: VNodeRef
|
||||||
|
|
||||||
@ -104,7 +110,11 @@ export type VNodeNormalizedChildren =
|
|||||||
| RawSlots
|
| RawSlots
|
||||||
| null
|
| null
|
||||||
|
|
||||||
export interface VNode<HostNode = RendererNode, HostElement = RendererElement> {
|
export interface VNode<
|
||||||
|
HostNode = RendererNode,
|
||||||
|
HostElement = RendererElement,
|
||||||
|
ExtraProps = { [key: string]: any }
|
||||||
|
> {
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
@ -114,7 +124,7 @@ export interface VNode<HostNode = RendererNode, HostElement = RendererElement> {
|
|||||||
*/
|
*/
|
||||||
__v_skip: true
|
__v_skip: true
|
||||||
type: VNodeTypes
|
type: VNodeTypes
|
||||||
props: VNodeProps | null
|
props: (VNodeProps & ExtraProps) | null
|
||||||
key: string | number | null
|
key: string | number | null
|
||||||
ref: VNodeNormalizedRef | null
|
ref: VNodeNormalizedRef | null
|
||||||
scopeId: string | null // SFC only
|
scopeId: string | null // SFC only
|
||||||
@ -597,7 +607,7 @@ export function mergeProps(...args: (Data & VNodeProps)[]) {
|
|||||||
const incoming = toMerge[key]
|
const incoming = toMerge[key]
|
||||||
if (existing !== incoming) {
|
if (existing !== incoming) {
|
||||||
ret[key] = existing
|
ret[key] = existing
|
||||||
? [].concat(existing as any, toMerge[key])
|
? [].concat(existing as any, toMerge[key] as any)
|
||||||
: incoming
|
: incoming
|
||||||
}
|
}
|
||||||
} else {
|
} 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' })}
|
eee={() => ({ a: 'eee' })}
|
||||||
fff={(a, b) => ({ a: a > +b })}
|
fff={(a, b) => ({ a: a > +b })}
|
||||||
hhh={false}
|
hhh={false}
|
||||||
// should allow extraneous as attrs
|
// should allow class/style as attrs
|
||||||
class="bar"
|
class="bar"
|
||||||
|
style={{ color: 'red' }}
|
||||||
// should allow key
|
// should allow key
|
||||||
key={'foo'}
|
key={'foo'}
|
||||||
// should allow ref
|
// should allow ref
|
||||||
|
Loading…
Reference in New Issue
Block a user