跳转到内容

组件

刀刀

3/20/2025

0 字

0 分钟

组件通讯

父传子

参数

  1. type :类型限制
  2. default :默认值
  3. validator :校验规则
  4. required :是否必传
  • 父组件

    vue
    <script setup>
    import { ref } from "vue";
    import Son from "./Son.vue";
    
    const money = ref(1400)
    </script>
    
    <template>
      <div>daodao{{money}}</div>
      <Son :money="money"></Son>
    </template>
    
    <style>
    
    </style>
  • 子组件

    vue
    <script setup>
    // defineProps({
    //   money: Number
    // })
    defineProps({
      money: { 
        type: Number,
        required: true,
        validator(v) {
            return ['a', 'b'].includes(v)
        } // 校验
      }
    })
    </script>
    
    <template>
      <div>😀{{money}}</div>
    </template>
    
    <style>
    
    </style>

事件

  • 父组件

    vue
    <child @click="show"></child>
  • 子组件

    vue
    <div v-bind="$attrs"></div>

子传父

  • 子组件

    vue
    <script setup>
    defineProps({
      money: { 
        type: Number,
        required: true
      }
    })
    
    const emit = defineEmits(['changeMoney'])
    
    const change = () => {
      emit('changeMoney', 10)
    }
    </script>
    
    <template>
      <div>😀{{money}}</div>
      <button @click="change">花钱</button>
    </template>
  • 父组件

    vue
    <script setup>
    import { ref } from "vue";
    import Son from "./Son.vue";
    
    const money = ref(1400)
    
    const changeMoney = (num) => {
      money.value = money.value -= num
    }
    </script>
    
    <template>
      <div>daodao{{money}}</div>
      <Son @changeMoney="changeMoney" :money="money"></Son>
    </template>

父组件使用子组件数据

setup 语法糖的变量不允许跨组件使用,想要使用子组件的变量需要子组件通过 defineExpose 导出允许父组件使用的变量和方法。

透传 Attributes

Attributes 继承

“透传 attribute”指的是传递给一个组件,却没有被该组件声明为 propsemits 的 attribute 或者 v-on 事件监听器。最常见的例子就是 classstyleid。透传的 attribute 会自动添加到子组件的根元素上。例如,子组件模版如下:

vue
<button>
    click me
</button>

父组件使用了这个子组件,并传入了 classidclick 事件等:

vue
<MyButton class="my-button" id="my-button" @click="clickFn"></MyButton>

最后渲染出 DOM 的结果是:

html
<button class="my-button" id="my-button">
    click me
</button>

这里虽然子组件没有通过 props 接收类、id,但是由于透传机制,自动传透到子组件的根元素上。

合并

如果一个子组件在继承透传的 Attributes 时自己也有该 Attributes ,则会和从父组件上继承的值合并,例如,改造一下之前的子组件:

vue
<button class="son-class">
    click me
</button>

则继承之后渲染出来的 DOM 元素如下:

vue
<button class="son-class my-button" id="my-button">
    click me
</button>

禁用继承

如果不想要一个组件自动地继承 attribute,可以在组件选项中设置 inheritAttrs: false

如果使用了 <script setup>,需要一个额外的 <script> 块来书写这个选项声明:

vue
<script>
// 使用普通的 <script> 来声明选项
export default {
  inheritAttrs: false
}
</script>

<script setup>
// ...setup 部分逻辑
</script>

最常见的需要禁用 attribute 继承的场景就是 attribute 需要应用在根节点以外的其他元素上。通过设置 inheritAttrs 选项为 false,可以完全控制透传进来的 attribute 被如何使用。

这些透传进来的 attribute 可以在模板的表达式中直接用 $attrs 访问到。

vue
<span>Fallthrough attribute: {{ $attrs }}</span>

这个 $attrs 对象包含了除组件所声明的 propsemits 之外的所有其他 attribute,例如 classstylev-on 监听器等等。

有几点需要注意:

  • 和 props 有所不同,透传 attributes 在 JavaScript 中保留了它们原始的大小写,所以像 foo-bar 这样的一个 attribute 需要通过 $attrs['foo-bar'] 来访问。
  • @click 这样的一个 v-on 事件监听器将在此对象下被暴露为一个函数 $attrs.onClick

