从0实现react框架,React Fiber架构和Fiber Diff算法

react框架是目前最为流行的前端框架之一,尤其在很多大厂,应用更为广泛。相对于一些mvvm框架,react上手需要一定的技术基础,但掌握后,编码体验和性能是很不错的。react整体思想是函数式编程,可以很大程度上减少代码重复,易于并发编程,react内部实现了一套调度机制,给不同执行任务划分优先级,从高到低循环渲染(Fiber),同时引入一套Fiber Diff算法,最大限度的使网页平滑,性能最优。

react的源码在不断的迭代后,相对不那么好理解,那么怎么才能快速理解并掌握react底层运行原理呢,近期给员工作培训,基于react源码思想,设计和实现了一个简版react,本文将逐层深入,从0实现一个简版react,包括Fiber结构,Fiber Diff算法,useState hooks实现。

更多内容关注公众号:前端361

概述

1,实现基础版react

2,fiber数据结构实现

3,fiber diff算法实现

4,useState实现

一,实现基础版react

先看一段react代码

import React from "react"
import ReactDOM from "reaect-dom"

const container = document.getElementById("root")

ReactDOM.render(<div className="test" onClick={() => {console.log(123)}}>
    <p style={
   
   {background: 'red'}}>123</p>
</div>, container)

思路分析:

1,上面代码引用了React, ReactDOM对象,所以自己实现的话需要自定义React, ReactDOM对象

const React = {}
const ReactDOM = {}

2,执行react jsx代码会被@babel/plugin-transform-react-jsx转化为如下格式:

扫描二维码关注公众号,回复: 15305652 查看本文章

jsx转化

从上面代码可以看到,jsx代码在经过babel转义后,调用了React.createElement,所以要给自定义React对象加上createElement方法,createElement接收参数是标签类型(type),元素属性(props),元素的所有子元素(...children)

React.createElement = function(type, props, ...children){
    return {
        type,
        props: {
            ...props,
            children: children.map(child => {
                return child
            })
        }
    }
}

由于会存在文本节点,标签类型都是TEXT,将文本节点单独拎出来,添加createTextElement方法

React.createElement = function(type, props, ...children){
    // console.log(children)
    return {
        type,
        props: {
            ...props,
            children: children.map(child => {
                return typeof child === 'object' ? child : React.createTextElement(child)

                // 添加组件判断
            })
        }
    }
}

React.createTextElement = function(text){
  return {
    type: 'TEXT',
    props: {
      nodeValue: text,
      children: []
    }
  }
}

3, react是mvvm框架,基本原理是将Vdom转化为真实dom并添加到文档流中,所以实现ReactDOM.render方法,render内部将vDom转化为真实dom,并给dom添加属性,再添加到容器中;

ReactDOM.render = function(vDom, container){
    container.appendChild(initElement(vDom))
}
function initElement(vDom){ //生成真实dom结构
  if(!vDom){
    return
  }
  let dom
    if(vDom.type === 'TEXT'){
        dom = document.createTextNode("")
    } else {
        dom = document.createElement(vDom.type)
        setAtr(dom, vDom.props)
    }
    if(vDom.props.children.length > 0){
        vDom.props.children.map((child) => {
            dom.appendChild(initElement(child))
        })
    } 
   setAtr(dom)
    return dom
}
function  setAtr(dom, props){
  if(!props){
    return
  }
  for(let [key, value] of Object.entries(props)){
      if(key !== 'children'){
        // 添加样式
        if(key === 'style'){
            if(value && typeof value === 'object'){
                for(let i in value){
                    dom.style[i] = value[i]
                }
            } else {
                dom.removeAttribute(key) // 样式对象不存在或者样式格式不对
            }
        } else if(/on\w+/.test(key)){  // 绑定事件 
            //\W+:匹配一个或多个非字母进行切割,匹配到的非字母不缓存;
            key = key.toLowerCase()
            if(value){
                dom[key] = value
            } else {
                dom.removeAttribute(key)
            }

        } else if(key === 'nodeValue'){
            dom.nodeValue=value
        } else { // 其他属性
            if(value){
                dom.setAttribute(key, value)
            } else {
                dom.removeAttribute(key)
            }

        }
      }
  }
}

