Vue3+Ts<script setup>组合式API的Prop、Emit、Computed、WatchEffect、Watch、Provide/Inject、Ref用法

本文详细介绍了Vue.js中父子组件间的通信方式,包括props传递、emit事件触发、watch监听及计算属性等。同时展示了如何利用ref获取子组件实例,以及通过provide/inject实现跨级组件通信。

一、页面效果图

在这里插入图片描述

二、父组件代码

<template>
  <div class="user-box">
    <div>
      <h2>父组件:</h2>
      <p>propRef:{{ propRef }}</p>
      <p>propReactive:{{ propReactive }}</p>
    </div>

    <div>
      <Child :propRef="propRef" :propReactive="propReactive" @emitValue="getEmitValue" ref="childRef" />
    </div>
  </div>
</template>

<script setup lang="ts">
import { onMounted, ref, reactive, nextTick, toRefs, computed, provide } from 'vue';
import Child from './components/Child.vue'; //引入子组件

const childRef = ref(); //定义子组件的ref

const propRef = ref('我是父组件通过Ref定义的值'); //定义传入子组件的值

const propReactive = reactive({ name: '我是父组件的通过Reactive定义的值', path: 'index' }); //定义传入子组件的对象

const provideValueData = reactive({ name: 'provide', type: 'object' }); //定义provide传入子组件的对象

provide('provideValue', provideValueData); //通过provide发射给子组件

//
function getEmitValue(value) {
  console.log('子组件emit的值', value);
}

onMounted(() => {
  console.log('打印子组件的ref对象', childRef.value);
});
</script>

<style type="text/css" lang="less">
.user-box {
  padding: 20px;
}
</style>

三、子组件代码

<template>
  <div>
    <h2>子组件:</h2>
    <div>
      <h3>Props:</h3>
      <p>propRef:{{ propRef }}</p>
      <p>propReactive:{{ propReactive }}</p>
      <p>propDefault:{{ propDefault }}</p>
    </div>
    <div>
      <h3>Computed:</h3>
      <p>computerMode1:{{ computerMode1 }}</p>
      <p>computerMode2:{{ computerMode2 }}</p>
    </div>
  </div>
  <div>
    <h3>Emit:</h3>
    <a-button type="primary" @click="emitToParent">Emit给父组件</a-button>
  </div>

  <div>
    <h3>Watch:</h3>
    <a-button type="primary" @click="addWatch">触发Watch</a-button>
  </div>
  <div>
    <h3>inject</h3>
    <div>data:{{ provideValue }}</div>
  </div>
</template>

<script setup lang="ts">
import { ref, computed, watch, watchEffect, defineEmits, defineProps, withDefaults, inject } from 'vue';
/*定义父组件传过来Props值的类型 start */
interface Props {
  propRef: string;
  propReactive: any;
  propDefault?: string;
}
//接收父组件传过来的值设置默认值
const props = withDefaults(defineProps<Props>(), {
  propRef: '',
  propReactive: { name: '', path: '' },
  propDefault: '',
});

/*定义父组件传过来Props值的类型 end */

/* inject接收父组件provide传过来的值 start */
const provideValue = inject('provideValue');
/* inject接收父组件provide传过来的值 end */

/** emit使用 start */

//注册emit
const emits = defineEmits(['emitValue']);

//定义emit的值
const emitValue = ref('我是子组件的emit,我被发射了');

//通过定义的emits将值发射到父组件
function emitToParent() {
  emits('emitValue', emitValue.value);
}

/** emit使用 end */

/** watch 使用start */
//定义watch被监听的值
const watchValue = ref(1);

function addWatch() {
  watchValue.value++;
}

watch(
  watchValue,
  (oldValue, newValue) => {
    console.log('[ oldValue,newValue ]', oldValue, newValue);
  },
  { immediate: true, deep: true }
);

/** watch 使用end */

/* watchEffect start */
watchEffect(() => {
  console.log('通过watchEffect监听watchValue的值的变化:', watchValue.value);
});
/* watchEffect end */

/** 计算属性 start */
//定义计算属性值
const computedValue1 = ref(1);
const computedValue2 = ref(10);

