实现mini-vue -- runtime-core模块(十二)实现getCurrentInstance

299 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第16天,点击查看活动详情

本篇文章是给之后实现provide/inject功能做铺垫的,我们会实现vue3中的一个API -- getCurrentInstance,它能够允许我们在setup中获取到当前组件实例对象,有了它,之后实现provide/inject会很方便

1. 思考实现方式

getCurrentInstance用于获取内部组件实例,它只能在setup或生命周期钩子当中调用

这是vue3官方文档中对getCurrentInstance的描述,根据这个描述,我们可以思考一下如何实现 既然只能在setup中调用,那么肯定要找到调用setup的地方,设置一个全局变量currentInstance,然后在调用setup之前将该变量指向当前的组件实例,那么在执行setup的时候,用户在setup中调用getCurrentInstance时,就可以获取到这个标记变量指向的组件实例了 思路还是比较直接的,接下来就实现它吧!


2. 实现

该功能明显是和组件有关的,因此在component.ts中实现,创建一个全局变量currentInstance用于记录当前组件实例,以及函数getCurrentInstance用于获取当前组件实例

let currentInstance = null;

export function getCurrentInstance() {
  return currentInstance;
}

函数的实现很简单,直接返回全局变量的值即可 然后在index.ts中导出,从而能够让rollup进行打包

export { createApp } from './createApp';
export { h } from './h';
export { renderSlots } from './helpers/renderSlots';
export { createTextVNode } from './vnode';
+ export { getCurrentInstance } from './component';

还需要在setupStatefulComponent函数中执行setup之前将currentInstance指向当前组件实例,并在执行完setup之后将其置空

function setupStatefulComponent(instance: any) {
  const Component = instance.type;
  const { setup } = Component;

  // ctx -- context
  instance.proxy = new Proxy({ _: instance }, PublicInstanceProxyHandlers);

  if (setup) {
+    currentInstance = instance;
    const setupResult = setup(shallowReadonly(instance.props), {
      emit: instance.emit.bind(null, instance),
    });
+    currentInstance = null;

    // setupResult 可能是 function 也可能是 object
    // - function 则将其作为组件的 render 函数
    // - object 则注入到组件的上下文中
    handleSetupResult(instance, setupResult);
  }
}

现在整个getCurrentInstance就算实现完了,接下来通过一个简单的demo测试一下


3. 通过案例测试

首先是App.js

{ getCurrentInstance, h } from '../../lib/plasticine-mini-vue.esm.js';
import { Foo } from './Foo.js';

export const App = {
  name: 'App',
  setup() {
    const currentInstance = getCurrentInstance();
    console.log('App: ', currentInstance);
  },
  render() {
    return h('div', {}, [h('div', {}, 'App'), h(Foo)]);
  },
};

setup中调用getCurrentInstance获取到组件实例并打印出来 然后是Foo.js

import { getCurrentInstance, h } from '../../lib/plasticine-mini-vue.esm.js';

export const Foo = {
  name: 'Foo',
  setup() {
    const currentInstance = getCurrentInstance();
    console.log('Foo: ', currentInstance);
  },
  render() {
    return h('div', {}, 'Foo');
  },
};

同样也是在setup中调用getCurrentInstace并打印 image.png 可以看到成功获取到了


4. 重构

虽然实现的方式比较简单,代码也比较少,但仍然有一个地方可以重构,那就是currentInstance = instance这一句代码,就这样一句代码有什么好重构的呢? 思考一下,实际使用的时候是会有很多组件的,那么currentInstance = instance这句代码就会被执行很多次,而万一有那么一次,这个赋值出错了,是不利于我们追踪出错的地方的 但是如果用一个setCurrentInstance函数去封装,情况就不一样了,我们可以直接在这个函数内打上断点,就可以知道哪个地方出错了

function setupStatefulComponent(instance: any) {
  const Component = instance.type;
  const { setup } = Component;

  // ctx -- context
  instance.proxy = new Proxy({ _: instance }, PublicInstanceProxyHandlers);

  if (setup) {
-		currentInstance = instance;
+   setCurrentInstance(instance);
    const setupResult = setup(shallowReadonly(instance.props), {
      emit: instance.emit.bind(null, instance),
    });
-		currentInstance = null;
+   setCurrentInstance(null);

    // setupResult 可能是 function 也可能是 object
    // - function 则将其作为组件的 render 函数
    // - object 则注入到组件的上下文中
    handleSetupResult(instance, setupResult);
  }
}

+ function setCurrentInstance(instance) {
+   currentInstance = instance;
+ }