前情提要
啪!上回书说到我们已经实现了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");