]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
wip: refactor attrs inheritance
authorEvan You <yyx990803@gmail.com>
Mon, 24 Sep 2018 22:51:58 +0000 (18:51 -0400)
committerEvan You <yyx990803@gmail.com>
Mon, 24 Sep 2018 22:51:58 +0000 (18:51 -0400)
packages/core/src/component.ts
packages/core/src/componentOptions.ts
packages/core/src/componentProps.ts
packages/core/src/componentUtils.ts
packages/core/src/createRenderer.ts
packages/core/src/vdom.ts
packages/renderer-dom/src/modules/style.ts

index 98fe6c7ded38df66ef751a4180e622bfe7236ec7..376d28fc69d46fecc964bc42e53c628641dc7aa4 100644 (file)
@@ -30,6 +30,7 @@ export interface MountedComponent<D = Data, P = Data> extends Component {
   $vnode: VNode
   $data: D
   $props: P
+  $attrs: Data
   $computed: Data
   $slots: Slots
   $root: MountedComponent
@@ -70,6 +71,7 @@ export class Component {
   public $parentVNode: VNode | null = null
   public $data: Data | null = null
   public $props: Data | null = null
+  public $attrs: Data | null = null
   public $computed: Data | null = null
   public $slots: Slots | null = null
   public $root: MountedComponent | null = null
index c8ce0caa16ada283c8be1be2f67984606be7d1ba..82c5f0ca3a67a080123ffa874906a5b104a936a3 100644 (file)
@@ -4,7 +4,7 @@ import { MountedComponent } from './component'
 export type Data = Record<string, any>
 
 export interface RenderFunction<P = Data> {
-  (props: P, slots: Slots): any
+  (props: P, slots: Slots, attrs: Data): any
 }
 
 export interface ComponentOptions<D = Data, P = Data> {
index dd2de0ed33746832337b34e19be4ba7c46cab5e1..bd508b93d439049ba652e895f0d20ddbcce88eff 100644 (file)
@@ -1,5 +1,10 @@
 import { EMPTY_OBJ } from './utils'
-import { Component, ComponentClass, MountedComponent } from './component'
+import {
+  Component,
+  ComponentClass,
+  MountedComponent,
+  FunctionalComponent
+} from './component'
 import { immutable, unwrap, lock, unlock } from '@vue/observer'
 import {
   Data,
@@ -9,19 +14,30 @@ import {
   PropOptions
 } from './componentOptions'
 
-export function initializeProps(instance: Component, props: Data | null) {
+export function initializeProps(instance: Component, data: Data | null) {
+  const { props, attrs } = resolveProps(
+    data,
+    instance.$options.props,
+    instance.constructor as ComponentClass
+  )
   instance.$props = immutable(props || {})
+  instance.$attrs = immutable(attrs || {})
 }
 
-export function updateProps(instance: MountedComponent, nextProps: Data) {
-  // instance.$props is an observable that should not be replaced.
-  // instead, we mutate it to match latest props, which will trigger updates
-  // if any value has changed.
-  if (nextProps != null) {
-    const props = instance.$props
-    const rawProps = unwrap(props)
+export function updateProps(instance: MountedComponent, nextData: Data) {
+  // instance.$props and instance.$attrs are observables that should not be
+  // replaced. Instead, we mutate them to match latest props, which will trigger
+  // updates if any value that's been used in child component has changed.
+  if (nextData != null) {
+    const { props: nextProps, attrs: nextAttrs } = resolveProps(
+      nextData,
+      instance.$options.props,
+      instance.constructor as ComponentClass
+    )
     // unlock to temporarily allow mutatiing props
     unlock()
+    const props = instance.$props
+    const rawProps = unwrap(props)
     for (const key in rawProps) {
       if (!nextProps.hasOwnProperty(key)) {
         delete props[key]
@@ -30,24 +46,46 @@ export function updateProps(instance: MountedComponent, nextProps: Data) {
     for (const key in nextProps) {
       props[key] = nextProps[key]
     }
+    if (nextAttrs) {
+      const attrs = instance.$attrs
+      const rawAttrs = unwrap(attrs)
+      for (const key in rawAttrs) {
+        if (!nextAttrs.hasOwnProperty(key)) {
+          delete attrs[key]
+        }
+      }
+      for (const key in nextAttrs) {
+        attrs[key] = nextAttrs[key]
+      }
+    }
     lock()
   }
 }
 
-// This is called for every component vnode created. This also means the data
-// on every component vnode is guarunteed to be a fresh object.
-export function normalizeComponentProps(
+const EMPTY_PROPS = { props: EMPTY_OBJ }
+
+// resolve raw VNode data.
+// - filter out reserved keys (key, ref, slots)
+// - extract class, style and nativeOn* into $attrs (to be merged onto child
+//   component root)
+// - for the rest:
+//   - if has declared props: put declared ones in `props`, the rest in `attrs`
+//   - else: everything goes in `props`.
+export function resolveProps(
   raw: any,
-  rawOptions: ComponentPropsOptions,
-  Component: ComponentClass
-): Data {
+  rawOptions: ComponentPropsOptions | void,
+  Component: ComponentClass | FunctionalComponent
+): { props: Data; attrs?: Data } {
   const hasDeclaredProps = rawOptions !== void 0
   const options = (hasDeclaredProps &&
-    normalizePropsOptions(rawOptions)) as NormalizedPropsOptions
+    normalizePropsOptions(
+      rawOptions as ComponentPropsOptions
+    )) as NormalizedPropsOptions
   if (!raw && !hasDeclaredProps) {
-    return EMPTY_OBJ
+    return EMPTY_PROPS
   }
-  const res: Data = {}
+  const props: any = {}
+  let attrs: any = void 0
   if (raw) {
     for (const key in raw) {
       // key, ref, slots are reserved
@@ -66,35 +104,36 @@ export function normalizeComponentProps(
         (hasDeclaredProps && !options.hasOwnProperty(key))
       ) {
         const newKey = isNativeOn ? 'on' + key.slice(8) : key
-        ;(res.attrs || (res.attrs = {}))[newKey] = raw[key]
+        ;(attrs || (attrs = {}))[newKey] = raw[key]
       } else {
         if (__DEV__ && hasDeclaredProps && options.hasOwnProperty(key)) {
           validateProp(key, raw[key], options[key], Component)
         }
-        res[key] = raw[key]
+        props[key] = raw[key]
       }
     }
   }
   // set default values
   if (hasDeclaredProps) {
     for (const key in options) {
-      if (res[key] === void 0) {
+      if (props[key] === void 0) {
         const opt = options[key]
         if (opt != null && opt.hasOwnProperty('default')) {
           const defaultValue = opt.default
-          res[key] =
+          props[key] =
             typeof defaultValue === 'function' ? defaultValue() : defaultValue
         }
       }
     }
   }
-  return res
+  return { props, attrs }
 }
 
-const normalizeCache: WeakMap<
+const normalizeCache = new WeakMap<
   ComponentPropsOptions,
   NormalizedPropsOptions
-> = new WeakMap()
+>()
+
 function normalizePropsOptions(
   raw: ComponentPropsOptions
 ): NormalizedPropsOptions {
@@ -116,7 +155,7 @@ function validateProp(
   key: string,
   value: any,
   validator: PropValidator<any>,
-  Component: ComponentClass
+  Component: ComponentClass | FunctionalComponent
 ) {
   // TODO
 }
index 4b32e9030d8d455bee7a957b8df0a8c7be413fbd..4ad27fd7403ff47bb270fec0e1b55c215b002bb1 100644 (file)
@@ -74,6 +74,7 @@ export function renderInstanceRoot(instance: MountedComponent) {
   return normalizeComponentRoot(
     vnode,
     instance.$parentVNode,
+    instance.$attrs,
     instance.$options.inheritAttrs
   )
 }
@@ -93,6 +94,7 @@ export function teardownComponentInstance(instance: MountedComponent) {
 export function normalizeComponentRoot(
   vnode: any,
   componentVNode: VNode | null,
+  attrs: Data | void,
   inheritAttrs: boolean | void
 ): VNode {
   if (vnode == null) {
@@ -108,9 +110,10 @@ export function normalizeComponentRoot(
       componentVNode &&
       (flags & VNodeFlags.COMPONENT || flags & VNodeFlags.ELEMENT)
     ) {
-      const parentData = componentVNode.data
-      if (inheritAttrs !== false && parentData && parentData.attrs) {
-        vnode = cloneVNode(vnode, parentData.attrs)
+      if (inheritAttrs !== false && attrs !== void 0) {
+        // TODO should merge
+        console.log(attrs)
+        vnode = cloneVNode(vnode, attrs)
       }
       if (vnode.el) {
         vnode = cloneVNode(vnode)
index 3d9af821394945eae60cff73b7b7c581b4281bc6..1eb230e7eb0bbe8c2ab4e8c97c686c5a53d6bfaa 100644 (file)
@@ -18,7 +18,7 @@ import {
   FunctionalComponent,
   ComponentClass
 } from './component'
-import { updateProps } from './componentProps'
+import { updateProps, resolveProps } from './componentProps'
 import {
   renderInstanceRoot,
   createComponentInstance,
@@ -272,12 +272,15 @@ export function createRenderer(options: RendererOptions) {
       )
     } else {
       // functional component
+      const render = tag as FunctionalComponent
+      const { props, attrs } = resolveProps(data, render.props, render)
       const subTree = (vnode.children = normalizeComponentRoot(
-        (tag as FunctionalComponent)(data || EMPTY_OBJ, slots || EMPTY_OBJ),
+        render(props, slots || EMPTY_OBJ, attrs || EMPTY_OBJ),
         vnode,
-        (tag as FunctionalComponent).inheritAttrs
+        attrs,
+        render.inheritAttrs
       ))
-      el = vnode.el = mount(subTree, null, parentComponent, isSVG, null)
+      el = vnode.el = mount(subTree, null, parentComponent, isSVG, endNode)
     }
     if (container != null) {
       insertOrAppend(container, el, endNode)
@@ -508,7 +511,7 @@ export function createRenderer(options: RendererOptions) {
   function patchStatefulComponent(prevVNode: VNode, nextVNode: VNode) {
     const { childFlags: prevChildFlags } = prevVNode
     const {
-      data: nextProps,
+      data: nextData,
       slots: nextSlots,
       childFlags: nextChildFlags
     } = nextVNode
@@ -519,8 +522,8 @@ export function createRenderer(options: RendererOptions) {
     instance.$parentVNode = nextVNode
 
     // Update props. This will trigger child update if necessary.
-    if (nextProps !== null) {
-      updateProps(instance, nextProps)
+    if (nextData !== null) {
+      updateProps(instance, nextData)
     }
 
     // If has different slots content, or has non-compiled slots,
@@ -546,20 +549,22 @@ export function createRenderer(options: RendererOptions) {
     isSVG: boolean
   ) {
     // functional component tree is stored on the vnode as `children`
-    const { data: prevProps, slots: prevSlots } = prevVNode
-    const { data: nextProps, slots: nextSlots } = nextVNode
+    const { data: prevData, slots: prevSlots } = prevVNode
+    const { data: nextData, slots: nextSlots } = nextVNode
     const render = nextVNode.tag as FunctionalComponent
     const prevTree = prevVNode.children as VNode
 
     let shouldUpdate = true
     if (render.pure && prevSlots == null && nextSlots == null) {
-      shouldUpdate = shouldUpdateFunctionalComponent(prevProps, nextProps)
+      shouldUpdate = shouldUpdateFunctionalComponent(prevData, nextData)
     }
 
     if (shouldUpdate) {
+      const { props, attrs } = resolveProps(nextData, render.props, render)
       const nextTree = (nextVNode.children = normalizeComponentRoot(
-        render(nextProps || EMPTY_OBJ, nextSlots || EMPTY_OBJ),
+        render(props, nextSlots || EMPTY_OBJ, attrs || EMPTY_OBJ),
         nextVNode,
+        attrs,
         render.inheritAttrs
       ))
       patch(prevTree, nextTree, container, parentComponent, isSVG)
index 3b632c21b4dadb9bbdc89ff29bd20537e81d606a..da139d29210eb21c19d20aae3e9ce72dd13e7c1e 100644 (file)
@@ -4,9 +4,7 @@ import {
   FunctionalComponent
 } from './component'
 import { VNodeFlags, ChildrenFlags } from './flags'
-import { normalizeComponentProps } from './componentProps'
 import { createComponentClassFromOptions } from './componentUtils'
-import { ComponentPropsOptions } from './componentOptions'
 
 // Vue core is platform agnostic, so we are not using Element for "DOM" nodes.
 export interface RenderNode {
@@ -119,7 +117,6 @@ export function createComponentVNode(
 ) {
   // resolve type
   let flags: VNodeFlags
-  let propsOptions: ComponentPropsOptions
 
   // flags
   const compType = typeof comp
@@ -134,14 +131,12 @@ export function createComponentVNode(
         comp._normalized = true
       }
       comp = render
-      propsOptions = comp.props
     } else {
       // object literal stateful
       flags = VNodeFlags.COMPONENT_STATEFUL
       comp =
         comp._normalized ||
         (comp._normalized = createComponentClassFromOptions(comp))
-      propsOptions = comp.options && comp.options.props
     }
   } else {
     // assumes comp is function here now
@@ -150,10 +145,8 @@ export function createComponentVNode(
     }
     if (comp.prototype && comp.prototype.render) {
       flags = VNodeFlags.COMPONENT_STATEFUL
-      propsOptions = comp.options && comp.options.props
     } else {
       flags = VNodeFlags.COMPONENT_FUNCTIONAL
-      propsOptions = comp.props
     }
   }
 
@@ -161,9 +154,6 @@ export function createComponentVNode(
     // TODO warn functional component cannot have ref
   }
 
-  // props
-  const props = normalizeComponentProps(data, propsOptions, comp)
-
   // slots
   let slots: any
   if (childFlags == null) {
@@ -188,7 +178,7 @@ export function createComponentVNode(
   return createVNode(
     flags,
     comp,
-    props,
+    data,
     null, // to be set during mount
     childFlags,
     key,
@@ -258,28 +248,7 @@ export function cloneVNode(vnode: VNode, extraData?: VNodeData): VNode {
         }
       }
       for (const key in extraData) {
-        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
-        }
+        clonedData[key] = extraData[key]
       }
     }
     return createVNode(
index f97df183ea4fc44dcb49d37537bd2cd889de9ceb..bdf876b7f36fbb107a03ed20a1c2270dfe3cd0a8 100644 (file)
@@ -22,13 +22,13 @@ export function patchStyle(el: any, prev: any, next: any, data: any) {
       if (typeof value === 'number' && !nonNumericRE.test(key)) {
         value = value + 'px'
       }
-      style.setProperty(key, value)
+      style[key] = value
     }
     if (prev && typeof prev !== 'string') {
       prev = normalizeStyle(prev)
       for (const key in prev) {
         if (!normalizedNext[key]) {
-          style.setProperty(key, '')
+          style[key] = ''
         }
       }
     }