mirror of https://github.com/vuejs/core.git
feat: basic template ref
This commit is contained in:
parent
6a26db2adc
commit
782d60475d
|
@ -21,6 +21,7 @@ import { transformVText } from './transforms/vText'
|
|||
import { transformVBind } from './transforms/vBind'
|
||||
import { transformVOn } from './transforms/vOn'
|
||||
import { transformVShow } from './transforms/vShow'
|
||||
import { transformRef } from './transforms/transformRef'
|
||||
import { transformInterpolation } from './transforms/transformInterpolation'
|
||||
import type { HackOptions } from './ir'
|
||||
|
||||
|
@ -95,7 +96,7 @@ export function getBaseTransformPreset(
|
|||
prefixIdentifiers?: boolean,
|
||||
): TransformPreset {
|
||||
return [
|
||||
[transformOnce, transformInterpolation, transformElement],
|
||||
[transformOnce, transformRef, transformInterpolation, transformElement],
|
||||
{
|
||||
bind: transformVBind,
|
||||
on: transformVOn,
|
||||
|
|
|
@ -26,6 +26,7 @@ import {
|
|||
type SetEventIRNode,
|
||||
type SetHtmlIRNode,
|
||||
type SetPropIRNode,
|
||||
type SetRefIRNode,
|
||||
type SetTextIRNode,
|
||||
type VaporHelper,
|
||||
type WithDirectiveIRNode,
|
||||
|
@ -386,6 +387,8 @@ function genOperation(oper: OperationNode, context: CodegenContext) {
|
|||
return genSetEvent(oper, context)
|
||||
case IRNodeTypes.SET_HTML:
|
||||
return genSetHtml(oper, context)
|
||||
case IRNodeTypes.SET_REF:
|
||||
return genSetRef(oper, context)
|
||||
case IRNodeTypes.CREATE_TEXT_NODE:
|
||||
return genCreateTextNode(oper, context)
|
||||
case IRNodeTypes.INSERT_NODE:
|
||||
|
@ -442,6 +445,14 @@ function genSetHtml(oper: SetHtmlIRNode, context: CodegenContext) {
|
|||
)
|
||||
}
|
||||
|
||||
function genSetRef(oper: SetRefIRNode, context: CodegenContext) {
|
||||
const { newline, pushFnCall, vaporHelper } = context
|
||||
newline()
|
||||
pushFnCall(vaporHelper('setRef'), `n${oper.element}`, () =>
|
||||
genExpression(oper.value, context),
|
||||
)
|
||||
}
|
||||
|
||||
function genCreateTextNode(
|
||||
oper: CreateTextNodeIRNode,
|
||||
context: CodegenContext,
|
||||
|
|
|
@ -17,6 +17,7 @@ export enum IRNodeTypes {
|
|||
SET_TEXT,
|
||||
SET_EVENT,
|
||||
SET_HTML,
|
||||
SET_REF,
|
||||
|
||||
INSERT_NODE,
|
||||
PREPEND_NODE,
|
||||
|
@ -93,6 +94,12 @@ export interface SetHtmlIRNode extends BaseIRNode {
|
|||
value: IRExpression
|
||||
}
|
||||
|
||||
export interface SetRefIRNode extends BaseIRNode {
|
||||
type: IRNodeTypes.SET_REF
|
||||
element: number
|
||||
value: IRExpression
|
||||
}
|
||||
|
||||
export interface CreateTextNodeIRNode extends BaseIRNode {
|
||||
type: IRNodeTypes.CREATE_TEXT_NODE
|
||||
id: number
|
||||
|
@ -134,6 +141,7 @@ export type OperationNode =
|
|||
| SetTextIRNode
|
||||
| SetEventIRNode
|
||||
| SetHtmlIRNode
|
||||
| SetRefIRNode
|
||||
| CreateTextNodeIRNode
|
||||
| InsertNodeIRNode
|
||||
| PrependNodeIRNode
|
||||
|
|
|
@ -4,7 +4,7 @@ import {
|
|||
ElementTypes,
|
||||
NodeTypes,
|
||||
} from '@vue/compiler-dom'
|
||||
import { isBuiltInDirective, isVoidTag } from '@vue/shared'
|
||||
import { isBuiltInDirective, isReservedProp, isVoidTag } from '@vue/shared'
|
||||
import type { NodeTransform, TransformContext } from '../transform'
|
||||
import { IRNodeTypes, type VaporDirectiveNode } from '../ir'
|
||||
|
||||
|
@ -60,6 +60,8 @@ function transformProp(
|
|||
context: TransformContext<ElementNode>,
|
||||
): void {
|
||||
const { name, loc } = prop
|
||||
if (isReservedProp(name)) return
|
||||
|
||||
if (prop.type === NodeTypes.ATTRIBUTE) {
|
||||
context.template += ` ${name}`
|
||||
if (prop.value) context.template += `="${prop.value.content}"`
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
import {
|
||||
NodeTypes,
|
||||
type SimpleExpressionNode,
|
||||
findProp,
|
||||
} from '@vue/compiler-dom'
|
||||
import type { NodeTransform } from '../transform'
|
||||
import { type IRExpression, IRNodeTypes } from '../ir'
|
||||
import { normalizeBindShorthand } from './vBind'
|
||||
|
||||
export const transformRef: NodeTransform = (node, context) => {
|
||||
if (node.type !== NodeTypes.ELEMENT) return
|
||||
const dir = findProp(node, 'ref', false, true)
|
||||
|
||||
if (!dir) return
|
||||
|
||||
let value: IRExpression
|
||||
if (dir.type === NodeTypes.DIRECTIVE) {
|
||||
value =
|
||||
(dir.exp as SimpleExpressionNode | undefined) ||
|
||||
normalizeBindShorthand(dir.arg as SimpleExpressionNode)
|
||||
} else {
|
||||
value = dir.value ? JSON.stringify(dir.value.content) : '""'
|
||||
}
|
||||
|
||||
context.registerOperation({
|
||||
type: IRNodeTypes.SET_REF,
|
||||
element: context.reference(),
|
||||
value,
|
||||
loc: dir.loc,
|
||||
})
|
||||
}
|
|
@ -4,10 +4,20 @@ import {
|
|||
createCompilerError,
|
||||
createSimpleExpression,
|
||||
} from '@vue/compiler-core'
|
||||
import { camelize } from '@vue/shared'
|
||||
import { camelize, isReservedProp } from '@vue/shared'
|
||||
import { IRNodeTypes } from '../ir'
|
||||
import type { DirectiveTransform } from '../transform'
|
||||
|
||||
export function normalizeBindShorthand(
|
||||
arg: SimpleExpressionNode,
|
||||
): SimpleExpressionNode {
|
||||
// shorthand syntax https://github.com/vuejs/core/pull/9451
|
||||
const propName = camelize(arg.content)
|
||||
const exp = createSimpleExpression(propName, false, arg.loc)
|
||||
exp.ast = null
|
||||
return exp
|
||||
}
|
||||
|
||||
export const transformVBind: DirectiveTransform = (dir, node, context) => {
|
||||
let { arg, exp, loc, modifiers } = dir
|
||||
|
||||
|
@ -15,12 +25,9 @@ export const transformVBind: DirectiveTransform = (dir, node, context) => {
|
|||
// TODO support v-bind="{}"
|
||||
return
|
||||
}
|
||||
if (!exp) {
|
||||
// shorthand syntax https://github.com/vuejs/core/pull/9451
|
||||
const propName = camelize(arg.content)
|
||||
exp = createSimpleExpression(propName, false, arg.loc)
|
||||
exp.ast = null
|
||||
}
|
||||
if (arg.isStatic && isReservedProp(arg.content)) return
|
||||
|
||||
if (!exp) exp = normalizeBindShorthand(arg)
|
||||
|
||||
let camel = false
|
||||
if (modifiers.includes('camel')) {
|
||||
|
|
|
@ -2,6 +2,7 @@ import { isArray, toDisplayString } from '@vue/shared'
|
|||
import type { Block, ParentBlock } from './render'
|
||||
|
||||
export * from './dom/patchProp'
|
||||
export * from './dom/templateRef'
|
||||
|
||||
export function insert(block: Block, parent: Node, anchor: Node | null = null) {
|
||||
// if (!isHydrating) {
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
import { type Ref, type SchedulerJob, isRef } from '@vue/reactivity'
|
||||
import { currentInstance } from '../component'
|
||||
import { VaporErrorCodes, callWithErrorHandling } from '../errorHandling'
|
||||
import { hasOwn, isFunction, isString } from '@vue/shared'
|
||||
import { warn } from '../warning'
|
||||
import { queuePostRenderEffect } from '../scheduler'
|
||||
|
||||
export type NodeRef = string | Ref | ((ref: Element) => void)
|
||||
|
||||
/**
|
||||
* Function for handling a template ref
|
||||
*/
|
||||
export function setRef(el: Element, ref: NodeRef) {
|
||||
if (!currentInstance) return
|
||||
const { setupState, isUnmounted } = currentInstance
|
||||
|
||||
if (isFunction(ref)) {
|
||||
callWithErrorHandling(ref, currentInstance, VaporErrorCodes.FUNCTION_REF, [
|
||||
el,
|
||||
// refs,
|
||||
])
|
||||
} else {
|
||||
const _isString = isString(ref)
|
||||
const _isRef = isRef(ref)
|
||||
|
||||
if (_isString || _isRef) {
|
||||
const doSet = () => {
|
||||
if (_isString) {
|
||||
if (hasOwn(setupState, ref)) {
|
||||
setupState[ref] = el
|
||||
}
|
||||
} else if (_isRef) {
|
||||
ref.value = el
|
||||
} else if (__DEV__) {
|
||||
warn('Invalid template ref type:', ref, `(${typeof ref})`)
|
||||
}
|
||||
}
|
||||
// #9908 ref on v-for mutates the same array for both mount and unmount
|
||||
// and should be done together
|
||||
if (isUnmounted /* || isVFor */) {
|
||||
doSet()
|
||||
} else {
|
||||
// #1789: set new refs in a post job so that they don't get overwritten
|
||||
// by unmounting ones.
|
||||
;(doSet as SchedulerJob).id = -1
|
||||
queuePostRenderEffect(doSet)
|
||||
}
|
||||
} else if (__DEV__) {
|
||||
warn('Invalid template ref type:', ref, `(${typeof ref})`)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -10,6 +10,7 @@ import {
|
|||
import { initProps } from './componentProps'
|
||||
import { invokeDirectiveHook } from './directive'
|
||||
import { insert, remove } from './dom'
|
||||
import { queuePostRenderEffect } from './scheduler'
|
||||
|
||||
export type Block = Node | Fragment | Block[]
|
||||
export type ParentBlock = ParentNode | Node[]
|
||||
|
@ -78,8 +79,10 @@ export function mountComponent(
|
|||
instance.isMounted = true
|
||||
|
||||
// hook: mounted
|
||||
invokeDirectiveHook(instance, 'mounted')
|
||||
m && invokeArrayFns(m)
|
||||
queuePostRenderEffect(() => {
|
||||
invokeDirectiveHook(instance, 'mounted')
|
||||
m && invokeArrayFns(m)
|
||||
})
|
||||
reset()
|
||||
|
||||
return instance
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script setup lang="ts">
|
||||
import { ref } from 'vue/vapor'
|
||||
import { onMounted, ref } from 'vue/vapor'
|
||||
|
||||
interface Task {
|
||||
title: string
|
||||
|
@ -7,6 +7,7 @@ interface Task {
|
|||
}
|
||||
const tasks = ref<Task[]>([])
|
||||
const value = ref('hello')
|
||||
const inputRef = ref<HTMLInputElement>()
|
||||
|
||||
function handleAdd() {
|
||||
tasks.value.push({
|
||||
|
@ -16,6 +17,11 @@ function handleAdd() {
|
|||
// TODO: clear input
|
||||
value.value = ''
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
console.log('onMounted')
|
||||
console.log(inputRef.value)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -41,6 +47,7 @@ function handleAdd() {
|
|||
<li>
|
||||
<input
|
||||
type="text"
|
||||
:ref="el => (inputRef = el)"
|
||||
:value="value"
|
||||
@input="evt => (value = evt.target.value)"
|
||||
/>
|
||||
|
|
Loading…
Reference in New Issue