//写法一
const computerMode1 = computed(() => {
  return computedValue1.value + computedValue2.value;
});

//写法二
const computerMode2 = computed({
  get() {
    return computedValue1.value + computedValue2.value;
  },
  set() {
    return computedValue1.value + computedValue2.value;
  },
});

/** 计算属性 end */

/* 定义被父组件可以访问的值或方法 start */
const childValue = ref('我是子组件的值');
function childFunction() {
  console.log('我是子组件的方法');
}
/* 定义被父组件可以访问的值或方法 end */

/* 只有defineExpose暴露的值或方法才能被父组件通过ref访问 start */
defineExpose({
  childValue,
  childFunction,
});
/* 只有defineExpose暴露的值或方法才能被父组件通过ref访问 end */
</script>

我想要实现一个功能,是下拉菜单,但是要用组件实现,有三个文件,一个是业务文件,一个是select组件,一个是option组件 按下面的代码风格写:业务代码 <template> <div> <custom-table :data="tableData" stripe> <custom-table-column prop="name" label="姓名" width="120" /> <custom-table-column prop="age" label="年龄" width="80" /> <custom-table-column prop="address" label="地址" /> <custom-table-column label="操作"> <template #default="{ row }"> <button @click="handleClick(row)">操作</button> </template> </custom-table-column> </custom-table> </div> </template> <script setup> import { ref } from &#39;vue&#39; import customTable from &#39;@/components/table&#39; import customTableColumn from &#39;@/components/table/tableColumn&#39; const tableData = ref([ { name: &#39;张三&#39;, age: 28, address: &#39;北京市朝阳区&#39; }, { name: &#39;李四&#39;, age: 32, address: &#39;上海市浦东新区&#39; }, { name: &#39;王五&#39;, age: 45, address: &#39;广州市天河区&#39; } ]) const handleClick = (row) => { alert(JSON.stringify(row)) } </script> 组件:<template> <table :class="{ stripe: stripe }"> <thead> <tr> <th v-for="column in setColumns" > {{ column.label }} </th> </tr> </thead> <tbody> <tr v-for="(row, index) in props.data" :key="index"> <td v-for="column in setColumns" :key="column.id"> <template v-if="column.slot"> <component :is="column.slot" :row="row" :column="column" /> </template> <template v-else> {{ getCellValue(row, column.prop) }} </template> </td> </tr> </tbody> </table> <template v-show="false"> <slot></slot> </template> </template> <script setup> import { ref, provide, computed } from &#39;vue&#39; const props = defineProps({ data: { type: Array, default: () => [] }, stripe: { type: Boolean, default: false } }) // 存储列定义 const columns = ref([]) // 提供给子组件的注册方法 const registerColumn = (column) => { columns.value.push(column) } // 提供注入 provide(&#39;registerColumn&#39;, registerColumn) // 获取单元格值的辅助函数 const getCellValue = (row, prop) => { if (!prop) return &#39;&#39; return row[prop] } const setColumns = computed(() => { const map = new Map(); columns.value.forEach(item => { const key = `${item.label}`; if (!map.has(key)) { map.set(key, item); } }) const uniqueArray = Array.from(map.values()) return uniqueArray }) </script> <style scoped> .custom-table { width: 100%; border-collapse: collapse; } .custom-table th, .custom-table td { padding: 8px; text-align: left; border: 1px solid #ddd; } .custom-table tr.stripe:nth-child(even) { background-color: #f2f2f2; } </style> 子组件:<template> <slot></slot> </template> <script setup> import { inject, onMounted, ref } from &#39;vue&#39; const props = defineProps({ prop: String, label: String, }) const slots = useSlots() // 从父组件注入注册方法 const registerColumn = inject(&#39;registerColumn&#39;) // 组件挂载后注册列定义 onMounted(() => { if (registerColumn) { let slot = null if (slots.default) { slot = slots.default } registerColumn({ id: props.prop || `column-${Math.random().toString(36).slice(2)}`, prop: props.prop, label: props.label, slot: slot }) } }) </script>
07-09
业务画面:<template> <div> <CustomForm :form-data="formData" :rules="rules" @submit="handleSubmit"> <!-- 表单内容通过插槽注入 --> <template #default> <CustomFormItem label="产品名称" prop="name":required="true"> <CustomInput v-model="formData.name" placeholder="请输入产品名称"/> </CustomFormItem> <CustomFormItem label="产品描述" prop="description":required="true"> <CustomInput v-model="formData.description" placeholder="请输入产品描述" type="textarea"/> </CustomFormItem> </template> </CustomForm> <div>产品名称值: {{ formData.name }}</div> <div>产品描述值: {{ formData.description }}</div> </div> </template> <script setup> import { reactive } from &#39;vue&#39;; import CustomForm from &#39;@/components/customForm&#39;; import CustomFormItem from &#39;@/components/customFormItem&#39;; import CustomInput from &#39;@/components/customInput&#39;;// 输入框组件路径 // 业务数据 const formData = reactive({ name: &#39;&#39;, description: &#39;&#39; }); // 验证规则 const rules = reactive({ name: [ { required: true, message: &#39;产品名称不能为空&#39; }, { max: 10, message: &#39;长度超过10个汉字&#39; } ], description: [ { required: true, message: &#39;产品描述不能为空&#39; }, { max: 200, message: &#39;描述不能超过200个汉字&#39; } ] }); // 提交处理 function handleSubmit(valid, data) { if (valid) { alert(&#39;表单验证通过! 提交数据: &#39; + JSON.stringify(data)); } else { console.log(&#39;表单验证失败&#39;); } } </script> customForm:<template> <el-form :model="formData" :rules="rules" ref="formRef" class="custom-form" @submit.prevent> <!-- 表单内容插槽 --> <slot name="default"></slot> <el-button type="primary" @click="submitForm" class="submit-btn"> 提交 </el-button> </el-form> </template> <script setup> import { ref, defineProps, defineEmits } from &#39;vue&#39;; const props = defineProps({ formData: { type: Object, required: true }, rules: { type: Object, default: () => ({}) } }); const emit = defineEmits([&#39;submit&#39;]); const formRef = ref(null); function submitForm() { formRef.value.validate((valid) => { emit(&#39;submit&#39;, valid, props.formData); }); } </script> <style scoped> .custom-form { margin-bottom: 20px; } .submit-btn { margin-top: 15px; } </style> customFormItem:<template> <el-form-item :label="label" :prop="prop":rules="computedRules" class="custom-form-item"> <slot></slot> </el-form-item> </template> <script setup> import { computed } from &#39;vue&#39;; const props = defineProps({ label: String, prop: String, required: Boolean }); // 动态计算验证规则 const computedRules = computed(() => { const rules = []; if (props.required) { rules.push({ required: true, message: `${props.label}不能为空` }); } return rules; }); </script> <style scoped> .custom-form-item { margin-bottom: 18px; } </style> customInput:<template> <div class="custom-input"> <input v-if="type !== &#39;textarea&#39;" :value="modelValue" @input="handleInput" @blur="handleBlur":placeholder="placeholder":type="type" class="input-field"/> <textarea v-else :value="modelValue" @input="handleInput" @blur="handleBlur" :placeholder="placeholder" class="textarea-field"></textarea> </div> </template> <script setup> import { defineProps, defineEmits } from &#39;vue&#39;; const props = defineProps({ modelValue: [String, Number], placeholder: { type: String, default: "" }, type: { type: String, default: "text" } }); const emit = defineEmits([&#39;update:modelValue&#39;, &#39;blur&#39;]); function handleInput(event) { emit(&#39;update:modelValue&#39;, event.target.value); } function handleBlur() { emit(&#39;blur&#39;); } </script> <style scoped> .custom-input { width: 100%; } .input-field, .textarea-field { width: 100%; padding: 8px 12px; border: 1px solid #dcdfe6; border-radius: 4px; transition: border-color 0.3s; } .textarea-field { min-height: 80px; resize: vertical; } .input-field:focus, .textarea-field:focus { border-color: #409eff; outline: none; } </style> 这几个组件我不想用element·写
最新发布
07-17
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值