一、前言

实际开发中,都是用单文件组件,那组件之间传值和调用方法是最常见的功能。
传参的方式有很多,比如props、emit、provide和inject。
在学习组件之间传值之前,需要了解下模板ref。

二、模板ref

<input ref="input">

ref 是一个特殊的 attribute,它允许我们在一个特定的 DOM 元素或子组件实例被挂载后,获得对它的直接引用。

1 访问模板ref

我们给一个组件标签赋值ref属性,我们还需要声明一个同名的 ref变量。
举例:在查询列表页面打开编辑页面。
在这里插入图片描述

  • 列表页面是DictData.vue
  • 编辑页面是DictDataEdit.vue
  • <dict-data-edit> 的ref=“dictDataEditDialog”
  • 在<script setup >中定义的组件ref引用也必须是dictDataEditDialog
</template>
      <dict-data-edit 
         v-model:visible="viewState.editDialog.visible"
         ref="dictDataEditDialog"
         @close="closeEdit"
      />
</template>

<script setup lang="ts">
import {nextTick, reactive, ref} from "vue";
//这了的变量名称必须和<dict-data-edit>的ref值一样
const dictDataEditDialog = ref<any>();

const viewState = reactive({
  editDialog: {visible: false} as Dialog
});

function handleEdit(id: string) {
  viewState.editDialog.visible = true;
  nextTick(() => {
  	//因为是ref创建的,所以得加.value
    dictDataEditDialog.value.initEditData(id);
  })
}
</script>

三、父给子传值

1 子组件使用prop声明接收的参数

子组件(编辑页面)需要接收一个参数:visible,这个visible是由父组件(列表页面)传给它的,用来控制子组件的是否显示。

子组件(编辑页面)声明了一个visible参数,控制dialog的是否显示。

<script lang="ts">
export default {
  name: "DictTypeEdit"
}
</script>

<template>
  <el-dialog
      :model-value="visible"
      width="500px"
  />
</template>

<script setup lang="ts">
//prop 可以使用 defineProps() 宏来定义
const props = defineProps(['visible'])
</script>

2 父组件使用v-model传递值

父组件(列表页面) 用v-model:visible传值

<template>
    <dict-type-edit v-model:visible="viewState.editDialog.visible"
                    ref="dictTypeEditDialog"
    />
</template>

<script setup lang="ts">
import {reactive} from 'vue';

const dictDataEditDialog = ref<any>();
const viewState = reactive({
  editDialog: {visible: false} as Dialog
});
</script>

四、父调子的方法

1 子组件定义方法,并暴露它

  • 使用defineExpose()暴露方法,别的组件才能调用
<script lang="ts">
export default {
  name: "DictTypeEdit"
}
</script>

<template>
  <el-dialog
      :model-value="visible"
      width="500px"
  >
  省略Form表达的代码
  </el-dialog>
</template>

<script setup lang="ts">
import {reactive} from "vue";
import {getDictTypeById} from '@/api/system/dictType'
import {DictType} from "@/types";
//注意:使用defineExpose()暴露方法,别的组件才能调用
defineExpose({initEditData});

let dataState = reactive({
  dictType: {
    state: '1'
  } as DictType
});

//子组件定义了一个方法用来初始化数据
function initEditData(id: string) {
  dataState.dictType.id = id;
  if (id) {
    getDictTypeById(id).then(({data}) => {
      dataState.dictType = data
    })
  }
}
</script>

2 父组件调用子组件的方法

  • 父组件在handleEdit方法里调用了子组件的方法
</template>
    <dict-type-edit
         v-model:visible="viewState.editDialog.visible"
         ref="dictTypeEditDialog"
      />
</template>

<script setup lang="ts">
import {nextTick, reactive, ref} from "vue";
//这了的变量名称必须和<dict-type-edit>的ref值一样
const dictTypeEditDialog= ref<any>();

const viewState = reactive({
  editDialog: {visible: false} as Dialog
});

function handleEdit(id: string) {
  viewState.editDialog.visible = true;
  nextTick(() => {
  	//因为是ref创建的,所以得加.value,调用子组件方法
    dictDataEditDialog.value.initEditData(id);
  })
}
</script>

五、子组件调用父组件方法

1 在父组件中定义方法,并在子组件标签上指定调用

  • 在父组件中的<dict-type-edit>添加@close自定义方法,它执行父组件自己的closeEdit(val: any)方法
  • closeEdit(val: any),这any就子组件传来的值
</template>	
    <dict-type-edit
         v-model:visible="viewState.editDialog.visible"
         ref="dictTypeEditDialog"
         @close="closeEdit"
      />
</template>

<script setup lang="ts">
import {nextTick, reactive, ref} from "vue";
//这了的变量名称必须和<dict-type-edit>的ref值一样
const dictTypeEditDialog= ref<any>();

const viewState = reactive({
  editDialog: {visible: false} as Dialog
});

