组件的挂载流程
组件需要提供一个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;
for(let i = 0; i < queue.length;i++){
let job = queue[i];
job();
}
queue.length = 0;
})
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
组件Props、Attrs实现
Props
和Attrs
关系是:没有定义在component.props
中的属性将存储到attrs
对象中
const VueComponent = {
data(){
return {age:13}
},
props:{
address:String
},
render(){
return h('p',[
h(Text,"I'm Jiang sir"),
h('span',this.age),
h('span',this.address),
h(Text,this.$attrs.a + this.$attrs.b)
])
}
}
createRenderer(renderOptions).render(h(VueComponent,{address:'天龙苑',a:1,b:2}),document.getElementById('app'))
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
initProps
function initProps(instance,propsOptions,propsData){
const props = {};
const attrs = {};
for(const key in propsData){
if(key in propsOptions){ // 如果组件中声明了
props[key] = propsData[key];
}else{
attrs[key] = propsData[key];
}
}
instance.props = reactive(props);
instance.attrs = attrs
}
const mountComponent = (n2,container,anchor)=>{
const {render,data=()=>({}),props:propsOptions={}} = n2.type;
const state = reactive(data())
const instance = {
state, // 组件的状态
isMounted:false, // 组件是否挂载
subTree:null, // 子树
update:null,
vnode:n2,
attrs:{},
props:{},
}
n2.component = instance; // 用于更新
// 用户写的props 及 传入的props
initProps(instance,propsOptions,n2.props); // 初始化属性
}
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
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
属性代理
const publicPropertiesMap = {
$attrs:i=> i.attrs
}
const renderContext = 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
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
属性更新
const My = {
props:{address:String},
render(){return h('div',this.address)}
}
const VueComponent = {
data(){return {flag:true}},
props:{address:String},
render(){
setTimeout(()=>{
this.flag = false;
},1000)
return h(My,{address:this.flag ? '天龙苑' : '回龙观'})
}
}
createRenderer(renderOptions).render(h(VueComponent),document.getElementById('app'))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const updateComponent = (n1,n2)=>{
const instance = (n2.component = n1.component);
const {props:prevProps} = n1;
const {props:nextProps} = n2;
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]
}
}
}
}
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
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
这里我们将更新逻辑放到componentFn中
const updateComponent = (n1,n2)=>{
const instance = (n2.component = n1.component);
const {props:prevProps} = n1;
const {props:nextProps} = n2;
if(hasPropsChanged(prevProps,nextProps)){
instance.next = n2 // 将新的虚拟节点放到next属性上
instance.update(); // 属性变化手动调用更新方法
}
}
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
const updateComponentPreRender = (instance,next) =>{
instance.next = null;
instance.vnode = next;
for(const key in next.props){ // 循环props
instance.props[key] = next.props[key];
}
for(const key in instance.props){ // 循环props
if(!(key in next.props)){
delete instance.props[key]
}
}
}
const componentUpdateFn = ()=>{
if(!instance.isMounted){
// ...
}else{
let {next} = instance;
if(next){ // 更新组件在渲染前更新
updateComponentPreRender(instance,next)
}
const subTree = render.call(renderContext,renderContext);
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25