现在再次使用一下前面子组件例子。有时候我们可能为了样式,需要在 <button> 元素外包装一层 <div>

vue
<div class="btn-wrapper">
  <button class="son-class">click me</button>
</div>

我们想要所有像 classv-on 监听器这样的透传 attribute 都应用在内部的 <button> 上而不是外层的 <div> 上。我们可以通过设定 inheritAttrs: false 和使用 v-bind="$attrs" 来实现:

vue
<div class="btn-wrapper">
  <button class="btn" v-bind="$attrs">click me</button>
</div>

小提示

没有参数的 v-bind 会将一个对象的所有属性都作为 attribute 应用到目标元素上。

多根节点的 Attributes 继承

如果子组件不是单节点,而是多节点,且没有自动绑定 $attrs ,如下:

vue
<header></header>
<main></main>
<footer></footer>

父组件在透传 attributes 时就会报错:

vue
<Son id="father-id"></Son>

因为 Vue 不清楚要把 attribute 透传给谁。因此需要显示绑定 $attrs ,如下:

vue
<header>...</header>
<main v-bind="$attrs">...</main>
<footer>...</footer>

JavaScript 访问透传 Attributes

如果需要,可以引入 Vue 里面的 useAttrs() API 来访问一个组件的所有透传 attribute:

vue
<script setup>
import { useAttrs } from 'vue'

const attrs = useAttrs()
</script>

如果没有使用 <script setup>attrs 会作为 setup() 上下文对象的一个属性暴露:

js
export default {
  setup(props, ctx) {
    // 透传 attribute 被暴露为 ctx.attrs
    console.log(ctx.attrs)
  }
}

需要注意的是,虽然这里的 attrs 对象总是反映为最新的透传 attribute,但它并不是响应式的 (考虑到性能因素)。不能通过侦听器去监听它的变化。如果需要响应性,可以使用 prop。或者也可以使用 onUpdated() 使得在每次更新时结合最新的 attrs 执行副作用。

模板 ref 的使用

获取元素

  1. 创建一个空的 ref

    js
    const dao = ref(null)
  2. 模板中建立关联,模板挂在完毕后,自动把DOM节点的内存地址传给 ref

    html
    <div ref="dao">daodao</div>
  3. 组件挂载完毕后,获取 DOM 节点了

    js
    onMounted(()=>{
      console.log(dao);
    })
vue
<script setup>
import { onMounted, ref } from "vue";

const dao = ref(null)

onMounted(()=>{
  console.log(dao);
})
</script>

<template>
  <div ref="dao">daodao</div>
</template>

v-for多元素

当在 v-for 中使用模板引用时,对应的 ref 中包含的值是一个数组,它将在元素被挂载后包含对应整个列表的所有元素:

vue
<script setup>
import { ref, onMounted } from 'vue'

const list = ref([
  /* ... */
])

const itemRefs = ref([])

onMounted(() => console.log(itemRefs.value))
</script>

<template>
  <ul>
    <li v-for="item in list" ref="itemRefs">
      {{ item }}
    </li>
  </ul>
</template>

注意

ref 数组并不保证与源数组相同的顺序。

操作组件

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

  • 父组件

    vue
    <script setup>
    import { onMounted, ref } from "vue";
    import Son from "./Son.vue";
    
    const son = ref(null)
    
    onMounted(()=>{
      console.log(son);
    })
    </script>
    
    <template>
      <Son ref="son"></Son>
    </template>
  • 子组件

    vue
    <script setup>
    import { ref } from 'vue';
    
    const count = ref(0)
    defineExpose({
      count
    })
    </script>
    
    <template>
      <button @click="changeMoney">花钱</button>
    </template>

toRefs 函数

如果对一个响应数据,进行解构 或者 展开,会丢失他的响应式特性!

  1. reactive/ref的响应式功能是赋值给对象的, 如果给对象解构或者展开, 会让数据丢失响应式的能力
  2. 使用 toRefs 可以保证该对象展开的每个属性都是响应式的
vue
<template>
  <div>{{ money }}</div>
  <div>{{ car }}</div>
  <div>{{ name }}</div>
  <button @click="money++">改值</button>
</template>

<script setup>
import { reactive, ref, toRefs } from 'vue'
const user = ref({
  name: 'zs',
  age: 18,
})
const { name, age } = toRefs(user.value)
</script>

