• 11. Vue3组件的更新渲染逻辑


    • vue3中组件的更新 渲染逻辑和元素的逻辑相似,只不过组件中包裹着要渲染的元素,实际渲染的还是里面的元素,组件的状态改变,会被侦测到,通过响应式带动里面元素的更新
    const {createVNode,render,h} =  VueRuntimeDOM
           // const {render,h} =  Vue
           const VueComponent = {
               data(){
                   return { name:'zf'}
               },
               render(){
                   return h('button',{
                       onClick:()=>{
                           this.name = 'jw'
                       }
                   },h('span',this.name))
               }
           }
           render(h(VueComponent),app)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 组件的渲染入门也是在h函数中,本质上还是通过createVNode函数创建一个虚拟节点,那么则需要在createVNode中添加一个对组件的类型判断
    //创建虚拟节点
       let shapeFlags = isString(type)? ShapeFlags.ELEMENT:isObject(type)?ShapeFlags.STATEFUL_COMPONENT:0
    
    • 1
    • 2
    • 如果传入的type是一个对象,则说明是组件,虚拟节点中的type属性对应的就是一个用户创建的组件对象.
    • render渲染中,组件和元素都要走pathch函数进行比对,在patch函数中添加对组件类型的比对
    if (shapeFlags & ShapeFlags.ELEMENT) {
                       processElement(n1, n2, container, anchor)
                   }else if(shapeFlags & ShapeFlags.STATEFUL_COMPONENT){
                       processComponent(n1,n2,container,anchor);
                   }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 这里如果是组件类型,则直接进入到processComponent函数中,此时的n14表示旧的组件虚拟节点,其值要么是null,要么组件类型和n2相同。
    • processComponent函数中需要对n1进行判断,如果为null,则直接初始化n2,让其挂载即可。如果不为null,则复用n1创建的元素,更新属性即可.
     //处理组件
       function processComponent(n1,n2,container,anchor){
           if(n1==null){
               //进入到初始化组件流程
               mountComponent(n2,container,anchor)
           }
           else {
               //进入到更新组件流程
               updateComponent(n1,n2)
           }
       }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 组件初始化主要做三件事
      • 创建一个组件实例
      • 处理实例属性,将其变成侦测属性
      • 将实例处理成响应式
      function mountComponent(vnode,container,anchor){
           //组件挂在前,需要产生一个组件的实例,组件的状态,组件的属性,组件对应的生命周期
           //然后将创建的实例保存在vnode上
           const instance = vnode.component = createComponentInstance(vnode)
           //处理组件属性
           setupComponent(instance)
           //给产生一个effect,使之变成响应式
           setupRenderEffect(instance,container,anchor)
       }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 虚拟节点的component上记录其实例,实例的vnode属性记录虚拟节点,双向记忆
    export function createComponentInstance(vnode){
       let instance = {
           data:null, //组件本身的数据
           vnode,
           subTree:null,//组件对应的渲染的虚拟节点
           isMounted:false,//组件是否 挂在过
           update:null,//组件的effect.run方法
           render:null,
           //组件内部定义的属性参数
           propsOption:vnode.type.props ||{},
           props:{},//组件传入的属性参数
           attrs:{},//用户没有接收的参数
           proxy:null//代理对象
       }
       return instance
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 这里的 props是用户传入的属性,但是其并不能直接在组件内部使用,因为组件内部定义的时候可以对属性进行限制,比如:
      const VueComponent = {
               props:{
                   name:String,
                   age:Number
               },
    
               data(){
                   return { name:'zf'}
               },
               render(){
                   return h('button',{
                       onClick:()=>{
                           this.name = 'jw'
                       }
                   },h('span',this.name))
               }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 这里的props就是实例中的propsOption属性,当用户给组件传入属性的时候,需要通过propsOption进行筛选,符合的保存到props中,不符合的保存到attrs中。
    • 实例创建好之后,需要处理实例的属性
    export function setupComponent(instance){
       let { type,props,children}  = instance.vnode
       let {data,render} = type
       //初始化属性
       initProps(instance,props);
       instance.proxy = new Proxy(instance,instanceProxy);
       if(data){
           if(!isFunction(data)){
               return console.warn('The data option must be a function.')
           }
           //给实例赋予data属性
           instance.data = reactive(data.call({}))
       }
       instance.render = render
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 这里的props是用户传入的属性,在vnode中,通过initProps函数将其传给组件内部,并将其实现数据侦测,同时,将proxy上绑定对props,data,attrs的访问,实现数据侦测,后面render方法的this指向只需指向proxy即可。
    function initProps(instance,rawProps){
       const props = {}
       const attrs = {}
       const options = instance.propsOption
       //让用户传入的数据分给props和attrs
       if(rawProps){
           for(let key in rawProps){
               let value = rawProps[key]
               if(key in options){
                   props[key] = value
               }else {
                   attrs[key] = value
               }
           }
       }
       //检测props数据
       instance.props = reactive(props)
       instance.attrs = attrs
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    //做代理
    const instanceProxy = {
       get(target,key){
           const { data,props} = target
           if(data && hasOwn(data,key)){
               return data[key]
           }else if(props && hasOwn(props,key)){
               return props[key]
           }
           let getter = publicProperties[key]
           if(getter){
               return getter(target)
           }
       },
       set(target,key,value,receiver){
           const {data,props} = target
           if(data && hasOwn(data,key)){
               data[key] = value
           }else if(props && hasOwn(props,key)){
               console.warn('props not update');
               return false
           }
           return true
       }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 这样数据已经实现了侦测,接下来只需要完成响应式的绑定,创建一个响应式函数,实现组件内部render方法的调用,这样就可以访问到内部数据,然后通过响应式组件传入该函数,初始化的时候调用一次即可。
     function setupRenderEffect(instance,container,anchor){
           const componentUpdate = ()=>{
               const {render,data} = instance
               if(!instance.isMounted){
                   //组件最终渲染的虚拟节点实际上是subTree
                   //调用render会做收集依赖,数据变化则重新调用update
                   // debugger
                   const subTree = render.call(instance.proxy)
                   patch(null,subTree,container,anchor)
                   instance.subTree = subTree
                   instance.isMounted = true
               }else {
                   //组件挂载过,走更新逻辑
                   let next = instance.next //表示新的虚拟节点
                   if(next){
                       //更新属性
                       updateComponentPreRender(instance,next)
                   }
                   const subTree = render.call(instance.proxy)
                   patch(instance.subTree,subTree,container,anchor)
                   instance.subTree = subTree
               }
           }
           const effect = new ReactiveEffect(componentUpdate)
           let update = instance.update = effect.run.bind(effect)
           update()
       }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 函数中的else流程是更新属性的操作,默认原组件和新组件一致,只需要更新属性,这里回到最初走组件更新的流程.
     function updateComponent(n1,n2){
           //拿到之前的组件
           const instance = n2.component = n1.component
           if(shouldComponentUpdate(n1,n2)){
               instance.next = n2;
               instance.update() //让effect重新执行
           }else {
               instance.vnode = n2
           }
       }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    function hasChange(preProps,nextPros){
           for(let key in nextPros){
               if(nextPros[key] != preProps[key]){
                   return true
               }
           }
           return false
       }
       //判断属性是否不同
       function shouldComponentUpdate(n1,n2){
           const prevProps = n1.props;
           const nextProps = n2.props
           return hasChange(prevProps,nextProps)
       }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 自此,组件的响应式更新,基本完成.
  • 相关阅读:
    Python 爬虫中文返回乱码
    单目目标检测
    selenium也能过某数、5s盾..
    图像标签的使用
    利用PHP开发具有注册、登陆、文件上传、发布动态功能的网站
    南卡和声阔真无线降噪耳机哪款更好?南卡和声阔蓝牙耳机测评
    运行obotframework-ride控制台报错module ‘urllib‘ has no attribute ‘Request‘
    嵌入式行业入行6年的一点小感想
    sysbench
    多模态大一统:开启全模态LLM和通用AI时代的大门
  • 原文地址:https://blog.csdn.net/wenyeqv/article/details/126437248