]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
feat: basic template ref
author三咲智子 Kevin Deng <sxzz@sxzz.moe>
Sat, 20 Jan 2024 15:48:10 +0000 (23:48 +0800)
committer三咲智子 Kevin Deng <sxzz@sxzz.moe>
Sat, 20 Jan 2024 15:48:10 +0000 (23:48 +0800)
packages/compiler-vapor/src/compile.ts
packages/compiler-vapor/src/generate.ts
packages/compiler-vapor/src/ir.ts
packages/compiler-vapor/src/transforms/transformElement.ts
packages/compiler-vapor/src/transforms/transformRef.ts [new file with mode: 0644]
packages/compiler-vapor/src/transforms/vBind.ts
packages/runtime-vapor/src/dom.ts
packages/runtime-vapor/src/dom/templateRef.ts [new file with mode: 0644]
packages/runtime-vapor/src/render.ts
playground/src/todo-mvc.vue

index 6d304ae200b341033720be378f5eb6a11a0fb372..ab74e5ffd2242d3935b445ca61cbb423d3469985 100644 (file)
@@ -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,
index d5f6215104a66acb1be07faa2bc5de218065f9fa..b66a4629c42503ec695040af74945a2d43d6eeaa 100644 (file)
@@ -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,
index ae76aa92a03bbdfacdd2ffd27d348790eff8e737..20e0d14acd39b51b18b7b9f1206b3aa6085f67cb 100644 (file)
@@ -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
index 5b3787571354439bbd2b82291520384a0512e901..1e519935c9a9daea8ece476f0b3a54e0d951aaa9 100644 (file)
@@ -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}"`
diff --git a/packages/compiler-vapor/src/transforms/transformRef.ts b/packages/compiler-vapor/src/transforms/transformRef.ts
new file mode 100644 (file)
index 0000000..141b01f
--- /dev/null
@@ -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,
+  })
+}
index 4e013c83619b7e2b9ecaa55b41fd9801e51904fb..ff2799a15807243a36967577045724e6d8d63b2f 100644 (file)
@@ -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')) {
index 0c8a53222b1da0c7f255f14d83c6acc97da4d9db..2641a33d1458ae22a60ce04914544cbd9c280912 100644 (file)
@@ -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) {
diff --git a/packages/runtime-vapor/src/dom/templateRef.ts b/packages/runtime-vapor/src/dom/templateRef.ts
new file mode 100644 (file)
index 0000000..fcedbd1
--- /dev/null
@@ -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})`)
+    }
+  }
+}
index d422db08aae745e304bfa9fee552864e97446fad..10b121a9e7d4190cd957040a1497a9994c3918f2 100644 (file)
@@ -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
index 6ebac01f58f1887742a72a2396aabaeade95a709..e6228f8c494772ccd1954a95c76de648ee47233a 100644 (file)
@@ -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)"
       />