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

Vue3里getCurrentInstance到底怎么用?踩过的坑能不能避?

terry 1小时前 阅读数 16 #Vue

最近刷前端群,看到好多刚转Vue3的小伙伴在聊getCurrentInstance——有人说它是“开发神器”,能拿到Vue2里this的所有东西;有人刚用就踩雷,一会获取不到ctx一会又报错ctx只能在开发环境用;还有人纠结什么时候该用什么时候不该碰,今天就把我做项目踩过的坑、查过的文档(当然是官方的)和实际用到的场景串起来讲清楚,争取一篇就能把这个API搞明白。

先搞懂:getCurrentInstance到底是什么来头

先回到Vue3的核心变化——Composition API对吧?Vue2我们用Options API,不管是data、methods还是computed,所有逻辑都绑在组件实例this上,拿根节点DOM、访问全局属性、操作子组件都是顺理成章的事,但Composition API不一样,setup函数是在组件实例创建之前执行的(准确说是beforeCreate钩子前,created钩子时其实实例已经挂了但官方还是推荐别在setup外依赖它的内部状态),这时候根本没有this可用,那怎么办呢?

官方就给了getCurrentInstance这个API,作用就是在setup函数(或者它内部同步调用的组合式函数)里,获取当前正在执行的组件实例对象,这里画个重点:必须是同步调用,异步回调里(比如setTimeout、Promise.then、async/await的await后面)直接调它拿不到东西,返回的是null,这个后面避坑第一条会详细说。

实际用起来:getCurrentInstance能帮我们做什么事?

别以为getCurrentInstance只是用来补Vue2 this的缺口的,它有几个专属的、非常实用的场景,我整理了三个高频的:

获取组件的内部属性和方法(不推荐直接用在生产环境的核心逻辑)

这里先说明:getCurrentInstance返回的实例对象,有两个核心部分——ctx(上下文)proxy(代理对象)

ctx在开发环境里,几乎是Vue2 this的翻版,你可以拿到$data、$props、$attrs、$refs、$emit这些熟悉的东西,甚至能访问到全局挂载的$axios、$message(比如用element-plus全局引入的),但注意!ctx是仅供开发调试用的,生产环境会被压缩或者移除,别不信邪写到生产环境,上线后直接报错找不到属性哭都来不及。

那要在生产环境用Vue2 this的功能怎么办?用proxy!proxy才是组件实例的安全代理,不管开发还是生产环境都稳定存在,它上面暴露的属性和方法,和Vue3官方推荐的对外公开API完全一致——$props、$attrs、$slots、$emit、$refs这些都有,而且和Vue2里的用法几乎没区别,举个简单的例子:

import { getCurrentInstance, ref, onMounted } from 'vue'
export default {
  setup() {
    const { proxy } = getCurrentInstance()
    const inputRef = ref(null)
    // 模拟全局挂载的$message弹框
    const showSuccess = () => {
      proxy.$message.success('操作成功啦!')
    }
    // onMounted同步调用没问题,这时候组件已经挂载完成,$refs也能拿到
    onMounted(() => {
      inputRef.value.focus() // 这里用ref绑定更安全,但如果是通过$el或者全局注册的子组件用$refs也可以
      // 如果非要用proxy的$refs也行:proxy.$refs.inputRef.focus(),但前提是inputRef绑定在模板的ref上
    })
    return { inputRef, showSuccess }
  }
}

访问组合式函数内部暴露的私有状态(仅限自己写的组合式函数或者完全可控的场景)

有时候我们写组合式函数,不想把所有状态和方法都暴露给组件,但又想在另一个组合式函数或者组件的特定逻辑里用到这些私有内容,这时候getCurrentInstance就派上用场了——通过它可以拿到组件实例的exposed对象?不对不对,exposed是组件用defineExpose暴露给父组件的内容,私有状态在instance的setupState里。

举个场景,比如我写了一个useCounter组合式函数,用来管理计数器,有私有变量count、私有方法addPrivateCount,只想暴露给组件公共的count和add方法,但又想写一个useCounterLogger组合式函数,专门监听privateCount的变化打印日志:

// useCounter.js
import { ref } from 'vue'
export function useCounter() {
  const privateCount = ref(0)
  const publicCount = ref(0)
  const addPrivate = () => privateCount.value++
  const addPublic = () => publicCount.value++
  return { publicCount, addPublic }
}
// useCounterLogger.js
import { getCurrentInstance, watch } from 'vue'
export function useCounterLogger() {
  const instance = getCurrentInstance()
  if (!instance) return // 防止异步调用或者非setup调用
  // 监听setupState里的privateCount
  watch(
    () => instance.setupState.privateCount,
    (newVal) => {
      console.log(`私有计数器变了:${newVal}`)
    }
  )
}
// 组件里用
import { useCounter, useCounterLogger } from './composables'
export default {
  setup() {
    // 必须先调用useCounter,把privateCount挂到setupState上
    const { publicCount, addPublic } = useCounter()
    // 再调用useCounterLogger同步监听
    useCounterLogger()
    // 加个测试按钮触发addPublic的同时也触发addPrivate?哦不对,addPrivate没暴露,那只能通过组合式函数内部或者instance.setupState调用
    const testBoth = () => {
      addPublic()
      const instance = getCurrentInstance()
      if (instance) instance.setupState.addPrivate()
    }
    return { publicCount, testBoth }
  }
}

