]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(runtime-core): respect props from mixins and extends
authorEvan You <yyx990803@gmail.com>
Tue, 9 Jun 2020 15:27:40 +0000 (11:27 -0400)
committerEvan You <yyx990803@gmail.com>
Tue, 9 Jun 2020 15:27:40 +0000 (11:27 -0400)
fix #1236, close #1250

packages/runtime-core/src/component.ts
packages/runtime-core/src/componentEmits.ts
packages/runtime-core/src/componentOptions.ts
packages/runtime-core/src/componentProps.ts
packages/runtime-core/src/componentProxy.ts

index 522e64570d425d8fd06f756cef207ede65b830a0..94f972d14b1362e47e17a7490f4c4b99a02d73d9 100644 (file)
@@ -15,7 +15,11 @@ import {
   exposePropsOnRenderContext,
   exposeSetupStateOnRenderContext
 } from './componentProxy'
-import { ComponentPropsOptions, initProps } from './componentProps'
+import {
+  ComponentPropsOptions,
+  NormalizedPropsOptions,
+  initProps
+} from './componentProps'
 import { Slots, initSlots, InternalSlots } from './componentSlots'
 import { warn } from './warning'
 import { ErrorCodes, callWithErrorHandling } from './errorHandling'
@@ -50,7 +54,11 @@ export type Data = { [key: string]: unknown }
 
 // Note: can't mark this whole interface internal because some public interfaces
 // extend it.