动态组件

使用

子组件微信:

vue
<template>
	<div>
        微信
    </div>
</template>

子组件支付宝:

vue
<template>
	<div>
        支付宝
    </div>
</template>

父组件:

vue
<template>
	<div v-for="item in list" :key="item.type" @click="componentType = item.type">
        {{item.name}}
    </div>

	<component :is="componentType" />
</template>

<script setup>
import Wexin from './Weixin.vue'
import Zhifubao from './Zhifubao.vue'
import { ref } from 'vue'
    
const list = ref([
    {name: '微信支付', type: 'wexin'},
    {name: '支付宝支付', type: 'zhifubao'},
])

const componentType = ref('')
</script>

注意:

组件引入名称需要与变量一致,名称一致时就会显示对应的组件。

动态切换

有些场景会需要在两个组件间来回切换,比如 Tab 界面

上面的例子是通过 Vue 的 <component> 元素和特殊的 is attribute 实现的:

vue
<!-- currentTab 改变时组件也改变 -->
<component :is="tabs[currentTab]"></component>

在上面的例子中,被传给 :is 的值可以是以下几种:

  • 被注册的组件名
  • 导入的组件对象

你也可以使用 is attribute 来创建一般的 HTML 元素。

当使用 <component :is="..."> 来在多个组件间作切换时,被切换掉的组件会被卸载。我们可以通过 KeepAlive 组件强制被切换掉的组件仍然保持“存活”的状态。

DOM 模板解析注意事项

如果你想在 DOM 中直接书写 Vue 模板,Vue 则必须从 DOM 中获取模板字符串。由于浏览器的原生 HTML 解析行为限制,有一些需要注意的事项。

注意

请注意下面讨论只适用于直接在 DOM 中编写模板的情况。如果你使用来自以下来源的字符串模板,就不需要顾虑这些限制了:

  • 单文件组件
  • 内联模板字符串 (例如 template: '...')
  • <script type="text/x-template">

大小写区分

HTML 标签和属性名称是不分大小写的,所以浏览器会把任何大写的字符解释为小写。这意味着当你使用 DOM 内的模板时,无论是 PascalCase 形式的组件名称、camelCase 形式的 prop 名称还是 v-on 的事件名称,都需要转换为相应等价的 kebab-case (短横线连字符) 形式:

js
// JavaScript 中的 camelCase
const BlogPost = {
  props: ['postTitle'],
  emits: ['updatePost'],
  template: `
    <h3>{{ postTitle }}</h3>
  `
}
html
<!-- HTML 中的 kebab-case -->
<blog-post post-title="hello!" @update-post="onUpdatePost"></blog-post>

闭合标签

我们在上面的例子中已经使用过了闭合标签 (self-closing tag):

html
<MyComponent />

这是因为 Vue 的模板解析器支持任意标签使用 /> 作为标签关闭的标志。

然而在 DOM 模板中,我们必须显式地写出关闭标签:

html
<my-component></my-component>

这是由于 HTML 只允许一小部分特殊的元素省略其关闭标签,最常见的就是 <input><img>。对于其他的元素来说,如果你省略了关闭标签,原生的 HTML 解析器会认为开启的标签永远没有结束,用下面这个代码片段举例来说:

html
<my-component /> <!-- 我们想要在这里关闭标签... -->
<span>hello</span>

将被解析为:

html
<my-component>
  <span>hello</span>
</my-component> <!-- 但浏览器会在这里关闭标签 -->

元素位置限制

某些 HTML 元素对于放在其中的元素类型有限制,例如 <ul><ol><table><select>,相应的,某些元素仅在放置于特定元素中时才会显示,例如 <li><tr><option>

这将导致在使用带有此类限制元素的组件时出现问题。例如:

html
<table>
  <blog-post-row></blog-post-row>
</table>

自定义的组件 <blog-post-row> 将作为无效的内容被忽略,因而在最终呈现的输出中造成错误。我们可以使用特殊的 is attribute 作为一种解决方案:

html
<table>
  <tr is="vue:blog-post-row"></tr>
</table>

注意

当使用在原生 HTML 元素上时,is 的值必须加上前缀 vue: 才可以被解析为一个 Vue 组件。这一点是必要的,为了避免和原生的自定义内置元素相混淆。

依赖注入

