Appearance
组件的挂载流程
组件需要提供一个render函数,渲染函数需要返回虚拟DOM
const VueComponent = {
data(){
return {age:13}
},
render(){
return h('p',[h(Text,"I'm Jiang sir"),h('span',this.age+'')])
}
}
createRenderer(renderOptions).render(h(VueComponent),document.getElementById('app'))
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
添加组件类型
h方法中传入一个对象说明要渲染的是一个组件。(后续还有其他可能)
export const createVNode = (type,props,children = null)=>{
const shapeFlag = isString(type)
? ShapeFlags.ELEMENT: isObject(type)
? ShapeFlags.STATEFUL_COMPONENT:0;
// ... 稍后可以根据类型来进行组件的挂载
}
1
2
3
4
5
6
2
3
4
5
6
组件的渲染
const patch = (n1,n2,container,anchor?) => {
// 初始化和diff算法都在这里喲
if(n1 == n2){return }
if(n1 && !isSameVNodeType(n1,n2)){ // 有n1 是n1和n2不是同一个节点
unmount(n1)
n1 = null
}
const {type,shapeFlag} = n2;
switch(type){
// ...
default:
if(shapeFlag & ShapeFlags.ELEMENT){
processElement(n1,n2,container,anchor)
}else if(shapeFlag & ShapeFlags.COMPONENT){
processComponent(n1,n2,container,anchor)
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const mountComponent = (n2,container,anchor)=>{
const {render,data=()=>({})} = n2.type;
const state = reactive(data())
const instance = {
state, // 组件的状态
isMounted:false, // 组件是否挂载
subTree:null, // 子树
update:null,
vnode:n2
}
const componentUpdateFn = ()=>{
if(!instance.isMounted){
const subTree = render.call(state,state);
patch(null,subTree,container,anchor);
instance.subTree = subTree
instance.isMounted = true;
}else{
const subTree = render.call(state,state);
patch(instance.subTree,subTree,container,anchor)
instance.subTree = subTree
}
}
const effect = new ReactiveEffect(componentUpdateFn)
const update = instance.update = effect.run.bind(effect);
update();
}
const processComponent = (n1,n2,container,anchor)=>{
if(n1 == null){
mountComponent(n2,container,anchor);
}else{
// 组件更新逻辑
}
}
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
28
29
30
31
32
33
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
28
29
30
31
32
33
组件异步渲染
修改调度方法,将更新方法压入到队列中
const effect = new ReactiveEffect(
componentUpdateFn,
()=>queueJob(instance.update)
);
const update = instance.update = effect.run.bind(effect);
1
2
3
4
5
2
3
4
5
批处理操作scheduler.js
const queue = [];
let isFlushing = false;
const resolvedPromise = Promise.resolve()
export function queueJob(job){
if(!queue.includes(job)){
queue.push(job);
}
if(!isFlushing){
isFlushing = true;
resolvedPromise.then(()=>{
isFlushing = false;
let copy = queue.slice(0)
queue.length = 0; // 这里要先清空,防止在执行过程中在加入新的job
for(let i = 0; i < queue.length;i++){
let job = queue[i];
job();
}
copy.length = 0;
})
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
组件Props、Attrs实现
Props
和Attrs
关系是:没有定义在component.props
中的属性将存储到attrs
对象中
let {createRenderer,h,render,Text,Fragment} = VueRuntimeDOM
const VueComponent = {
data(){
return {name:'zf',age:13}
},
props:{
address:String,
},
render(){
return h('p',[`${this.name}今年${this.age}岁了`,`${this.address}`,`${this.$attrs.a}、${this.$attrs.b}`]);
}
}
render(h(VueComponent,{address:'霍营',a:1,b:2}),app);
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
initProps
const mountComponent = (vnode,container,anchor) =>{
let {data=()=>({}),render,props:propsOptions = {}} = vnode.type; // 这个就是用户写的内容
const state = reactive(data()); // pinia 源码就是 reactive({}) 作为组件的状态
const instance = { // 组件的实例
state,
vnode, // vue2的源码中组件的虚拟节点叫$vnode 渲染的内容叫_vnode
subTree:null, // vnode组件的虚拟节点 subTree渲染的组件内容
isMounted:false,
update:null,
propsOptions,
attrs:{},
props:{}
}
vnode.component = instance
initProps(instance,vnode.props);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
componentProps.ts
export function initProps(instance,rawProps){
const props = {};
const attrs = {};
const options = instance.propsOptions || {}; // 获取组件用户的配置
if(rawProps){
for(let key in rawProps){
const value = rawProps[key];
if( key in options){
props[key] = value;
}else{
attrs[key] = value
}
}
}
instance.props = reactive(props); // 这里应该用shallowReactive,遵循单向数据流原则
instance.attrs = attrs;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
属性代理
const publicPropertiesMap = {
$attrs:i=> i.attrs
}
const mountComponent = (vnode,container,anchor) =>{
// ...
const instance = { // 组件的实例
// ...
proxy:null
}
vnode.component = instance
initProps(instance,vnode.props);
instance.proxy = new Proxy(instance,{
get(target,key){
const {state,props} = target;
if(state && hasOwn(state,key)){
return state[key];
}else if(hasOwn(props,key)){
return props[key];
}
const publicGetter = publicPropertiesMap[key];
if(publicGetter){
return publicGetter(instance)
}
},
set(target,key,value){
const {state,props} = target;
if(state && hasOwn(state,key)){
state[key] = value;
return true;
}else if(hasOwn(props,key)){
console.warn(`Attempting to mutate prop "${key}". Props are readonly.`)
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
26
27
28
29
30
31
32
33
34
35
36
37
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
28
29
30
31
32
33
34
35
36
37
组件流程整合
const mountComponent = (vnode,container,anchor) =>{
// 1) 创建实例
const instance = vnode.component = createComponentInstance(vnode);
// 2) 给实例赋值
setupComponent(instance)
// 3) 创建渲染effect及更新
setupRenderEffect(instance,container,anchor);
}
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
1)创建组件实例
component.ts
export function createComponentInstance(vnode){
const instance = { // 组件的实例
data:null,
vnode, // vue2的源码中组件的虚拟节点叫$vnode 渲染的内容叫_vnode
subTree:null, // vnode组件的虚拟节点 subTree渲染的组件内容
isMounted:false,
update:null,
attrs:{},
props:{},
proxy:null,
propsOptions:vnode.type.props
}
return instance
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
2)设置组件属性
const publicPropertiesMap = {
$attrs:i=> i.attrs
}
const PublicInstanceProxyHandlers = {
get(target,key){
const {data,props} = target;
if(data && hasOwn(data,key)){
return data[key];
}else if(hasOwn(props,key)){
return props[key];
}
const publicGetter = publicPropertiesMap[key];
if(publicGetter){
return publicGetter(target)
}
},
set(target,key,value){
const {data,props} = target;
if(data && hasOwn(data,key)){
data[key] = value;
return true;
}else if(hasOwn(props,key)){
console.warn(`Attempting to mutate prop "${key}". Props are readonly.`)
return false;
}
return true;
}
}
export function setupComponent(instance){
const {props,type} = instance.vnode;
initProps(instance,props);
instance.proxy = new Proxy(instance,PublicInstanceProxyHandlers);
const data = type.data;
if(data){
if(!isFunction(data)) return console.warn('The data option must be a function.')
instance.data = reactive(data.call(instance.proxy))
}
instance.render = type.render
}
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
28
29
30
31
32
33
34
35
36
37
38
39
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
28
29
30
31
32
33
34
35
36
37
38
39
3)渲染effect
const setupRenderEffect = (instance,container,anchor) =>{
const {render} = instance
const componentUpdateFn = () =>{ // 区分是初始化 还是要更新
if(!instance.isMounted){ // 初始化
const subTree = render.call(instance.proxy,instance.proxy); // 作为this,后续this会改
patch(null,subTree,container,anchor); // 创造了subTree的真实节点并且插入了
instance.subTree = subTree;
instance.isMounted = true
}else{ // 组件内部更新
const subTree = render.call(instance.proxy,instance.proxy);
patch(instance.subTree,subTree,container,anchor);
instance.subTree = subTree;
}
}
// 组件的异步更新
const effect = new ReactiveEffect(componentUpdateFn,()=> queueJob(instance.update))
// 我们将组件强制更新的逻辑保存到了组件的实例上,后续可以使用
let update = instance.update = effect.run.bind(effect); // 调用effect.run可以让组件强制重新渲染
update();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
属性更新
const My = {
props:{address:String},
render(){return h('div',this.address)}
}
const VueComponent = {
data(){
return {name:'zf',age:13,flag:false}
},
render(){
return h(Fragment,[
h('button',{onClick:() => this.flag = !this.flag},'切换渲染'),
h(My,{address:this.flag? '天龙苑':'回龙观'}),
])
}
}
render(h(VueComponent),app);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const updateComponent = (n1,n2)=>{
const instance = (n2.component = n1.component);
const {props:prevProps} = n1;
const {props:nextProps} = n2;
updateProps(instance,prevProps,nextProps)
}
const 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
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
props.ts
const hasPropsChanged = (prevProps={},nextProps={}) =>{
const nextKeys = Object.keys(nextProps);
if(nextKeys.length !== Object.keys(prevProps).length){
return true;
}
for(let i = 0; i < nextKeys.length ; i++){
const key = nextKeys[i];
if(nextProps[key] !== prevProps[key]){
return true;
}
}
return false
}
export function updateProps(instance,prevProps,nextProps){
if(hasPropsChanged(prevProps,nextProps)){ // 比较前后属性是否一致
for(const key in nextProps){ // 循环props
instance.props[key] = nextProps[key]; // 响应式属性更新后会重新渲染
}
for(const key in instance.props){ // 循环props
if(!(key in nextProps)){
delete instance.props[key]
}
}
}
}
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
这里我们将更新逻辑放到
componentFn
中,因为除了属性更新之外,插槽也会导致页面更新
const shouldUpdateComponent = (n1,n2) =>{
const { props: prevProps, children: prevChildren } = n1
const { props: nextProps, children: nextChildren } = n2;
if(prevChildren || nextChildren) return true
if(prevProps === nextProps) return false;
return hasPropsChanged(prevProps,nextProps)
}
const updateComponent = (n1,n2)=>{
const instance = (n2.component = n1.component);
if(shouldUpdateComponent(n1,n2)){
instance.next = n2 // 将新的虚拟节点放到next属性上
instance.update(); // 属性变化手动调用更新方法
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
export function updateProps(prevProps,nextProps){
for(const key in nextProps){ // 循环props
prevProps[key] = nextProps[key]; // 响应式属性更新后会重新渲染
}
for(const key in prevProps){ // 循环props
if(!(key in nextProps)){
delete prevProps[key]
}
}
}
function updateComponentPreRender(instance,next){
instance.next = null;
instance.vnode = next;
updateProps(instance.props,next.props)
}
const componentUpdateFn = ()=>{
if(!instance.isMounted){
// ...
}else{
let {next} = instance;
if(next){
updateComponentPreRender(instance,next)
}
const subTree = render.call(instance.proxy,instance.proxy);
patch(instance.subTree,subTree,container,anchor);
instance.subTree = subTree;
}
}
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
28
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
28