]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
wip: props immutability
authorEvan You <yyx990803@gmail.com>
Wed, 29 May 2019 02:43:27 +0000 (10:43 +0800)
committerEvan You <yyx990803@gmail.com>
Wed, 29 May 2019 02:43:27 +0000 (10:43 +0800)
packages/runtime-core/src/component.ts
packages/runtime-core/src/componentProps.ts
packages/runtime-core/src/componentProxy.ts [new file with mode: 0644]
packages/runtime-core/src/createRenderer.ts

index 21d2e07d1ccac0e923183f61fba92b18b9b2a35f..b7a1ea3e22a5bcaa957cd74f49ec5150a8c2e62f 100644 (file)
@@ -1,26 +1,32 @@
 import { VNode, normalizeVNode, VNodeChild } from './vnode'
 import { ReactiveEffect } from '@vue/observer'
 import { isFunction, EMPTY_OBJ } from '@vue/shared'
-import { resolveProps, ComponentPropsOptions } from './componentProps'
+import { RenderProxyHandlers } from './componentProxy'
+import {
+  resolveProps,
+  ComponentPropsOptions,
+  initializeProps,
+  PropValidator
+} from './componentProps'
 
 interface Value<T> {
   value: T
 }
 
+export type Data = { [key: string]: any }
+
 type UnwrapBindings<T> = {
   [key in keyof T]: T[key] extends Value<infer V> ? V : T[key]
 }
 
-type Prop<T> = { (): T } | { new (...args: any[]): T & object }
-
 type ExtractPropTypes<PropOptions> = {
-  readonly [key in keyof PropOptions]: PropOptions[key] extends Prop<infer V>
+  readonly [key in keyof PropOptions]: PropOptions[key] extends PropValidator<
+    infer V
+  >
     ? V
     : PropOptions[key] extends null | undefined ? any : PropOptions[key]
 }
 
