Code前端首页关于Code前端联系我们

Vue2中的expose到底有啥用?怎么用才高效?

terry 7小时前 阅读数 11 #Vue
文章标签 Vue2 expose

做Vue2项目开发时,你有没有过这种纠结?想在父组件里调用子组件的方法,要是用$emit把方法传上去,代码绕来绕去特麻烦;直接用$refs吧,又怕不小心动了子组件里没打算对外开放的东西,心里没底,这时候Vue2里的expose选项就能解决这些痛点!今天咱就把expose从“是干啥的”到“怎么用得溜”,再到“实际场景咋选”这些问题一次性讲透,看完你再处理组件通信,思路能清晰不少~

Vue2里expose是做什么的?

简单说,expose是Vue2给子组件提供的一个“权限开关”——控制哪些自身的属性、方法能被父组件通过$refs拿到

先想默认情况:如果子组件里没写expose,父组件用this.$refs.子组件ref名拿到的是子组件的整个实例,能直接访问子组件的data、methods、computed这些东西,比如子组件有个internalMethod没打算对外用,但父组件一不小心通过$refs调用了,后续子组件迭代时改了这个方法名,父组件就直接崩了,维护起来特别糟心。

而加了expose之后,就像给子组件设了个“白名单”:只有expose数组里列出来的成员,父组件才能通过$refs访问,其他的统统“隐藏”,举个实际代码例子:

子组件Child.vue

<template>
  <button @click="selfClick">子组件内部按钮</button>
</template>
<script>
export default {
  name: 'Child',
  methods: {
    selfClick() {
      console.log('子组件自己的点击逻辑');
    },
    helperMethod() { // 这个方法不想对外暴露
      console.log('内部辅助逻辑');
    }
  },
  expose: ['selfClick'] // 只把selfClick放进白名单
}
</script>

父组件Parent.vue里用ref调用:

<template>
  <Child ref="childRef" />
  <button @click="callChild">调用子组件方法</button>
</template>
<script>
export default {
  components: { Child },
  methods: {
    callChild() {
      this.$refs.childRef.selfClick(); // 能正常调用,因为在expose里
      this.$refs.childRef.helperMethod(); // 会报错!因为没暴露
    }
  }
}
</script>

能看到,expose的核心价值是让子组件主动把控对外暴露的“接口”,避免父组件依赖子组件内部没公开的逻辑,减少后续维护时的风险。

expose和props、$emit有啥不一样?

很多同学刚接触expose时,会和props、$emit搞混,其实它们完全是不同维度的逻辑:

  • props:是“父组件给子组件传数据”的通道,单向的(父→子),比如父组件给子组件传title,子组件用props接收后渲染。
  • $emit:是“子组件给父组件发通知”的方式,子组件通过this.$emit('事件名')触发,父组件在标签上用@事件名监听,比如子组件点击按钮后,$emit告诉父组件“我被点了”,父组件执行对应逻辑。
  • expose:是“子组件主动把自己的能力(方法、属性)开放给父组件”,父组件通过$refs拿到子组件实例后,直接调用这些暴露的能力。

举个场景对比更清楚:假设父组件需要让子组件执行一个“重置表单”的操作。

  • 用$emit的话:子组件得在methods里写reset方法,然后触发this.$emit('reset'),父组件监听@reset后,再通过$refs调用子组件的reset?这就绕了——明明子组件自己有方法,却要来回传事件。
  • 用expose的话:子组件把reset放进expose数组,父组件直接this.$refs.子组件.ref.reset()调用,一步到位。

所以expose更适合父组件需要主动调用子组件内部逻辑的场景,比props、$emit更直接高效。

实际项目里哪些场景适合用expose?

光懂概念不够,得知道啥时候用才是真会了,这些场景里用expose,代码会更整洁、可维护:

封装复杂组件时,只开放必要API

比如做一个富文本编辑器组件,内部有一堆初始化、格式转换、内容保存的逻辑,但对外只需要让父组件能调用“保存内容”“清空内容”这两个方法,这时候用expose把这两个方法列出来,其他内部逻辑(比如格式校验的辅助方法)全部隐藏,防止外部误用。

组件库开发,控制对外稳定性

做公共组件库时,最怕用户依赖组件内部的私有逻辑,比如你写了个下拉选择组件,内部有个_handleDropdown方法(下划线开头表示私有),结果用户通过$refs直接调用了这个方法,后续你迭代组件时,把_handleDropdown改名或逻辑改掉,所有用了这个方法的业务代码全崩了,用expose明确只开放open close这些公开方法,就能避免这种依赖风险。

多人协作项目,减少沟通成本

团队开发时,组件的维护者可以通过expose明确告诉其他开发者:“这个组件对外只提供这些方法/属性,你们要操作组件,只用这些就够了”,比如做一个弹窗组件,暴露show hide方法,其他同学看到expose里的名单,就知道怎么和这个组件交互,不用去猜内部有多少可调用的东西。