一般情况下,当子子组件需要使用父组件中的数据时,需要父组件与子组件都使用 prop 传递数据,如下图所示:

props逐级传递

这里虽然 <Footer> 组件不需要使用 prop 数据,但是其子组件需要,因此被迫接收传递。这种场景又称之为“prop 逐级透传” 。很显然这种现象不是我们想要看到的,而 provideinject 很好的解决了这个问题。

一个父组件相对于其所有子孙后代组件,会作为依赖提供者,任何后代组件,无论层级有多深,都可以注入使用父组件提供的依赖,如下图所示:

provide

Provide(提供)

要为组件后代提供数据,需要使用到 provide() 函数:

vue
<script setup>
import { provide } from 'vue'

provide(/* 注入名 */ 'message', /* 值 */ 'hello!')
</script>

如果不使用 <script setup>,请确保 provide() 是在 setup() 同步调用的:

js
import { provide } from 'vue'

export default {
  setup() {
    provide(/* 注入名 */ 'message', /* 值 */ 'hello!')
  }
}

provide() 函数接收两个参数。第一个参数被称为注入名,可以是一个字符串或是一个 Symbol。后代组件会用注入名来查找期望注入的值。一个组件可以多次调用 provide(),使用不同的注入名,注入不同的依赖值。

第二个参数是提供的值,值可以是任意类型,包括响应式的状态,比如一个 ref:

js
import { ref, provide } from 'vue'

const count = ref(0)
provide('key', count)

提供的响应式状态使后代组件可以由此和提供者建立响应式的联系。

应用层 Provide

除了在一个组件中提供依赖,我们还可以在整个应用层面提供依赖:

js
import { createApp } from 'vue'

const app = createApp({})

app.provide(/* 注入名 */ 'message', /* 值 */ 'hello!')

在应用级别提供的数据在该应用内的所有组件中都可以注入。这在编写插件时会特别有用,因为插件一般都不会使用组件形式来提供值。

Inject (注入)

要注入上层组件提供的数据,需使用 inject() 函数:

vue
<script setup>
import { inject } from 'vue'

const message = inject('message')
</script>

如果提供的值是一个 ref,注入进来的会是该 ref 对象,而不会自动解包为其内部的值。这使得注入方组件能够通过 ref 对象保持了和供给方的响应性链接。

同样的,如果没有使用 <script setup>inject() 需要在 setup() 内同步调用:

js
import { inject } from 'vue'

export default {
  setup() {
    const message = inject('message')
    return { message }
  }
}

注入默认值

默认情况下,inject 假设传入的注入名会被某个祖先链上的组件提供。如果该注入名的确没有任何组件提供,则会抛出一个运行时警告。

如果在注入一个值时不要求必须有提供者,那么我们应该声明一个默认值,和 props 类似:

js
// 如果没有祖先组件提供 "message"
// `value` 会是 "这是默认值"
const value = inject('message', '这是默认值')

在一些场景中,默认值可能需要通过调用一个函数或初始化一个类来取得。为了避免在用不到默认值的情况下进行不必要的计算或产生副作用,我们可以使用工厂函数来创建默认值:

js
const value = inject('key', () => new ExpensiveClass())

和响应式数据配合使用

当提供 / 注入响应式的数据时,建议尽可能将任何对响应式状态的变更都保持在供给方组件中。这样可以确保所提供状态的声明和变更操作都内聚在同一个组件内,使其更容易维护。

有的时候,我们可能需要在注入方组件中更改数据。在这种情况下更推荐在供给方组件内声明并提供一个更改数据的方法函数:

vue
<!-- 在供给方组件内 -->
<script setup>
import { provide, ref } from 'vue'

const location = ref('North Pole')

function updateLocation() {
  location.value = 'South Pole'
}

provide('location', {
  location,
  updateLocation
})
</script>
vue
<!-- 在注入方组件 -->
<script setup>
import { inject } from 'vue'

const { location, updateLocation } = inject('location')
</script>

<template>
  <button @click="updateLocation">{{ location }}</button>
</template>

最后,如果想确保提供的数据不能被注入方的组件更改,可以使用 readonly() 来包装提供的值。

vue
<script setup>
import { ref, provide, readonly } from 'vue'

const count = ref(0)
provide('read-only-count', readonly(count))
</script>

异步组件

基本用法

