《vue.js的设计与实现》12-14章小结

一、组件的渲染

一个有状态组件就是一个选项对象,如下所示MyComponent是一个组件:

const MyComponent = {
    name:'MyComponent',
    data(){
        return{
            foo:'hello world'
        }
    },
    //一个组件必须包括一个渲染函数,返回值为虚拟dom
    render(){
        return{
            type:'div',
            children:`foo的值是:${this.foo}`  //在渲染函数内使用组件状态
        }
    }
}

从渲染器的内部实现来看,一个组件是一个特殊类型的虚拟dom节点,type属性就是组件的选项对象:

const vnode = {
	type:MyComponent
}

渲染器的patch函数会对虚拟dom的type类型进行判断,如果是对象的话,就当成组件来处理,执行挂载组件mountComponent和更新组件patchComponent的操作。在挂载组件函数中,要调用reactive函数将data函数返回的状态包装为响应式数据,并且要将渲染函数包装为副作用函数,将render函数的this指向响应式数据state,这样在渲染函数内就能通过this拿到组件状态,并且当组件自身的响应式数据发生变化,组件就会重新执行渲染函数。

function mountComponent(vnode,container,anchor){
    const componentOptions = vnode.type
    const {render,data} = componentOptions
    const state = reactive(data())

    effect(()=>{
        const subTree = render.call(state,state)
        patch(null,subTree,container,anchor)
    })
}

现在effect的执行是同步的,多次修改响应式数据,将会导致渲染函数执行多次。为副作用函数指定一个调度器,当副作用函数需要重新执行时,不立即执行它,而是将它缓存到微任务队列,等执行栈清空后,再将其从微任务队列中取出并执行,这样无论对响应式数据进行多少次修改,副作用函数都会只重新执行一次。
这样做还存在的问题是现在patch函数的第一个参数是null,相当于每次发生更新都会进行全新的挂载。因此需要组件实例,更新的时候用新的subTree与上一次组件渲染的subTree打补丁就行。

二、组件实例

为了解决上述问题,在挂载组件函数中定义组件实例,并将实例设置到vnode上:

const instance = {
    //组件自身的状态数据,即data
    state,
    //用来表示组件是否已经被挂载
    isMounted:false,
    //组件所渲染的内容
    subTree:null
}

三、组件的props

对于一个组件来说,有两部分关于props的内容:
1、为组件传递的props数据,即组件的vnode.props对象
2、组件选项对象中定义的props选项,即MyComponent.props对象
props本质上是父组件传过来的数据,当props发生变化时,会触发父组件重新渲染,更新过程中,渲染器发现父组件的subTree包含组件类型的虚拟节点,就会调用子组件更新函数,这就叫子组件的被动更新

四、组件事件

在组件中用emit来发射自定义事件,在父组件中使用该组件,就可以监听由emit函数发射的自定义事件:

<MyComponent @change="handler" />
const CompVNode = {
    type:MyComponent,
    props:{
        onChange:handler
    }
}

自定义事件change被编译成名为onchange的属性,并存储在props数据对象中。

五、setup函数

组件的setup函数是vue3新增的组件选项,为了解决vue 中 data、computed、methods、watch 等内容非常多以后,同一业务逻辑的 data 中的数据和 methods 中的方法在 vue 文件中“相隔甚远”的问题。setup函数返回一个对象,该对象中包含的数据将暴露给模版使用。

六、异步组件

使用 Vue3 的 defineAsyncComponent 特性可以延迟加载组件,会在服务器需要时加载。使用动态导入语句import()来加载组件,会返回一个promise实例。defineAsyncComponent接收的参数包括异步组件加载器、超时时长、出错时要渲染的组件、延迟时间、loading组件。常用于让多个组件使用同一个挂载点,并动态切换。
这个函数的实现原理是设定时长为延迟时间的定时器,超过这个时间还没加载完就把loading置为true,渲染loading组件;如果加载超时,就渲染error组件,如果加载成功,渲染该异步组件;无论加载成功或失败,最后都要将loading置回false。

七、内建组件-KeepAlive

用KeepAlive包裹的内部组件可以对渲染的dom进行缓存,当组件切换时,不会重新卸载挂载dom,优化了性能。应用场景:当表单填写至一半跳转到别的页面,再跳转回来时刚刚填写的内容依然存在。
实现原理:
1、组件本身不会渲染额外的内容,因为它的渲染函数最终只返回被keepalive包裹的组件。默认插槽就是被包裹的组件,因此在setup函数中可以拿到。
2、使用map对象来实现对组件的缓存,键是vnode.type即组件选项对象,值是用于描述组件的vnode对象,缓存了这个对象就相当于缓存了组件实例。
3、KeepAlive组件的实例上会被添加两个内部函数_deactivate和_activate,如果有缓存的内容,在渲染器中就要调用_activate函数来激活它,激活的本质就是将组件的内容从隐藏容器搬回来,在卸载组件的操作中调用_deactivate函数使其失活。
4、KeepAlive组件支持三个 Props,分别是 include、exclude 和 max。如果子组件名称不匹配 include 的 vnode ,以及子组件名称匹配 exclude 的 vnode 都不应该被缓存。max表示能缓存组件的最大数量,缓存策略就是每次要将当前访问的组件作为最新组件,当缓存数量超过max时,删除最久没有访问过的组件。

八、内建组件-Teleport

用Teleport包裹的组件可以被指定挂载到to属性指定的父节点下,但逻辑仍保持原来的逻辑。应用场景:一个全屏模式的组件,用Teleport包裹能使它的样式不受父组件的影响。
实现原理:在patch函数中,Teleport组件的渲染逻辑被分离出来,这个组件的创建主要分为三步:1、在主视图里插入注释节点或者空白文本节点;2、获取目标元素节点;3、调用mount方法创建子节点往目标元素插入子节点。这个组件的更新操作包括更新子节点,处理 disabled 属性变化的情况,处理 to 属性变化的情况。

九、内建组件-Transition

用Transition包裹的组件可以为其添加入场/出场的过渡效果,Transition组件本身不会渲染任何额外的内容,它通过默认插槽读取过渡元素,并渲染需要过渡的元素。
实现原理:在过渡元素的vnode对象上添加与dom元素过渡相关的钩子函数。比如beforeenter钩子会在创建dom后,挂载dom前调用,在这个钩子函数中设置初始状态,添加enter-from和enter-active类;enter钩子在挂载dom后执行,在这个钩子函数中要在下一帧(类似于requestanimation效果,如果在同一帧那么就直接不绘制enter-from类的效果了)移除enter-from类,添加enter-to类,并监听transitioned事件,在过渡效果结束后删除enter-to和enter-active。leave钩子在卸载元素时调用,将卸载操作封装为一个函数,先调用leave钩子,再执行卸载操作。在leave钩子中先设置离场过渡的初始状态,添加leave-from类和leave-active类,在下一帧移除leave-from,添加leave-to,并监听transitioned事件,在过渡效果结束后删除leave-to和leave-active。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值