实现一个React,理解React原理

react.createElement

创建出一个对象

React中:

const element = <h1 title="foo">Hello</h1>
const container = document.getElementById('root')
ReactDOM.render(element, container)

自己实现:

const element = {
	type: 'h1',
    props: {
		title: 'foo',
        children: 'hello'
    },
}

const container = document.getElementById('root')

const node = document.createElement(element.type)
node['title'] = element.props.title

const text = document.createTextNode('')
text['nodeValue'] = element.props.children

node.appendChild(text)
container.appendChild(node)

createElement

实现自己的 createEelment 而不是 React.createElement

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


function createTextElement(text) {
	return {
        type: 'TEXT_ELEMENT',
        props: {
			nodeValue: text,
            children: [],
        },
    }
}



eg
createElement('div')会返回:
{
	type: 'div'
    props: {
        children: []
    }
}

挂载到 Ha0ran 上

const Ha0ran = {
    createElement,
}

就可以像用 React.createElement 一样用 Ha0ran.createElement

render

我们从用 element type 创建 DOM 节点开始,并且把新节点追加到 container 上

function rener(element, container) {
	const dom = document.createElement(element.type)
    
    container.appendChild(dom)
}

递归处理子元素

function render(element, container) {
   const dom = document.createElement(element.type)

   element.props.children.forEach(child => {
      render(child, dom);
   })

   container.appendChild(dom);
}

我们需要处理文本元素,当 element.type 是 TEXT_ElEMENT 的时候需要创建文本节点而不是常规的节点,所以 dom 出代码需要改为

const dom = 
      element.type === 'TEXT_ELEMENT'
	  	? document.createTextNode("")
		: document.createElement(element.type)
      

最后把元素的属性挂载到节点上(除了children),这里指的属性是 ‘id’ ‘title’ ‘class’ 这些属性

const isProperty = key => key !== 'children')

Obejct.keys(element.props)
	.filter(isProperty)
	.forEach(name => {
    dom[name] = element.props[name]
})

render代码汇总

  function render(element, container) {

      // 创建dom节点 对文本进行了处理
      const dom =
        element.type === 'TEXT_ELEMENT'
          ? document.createTextNode('')
          : document.createElement(element.type);

      // 找出 element 的属性部分
      const isProperty = key => key !== 'children';

      // 将属性挂载到 dom 上
      Object.keys(element.props)
        .filter(isProperty)
        .forEach(name => {
          dom[name] = element.props[name];
        });

      // 递归渲染 子元素
      element.props.children.forEach(child => {
        render(child, dom);
      })

      // 挂载到 container 容器上展示
      container.appendChild(dom);
    }

Concurrent Mode

对于上面的递归函数有问题。

一旦我们开始渲染就不会停止直到整个渲染完成。如果这颗元素树很大,就会造成阻塞主线程。

所以就需要把工作分解成小的单元,每完成一个小的单元就打断渲染如果有其他的事情需要做。

	// 下一个单元的工作
    let nextUnitOfWork = null;

	// 工作循环,循环执行单元,渲染任务
    function workLoop(deadline) {
       // 打断的标志
      let shouldYield = false;
      while (nextUnitOfWork && !shouldYield) {
        nextUnitOfWork = preformUnitOfWork(nextUnitOfWork);
         // 打断标志取决于时间
        shouldYield = deadline.timeRemaining() < 1;
      }
      requestIdleCallback(workLoop);
    }
    requestIdleCallback(workLoop);

    function performUnitOfWork(nextUnitOfWork) {
		// ToDo
        
    }

这里用 requestIdleCallback 做了一个循环,你可以把它看作是 setTimeout,但是requestIdleCallback 会在浏览器空闲的时候浏览器自动调用。

React 官方已经没有用 requestIdleCallback 了,但是实现的效果是差不多的。

Fiber

为了组织这些工作单元,我们需要一个数据结构:a fiber tree