这个场景虽然有点偏,但在写复杂的业务逻辑或者开源组合式库的时候,偶尔会用到,不过还是要强调:非必要别碰setupState,因为它是Vue3的内部实现,没有对外公开API稳定,版本更新可能会改变结构,导致代码直接挂掉。

全局挂载工具函数的另一种方式?或者说组合式函数里访问全局属性更优雅?

哦对了,刚才用proxy访问全局挂载的$axios、$message是一种方式,但如果我们写组合式函数,每次都要先getCurrentInstance再拿proxy会不会有点麻烦?有没有更优雅的方式?其实Vue3官方还推荐用provide/inject来传递全局属性,但如果是已经全局挂载到app.config.globalProperties上的老项目工具,getCurrentInstance的proxy还是最快的过渡方式。

不过这里可以提一个小技巧:我们可以自己封装一个useGlobalProperties组合式函数,把getCurrentInstance的逻辑包起来,这样每次用的时候直接导入调用就行:

// useGlobalProperties.js
import { getCurrentInstance } from 'vue'
export function useGlobalProperties() {
  const instance = getCurrentInstance()
  if (!instance) {
    throw new Error('useGlobalProperties 只能在setup函数或其同步调用的组合式函数中使用')
  }
  return instance.proxy
}
// 组件或者组合式函数里用
import { useGlobalProperties } from './composables'
export default {
  setup() {
    const { $axios, $message } = useGlobalProperties()
    const fetchData = async () => {
      try {
        const res = await $axios.get('/api/data')
        $message.success('数据获取成功')
        return res.data
      } catch (err) {
        $message.error('数据获取失败')
        console.error(err)
      }
    }
    fetchData() // 注意这里fetchData是async函数,await后面不能再调useGlobalProperties或者getCurrentInstance哦
    return {}
  }
}

这样是不是清爽多了?而且我们还加了错误提示,万一有人在异步回调里用,会直接抛出明确的错误,方便调试。

划重点:getCurrentInstance的避坑指南,这几个雷千万别踩!

刚才说了那么多好处,现在要泼点冷水——getCurrentInstance虽然好用,但坑也不少,我整理了三个最常见、最容易犯的:

雷区1:在异步回调里直接调用getCurrentInstance,返回null

刚才已经提过一次,但这个坑太常见了,必须单独拿出来说三遍:同步调用!同步调用!同步调用!

为什么异步回调里不行?因为getCurrentInstance是通过内部的“当前实例栈”来工作的——每次进入一个组件的setup函数或者同步调用的组合式函数,Vue3会把当前组件实例压入栈顶;执行完之后,再把它弹出栈,而异步回调(比如setTimeout、Promise.then、async/await的await后面)是在事件循环的下一个周期执行的,这时候当前实例栈早就空了,所以getCurrentInstance只能返回null。

那如果非要在异步回调里用到proxy或者setupState怎么办?提前在同步代码里把它们存起来!

import { getCurrentInstance, ref } from 'vue'
export default {
  setup() {
    const { proxy } = getCurrentInstance() // 同步存起来
    const data = ref(null)
    const fetchData = async () => {
      try {
        // await后面不能再调getCurrentInstance,但可以用提前存的proxy
        const res = await proxy.$axios.get('/api/data')
        data.value = res.data
        proxy.$message.success('数据获取成功')
      } catch (err) {
        proxy.$message.error('数据获取失败')
      }
    }
    fetchData()
    return { data }
  }
}

对,就这么简单——提前把需要的东西赋值给setup里的变量,异步回调里就能用了。

雷区2:在生产环境用ctx访问属性,上线后直接报错

刚才也说了,ctx是开发环境的调试工具,生产环境会被Tree Shaking掉或者压缩成乱码,所以千万千万不要用ctx,要用proxy!

很多刚转Vue3的小伙伴可能会图省事,一开始在开发环境用ctx测试没问题,就直接上线了,结果用户一打开页面就白屏,控制台一堆“Cannot read property '$xxx' of undefined”的错误,排查半天还找不到原因,这就是踩了这个雷。

怎么避免?养成习惯——只要用getCurrentInstance,就只解构proxy,连ctx看都别看!比如刚才的useGlobalProperties组合式函数,我们只返回了proxy,根本没碰ctx。