到此就实现了一个简版react(不包括组件,组件类型在下面介绍)

二,Fiber数据结构实现

上面的递归子节点是不可打断的,由于js是单线程的,如果此时存在其他高级任务渲染,页面会出现卡顿,基于这个问题react设计了一个双向链表结构(Fiber),将递归转化为循环遍历,有高级任务介入时,暂停循环,待高级任务执行后再继续遍历。循环遍历主要利用了浏览器的特性,主要用到requestIdleCallback来判断每一帧是否有空闲时间,利用空闲时间遍历生成fiber树。

思路:

由于遍历fiber暂停后,可以再回到上次暂停的位置继续,所以需要定义一个全局变量nextUnitOfWork,用来存储当前遍历的节点,在任意fiber节点都可以继续遍历整棵树,所以通过fiber节点可以找到其父级节点,兄弟节点,子节点,设置fiber.return指向父级节点,fiber.child指向子节点,fiber.sibling执行兄弟节点,整颗fiber树是双向链表结构。

在遍历fiber的过程中,同步创建fiber节点对应的真实dom

1,将每次遍历看作是一个工作单元,执行完当前工作单元后(performUnitOfWork),返回下一个工作单元,

performUnitOfWork函数实现以下功能

  • 创建fiber节点的dom
  • 将fiber节点的子节点创建为Fiber结构
  • 返回fiber节点的下一个工作单元(要执行的节点)
function createDom(fiber){
  let dom = fiber.type === 'TEXT' ? document.createTextNode("") : document.createElement(fiber.type)
  setAtr(dom, fiber.props)
  return dom
}
let nextUnitOfWork = null

// performUnitOfWork执行工作单元时,将当前节点的子元素都创建为fiber结构
// 遍历vDom和创建fiber节点是同步的
// fiber.props.children可以访问到子节点,根据这个将vDom遍历完毕
function performUnitOfWork(fiber){
  if(!fiber.dom){ 
    fiber.dom = createDom(fiber)
  }
  // 为每个子节点创建dom
  let elements = fiber.props.children 

  let index = 0

  let prevSibling = null

  // 将当前子节点虚拟dom对象创建为fiber节点
  while(index < elements.length){
      const element = elements[index]
      const newFiber = {
          type: element.type,
          props: element.props,
          return: fiber,
          dom: null
      }

      if(index === 0){ //如果是第一个元素,就设置为子节点
        fiber.child = newFiber
      } else {
        prevSibling.sibling = newFiber // 如果不是第一个元素,则指向上一个元素的兄弟节点
      }

      prevSibling = newFiber //每次循环,缓存当前节点
      index++

  }

  // 上面创建fiber节点,下面遍历fiber节点,将其子节点再次传入performUnitOfWork
  if(fiber.child){
    return fiber.child
  }

  let nextFiber = fiber 
  while(nextFiber){
    if(nextFiber.sibling){  
        return nextFiber.sibling
    }
    nextFiber = nextFiber.return
  }

}

2,设置一个循环函数workLoop,浏览器每一帧有空闲时间时执行workLoop,workLoop内部执行performUnitOfWork,直到nextUnitOfWork为null,如果存在工作单元,将workLoop赋值给浏览器,在浏览器空余时间执行workLoop

function wookLoop(deadline){  
    let shouldYield = false //是否有剩余时间

    // 有空余时间的情况下,继续渲染
    while(nextUnitOfWork && !shouldYield) { // 链表结构没有渲染完毕并且有空余时间
        nextUnitOfWork = performUnitOfWork(nextUnitOfWork)
        shouldYield = deadline.timeRemaining < 1
    }
    
    // 没有空余时间,交给浏览器等待执行
    if(nextUnitOfWork){
        requestIdleCallback(wookLoop)
    }
}

