Vue3 组件 ref 注意事项

91 阅读2分钟

如果一个子组件使用的是选项式 API 或没有使用 <script setup>,被引用的组件实例和该子组件的 this 完全一致,这意味着父组件对子组件的每一个属性和方法都有完全的访问权。这使得在父组件和子组件之间创建紧密耦合的实现细节变得很容易,当然也因此,应该只在绝对需要时才使用组件引用。大多数情况下,你应该首先使用标准的 props 和 emit 接口来实现父子组件交互。

有一个例外的情况,使用了 <script setup> 的组件是默认私有的:一个父组件无法访问到一个使用了 <script setup> 的子组件中的任何东西,除非子组件在其中通过 defineExpose 宏显式暴露

                  const a = 1
                  const b = ref(2)
                  
                  // 像 defineExpose 这样的编译器宏不需要导入
                  defineExpose({
                    a,
                      b
                      })
                      </script>" aria-label="复制" data-bs-original-title="复制">
                      <i class="far fa-copy"></i>
          </button>
</div>
      </div><pre class="hljs language-dust"><span class="language-xml"><span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">setup</span>&gt;</span><span class="language-javascript">
      <span class="hljs-keyword">import</span> </span></span><span class="hljs-template-variable">{ ref }</span><span class="language-xml"><span class="language-javascript"> <span class="hljs-keyword">from</span> <span class="hljs-string">'vue'</span>
      
      <span class="hljs-keyword">const</span> a = <span class="hljs-number">1</span>
      <span class="hljs-keyword">const</span> b = <span class="hljs-title function_">ref</span>(<span class="hljs-number">2</span>)
      
      <span class="hljs-comment">// 像 defineExpose 这样的编译器宏不需要导入</span>
      <span class="hljs-title function_">defineExpose</span>(</span></span><span class="hljs-template-variable">{
        a,
          b
          }</span><span class="language-xml">)
          <span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span></span></pre><p>当父组件通过模板引用获取到了该组件的实例时,得到的实例类型为&nbsp;<code>{ a: number, b: number }</code>&nbsp;(ref 都会自动解包,和一般的实例一样)。</p><h2 id="item-1">为组件模板引用标注类型</h2><p>有时,你可能需要为一个子组件添加一个模板引用,以便调用它公开的方法。举例来说,我们有一个&nbsp;<code>MyModal</code>&nbsp;子组件,它有一个打开模态框的方法:</p><div class="widget-codetool" style="display: none;">
      <div class="widget-codetool--inner">
                  <button type="button" class="btn btn-dark rounded-0 sflex-center copyCode" data-toggle="tooltip" data-placement="top" data-clipboard-text="<!-- MyModal.vue -->
                  <script setup lang=&quot;ts&quot;>
                  import { ref } from 'vue'
                  
                  const isContentShown = ref(false)
                  const open = () => (isContentShown.value = true)
                  
                  defineExpose({
                    open
                    })
                    </script>" aria-label="复制" data-bs-original-title="复制">
                      <i class="far fa-copy"></i>
          </button>
</div>
      </div><pre class="hljs language-dust"><span class="language-xml"><span class="hljs-comment">&lt;!-- MyModal.vue --&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">setup</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"ts"</span>&gt;</span><span class="language-javascript">
      <span class="hljs-keyword">import</span> </span></span><span class="hljs-template-variable">{ ref }</span><span class="language-xml"><span class="language-javascript"> <span class="hljs-keyword">from</span> <span class="hljs-string">'vue'</span>
      
      <span class="hljs-keyword">const</span> isContentShown = <span class="hljs-title function_">ref</span>(<span class="hljs-literal">false</span>)
      <span class="hljs-keyword">const</span> <span class="hljs-title function_">open</span> = (<span class="hljs-params"></span>) =&gt; (isContentShown.<span class="hljs-property">value</span> = <span class="hljs-literal">true</span>)
      
      <span class="hljs-title function_">defineExpose</span>(</span></span><span class="hljs-template-variable">{
        open
        }</span><span class="language-xml">)
        <span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span></span></pre><p>为了获取&nbsp;<code>MyModal</code>&nbsp;的类型,我们首先需要通过&nbsp;<code>typeof</code>&nbsp;得到其类型,再使用 TypeScript 内置的&nbsp;<code>InstanceType</code>&nbsp;工具类型来获取其实例类型:</p><div class="widget-codetool" style="display: none;">
      <div class="widget-codetool--inner">
                  <button type="button" class="btn btn-dark rounded-0 sflex-center copyCode" data-toggle="tooltip" data-placement="top" data-clipboard-text="<!-- App.vue -->
                  <script setup lang=&quot;ts&quot;>
                  import MyModal from './MyModal.vue'
                  
                  const modal = ref<InstanceType<typeof MyModal> | null>(null)
                  
                  const openModal = () => {
                    modal.value?.open()
                    }
                    </script>" aria-label="复制" data-bs-original-title="复制">
                      <i class="far fa-copy"></i>
          </button>
</div>
      </div><pre class="hljs language-dust"><span class="language-xml"><span class="hljs-comment">&lt;!-- App.vue --&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">setup</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"ts"</span>&gt;</span><span class="language-javascript">
      <span class="hljs-keyword">import</span> <span class="hljs-title class_">MyModal</span> <span class="hljs-keyword">from</span> <span class="hljs-string">'./MyModal.vue'</span>
      
      <span class="hljs-keyword">const</span> modal = ref&lt;<span class="hljs-title class_">InstanceType</span>&lt;<span class="hljs-keyword">typeof</span> <span class="hljs-title class_">MyModal</span>&gt; | <span class="hljs-literal">null</span>&gt;(<span class="hljs-literal">null</span>)
      
      <span class="hljs-keyword">const</span> <span class="hljs-title function_">openModal</span> = (<span class="hljs-params"></span>) =&gt; </span></span><span class="hljs-template-variable">{
        modal.value?.open()
        }</span><span class="language-xml">
        <span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span></span></pre><p>注意,如果你想在 TypeScript 文件而不是在 Vue SFC 中使用这种技巧,需要开启 Volar 的&nbsp;<a target="_blank" href="https://link.segmentfault.com/?enc=qC92WLHAtq3OQDb%2BYhxm9A%3D%3D.K3cISPRXZ9Q12vnc4T0DmSmDnCwOhhCnl9PN7PMc7kTtBwmFslmvxzzDuIKusrjhz3x6enpvuCpZmTVO8UTPKFH5PSU8bBeh34Z%2Bnt2Fh1k%3D">Takeover 模式</a></p><blockquote>本文由博客一文多发平台 <a target="_blank" href="https://link.segmentfault.com/?enc=IUcUGPgiEandplR9YxyF9g%3D%3D.QuYlILF6rqWCHicGtwgVUVcwJevM1goDKTwGmWeMsiR8XFNF3IH5e0%2BM8JT7zuOp">OpenWrite</a> 发布!</blockquote>