在大型项目中,我们可能需要拆分应用为更小的块,并仅在需要时再从服务器加载相关组件。Vue 提供了 defineAsyncComponent 方法来实现此功能:

js
import { defineAsyncComponent } from 'vue'

const AsyncComp = defineAsyncComponent(() => {
  return new Promise((resolve, reject) => {
    // ...从服务器获取组件
    resolve(/* 获取到的组件 */)
  })
})
// ... 像使用其他一般组件一样使用 `AsyncComp`

如你所见,defineAsyncComponent 方法接收一个返回 Promise 的加载函数。这个 Promise 的 resolve 回调方法应该在从服务器获得组件定义时调用。你也可以调用 reject(reason) 表明加载失败。

ES 模块动态导入也会返回一个 Promise,所以多数情况下我们会将它和 defineAsyncComponent 搭配使用。类似 Vite 和 Webpack 这样的构建工具也支持此语法 (并且会将它们作为打包时的代码分割点),因此我们也可以用它来导入 Vue 单文件组件:

js
import { defineAsyncComponent } from 'vue'

const AsyncComp = defineAsyncComponent(() =>
  import('./components/MyComponent.vue')
)

最后得到的 AsyncComp 是一个外层包装过的组件,仅在页面需要它渲染时才会调用加载内部实际组件的函数。它会将接收到的 props 和插槽传给内部组件,所以你可以使用这个异步的包装组件无缝地替换原始组件,同时实现延迟加载。

与普通组件一样,异步组件可以使用 app.component() 全局注册

js
app.component('MyComponent', defineAsyncComponent(() =>
  import('./components/MyComponent.vue')
))

也可以直接在父组件中直接定义它们:

vue
<script setup>
import { defineAsyncComponent } from 'vue'

const AdminPage = defineAsyncComponent(() =>
  import('./components/AdminPageComponent.vue')
)
</script>

<template>
  <AdminPage />
</template>

加载与错误状态

异步操作不可避免地会涉及到加载和错误状态,因此 defineAsyncComponent() 也支持在高级选项中处理这些状态:

js
const AsyncComp = defineAsyncComponent({
  // 加载函数
  loader: () => import('./Foo.vue'),

  // 加载异步组件时使用的组件
  loadingComponent: LoadingComponent,
  // 展示加载组件前的延迟时间,默认为 200ms
  delay: 200,

  // 加载失败后展示的组件
  errorComponent: ErrorComponent,
  // 如果提供了一个 timeout 时间限制,并超时了
  // 也会显示这里配置的报错组件,默认值是:Infinity
  timeout: 3000
})

如果提供了一个加载组件,它将在内部组件加载时先行显示。在加载组件显示之前有一个默认的 200ms 延迟——这是因为在网络状况较好时,加载完成得很快,加载组件和最终组件之间的替换太快可能产生闪烁,反而影响用户感受。

如果提供了一个报错组件,则它会在加载器函数返回的 Promise 抛错时被渲染。你还可以指定一个超时时间,在请求耗时超过指定时间时也会渲染报错组件。

总结

组件之间的通讯方式分为如下几步:

props

props 可以实现父子组件通信,在 vue3 中我们可以通过 defineProps 获取父组件传递的数据。且在组件内部不需要引入 defineProps 方法可以直接使用!

父组件给子组件传递数据

vue
<Child info="我爱祖国" :money="money"></Child>

子组件获取父组件传递数据:方式1

js
let props = defineProps({
  info:{
   type:String,//接受的数据类型
   default:'默认参数',//接受默认数据
  },
  money:{
   type:Number,
   default:0
}})

子组件获取父组件传递数据:方式2

js
let props = defineProps(["info",'money']);

子组件获取到 props 数据就可以在模板中使用了,但是切记 props 是只读的(只能读取,不能修改)

自定义事件

在vue框架中事件分为两种:一种是原生的DOM事件,另外一种自定义事件。

原生DOM事件可以让用户与网页进行交互,比如click、dbclick、change、mouseenter、mouseleave....

自定义事件可以实现子组件给父组件传递数据

原生DOM事件

代码如下:

vue
 <pre @click="handler"> 我是祖国的老花骨朵 </pre>

当前代码级给pre标签绑定原生 DOM 事件点击事件,默认会给事件回调注入 event 事件对象。当然点击事件想注入多个参数可以按照下图操作。但是切记注入的事件对象务必叫做 $event

