wip: props immutability
This commit is contained in:
parent
6dfec3a4ae
commit
eac8a4baa3
@ -1,26 +1,32 @@
|
|||||||
import { VNode, normalizeVNode, VNodeChild } from './vnode'
|
import { VNode, normalizeVNode, VNodeChild } from './vnode'
|
||||||
import { ReactiveEffect } from '@vue/observer'
|
import { ReactiveEffect } from '@vue/observer'
|
||||||
import { isFunction, EMPTY_OBJ } from '@vue/shared'
|
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> {
|
interface Value<T> {
|
||||||
value: T
|
value: T
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type Data = { [key: string]: any }
|
||||||
|
|
||||||
type UnwrapBindings<T> = {
|
type UnwrapBindings<T> = {
|
||||||
[key in keyof T]: T[key] extends Value<infer V> ? V : T[key]
|
[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> = {
|
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
|
? V
|
||||||
: PropOptions[key] extends null | undefined ? any : PropOptions[key]
|
: PropOptions[key] extends null | undefined ? any : PropOptions[key]
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Data = { [key: string]: any }
|
|
||||||
|
|
||||||
export interface ComponentPublicProperties<P = Data, S = Data> {
|
export interface ComponentPublicProperties<P = Data, S = Data> {
|
||||||
$state: S
|
$state: S
|
||||||
$props: P
|
$props: P
|
||||||
@ -64,21 +70,6 @@ export type Slots = Readonly<{
|
|||||||
[name: string]: Slot
|
[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
|
type LifecycleHook = Function[] | null
|
||||||
|
|
||||||
export interface LifecycleHooks {
|
export interface LifecycleHooks {
|
||||||
@ -97,22 +88,38 @@ export interface LifecycleHooks {
|
|||||||
|
|
||||||
export type ComponentInstance = {
|
export type ComponentInstance = {
|
||||||
type: FunctionalComponent | ComponentOptions
|
type: FunctionalComponent | ComponentOptions
|
||||||
vnode: VNode | null
|
vnode: VNode
|
||||||
next: VNode | null
|
next: VNode | null
|
||||||
subTree: VNode | null
|
subTree: VNode
|
||||||
update: ReactiveEffect
|
update: ReactiveEffect
|
||||||
|
|
||||||
|
// the rest are only for stateful components
|
||||||
bindings: Data | null
|
bindings: Data | null
|
||||||
proxy: Data | null
|
proxy: Data | null
|
||||||
} & LifecycleHooks &
|
} & LifecycleHooks &
|
||||||
ComponentPublicProperties
|
ComponentPublicProperties
|
||||||
|
|
||||||
export function createComponentInstance(vnode: VNode): ComponentInstance {
|
// no-op, for type inference only
|
||||||
const type = vnode.type as any
|
export function createComponent<
|
||||||
const instance = {
|
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,
|
type,
|
||||||
vnode: null,
|
vnode: null as any,
|
||||||
next: null,
|
next: null,
|
||||||
subTree: null,
|
subTree: null as any,
|
||||||
update: null as any,
|
update: null as any,
|
||||||
bindings: null,
|
bindings: null,
|
||||||
proxy: null,
|
proxy: null,
|
||||||
@ -136,41 +143,50 @@ export function createComponentInstance(vnode: VNode): ComponentInstance {
|
|||||||
$slots: EMPTY_OBJ,
|
$slots: EMPTY_OBJ,
|
||||||
$state: EMPTY_OBJ
|
$state: EMPTY_OBJ
|
||||||
}
|
}
|
||||||
if (typeof type === 'object' && type.setup) {
|
|
||||||
setupStatefulComponent(instance)
|
|
||||||
}
|
|
||||||
return instance
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export let currentInstance: ComponentInstance | null = null
|
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
|
// 1. create render proxy
|
||||||
const proxy = (instance.proxy = new Proxy(instance, RenderProxyHandlers))
|
const proxy = (instance.proxy = new Proxy(instance, RenderProxyHandlers))
|
||||||
// 2. resolve initial props
|
// 2. resolve initial props
|
||||||
|
initializeProps(instance, Component.props, props)
|
||||||
// 3. call setup()
|
// 3. call setup()
|
||||||
const type = instance.type as ComponentOptions
|
if (Component.setup) {
|
||||||
if (type.setup) {
|
|
||||||
currentInstance = instance
|
currentInstance = instance
|
||||||
instance.bindings = type.setup.call(proxy, proxy)
|
instance.bindings = Component.setup.call(proxy, proxy)
|
||||||
currentInstance = null
|
currentInstance = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function renderComponentRoot(instance: ComponentInstance): VNode {
|
export function renderComponentRoot(
|
||||||
|
instance: ComponentInstance,
|
||||||
|
useAlreadyResolvedProps?: boolean
|
||||||
|
): VNode {
|
||||||
const { type, vnode, proxy, bindings, $slots } = instance
|
const { type, vnode, proxy, bindings, $slots } = instance
|
||||||
if (!type) debugger
|
const renderArg: RenderFunctionArg = {
|
||||||
|
state: bindings || EMPTY_OBJ,
|
||||||
|
slots: $slots,
|
||||||
|
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(
|
const { 0: props, 1: attrs } = resolveProps(
|
||||||
(vnode as VNode).props,
|
(vnode as VNode).props,
|
||||||
type.props
|
type.props
|
||||||
)
|
)
|
||||||
const renderArg = {
|
instance.$props = renderArg.props = props
|
||||||
state: bindings || EMPTY_OBJ,
|
instance.$attrs = renderArg.attrs = attrs
|
||||||
slots: $slots,
|
|
||||||
props,
|
|
||||||
attrs
|
|
||||||
}
|
}
|
||||||
if (isFunction(type)) {
|
if (isFunction(type)) {
|
||||||
return normalizeVNode(type(renderArg))
|
return normalizeVNode(type(renderArg))
|
||||||
|
@ -45,7 +45,7 @@ const isReservedKey = (key: string): boolean => key[0] === '_' || key[0] === '$'
|
|||||||
|
|
||||||
export function initializeProps(
|
export function initializeProps(
|
||||||
instance: ComponentInstance,
|
instance: ComponentInstance,
|
||||||
options: NormalizedPropsOptions | undefined,
|
options: ComponentPropsOptions | undefined,
|
||||||
rawProps: Data | null
|
rawProps: Data | null
|
||||||
) {
|
) {
|
||||||
const { 0: props, 1: attrs } = resolveProps(rawProps, options)
|
const { 0: props, 1: attrs } = resolveProps(rawProps, options)
|
||||||
|
1
packages/runtime-core/src/componentProxy.ts
Normal file
1
packages/runtime-core/src/componentProxy.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export const RenderProxyHandlers = {}
|
@ -11,7 +11,8 @@ import {
|
|||||||
ComponentInstance,
|
ComponentInstance,
|
||||||
renderComponentRoot,
|
renderComponentRoot,
|
||||||
shouldUpdateComponent,
|
shouldUpdateComponent,
|
||||||
createComponentInstance
|
createComponentInstance,
|
||||||
|
setupStatefulComponent
|
||||||
} from './component'
|
} from './component'
|
||||||
import { isString, isArray, EMPTY_OBJ, EMPTY_ARR } from '@vue/shared'
|
import { isString, isArray, EMPTY_OBJ, EMPTY_ARR } from '@vue/shared'
|
||||||
import { TEXT, CLASS, STYLE, PROPS, KEYED, UNKEYED } from './patchFlags'
|
import { TEXT, CLASS, STYLE, PROPS, KEYED, UNKEYED } from './patchFlags'
|
||||||
@ -348,14 +349,22 @@ export function createRenderer(options: RendererOptions) {
|
|||||||
container: HostNode,
|
container: HostNode,
|
||||||
anchor?: HostNode
|
anchor?: HostNode
|
||||||
) {
|
) {
|
||||||
|
const Component = vnode.type
|
||||||
const instance: ComponentInstance = (vnode.component = createComponentInstance(
|
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(() => {
|
instance.update = effect(() => {
|
||||||
if (!instance.vnode) {
|
if (!instance.vnode) {
|
||||||
// initial mount
|
// initial mount
|
||||||
instance.vnode = vnode
|
instance.vnode = vnode
|
||||||
const subTree = (instance.subTree = renderComponentRoot(instance))
|
const subTree = (instance.subTree = renderComponentRoot(
|
||||||
|
instance,
|
||||||
|
needsSetup
|
||||||
|
))
|
||||||
if (instance.bm !== null) {
|
if (instance.bm !== null) {
|
||||||
invokeHooks(instance.bm)
|
invokeHooks(instance.bm)
|
||||||
}
|
}
|
||||||
@ -373,7 +382,7 @@ export function createRenderer(options: RendererOptions) {
|
|||||||
instance.vnode = next
|
instance.vnode = next
|
||||||
instance.next = null
|
instance.next = null
|
||||||
}
|
}
|
||||||
const prevTree = instance.subTree as VNode
|
const prevTree = instance.subTree
|
||||||
const nextTree = (instance.subTree = renderComponentRoot(instance))
|
const nextTree = (instance.subTree = renderComponentRoot(instance))
|
||||||
patch(
|
patch(
|
||||||
prevTree,
|
prevTree,
|
||||||
@ -651,7 +660,7 @@ export function createRenderer(options: RendererOptions) {
|
|||||||
|
|
||||||
function move(vnode: VNode, container: HostNode, anchor: HostNode) {
|
function move(vnode: VNode, container: HostNode, anchor: HostNode) {
|
||||||
if (vnode.component != null) {
|
if (vnode.component != null) {
|
||||||
move(vnode.component.subTree as VNode, container, anchor)
|
move(vnode.component.subTree, container, anchor)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (vnode.type === Fragment) {
|
if (vnode.type === Fragment) {
|
||||||
@ -671,7 +680,7 @@ export function createRenderer(options: RendererOptions) {
|
|||||||
if (instance != null) {
|
if (instance != null) {
|
||||||
// TODO teardown component
|
// TODO teardown component
|
||||||
stop(instance.update)
|
stop(instance.update)
|
||||||
unmount(instance.subTree as VNode, doRemove)
|
unmount(instance.subTree, doRemove)
|
||||||
if (instance.um !== null) {
|
if (instance.um !== null) {
|
||||||
queuePostFlushCb(instance.um)
|
queuePostFlushCb(instance.um)
|
||||||
}
|
}
|
||||||
@ -702,7 +711,7 @@ export function createRenderer(options: RendererOptions) {
|
|||||||
function getNextHostNode(vnode: VNode): HostNode {
|
function getNextHostNode(vnode: VNode): HostNode {
|
||||||
return vnode.component === null
|
return vnode.component === null
|
||||||
? hostNextSibling(vnode.anchor || vnode.el)
|
? hostNextSibling(vnode.anchor || vnode.el)
|
||||||
: getNextHostNode(vnode.component.subTree as VNode)
|
: getNextHostNode(vnode.component.subTree)
|
||||||
}
|
}
|
||||||
|
|
||||||
return function render(vnode: VNode, dom: HostNode): VNode {
|
return function render(vnode: VNode, dom: HostNode): VNode {
|
||||||
|
Loading…
Reference in New Issue
Block a user