233333(二)

218 阅读5分钟

前情提要

啪!上回书说到我们已经实现了createElement和render方法了。

const myReact = {
  createElement,
  render
}

如果有小伙伴已经忘记了,可以去翻一下上一篇的文章 链接🔗

接下来我们实现function component 和 hook

话不多说开始吧

function component

先写一个function component

function App(props) {
  return <h1>hi {props.name}</h1>
}
const element = <App name="lyc">

等价于

function App(props) {
	return mReact.createElement("h1", null, "hi", props.name)
}
const element = mReact.createElement(App, {name: "lyc"})

在myReact中function component和普通的节点有两点不同

  • fiber没有dom node(也就是dom属性为null)
  • 获取children是需要执行这个function的,而不是通过vdom的props.children获取

这样我们需要修改下我们的performUnitOfWork函数。把普通的节点和function component区分开来,简单一点,直接通过判断vdom的type属性就可以了(感谢babel 🐶)。

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;
  .....
}

原来这部分逻辑适用于一般的nodeComponent,保持这部分逻辑不变,把他们放在nodeComponent函数中,添加一个新函数 functionComponent

function performUnitOfWork(fiber) {
 + const isFunctionComponent = fiber.type instanceof Function; // 判断是否是函数组件
 + if (isFunctionComponent) {
 +  functionComponent(fiber);
  } else {
 +  nodeComponent(fiber);
  }
  if (fiber.child) {
    return fiber.child;
  }
  ....
}

function functionComponent(fiber) {
  const elements = [fiber.type(fiber.props)] //执行下函数,获取children
  reconcileChildren(fiber, elements);
}

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

构建fiber的reconcileChildren函数无需改变,看下现在的fiber tree

functionComponent是没有dom的,无法完成挂载。我们需要改变挂载dom的commitWork函数。

function commitWork(fiber) {
  if (!fiber) {
    return;
  }
- const domParent = fiber.parent.dom;
+ let domParentFiber = fiber.parent;
  // 直到找到有dom属性的父节点(DOM node)
+ while (!domParentFiber.dom) {
+   domParentFiber = domParentFiber.parent;
+ }
+ const domParent = domParentFiber.dom;
  if (fiber.effectTag === "add-element" && fiber.dom != null) {
    domParent.appendChild(fiber.dom);
  }else if ......
  ......
}

可以看到思想其实非常简单,就是找到这个functionComponent的父节点而已。

好啦,现在我们已经能够正常挂载了,但第二次render的时候万一这个component被删除,还需要改下删除dom的方法

function commitWork(fiber) {
      if (!fiber) {
        return;
      }
 ....
  if (fiber.effectTag === "delete-element") {
    -	domParent.removeChild(fiber.dom)
    +	commitDeletion(fiber, domParent)
  }
}

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

来看一下效果

function App(props) {
  return <h1>hi {props.name}</h1>;
}
const myRender = (value) => {
  const Element = (
    <div>
      <input onInput={updateValue} value={value} />
      <h2 onClick={() => changeDom(value)}>Hello {value}</h2>
      {flag ? <p>123</p> : <span>321</span>}
      <App name="lyc" />
    </div>
  )
  myReact.render(Element, container);
}
myRender('cool');

function component基本就完成了,那么既然有了function component,它就应该有自己的state,接下来我们来实现一下useState.

hook(useState)

先康康useState的使用方法

看到我们需要我们自己的类下面添加一个useState属性,它接受一个参数并且它返回一个数组。

const myReact = {
  createElement,
  render,
  useState
}

function useState (initial) {
  // .....
  const setState = () => {}
  return [initial, setState]
}

通过上图可以看出,我们需要在functionComponent被call的时候,在获取children之前做一些事情。

let wipFiber = null;
let hookIndex = null;

function functionComponent(fiber) {
+ wipFiber = fiber; 	// 记录下当前fiber,可以让我们在useState函数中访问到
+ hookIndex = 0; 	// 支持多个hook
+ wipFiber.hooks = []; 	// 给function fiber 添加hooks属性用于收集该function的hook
  const elements = [fiber.type(fiber.props)];
  reconcileChildren(fiber, elements)
}

fiber.type(fiber.props)这句话执行的时候(就是Counter函数被call的时候),我们就会执行useState函数了。先让它能够返回state

