Vue中this是怎么直接拿到数据的?

Vue中this是怎么直接拿到数据的?

本文是 Vue3 源码实战专栏第 13 篇,实现组件代理对象。

前言

Vue 项目开发中,在data中定义的数据可以通过this获取,甚至this上也挂载着$data$el,方便用户开发时直接使用this操作。

例如,$el 官方文档 中描述:

该组件实例管理的 DOM 根节点

根据上一篇文章《createApp之后发生了什么?》中示例,修改App.jsApp

window.self = null;
export const App = {
  render() {
    window.self = this;
    return h("div""hello " + this.msg);
  },
  setup() {
    return {
      msg"world",
    };
  },
};

运行项目,页面上显示hello undefined

本文最终需要实现的就是能够在页面上显示出hello world,在控制台输入self.$el输出divDOM。

代理数据

setup中返回的数据,还有官网中罗列的$el$data等都可以通过this访问,底层是使用了Proxy作为代理,当组件初始化的时,设置了代理对象。

数据的代理实现更简单,我们先来实现。

组件初始化的方法是setupStatefulComponent,将Proxy挂载在组件实例上。

component.ts中,

function setupStatefulComponent(instance{
  const Component = instance.type;

  instance.proxy = new Proxy({}, {
      get(target, key) {
        const { setupState } = instance;
        if (key in setupState) {
          return Reflect.get(setupState, key);
        }
      },
  });

  const { setup } = Component;
  if (setup) {
    const setupResult = setup();
    handleSetupResult(instance, setupResult);
  }
}

上面代码,在实例instance上挂载了代理对象,当触发get时,可以获取到setup的返回值setupState对象,key就是需要访问的属性,也就是示例中msg

组件实例上挂载了proxy,在render函数调用时,把函数的this指向proxy就能通过this拿到数据了。

renderer.ts中,

function setupRenderEffect(instance, container{
  const { proxy } = instance;
  const subTree = instance.render.call(proxy);
  patch(subTree, container);
}

此时,重新打包启动项目,就可以在页面上看到了hello world

$el

实现$el,可以在控制台通过self.$el获取到div的 DOM 结构。

想要获取到el,需要在创建的时候缓存elel的创建是在mountElement方法中。

renderer.ts

function mountElement(vnode, container{
  const { type, children } = vnode;
  let el = (vnode.el = document.createElement(type));
  ...
}

将创建的el,挂载到虚拟节点vnode上。

那在访问时还是通过代理实现,即key等于$el时把挂载的el返回出去,再次回到setupStatefulComponent方法,

instance.proxy = new Proxy({}, {
    get(target, key) {
      const { setupState } = instance;
      if (key in setupState) {
        return Reflect.get(setupState, key);
      }

      if(key === '$el') {
        return instance.vnode.el
      }
    },
});

但需要注意的是,这两处的el并不是同一个,mountElement中保存在虚拟节点上的elelement,也就是示例中的div;而instancevnode上的el,其实并不存在,因为这儿是组件实例上虚拟节点。

在组件初始化中,最终是会调用setupRenderEffect方法,该方法中会调用patch,再执行一遍初始化,此时就是element的逻辑,那虚拟节点subTree上的el就是在创建element时绑定vnode上的el。这时也就是在patch之后,将这个el绑定到组件vnode上,在代理触发$el返回就能拿到组件实例上vnodeel

renderer.ts中,

function mountComponent(initialVnode, container{
  const instance = createComponentInstance(initialVnode);
  setupComponent(instance);
  setupRenderEffect(instance, initialVnode, container);
}

function setupRenderEffect(instance, initialVnode, container{
  const { proxy } = instance;
  const subTree = instance.render.call(proxy);
  patch(subTree, container);

  initialVnode.el = subTree.el;
}

再次执行打包启动项目,在浏览器控制台中可以打印出self.$el

重构

setupStatefulComponent中实现代理的逻辑,抽离出来。

新建文件componentPublicInstance.ts

const publicPropertiesMap = {
  $el: (i) => i.vnode.el,
};

export const PublicInstanceProxyHandlers = {
  get({ _: instance }, key) {
    const { setupState } = instance;
    if (key in setupState) {
      return Reflect.get(setupState, key);
    }

    const proxyGetter = publicPropertiesMap[key];
    if (proxyGetter) {
      return proxyGetter(instance);
    }
  },
};

上面代码,通过target对象传入instance参数;定义一个publicPropertiesMap对象,方便后续$data$props等的扩展。

component.tssetupStatefulComponent方法相应修改,

function setupStatefulComponent(instance{
  const Component = instance.type;

  instance.proxy = new Proxy({ _: instance }, PublicInstanceProxyHandlers);

  const { setup } = Component;
  if (setup) {
    const setupResult = setup();
    handleSetupResult(instance, setupResult);
  }
}

总结

无论是setup返回的数据还是data数据,还有$data$el…都是通过Proxy代理,在组件初始化阶段执行。代理数据的实现是将setup执行结果setupState中数据返回,再在渲染render时绑定this指向;像$el这类便于用户开发使用的,同样是在Proxy中处理,根据不同的key处理不同的逻辑。

Vue中this是怎么直接拿到数据的?


原文始发于微信公众号(前端一起学):Vue中this是怎么直接拿到数据的?

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/191236.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!