我们将为每个元素提供一个 fiber 并且每个 fiber 都是一个工作单元。

eg. 我们想要渲染这么一颗树:

<div>
    <h1>
      <p />
      <a />
    </h1>
    <h2 />
  </div>

那么就会创建一颗这样的 fiber tree:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IFEbGtgl-1629298565181)(https://i.loli.net/2021/08/18/nBlyXvpxq1YQTcI.png)]

在 render 中我们创建了根节点 root fiber,并将其设置为 nextUnitOfWork,剩下的工作将在 performUnitOfWork 中进行,在 performUnitOfWork 这里我们要做三件事:

  1. 将元素添加到 DOM 上
  2. 给元素的每一个子元素都创建 fiber
  3. 选择下一个工作单元
function performUnitOfWork(fiber) {
    // ToDo add dom node
    // Todo create new fiber
    // Todo return next unit of work
}

这种数据结构的目标之一是轻松的找到下一个工作单元,所以每一个 fiber 都有一个一个连接到他的子节点,兄弟节点,和父节点。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dYYbxDhB-1629298565183)(https://i.loli.net/2021/08/18/nPl4qxJfyvMkeL1.png)]

如果该 fiber 的工作完成,就会去找下一个 工作单元,而这个工作单元就是他下一个子元素的 fiber。如果没有子元素那就去找 兄弟元素 sibling,如果兄弟元素也没有了就去找他的父元素,这里可以是兄弟元素的父元素,比如上面的 a 指向了 h1。最后直到回到 root 就算渲染完成。

创建一个 createDom 函数:

function createDom(fiber) {
    const dom = 
          fiber.type === 'TEXT_ELEMENT'
    	  ? document.createTextNode('')
    	  : document.createElement(fiber.type)
    const isProperty = key => key !== 'children';
    Object.keys(fiber.type)
    	.filter(isProperty)
    	.forEach(name => {
        dom[name] = fiber.props[name]
    })
    return dom;
}

改变 render 函数:

function render(element, container) {
    nextUnitOfWork = {
        dom: container,
        props: {
            children: [element]
        }
    }
}

在 render 函数里我设置了 nextUnitOfWork 到 fiber 树的 root节点。

performUnitOfWork:

function performUnitOfWork(fiber) {
    if (!fiber.dom) {
		fiber.dom = createDom(fiber)
    }
    
    if (fiber.parent) {
        fiber.parent.dom.appendChild(fiber.dom)
    }
    
    // Todo create new fiber
    // Todo return next unit of work
    
}

然后对于每个子元素都创建一个新的 fiber

function performUnitOfWork(fiber) {
    if (!fiber.dom) {
		fiber.dom = createDom(fiber)
    }
    
    if (fiber.parent) {
        fiber.parent.dom.appendChild(fiber.dom)
    }
    
    const elements = fiber.props.children;
    const index = 0;
    let preSibling = null;
    
    while (index < elements.length) {
		const element = elements[index];
        
        const newFiber = {
			type: element.type,
            props: element.props,
            parent: fiber,
            dom: null,
        }
    }
    
    // Todo return next unit of work
    
}

然后我们把 newFiber 添加到 fiber tree 中将其设为孩子或者兄弟,取决于它是否是第一个孩子。

function performUnitOfWork(fiber) {
    // ToDo add dom node
    if (!fiber.dom) {
		fiber.dom = createDom(fiber)
    }
    
    if (fiber.parent) {
        fiber.parent.dom.appendChild(fiber.dom)
    }
    
      // Todo create new fiber
    const elements = fiber.props.children;
    const index = 0;
    let preSibling = null;
    
    while (index < elements.length) {
		const element = elements[index];
        
        const newFiber = {
			type: element.type,
            props: element.props,
            parent: fiber,
            dom: null,
        }
        
        
        if (index === 0) {
            fiber.child = newFiber
        } else {
            preSibling.sibling = newFiber
        }
        preSibling = newFiber
        index++;
    }
    
    
    
    
    // Todo return next unit of work
    
}

最后我们找下一个工作单元:

function performUnitOfWork(fiber) {
    // ToDo add dom node
    if (!fiber.dom) {
		fiber.dom = createDom(fiber)
    }
    
    if (fiber.parent) {
        fiber.parent.dom.appendChild(fiber.dom)
    }
    
     // Todo create new fiber
    const elements = fiber.props.children;
    const index = 0;
    let preSibling = null;
    
    while (index < elements.length) {
		const element = elements[index];
        
        const newFiber = {
			type: element.type,
            props: element.props,
            parent: fiber,
            dom: null,
        }
        
        
        if (index === 0) {
            fiber.child = newFiber
        } else {
            preSibling.sibling = newFiber
        }
        preSibling = newFiber
        index++;
    }
    
    // Todo return next unit of work
    
    if (fiber.child) {
		return fiber.child;
    }
    let nextFiber = fiber;
    while (nextFiber) {
        if (nextFiber.sibling) {
			return nextFiber.sibling;
        }
       	nextFiber = nextFiber.parent;
    }
    
    
}

Render and Commit phases

为了让用户看到完整的 UI(因为浏览器会打断我们的渲染工作),我们需要删除 performUnitOfWork 中的这段代码

if (fiber.parent) {
    fiber.parent.dom.appendChild(fiber.dom)
}

并在 render 函数中这样跟踪 fiber 树中的根节点

function render(element, container) {
    wipRoot = {
        dom: container,
        props: {
            children: [element],
        },
    }
    nextUnitOfWork = wipRoot;
}
let nextUnitOfWork = null
let wipRoot = null

一旦完成(没有下一个工作单元了)我们就提交整个 fiber tree 给DOM

function commitRoot() {
    // ToDo add nodes to dom
    commitWork(wipRoot.child);
    wipRoot = null;
}

// 递归 将所有节点追加到 dom 上
function commitWork(fiber) {
	if (!fiber) {
        return
    }
    const domParent = fiber.parent.dom;
    domParent.appendChild(fiber.dom)
    commitWork(fiber.child)
    commitWork(fiber.sibling)
}

function workLoop(deadline) {
	...
    ...
    if (!nextUnitOfWork && wipRoot) {
        commitRoot();
    }
}

到目前为止我们只做了添加功能,下面还要做更新和删除功能

Reconciliation

我们需要保存一个保存一个引用指向"上一个 fiber tree 我们提交到DOM上的"当我们完成提交。取名为 currentRoot。

还需要给每个 fiber 添加一个属性叫 alternate,这个属性指向上一个 fiber(在上一次提交阶段我们提交到DOM上的)。

function commitRoot(){
	commitWork(wipRoot.child)
    // newly added snippet
    currentRoot = wipRoot
    
    wipRoot = null
}

function render(element, container) {
	wipRoot = {
        dom: container,
        props: {
            children: [element],
        },
        // newly added snippet
        alternate: currentRoot,
    }
    nextUnitOfWork = wipRoot;
}

let nextUnitOfWork = null
// Newly added snippet 
let currentRoot = null

let wipRoot = null

现在的 perfromUnitOfWork

function performUnitOfWork(fiber) {
    // ToDo add node to dom
    if (!fiber.dom) {
		fiber.dom = createDom(fiber)
    }
    // ToDo create new fiber
    const elements = fiber.props.children;
    const index = 0;
    const prevSibling = null;
    while (index < elements.length) {
		const element = elements[index];
        const newFiber = {
            dom: null,
            type: element.type,
            props: element.props,
            parent: fiber
        }
        if (index === 0) {
            fiber.child = newFiber
        } else {
			prevSibling.sibling = newFiber
        }
        prevSibling = newFiber;
        index++;
    }
    
    // ToDo return next unit of work
    if (fiber.child) {
        return fiber.child;
    }
    let newFiber = fiber;
    while(newFiber) {
		if (newFiber.sibling) {
            return newFiber.sibling;
        }
        newFiber = newFiber.parent;
    }
}

添加一个新的函数:reconcileChildren

同时迭代之前的fiber的子元素(wipFiber.alternate)和我们想要调和的元素的数组

这段时间,我们只留下两个最重要的东西:oldFiber 和 elements

element 是我们想要渲染到 dom 上的东西

oldFiber 是我们之前渲染的东西

function performUnitOfWork(fiber) {
    if (!fiber.dom) {
        fiber.dom = createDom(fiber);
    }
    
    const elements = fiber.props.children;
    reconcileChildren(fiber, element);
	
    if (fiber.child) {
        ...
    }
}
    
    
// 这里我们将调和之前的fiber和新的元素

function reconcileChildren (wipFiber, elements) {
	let index = 0;
    let  oldFiber = 
        wipFiber.alternate && wipFiber.alternate.child;
    
    while (
        index < element.length ||
        oldFiber != null   // 注意。这里不能用 !== ,否则不行,具体原因还未知
    ) {
        const element = element[index];
        
        // ToDo compare oldFiber to element
    }
}

比较 oldFiber 和 element 的方法:

这里类型说的是节点类型 比如 div h1 这种类型

  • 如果之前的 fiber 和 新的元素类型相同,我们保留这个节点,仅仅只是更新新的属性。[1]
  • 如果类型不一样并且这里有新的元素进来,我们就要创建节点。[2]
  • 如果类型不同并且还有之前的 fiber(节点与新的节点不一样),就要移除之前的节点 [3]
const sameType = 
      oldFiber && 
      element &&
      element.type === oldFiber.type;
if (sameType) {
	// ToDo update the node
}
if (element && !sameType) {
    // ToDo add this node
}
if (oldFiber && !sameType) {
    // ToDo delete the oldFiber's node
}

当为 [1] 的时候:

if (sameType) {
	newFiber = {
        type: oldFiber.type,
        props: element.props,
        dom: oldFiber.dom,
        parent: wipFiber,
        alternate: oldFiber,
        effectTag: 'UPDATE',
    }
}

这里我们添加了一个新的属性: effectTag 后面提交阶段会用到。

当为 [2] 的时候:

if (element && !sameType) {
	newFiber = {
        type: element.type,
       	props: element.props,
        dom: null,
        parent: wipFiber,
        alternate: null,
        effectTag: 'PLACEMENT',
    }
}

当为 [3] 时:

if (oldFiber && !sameType) {
    oldFiber.effectTag = 'DELETION';
    deletions.push(oldFiber);
}

但是当我们从正在进行的根节点把 fiber tree 提交到 DOM 时,是没有之前的 fiber的,所以 render 函数中需要加个数组跟踪我们想要移除的节点。

funciton render(element, container) {
	...
    deletions = [];
    ...
}
    
let deletions = null;
function commitRoot() {
	deletions.forEach(commitWork);
    commitWork(wipRoot.child);
    currentRoot = wipRoot;
    wipRoot = null;
}

修改 commitWork 的代码,处理不同的 eefectTags

function updateDom(dom, prevProps, nextProps) {
    // ToDo
}

function commitWork(fiber) {
	if (fiber) {
		return;
    }
    const domParent = fiber.parent.dom;
    if (
        fiber.effectTag === 'PLACEMENT' 
    	&& fiber.dom !== null
    ) {
        domParent.appendChild(fiber.dom);
    } else if (fiber.effectTag === 'DELETION') {
		domParent.removeChild(fiber,dom);
    } else if (
    	fiber.effectTag === 'UPDATE' &&
        fiber.dom !== null
    ) {
		updateDom(
        	fiber.dom,
            fiber.alternate.props,
            fiber.props
        )
    }
}

我们比较之前 fiber 的 props 和新的 fiber 的 props,移除已经消失的 props,设置新的或者改变的 props。

const isProperty = key => key !== 'children';
const isNew = (prev, next) => key =>
	prev[key] !== next[key];
const isGone = (prev, next) => key => !(key in next);

function updateDom(dom, prevProps, nextProps) {
    // Remove old properties
    Object.keys(prevProps)
    	.filter(isProperty)
    	.filter(isGone(prevProps, nextProps))
    	.forEach(name => {
        	dom[name] = ""
    });
    
    // Set new or changed properties
    Obejct.keys(nextProps)
    	.filter(isProperty)
    	.filter(isNew(prevProps, nextProps))
    	.forEach(name => {
        	dom[name] = nextProps[name];
    });
}

我们还需要处理这样一个特殊的属性:事件监听(以 on 开头的)

所以代码应该改为

const isEvent = key => key.startsWith('on');
const isProperty = key =>
	ket !== 'children' && !isEvent(key);

updateDom 中添加对事件的处理:移除和添加

function updateDom(dom, prevProps, nextProps) {
    // Remove old or changed event listeners
    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,
                preProps[name]
            )
    	})
    
    // Add event listener
    Object.keys(nextProps)
    	.filter(isEvent)
    	.filter(isNew(prevProps, nextProps))
    	.forEach(name => {
        	const eventType = name
            	.toLowerCase()
            	.substring(2);
        	dom.addEventListener(
            	eventType,
                nextProps[name]
            )
    	})
}