-export interface SFCInternalOptions {
+export interface ComponentInternalOptions {
+  /**
+   * @internal
+   */
+  __props?: NormalizedPropsOptions | []
   /**
    * @internal
    */
@@ -76,7 +84,7 @@ export interface SFCInternalOptions {
 export interface FunctionalComponent<
   P = {},
   E extends EmitsOptions = Record<string, any>
-> extends SFCInternalOptions {
+> extends ComponentInternalOptions {
   // use of any here is intentional so it can be a valid JSX Element constructor
   (props: P, ctx: SetupContext<E>): any
   props?: ComponentPropsOptions<P>
index 28a6af654e57c9a234d7b17662e60ef73767def0..7bb20f73e2e94575e7eb80976de9269fa44368a5 100644 (file)
@@ -44,7 +44,7 @@ export function emit(
     const options = normalizeEmitsOptions(instance.type.emits)
     if (options) {
       if (!(event in options)) {
-        const propsOptions = normalizePropsOptions(instance.type.props)[0]
+        const propsOptions = normalizePropsOptions(instance.type)[0]
         if (!propsOptions || !(`on` + capitalize(event) in propsOptions)) {
           warn(
             `Component emitted event "${event}" but it is neither declared in ` +
index 0cab6fc0cc8e16b91ea6d6a41d85acc16eb46e68..54be5467a270d9b39ffdf280d719a1982a835927 100644 (file)
@@ -2,7 +2,7 @@ import {
   ComponentInternalInstance,
   Data,
   SetupContext,
-  SFCInternalOptions,
+  ComponentInternalOptions,
   PublicAPIComponent,
   Component
 } from './component'
@@ -87,7 +87,7 @@ export interface ComponentOptionsBase<
   EE extends string = string
 >
   extends LegacyOptions<Props, D, C, M, Mixin, Extends>,
-    SFCInternalOptions,
+    ComponentInternalOptions,
     ComponentCustomOptions {
   setup?: (
     this: void,
@@ -367,7 +367,6 @@ export function applyOptions(
     mixins,
     extends: extendsOptions,
     // state
-    props: propsOptions,
     data: dataOptions,
     computed: computedOptions,
     methods,
@@ -413,9 +412,12 @@ export function applyOptions(
 
   const checkDuplicateProperties = __DEV__ ? createDuplicateChecker() : null
 
-  if (__DEV__ && propsOptions) {
-    for (const key in normalizePropsOptions(propsOptions)[0]) {
-      checkDuplicateProperties!(OptionTypes.PROPS, key)
+  if (__DEV__) {
+    const propsOptions = normalizePropsOptions(options)[0]
+    if (propsOptions) {
+      for (const key in propsOptions) {
+        checkDuplicateProperties!(OptionTypes.PROPS, key)
+      }
     }
   }
 
index 472d4dbaf0a6054af91c857d2656d5aff5a27447..5c3c1f3669d089d66a4b31a60c3cc49993523a53 100644 (file)
@@ -17,7 +17,12 @@ import {
   def
 } from '@vue/shared'
 import { warn } from './warning'
-import { Data, ComponentInternalInstance } from './component'
+import {
+  Data,
+  ComponentInternalInstance,
+  ComponentOptions,
+  Component
+} from './component'
 import { isEmitListener } from './componentEmits'
 import { InternalObjectKey } from './vnode'
 
@@ -96,7 +101,7 @@ type NormalizedProp =
 
 // normalized value is a tuple of the actual normalized options
 // and an array of prop keys that need value casting (booleans and defaults)
-type NormalizedPropsOptions = [Record<string, NormalizedProp>, string[]]
+export type NormalizedPropsOptions = [Record<string, NormalizedProp>, string[]]
 
 export function initProps(
   instance: ComponentInternalInstance,
@@ -108,17 +113,16 @@ export function initProps(
   const attrs: Data = {}
   def(attrs, InternalObjectKey, 1)
   setFullProps(instance, rawProps, props, attrs)
-  const options = instance.type.props
   // validation
-  if (__DEV__ && options && rawProps) {
-    validateProps(props, options)
+  if (__DEV__) {
+    validateProps(props, instance.type)
   }
 
   if (isStateful) {
     // stateful
     instance.props = isSSR ? props : shallowReactive(props)
   } else {
-    if (!options) {
+    if (!instance.type.props) {
       // functional w/ optional props, props === attrs
       instance.props = attrs
     } else {
@@ -140,9 +144,8 @@ export function updateProps(
     attrs,
     vnode: { patchFlag }
   } = instance
-  const rawOptions = instance.type.props
   const rawCurrentProps = toRaw(props)
-  const [options] = normalizePropsOptions(rawOptions)
+  const [options] = normalizePropsOptions(instance.type)
 
   if ((optimized || patchFlag > 0) && !(patchFlag & PatchFlags.FULL_PROPS)) {
     if (patchFlag & PatchFlags.PROPS) {
@@ -211,8 +214,8 @@ export function updateProps(
     }
   }
 
-  if (__DEV__ && rawOptions && rawProps) {
-    validateProps(props, rawOptions)
+  if (__DEV__ && rawProps) {
+    validateProps(props, instance.type)
   }
 }
 
@@ -222,9 +225,7 @@ function setFullProps(
   props: Data,
   attrs: Data
 ) {
-  const [options, needCastKeys] = normalizePropsOptions(
-    instance.type.props
-  )
+  const [options, needCastKeys] = normalizePropsOptions(instance.type)
   const emits = instance.type.emits
 
   if (rawProps) {
@@ -292,16 +293,38 @@ function resolvePropValue(
 }
 
 export function normalizePropsOptions(
-  raw: ComponentPropsOptions | undefined
+  comp: Component
 ): NormalizedPropsOptions | [] {
-  if (!raw) {
-    return EMPTY_ARR as any
-  }
-  if ((raw as any)._n) {
-    return (raw as any)._n
+  if (comp.__props) {
+    return comp.__props
   }
+
+  const raw = comp.props
   const normalized: NormalizedPropsOptions[0] = {}
   const needCastKeys: NormalizedPropsOptions[1] = []
+
+  // apply mixin/extends props
+  let hasExtends = false
+  if (__FEATURE_OPTIONS__ && !isFunction(comp)) {
+    const extendProps = (raw: ComponentOptions) => {
+      const [props, keys] = normalizePropsOptions(raw)
+      Object.assign(normalized, props)
+      if (keys) needCastKeys.push(...keys)
+    }
+    if (comp.extends) {
+      hasExtends = true
+      extendProps(comp.extends)
+    }
+    if (comp.mixins) {
+      hasExtends = true
+      comp.mixins.forEach(extendProps)
+    }
+  }
+
+  if (!raw && !hasExtends) {
+    return (comp.__props = EMPTY_ARR)
+  }
+
   if (isArray(raw)) {
     for (let i = 0; i < raw.length; i++) {
       if (__DEV__ && !isString(raw[i])) {
@@ -312,7 +335,7 @@ export function normalizePropsOptions(
         normalized[normalizedKey] = EMPTY_OBJ
       }
     }
-  } else {
+  } else if (raw) {
     if (__DEV__ && !isObject(raw)) {
       warn(`invalid props options`, raw)
     }
@@ -337,7 +360,7 @@ export function normalizePropsOptions(
     }
   }
   const normalizedEntry: NormalizedPropsOptions = [normalized, needCastKeys]
-  def(raw, '_n', normalizedEntry)
+  comp.__props = normalizedEntry
   return normalizedEntry
 }
 
@@ -368,9 +391,12 @@ function getTypeIndex(
   return -1
 }
 
-function validateProps(props: Data, rawOptions: ComponentPropsOptions) {
+/**
+ * dev only
+ */
+function validateProps(props: Data, comp: Component) {
   const rawValues = toRaw(props)
-  const options = normalizePropsOptions(rawOptions)[0]
+  const options = normalizePropsOptions(comp)[0]
   for (const key in options) {
     let opt = options[key]
     if (opt == null) continue
@@ -378,6 +404,9 @@ function validateProps(props: Data, rawOptions: ComponentPropsOptions) {
   }
 }
 
+/**
+ * dev only
+ */
 function validatePropName(key: string) {
   if (key[0] !== '$') {
     return true
@@ -387,6 +416,9 @@ function validatePropName(key: string) {
   return false
 }
 
+/**
+ * dev only
+ */
 function validateProp(
   name: string,
   value: unknown,
@@ -434,6 +466,9 @@ type AssertionResult = {
   expectedType: string
 }
 
+/**
+ * dev only
+ */
 function assertType(value: unknown, type: PropConstructor): AssertionResult {
   let valid
   const expectedType = getType(type)
@@ -457,6 +492,9 @@ function assertType(value: unknown, type: PropConstructor): AssertionResult {
   }
 }
 
+/**
+ * dev only
+ */
 function getInvalidTypeMessage(
   name: string,
   value: unknown,
@@ -485,6 +523,9 @@ function getInvalidTypeMessage(
   return message
 }
 
+/**
+ * dev only
+ */
 function styleValue(value: unknown, type: string): string {
   if (type === 'String') {
     return `"${value}"`
@@ -495,11 +536,17 @@ function styleValue(value: unknown, type: string): string {
   }
 }
 
+/**
+ * dev only
+ */
 function isExplicable(type: string): boolean {
   const explicitTypes = ['string', 'number', 'boolean']
   return explicitTypes.some(elem => type.toLowerCase() === elem)
 }
 
+/**
+ * dev only
+ */
 function isBoolean(...args: string[]): boolean {
   return args.some(elem => elem.toLowerCase() === 'boolean')
 }
index 01c31897546fd482031baf0bcabade1923f143a6..73a2d1541fd8e0ce0b06cc6528c96e6d030dd33c 100644 (file)
@@ -238,7 +238,7 @@ export const PublicInstanceProxyHandlers: ProxyHandler<any> = {
         // only cache other properties when instance has declared (thus stable)
         // props
         type.props &&
-        hasOwn(normalizePropsOptions(type.props)[0]!, key)
+        hasOwn(normalizePropsOptions(type)[0]!, key)
       ) {
         accessCache![key] = AccessTypes.PROPS
         return props![key]
@@ -347,7 +347,7 @@ export const PublicInstanceProxyHandlers: ProxyHandler<any> = {
       accessCache![key] !== undefined ||
       (data !== EMPTY_OBJ && hasOwn(data, key)) ||
       (setupState !== EMPTY_OBJ && hasOwn(setupState, key)) ||
-      (type.props && hasOwn(normalizePropsOptions(type.props)[0]!, key)) ||
+      (type.props && hasOwn(normalizePropsOptions(type)[0]!, key)) ||
       hasOwn(ctx, key) ||
       hasOwn(publicPropertiesMap, key) ||
       hasOwn(appContext.config.globalProperties, key)
@@ -430,12 +430,10 @@ export function createRenderContext(instance: ComponentInternalInstance) {
 export function exposePropsOnRenderContext(
   instance: ComponentInternalInstance
 ) {
-  const {
-    ctx,
-    type: { props: propsOptions }
-  } = instance
+  const { ctx, type } = instance
+  const propsOptions = normalizePropsOptions(type)[0]
   if (propsOptions) {
-    Object.keys(normalizePropsOptions(propsOptions)[0]!).forEach(key => {
+    Object.keys(propsOptions).forEach(key => {
       Object.defineProperty(ctx, key, {
         enumerable: true,
         configurable: true,