3,设置全局变量wipRoot存储链表根节点,ReactDOM.render时,初始化第一个fiber节点赋值给wipRoot,并将其赋值给nextUnitOfWork

let wipRoot = null
ReactDOM.render = function(vDom, container){
    wipRoot = {
        dom: container,
        props: {
            children: [vDom]
        }
    }
    nextUnitOfWork = wipRoot
    // 触发workLoop
    requestIdleCallback(wookLoop)
}

4,当nextUnitOfWork不存在时,证明已经生成fiberTree,此时要提交整个渲染

function wookLoop(deadline){  
    let shouldYield = false //是否有剩余时间

    // 有空余时间的情况下,继续渲染
    while(nextUnitOfWork && !shouldYield) { // 链表结构没有渲染完毕并且有空余时间
        nextUnitOfWork = performUnitOfWork(nextUnitOfWork)
        shouldYield = deadline.timeRemaining < 1
    }
    
    // 没有空余时间,交给浏览器等待执行
    if(nextUnitOfWork){
        requestIdleCallback(wookLoop)
    }

    // nextUnitOfWork不存在,也就是fiber已经全部创建
    if(!nextUnitOfWork && wipRoot){
        // 执行完成,提交渲染
        commitRoot()
    }
}

5,commitRoot用来遍历fiber树,将dom添加到文档流

/**
 * 转化为双向链表结构后,开始提交和渲染真实dom
 * 提交整个fiber树(双向链表)
 */
function commitRoot(){
    commitWork(wipRoot.child)
    wipRoot = null
}

/**
 * 节点真实dom提交到父级dom中
 */
function commitWork(fiber){
    if(!fiber){
        return
    }
    
    const parentDom = fiber.return.dom;
    parentDom.appendChild(fiber.dom)
    commitWork(fiber.child)
    commitWork(fiber.sibling)

}

以上实现了fiber数据结构和fiber双向链表树

总结:fiber架构相当于把原来的递归执行权限让给了浏览器,让浏览器在空闲情况下执行递归,相当于循环遍历vDom,可中断操作。

三,fiber diff算法实现

要实现数据结构对比,首先要缓存上次的fiber树,对比后需要删除旧树上没用的节点

1,设置全局变量存储fiber树,收集要删除的节点,reactDOM.render执行时,fiber添加alternate属性指向上次的fiber树,重置deletions

let currentRoot = null //上一次提交的fiber树
let deletions = []

ReactDOM.render = function(vDom, container){
    wipRoot = {
        dom: container,
        props: {
            children: [vDom]
        },
        alternate: currentRoot, // 上一次提交的fiber
    }

    nextUnitOfWork = wipRoot

    deletions = [] // 渲染开始前将deletions重置为空

    // 触发workLoop
    requestIdleCallback(wookLoop)
}

2,从根节点开始,采用深度优先遍历,和旧的fiber树对比并生成新的fiber树

function performUnitOfWork(fiber){
   if(!fiber.dom){ //如果当前节点上没有真实节点,创建fiber对应的真实节点
        fiber.dom = createDom(fiber)
    }
   elements = fiber.props.children

  // 加入diff算法,对比新老节点
  reconcileChildren(fiber, elements) //说明 在遍历当前节点时,会遍历其所有子节点,并给子节点和子节点的兄弟节点设置alternate, 设置过之后,在下次递归中,刚好可以对比新老节点

  if(fiber.child){
    return fiber.child
  }

  let nextFiber = fiber //缓存当前fiber
  while(nextFiber){
    if(nextFiber.sibling){  //返回当前节点的兄弟节点
        return nextFiber.sibling
    }
    
    // 如果不存在兄弟节点,就返回父级节点,进而去查找父级节点的兄弟节点,如果没有也没有兄弟节点,则继续向上返回
    nextFiber = nextFiber.return
  }

}