对目前实现的代码做个汇总:

function createElement(type, props, ...children) {
  return {
    type,
    props: {
      ...props,
      children: children.map(child =>
        typeof child === "object"
          ? child
          : createTextElement(child)
      ),
    },
  }
}

function createTextElement(text) {
  return {
    type: "TEXT_ELEMENT",
    props: {
      nodeValue: text,
      children: [],
    },
  }
}

function createDom(fiber) {
  const dom =
    fiber.type == "TEXT_ELEMENT"
      ? document.createTextNode("")
      : document.createElement(fiber.type)

  updateDom(dom, {}, fiber.props)

  return dom
}

const isEvent = key => key.startsWith("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) {
  //Remove old or changed event listeners
  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]
      )
    })

  // Remove old properties
  Object.keys(prevProps)
    .filter(isProperty)
    .filter(isGone(prevProps, nextProps))
    .forEach(name => {
      dom[name] = ""
    })

  // Set new or changed properties
  Object.keys(nextProps)
    .filter(isProperty)
    .filter(isNew(prevProps, nextProps))
    .forEach(name => {
      dom[name] = nextProps[name]
    })

  // Add event listeners
  Object.keys(nextProps)
    .filter(isEvent)
    .filter(isNew(prevProps, nextProps))
    .forEach(name => {
      const eventType = name
        .toLowerCase()
        .substring(2)
      dom.addEventListener(
        eventType,
        nextProps[name]
      )
    })
}

