如果一个子组件使用的是选项式 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"><<span class="hljs-name">script</span> <span class="hljs-attr">setup</span>></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"></<span class="hljs-name">script</span>></span></span></pre><p>当父组件通过模板引用获取到了该组件的实例时,得到的实例类型为 <code>{ a: number, b: number }</code> (ref 都会自动解包,和一般的实例一样)。</p><h2 id="item-1">为组件模板引用标注类型</h2><p>有时,你可能需要为一个子组件添加一个模板引用,以便调用它公开的方法。举例来说,我们有一个 <code>MyModal</code> 子组件,它有一个打开模态框的方法:</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="ts">
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"><!-- MyModal.vue --></span>
<span class="hljs-tag"><<span class="hljs-name">script</span> <span class="hljs-attr">setup</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"ts"</span>></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>) => (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"></<span class="hljs-name">script</span>></span></span></pre><p>为了获取 <code>MyModal</code> 的类型,我们首先需要通过 <code>typeof</code> 得到其类型,再使用 TypeScript 内置的 <code>InstanceType</code> 工具类型来获取其实例类型:</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="ts">
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"><!-- App.vue --></span>
<span class="hljs-tag"><<span class="hljs-name">script</span> <span class="hljs-attr">setup</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"ts"</span>></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<<span class="hljs-title class_">InstanceType</span><<span class="hljs-keyword">typeof</span> <span class="hljs-title class_">MyModal</span>> | <span class="hljs-literal">null</span>>(<span class="hljs-literal">null</span>)
<span class="hljs-keyword">const</span> <span class="hljs-title function_">openModal</span> = (<span class="hljs-params"></span>) => </span></span><span class="hljs-template-variable">{
modal.value?.open()
}</span><span class="language-xml">
<span class="hljs-tag"></<span class="hljs-name">script</span>></span></span></pre><p>注意,如果你想在 TypeScript 文件而不是在 Vue SFC 中使用这种技巧,需要开启 Volar 的 <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>