vue
<div @click="handler1(1,2,3,$event)">我要传递多个参数</div>

在vue3框架click、dbclick、change(这类原生DOM事件),不管是在标签、自定义标签上(组件标签)都是原生DOM事件。

注意

vue2中却不是这样的,在vue2中组件标签需要通过native修饰符才能变为原生DOM事件

自定义事件emit

自定义事件可以实现子组件给父组件传递数据.在项目中是比较常用的。

比如在父组件内部给子组件(Event2)绑定一个自定义事件 xxx

vue
<Event2  @xxx="handler3"></Event2>

Event2 子组件内部触发这个自定义事件

vue
<template>
  <div>
    <h1>我是子组件2</h1>
    <button @click="handler">点击我触发xxx自定义事件</button>
  </div>
</template>

<script setup lang="ts">
let $emit = defineEmits(["xxx"]);
const handler = () => {
  $emit("xxx", "法拉利", "茅台");
};
</script>
<style scoped>
</style>

我们会发现在 script 标签内部,使用了 defineEmits 方法,此方法是vue3提供的方法,不需要引入直接使用。 defineEmits 方法执行,传递一个数组,数组元素即为将来组件需要触发的自定义事件类型,此方执行会返回一个$emit方法用于触发自定义事件。

当点击按钮的时候,事件回调内部调用 $emit 方法去触发自定义事件,第一个参数为触发事件类型,第二个、三个、N个参数即为传递给父组件的数据。

需要注意的是:

vue
<script setup>
	let $emit = defineEmits(["xxx",'click']);
</script>

<Event2  @xxx="handler3" @click="handler"></Event2>

正常说组件标签书写 @click 应该为原生DOM事件,但是如果子组件内部通过 defineEmits 定义就变为自定义事件了。

全局事件总线

全局事件总线可以实现任意组件通信,在vue2中可以根据VM与VC关系推出全局事件总线。

但是在vue3中没有Vue构造函数,也就没有 Vue.prototype. 以及组合式API写法没有 this

那么在Vue3想实现全局事件的总线功能就有点不现实啦,如果想在Vue3中使用全局事件总线功能,可以使用插件mitt实现。

mitt:官网地址:https://www.npmjs.com/package/mitt

v-model

v-model 指令可是收集表单数据(数据双向绑定),除此之外它也可以实现父子组件数据同步。

v-model 实指利用 props[modelValue] 与自定义事件 [update:modelValue] 实现的。

下方代码:相当于给组件Child传递一个props(modelValue) 与绑定一个自定义事件update:modelValue 实现父子组件数据同步:

vue
<Child v-model="msg"></Child>

在vue3中一个组件可以通过使用多个 v-model ,让父子组件多个数据同步,下方代码相当于给组件 Child 传递两个 props 分别是 pageNopageSize ,以及绑定两个自定义事件 update:pageNoupdate:pageSize 实现父子数据同步

vue
<Child v-model:pageNo="msg" v-model:pageSize="msg1"></Child>

useAttrs

在Vue3中可以利用 useAttrs 方法获取组件的属性与事件(包含:原生DOM事件或者自定义事件),次函数功能类似于Vue2框架中$attrs属性与$listeners方法。

比如:在父组件内部使用一个子组件my-button

vue
<my-button type="success" size="small" title='标题' @click="handler"></my-button>

子组件内部可以通过 useAttrs 方法获取组件属性与事件.因此你也发现了,它类似于props,可以接受父组件传递过来的属性与属性值。需要注意如果 defineProps 接受了某一个属性,useAttrs方法返回的对象身上就没有相应属性与属性值。

vue
<script setup lang="ts">
import {useAttrs} from 'vue';
let $attrs = useAttrs();
</script>

ref与$parent

ref,提及到ref可能会想到它可以获取元素的DOM或者获取子组件实例的VC。既然可以在父组件内部通过ref获取子组件实例VC,那么子组件内部的方法与响应式数据父组件可以使用的。

比如:在父组件挂载完毕获取组件实例

父组件内部代码:

vue
<template>
  <div>
    <h1>ref与$parent</h1>
    <Son ref="son"></Son>
  </div>