function commitRoot() {
  deletions.forEach(commitWork)
  commitWork(wipRoot.child)
  currentRoot = wipRoot
  wipRoot = null
}

function commitWork(fiber) {
  if (!fiber) {
    return
  }

  const domParent = fiber.parent.dom
  if (
    fiber.effectTag === "PLACEMENT" &&
    fiber.dom != null
  ) {
    domParent.appendChild(fiber.dom)
  } else if (
    fiber.effectTag === "UPDATE" &&
    fiber.dom != null
  ) {
    updateDom(
      fiber.dom,
      fiber.alternate.props,
      fiber.props
    )
  } else if (fiber.effectTag === "DELETION") {
    domParent.removeChild(fiber.dom)
  }

  commitWork(fiber.child)
  commitWork(fiber.sibling)
}

function render(element, container) {
  wipRoot = {
    dom: container,
    props: {
      children: [element],
    },
    alternate: currentRoot,
  }
  deletions = []
  nextUnitOfWork = wipRoot
}

let nextUnitOfWork = null
let currentRoot = null
let wipRoot = null
let deletions = null

function workLoop(deadline) {
  let shouldYield = false
  while (nextUnitOfWork && !shouldYield) {
    nextUnitOfWork = performUnitOfWork(
      nextUnitOfWork
    )
    shouldYield = deadline.timeRemaining() < 1
  }

  if (!nextUnitOfWork && wipRoot) {
    commitRoot()
  }

  requestIdleCallback(workLoop)
}

