feat: applyDirectives

This commit is contained in:
Evan You 2019-08-31 16:36:36 -04:00
parent 6801885f57
commit a3b0f2bd1c
5 changed files with 140 additions and 6 deletions

View File

@ -8,7 +8,7 @@ import {
import { queueJob, queuePostFlushCb } from './scheduler'
import { EMPTY_OBJ, isObject, isArray, isFunction } from '@vue/shared'
import { recordEffect } from './apiReactivity'
import { getCurrentInstance } from './component'
import { currentInstance } from './component'
import {
ErrorTypes,
callWithErrorHandling,
@ -83,7 +83,7 @@ function doWatch(
| null,
{ lazy, deep, flush, onTrack, onTrigger }: WatchOptions = EMPTY_OBJ
): StopHandle {
const instance = getCurrentInstance()
const instance = currentInstance
let getter: Function
if (isArray(source)) {

View File

@ -355,6 +355,10 @@ function createSetupContext(instance: ComponentInstance): SetupContext {
return __DEV__ ? Object.freeze(context) : context
}
// mark the current rendering instance for asset resolution (e.g.
// resolveComponent, resolveDirective) during render
export let currentRenderingInstance: ComponentInstance | null = null
export function renderComponentRoot(instance: ComponentInstance): VNode {
const {
type: Component,
@ -367,9 +371,12 @@ export function renderComponentRoot(instance: ComponentInstance): VNode {
refs,
emit
} = instance
let result
currentRenderingInstance = instance
try {
if (vnode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT) {
return normalizeVNode(
result = normalizeVNode(
(instance.render as RenderFunction).call(
renderProxy,
props,
@ -379,7 +386,7 @@ export function renderComponentRoot(instance: ComponentInstance): VNode {
} else {
// functional
const render = Component as FunctionalComponent
return normalizeVNode(
result = normalizeVNode(
render.length > 1
? render(props, {
attrs,
@ -392,8 +399,10 @@ export function renderComponentRoot(instance: ComponentInstance): VNode {
}
} catch (err) {
handleError(err, instance, ErrorTypes.RENDER_FUNCTION)
return createVNode(Empty)
result = createVNode(Empty)
}
currentRenderingInstance = null
return result
}
export function shouldUpdateComponent(

View File

@ -0,0 +1,122 @@
/**
Runtime helper for applying directives to a vnode. Example usage:
const comp = resolveComponent('comp')
const foo = resolveDirective('foo')
const bar = resolveDirective('bar')
return applyDirectives(
h(comp),
[foo, this.x],
[bar, this.y]
)
*/
import { VNode, cloneVNode } from './vnode'
import { extend } from '@vue/shared'
import { warn } from './warning'
import {
ComponentInstance,
currentRenderingInstance,
ComponentRenderProxy
} from './component'
interface DirectiveBinding {
instance: ComponentRenderProxy | null
value?: any
oldValue?: any
arg?: string
modifiers?: DirectiveModifiers
}
type DirectiveHook = (
el: any,
binding: DirectiveBinding,
vnode: VNode,
prevVNode: VNode | void
) => void
interface Directive {
beforeMount: DirectiveHook
mounted: DirectiveHook
beforeUpdate: DirectiveHook
updated: DirectiveHook
beforeUnmount: DirectiveHook
unmounted: DirectiveHook
}
type DirectiveModifiers = Record<string, boolean>
const valueCache = new WeakMap<Directive, WeakMap<any, any>>()
function applyDirective(
props: Record<any, any>,
instance: ComponentInstance,
directive: Directive,
value?: any,
arg?: string,
modifiers?: DirectiveModifiers
) {
let valueCacheForDir = valueCache.get(directive) as WeakMap<VNode, any>
if (!valueCacheForDir) {
valueCacheForDir = new WeakMap<VNode, any>()
valueCache.set(directive, valueCacheForDir)
}
for (const key in directive) {
const hook = directive[key as keyof Directive]
const hookKey = `vnode` + key[0].toUpperCase() + key.slice(1)
const vnodeHook = (vnode: VNode, prevVNode?: VNode) => {
let oldValue
if (prevVNode !== void 0) {
oldValue = valueCacheForDir.get(prevVNode)
valueCacheForDir.delete(prevVNode)
}
valueCacheForDir.set(vnode, value)
hook(
vnode.el,
{
instance: instance.renderProxy,
value,
oldValue,
arg,
modifiers
},
vnode,
prevVNode
)
}
const existing = props[hookKey]
props[hookKey] = existing
? [].concat(existing as any, vnodeHook as any)
: vnodeHook
}
}
type DirectiveArguments = [
Directive,
any,
string | undefined,
DirectiveModifiers | undefined
][]
export function applyDirectives(
vnode: VNode,
...directives: DirectiveArguments
) {
const instance = currentRenderingInstance
if (instance !== null) {
vnode = cloneVNode(vnode)
vnode.props = vnode.props != null ? extend({}, vnode.props) : {}
for (let i = 0; i < directives.length; i++) {
applyDirective(vnode.props, instance, ...directives[i])
}
} else if (__DEV__) {
warn(`applyDirectives can only be used inside render functions.`)
}
return vnode
}
export function resolveDirective(name: string): Directive {
// TODO
return {} as any
}

View File

@ -35,6 +35,9 @@ export {
callWithAsyncErrorHandling
} from './errorHandling'
// For the compiler
export { applyDirectives, resolveDirective } from './directives'
// Types -----------------------------------------------------------------------
export { VNode } from './vnode'

View File

@ -34,7 +34,7 @@ export type NormalizedChildren = string | VNodeChildren | RawSlots | null
export interface VNode {
type: VNodeTypes
props: { [key: string]: any } | null
props: Record<any, any> | null
key: string | number | null
ref: string | Function | null
children: NormalizedChildren