控制子组件状态的修改权限

有时候父组件需要修改子组件的某个状态,但又不想用props(比如状态是子组件内部维护的,父组件只是偶尔要改),这时候可以把子组件的setInternalState方法暴露出来,父组件调用这个方法来修改,而不是直接去改子组件的data(直接改data容易失控)。

怎么在Vue2中正确配置expose?

知道了场景,接下来得搞懂具体怎么写代码,步骤很简单,但细节要注意:

步骤1:在子组件的选项中配置expose

expose是Vue2组件选项中的一个数组,元素是字符串,对应要暴露的成员名(比如methods里的方法名、data里的属性名、computed里的计算属性名等)。

子组件示例(暴露方法和属性):

<template>
  <div>{{ message }}</div>
</template>
<script>
export default {
  name: 'Child',
  data() {
    return {
      message: '我是子组件的消息'
    }
  },
  methods: {
    updateMessage(newMsg) {
      this.message = newMsg;
    }
  },
  expose: ['message', 'updateMessage'] // 暴露data里的message和methods里的updateMessage
}
</script>

步骤2:父组件用ref获取子组件实例

父组件中,给子组件标签加ref属性,然后通过this.$refs.xxx拿到子组件实例,就能访问暴露的成员了。

父组件示例:

<template>
  <Child ref="childRef" />
  <button @click="changeChildMsg">修改子组件消息</button>
  <button @click="logChildMsg">打印子组件消息</button>
</template>
<script>
export default {
  components: { Child },
  methods: {
    changeChildMsg() {
      this.$refs.childRef.updateMessage('新消息~'); // 调用暴露的方法
    },
    logChildMsg() {
      console.log(this.$refs.childRef.message); // 访问暴露的属性
    }
  }
}
</script>

注意这几个细节:

  • 成员名要一致:expose里写的名字,必须和子组件内部定义的名字完全一样,比如methods里是updateMsg,expose里写成updateMessage,父组件调用时就会报错。
  • 没写expose时的默认行为:如果子组件不写expose选项,父组件$refs能拿到子组件的所有实例成员(data、methods、computed等),但不推荐这么做,因为会让子组件的内部实现暴露无遗,后续维护风险高。
  • 能暴露哪些类型?:data里的属性、methods里的方法、computed里的计算属性、甚至自定义的实例属性(比如this.xxx = yyy)都能暴露,只要名字在expose数组里。

用expose时容易踩的坑有哪些?

就算步骤对了,这些细节没注意,还是会出问题,提前避坑,开发更顺畅:

坑1:暴露的成员“不存在”

比如子组件expose里写了foo,但methods里根本没定义foo方法,或者data里没foo属性,这时候父组件调用this.$refs.xxx.foo就会报“undefined is not a function”或者“无法读取属性”的错误。

怎么避免?写expose前,先检查子组件里有没有对应的成员,保持名字一致。

坑2:父组件“过早”访问$refs

Vue的渲染是有生命周期的,父组件如果在created钩子(这时候子组件还没挂载)里访问this.$refs.childRef,拿到的是undefined

解决方法:把访问$refs的逻辑放到mounted钩子,或者用this.$nextTick确保DOM渲染完成后再访问。

mounted() {
  this.$nextTick(() => {
    this.$refs.childRef.xxx(); // 确保子组件已经挂载
  });
}

坑3:混淆“暴露范围”

比如想暴露一个计算属性,但计算属性没在实例上?不可能,因为Vue的computed默认会挂载到实例上,但如果是在methods里定义的函数,要确保函数名和expose里一致。不要暴露子组件的私有状态逻辑,比如只在mounted里执行一次的初始化方法,暴露出去后父组件乱调用,容易让组件状态混乱。

和Vue3的expose有啥区别?

如果以后要学Vue3,提前了解差异,迁移时更顺手:

  • Vue2的expose:属于选项API的一部分,只能用数组列名字,写法比较“死板”。

    export default {
      expose: ['foo', 'bar'],
      methods: { foo() {}, bar() {} }
    }
  • Vue3的expose:属于组合式API,用expose函数来传对象,更灵活,比如在setup里:

    import { defineComponent, expose } from 'vue'
    export default defineComponent({
      setup(props, { expose }) {
        const foo = () => {}
        const bar = '值'
        expose({ foo, bar }) // 直接传要暴露的对象
        return {}
      }
    })

虽然写法不同,但核心目的完全一致:都是让子组件自己决定对外暴露哪些能力,防止父组件滥用内部逻辑。

看完这些,你应该对Vue2的expose从“是啥”到“咋用”,再到“哪里用”都有了清晰认识,简单总结下:expose是子组件的“权限管家”,帮你精准控制对外公开的能力,让组件通信更安全、维护更轻松,下次写组件时,别再盲目用$refs调用所有方法啦,试试expose,代码的可读性和可维护性会提升一大截~要是你还有其他Vue2的疑问,评论区随时聊~

版权声明

本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

热门