前面初次渲染时,我们已经得到了一个完整的fiber树,并且树上链接着副作用链表。
而react的更新逻辑,也是走workloop,由于基于idleCallback,这里简化起见,写成了一个类似死循环的模式,相当于每当浏览器idle时执行下函数,但因为是idle时,所以用户不觉得卡。就有点像我们监听了scroll,结果只要一滚动就会不断触发函数一样。当然,里面应该还有若干优化。本篇先排除那些优化,实现简易逻辑。
由于是死循环,所以每次执行时都会根据新的虚拟dom来生成新的fiber树,其中自然也会有复用逻辑之类,新生成的fiber树里同样也会有副作用链表,最后根据副作用链表来commit,渲染出最新的页面。
首先使用元素渲染来实现更新操作:
import React from 'react';
import ReactDOM from 'react-dom';
let style={border:'3px solid red',marigin:'5px'}
let element = (
<div id="A1" style={style}>
A1
<div id="B1" style={style}>
B1
<div id="C1" style={style}>C1</div>
<div id="C2" style={style}>C2</div>
</div>
<div id="B2" style={style}>B2</div>
</div>
)
ReactDOM.render(
element,
document.getElementById('root')
);
let render2=document.getElementById('render2')
render2.addEventListener('click',()=>{
let element2=(
<div id="A12" style={style}>
A1
<div id="B12" style={style}>
B1
<div id="C12" style={style}>C12</div>
<div id="C22" style={style}>C22</div>
</div>
<div id="B22" style={style}>B22</div>
<div id="B33" style={style}>B33</div>
</div>
)
ReactDOM.render(
element2,
document.getElementById('root')
);
})
let render3=document.getElementById('render3')
render3.addEventListener('click',()=>{
let element3=(
<div id="A12" style={style}>
A3
<div id="B12" style={style}>
B3 <div id="C12" style={style}>33</div>
<div id="C22" style={style}>C22</div>
</div>
</div>
)
ReactDOM.render(
element3,
document.getElementById('root')
);
})
let nextUnitOfWork=null
let workInProgressRoot=null
let currentRoot=null
let deletions=[]//删除不在effectlist里
export function scheduleRoot(rootfiber){
if(currentRoot){//如果有值,说明时是更新逻辑
rootfiber.alternate=currentRoot
}
workInProgressRoot=rootfiber//work是根
nextUnitOfWork=workInProgressRoot//相当于每个工作单元,workloop里面会不断变化
}
function reconcilieChildren(currentFiber,newChildren){
let newChildIndex=0
let oldFiber = currentFiber.alternate&¤tFiber.alternate.child//这个指的是老的子节点
let prevSibiling
while(newChildIndex<newChildren.length||oldFiber){//while遍历孩子构建子节点 这里或者代表把新老节点取最大值(可能新的删了),当然oldfiber有的话只有1个
let newChild = newChildren[newChildIndex]
const sameType=oldFiber&&newChild&&oldFiber.type===newChild.type//新老节点一样
let newFiber
let tag
if(newChild&&newChild.type===ELEMENT_TEXT){
tag=TAG_TEXT//字符串单独处理
}else if(newChild&&typeof newChild.type==='string'){
tag=TAG_HOST//原生节点
}
if(sameType){
newFiber={//复用
tag:oldFiber.tag,
type:oldFiber.type,
props:newChild.props,
stateNode:oldFiber.stateNode,
return:currentFiber,
alternate: oldFiber,//让新的fiber的alternate指向老的fiber节点
effectTag:UPDATE,//更新操作
nextEffect:null,//链表指向
}
}else{
if(newChild){//不同且有新孩子,创建它
newFiber={
tag,
type:newChild.type,
props:newChild.props,
stateNode:null,
return:currentFiber,
effectTag:PLACEMENT,//增加操作
nextEffect:null,//链表指向
}
}
if(oldFiber){//有新fiber了就要删除老的
oldFiber.effectTag=DELETION
deletions.push(oldFiber)
}
}
if(oldFiber){
oldFiber=oldFiber.sibling//因为newchild是虚拟dom,是个数组,老节点是fiber树,所以往后移动时需要走兄弟
}
if(newFiber){
if(newChildIndex===0){//第一个儿子
currentFiber.child=newFiber
}else{
prevSibiling.sibling=newFiber
}
prevSibiling=newFiber
}
newChildIndex++
}
}
function performUnitOfWork(currentFiber){ //这个就是前面deep函数样例的逻辑
beginWork(currentFiber)//转化为fiber
if(currentFiber.child){
return currentFiber.child
}
while(currentFiber){
completeUnitOfWork(currentFiber)
if(currentFiber.sibling){
return currentFiber.sibling
}
currentFiber=currentFiber.return
}
}
function workLoop(deadline){
let shouldYield =false
while(nextUnitOfWork&&!shouldYield){
nextUnitOfWork=performUnitOfWork(nextUnitOfWork)
shouldYield=deadline.timeRemaining()<=0
}
if (!nextUnitOfWork && workInProgressRoot) {//如果时间片到期后还有任务没有完成,就需要请求浏览器再次调度
console.log('over');
commitRoot();
}
requestIdleCallback(workLoop, { timeout: 500 });
}
function commitRoot(){
deletions.forEach(commitWork)
let currentFiber
if(workInProgressRoot){
currentFiber=workInProgressRoot.firstEffect
}
while(currentFiber){
commitWork(currentFiber)
currentFiber=currentFiber.nextEffect //单链表,一个个effect做
}
deletions.length=0//更新完清空
currentRoot=workInProgressRoot//渲染完成的给currentroot
workInProgressRoot=null
}
function commitWork(currentFiber){
if(!currentFiber)return
let returnFiber=currentFiber.return
let returnDom=returnFiber.stateNode//拿到真实dom
if(currentFiber.effectTag===PLACEMENT){
returnDom.appendChild(currentFiber.stateNode)
}else if(currentFiber.effectTag===DELETION){
returnDom.removeChild(currentFiber.stateNode)
}else if(currentFiber.effectTag===UPDATE){
if(currentFiber.type===ELEMENT_TEXT){
if(currentFiber.alternate.props.text!==currentFiber.props.text){//上一个不等于本次
currentFiber.stateNode.textContent=currentFiber.props.text
}
}else{//文本进行比对,否则更新
updateDOM(currentFiber.stateNode,currentFiber.alternate.props,currentFiber.props)
}
}
currentFiber.effectTag=null
}
export class UpdateQueue {
constructor() {
this.firstUpdate = null;
this.lastUpdate = null;
}
enqueueUpdate(update) {
if (this.lastUpdate === null) {
this.firstUpdate = this.lastUpdate = update;
} else {
this.lastUpdate.nextUpdate = update;
this.lastUpdate = update;
}
}
forceUpdate(state) {
let currentUpdate = this.firstUpdate;
while (currentUpdate) {
let nextState = typeof currentUpdate.payload === 'function' ? currentUpdate.payload(state) : currentUpdate.payload;
state = { ...state, ...nextState };
currentUpdate = currentUpdate.nextUpdate;
}
this.firstUpdate = this.lastUpdate = null;
return state;
}
}
class Component {
constructor(props) {
this.props = props;
}
setState(payload) {
let update = new Update(payload);
this.internalFiber.updateQueue.enqueueUpdate(update);//更新的放链表
scheduleRoot();//从根节点开始调度
}
}
Component.prototype.isReactComponent = {};
export function scheduleRoot(rootfiber){
if(currentRoot){//如果有值,说明时是第一次更新逻辑
if(rootfiber){
rootfiber.alternate=currentRoot
workInProgressRoot=rootfiber//work是根
}else{
workInProgressRoot = {
...currentRoot,
alternate: currentRoot
}
}
}else{//初次
workInProgressRoot=rootfiber
}
workInProgressRoot.firstEffect = workInProgressRoot.lastEffect = workInProgressRoot.nextEffect = null;
nextUnitOfWork=workInProgressRoot//相当于每个工作单元,workloop里面会不断变化
}
function beginWork(currentFiber){//如果原生dom,需要创建真实dom元素。current会变
if(currentFiber.tag===TAG_ROOT){//根不需要建真实dom
updateHostRoot(currentFiber)
}else if(currentFiber.tag===TAG_TEXT){
updateHostText(currentFiber)
}else if(currentFiber.tag===TAG_HOST){
updateHost(currentFiber)
}else if(currentFiber.tag===TAG_CLASS){
updateClassComponent(currentFiber)
}
}
function updateClassComponent(currentFiber){
if(!currentFiber.stateNode){
currentFiber.stateNode=new currentFiber.type(currentFiber.props)
currentFiber.stateNode.internalFiber=currentFiber
currentFiber.updateQueue=new UpdateQueue()
}
currentFiber.stateNode.state = currentFiber.updateQueue
.forceUpdate(currentFiber.stateNode.state);//这个传入的是老状态,链表里那个是最新的
let newElement = currentFiber.stateNode.render();
const newChildren = [newElement];
reconcilieChildren(currentFiber, newChildren);
}
function reconcilieChildren(currentFiber,newChildren){
let newChildIndex=0
let oldFiber = currentFiber.alternate&¤tFiber.alternate.child//这个指的是老的子节点
let prevSibiling
while(newChildIndex<newChildren.length||oldFiber){//while遍历孩子构建子节点 这里或者代表把新老节点取最大值(可能新的删了),当然oldfiber有的话只有1个
let newChild = newChildren[newChildIndex]
const sameType=oldFiber&&newChild&&oldFiber.type===newChild.type//新老节点一样
let newFiber
let tag
if(newChild && typeof newChild.type==='function' &&newChild.type.prototype.isReactComponent){
tag =TAG_CLASS
}else if(newChild&&newChild.type===ELEMENT_TEXT){
tag=TAG_TEXT//字符串单独处理
}else if(newChild&&typeof newChild.type==='string'){
tag=TAG_HOST//原生节点
}
if(sameType){//复用逻辑
newFiber={//复用上一次的
tag:oldFiber.tag,
type:oldFiber.type,
props:newChild.props,
stateNode:oldFiber.stateNode,
return:currentFiber,
alternate: oldFiber,//让新的fiber的alternate指向老的fiber节点
effectTag:UPDATE,//更新操作
nextEffect:null,//链表指向
updateQueue:oldFiber.updateQueue||new UpdateQueue()
}
}else{
if(newChild){//不同且有新孩子,创建它
newFiber={
tag,
type:newChild.type,
props:newChild.props,
stateNode:null,
return:currentFiber,
effectTag:PLACEMENT,//增加操作
nextEffect:null,//链表指向
updateQueue:new UpdateQueue()
}
}
if(oldFiber){//有新fiber了就要删除老的
oldFiber.effectTag=DELETION
deletions.push(oldFiber)
}
}
if(oldFiber){
oldFiber=oldFiber.sibling//因为newchild是虚拟dom,是个数组,老节点是fiber树,所以往后移动时需要走兄弟
}
if(newFiber){
if(newChildIndex===0){//第一个儿子
currentFiber.child=newFiber
}else{
prevSibiling.sibling=newFiber
}
prevSibiling=newFiber
}
newChildIndex++
}
}
function commitWork(currentFiber){
if(!currentFiber)return
let returnFiber=currentFiber.return
while (returnFiber.tag !== TAG_HOST &&
returnFiber.tag !== TAG_ROOT &&
returnFiber.tag !== TAG_TEXT) {
returnFiber = returnFiber.return;
}
let returnDom=returnFiber.stateNode//拿到真实dom
if(currentFiber.effectTag===PLACEMENT){
let nextFiber = currentFiber;
// 如果要挂载的节点不是DOM节点,比如说是类组件Fiber,一直找第一个儿子,直到找到一个真实DOM节点为止
while (nextFiber.tag !== TAG_HOST && nextFiber.tag !== TAG_TEXT) {
nextFiber = currentFiber.child;
}
returnDom.appendChild(nextFiber.stateNode);
}else if(currentFiber.effectTag===DELETION){
commitDeletion(currentFiber,returnDom)
//returnDom.removeChild(currentFiber.stateNode)
}else if(currentFiber.effectTag===UPDATE){
if(currentFiber.type===ELEMENT_TEXT){
if(currentFiber.alternate.props.text!==currentFiber.props.text){//上一个不等于本次
currentFiber.stateNode.textContent=currentFiber.props.text
}
}else{//文本进行比对,否则更新
updateDOM(currentFiber.stateNode,currentFiber.alternate.props,currentFiber.props)
}
}
currentFiber.effectTag=null
}
function commitDeletion(currentFiber, domReturn) {
if (currentFiber.tag == TAG_HOST || currentFiber.tag == TAG_TEXT) {
domReturn.removeChild(currentFiber.stateNode);
} else {
commitDeletion(currentFiber.child, domReturn)
}
}