最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 一次在 Vue3 中使用render函数的事故,从源码中找到了答案

    正文概述 掘金(武康)   2020-12-09   779

    一次在 Vue3 中使用render函数的事故,从源码中找到了答案

    第一遍看 vue3 的源码,有不对的地方,希望各位大佬能指出,感谢!!!

    Vue3 版本 3.0.4

    Vue3.0 创建组件时,在 render 函数中使用 setup 函数中返回的 ref 数据,无法动态绑定

    示例:

    Vue.createApp({
        setup() {
            const elRef = Vue.ref(null)
            Vue.onMounted(() => {
            	elRef.value.innerHTML = "123"
            })
            return {
            	elRef
            }
        },
        render() {
            return Vue.h("div", {
                ref: this.elRef
            })
        }
    }).mount("#app")
    

    浏览器报错

    一次在 Vue3 中使用render函数的事故,从源码中找到了答案

    查找问题

    1、找到 render 函数的执行

    源码位置:github.com/vuejs/vue-n… 77行

    删减后的代码:

    export function renderComponentRoot(
      instance: ComponentInternalInstance
    ): VNode {
      const {
        proxy,
        withProxy,
        props,
        render,
        renderCache,
        data,
        setupState,
        ctx
      } = instance
    
      let result
      try {
        let fallthroughAttrs
        if (vnode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT) {
    	  const proxyToUse = withProxy || proxy
          result = normalizeVNode(
            render!.call(
              proxyToUse,
              proxyToUse!,
              renderCache,
              props,
              setupState,
              data,
              ctx
            )
          )
          
          fallthroughAttrs = attrs
        } else {
          // 函数组件
        }
      return result
    }
    

    render 函数在执行的时候,将 this 改变成了 proxy,(在生产环境是通过在 with 块中执行改变)

    2、找到 proxy 的定义

    源码位置 github.com/vuejs/vue-n… 565行

    删减后的代码:

    function setupStatefulComponent(
      instance: ComponentInternalInstance,
      isSSR: boolean
    ) {
      const Component = instance.type as ComponentOptions // type 就是组件的options
      instance.accessCache = Object.create(null)
      // proxy 在这里定义 
      instance.proxy = new Proxy(instance.ctx, PublicInstanceProxyHandlers)
      // setup 方法在这里执行
      const { setup } = Component
      if (setup) {
        const setupContext = (instance.setupContext =
          setup.length > 1 ? createSetupContext(instance) : null)
    
        currentInstance = instance
        pauseTracking()
        const setupResult = callWithErrorHandling( // 这个方法内部只是使用 try catch 捕捉 setup 函数时的异常
          setup,
          instance,
          ErrorCodes.SETUP_FUNCTION,
          [__DEV__ ? shallowReadonly(instance.props) : instance.props, setupContext]
        )
        handleSetupResult(instance, setupResult, isSSR) // 这里处理 setup 的返回值
      }
    }
    

    PublicInstanceProxyHandlersget 方法的处理

    源码位置:github.com/vuejs/vue-n… 143行

    删减后的代码:

    export const PublicInstanceProxyHandlers: ProxyHandler<any> = {
      get({ _: instance }: ComponentRenderContext, key: string) {
        const {
          ctx,
          setupState,
          data,
          props,
          accessCache,
          type,
          appContext
        } = instance
        let normalizedProps
        if (key[0] !== '$') {
          // 如果缓存里有 key 对应的数据类型,就不需要在判断 key 的来源
          const n = accessCache![key]
          if (n !== undefined) {
            switch (n) {
              case AccessTypes.SETUP:
                return setupState[key]
              case AccessTypes.DATA:
                return data[key]
              case AccessTypes.CONTEXT:
                return ctx[key]
              case AccessTypes.PROPS:
                return props![key]
            }
          } else if (setupState !== EMPTY_OBJ && hasOwn(setupState, key)) {
            // 缓存中没有 key 的数据来源,把 key 的数据来源存入到缓存中,返回数据
            accessCache![key] = AccessTypes.SETUP
            return setupState[key]
          }
        }
      }
    }
    

    这里返回的 setupState 的值,setupState 也是一个 proxy 的类型,在 setup 执行完成后创建

    源码位置:github.com/vuejs/vue-n… 610行

    删减后的代码:

    export function handleSetupResult(
      instance: ComponentInternalInstance,
      setupResult: unknown,
      isSSR: boolean
    ) {
      if (isFunction(setupResult)) {
        // setup 中返回一个函数,这个函数作为render函数使用
        instance.render = setupResult as InternalRenderFunction
      } else if (isObject(setupResult)) {
        // 这里定义 setupState
        instance.setupState = proxyRefs(setupResult) // proxyRefs 函数返回一个 proxy 对象
      }
      finishComponentSetup(instance, isSSR)
    }
    

    proxyRefs 函数 返回一个 proxy 对象,这个 proxy 对象的 get 方法,会返回 ref 数据的 value

    源码位置: github.com/vuejs/vue-n… 105行

    删减后的代码:

    export function unref<T>(ref: T): T extends Ref<infer V> ? V : T {
      // 会返回 ref 数据的 value 属性,而不是整个对象
      return isRef(ref) ? (ref.value as any) : ref
    }
    
    const shallowUnwrapHandlers: ProxyHandler<any> = {
      get: (target, key, receiver) => unref(Reflect.get(target, key, receiver)),
    }
    
    export function proxyRefs<T extends object>(
      objectWithRefs: T
    ): ShallowUnwrapRef<T> {
      return isReactive(objectWithRefs)
        ? objectWithRefs
        : new Proxy(objectWithRefs, shallowUnwrapHandlers)
    }
    

    到这里,就可以知道 为什么在 render 函数中使用 this. 的方法获取到的不是 ref 对象,而是原始值了

    原因总结:

    render 方法中使用 this. 的方式获取的是 setup 中返回的 ref 数据的 value 属性,所以不能给元素做动态绑定

    怎么解决:

    删减后的代码:

    export function handleSetupResult(
      instance: ComponentInternalInstance,
      setupResult: unknown,
      isSSR: boolean
    ) {
      if (isFunction(setupResult)) {
        // setup 中返回一个函数,这个函数作为render函数使用
        instance.render = setupResult as InternalRenderFunction
      } else if (isObject(setupResult)) {
        // 这里定义 setupState
        instance.setupState = proxyRefs(setupResult) // proxyRefs 函数返回一个 proxy 对象
      }
      finishComponentSetup(instance, isSSR)
    }
    

    setup 的函数返回值是一个函数的时候,这个函数会被作为 render 函数处理(这个返回值会覆盖在 opitons 中的 render 函数)

    所以我们之前的示例就可以改成下面这个样子:

    Vue.createApp({
        setup() {
            const elRef = Vue.ref(null)
            Vue.onMounted(() => {
            	elRef.value.innerHTML = "123"
            })
            return () =>  Vue.h("div", {
            	ref: elRef
            })
        }
    }).mount("#app")
    

    一次在 Vue3 中使用render函数的事故,从源码中找到了答案

    页面中就可以正确的显示内容了

    总结:

    setup 中可以返回一个 对象 或者 函数,当返回 函数是,这个函数就是这个组件的 render 函数

    当返回 对象的时候,这个对象的数据通过 this 获取值的时候,只能获取到 .value 的值,获取不到 ref 对象


    起源地下载网 » 一次在 Vue3 中使用render函数的事故,从源码中找到了答案

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元