雷区3:在非setup函数或其同步调用的组合式函数里使用,比如Options API的methods、mounted里

这个雷区虽然不如前两个常见,但偶尔也会有人犯——比如有人觉得Vue2的this用习惯了,突然转Vue3写了一半Options API,想加个Composition API的小功能,就直接在mounted或者methods里调getCurrentInstance,结果要么返回null要么报错。

为什么不行?因为Options API的生命周期钩子(除了beforeCreate和created,但created里其实也没必要用)都是在组件实例已经完全创建并挂载之后执行的,这时候虽然组件实例存在,但getCurrentInstance的“当前实例栈”机制已经不工作了(因为setup函数早就执行完了),所以返回null。

那如果非要在Options API的生命周期钩子或者methods里用到类似proxy的东西怎么办?直接用this啊!Vue3的Options API完全兼容Vue2的this,this就是proxy的别名(或者说完全一致的对象),

export default {
  data() {
    return {
      count: 0
    }
  },
  mounted() {
    // 直接用this访问全局属性或者$refs,别调getCurrentInstance
    this.$refs.inputRef.focus()
    this.$message.success('组件挂载完成')
  },
  methods: {
    add() {
      this.count++
      this.$axios.post('/api/count', { count: this.count })
    }
  }
}

对,Vue3的Options API还是原来的味道,不用强行混着Composition API用getCurrentInstance,反而容易出错。

深度思考:getCurrentInstance什么时候该用,什么时候不该碰?

刚才说了那么多场景和避坑,现在要总结一下——getCurrentInstance到底什么时候该用,什么时候不该碰?

该用的场景:

  1. 组合式函数里需要访问全局挂载的app.config.globalProperties属性(比如老项目的$axios、$message),且不想用provide/inject重构:这时候getCurrentInstance的proxy是最快的过渡方式,封装成useGlobalProperties组合式函数更优雅。
  2. 自己写的开源组合式库或者复杂业务组合式函数,需要在内部同步访问另一个自己写的组合式函数暴露的私有状态:这时候可以用setupState,但一定要加注释说明,而且要注意Vue3版本更新的影响,尽量用对外公开的API替代。
  3. 调试阶段需要快速查看组件的内部状态(比如setupState、props、attrs等):这时候可以用ctx,因为开发环境ctx的属性最全,最方便调试,但调试完一定要删掉。

不该碰的场景:

  1. 能用Vue3官方对外公开的API替代的场景:比如获取根节点DOM用ref绑定,访问父组件传递的props用defineProps,访问attrs用defineAttrs,触发事件用defineEmits,操作子组件用defineExpose+ref绑定,全局传递属性用provide/inject——这些都是官方推荐的、稳定的API,比getCurrentInstance安全多了。
  2. 生产环境的核心逻辑:不管是ctx还是setupState,都不是生产环境的稳定选择,千万千万不要碰。
  3. 异步回调里:除非提前在同步代码里把需要的东西存起来,否则别调getCurrentInstance。
  4. Options API的生命周期钩子或methods里:直接用this就行,没必要混着用。

最后再补充一点:Vue3.3+有没有对getCurrentInstance的优化或者替代方案?

哦对了,Vue3.3+版本确实有一些小优化,

  1. defineProps、defineEmits、defineAttrs、defineSlots这些宏,现在可以在组合式函数里直接用了:不过需要注意,这些宏必须在setup函数的顶层或者组合式函数的顶层同步调用,不能在异步回调或者条件语句里用。
  2. useAttrs、useSlots现在是官方对外公开的API了:Vue3早期版本useAttrs和useSlots是内部API,需要通过getCurrentInstance的ctx或者proxy访问,现在可以直接导入使用,更安全了。
  3. provide/inject现在支持类型推断了:如果用TypeScript的话,provide/inject的体验更好了,完全可以替代全局挂载的app.config.globalProperties属性,毕竟类型安全才是Vue3的核心优势之一。

那是不是Vue3.3+之后,getCurrentInstance就没用了?也不是,刚才说的几个该用的场景(比如老项目过渡、组合式库内部访问私有状态、调试)还是有用的,但确实用得越来越少了,毕竟官方对外公开的API越来越完善了。

今天我们从getCurrentInstance的来历、实际用起来的三个高频场景、三个最常见的雷区、什么时候该用什么时候不该碰,以及Vue3.3+的优化几个方面,把这个API讲得差不多了。

最后再给刚转Vue3的小伙伴一个建议:尽量先用官方对外公开的API(ref、reactive、defineProps、defineEmits、provide/inject等),getCurrentInstance能不用就不用,非用不可的话,一定要注意同步调用、只用proxy、别碰生产环境的核心逻辑

如果你还有其他关于Vue3的问题,欢迎在评论区留言,我们一起讨论!

版权声明

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

热门