19-mini-vue setup $el $data $props

实现组件代理对象

实现 this 访问 setup 数据

  1. 其实我们要实现的功能是我们在模板里面要能够访问到 this,通过 this 拿到 setup 里面挂载的对象,
    下一步而且可以通过 this.$el 可以获取当前组件的 div,我们还需要 兼容 vue2 通过 this.$data.x 获取属性
  2. 难道是我们把 render 的this绑定到 setup 对象上了吗?
  3. 其实是做了一层代理
  • 我们创建 comopnent 时,创建一层代理对象,访问时如果 key 在 setupState 上,返回对应的 value
  • 当我们调用我们的 render 函数时,this指向将我们创建好的 proxy 对象,这样在 render 函数里面,我们也能访问到 setup 里面的值
  1. 代码
js 复制代码
// component.ts
export function createComponentInstance(vnode) {
  const component = {
    vnode,
    type: vnode.type,
    setupState: {} // ✅ 存放 setup 返回的状态
  }
  return component
}
function setupStatefulComponents(instance: any) {
  const Component = instance.vnode.type
  const { setup } = Component
  instance.proxy = new Proxy({}, { // ✅ 做一层代理,拦截 get,我们通过 this 访问属性时,就会走这里
    get(target, key) {
      let { setupState } = instance // 这里的解构要注意在 get 里面,不然拿到的就是空对象
      if(key in setupState) {
        return setupState[key]
      }
    }
  })
  if(setup) {
    const setupResult = setup()
    handleSetupResult(instance, setupResult) // 将状态结果进心处理
  }
}
// renerer.ts
function setupRenderEffect(instance, container) {
  const { proxy } = instance
  const subTree = instance.render.call(proxy) // ✅ 将render 的 this 指向 proxy,这样在 render 里面访问 this.msg 就能拿到值
  // vnode  -> patch
  // vnode -> element -> mountElement
  patch(subTree, container)
}

实现 this.$el

  1. 思路
  • 我们在 mounteElement 这个函数里面创建了一个 el,我们可以挂在 虚拟dom上面,然后当在 this 中访问时,按照 proxy 拦截到 key 进行赋值

    js 复制代码
    // vnode.ts
    export function createVNode(type, props?, children?) {
      const vnode = {
        type,
        props,
        children,
        el: null  // ✅
      }
      return vnode
    }
    //renderer.ts
    function mountElement(vnode, container) {
      const { type, props, children } = vnode
      // 把 el 挂载在虚拟节点上
      const el = (vnode.el =  document.createElement(type)) // ✅
      if (typeof children === 'string') {
        el.textContent = children
      } else if (Array.isArray(children)) {
        mountChildren(children, el)
      }
      for (let key in props) {
        let val = props[key]
        el.setAttribute(key, val)
      }
      container.append(el)
    }
  1. 测试
  • 思路: 我们没有实现事件,所以想要通过点击事件获取 this.$el 行不通,可以进行this赋值,然后通过 window.self.$el 直接在浏览器控制台来测试有么有成功

    js 复制代码
    import {h} from '../lib/guide-mini-vue.esm.js'
    window.self = null // ✅
    export const App = {
      // .vue
      // template
      // render
      render() { // render 必须写,不然无法渲染
        // this 赋值 // ✅
        window.self = this 
        return h("div",{
          id: 'root',
          class: ['red', 'hard']
        },
        // 实现 this 挂载变量,还可以 this.$el 可以获取当前组件的 div
        'hello'+this.msg
        // 实现 h 函数
        // [h("p",{class:"red"}, "hi"), h("p",{class:"blue"},"mini-vue")]
        )
      },
      setup() {
        // compsition api
        return {
          msg: "mini-vue-haha",
        }
      }
    }
  • 发现 window.self.$el 是 null ,我们在 if(key === '$el')上面 debugger,可以看到 vnode.type 是一个对象,然后children,props,都没有值,其实这是一个组件,所以我们赋值挂载 el 的虚拟节点,与我们proxy 拦截的 $el 不是一个虚拟节点,我们是在 mountElement 这个函数里面挂载的 $el,所以我们得在element相关逻辑挂载完毕以后,再进行挂载,其实在 ---->

js 复制代码
function setupRenderEffect(instance, vnode, container) {
  const { proxy } = instance
  const subTree = instance.render.call(proxy)
  // vnode  -> patch
  // vnode -> element -> mountElement
  patch(subTree, container) 
  // 这里 element 已经处理完毕 ✅,可以挂载 el 到 虚拟节点上  <------
  vnode.el = subTree.el
}
  • 总结一下,我们从 main.js 顺着入口进入,一开始总要先把根节点整上去吧,那此时vnode 还没渲染里面的子节点,也就是走 mounteElement 这个函数,所以el 还是 null,等到 patch 走完 mountElement 以后,el 就有值了,我们再把 el 挂载到 根节点的 vnode 上,这样我们在 proxy 里面访问 $el 的时候,就能拿到值了

data props 等等也需要处理

  1. 思路:我们得把这块属性拦截的逻辑抽离出来,对 data props 做映射,后续只要在映射对象里面添加 key value ,就可以实现相应功能
  2. 代码实现
js 复制代码
// component.ts
function setupStatefulComponents(instance: any) {
  const Component = instance.vnode.type
  const { setup } = Component
  // 注意:这里的虚拟节点是属于 component 的
  instance.proxy = new Proxy({_: instance}, PublicInstanceProxyHandlers) // get 逻辑抽离 // ✅
  if(setup) {
    const setupResult = setup()
    handleSetupResult(instance, setupResult) // 将状态结果进心处理
  }
}
// componentPublicInstance.ts // ✅
const publicPropertiesMap = {
  $el:(i)=>i.vnode.el
}

export const PublicInstanceProxyHandlers = {
  get({_: instance}, key) {
    let { setupState } = instance
    if(key in setupState) {
      return setupState[key]
    }
    // key -> $el
    // if(key === '$el') {
    //   return instance.vnode.el
    // }
    const publicGetter = publicPropertiesMap[key]
    if(publicGetter) {
      return publicGetter(instance)
    }
  }
}
  1. 易错问题
  • elementMount 挂载完毕,对根节点 vnode.el 进行赋值,不要搞反了 vnode.el = subTree.el
  • rollup.config.js 里面 import pkg from './package.json' with { type: 'json' }; 需要配置 type: 'json',不然会报错, 打包不动
相关推荐
xkxnq1 天前
第一阶段:Vue 基础入门(第 10 天)
前端·javascript·vue.js
智商偏低1 天前
abp PermissionDefinitionManager源码解析
开发语言·前端·javascript
lgliuying1 天前
wangEditor5 富文本编辑器中使用 kityformula 公式编辑器的具体实践
前端·javascript·html
Benny的老巢1 天前
基于Playwright TypeScript/JavaScript的API调用爬虫成熟方案
javascript·爬虫·typescript·自动化·agent·playwright
zpjing~.~1 天前
检查元素内部是否存在具有特定类名的子元素的方法
前端·javascript·html
一个很帅的帅哥1 天前
nums.sort()和nums.sort((a, b) => a - b)
javascript
满天星辰1 天前
Vue.js的优点
前端·vue.js
满天星辰1 天前
使用 onCleanup处理异步副作用
前端·vue.js
POLITE31 天前
Leetcode 142.环形链表 II JavaScript (Day 10)
javascript·leetcode·链表