requestIdleCallback(workLoop)

function performUnitOfWork(fiber) {
  if (!fiber.dom) {
    fiber.dom = createDom(fiber)
  }

  const elements = fiber.props.children
  reconcileChildren(fiber, elements)

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

function reconcileChildren(wipFiber, elements) {
  let index = 0
  let oldFiber = wipFiber.alternate && wipFiber.alternate.child
  let prevSibling = null

  while (index < elements.length || oldFiber != null) {
    const element = elements[index]
    let newFiber = null

    const sameType =
      oldFiber &&
      element &&
      element.type == oldFiber.type

    if (sameType) {
      newFiber = {
        type: oldFiber.type,
        props: element.props,
        dom: oldFiber.dom,
        parent: wipFiber,
        alternate: oldFiber,
        effectTag: "UPDATE",
      }
    }

    if (element && !sameType) {
      newFiber = {
        type: element.type,
        props: element.props,
        dom: null,
        parent: wipFiber,
        alternate: null,
        effectTag: "PLACEMENT",
      }
    }

    if (oldFiber && !sameType) {
      oldFiber.effectTag = "DELETION"
      deletions.push(oldFiber)
    }

    if (oldFiber) {
      oldFiber = oldFiber.sibling
    }

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

    prevSibling = newFiber
    index++
  }
}

Function Component

函数组件与之前不同的地方在于

  • fiber 来自于函数组件没有 DOM 节点
  • 子元素来自于于函数的执行而不是直接从 props 中获取

改变 performUnitOfWork 的代码

function performUnitOfWork(fiber) {
    const isFunctionComponent = 
          fiber.type instanceof Function;
    if (isFunctionComponent) {
		updateFunctionComponent(fiber);
    } else {
        updateHostComponent(fiber)
    }
    if (fiber.child) {
		return fiber.child;
    }
    let nextFiber = fiber;
    while (nextFiber) {
        if (nextFiber.sibling) {
            return nextFiber.sibling;
        }
        nextFiber = nextFiber.parent;
    }
}


function updateFunctionComponent(fiber) {
    // ToDo
}

function updateHostComponent(fiber) {
    if (!fiber.dom) {
        fiber.dom = createDom(fiber);
    }
    reconcileChildren(fiber, fiber.props.children);
}

在 updateFunctionComponent 中我们执行函数然后拿到 children

function updateFunctionComponent(fiber) {
    const children = [fiber.type(fiber.props)]
    reconcileChildren(fiber, children);
}

将 commitWork 中

const domParent = fiber.parent.dom

改为

let domParentFiber = fiber.parent
// 一直往上找直到找到 DOM节点
while (!domParentFiber.dom) {
    domParentFiber = domParentFiber.parent;
}
const domParent = domParentFiber.dom

删除的时候也要改

if (fiber.effectTag === 'DELETION') {
	domParent.removeChild(fiber.dom)
}

改为

if (fiber.effectTag === 'DELETION') {
	commitDeletion(fiber, domParent);
}
function commitDeletion(fiber, domParent) {
	if (fiber.dom) {
		domParent.removeChild(fiber.dom)
    } else {
		commitDeletion(fiber.child, domParent)
    }
}

Hooks

let wipFiber = null
let hookIndex = null

function updateFunctionComponent(fiber) {
    wipFiber = fiber;
    hookIndex = 0;
    wipFiber.hooks = [];
}

function useState(initial) {
    // ToDo
}

当调用 useState 的时候,检查是否有 old hook,通过用 hook index 检查 fiber 中的 alternate。

如果有 old hook,就把 state 从 old hook 中复制到 new hook 中,如果没有就初始化一个。

当我们给 fiber 添加了一个 hook,就将索引值 index 加一,并返回 state。

function useState(initial) {
    const oldHook = 
          wipFiber.alternate &&
          wipFiber.alternate.hooks &&
          wipFiber.alternate.hooks[hookIndex]
    const hook = {
        state: oldHook ? oldHook.state : initial,
    }
    wipFiber.hooks.push(hook);
    hookIndex++;
    return [hook, state];
}

我们还需要添加一个方法更新 state,就是 setState。

然后我们做一些类似于我们在渲染函数中所做的事情,将一个新的正在进行的工作根设置为下一个工作单元,这样工作循环就可以开始一个新的渲染阶段。

我们在下一次渲染组件时这样做,我们从旧的钩子队列中获取所有的动作,然后将它们一一应用到新的钩子状态,所以当我们返回状态时,它会被更新。

function useState(initial) {
    const oldHook = 
          wipFiber.alternate &&
          wipFiber.alternate.hooks &&
          wipFiber.alternate.hooks[hookIndex]
    const hook = {
        state: oldHook ? oldHook.state : initial,
        // Newly added snippet
        queue: [],
    }
    // Newly added snippet
    const actions = oldHook ? oldHook.queue: []
    actions.forEach(action => {
        hook.state = action(hook.state);
    })
    
    // Newly added snippet
    const setState = action => {
        hook.queue.push(action);
        wipRoot = {
            dom: currentRoot.dom,
            props: currentRoot.props,
            alternate: currentRoot,
        }
        nextUnitOfWork = wipRoot;
        deletions = [];
    }
    
    
    wipFiber.hooks.push(hook);
    hookIndex++;
    return [hook, state, setState];
}

至此,我们完成了自己的 react。

function createElement(type, props, ...children) {
  return {
    type,
    props: {
      ...props,
      children: children.map(child =>
        typeof child === "object" ? child : createTextElement(child)
      )
    }
  };
}

function createTextElement(text) {
  return {
    type: "TEXT_ELEMENT",
    props: {
      nodeValue: text,
      children: []
    }
  };
}

function createDom(fiber) {
  const dom =
    fiber.type == "TEXT_ELEMENT"
      ? document.createTextNode("")
      : document.createElement(fiber.type);

  updateDom(dom, {}, fiber.props);

  return dom;
}

const isEvent = key => key.startsWith("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) {
  //Remove old or changed event listeners
  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]);
    });

  // Remove old properties
  Object.keys(prevProps)
    .filter(isProperty)
    .filter(isGone(prevProps, nextProps))
    .forEach(name => {
      dom[name] = "";
    });

  // Set new or changed properties
  Object.keys(nextProps)
    .filter(isProperty)
    .filter(isNew(prevProps, nextProps))
    .forEach(name => {
      dom[name] = nextProps[name];
    });

  // Add event listeners
  Object.keys(nextProps)
    .filter(isEvent)
    .filter(isNew(prevProps, nextProps))
    .forEach(name => {
      const eventType = name.toLowerCase().substring(2);
      dom.addEventListener(eventType, nextProps[name]);
    });
}

