VUE中多级透传的常用优化方法

VUE中多级透传的常用方法

多级传递参数,自定义函数

  1. 根组件 parent.vue
  <template>
    <div class="w-100p h-100p relative">
    <my-console-assign
    task-type="5"
    :card-height="cardHeight"
    :subtract-width="subtractWidth"
    is-enhance-find-lesson
    :is-show-none="true"
    :is-dispatch="false"
    @handleButtonEvent="handleButtonEvent"
    ></my-console-assign>
    </div>
    </template>
    <script>
    import myConsoleAssign from '@/pc/view/listenAndDiscuss/myConsole/myConsoleAssign';
    import WrapperContentBox from '@/pc/components/wrapperContentBox/wrapperContentBox.vue';
    export default {
        name: 'parent',
        components: {
            myConsoleAssign
        },
        props: {
            // 面板高度
            cardHeight: {
                default: 222,
                type: Number
            },
            // 左侧距离
            subtractWidth: {
                default: 0, //330通用	(减去了左侧的区块)			0(屏幕百分比宽度)
                type: Number
            }
        },
        data() {
            return {};
        },
      methods:{
        handleButtonEvent(){
            
        }
      }
    };
    </script>
    <style lang="scss" scoped></style>

  1. 中间组件

props(父组件向子组件传值) 子传父应该用通过绑定事件然后及$emit传值,普通组件间传值用全局事件总线 $bus

中间组件不用接收 props 可以使用

v-bind=“$attrs” 传递 下级组件调用即可

自定义事件 v-on=“$listeners”

.sync 子更新父数据(当一个子组件改变了一个 prop 的值时,这个变化也会同步到父组件中所绑定。 .sync 它会被扩展为一个自动更新父组件属性的 v-on 监听器)

:data.sync=“dataList”

:columns=“getTableColumns()”

:current-page.sync=“page”

<template>
	<div class="border-box flex-column p-20 h-100p w-100p">
		<el-select class="m-b-20 select-drop-list select-w-custom" v-model="currentTermId" :placeholder="formatTerm">
			<el-option
				v-for="item in schoolTermList"
				:key="item.school_term_id"
				:label="item.label"
				:value="item.val"
			>
			</el-option>
		</el-select>
      <module-wrap-title
          title="我的听课记录"
          title-class="f-s-18"
          :margin-bottom="20"
          :height="20"
          is-show-notice-board
          more-class="f-s-14  col-b-blue bold" 
      >
      </module-wrap-title>
      <wrapper-content-box
          :data-list="dataListLessRecordList"
          @diffSize="diffSize"
          @handleButtonEvent="handleButtonEvent"
          v-on="$listeners"
          v-bind="$attrs"
          :line-number="1"
      ></wrapper-content-box>
	</div>
</template>
<script>
import { getCurrentMonthRangeDate } from '@/utils/old/time'; 
import myConsole from '@/pc/view/listenAndDiscuss/myConsole/mixins/myConsole'; 

export default {
	components: {
		ModuleWrapTitle,
		WrapperContentBox, 
	},
	filters: { 
	},
	mixins: [myConsole],
	props: { 
		taskType: {
			type: String,
			default: ''
		}
	},
	data() {
		return {
			cardWidth: '325px',
			dataList: [], 
			dateParams: {}, 
		};
	},
	computed: {   
	},
	watch: { 
		currentTerm(newVal) {
			if (newVal.term_start) {
				//newVal?.term_year + '' + (newVal?.term_number === '1' ? '春季' : '秋季') + '学期';
				this.currentTerm.term_start = newVal.term_start;
				this.currentTerm.term_end = newVal.term_end;
				// this.getStatisticData();
			}
		}, 
	},
	mounted() {},
	created() {
		// 查询的用户id
		this.queryUserId = this.$route.query.id ? this.$route.query.id : this.userInfo.user_id;
		  
	},
	methods: {
		init() { 
		}, 
		// 格式化当前日期选项f
		formatTermLabel(percentage) {
			return percentage === 0 ? '---' : `${percentage}%`;
		},    
	}
};
</script>

<style lang="scss" scoped>
$borderColor: #e6e6e6; 

::v-deep .el-progress {
	.el-progress-bar__outer,
	.el-progress-bar__inner {
		border-radius: 4px;
	}
}

@media screen and (max-width: 1919px) {
	.calendar-min {
		width: 304px;
	}
}

