实现 this 指向
        
            
            
              ts
              
              
            
          
          import { h } from '../lib/guide-mini-vue.esm.js';
export const App = {
  name: 'App',
  render() {
    window.self = this;
    return h('div', { id: 'app' }, [
      h('p', { class: 'red' }, 'Hello' + this.msg),
    ]);
  },
  setup() {
    return {
      msg: 'jerry'
    }
  }
}在这个例子中,我们需在 render 中实现 this 的指向,在 setupRenderEffect 调用 instance.render 时,理论上是让 render 函数指向 setupState。
setupState 指的是 setup 函数的返回结果
            
            
              ts
              
              
            
          
          // 伪代码
instance.render.call(setupState);实现
在 createComponentInstance 这里,给 instance 初始化了 setupState 属性
            
            
              ts
              
              
            
          
          function createComponentInstance(vnode) {
    const component = {
        setupState: {},
    }
}在 setupStatefulComponent 这里,实现组件对象的代理
            
            
              ts
              
              
            
          
          // runtime-core/component.ts
function setupStatefulComponent(instance) {
  const Component = instance.type;
  const { setup } = Component;
  
  // 实现组件对象的代理
  instance.proxy = new Proxy({ _: instance }, PublicInstanceProxyHandlers)
  if(setup) {
    const setupResult = setup();
    // 处理 setup 函数的返回结果
    handleSetupResult(instance, setupResult);
  }
}
            
            
              ts
              
              
            
          
          // runtime-core/componentPublicInstance.ts
export const PublicInstanceProxyHandlers = {
  get({ _: instance }, key) {
    const { setupState, props } = instance;
    
    const hasOwn = (val, key) => Object.prototype.hasOwnProperty.call(val, key);
    if(hasOwn(setupState, key)) {
      return setupState[key];
    }
  }
}回顾 runtime-core 的流程中,等到 setupRenderEffect 执行时,已完成实例对象 proxy 的绑定。
            
            
              ts
              
              
            
          
          function mountComponent() {
    createComponentInstance();
    setupComponent();
    setupRenderEffect();
}在 setupRenderEffect 内调用 instance.render 时,把实例的代理对象 proxy 绑定到 render,那么 render 函数内的 this 便指向了 instance.proxy
            
            
              ts
              
              
            
          
          // rumtime-core/renderer.ts
function setupRenderEffect(instance) {
    const { proxy } = instance;
    const subTree = instance.render.call(proxy);
    patch(subTree, container);
}这里需要注意的是,被 Proxy 代理的目标对象是 instance,但是我们在 render 函数内部访问 this.msg 的时候,不是访问 instance 的属性,而是 instance.proxy 的属性。
在访问 this.msg 的时候,被 Proxy 拦截处理,内部的机制是,从 instance.setupState 里面获取 msg 属性值并返回。
            
            
              ts
              
              
            
          
          const { setupState, props } = instance;
if(hasOwn(setupState, key)) { 
    return setupState[key]; 
}$el
vnode 的 el
        
            
            
              ts
              
              
            
          
          function createVNode(type, props?, children) {
    const vnode = {
        el: null,
    }
}开始给 el 赋值是在 mountElement 函数内部处理。
            
            
              ts
              
              
            
          
          function mountElement(vnode, container) {
    const el = vnode.el = document.createElement(vnode.type); // 开始给 el 赋值
}
            
            
              ts
              
              
            
          
          function mountComponent(initialVNode, container) {
    const instance = createComponentInstance(initialVNode)
    setupComponent(instance);
    // 把 initialVNode 传给 setupRenderEffect
    setupRenderEffect(instance, initialVNode, container);
}
            
            
              ts
              
              
            
          
          function setupRenderEffect(instance, initialVNode, container) {
    const subTree = instance.render.call(proxy);
    patch(subTree, container);
    
    // 经过 patch 之后,subTree 获得了 el
    initialVNode.el = subTree.el;
}为什么是 initialVNode.el = subTree.el; 呢?
vnode 和 subTree 的区别
比如,在这个例子中:
            
            
              js
              
              
            
          
          const Child = {
    name: 'Child',
    render() {
        return h('div', {}, 'child')
    }
};
const App = {
    name: 'App',
    render() {
        return h('div', { class: 'app' }, [
            h(Child, {})
        ])
    }
}App 的 el 应该是
            
            
              html
              
              
            
          
          <div class="app">
    <div class="child">child</div>
</div>Child 的 el 应该是:
            
            
              html
              
              
            
          
          <div class="child">child</div>App vnode 的 el
一开始 App 传给 createVNode 得到了一个 App 的 initialVnode,
            
            
              ts
              
              
            
          
          {
    type: App,
    props: {},
    children,
    el: null,
}initialVnode 传给了 render
接着 patch(initialVnode)
接着 mountComponent(initialVnode)
接着来到 setupRenderEffect(initialVnode)
接着执行 App instance 的 render,得到 subTree
            
            
              ts
              
              
            
          
          // App vnode 的 subTree
subTree = h('div', { class: 'app' }, [
    h(Child, {})
])h 函数返回的是:
            
            
              ts
              
              
            
          
          {
    type: 'div',
    props: {
        class: 'app'
    },
    children: h(Child, {}),
    el: null,
}也就是 subTree 指的是:
            
            
              ts
              
              
            
          
          {
    type: 'div',
    props: {
        class: 'app'
    },
    children: h(Child, {}),
    el: null,
}接着 patch(subTree)
接着 mountElement(subTree)
接着 subTree.el = document.createElement('div')
至此,patch 完成
接着 initialVnode.el = subTree.el
最后,initialVnode 即 App 对应的这个 vnode 的 el 为 document.createElement('div')
Child vnode 的 el
在第二次 patch 后,根据 type='div'
来到 mountElement
接着判断到 vnode.children 是数组
接着 mountChildren
接着再次 patch
            
            
              ts
              
              
            
          
          mountChildren(vnode) {
    vnode.children.forEach(v => {
        patch(v);
    })
}这里,v 表示 Child 的 vnode
根据 type=Child
接着来到 mountComponent,这时的 initialVnode 为 Child 的 vnode
            
            
              ts
              
              
            
          
          initialVnode = {
    type: Child,
    props: {},
    children: {},
    el: null,
}接着来到 setupRenderEffect
接着执行 Child 的 render得到 Child 的 subTree
            
            
              ts
              
              
            
          
          const subTree = instance.render.call(proxy);即
            
            
              ts
              
              
            
          
          subTree = h('div', { class: 'child' }, 'child')然后再 patch
最终 Child vnode 的 el 是 <div class="child">child</div>
el 挂载到 instance.proxy 代理对象
        
            
            
              ts
              
              
            
          
          const PublicPropertiesMaps = {
  $el: (i) => i.vnode.el,
}
export const PublicInstanceProxyHandlers = {
  get({ _: instance }, key) {
    const { setupState, props } = instance;
    
    // 省略...
    // 在这里统一处理 $el、$props 等对象
    const publicGetter = PublicPropertiesMaps[key];
    if(publicGetter) {
      return publicGetter(instance);
    }
  }
}这样,在 render 中访问 this.$el,就可获取到组件对应的 el。