前言
- 结合前面知识,本篇粗略实现下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了。