]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
feat: attribute fallthrough
authorEvan You <yyx990803@gmail.com>
Mon, 24 Sep 2018 01:54:48 +0000 (21:54 -0400)
committerEvan You <yyx990803@gmail.com>
Mon, 24 Sep 2018 01:54:48 +0000 (21:54 -0400)
packages/core/src/componentProps.ts
packages/core/src/componentUtils.ts
packages/core/src/vdom.ts

index bfcec057b9b6386064c0cc977e3bf300846af861..069f5cdc42734852bac4b66e4ce98e2409745029 100644 (file)
@@ -1,4 +1,4 @@
-import { EMPTY_OBJ, isReservedProp } from './utils'
+import { EMPTY_OBJ } from './utils'
 import { Component, ComponentClass, MountedComponent } from './component'
 import { immutable, unwrap, lock, unlock } from '@vue/observer'
 import {
@@ -38,29 +38,42 @@ export function updateProps(instance: MountedComponent, nextProps: Data) {
 // on every component vnode is guarunteed to be a fresh object.
 export function normalizeComponentProps(
   raw: any,
-  options: ComponentPropsOptions,
+  rawOptions: ComponentPropsOptions,
   Component: ComponentClass
 ): Data {
-  if (!raw) {
+  const hasDeclaredProps = rawOptions !== void 0
+  const options = (hasDeclaredProps &&
+    normalizePropsOptions(rawOptions)) as NormalizedPropsOptions
+  if (!raw && !hasDeclaredProps) {
     return EMPTY_OBJ
   }
   const res: Data = {}
-  const normalizedOptions = options && normalizePropsOptions(options)
-  for (const key in raw) {
-    if (isReservedProp(key)) {
-      continue
-    }
-    if (__DEV__ && normalizedOptions != null) {
-      validateProp(key, raw[key], normalizedOptions[key], Component)
-    } else {
-      res[key] = raw[key]
+  if (raw) {
+    for (const key in raw) {
+      if (key === 'key' || key === 'ref' || key === 'slot') {
+        continue
+      }
+      if (hasDeclaredProps) {
+        if (options.hasOwnProperty(key)) {
+          if (__DEV__) {
+            validateProp(key, raw[key], options[key], Component)
+          }
+          res[key] = raw[key]
+        } else {
+          // when props are explicitly declared, any non-matching prop is
+          // extracted into attrs instead.
+          ;(res.attrs || (res.attrs = {}))[key] = raw[key]
+        }
+      } else {
+        res[key] = raw[key]
+      }
     }
   }
   // set default values
-  if (normalizedOptions != null) {
-    for (const key in normalizedOptions) {
+  if (hasDeclaredProps) {
+    for (const key in options) {
       if (res[key] === void 0) {
-        const opt = normalizedOptions[key]
+        const opt = options[key]
         if (opt != null && opt.hasOwnProperty('default')) {
           const defaultValue = opt.default
           res[key] =
index 093e5800811707def3c054ee317a71a565661e14..ff893b66b28d70ee0f1b6c3850e713840630f546 100644 (file)
@@ -103,30 +103,31 @@ export function normalizeComponentRoot(
       componentVNode &&
       (flags & VNodeFlags.COMPONENT || flags & VNodeFlags.ELEMENT)
     ) {
-      const parentData = componentVNode.data || EMPTY_OBJ
-      const childData = vnode.data || EMPTY_OBJ
-      let extraData: any = null
-      for (const key in parentData) {
-        // class/style bindings on parentVNode are merged down to child
-        // component root.
-        if (key === 'class') {
-          ;(extraData || (extraData = {})).class = childData.class
-            ? [].concat(childData.class, parentData.class)
-            : parentData.class
-        } else if (key === 'style') {
-          ;(extraData || (extraData = {})).style = childData.style
-            ? [].concat(childData.style, parentData.style)
-            : parentData.style
-        } else if (key.startsWith('nativeOn')) {
-          // nativeOn* handlers are merged down to child root as native listeners
-          const event = 'on' + key.slice(8)
-          ;(extraData || (extraData = {}))[event] = childData.event
-            ? [].concat(childData.event, parentData[key])
-            : parentData[key]
+      const parentData = componentVNode.data
+      if (parentData != null) {
+        let extraData: any = null
+        for (const key in parentData) {
+          // attrs/class/style bindings on parentVNode are merged down to child
+          // component root,
+          // nativeOn* handlers are merged to child root as normal on* handlers.
+          // cloneVNode contains special logic for merging these props with
+          // existing values.
+          if (key === 'attrs') {
+            extraData = extraData || {}
+            const { attrs } = parentData
+            for (const attr in attrs) {
+              extraData[attr] = attrs[attr]
+            }
+          } else if (key === 'class' || key === 'style') {
+            ;(extraData || (extraData = {}))[key] = parentData[key]
+          } else if (key.startsWith('nativeOn')) {
+            ;(extraData || (extraData = {}))['on' + key.slice(8)] =
+              parentData[key]
+          }
+        }
+        if (extraData) {
+          vnode = cloneVNode(vnode, extraData)
         }
-      }
-      if (extraData) {
-        vnode = cloneVNode(vnode, extraData)
       }
       if (vnode.el) {
         vnode = cloneVNode(vnode)
index 519ea095c5038260da1c372ee8c5a15714448bdc..3b632c21b4dadb9bbdc89ff29bd20537e81d606a 100644 (file)
@@ -258,7 +258,28 @@ export function cloneVNode(vnode: VNode, extraData?: VNodeData): VNode {
         }
       }
       for (const key in extraData) {
-        clonedData[key] = extraData[key]
+        const existing = clonedData[key]
+        const extra = extraData[key]
+        if (extra === void 0) {
+          continue
+        }
+        // special merge behavior for attrs / class / style / on.
+        let isOn
+        if (key === 'attrs') {
+          clonedData.attrs = existing
+            ? Object.assign({}, existing, extra)
+            : extra
+        } else if (
+          key === 'class' ||
+          key === 'style' ||
+          (isOn = key.startsWith('on'))
+        ) {
+          // all three props can handle array format, so we simply merge them
+          // by concating.
+          clonedData[key] = existing ? [].concat(existing, extra) : extra
+        } else {
+          clonedData[key] = extra
+        }
       }
     }
     return createVNode(