3,执行fiber节点时,创建的fiber节点添加属性alternate指向对应的oldFiber,在对比新老节点时,可以做到相同层级相同位置进行比较

alternate对应

对比老节点同时生成新的fiber,在新的fiber上打上不同的effecTag,在commit阶段执行不同的操作

将要删除的fiber放入deletions

这里还有一个特别注意的点,当新老节点类型不同,需要将老节点删除,添加新节点时需要找到老节点所在的位置,这时需要记录新插入的节点对应子元素的序号

/**
 * 对比新老节点,并为节点创建fiber结构
 * @param {*} wipFiber 当前节点
 * @param {*} elements 当前节点的子节点
 * 遍历当前节点时,设置其子元素和子元素兄弟元素的alternate,当遍历到子元素和兄弟元素时,刚好可以访问到alternate
 * 遍历同时,先设置alternate,后面再对比新老节点
 */
function reconcileChildren(wipFiber, elements){

    let index = 0;
    let prevSibling = null;

    // 获取老节点
    let oldFiber = wipFiber.alternate && wipFiber.alternate.child

    while(index < elements.length || oldFiber != null){ // 子元素存在 或者 当前节点的旧节点存在
        const element = elements[index]
        let sameType = element && oldFiber && element.type === oldFiber.type
        let newFiber
        console.error('sameType', element.type, sameType)
        if(sameType){ //节点类型相同,比如均为div
            newFiber = {
                return: wipFiber,
                props: element.props,
                type: element.type,
                dom: oldFiber.dom,
                alternate: oldFiber, //这里将和当前节点对应的旧的fiber赋值记录下来,当比对当前节点子节点时,用来获得对应的旧的节点对象
                effectTag: 'UPDATE'
            }
        }
        
        if(element && !sameType){// 节点类型不同,比如新节点是span, 老节点是div
            newFiber = {
                return: wipFiber,
                props: element.props,
                type: element.type,
                dom: null, //重新创建dom
                alternate: null,
                effectTag: 'ADD',
                childIndex: index,// 用来记录当前元素是第几个子元素,在新节点是新类型时,根据index找到dom插入位置
                siblingNum: elements.length
            }
        }

        if(oldFiber && !sameType){ 
            newFiber.effectTag = "PLACEMENT" // 新老节点都存在时,类型不同,直接替换为新节点,删除老节点
            oldFiber.effectTag="DELETION";
            deletions.push(oldFiber) // 收集要删除的节点
        }

        // 每次while循环结束后, oldFiber指向其兄弟节点
        if(oldFiber){
            oldFiber = oldFiber.sibling
        }

        if(index === 0){
            wipFiber.child = newFiber
        } else {
            prevSibling.sibling = newFiber
        }
        prevSibling = newFiber 

        index++
    }
}

4,commitRoot改造,提交前删除没用的旧节点,提交后缓存currentRoot

/**
 * 转化为双向链表结构后,开始提交和渲染真实dom
 * 提交整个fiber树(双向链表)
 */
function commitRoot(){
    //处理要删除的节点
    deletions.forEach(commitWork)
    commitWork(wipRoot.child)
    currentRoot = wipRoot // 缓存上次提交的fiber树
    wipRoot = null
}

/**
 * 节点真实dom提交到父级dom中
 */
function commitWork(fiber){
    if(!fiber){
        return
    }
    
    const parentDom = fiber.return.dom; 
    if(fiber.effectTag === 'UPDATE' && fiber.dom != null){
        updateDom(fiber.dom, fiber.alternate.props, fiber.props) //更新节点属性
    } else if(fiber.effectTag === 'DELETION'){
        domParent.removeChild(fiber.dom)
    } else if(fiber.effectTag === 'PLACEMENT' && fiber.dom != null){
        if(fiber.childIndex < fiber.siblingNum){ // 中间元素
            parentDom.insertBefore(fiber.dom, fiber.sibling.dom)
        } else {
            parentDom.appendChild(fiber.dom)
        }
    } else if(fiber.effectTag === 'ADD' && fiber.dom != null){
        parentDom.appendChild(fiber.dom)
    }
    
    
    // 处理子节点和兄弟节点
    commitWork(fiber.child)
    commitWork(fiber.sibling)

}