</template>
<script setup lang="ts">
import Son from "./Son.vue";
import { onMounted, ref } from "vue";
const son = ref();
onMounted(() => {
  console.log(son.value);
});
</script>

但是需要注意,如果想让父组件获取子组件的数据或者方法需要通过defineExpose对外暴露,因为vue3中组件内部的数据对外“关闭的”,外部不能访问

vue
<script setup lang="ts">
import { ref } from "vue";
//数据
let money = ref(1000);
//方法
const handler = ()=>{
}
defineExpose({
  money,
   handler
})
</script>

$parent 可以获取某一个组件的父组件实例VC,因此可以使用父组件内部的数据与方法。必须子组件内部拥有一个按钮点击时候获取父组件实例,当然父组件的数据与方法需要通过 defineExpose 方法对外暴露

vue
<button @click="handler($parent)">点击我获取父组件实例</button>

provide与inject

provide[提供]

inject[注入]

vue3提供两个方法provide与inject,可以实现隔辈组件传递参数

组件组件提供数据:

provide方法用于提供数据,此方法执需要传递两个参数,分别提供数据的key与提供数据value

vue
<script setup lang="ts">
import {provide} from 'vue'
provide('token','admin_token');
</script>

后代组件可以通过inject方法获取数据,通过key获取存储的数值

vue
<script setup lang="ts">
import {inject} from 'vue'
let token = inject('token');
</script>

pinia

pinia官网:https://pinia.web3doc.top/

pinia也是集中式管理状态容器,类似于vuex。但是核心概念没有mutation、modules,使用方式参照官网

slot

插槽:默认插槽、具名插槽、作用域插槽可以实现父子组件通信.

默认插槽:

在子组件内部的模板中书写slot全局组件标签

vue
<template>
  <div>
    <slot></slot>
  </div>
</template>
<script setup lang="ts">
</script>
<style scoped>
</style>

在父组件内部提供结构:Todo即为子组件,在父组件内部使用的时候,在双标签内部书写结构传递给子组件

注意开发项目的时候默认插槽一般只有一个

vue
<Todo>
  <h1>我是默认插槽填充的结构</h1>
</Todo>

具名插槽:

顾名思义,此插槽带有名字在组件内部留多个指定名字的插槽。

下面是一个子组件内部,模板中留两个插槽

vue
<template>
  <div>
    <h1>todo</h1>
    <slot name="a"></slot>
    <slot name="b"></slot>
  </div>
</template>
<script setup lang="ts">
</script>

<style scoped>
</style>

父组件内部向指定的具名插槽传递结构。需要注意v-slot:可以替换为#

vue
<template>
  <div>
    <h1>slot</h1>
    <Todo>
      <template v-slot:a>  //可以用#a替换
        <div>填入组件A部分的结构</div>
      </template>
      <template v-slot:b>//可以用#b替换
        <div>填入组件B部分的结构</div>
      </template>
    </Todo>
  </div>
</template>

<script setup lang="ts">
import Todo from "./Todo.vue";
</script>
<style scoped>
</style>

作用域插槽

作用域插槽:可以理解为,子组件数据由父组件提供,但是子组件内部决定不了自身结构与外观(样式)

子组件Todo代码如下:

vue
<template>
  <div>
    <h1>todo</h1>
    <ul>
     <!--组件内部遍历数组-->
      <li v-for="(item,index) in todos" :key="item.id">
         <!--作用域插槽将数据回传给父组件-->
         <slot :$row="item" :$index="index"></slot>
      </li>
    </ul>
  </div>
</template>
<script setup lang="ts">
defineProps(['todos']);//接受父组件传递过来的数据
</script>
<style scoped>
</style>

父组件内部代码如下:

vue
<template>
  <div>
    <h1>slot</h1>
    <Todo :todos="todos">
      <template v-slot="{$row,$index}">
         <!--父组件决定子组件的结构与外观-->
         <span :style="{color:$row.done?'green':'red'}">{{$row.title}}</span>
      </template>
    </Todo>
  </div>
</template>

<script setup lang="ts">
import Todo from "./Todo.vue";
import { ref } from "vue";
//父组件内部数据
let todos = ref([
  { id: 1, title: "吃饭", done: true },
  { id: 2, title: "睡觉", done: false },
  { id: 3, title: "打豆豆", done: true },
]);
</script>
<style scoped>
</style>