【问题标题】:can't use template ref on component in vue 3 composition api无法在 vue 3 组合 API 中的组件上使用模板引用
【发布时间】:2021-07-06 02:11:06
【问题描述】:

我想从父级获取 vue.js 组件的尺寸(我正在使用实验性 script setup)。

当我在组件中使用 ref 时,它按预期工作。我得到了尺寸:

// Child.vue
<template>
  <div ref="wrapper">
   // content ...
  </div>
</template>
<script setup>
import { ref, onMounted } from 'vue'

const wrapper = ref(null)

onMounted(() => {
  const rect = wrapper.value.getBoundingClientRect()
  console.log(rect) // works fine!
})
</script>

但我想获取父组件内的维度。这可能吗?

我试过这个:

// Parent.vue
<template>
  <Child ref="wrapper" />
</template>
<script setup>
import Child from './Child'
import { ref, onMounted } from 'vue'

const wrapper = ref(null)

onMounted(() => {
  const rect = wrapper.value.getBoundingClientRect()
  console.log(rect) // failed!
})
</script>

控制台记录此错误消息: Uncaught (in promise) TypeError: x.value.getBoundingClientRect is not a function


在文档中我只能找到使用template refsinside the child component的方法

这种方法是否行不通,因为引用“默认关闭”为rfcs description says

【问题讨论】:

    标签: vue.js vue-component vuejs3 ref vue-composition-api


    【解决方案1】:

    您可以使用$el 字段访问根元素,如下所示:

    <template>
      <Child ref="wrapper" />
    </template>
    
    <script setup>
    import Child from './Child'
    import { ref, onMounted } from 'vue'
    
    const wrapper = ref(null)
    
    onMounted(() => {
      const rect = wrapper.value.$el.getBoundingClientRect()
      console.log(rect) 
    })
    </script
    

    【讨论】:

    • 遇到另一个错误:Uncaught TypeError: wrapper.value.$el is undefined
    • console.log(wrapper.value) 给我输出
    • 这里是输出:imgur.com/a/hrwry2Z在脚本里面是'null',在onMounted里面是empty object { }
    • 在我的情况下,我得到带有 $el,$data $options 字段的对象
    • &lt;script setup&gt; 是一种新的糖语法,它取代了setup 钩子,并且任何属性都可以在不使用return{} 的情况下公开
    【解决方案2】:

    我不认为这必然与&lt;script setup&gt; 标签有关。即使在标准脚本语法中,您的第二个示例也不会按原样工作。

    问题是您将ref 直接放在子组件上:

    <template>
      <Child ref="wrapper" />
    </template>
    

    并且对组件的引用与对该组件的 root 元素的引用相同。它没有getBoundingClientRect() 方法。

    事实上,Vue 3 不再要求组件具有单个根元素。您可以将您的子组件定义为:

    <template>
      <div ref="wrapper1">// content ...</div>
      <div ref="wrapper2">// content ...</div>
    </template>
    <script >
    import { ref } from "vue";
    
    export default {
      name: "Child",
    
      setup() {
        const wrapper1 = ref(null);
        const wrapper1 = ref(null);
     
        return { wrapper1, wrapper2 };
      },
    };
    </script>
    

    您的 Parent 组件中的 ref 现在应该是什么?

    wrapper.value 从您的父组件记录到您的控制台。它实际上是您的 Child 组件中所有 ref 的对象:

    {
      wrapper1: {...}, // the 1st HTMLDivElement
      wrapper2: {...}  // the 2nd HTMLDivElement
    }
    

    你可以wrapper.value.wrapper1.getBoundingClientRect(),这样就可以了。

    【讨论】:

      【解决方案3】:

      好的,你需要这样做:

      // Parent component
      <template>
        <Child :get-ref="(el) => { wrapper = el }" />
      </template>
      
      <script setup>
      import Child from './Child.vue';
      import { ref, onMounted } from 'vue';
      
      const wrapper = ref();
      
      onMounted(() => {
        const rect = wrapper.value.getBoundingClientRect()
        console.log(rect) // works fine!
      });
      </script>
      

      // Child component
      <template>
        <div :ref="(el) => { wrapper = el; getRef(el)}">
         // content ...
        </div>
      </template>
      
      <script setup>
      import { defineProps, ref, onMounted } from 'vue';
        
      const props = defineProps({
        getRef: {
          type: Function,
        },
      });
      
      const wrapper = ref();
      
      onMounted(() => {
        const rect = wrapper.value.getBoundingClientRect()
        console.log(rect) // works fine!
      });
      </script>
      

      要了解原因,我们需要查看 Vue 在 ref 上的文档: Vue special-attribute 'ref'.

      关于(模板)ref 的动态绑定,它说:

      <!-- When bound dynamically, we can define ref as a callback function,
      passing the element or component instance explicitly -->
      <child-component :ref="(el) => child = el"></child-component>
      

      由于prop 允许您将数据从父级传递给子级,我们可以使用prop 和动态ref 绑定的组合来获得想要的结果。首先,我们将动态的ref 回调函数传递给孩子作为getRef prop

      <Child :get-ref="(el) => { wrapper = el }" />
      

      然后,子级在元素上执行动态ref 绑定,它将目标el 分配给它的wrapper ref 并调用getRef prop 函数在那个回调函数中让父级也抓住el

      <div :ref="(el) => {
                  wrapper = el; // child registers wrapper ref
                  getRef(el); // parent registers the wrapper ref
                  }">
      

      请注意,这允许我们在父组件子组件中都有wrapper 元素的ref。如果您希望在父组件中访问wrapper 元素only,您可以跳过子组件的回调函数,只需将ref 绑定到prop,如下所示:

      // Child component
      <template>
        <div :ref="getRef">
         // content ...
        </div>
      </template>
      
      <script setup>
      import { defineProps } from 'vue';
      
      const props = defineProps({
        getRef: {
          type: Function,
        },
      });
      </script>
      

      这将只让父级拥有ref 到您模板的wrapper

      【讨论】:

        【解决方案4】:

        我今天遇到了这个问题。问题是,当使用&lt;script setup&gt; 模式时,没有返回任何声明的变量。当您获得组件的引用时,它只是一个空对象。解决这个问题的方法是在设置块中使用defineExpose

        // Child.vue
        
        <template>
          <div ref="wrapper">
           <!-- content ... -->
          </div>
        </template>
        
        <script setup>
        import { defineExpose, ref } from 'vue'
        
        const wrapper = ref(null)
        
        defineExpose({ wrapper })
        </script>
        

        您在父级中设置模板引用的方式很好。您在控制台中看到 empty object { } 的事实意味着它正在工作。

        就像已经说过的其他答案一样,可以像这样从父级访问子引用:wrapper.value.wrapper.getBoundingClientRect()

        rfc 有一个部分讨论了它的工作原理/原因:https://github.com/vuejs/rfcs/blob/master/active-rfcs/0040-script-setup.md#exposing-components-public-interface

        还需要注意的是,使用&lt;script setup&gt; 模式,您在父组件中的 ref 将不是 ComponentInstance。这意味着您不能像其他方式那样调用$el。它只会包含您在defineExpose 中输入的值。

        【讨论】:

          猜你喜欢
          • 2021-06-08
          • 2021-04-22
          • 2020-12-20
          • 1970-01-01
          • 2021-05-04
          • 2022-11-05
          • 1970-01-01
          • 2022-06-12
          • 2022-01-18
          相关资源
          最近更新 更多