@media screen and (min-width: 1920px) {
	.calendar-min {
		width: 332px;
	}
} 
</style>

  1. 孙子组件
<template>
  <div class="flex flex-wrap wrapper-box">
    <!--放置占位卡片-->
    <MediaWrapper v-if="isSeat" :match-size="matchSizeInfo.width">
      <slot></slot>
    </MediaWrapper>
    <!--添加-->
    <MediaWrapper v-if="isAdd" :card-width="cardWidth" :match-size="matchSizeInfo.width">
      <div class="w-100p flex-center pointer border-box p-20 brs8 white-bg" :style="{ height: `${addHeight}px` }">
        <div class="justify-center align-center flex-col add w-100p h-100p" @click="add">
          <img src="@static/img/add_supervisor.png" alt="" class="w-32 h-32" />
          <div class="m-t-16">{{ addTitle }}</div>
        </div>
      </div>
    </MediaWrapper>
    <MediaWrapper
        v-for="(item, index) in newList"
        :key="index"
        is-margin
        :card-width="cardWidth"
        :match-size="matchSizeInfo.width"
    > <!--新版听评课卡片-->
      <enhance-find-lesson
          v-if="isEnhanceFindLesson"
          :item-info="item"
          v-bind="$attrs"
          v-on="$listeners"
      ></enhance-find-lesson>
      <!--督导评课基本设置-->
      <listen-manage-card
          :item-info="item"
          v-if="isListenManage"
          :threshold-value="thresholdValue"
          v-bind="$attrs"
      ></listen-manage-card>
    </MediaWrapper>
    <div
        v-if="newList.length === 0 && isShowNone"
        class="flex-1 m-10 "
        v-bind="$attrs"
        :title="noDataTitle"
        v-empty
    ></div>
  </div>
</template>
<script> 
export default {
  name: 'wrapperContentBox',
  components: { 
  },
  mixins: [multiPlatformType],
  props: {
    dataList: {
      default: () => {
        return [];
      },
      type: Array
    },
    //卡片前需要占位的个数
    hasSizeNumber: {
      default: 0,
      type: Number
    },
    //是否单行展示
    isWrap: {
      default: false,
      type: Boolean
    },
    //是否有添加的卡片
    isAdd: {
      default: false,
      type: Boolean
    }, 
    //新开页面
    isOpen: {
      default: false,
      type: Boolean
    }, 
    // 是否阈值
    thresholdValue: {
      default: false,
      type: Boolean
    }, 
  },
  data() {
    return {
      isScrollbarY: false,
      diffSize: 0, 
      // 当前可用的宽度
      freeScreenWidth: 0,
      // 当前的matchSize信息
      matchSizeInfo: {}
    };
  },
  computed: { 
  },
  created() { 
  },

  // 大量页面用该组件包裹,所以缓存后的触发调用接口也放在该组件内
  activated() {
    // 防止多掉一次接口:created或者mounted中也会getData
    if (this.$route.meta?.isFirstEnter === false) {
      this.calcPageSize();
    }
  },

  methods: {
    async calcPageSize() {
      await this.$store.dispatch('app/getViewWidth');
      // 当前屏幕可用宽度
      this.freeScreenWidth = this.$store.state.app.viewWidth - this.subtractWidth;
      console.log('freeWidth', this.freeScreenWidth);
      this.matchSizeInfo = getMatchSizeInfo(this.freeScreenWidth, this.pageSizeInDiffPx);
      this.diffSize = listPageSize(this.freeScreenWidth, this.lineNumber);
      if (this.isAdd) {
        this.diffSize = this.diffSize - 1;
      }
      this.$emit('diffSize', this.diffSize);
    }, 
    toDetail(name, id, type, isPassword, isNewOpen, val) {
      let routeUrl = this.$router.resolve({
        name: name
      }); 
      if (isNewOpen) {
        const token = getStorage(AccessToken);
        window.open(
            window.location.origin + window.location.pathname + routeUrl.href + '?id=' + id + '&token=' + token
        );
      } else {
        if (isPassword) {
          passwordDia(this, id, name, type, this.isOpen);

          return;
        } else if (this.isOpen) {
          const token = getStorage(AccessToken);
          window.open(
              window.location.origin +
              window.location.pathname +
              routeUrl.href +
              '?id=' +
              id +
              '&token=' +
              token
          );
          return;
        }
        this.$router.push({
          name: name,
          query: { 
            ...concatDistrictParams(this.$isDistrict, { id: id }, { isDistrict: 1 })
          }
        });
      }
    }
  }
};
</script>
<style lang="scss" scoped> 
</style>


