Vue3 组件传值

312 阅读2分钟

父子组件传值

父组件通过v-bind绑定一个数据,然后子组件通过defineProps接受传过来的值

<template>
    <div class="layout">
        <Menu v-bind:data="data" title="我是标题"></Menu>
        <div class="layout-right">
            <Header></Header>
            <Content></Content>
        </div>
    </div>
</template>
 
<script setup lang="ts">
import Menu from './Menu/index.vue'
import Header from './Header/index.vue'
import Content from './Content/index.vue'
import { reactive } from 'vue';
 
const data = reactive<number[]>([1, 2, 3])
</script>
  • 如上述代码,给Menu组件 传递了一个 title字符串类型是不需要v-bind
  • 传递非字符串类型需要加v-bind  简写 冒号

子组件接受值

  • 通过defineProps 来接受 defineProps是无须引入的直接使用即可(setup语法糖)
  • 如果我们使用的TypeScript可以使用传递字面量类型的纯类型语法做为参数
<template>
    <div class="menu">
        菜单区域 父组件传值:{{ title }}
        <div>父组件传值:{{ data }}</div>
    </div>
</template>
 
<script setup lang="ts">
defineProps<{
    title:string,
    data:number[]
}>()
</script>

或者

<template>
    <div class="menu">
        菜单区域 父组件传值:{{ title }}
        <div>父组件传值:{{ data }}</div>
    </div>
</template>
 
<script setup lang="ts">
type Props = {
  title: string,
  data: number[]
}
defineProps<Props>()
</script>
  • TS 特有的默认值方式
  • withDefaults是个函数也是无须引入开箱即用接受一个props函数第二个参数是一个对象设置默认值
type Props = {
    title?: string,
    data?: number[]
}
withDefaults(defineProps<Props>(), {
    title: "张三",
    data: () => [1, 2, 3]
})

如果你使用的不是TS,还是vue2的写法

defineProps({
    title:{
        default:"",
        type:string
    },
    data:Array
})

image.png

子组件向父组件传值是通过defineEmits派发一个事件

  • 我们在子组件绑定了一个click 事件 然后通过defineEmits 注册了一个自定义事件
  • 点击click 触发 emit 去调用我们注册的事件 然后传递参数
<template>
    <div class="menu">
        菜单区域 父组件传值:{{ title }}
        <div>父组件传值:{{ data }}</div>
        <div>
          <button @click="handleClick">派发</button>
        </div>
    </div>
</template>
<script setup lang="ts">
import { reactive } from 'vue'
const list = reactive<number[]>([1,1,1])
type Props = {
  title: string,
  data: number[]
}
// 接收父组件传值
defineProps<Props>()

// 子组件向父组件传值
/**
 * @description: 
 * @params [自定义事件名称]
 * @return {*} 
 */
const emit = defineEmits(['selfClick'])
const handleClick = () => {
  emit('selfClick', list, false)
}
</script>

父组件

<template>
    <div class="layout">
        <Menu @selfClick="getList" v-bind:data="data" title="我是标题"></Menu>
        <div class="layout-right">
            <Header></Header>
            <Content></Content>
        </div>
    </div>
</template>
<script setup lang="ts">
import Menu from './Menu/index.vue'
import Header from './Header/index.vue'
import Content from './Content/index.vue'
import { reactive } from 'vue';
 
const data = reactive<number[]>([1, 2, 3])
const getList = (list:number[], flag:number) => {
    console.log(list, '我是子组件中传递过来的值', flag)
}
</script>

获取子组件实例

<template>
    <div class="layout">
        <Menu ref="menus" @selfClick="getList" v-bind:data="data" title="我是标题"></Menu>
    </div>
</template>
<script setup lang="ts">
import { reactive, ref } from 'vue';
const menus = ref(null) // menus与定义的ref值一样
console.log(menus.value) // 获取子组件实例
// 然后打印menus.value 发现没有任何属性
</script>
  • 这时候父组件想要读到子组件的属性可以在子组件中通过defineExpose暴露,defineExpose是个函数也是无须引入开箱即用
  • 在子组件中
const list = reactive<number[]>([1,1,1])
// 子组件向外暴露
defineExpose({
  list
})

父组件接收

type SonList = {
    list: number[],
}
let sonList = menus.value as unknown as SonList
/* 
    在开发中经常会遇到类型定义的不太好,需要用 as 进行断言的情况,简单来看,可以直接用 as any 解决几乎所有的 ts 类型问题

    但不利于后续的维护,维护者可能并不知道被 as any 的目标应该是什么类型,用 as unknown as 代替可以解决该问题,而且能看到明确的类型和具体的格式
*/
console.log(sonList.list, '打印子组件实例上向外暴露的值') // 获取子组件实例

配置递归组件

原理跟我们写js递归是一样的 自己调用自己 通过一个条件来结束递归 否则导致内存泄漏

  • 案例递归树
  • 在父组件配置数据结构 数组对象格式 传给子组件
<template>
  <div>
    <Tree @selfClick="getList" :data="data"></Tree>
  </div>
</template>
<script setup lang="ts">
import { reactive } from 'vue';
import Tree from './components/Tree/index.vue'
type TreeList = {
  name: string;
  icon?: string;
  children?: TreeList[] | [];
}
const data = reactive<TreeList[]>([
  {
    name: "no.1",
    children: [
      {
        name: "no.1-1",
        children: [
          {
            name: "no.1-1-1",
          },
        ],
      },
    ],
  },
  {
    name: "no.2",
    children: [
      {
        name: "no.2-1",
      },
    ],
  },
  {
    name: "no.3",
  },
])

const getList = (items: TreeList) => {
  console.log(items, '子组件传递过来的值')
}
</script>

子组件接收值

  • TreeItem 其实就是当前组件自身 通过import 把自身又引入了一遍 如果他没有children 了就结束
<template>
    <div style="padding-left:10px;" class="tree">
    <div style="text-align:left;" :key="index" v-for="(item,index) in data">
      <div @click.stop='clickItem(item)'>{{item.name}}
    </div>
    <!-- 向组件内部派发selfClick -->
    <TreeItem @selfClick='clickItem' v-if='item?.children?.length' :data="item.children"></TreeItem>
  </div>
  </div>
</template>
<script setup lang="ts">
// import TreeItem from './index.vue'
type TreeList = {
  name: string;
  icon?: string;
  children?: TreeList[] | [];
}
type Props<T> = {
    data?:T[] | []
}
defineProps<Props<TreeList>>()

const emit = defineEmits(['selfClick'])

const clickItem = (item: TreeList) => {
    // console.log(item, '222')
    emit('selfClick', item)
}
</script>

<!-- 不带setup -->
<script lang="ts">
    // 向外暴露组件名字
    export default {
        name: "TreeItem"
    }
</script>

效果

image.png

image.png