function useState (initial) {
  // alternate属性是在执行reconcileChildren函数的时候挂上的,用于对比新老fiber
  const oldHook =
  wipFiber.alternate &&
  wipFiber.alternate.hooks &&
  wipFiber.alternate.hooks[hookIndex];
  const hook = {
    state: oldHook ? oldHook.state : initial  
  };
  wipFiber.hooks.push(hook); // 记录当前的state,下一次需要render时候使用
  hookIndex++;
  
  const setState = action => {}
  return [hook.state, setState]
}

接下来实现setState方法。说一下思路。

当我们调用setState方法后,需要记录一下我们的action(也就是setState的参数函数)和当前state,然后我们触发workLoop重新计算fiber tree,第二次(n次)调用useState的时候,通过上一次记录下的action和state返回计算后的state并render

function useState (initial) {
    const oldHook =
    wipFiber.alternate &&
    wipFiber.alternate.hooks &&
    wipFiber.alternate.hooks[hookIndex];
    const hook = {
      state: oldHook ? oldHook.state : initial,
+     queue: [], // 收集action
    };
    wipFiber.hooks.push(hook);
    hookIndex++;
+   const actions = oldHook ? oldHook.queue : [];  // 取出action
	// 计算当前state
+   actions.forEach((action) => {
+     hook.state = action(hook.state);
+   });
+   const setState = (action) => {
	// 利用闭包访问到 wipFiber  
+     hook.queue.push(action);  //记录action
	// 通过nextUnitOfWork 触发workLoop重新计算fiber tree
+     wipRoot = {
+       dom: currentRoot.dom,
+       props: currentRoot.props,
+       alternate: currentRoot,
+     };
+     nextUnitOfWork = wipRoot;
+   };
    hookIndex++;
    return [hook.state, setState]
}

可以看一下我们oldfiber中的hook

那么我们useState最基本的功能就已经实现了。 我们的myReact也差不多了最后我贴下完整的代码,下一篇文章我想想,那就myWebpack吧。

所有代码

import React from "react";

const myReact = {
  createElement,
  render,
  useState,
};
//  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",
    props: {
      nodeValue: text,
      children: [],
    },
  };
}

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

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

// hook
let wipFiber = null;
let hookIndex = null;
requestIdleCallback(workLoop);

function workLoop(deadline) {
  let shouldYield = false;
  while (nextUnitOfWork && !shouldYield) {
    nextUnitOfWork = performUnitOfWork(nextUnitOfWork); // 接受一个节点作为参数,并返回下一个要挂载的节点
    shouldYield = deadline.timeRemaining() < 1;
  }
  if (!nextUnitOfWork && wipRoot) {
    commitRoot(); //将fiber.dom 挂到真实的dom上
  }
  // 浏览器空闲时再执行nextUnitOfWork
  requestIdleCallback(workLoop);
  // console.log(wipRoot);
}

function performUnitOfWork(fiber) {
  const isFunctionComponent = fiber.type instanceof Function; // 判断是否是函数组件
  if (isFunctionComponent) {
    functionComponent(fiber);
  } else {
    nodeComponent(fiber);
  }
  if (fiber.child) {
    return fiber.child;
  }
  // 确定 nextFiber (nextUnitOfWork)
  let nextFiber = fiber;
  while (nextFiber) {
    if (nextFiber.sibling) {
      return nextFiber.sibling;
    }
    nextFiber = nextFiber.parent;
  }
}

function nodeComponent(fiber) {
  if (!fiber.dom) {
    fiber.dom = createDom(fiber); // 将vdom转化为真实dom
  }
  const elements = fiber.props.children;
  reconcileChildren(fiber, elements); //函数作用是: 通过vdom 构建fiber
}

function functionComponent(fiber) {
  // console.log(fiber);
  wipFiber = fiber; // 记录下当前fiber,可以让我们在useState函数中访问到
  hookIndex = 0; // 一个函数支持多个hook
  wipFiber.hooks = []; // // 给function fiber 添加hooks 属性用于收集该function的hook
  const elements = [fiber.type(fiber.props)];
  reconcileChildren(fiber, elements); //函数作用是: 通过vdom 构建fiber
}