5,updateDom更新dom属性,由于该类型时复用dom,只需要对比props即可,简单实现如下,同时createDom也使用updateDom

function createDom(fiber){
  let dom = fiber.type === 'TEXT' ? document.createTextNode('') : document.createElement(fiber.type)
  // 添加属性,文本节点除外
  updateDom(dom, {}, fiber.props)
  return dom
}
// 前缀是 on 返回是 true
const isEvent = (key) => key.startsWith("on");
// 去掉 children 和 on 开头的
const isProperty = (key) => key !== "children" && !isEvent(key);
// 前一次 和 本次 不同
const isNew = (prev, next) => (key) => prev[key] !== next[key];
// 过滤 匹配 下一次 中没有的值
const isGone = (prev, next) => (key) => !(key in next);

function updateDom(dom, prevProps, nextProps){
    // 清空 旧 事件
    Object.keys(prevProps)
    .filter(isEvent)
    .filter((key) => !(key in nextProps) || isNew(prevProps, nextProps)(key))
    .forEach((name) => {
    const eventType = name.toLowerCase().substring(2);
        dom.removeEventListener(eventType, prevProps[name]);
    });

    // 清空 旧 的值
    Object.keys(prevProps)
    .filter(isProperty)
    .filter(isGone(prevProps, nextProps))
    .forEach((name) => {
        dom[name] = "";
    });

    // 设置 新 事件
    Object.keys(nextProps)
    .filter(isEvent)
    .filter(isNew(prevProps, nextProps))
    .forEach((name) => {
        const eventType = name.toLowerCase().substring(2);
        dom.addEventListener(eventType, nextProps[name]);
    });

    // 设置 新 的值
    Object.keys(nextProps)
    .filter(isProperty)
    .filter(isNew(prevProps, nextProps))
    .forEach((name) => {
        if(dom instanceof Object &&  dom.setAttribute){
            // 设置样式
            if(name === 'style'){
                const value = nextProps[name]
                // console.log('style value', value)
                if(value && typeof value === 'object'){
                    for(let i in value){
                        dom.style[i] = value[i]
                    }
                } else {
                    dom.removeAttribute(name) // 样式对象不存在或者样式格式不对
                }
            } else if(name === 'nodeValue'){
                dom.nodeValue=nextProps[name]
            } else {
                dom.setAttribute(name, nextProps[name]);
            }
            
        }else{
            // console.log('设置样式', name)
            dom[name] = nextProps[name];
        }
    });

}

以上实现了Fiber的diff算法

总结:由于创建新的fiber节点时,属性alternate指向了旧fiber,fiber新旧树在对比时,是同层级同位置进行比较,提高了计算效率,算法对比有三种情况:

1,添加新节点

2,替换为新节点,删除旧节点

3,修改节点属性

四,useState实现

1,useState主要应用在函数式组件中,实现useState之前,需要先兼容节点是组件的场景

分析:

组件经过babel转义后,得到的type类型是函数,因此要得到函数内部的dom,需要先执行该函数,将performUnitOfWork进行如下改造

function performUnitOfWork(fiber){
    let elements;
  // 判断fiber是不是组件
  if(typeof fiber.type === 'function'){
    // 判断是否是类组件
    if(fiber.type.prototype.render){
        elements = [fiber.type.prototype.render(fiber.props)] //兼容类组件
    } else {
        elements = [fiber.type(fiber.props)]
    }
  } else {
    if(!fiber.dom){ //如果当前节点上没有真实节点,创建fiber对应的真实节点
        fiber.dom = createDom(fiber)
    }
    elements = fiber.props.children
  }

  // 加入diff算法,对比新老节点
  reconcileChildren(fiber, elements) //说明 在遍历当前节点时,会遍历其所有子节点,并给子节点和子节点的兄弟节点设置alternate, 设置过之后,在下次递归中,刚好可以对比新老节点
  if(fiber.child){
    return fiber.child
  }

  let nextFiber = fiber //缓存当前fiber
  while(nextFiber){
    if(nextFiber.sibling){  //返回当前节点的兄弟节点
        return nextFiber.sibling
    }
    nextFiber = nextFiber.return
  }

}

