vue3-yuanma/packages/runtime-core/src/component.ts

317 lines
8.4 KiB
TypeScript
Raw Normal View History

2019-05-28 17:19:47 +08:00
import { VNode, normalizeVNode, VNodeChild } from './vnode'
2019-05-30 23:16:15 +08:00
import {
ReactiveEffect,
UnwrapValue,
state,
immutableState
} from '@vue/reactivity'
import { EMPTY_OBJ, isFunction } from '@vue/shared'
2019-05-29 10:43:27 +08:00
import { RenderProxyHandlers } from './componentProxy'
2019-05-31 18:07:43 +08:00
import { ComponentPropsOptions, ExtractPropTypes } from './componentProps'
import { PROPS, DYNAMIC_SLOTS, FULL_PROPS } from './patchFlags'
2019-05-31 18:07:43 +08:00
import { Slots } from './componentSlots'
2019-06-02 22:22:44 +08:00
import { STATEFUL_COMPONENT } from './typeFlags'
2019-05-28 13:27:31 +08:00
2019-05-29 10:43:27 +08:00
export type Data = { [key: string]: any }
export type ComponentRenderProxy<P = {}, S = {}, PublicProps = P> = {
2019-05-28 17:19:47 +08:00
$state: S
$props: PublicProps
2019-05-28 18:06:00 +08:00
$attrs: Data
// TODO
$refs: Data
$slots: Data
2019-05-29 13:43:46 +08:00
$root: ComponentInstance | null
$parent: ComponentInstance | null
2019-05-30 13:35:50 +08:00
} & P &
S
2019-05-28 18:06:00 +08:00
type RenderFunction<P = Data> = (
props: P,
slots: Slots,
attrs: Data,
vnode: VNode
) => any
type RenderFunctionWithThis<Props, RawBindings> = <
Bindings extends UnwrapValue<RawBindings>
>(
this: ComponentRenderProxy<Props, Bindings>,
props: Props,
slots: Slots,
attrs: Data,
vnode: VNode
) => VNodeChild
interface ComponentOptionsWithProps<
PropsOptions = ComponentPropsOptions,
RawBindings = Data,
Props = ExtractPropTypes<PropsOptions>
> {
props: PropsOptions
setup?: (
this: ComponentRenderProxy<Props>,
props: Props
) => RawBindings | RenderFunction<Props>
render?: RenderFunctionWithThis<Props, RawBindings>
}
interface ComponentOptionsWithoutProps<Props = Data, RawBindings = Data> {
props?: undefined
setup?: (
this: ComponentRenderProxy<Props>,
props: Props
) => RawBindings | RenderFunction<Props>
render?: RenderFunctionWithThis<Props, RawBindings>
}
interface ComponentOptionsWithArrayProps<
2019-06-12 16:22:52 +08:00
PropNames extends string = string,
2019-06-01 00:47:05 +08:00
RawBindings = Data,
Props = { [key in PropNames]?: any }
2019-05-28 17:19:47 +08:00
> {
props: PropNames[]
setup?: (
this: ComponentRenderProxy<Props>,
props: Props
) => RawBindings | RenderFunction<Props>
render?: RenderFunctionWithThis<Props, RawBindings>
2019-05-28 17:19:47 +08:00
}
2019-05-28 13:27:31 +08:00
2019-06-12 16:22:52 +08:00
type ComponentOptions =
| ComponentOptionsWithProps
| ComponentOptionsWithoutProps
| ComponentOptionsWithArrayProps
export interface FunctionalComponent<P = {}> extends RenderFunction<P> {
2019-05-28 18:06:00 +08:00
props?: ComponentPropsOptions<P>
displayName?: string
}
2019-05-28 19:36:15 +08:00
type LifecycleHook = Function[] | null
export interface LifecycleHooks {
bm: LifecycleHook // beforeMount
m: LifecycleHook // mounted
bu: LifecycleHook // beforeUpdate
u: LifecycleHook // updated
bum: LifecycleHook // beforeUnmount
um: LifecycleHook // unmounted
da: LifecycleHook // deactivated
a: LifecycleHook // activated
rtg: LifecycleHook // renderTriggered
rtc: LifecycleHook // renderTracked
ec: LifecycleHook // errorCaptured
}
2019-05-29 11:36:16 +08:00
export type ComponentInstance<P = Data, S = Data> = {
2019-05-28 18:06:00 +08:00
type: FunctionalComponent | ComponentOptions
2019-06-03 09:43:28 +08:00
parent: ComponentInstance | null
root: ComponentInstance
2019-05-29 10:43:27 +08:00
vnode: VNode
2019-05-28 17:19:47 +08:00
next: VNode | null
2019-05-29 10:43:27 +08:00
subTree: VNode
2019-05-28 17:19:47 +08:00
update: ReactiveEffect
2019-05-29 23:44:59 +08:00
effects: ReactiveEffect[] | null
render: RenderFunction<P> | null
2019-05-29 10:43:27 +08:00
// the rest are only for stateful components
renderProxy: ComponentRenderProxy | null
2019-05-30 23:16:15 +08:00
propsProxy: Data | null
2019-05-29 11:36:16 +08:00
state: S
props: P
attrs: Data
slots: Slots
refs: Data
} & LifecycleHooks
2019-05-28 19:36:15 +08:00
2019-05-29 10:43:27 +08:00
// no-op, for type inference only
export function createComponent<Props>(
setup: (props: Props) => RenderFunction<Props>
): (props: Props) => any
export function createComponent<PropNames extends string, RawBindings>(
options: ComponentOptionsWithArrayProps<PropNames, RawBindings>
): {
// for Vetur and TSX support
new (): ComponentRenderProxy<
{ [key in PropNames]?: any },
UnwrapValue<RawBindings>
>
}
export function createComponent<Props, RawBindings>(
options: ComponentOptionsWithoutProps<Props, RawBindings>
): {
// for Vetur and TSX support
new (): ComponentRenderProxy<Props, UnwrapValue<RawBindings>>
}
export function createComponent<PropsOptions, RawBindings>(
options: ComponentOptionsWithProps<PropsOptions, RawBindings>
2019-05-29 10:43:27 +08:00
): {
// for Vetur and TSX support
new (): ComponentRenderProxy<
ExtractPropTypes<PropsOptions>,
UnwrapValue<RawBindings>,
ExtractPropTypes<PropsOptions, false>
>
}
export function createComponent(options: any) {
return isFunction(options) ? { setup: options } : (options as any)
2019-05-29 10:43:27 +08:00
}
2019-06-03 09:43:28 +08:00
export function createComponentInstance(
type: any,
parent: ComponentInstance | null
): ComponentInstance {
const instance = {
2019-05-28 19:36:15 +08:00
type,
2019-06-03 09:43:28 +08:00
parent,
root: null as any, // set later so it can point to itself
2019-05-29 10:43:27 +08:00
vnode: null as any,
2019-05-28 19:36:15 +08:00
next: null,
2019-05-29 10:43:27 +08:00
subTree: null as any,
2019-05-28 19:36:15 +08:00
update: null as any,
render: null,
2019-05-30 23:16:15 +08:00
renderProxy: null,
propsProxy: null,
2019-05-28 19:36:15 +08:00
bm: null,
m: null,
bu: null,
u: null,
um: null,
bum: null,
da: null,
a: null,
rtg: null,
rtc: null,
ec: null,
2019-05-29 23:44:59 +08:00
effects: null,
2019-05-28 19:36:15 +08:00
// public properties
2019-05-29 11:36:16 +08:00
state: EMPTY_OBJ,
props: EMPTY_OBJ,
attrs: EMPTY_OBJ,
slots: EMPTY_OBJ,
refs: EMPTY_OBJ
2019-05-28 19:36:15 +08:00
}
2019-06-03 09:43:28 +08:00
instance.root = parent ? parent.root : instance
return instance
2019-05-28 19:36:15 +08:00
}
export let currentInstance: ComponentInstance | null = null
2019-05-29 11:36:16 +08:00
export function setupStatefulComponent(instance: ComponentInstance) {
2019-05-29 10:43:27 +08:00
const Component = instance.type as ComponentOptions
2019-05-28 19:36:15 +08:00
// 1. create render proxy
2019-05-30 23:16:15 +08:00
const proxy = (instance.renderProxy = new Proxy(
2019-05-29 11:36:16 +08:00
instance,
RenderProxyHandlers
) as any)
// 2. call setup()
2019-05-30 23:16:15 +08:00
const { setup } = Component
if (setup) {
2019-05-28 19:36:15 +08:00
currentInstance = instance
2019-05-30 23:16:15 +08:00
// the props proxy makes the props object passed to setup() reactive
// so props change can be tracked by watchers
// only need to create it if setup() actually expects it
// it will be updated in resolveProps() on updates before render
const propsProxy = (instance.propsProxy = setup.length
? immutableState(instance.props)
2019-05-30 23:16:15 +08:00
: null)
const setupResult = setup.call(proxy, propsProxy)
if (isFunction(setupResult)) {
// setup returned a render function
instance.render = setupResult
} else {
// setup returned bindings
instance.state = state(setupResult)
if (__DEV__ && !Component.render) {
// TODO warn missing render fn
}
instance.render = Component.render as RenderFunction
}
2019-05-28 19:36:15 +08:00
currentInstance = null
}
}
2019-05-28 17:19:47 +08:00
2019-05-29 11:36:16 +08:00
export function renderComponentRoot(instance: ComponentInstance): VNode {
const { type: Component, renderProxy, props, slots, attrs, vnode } = instance
2019-06-02 16:35:19 +08:00
if (vnode.shapeFlag & STATEFUL_COMPONENT) {
2019-05-30 23:16:15 +08:00
return normalizeVNode(
(instance.render as RenderFunction).call(
renderProxy,
props,
slots,
attrs,
vnode
)
2019-05-30 23:16:15 +08:00
)
2019-06-02 16:35:19 +08:00
} else {
// functional
return normalizeVNode(
(Component as FunctionalComponent)(props, slots, attrs, vnode)
)
2019-05-28 17:19:47 +08:00
}
2019-05-28 13:27:31 +08:00
}
export function shouldUpdateComponent(
prevVNode: VNode,
2019-06-01 02:14:49 +08:00
nextVNode: VNode,
optimized?: boolean
2019-05-28 13:27:31 +08:00
): boolean {
const { props: prevProps, children: prevChildren } = prevVNode
const { props: nextProps, children: nextChildren, patchFlag } = nextVNode
2019-06-03 09:43:28 +08:00
if (patchFlag) {
if (patchFlag & DYNAMIC_SLOTS) {
2019-05-30 16:00:42 +08:00
// slot content that references values that might have changed,
// e.g. in a v-for
return true
}
if (patchFlag & FULL_PROPS) {
// presence of this flag indicates props are always non-null
return hasPropsChanged(prevProps as Data, nextProps as Data)
} else if (patchFlag & PROPS) {
2019-05-30 16:00:42 +08:00
const dynamicProps = nextVNode.dynamicProps as string[]
for (let i = 0; i < dynamicProps.length; i++) {
const key = dynamicProps[i]
if ((nextProps as any)[key] !== (prevProps as any)[key]) {
return true
}
}
}
2019-06-01 02:14:49 +08:00
} else if (!optimized) {
// this path is only taken by manually written render functions
// so presence of any children leads to a forced update
if (prevChildren != null || nextChildren != null) {
return true
}
2019-05-30 16:00:42 +08:00
if (prevProps === nextProps) {
return false
}
if (prevProps === null) {
return nextProps !== null
}
if (nextProps === null) {
return prevProps !== null
}
return hasPropsChanged(prevProps, nextProps)
}
return false
}
function hasPropsChanged(prevProps: Data, nextProps: Data): boolean {
const nextKeys = Object.keys(nextProps)
if (nextKeys.length !== Object.keys(prevProps).length) {
return true
}
for (let i = 0; i < nextKeys.length; i++) {
const key = nextKeys[i]
if (nextProps[key] !== prevProps[key]) {
2019-05-28 13:27:31 +08:00
return true
}
}
return false
}