$emit(子传父) 使用 .sync省略手动监听

<!--- 父组件 --->
<template>
    <div class="app">
        <Student v-on:at="getStudentName" v-on:go="getStudentAge"></Student>
    </div>
</template>

<script>
    // 引入Student组件
    import Student from './components/Student.vue' 
    // 定义vue组件
    export default {
        name:'App',
        components:{
            Student
        },
        data() {
            return {
                msg:'你好啊'
            }
        },
        methods: {
            getStudentName(name) {
                console.log('app收到了学生名',name);
            },
            getStudentAge(age) {
                console.log('app收到了学生年龄',age);
            }
        }
    }
</script> 


		<!--- 子组件 --->
		<template>
		    <div class="student">
		        <h2>学生姓名:{{name}}</h2>
		        <h2>学校年龄:{{age}}</h2>
		        <button @click="sendStudentName">把学生名给app</button>
		        <button @click="sendStudentAge">把学生年龄给app</button>
		    </div>
		</template>

<script>
    export default {
        name:'MyStudent',
        data() {
            return {
               name:'张三',
               age:18
            }
        },
        methods: {
            sendStudentName() {
                // 触发Student组件实例对象vc上的at事件(绑定at事件)
                this.$emit('at',this.name)
            },
            sendStudentAge() {
				 // 触发Student组件实例对象vc上的go事件(绑定go事件)
                this.$emit('go',this.age)
			}
    }
</script> 
<think>好的,我现在要详细回答用户关于Vue中$attrs属性的问题。首先,用户已经提供了三个引用内容,我需要结合这些引用以及之前关于render函数的回答来组织内容。 首先,根据引用[2]和引用[3],$attrs的作用是包含父组件递给子组件但未被props接收的属性。用户提到默认情况下这些属性会绑定到根元素,但可以通过inheritAttrs: false来禁用,然后手动绑定到特定位置。引用[3]中的例子展示了如何在子组件中使用v-bind="$attrs"将属性递给孙组件。 接下来,我需要明确$attrs的定义和用途。根据Vue的文档,$attrs包含了父作用域中不作为组件props或自定义事件的属性绑定(class和style除外)。这在多层组件递属性时非常有用,尤其是在构建高阶组件或封装第三方库时。 然后,考虑用户的使用场景。比如,在封装表单组件时,可能需要将原生HTML属性(如placeholder、disabled)递给内部的input元素。这时,使用$attrs可以避免在每一层组件中都声明props,简化代码。 另外,引用[1]提到Vue无法检测对象属性的添加或删除,这可能与动态绑定属性有关。但$attrs本身是响应式的,所以只要正确使用,属性变化会被Vue处理。 在之前的回答中,关于render函数部分提到了使用data参数中的attrs来递属性,比如在h函数中设置attrs对象。这可能与$attrs的使用相关联,尤其是在手动渲染时处理属性。 现在需要组织内容结构,分为定义、核心特性、使用场景、注意事项、与相关API的对比等部分。同时,要给出实际代码示例,帮助用户理解如何在不同场景下应用$attrs。 还需要注意用户可能的疑问点,比如inheritAttrs的作用,如何避免属性重复绑定,以及$attrs和$props的区别。引用[3]中的例子展示了$props和$attrs的区别,可以在这里详细说明。 最后,总结最佳实践,帮助用户合理使用$attrs,避免常见错误,比如忘记禁用inheritAttrs导致属性被重复绑定到根元素。</think>#### 一、`$attrs` 的核心定义 `$attrs` 是Vue提供的一个内置属性,用于**自动收集父组件递给子组件但未被props显式声明的所有属性**(不含`class`和`style`)。其核心特性包括: - **自动机制**:未在子组件props中声明的属性会自动存入`$attrs`对象 - **响应式特性**:属性值变化会触发视图更新(与引用[1]的响应式限制不同,`$attrs`本身是响应式对象) - **层级穿能力**:支持通过`v-bind="$attrs"`跨多层组件递属性[^2] #### 二、核心特性深度解析(附对比表格) | 特性 | `$attrs` | `$props` | |---------------------|-----------------------------------|-----------------------------| | **数据来源** | 父组件递的非props属性 | 父组件显式声明的props | | **包含内容** | 普通属性/原生DOM属性/自定义属性 | 仅组件props定义的内容 | | **响应式** | ✅ 自动响应 | ✅ 自动响应 | | **层级** | 可跨多级组件递 | 仅限直接父子组件 | | **典型应用场景** | 高阶组件/原生属性 | 基础数据递 | #### 三、六大核心使用场景详解 ##### 场景1:原生DOM属性(表单组件封装) ```vue <!-- 父组件 --> <BaseInput placeholder="请输入" maxlength="20" data-test="input"/> <!-- 子组件BaseInput.vue --> <template> <div class="input-wrapper"> <input v-bind="$attrs"> <!-- 自动接收placeholder/maxlength等属性 --> </div> </template> <script> export default { inheritAttrs: false // 阻止自动绑定到根元素div } </script> ``` **技术要点**:通过`inheritAttrs: false`禁用默认绑定,手动控制属性位置(如引用[2]所述) ##### 场景2:多层组件属性穿 ```javascript // 祖父组件 → 父组件 → 子组件 // 祖父递属性 <ParentComponent type="success" icon="user"/> // 父组件(中间层) <ChildComponent v-bind="$attrs"/> // 子组件直接使用$attrs接收 ``` **优势**:避免中间层组件逐个声明props(如引用[3]示例中的跨级递) ##### 场景3:高阶组件(HOC)开发 ```javascript // 权限控制高阶组件 const withAuth = (WrappedComponent) => ({ inheritAttrs: false, render(h) { return this.$auth.check() ? h(WrappedComponent, { attrs: this.$attrs, on: this.$listeners }) : h('div', '无权限') } }) ``` ##### 场景4:第三方库组件增强 ```vue <el-input v-bind="$attrs" :suffix-icon="SearchIcon"> ``` **作用**:扩展Element UI组件时保留所有原生属性支持 ##### 场景5:动态属性绑定 ```javascript // 根据状态动态绑定属性 computed: { dynamicAttrs() { return { ...this.$attrs, [this.isMobile ? 'data-mobile' : 'data-desktop']: true } } } ``` ##### 场景6:与渲染函数配合使用 ```javascript // render函数中手动处理attrs export default { render(h) { return h('div', { attrs: { id: 'container', ...this.$attrs // 合并自定义属性 } }) } } ``` #### 四、四条关键实践原则 1. **属性冲突解决策略** ```javascript // 显式定义的属性会覆盖$attrs中的同名属性 <ChildComponent title="本地标题" v-bind="$attrs"/> ``` 2. **敏感属性过滤** ```javascript // 过滤特定属性 const safeAttrs = _.omit(this.$attrs, ['dangerousProp']) ``` 3. **TypeScript类型增强** ```typescript // 为$attrs添加类型声明 declare module '@vue/runtime-core' { interface ComponentCustomProps { customProp?: string } } ``` 4. **性能优化技巧** ```javascript // 避免在模板中直接展开大型$attrs对象 // 错误示例 <div v-bind="$attrs"> // 正确优化 <div :id="$attrs.id" :data-type="$attrs.type"> ``` #### 五、与相关API的联动关系 1. **`inheritAttrs` 配置项** - `true`(默认):未处理属性自动绑定到根元素 - `false`:禁用自动绑定,需手动通过`v-bind="$attrs"`分配 2. **`$listeners` 配合使用** ```vue <!-- 同时递属性和事件 --> <GrandChild v-bind="$attrs" v-on="$listeners"/> ``` 3. **与`useAttrs()`组合式API** ```javascript // Vue3组合式API用法 import { useAttrs } from 'vue' setup() { const attrs = useAttrs() console.log(attrs.class) // 获取class属性 } ``` #### 六、常见误区与调试技巧 1. **浏览器Devtools观测** - Vue Devtools组件面板可直接查看`$attrs`内容 - 元素面板验证属性实际绑定位置 2. **典型问题排查** ```markdown 现象:属性未正确递 检查步骤: 1. 确认父组件属性命名规范(避免大写) 2. 检查中间组件是否阻断递 3. 验证子组件是否设置inheritAttrs: false 4. 查看DOM确认属性实际绑定位置 ``` 3. **版本差异注意** - Vue2中`$attrs`不包含class/style - Vue3中`$attrs`包含除props外的所有属性(含class/style) 通过以上深度解析,开发者可以掌握`$attrs`在组件通信、高阶组件开发等场景中的灵活应用,显著提升组件封装质量与开发效率。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值