function commitRoot() {
  deletions.forEach(commitWork);
  commitWork(wipRoot.child);
  currentRoot = wipRoot;
  wipRoot = null;
}

function commitWork(fiber) {
  if (!fiber) {
    return;
  }

  let domParentFiber = fiber.parent;
  while (!domParentFiber.dom) {
    domParentFiber = domParentFiber.parent;
  }
  const domParent = domParentFiber.dom;

  if (fiber.effectTag === "PLACEMENT" && fiber.dom != null) {
    domParent.appendChild(fiber.dom);
  } else if (fiber.effectTag === "UPDATE" && fiber.dom != null) {
    updateDom(fiber.dom, fiber.alternate.props, fiber.props);
  } else if (fiber.effectTag === "DELETION") {
    commitDeletion(fiber, domParent);
  }

  commitWork(fiber.child);
  commitWork(fiber.sibling);
}

function commitDeletion(fiber, domParent) {
  if (fiber.dom) {
    domParent.removeChild(fiber.dom);
  } else {
    commitDeletion(fiber.child, domParent);
  }
}

function render(element, container) {
  wipRoot = {
    dom: container,
    props: {
      children: [element]
    },
    alternate: currentRoot
  };
  deletions = [];
  nextUnitOfWork = wipRoot;
}

let nextUnitOfWork = null;
let currentRoot = null;
let wipRoot = null;
let deletions = null;

