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 { transformVBind } from './transforms/vBind'
|
||||||
import { transformVOn } from './transforms/vOn'
|
import { transformVOn } from './transforms/vOn'
|
||||||
import { transformVShow } from './transforms/vShow'
|
import { transformVShow } from './transforms/vShow'
|
||||||
|
import { transformRef } from './transforms/transformRef'
|
||||||
import { transformInterpolation } from './transforms/transformInterpolation'
|
import { transformInterpolation } from './transforms/transformInterpolation'
|
||||||
import type { HackOptions } from './ir'
|
import type { HackOptions } from './ir'
|
||||||
|
|
||||||
|
@ -95,7 +96,7 @@ export function getBaseTransformPreset(
|
||||||
prefixIdentifiers?: boolean,
|
prefixIdentifiers?: boolean,
|
||||||
): TransformPreset {
|
): TransformPreset {
|
||||||
return [
|
return [
|
||||||
[transformOnce, transformInterpolation, transformElement],
|
[transformOnce, transformRef, transformInterpolation, transformElement],
|
||||||
{
|
{
|
||||||
bind: transformVBind,
|
bind: transformVBind,
|
||||||
on: transformVOn,
|
on: transformVOn,
|
||||||
|
|
|
@ -26,6 +26,7 @@ import {
|
||||||
type SetEventIRNode,
|
type SetEventIRNode,
|
||||||
type SetHtmlIRNode,
|
type SetHtmlIRNode,
|
||||||
type SetPropIRNode,
|
type SetPropIRNode,
|
||||||
|
type SetRefIRNode,
|
||||||
type SetTextIRNode,
|
type SetTextIRNode,
|
||||||
type VaporHelper,
|
type VaporHelper,
|
||||||
type WithDirectiveIRNode,
|
type WithDirectiveIRNode,
|
||||||
|
@ -386,6 +387,8 @@ function genOperation(oper: OperationNode, context: CodegenContext) {
|
||||||
return genSetEvent(oper, context)
|
return genSetEvent(oper, context)
|
||||||
case IRNodeTypes.SET_HTML:
|
case IRNodeTypes.SET_HTML:
|
||||||
return genSetHtml(oper, context)
|
return genSetHtml(oper, context)
|
||||||
|
case IRNodeTypes.SET_REF:
|
||||||
|
return genSetRef(oper, context)
|
||||||
case IRNodeTypes.CREATE_TEXT_NODE:
|
case IRNodeTypes.CREATE_TEXT_NODE:
|
||||||
return genCreateTextNode(oper, context)
|
return genCreateTextNode(oper, context)
|
||||||
case IRNodeTypes.INSERT_NODE:
|
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(
|
function genCreateTextNode(
|
||||||
oper: CreateTextNodeIRNode,
|
oper: CreateTextNodeIRNode,
|
||||||
context: CodegenContext,
|
context: CodegenContext,
|
||||||
|
|
|
@ -17,6 +17,7 @@ export enum IRNodeTypes {
|
||||||
SET_TEXT,
|
SET_TEXT,
|
||||||
SET_EVENT,
|
SET_EVENT,
|
||||||
SET_HTML,
|
SET_HTML,
|
||||||
|
SET_REF,
|
||||||
|
|
||||||
INSERT_NODE,
|
INSERT_NODE,
|
||||||
PREPEND_NODE,
|
PREPEND_NODE,
|
||||||
|
@ -93,6 +94,12 @@ export interface SetHtmlIRNode extends BaseIRNode {
|
||||||
value: IRExpression
|
value: IRExpression
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface SetRefIRNode extends BaseIRNode {
|
||||||
|
type: IRNodeTypes.SET_REF
|
||||||
|
element: number
|
||||||
|
value: IRExpression
|
||||||
|
}
|
||||||
|
|
||||||
export interface CreateTextNodeIRNode extends BaseIRNode {
|
export interface CreateTextNodeIRNode extends BaseIRNode {
|
||||||
type: IRNodeTypes.CREATE_TEXT_NODE
|
type: IRNodeTypes.CREATE_TEXT_NODE
|
||||||
id: number
|
id: number
|
||||||
|
@ -134,6 +141,7 @@ export type OperationNode =
|
||||||
| SetTextIRNode
|
| SetTextIRNode
|
||||||
| SetEventIRNode
|
| SetEventIRNode
|
||||||
| SetHtmlIRNode
|
| SetHtmlIRNode
|
||||||
|
| SetRefIRNode
|
||||||
| CreateTextNodeIRNode
|
| CreateTextNodeIRNode
|
||||||
| InsertNodeIRNode
|
| InsertNodeIRNode
|
||||||
| PrependNodeIRNode
|
| PrependNodeIRNode
|
||||||
|
|
|
@ -4,7 +4,7 @@ import {
|
||||||
ElementTypes,
|
ElementTypes,
|
||||||
NodeTypes,
|
NodeTypes,
|
||||||
} from '@vue/compiler-dom'
|
} from '@vue/compiler-dom'
|
||||||
import { isBuiltInDirective, isVoidTag } from '@vue/shared'
|
import { isBuiltInDirective, isReservedProp, isVoidTag } from '@vue/shared'
|
||||||
import type { NodeTransform, TransformContext } from '../transform'
|
import type { NodeTransform, TransformContext } from '../transform'
|
||||||
import { IRNodeTypes, type VaporDirectiveNode } from '../ir'
|
import { IRNodeTypes, type VaporDirectiveNode } from '../ir'
|
||||||
|
|
||||||
|
@ -60,6 +60,8 @@ function transformProp(
|
||||||
context: TransformContext<ElementNode>,
|
context: TransformContext<ElementNode>,
|
||||||
): void {
|
): void {
|
||||||
const { name, loc } = prop
|
const { name, loc } = prop
|
||||||
|
if (isReservedProp(name)) return
|
||||||
|
|
||||||
if (prop.type === NodeTypes.ATTRIBUTE) {
|
if (prop.type === NodeTypes.ATTRIBUTE) {
|
||||||
context.template += ` ${name}`
|
context.template += ` ${name}`
|
||||||
if (prop.value) context.template += `="${prop.value.content}"`
|
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,
|
createCompilerError,
|
||||||
createSimpleExpression,
|
createSimpleExpression,
|
||||||
} from '@vue/compiler-core'
|
} from '@vue/compiler-core'
|
||||||
import { camelize } from '@vue/shared'
|
import { camelize, isReservedProp } from '@vue/shared'
|
||||||
import { IRNodeTypes } from '../ir'
|
import { IRNodeTypes } from '../ir'
|
||||||
import type { DirectiveTransform } from '../transform'
|
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) => {
|
export const transformVBind: DirectiveTransform = (dir, node, context) => {
|
||||||
let { arg, exp, loc, modifiers } = dir
|
let { arg, exp, loc, modifiers } = dir
|
||||||
|
|
||||||
|
@ -15,12 +25,9 @@ export const transformVBind: DirectiveTransform = (dir, node, context) => {
|
||||||
// TODO support v-bind="{}"
|
// TODO support v-bind="{}"
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (!exp) {
|
if (arg.isStatic && isReservedProp(arg.content)) return
|
||||||
// shorthand syntax https://github.com/vuejs/core/pull/9451
|
|
||||||
const propName = camelize(arg.content)
|
if (!exp) exp = normalizeBindShorthand(arg)
|
||||||
exp = createSimpleExpression(propName, false, arg.loc)
|
|
||||||
exp.ast = null
|
|
||||||
}
|
|
||||||
|
|
||||||
let camel = false
|
let camel = false
|
||||||
if (modifiers.includes('camel')) {
|
if (modifiers.includes('camel')) {
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { isArray, toDisplayString } from '@vue/shared'
|
||||||
import type { Block, ParentBlock } from './render'
|
import type { Block, ParentBlock } from './render'
|
||||||
|
|
||||||
export * from './dom/patchProp'
|
export * from './dom/patchProp'
|
||||||
|
export * from './dom/templateRef'
|
||||||
|
|
||||||
export function insert(block: Block, parent: Node, anchor: Node | null = null) {
|
export function insert(block: Block, parent: Node, anchor: Node | null = null) {
|
||||||
// if (!isHydrating) {
|
// 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 { initProps } from './componentProps'
|
||||||
import { invokeDirectiveHook } from './directive'
|
import { invokeDirectiveHook } from './directive'
|
||||||
import { insert, remove } from './dom'
|
import { insert, remove } from './dom'
|
||||||
|
import { queuePostRenderEffect } from './scheduler'
|
||||||
|
|
||||||
export type Block = Node | Fragment | Block[]
|
export type Block = Node | Fragment | Block[]
|
||||||
export type ParentBlock = ParentNode | Node[]
|
export type ParentBlock = ParentNode | Node[]
|
||||||
|
@ -78,8 +79,10 @@ export function mountComponent(
|
||||||
instance.isMounted = true
|
instance.isMounted = true
|
||||||
|
|
||||||
// hook: mounted
|
// hook: mounted
|
||||||
invokeDirectiveHook(instance, 'mounted')
|
queuePostRenderEffect(() => {
|
||||||
m && invokeArrayFns(m)
|
invokeDirectiveHook(instance, 'mounted')
|
||||||
|
m && invokeArrayFns(m)
|
||||||
|
})
|
||||||
reset()
|
reset()
|
||||||
|
|
||||||
return instance
|
return instance
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue/vapor'
|
import { onMounted, ref } from 'vue/vapor'
|
||||||
|
|
||||||
interface Task {
|
interface Task {
|
||||||
title: string
|
title: string
|
||||||
|
@ -7,6 +7,7 @@ interface Task {
|
||||||
}
|
}
|
||||||
const tasks = ref<Task[]>([])
|
const tasks = ref<Task[]>([])
|
||||||
const value = ref('hello')
|
const value = ref('hello')
|
||||||
|
const inputRef = ref<HTMLInputElement>()
|
||||||
|
|
||||||
function handleAdd() {
|
function handleAdd() {
|
||||||
tasks.value.push({
|
tasks.value.push({
|
||||||
|
@ -16,6 +17,11 @@ function handleAdd() {
|
||||||
// TODO: clear input
|
// TODO: clear input
|
||||||
value.value = ''
|
value.value = ''
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
console.log('onMounted')
|
||||||
|
console.log(inputRef.value)
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
@ -41,6 +47,7 @@ function handleAdd() {
|
||||||
<li>
|
<li>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
|
:ref="el => (inputRef = el)"
|
||||||
:value="value"
|
:value="value"
|
||||||
@input="evt => (value = evt.target.value)"
|
@input="evt => (value = evt.target.value)"
|
||||||
/>
|
/>
|
||||||
|
|
Loading…
Reference in New Issue