Skip to content

组件传参

组件间的数据传递,分为很多种方法,各方法的适用情况大致分三种:父组件传递给子组件、子组件给父组件、全局资源。

父组件给子组件

插槽

这是一个一次性的方法,复用了 html 的语法即可传递传递内容给子组件。

vue
<template>
  <div class="alert-box">
    <strong>This is an Error for Demo Purposes</strong>
    <slot /> // [!code highlight]
  </div>
</template>

<style scoped>
.alert-box {
  /* ... */
}
</style>
html
<AlertBox>
  Something bad happened.
</AlertBox>

插槽可设置默认内容

html
<button type="submit">
  <slot>
    Submit <!-- 默认内容 -->
  </slot>
</button>

具名插槽

组件里面是这样的

html
<div class="container">
  <header>
    <slot name="header"></slot>
  </header>
  <main>
    <slot></slot>
  </main>
  <footer>
    <slot name="footer"></slot>
  </footer>
</div>

父组件是这样的用的:

html
<BaseLayout>
  <template v-slot:header>
    <!-- header 插槽的内容放这里 -->
  </template>
</BaseLayout>

v-slot 有对应的简写 #,因此 <template v-slot:header> 可以简写为 <template #header>。其意思就是“将这部分模板片段传入子组件的 header 插槽中”。

具名插槽图示

若未指定名字,插槽的默认名字其实是default。当子组件内只有一个插槽时,该组件被使用时可省略 <template #default></template>,也就是最简易的用法。但是,当组件内有多个插槽时,可使用 <template #default></template> 插入数据到所有未被赋值的顶层插槽。

条件插槽

先理解一个参数 $slots,这是在具有插槽的组件内,用于表示父组件传入的插槽内容对象。

可以结合其他指令使用,比如结合 v-if 进行条件渲染,当有值时,对应组件才会被渲染:

vue
<template>
  <div class="card">
    <div v-if="$slots.header" class="card-header">
      <slot name="header" />
    </div>
    
    <div v-if="$slots.default" class="card-content">
      <slot />
    </div>
    
    <div v-if="$slots.footer" class="card-footer">
      <slot name="footer" />
    </div>
  </div>
</template>

Props 声明(类似函数声明)

在调用组件时,不是按照后端语言的参数位置为定位,而是需要在传递数据(参数)时使用键值对的写法。

用法:子组件使用 defineProps() 函数。

vue
<template>
    <ul>
  		<li v-for="item in list" :key="item.id">
    		{{item.name}}
    	</li>
    </ul>
</template>
<script setup>
	const props = defineProps(['list'])	
</script>

父组件:

vue
<template>
  <div>
    <ToDoMain :list="list"/>	// [!code highlight]
  </div>
</template>
<script setup>
  import ToDoMain from './components/ToDoMain.vue';
  import { ref } from 'vue';
  const list=ref([
    {id:1,name:'晨练',status:false},
    {id:2,name:'睡觉',status:false},
    {id:3,name:'吃早餐',status:false},
    {id:4,name:'学习',status:false},
  ])
</script>

可以这样指定需要的数据类型:

js
const props = defineProps({
  foo: String
})

provideinject

父组件:使用provide给后代组件提供数据,接收方可以是子组件也可以是孙子组件,可以同时有多个接收方。

js
  import ToDoMain from './components/ToDoMain.vue';
  import { ref,provide } from 'vue';
  const list=ref([
    {id:1,name:'晨练',status:false},
    {id:2,name:'睡觉',status:false},
    {id:3,name:'吃早餐',status:false},
    {id:4,name:'学习',status:false},
  ])
  provide('list',list)	

子组件:使用inject()注入函数。

vue
<template>
    <ul>
  		<li v-for="item in todoList" :key="item.id">	// [!code highlight]
    		{{item.name}}
    	</li>
    </ul>
</template>
<script setup>
  import { ref,inject } from 'vue'
  const todoList = inject('list')	
</script>

属性透传(继承)

懒得记了,看这里:https://cn.vuejs.org/guide/components/attrs.html#attribute-inheritance

子组件给父组件

组件事件

子组件:使用defineEmits('事件名')函数和 v-on 内联函数$emit('事件名',被监听变量)创建可被监听的事件。

vue
<template>
    <ul>
      <li v-for="item in list" :key="item.id">
        <div>        
          <input type="checkbox" class="toggle" v-model="item.status" :id="item.id">
          <label :for="item.id">{{item.name}}</label>
          <button @click="$emit('todoDelete',item.id)"></button>	// [!code highlight]
        </div>
      </li>
    </ul>
</template>
<script setup>
    import { ref } from 'vue'
    const props = defineProps(['list'])
    const emit = defineEmits(['todoDelete'])	
</script>

父组件:同样使用 v-on 或 @ 监听自定义组件事件。

vue
<template>
  <div>
    <ToDoMain :list="list" @todo-delete="todoDelete"/>	// [!code highlight]
  </div>
</template>
<script setup>
  import ToDoMain from './components/ToDoMain.vue';
  import { ref,provide } from 'vue';
  const list=ref([
    {id:1,name:'晨练',status:false},
    {id:2,name:'睡觉',status:false},
    {id:3,name:'吃早餐',status:false},
    {id:4,name:'学习',status:false},
  ])
  // [!code highlight: 4]
  const todoDelete = (id) => {	// 可同时使用形参接收子组件传来的数据。
    list.value=list.value.filter(item=>item.id!==id)
  }
  const add=(name)=>{
    list.value.push({
      id:list.value.length+1,
      name,
      status:false
    })
  }
</script>

作用域插槽

其实就是将 props 作为插槽传递,与 Props 不同的是,这是子组件将数据传递给父组件。

子组件ChildComponent.vue:

vue
<template>
  <div>
    <h2>子组件</h2>
    <!-- 在插槽中传递数据 -->
    <slot :user="user" :age="age"></slot>  // [!code highlight]
  </div>
</template>

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

const user = ref('张三')	
const age = ref(18)
</script>

父组件ParentComponent.vue:

vue
<template>
  <div>
    <h1>父组件</h1>
    <ChildComponent>	// [!code highlight:7]
      <!-- 使用v-slot接收子组件传递过来的数据 -->
      <template v-slot="slotProps">
        <p>从子组件接收到的用户: {{ slotProps.user }}</p>
        <p>从子组件接收到的年龄: {{ slotProps.age }}</p>
      </template>

      <!-- 也可以解构赋值
      <template v-slot="{ user, age }">
        <p>从子组件接收到的用户: {{ user }}</p>
        <p>从子组件接收到的年龄: {{ age }}</p>
      </template>
	  -->

    </ChildComponent>
  </div>
</template>

<script setup>
import ChildComponent from './ChildComponent.vue'
</script>