function workLoop(deadline) {
  let shouldYield = false;
  while (nextUnitOfWork && !shouldYield) {
    nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
    shouldYield = deadline.timeRemaining() < 1;
  }

  if (!nextUnitOfWork && wipRoot) {
    commitRoot();
  }

  requestIdleCallback(workLoop);
}

requestIdleCallback(workLoop);

function performUnitOfWork(fiber) {
  const isFunctionComponent = fiber.type instanceof Function;
  if (isFunctionComponent) {
    updateFunctionComponent(fiber);
  } else {
    updateHostComponent(fiber);
  }
  if (fiber.child) {
    return fiber.child;
  }
  let nextFiber = fiber;
  while (nextFiber) {
    if (nextFiber.sibling) {
      return nextFiber.sibling;
    }
    nextFiber = nextFiber.parent;
  }
}

let wipFiber = null;
let hookIndex = null;

function updateFunctionComponent(fiber) {
  wipFiber = fiber;
  hookIndex = 0;
  wipFiber.hooks = [];
  const children = [fiber.type(fiber.props)];
  reconcileChildren(fiber, children);
}

function useState(initial) {
  const oldHook =
    wipFiber.alternate &&
    wipFiber.alternate.hooks &&
    wipFiber.alternate.hooks[hookIndex];
  const hook = {
    state: oldHook ? oldHook.state : initial,
    queue: []
  };

  const actions = oldHook ? oldHook.queue : [];
  actions.forEach(action => {
    hook.state = action(hook.state);
  });

  const setState = action => {
    hook.queue.push(action);
    wipRoot = {
      dom: currentRoot.dom,
      props: currentRoot.props,
      alternate: currentRoot
    };
    nextUnitOfWork = wipRoot;
    deletions = [];
  };

  wipFiber.hooks.push(hook);
  hookIndex++;
  return [hook.state, setState];
}

function updateHostComponent(fiber) {
  if (!fiber.dom) {
    fiber.dom = createDom(fiber);
  }
  reconcileChildren(fiber, fiber.props.children);
}

function reconcileChildren(wipFiber, elements) {
  let index = 0;
  let oldFiber = wipFiber.alternate && wipFiber.alternate.child;
  let prevSibling = null;

  while (index < elements.length || oldFiber != null) {
    const element = elements[index];
    let newFiber = null;

    const sameType = oldFiber && element && element.type == oldFiber.type;

    if (sameType) {
      newFiber = {
        type: oldFiber.type,
        props: element.props,
        dom: oldFiber.dom,
        parent: wipFiber,
        alternate: oldFiber,
        effectTag: "UPDATE"
      };
    }
    if (element && !sameType) {
      newFiber = {
        type: element.type,
        props: element.props,
        dom: null,
        parent: wipFiber,
        alternate: null,
        effectTag: "PLACEMENT"
      };
    }
    if (oldFiber && !sameType) {
      oldFiber.effectTag = "DELETION";
      deletions.push(oldFiber);
    }

    if (oldFiber) {
      oldFiber = oldFiber.sibling;
    }

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

    prevSibling = newFiber;
    index++;
  }
}

原文连接: build your own react

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值