function closeEdit(val: any) {
  viewState.editDialog.visible = false;
}
</script>

2 子组件通过emit调用父组件的方法,并传值

  • 子组件用defineEmits声明要调用父组件的方法名
  • 用emit()实际调用父组件的方法
  • 注意:defineEmits声明的是<dict-type-edit @close=“closeEdit”>的close,而不是closeEdit
  • emit(‘close’, “子组件传给父组件的值”),close是方法名,第二个参数就是子组件传给父组件的值
<script lang="ts">
export default {
  name: "DictTypeEdit"
}
</script>

<template>
  <el-dialog
      :model-value="visible"
      width="500px"
      close-on-click-modal=false
      @close="cancel"
  >
  dialog关闭时,调用cancel方法
  省略Form表达的代码
  </el-dialog>
</template>

<script setup lang="ts">
import {onMounted, reactive, ref} from "vue";
import {getDictTypeById, saveDictType} from '@/api/system/dictType'
import {DictType} from "@/types";
import {ElForm, ElMessage} from "element-plus";

const props = defineProps(['visible'])
//用defineEmits声明要调用父组件的方法
//注意:这个是父组件<dict-type-edit>标签上的@close
//而不是父组件自己定义的方法closeEdit()
const emit = defineEmits(['close'])

let dataState = reactive({
  dictType: {
    state: '1'
  } as DictType
});

function cancel() {
  //执行调用<dict-type-edit @close="closeEdit">的close
  emit('close', "子组件传给父组件的值");
}
</script>

六、子给父传值,配合v-model:使用

这里用场景的分页插件做介绍,分页插件可以修改页号,和每页的大小
在这里插入图片描述

1 父组件中使用子组件<pagination>

<template>
    <pagination
        v-if="dataState.total > 0"
        :total="dataState.total"
        v-model:page="dataState.queryParams.pageSearch.pageNumber"
        v-model:limit="dataState.queryParams.pageSearch.pageSize"
        @pagination="handleQuery"
    ></pagination>
</template>

<script setup lang="ts">
import {reactive, ref} from 'vue';

const dataState = reactive({
  total: 10,
  //查询列表页面的数据
  queryParams: {
    pageSearch: {
      page: true,
      pageNumber: 1,
      pageSize: 10
    }
  } as DictTypePageQueryParam
});

const viewState = reactive({
  loading: false,
  editDialog: {visible: false} as Dialog
});

function handleQuery() {
	//省略
}
</script>

2 子组件用emit(‘update:limit’, value),触发更新值

子组件中,有如下代码

const emit = defineEmits(['update:page', 'update:limit', 'pagination']);

当limit的值有变化时,emit(‘update:limit’, value)就会更新父组件上limit的值

//每页大小
const pageSize = computed<number | undefined>({
  get: () => props.limit,
  set: value => {
    emit('update:limit', value)
  }
})
<template>
  <div :class="{ hidden: hidden }" class="pagination-container">
    <el-pagination
        v-model:current-page="currentPage"
        v-model:page-size="pageSize"
        :layout="layout"
        :page-sizes="pageSizes"
        :total="total"
        @size-change="handleSizeChange"
        @current-change="handleCurrentChange"
    >
    </el-pagination>
  </div>
</template>

<script setup lang="ts">
import {computed, PropType} from "vue";

const props = defineProps({
  total: {
    required: true,
    type: Number as PropType<number>,
    default: 0
  },
  page: {
    type: Number,
    default: 1
  },
  limit: {
    type: Number,
    default: 20
  },
  pageSizes: {
    type: Array as PropType<number[]>,
    default() {
      return [10, 20, 30, 50]
    }
  },
  //分页组件布局
  layout: {
    type: String,
    default: 'total, sizes, prev, pager, next, jumper'
  },
  hidden: {
    type: Boolean,
    default: false
  }
});

const emit = defineEmits(['update:page', 'update:limit', 'pagination']);

//当前页号
const currentPage = computed<number | undefined>({
  get: () => props.page,
  set: value => {
    emit('update:page', value)
  }
});

//每页大小
const pageSize = computed<number | undefined>({
  get: () => props.limit,
  set: value => {
    emit('update:limit', value)
  }
})

function handleSizeChange(val: number) {
  emit('pagination', {page: currentPage, limit: val})
  if (props.autoScroll) {
    scrollTo(0, 800)
  }
}
</script>

七、provide和inject

当组件的嵌套层级比较深的时候,用props就不合适了。
对于这种情况,我们可以使用一对 provide 和 inject。无论组件层次结构有多深,父组件都可以作为其所有子组件的依赖提供者。

  • 父级组件使用provide向下进行传递数据;
  • 子级组件使用inject来获取上级组件传递过来的数据;
  • provide可以提供值和方法。

父组件provide

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

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

子组件inject

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

const message = inject('message', '这是默认值')
</script>
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