支持hook
第一个问题是我们要如何区分出class component和function commponent,react是通过在Component上定义一个isReactComponent方法来区分的。 大家可以看一下react作者Dan Abramov的overreacted.io/zh-hans/how... 这个方式来区分, 文章里面解决这个问题考虑到了许多的点 好多都是很难想到的
-
babel转义以后class会变成function
-
存在多个react版本的问题 导致不能用
Child.prototype instanceof React.Component
js
class Component {
constructor(props) {
this.props = props;
this.state = this.state || {}
}
//这里
isReactComponent() {
return true;
}
setState(partialState) {
//?Component的state是在哪里更新的
//在resetNextUnitOfWork里面更新fiber节点上的partialState 然后在updateClassComponent把fiber上的partialState更新到Component上
scheduleUpdate(this, partialState);
}
}
我们把这个判断的逻辑在reconcileChildArray
加上去
js
if(element && !sameType) {
/**
这个情况stateNode没有创建,在下一次执行updateHostComponent or updateClassComponent的时候会创建
*/
newFiber = {
type: element.type,
tag: typeof element.type === 'string'? HOST_COMPONENT: element.type.prototype.isReactComponent? ClASS_COMPONENT: FUNNCTION_COMPONENT,
props: element.props,
parent: wipFiber,
effectTag: PLACEMENT
}
}
ok现在我们实现一个方法来处理function类型的fiber节点
js
function updateFunctionComponent(fiber) {
wipFiber = fiber;
hookIndex = 0;
wipFiber.hooks = [];
const newChildElements = wipFiber.type(wipFiber.props);
reconcileChildArray(wipFiber, newChildElements);
}
这里我们要把当前的fiber节点存到一个全局的变量wipFiber里面,这样hook里面才能访问到当前的fiber节点 hookIndex和wipFiber.hooks = [];
是什么作用?我们先定义一下useState这个hook,大家就知道了。
useState
因为useState是由用户来调用的,所以这里就不能把当前的fiber节点传到useState里面,只能把当前的fiber节点变成全局变量,useState里面才能访问
js
function useState(initial) {
const oldHook =
wipFiber.alternate &&
wipFiber.alternate.hooks &&
wipFiber.alternate.hooks[hookIndex]
//如果没有调用当前setState,只是组件重新渲染调用useState, state里面还是上次渲染的值,不会变成初始值
const hook = {
state: oldHook?oldHook.state: initial,
queue: []
}
//调用setState会先把action存到queue里面 然后在执行useState的时候执行queue
const actions = oldHook? oldHook.queue: [];
actions.forEach(action => {
hook.state = action(hook.state);
})
const setState = value => {
if(typeof value === "function") {
hook.queue.push(value);
} else {
hook.state = value;
}
//class component 调用setState的时候可以通过class instance的__fiber属性找到当前的fiber节点 然后通过parent一直向上找到root fiber节点
//hook没办法通过这种方式找到root fiber 只能通过把rootFiber变成全局变量的方式
nextUnitOfWork = {
tag: HOST_ROOT,
stateNode: wipFiberRoot.stateNode,
props: wipFiberRoot.props,
alternate: wipFiberRoot
}
requestIdleCallback(performWork);
}
wipFiber.hooks.push(hook);
hookIndex++;
return [hook.state, setState];
}
调用setState的时候我们先把最新的值或者set function存到old fiber tree的hook上 然后开始重新构建fiber tree,重新构建fiber tree 就需要拿到old root fiber node,所以我们在前面声明了一个全局的wipFiberRoot用来存old root fiber node。
在重新构建fiber tree的时候我们会重新调用useState useState会从old fiber tree上就拿前面设置的值或者set function,然后计算出最新的state更新到最新的fiber tree。 可以看到useState是通过index来访问old fiber tree上面的对应的hook 所以必须保证每次渲染hooks的顺序不能变化,不然拿到的值就错乱了
dom更新阶段
只要在commitDeletion里面把FUNNCTION_COMPONENT的判断加上就可以了
js
function commitDeletion(fiber, domParent) {
let node = fiber;
while (true) {
//这里
if (node.tag === ClASS_COMPONENT || node.tag === FUNNCTION_COMPONENT) {
node = node.child;
continue;
}
domParent.removeChild(node.stateNode);
while (node != fiber && !node.sibling) {
node = node.parent;
}
if (node == fiber) {
return;
}
node = node.sibling;
}
}