模板引用

在使用组合式 API 时,响应式引用模板引用的概念是统一的。为了获得对模板内元素或组件实例的引用,我们可以像往常一样声明 ref 并从 setup() 返回:

  1. <template>
  2. <div ref="root">This is a root element</div>
  3. </template>
  4. <script>
  5. import { ref, onMounted } from 'vue'
  6. export default {
  7. setup() {
  8. const root = ref(null)
  9. onMounted(() => {
  10. // DOM 元素将在初始渲染后分配给 ref
  11. console.log(root.value) // <div>This is a root element</div>
  12. })
  13. return {
  14. root
  15. }
  16. }
  17. }
  18. </script>

这里我们在渲染上下文中暴露 root,并通过 ref=”root”,将其绑定到 div 作为其 ref。在虚拟 DOM 补丁算法中,如果 VNode 的 ref 键对应于渲染上下文中的 ref,则 VNode 的相应元素或组件实例将被分配给该 ref 的值。这是在虚拟 DOM 挂载/打补丁过程中执行的,因此模板引用只会在初始渲染之后获得赋值。

作为模板使用的 ref 的行为与任何其他 ref 一样:它们是响应式的,可以传递到 (或从中返回) 复合函数中。

JSX 中的用法

  1. export default {
  2. setup() {
  3. const root = ref(null)
  4. return () =>
  5. h('div', {
  6. ref: root
  7. })
  8. // with JSX
  9. return () => <div ref={root} />
  10. }
  11. }

v-for 中的用法

组合式 API 模板引用在 v-for 内部使用时没有特殊处理。相反,请使用函数引用执行自定义处理:

  1. <template>
  2. <div v-for="(item, i) in list" :ref="el => { if (el) divs[i] = el }">
  3. {{ item }}
  4. </div>
  5. </template>
  6. <script>
  7. import { ref, reactive, onBeforeUpdate } from 'vue'
  8. export default {
  9. setup() {
  10. const list = reactive([1, 2, 3])
  11. const divs = ref([])
  12. // 确保在每次更新之前重置ref
  13. onBeforeUpdate(() => {
  14. divs.value = []
  15. })
  16. return {
  17. list,
  18. divs
  19. }
  20. }
  21. }
  22. </script>

侦听模板引用

侦听模板引用的变更可以替代前面例子中演示使用的生命周期钩子。

但与生命周期钩子的一个关键区别是,watch() 和 watchEffect() 在 DOM 挂载或更新之前运行副作用,所以当侦听器运行时,模板引用还未被更新。

  1. <template>
  2. <div ref="root">This is a root element</div>
  3. </template>
  4. <script>
  5. import { ref, watchEffect } from 'vue'
  6. export default {
  7. setup() {
  8. const root = ref(null)
  9. watchEffect(() => {
  10. // 这个副作用在 DOM 更新之前运行,因此,模板引用还没有持有对元素的引用。
  11. console.log(root.value) // => null
  12. })
  13. return {
  14. root
  15. }
  16. }
  17. }
  18. </script>

因此,使用模板引用的侦听器应该用 flush: ‘post’ 选项来定义,这将在 DOM 更新运行副作用,确保模板引用与 DOM 保持同步,并引用正确的元素。

  1. <template>
  2. <div ref="root">This is a root element</div>
  3. </template>
  4. <script>
  5. import { ref, watchEffect } from 'vue'
  6. export default {
  7. setup() {
  8. const root = ref(null)
  9. watchEffect(() => {
  10. console.log(root.value) // => <div>This is a root element</div>
  11. },
  12. {
  13. flush: 'post'
  14. })
  15. return {
  16. root
  17. }
  18. }
  19. }
  20. </script>