2,由于组件自身没有dom结构,所以在更新dom时,需要判断节点父级是否是组件,如果是组件则继续向上查找;

在删除无用dom元素时,要判断当前fiber节点是否是组件,如果是组件,则要删除其内部真实dom;

commitRoot, updateDom做以下修改

function commitRoot(){
    //处理要删除的节点
    deletions.forEach(commitWork)
    commitWork(wipRoot.child)
    currentRoot = wipRoot // 缓存上次提交的fiber树
    wipRoot = null
}

function commitWork(fiber){
    if(!fiber){
        return
    }
    const parentDom = findParentDom(fiber) //函数式组件的dom为null, 这里要再向上查找,直到找到存在dom的父级

    if(fiber.effectTag === 'UPDATE' && fiber.dom != null){
        updateDom(fiber.dom, fiber.alternate.props, fiber.props) //更新节点属性
    } else if(fiber.effectTag === 'DELETION'){
        commitDeletion(fiber, parentDom) // 存在组件后,fiber.dom有可能为null
    } else if(fiber.effectTag === 'PLACEMENT' && fiber.dom != null){
        if(fiber.childIndex < fiber.siblingNum){ // 中间元素
            parentDom.insertBefore(fiber.dom, fiber.sibling.dom)
        } else {
            parentDom.appendChild(fiber.dom)
        }
    } else if(fiber.effectTag === 'ADD' && fiber.dom != null){
        parentDom.appendChild(fiber.dom)
    }
    
    
    // 处理子节点和兄弟节点
    commitWork(fiber.child)
    commitWork(fiber.sibling)

}

function commitDeletion(fiber, domParent){
    if(fiber.dom){
      domParent.removeChild(fiber.dom)
    }else{
      commitDeletion(fiber.child, domParent) // 移除真实dom
    }
}

function findParentDom(fiber){
   if(fiber.return.dom){
        return fiber.return.dom
   } else {
        return findParentDom(fiber.return)
   }
}

3,useState实现

分析:

在函数式组件中,每次执行都会调用useState,所以在调用useState时,需要判断是否已经有状态存在,所以每个组件需要有一个变量存储状态

useState可能被调用多次,生成多个状态,存储状态的变量可以看作一个数组队列,存储多个状态

设置状态变量hooks = [],为了保证hooks只有一个,将其绑定在对应的组件fiber节点上

实现如下

在调用useState时,需要访问当前组件fiber节点,可以设置全局变量wipFiber,在performUnitOfWork执行当前节点时,把当前fiber赋值给wipFiber,并设置wipFiber.hooks=[]

定义全局hookIndex对应状态索引,初始为0,每调用一次useState, hookIndex+1

performUnitOfWork执行节点时,将hookIndex重置为0

let wipFiber = null;//本次操作的节点,在组件中赋值
let hookIndex = 0; //状态索引初始值