function useState(initial) {
  // alternate属性是在执行reconcileChildren函数的时候挂上的,用于对比新老fiber
  const oldHook =
    wipFiber.alternate &&
    wipFiber.alternate.hooks &&
    wipFiber.alternate.hooks[hookIndex];
  if (wipFiber.alternate && wipFiber.alternate.hooks) {
    console.log(wipFiber.alternate.hooks);
  }
  const hook = {
    state: oldHook ? oldHook.state : initial,
    queue: [], // // 收集action
  };
  const actions = oldHook ? oldHook.queue : []; // 取出action
  // 计算当前state
  actions.forEach((action) => {
    hook.state = action(hook.state);
  });

  const setState = (action) => {
    // 利用闭包访问到 wipFiber
    hook.queue.push(action); //记录action
    // 通过nextUnitOfWork 触发workLoop重新计算fiber tree
    wipRoot = {
      dom: currentRoot.dom,
      props: currentRoot.props,
      alternate: currentRoot,
    };
    nextUnitOfWork = wipRoot;
    deletions = [];
  };
  wipFiber.hooks.push(hook); // 记录当前的state,下一次需要render时候使用
  hookIndex++;
  return [hook.state, setState];
}

function createDom(fiber) {
  const dom =
    fiber.type == "text"
      ? document.createTextNode("")
      : document.createElement(fiber.type);
  updateDom(dom, {}, fiber.props); // 给dom 加上属性和事件

  return dom;
}
const isEvent = (key) => key.startsWith("on"); // 以on 开头的 一般都是监听事件
const isProperty = (key) => key !== "children" && !isEvent(key); // 排除children和event 选出属性
const isNew = (prev, next) => (key) => prev[key] !== next[key]; // 判断是否是新属性
const isGone = (prev, next) => (key) => !(key in next); // 判断要删除的属性
// 为真实dom挂上属性和事件
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 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 newFiber = null;

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

    // update
    if (sameType) {
      newFiber = {
        type: oldFiber.type,
        props: element.props,
        dom: oldFiber.dom,
        parent: wipFiber,
        alternate: oldFiber,
        effectTag: "update-element",
      };
    }
    // add
    if (element && !sameType) {
      newFiber = {
        type: element.type,
        props: element.props,
        dom: null,
        parent: wipFiber,
        alternate: null,
        effectTag: "add-element",
      };
    }
    // delete
    if (oldFiber && !sameType) {
      oldFiber.effectTag = "delete-element";
      deletions.push(oldFiber);
    }

    if (oldFiber) {
      oldFiber = oldFiber.sibling;
    }
    if (index === 0) {
      wipFiber.child = newFiber;
    } else if (element) {
      prevSibling.sibling = newFiber; // 给上一个fiber添加sibling(elements.length>1)也就是elements.prop.children大于一个
    }
    prevSibling = newFiber;
    index++;
  }
  // console.log(wipFiber);
}

function commitRoot() {
  deletions.forEach(commitWork); //再次挂载前先把要删除的节点先删掉
  commitWork(wipRoot.child); // 挂dom
  currentRoot = wipRoot; // 记录上一次render的fiber tree, 下一次render做比较
  wipRoot = null;
}
function commitWork(fiber) {
  if (!fiber) {
    return;
  }
  let domParentFiber = fiber.parent;
  // 直到找到有dom属性的父节点(DOM node)
  while (!domParentFiber.dom) {
    domParentFiber = domParentFiber.parent;
  }
  const domParent = domParentFiber.dom;
  if (fiber.effectTag === "add-element" && fiber.dom != null) {
    domParent.appendChild(fiber.dom);
  } else if (fiber.effectTag === "update-element" && fiber.dom != null) {
    updateDom(fiber.dom, fiber.alternate.props, fiber.props);
  } else if (fiber.effectTag === "delete-element") {
    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);
  }
}
/** @jsx myReact.createElement */
const container = document.getElementById("root");

function App(props) {
  return <h1>hi {props.name}</h1>;
}

function Counter() {
  const [state, setState] = myReact.useState(1);
  const [state1, setState1] = myReact.useState(100);
  return (
    <div>
      <h1 onClick={() => setState((num) => num + 1)}>Count: {state}</h1>
      <h1 onClick={() => setState1((num) => num + 1)}>Count: {state1}</h1>
    </div>
  );
}

let flag = true;
const updateValue = (e) => {
  // console.log(e.target.value);
  myRender(e.target.value);
};

const myRender = (value) => {
  const Element = (
    <div>
      <input onInput={updateValue} value={value} />
      <h2>Hello {value}</h2>
      <App name="lyc" />
      <Counter></Counter>
    </div>
  );
  myReact.render(Element, container);
};

myRender("cool");

参考文献

build-your-own-react