-export type Data = { [key: string]: any }
-
 export interface ComponentPublicProperties<P = Data, S = Data> {
   $state: S
   $props: P
@@ -64,21 +70,6 @@ export type Slots = Readonly<{
   [name: string]: Slot
 }>
 
-// no-op, for type inference only
-export function createComponent<
-  RawProps,
-  RawBindings,
-  Props = ExtractPropTypes<RawProps>,
-  Bindings = UnwrapBindings<RawBindings>
->(
-  options: ComponentOptions<RawProps, RawBindings, Props, Bindings>
-): {
-  // for TSX
-  new (): { $props: Props }
-} {
-  return options as any
-}
-
 type LifecycleHook = Function[] | null
 
 export interface LifecycleHooks {
@@ -97,22 +88,38 @@ export interface LifecycleHooks {
 
 export type ComponentInstance = {
   type: FunctionalComponent | ComponentOptions
-  vnode: VNode | null
+  vnode: VNode
   next: VNode | null
-  subTree: VNode | null
+  subTree: VNode
   update: ReactiveEffect
+
+  // the rest are only for stateful components
   bindings: Data | null
   proxy: Data | null
 } & LifecycleHooks &
   ComponentPublicProperties
 
-export function createComponentInstance(vnode: VNode): ComponentInstance {
-  const type = vnode.type as any
-  const instance = {
+// no-op, for type inference only
+export function createComponent<
+  RawProps,
+  RawBindings,
+  Props = ExtractPropTypes<RawProps>,
+  Bindings = UnwrapBindings<RawBindings>
+>(
+  options: ComponentOptions<RawProps, RawBindings, Props, Bindings>
+): {
+  // for TSX
+  new (): { $props: Props }
+} {
+  return options as any
+}
+
+export function createComponentInstance(type: any): ComponentInstance {
+  return {
     type,
-    vnode: null,
+    vnode: null as any,
     next: null,
-    subTree: null,
+    subTree: null as any,
     update: null as any,
     bindings: null,
     proxy: null,
@@ -136,41 +143,50 @@ export function createComponentInstance(vnode: VNode): ComponentInstance {
     $slots: EMPTY_OBJ,
     $state: EMPTY_OBJ
   }
-  if (typeof type === 'object' && type.setup) {
-    setupStatefulComponent(instance)
-  }
-  return instance
 }
 
 export let currentInstance: ComponentInstance | null = null
 
-const RenderProxyHandlers = {}
-
-export function setupStatefulComponent(instance: ComponentInstance) {
+export function setupStatefulComponent(
+  instance: ComponentInstance,
+  props: Data | null
+) {
+  const Component = instance.type as ComponentOptions
   // 1. create render proxy
   const proxy = (instance.proxy = new Proxy(instance, RenderProxyHandlers))
   // 2. resolve initial props
+  initializeProps(instance, Component.props, props)
   // 3. call setup()
-  const type = instance.type as ComponentOptions
-  if (type.setup) {
+  if (Component.setup) {
     currentInstance = instance
-    instance.bindings = type.setup.call(proxy, proxy)
+    instance.bindings = Component.setup.call(proxy, proxy)
     currentInstance = null
   }
 }
 
-export function renderComponentRoot(instance: ComponentInstance): VNode {
+export function renderComponentRoot(
+  instance: ComponentInstance,
+  useAlreadyResolvedProps?: boolean
+): VNode {
   const { type, vnode, proxy, bindings, $slots } = instance
-  if (!type) debugger
-  const { 0: props, 1: attrs } = resolveProps(
-    (vnode as VNode).props,
-    type.props
-  )
-  const renderArg = {
+  const renderArg: RenderFunctionArg = {
     state: bindings || EMPTY_OBJ,
     slots: $slots,
-    props,
-    attrs
+    props: null as any,
+    attrs: null as any
+  }
+  if (useAlreadyResolvedProps) {
+    // initial render for stateful components with setup()
+    // props are already resolved
+    renderArg.props = instance.$props
+    renderArg.attrs = instance.$attrs
+  } else {
+    const { 0: props, 1: attrs } = resolveProps(
+      (vnode as VNode).props,
+      type.props
+    )
+    instance.$props = renderArg.props = props
+    instance.$attrs = renderArg.attrs = attrs
   }
   if (isFunction(type)) {
     return normalizeVNode(type(renderArg))
index 8d39c7835285687aa8d543609164dcc10039f3d4..89a71e7baeb8a0d200e209bfe2aeb184b418bff2 100644 (file)
@@ -45,7 +45,7 @@ const isReservedKey = (key: string): boolean => key[0] === '_' || key[0] === '$'
 
 export function initializeProps(
   instance: ComponentInstance,
-  options: NormalizedPropsOptions | undefined,
+  options: ComponentPropsOptions | undefined,
   rawProps: Data | null
 ) {
   const { 0: props, 1: attrs } = resolveProps(rawProps, options)
diff --git a/packages/runtime-core/src/componentProxy.ts b/packages/runtime-core/src/componentProxy.ts
new file mode 100644 (file)
index 0000000..a447b61
--- /dev/null
@@ -0,0 +1 @@
+export const RenderProxyHandlers = {}
index b379c44639edd97be930a5f429bed58408cfd851..0b1d0cc1d6cd5c0d0289c1419890ed3172367e0e 100644 (file)
@@ -11,7 +11,8 @@ import {
   ComponentInstance,
   renderComponentRoot,
   shouldUpdateComponent,
-  createComponentInstance
+  createComponentInstance,
+  setupStatefulComponent
 } from './component'
 import { isString, isArray, EMPTY_OBJ, EMPTY_ARR } from '@vue/shared'
 import { TEXT, CLASS, STYLE, PROPS, KEYED, UNKEYED } from './patchFlags'
@@ -348,14 +349,22 @@ export function createRenderer(options: RendererOptions) {
     container: HostNode,
     anchor?: HostNode
   ) {
+    const Component = vnode.type
     const instance: ComponentInstance = (vnode.component = createComponentInstance(
-      vnode
+      Component
     ))
+    const needsSetup = typeof Component === 'object' && (Component as any).setup
+    if (needsSetup) {
+      setupStatefulComponent(instance, vnode.props)
+    }
     instance.update = effect(() => {
       if (!instance.vnode) {
         // initial mount
         instance.vnode = vnode
-        const subTree = (instance.subTree = renderComponentRoot(instance))
+        const subTree = (instance.subTree = renderComponentRoot(
+          instance,
+          needsSetup
+        ))
         if (instance.bm !== null) {
           invokeHooks(instance.bm)
         }
@@ -373,7 +382,7 @@ export function createRenderer(options: RendererOptions) {
           instance.vnode = next
           instance.next = null
         }
-        const prevTree = instance.subTree as VNode
+        const prevTree = instance.subTree
         const nextTree = (instance.subTree = renderComponentRoot(instance))
         patch(
           prevTree,
@@ -651,7 +660,7 @@ export function createRenderer(options: RendererOptions) {
 
   function move(vnode: VNode, container: HostNode, anchor: HostNode) {
     if (vnode.component != null) {
-      move(vnode.component.subTree as VNode, container, anchor)
+      move(vnode.component.subTree, container, anchor)
       return
     }
     if (vnode.type === Fragment) {
@@ -671,7 +680,7 @@ export function createRenderer(options: RendererOptions) {
     if (instance != null) {
       // TODO teardown component
       stop(instance.update)
-      unmount(instance.subTree as VNode, doRemove)
+      unmount(instance.subTree, doRemove)
       if (instance.um !== null) {
         queuePostFlushCb(instance.um)
       }
@@ -702,7 +711,7 @@ export function createRenderer(options: RendererOptions) {
   function getNextHostNode(vnode: VNode): HostNode {
     return vnode.component === null
       ? hostNextSibling(vnode.anchor || vnode.el)
-      : getNextHostNode(vnode.component.subTree as VNode)
+      : getNextHostNode(vnode.component.subTree)
   }
 
   return function render(vnode: VNode, dom: HostNode): VNode {