function performUnitOfWork(fiber){
    let elements;
  // 判断fiber是不是组件
  if(typeof fiber.type === 'function'){
      wipFiber = fiber
      hookIndex = 0 // 当前状态对应的是哪个hooks,状态索引
      wipFiber.hooks = [] //在组件上定义一个队列,用于存储状态,一个组件会有多个状态
    // 判断是否是类组件
    if(fiber.type.prototype.render){
        elements = [fiber.type.prototype.render(fiber.props)]
    } else {
        elements = [fiber.type(fiber.props)]
    }
  } else {
    if(!fiber.dom){ //如果当前节点上没有真实节点,创建fiber对应的真实节点
        fiber.dom = createDom(fiber)
    }
    elements = fiber.props.children
  }
  reconcileChildren(fiber, elements) //说明 在遍历当前节点时,会遍历其所有子节点,并给子节点和子节点的兄弟节点设置alternate, 设置过之后,在下次递归中,刚好可以对比新老节点
  if(fiber.child){
    return fiber.child
  }

  let nextFiber = fiber //缓存当前fiber
  while(nextFiber){
    if(nextFiber.sibling){  //返回当前节点的兄弟节点
        return nextFiber.sibling
    }
    nextFiber = nextFiber.return
  }

}

实现简版useState

function useState(initial){
    // 获取老的状态对象
    const oldHook = wipFiber.alternate && wipFiber.alternate.hooks && wipFiber.alternate.hooks[hookIndex]
    //当前状态值创建
    const hook = {
        state: oldHook ? oldHook : initial,
    }

    wipFiber.hooks.push(hook)
    hookIndex ++;

    return [hook.state]
}

4,给useState添加修改状态函数

useState调用时,返回setState函数,用来修改对应状态

setState是和单个状态相对应的,所以可以给每个状态设置一个事件队列queue,存储操作状态的函数

setState执行后将操作放进对应queue中,并且发起workLoop,重新渲染组件

在重新渲染组件时,会再次调用useState,此时在返回state之前,先调用state对应的queue函数,返回修改后的函数

function useState(initial){
    // 获取老的状态对象
    const oldHook = wipFiber.alternate && wipFiber.alternate.hooks && wipFiber.alternate.hooks[hookIndex]
    //当前状态值创建
    const hook = {
        state: oldHook ? oldHook : initial,
        queue:[] //用来存储操作状态的动作
    }

    const actions = oldHook ? oldHook.queue : []
    // 状态是在下一次渲染时,更新上次修改的状态
    actions.forEach(action => {
        if( typeof action === 'function'){
            hook.state = action(hook.state)
        } else {
            hook.state = action
        }
        
    })
    
    // 定义一个收集action的函数,setState
    const setState = action => {
        hook.queue.push(action)
        
        // 触发更新
        wipRoot = {
            dom: currentRoot.dom,
            props: currentRoot.props,
            alternate: currentRoot,
        }

        nextUnitOfWork = wipRoot
        deletions = []
        requestIdleCallback(wookLoop)

    }

    wipFiber.hooks.push(hook)
    hookIndex ++;

    return [hook.state, setState]
}

说明:

setState没有在执行后立即改变状态,而是先存储操作,在下次更新前,修改状态

state在组件上的存储是队列形式,每次是根据hookIndex获取,所以不能用在判断条件中

以上实现了useState

五,实例

function App(props){
    const [count, setCount] = useState(0)
    return <div className = "test-com" onClick={() => {console.log("test")}}>
        <div>测试</div>
        {
            count == 0 ? <div>当前count是0</div> : <div style={
   
   {color:'red'}}>当前大于0,count值为{count}</div>
        }
        <button onClick={() => {setCount(count+1)}}>点击增加</button>
        <Page></Page> // 组件嵌套
    </div>
}

function Page(){
    return <div>页面demo</div>
}

ReactDOM.render(<App></App>, container)

总结:

1,fiber架构在遍历vDom树同时创建fiber节点,利用浏览器控制递归执行权限,fiber diff算法在生成新的fiber时,给每个fiber节点添加alternater属性指向旧节点,对比时实现同层级同位置比较

2,fiber架构将渲染分为两个阶段,生成带标签的的fiber树(reconlice),提交和渲染fiber树(commit)

3,useState采用函数式编程,引入函数副作用存储状态和操作

更多内容关注公众号:前端361

猜你喜欢

转载自blog.csdn.net/qdmoment/article/details/124863776
今日推荐