vue3中组件的更新 渲染逻辑和元素的逻辑相似,只不过组件中包裹着要渲染的元素,实际渲染的还是里面的元素,组件的状态改变,会被侦测到,通过响应式带动里面元素的更新
const { createVNode, render, h} = VueRuntimeDOM
const VueComponent = {
data ( ) {
return { name : 'zf' }
} ,
render ( ) {
return h ( 'button' , {
onClick : ( ) => {
this . name = 'jw'
}
} , h ( 'span' , this . name) )
}
}
render ( h ( VueComponent) , app)
组件的渲染入门也是在h函数中,本质上还是通过createVNode函数创建一个虚拟节点,那么则需要在createVNode中添加一个对组件的类型判断
let shapeFlags = isString ( type) ? ShapeFlags. ELEMENT : isObject ( type) ? ShapeFlags. STATEFUL_COMPONENT : 0
如果传入的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) ;
}
这里如果是组件类型,则直接进入到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)
}
}
组件初始化主要做三件事
创建一个组件实例 处理实例属性,将其变成侦测属性 将实例处理成响应式
function mountComponent ( vnode, container, anchor ) {
const instance = vnode. component = createComponentInstance ( vnode)
setupComponent ( instance)
setupRenderEffect ( instance, container, anchor)
}
虚拟节点的component上记录其实例,实例的vnode属性记录虚拟节点,双向记忆
export function createComponentInstance ( vnode ) {
let instance = {
data : null ,
vnode,
subTree : null ,
isMounted : false ,
update : null ,
render : null ,
propsOption : vnode. type. props || { } ,
props : { } ,
attrs : { } ,
proxy : null
}
return instance
}
这里的 props是用户传入的属性,但是其并不能直接在组件内部使用,因为组件内部定义的时候可以对属性进行限制,比如:
const VueComponent = {
props : {
name : String,
age : Number
} ,
data ( ) {
return { name : 'zf' }
} ,
render ( ) {
return h ( 'button' , {
onClick : ( ) => {
this . name = 'jw'
}
} , h ( 'span' , this . name) )
}
这里的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.' )
}
instance. data = reactive ( data . call ( { } ) )
}
instance. render = render
}
这里的props是用户传入的属性,在vnode中,通过initProps函数将其传给组件内部,并将其实现数据侦测,同时,将proxy上绑定对props,data,attrs的访问,实现数据侦测,后面render方法的this指向只需指向proxy即可。
function initProps ( instance, rawProps ) {
const props = { }
const attrs = { }
const options = instance. propsOption
if ( rawProps) {
for ( let key in rawProps) {
let value = rawProps[ key]
if ( key in options) {
props[ key] = value
} else {
attrs[ key] = value
}
}
}
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) {
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 ( )
} else {
instance. vnode = n2
}
}
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)
}