前言
- 結合前面知識,本篇粗略實現下fiber。
虛擬dom處理
- 先對babel轉譯的createElement進行編寫,完成基礎功能,再給react-dom。
createElement.js
function createElement(type, config, ...children) {
delete config.__self;
delete config.__source;
return {
type,//類型
props: {
...config,//屬性
children: children.map(child => {
return typeof child === 'object' ? child : {//是字符串特殊處理
type: ELEMENT_TEXT,
props: { text: child, children: [] }
}
})
}
}
}
製作根節點
- 然後將虛擬dom傳給reactdom,編寫react-dom,製作根節點:
function render(element, container) {
let rootFiber = {
tag: TAG_ROOT,
stateNode: container,
props: { children: [element] }
}
scheduleRoot(rootFiber);
}
const ReactDOM = {
render
}
export default ReactDOM;
- 也就是虛擬dom是在rootFiber的props的children屬性上。掛載點在stateNode上。
初次渲染——虛擬dom轉換爲fiber樹
- 虛擬dom的孩子是一個數組形式,所以需要轉換爲fiber結構,我們以上一篇例子爲例:
let C1 = { type: 'div', key: 'C1'};
let C2 = { type: 'div', key: 'C2'};
let B1 = { type: 'div', key: 'B1', child:[C1,C2] };
let B2 = { type: 'div', key: 'B2'};
let A1 = { type: 'div', key: 'A1', child:[B1,B2] };
- 傳統是這樣結構,轉換成fiber結構需要用循環遍歷:
function createFiber(root,child){
let index=0
let prevFiber
if(child){
while(index<child.length){
let currentChild=child[index]
let fiber={
type:currentChild.type,
child:currentChild.child,
key:currentChild.key,
return:root
}
if(index===0){
root.child=fiber
}else{
prevFiber.sibling=fiber
}
prevFiber=fiber
index++
}
}
}
function deep(root){
createFiber(root,root.child)
if(root.child){
deep(root.child,root.child.child)//這裏改成return就可以拆開
}
while(root){
if(root.sibling){
deep(root.sibling,root.sibling.child)
}
root=root.return
}
}
deep(A1)
console.log(A1)
- 這裏的技巧就是傳2個值,一個是父節點,一個是它的孩子,然後修改父節點,將孩子進行while循環連接。
- 最後輸出:
- 每個節點只有一個孩子。
- 這樣就完成了轉化fiber結構。而這個轉化過程,是在上一篇console的start處進行的,所以結合一下idleCallback,寫成這樣:
function createDom(currentFiber) {
if (currentFiber.tag === TAG_TEXT) {
return document.createTextNode(currentFiber.props.text);
} else if (currentFiber.tag === TAG_HOST) {
let stateNode = document.createElement(currentFiber.type);
updateDOM(stateNode, {}, currentFiber.props);
return stateNode;
}
}
updateDOM(stateNode,oldProps,newProps){
setProps(stateNode,oldProps,newProps)//更新屬性
}
let nextUnitOfWork=null
let workInProgressRoot=null
export function scheduleRoot(rootfiber){
workInProgressRoot=rootfiber//work是根
nextUnitOfWork=rootfiber//相當於每個工作單元,workloop裏面會不斷變化
}
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 completeUnitOfWork(){//收集副作用
}
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)
}
}
function updateHost(currentFiber){
if(!currentFiber.stateNode){
currentFiber.stateNode=createDom(currentFiber)
}
const newChildren = currentFiber.props.children
reconcilieChildren(currentFiber,newChildren)
}
function updateHostText(currentFiber){
if(!currentFiber.stateNode){
currentFiber.stateNode=createDom(currentFiber)
}
}
function updateHostRoot(currentFiber){
let newChildren = currentFiber.props.children//取出當前任務的虛擬dom
reconcilieChildren(currentFiber,newChildren)//爲孩子創建fiber
}
function reconcilieChildren(currentFiber,newChildren){
let newChildIndex=0
let prevSibiling
while(newChildIndex<newChildren.length){//while遍歷孩子構建子節點
let newChild = newChildren[newChildIndex]
let tag
if(newChild.type===ELEMENT_TEXT){
tag=TAG_TEXT//字符串單獨處理
}else if(typeof newChild.type==='string'){
tag=TAG_HOST//原生節點
}
let newFiber={
tag,
type:newChild.type,
props:newChild.props,
stateNode:null,
return:currentFiber,
effectTag:PLACEMENT,//增加操作
nextEffect:null,//鏈表指向
}
if(newFiber){
if(newChildIndex==0){//第一個兒子
currentFiber.child=newFiber
}else{
prevSibiling.sibling=newFiber
}
prevSibiling=newFiber
}
newChildIndex++
}
}
function workLoop(deadline){
let shouldYield =false
while(nextUnitOfWork&&!shouldYield){
nextUnitOfWork=performUnitOfWork(nextUnitOfWork)
shouldYield=deadline.timeRemaining()<=0
}
if(nextUnitOfWork){//出while則讓給瀏覽器下次調度
requestIdleCallback(workLoop,{timeout:1000})
}else{
console.log('over')
}
}
requestIdleCallback(workLoop,{timeout:1000})
- 其中reconcilieChildren對應上面的createFiber函數,一個邏輯。performUnitOfWork對應上面deep函數。看不懂的看看上面簡寫的就懂了。
初次渲染——收集副作用
- 上一篇寫到,在while那裏會收集副作用,做成副作用鏈表,這次來編寫下:
function completeUnitOfWork(currentFiber){//收集副作用
let returnFiber=currentFiber.return
if(returnFiber){//父節點
if(!returnFiber.firstEffect){//這2個if的作用是爲了確保根節點能指向鏈表的頭和尾
returnFiber.firstEffect=currentFiber.firstEffect
}
if(currentFiber.lastEffect){//說明他孩子有副作用(沒孩子的進不來)
if(returnFiber.lastEffect){//如果父有指針,把當前有副作用的孩子掛到鏈表下一個
returnFiber.lastEffect.nextEffect=currentFiber.firstEffect
}
returnFiber.lastEffect=currentFiber.lastEffect
}
const effectTag=currentFiber.effectTag//如果這個節點有更新
if(effectTag){
if(returnFiber.lastEffect){//這個節點不是第一個,那就用next連接當前
returnFiber.lastEffect.nextEffect=currentFiber
}else{
returnFiber.firstEffect=currentFiber
}
returnFiber.lastEffect=currentFiber//父節點的尾指針,始終指向最後個有副作用的孩子
}
}
}
- 這個可能得稍微揣摩下,總之就是最終生成了一個副作用鏈表,沒副作用的就跳過不收集。
- 這個鏈表很巧的就是都是孩子收集完收集父親纔算完成,這樣可以先生成子節點,然後再掛到父節點上,最後掛根上。
初次渲染——提交階段
- 提交階段則在上面代碼的console.log over處執行:
function workLoop(deadline){
let shouldYield =false
while(nextUnitOfWork&&!shouldYield){
nextUnitOfWork=performUnitOfWork(nextUnitOfWork)
shouldYield=deadline.timeRemaining()<=0
}
if(nextUnitOfWork){//出while則讓給瀏覽器下次調度
requestIdleCallback(workLoop,{timeout:1000})
}else{
console.log('over')
commitRoot()
}
}
function commitRoot(){
let currentFiber=workInProgressRoot.firstEffect
while(currentFiber){
commitWork(currentFiber)
currentFiber=currentFiber.nextEffect //單鏈表,一個個effect做
}
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)
}
currentFiber.effectTag=null
}
- 這個就是順着鏈表添加就ok了。