简单地在vue3+tsx中实现this.$refs.xxxx

1,847 阅读2分钟

场景:需要直接拿到子组件的一个方法,并直接执行它

子组件:

import { defineComponent } from "vue"

const PropsType = {
  age: {
    type: Number,
    required: true
  }
} as const
const consoleLog = () => {
  console.log("11111")
}
export default defineComponent({
  props: PropsType,
  setup(props) {
    return () => {
      return <div onClick={consoleLog}>{props.age}</div>
    }
  }
})

父组件:

import { ref } from "vue"
import Test from "./test"
export default defineComponent({
  setup() {
	
    const test = ref(null)

    const trickMethods = (): void => {
    	//想要这在这里通过ref直接执行子组件的 consoleLog 方法
    }


    return () => {
      return (
        <div>
          <Test ref={test} age={12} />
          <div>
            <h1 onClick={trickMethods}>Test Click</h1>
          </div>
        </div>
      )
    }
  }
})

都搞过optionsApi的我们一定都知道,我们先要拿到这个实例对象。

那么我们可不可以通一个方法,来拿取这个对象的实例呢?

首先,我们重新改造一下这个ref

构建一个简单的declare文件,定义一个类型别名RefInstanceType

declare type RefInstanceType<T> = {
  $: T
} | null

定义一个拥有consoleLog这个方法的接口

export interface ActionType {
  consoleLog: () => void
}

然后将const test = ref(null)修改,现在的父组件是这样的:

import { ref } from "vue"
import { ActionType } from "./testTypes"
import Test from "./test"
export default defineComponent({
  setup() {
	
    //const test = ref(null)
    const test = ref<RefInstanceType<ActionType>>(null)
    
    const trickMethods = (): void => {
    	//想要这在这里通过ref直接执行子组件的 consoleLog 方法
    }


    return () => {
      return (
        <div>
          <Test ref={test} age={12} />
          <div>
            <h1 onClick={trickMethods}>Test Click</h1>
          </div>
        </div>
      )
    }
  }
})

这个时候,在理论上,我们已经可以拿到这个实例对象了,但是打印之后,我们发现这个实例对象是被proxy是包裹的

写了一个简单的方法


import { unref } from "vue"
export function getRef(origin: any) {
  const unRefsType = unref(origin)
  if (!unRefsType) {
    throw new Error("you want to get ref is null!")
  }
  return unRefsType.$
}


这个时候,通过getRef(test)已经可以拿到这个实例了,此时的父组件:

import {ref } from "vue"
import Test from "./test"
import { ActionType } from "./testTypes"
import { getRef } from "@/utils/getRef"
export default defineComponent({
  setup() {
    const test = ref<RefInstanceType<ActionType>>(null)


    const trickMethods = (): void => {
      console.log(getRef(test))
    }


    return () => {

      return (
        <div>
          <Test ref={test} age={12} />
          <div>
            <h1 onClick={trickMethods}>Test Click</h1>
          </div>
        </div>
      )
    }
  }
})


但是,我们会发现 ,实例上并没有consoleLog这个方法……

我们可以通过getCurrentInstance这个API来访问内部组件实例

那么,我们就写一个方法,将子组件的方法绑定到由getCurrentInstance获取到的实例中

定义一个方法

import { getCurrentInstance } from "vue"

export function useFuncEmit(fn: (instance: any) => void):any {
  const instance = getCurrentInstance()

  if (instance) {
    fn.call(null, instance)
  }
}

然后在子组件中进行挂载

import { defineComponent } from "vue"
import { useFuncEmit } from "@/utils/vueHelper"

const PropsType = {
  age: {
    type: Number,
    required: true
  }
} as const
const consoleLog = () => {
  console.log("11111")
}
export default defineComponent({
  props: PropsType,
  setup(props) {
    return () => {
      useFuncEmit(Instance => {
        Instance.consoleLog = consoleLog
      })
      return <div onClick={consoleLog}>{props.age}</div>
    }
  }
})

此时父组件:

import {ref} from "vue"
import Test from "./test"
import { ActionType } from "./testTypes"
import { getRef } from "@/utils/getRef"
export default defineComponent({
  setup() {
    const test = ref<RefInstanceType<ActionType>>(null)

    const trickMethods = (): void => {
      getRef(test).consoleLog()
    }

    return () => {

      return (
        <div>
          <Test ref={test} age={12} />
          <div>
            <h1 onClick={trickMethods}>Test Click</h1>
          </div>
        </div>
      )
    }
  }
})