摘要
OK,经过上一篇文章。我们调用了:
javascript
const root = document.querySelector('#root');
ReactDOM.createRoot(root)
生成了FilberRootNode和HostRootFilber。
并且二者之间的对应关系也已经确定。
而下一步我们就需要调用render方法来讲react元素挂载在root上:
javascript
//第一节实现的jsx方法
const reactElement = jsx("div", {
ref: "123",
children: jsx("span", {
children: "456"
})
});
ReactDOM.createRoot(root).render(reactElement)
所以我们现在要实现ReactDOM.createRoot中返回的render方法。
1.update和updateQueue
首先我们思考一下,在React中,除了通过上面的render方法,会让React组件更新。还有setState等逻辑,也可以触发React的更新。
也就是说,我们要实现一个方法。在触发React组件更新时调用:updateContainer。
在实现updateContainer方法前,我们先实现一套机制,用来保存更新的内容。这里可以创建一个update.js用来写这部分内容:
javascript
//创建一个更新内容
function createUpdate(action) {
return {
action
}
}
//给FilberNode创建一个更新队列
function createUpdateQueue() {
return {
shared: {
pending: null
}
}
}
//在更新队列里添加更新内容
function enqueueUpdate(updateQueue, update) {
updateQueue.shared.pending = update
}
//根据更新的内容,去更新FilberNode(this.setState)
function processUpdateQueue(baseState, pendingUpdate) {
const result = {
memoizedState: baseState
}
if(pendingUpdate !== null){
const action = pendingUpdate.action;
//setState(() => {}) 传入方法
if(typeof action === 'function'){
result.memoizedState = action(baseState);
}else {
//setState()
result.memoizedState = action;
}
}
return result;
}
这个时候,我们的更新相关的方法已经准备好了。现在就要开始干了。首先要在FilberNode上增加一个属性,updateQueue用来保存更新的内容:
this.updateQueue = null;
2.updateContainer方法
现在我们开始实现updateContainer方法,该方法接受两个参数,第一个是通过createContainer方法创建出来的FilberRootNode,第二个就是render方法传入的ReactElement。
javascript
function createRoot(root) {
const filberRootNode = createContainer(root);
return {
render(element) {
}
}
}
function updateContainer(root, element) {
}
在方法里,我们思考一下,如果不考虑setState的情况。第一次渲染的时候,对于最外层的FilberNode,他需要更新的内容,不就是传入的element吗。
所以我们通过root.current拿到最外层的FilberNode。执行对应的更新操作:
javascript
function createRoot(root) {
const filberRootNode = createContainer(root);
return {
render(element) {
updateContainer(filberRootNode, element)
}
}
}
function updateContainer(root, element) {
const hostRootFilber = root.current;
const update = createUpdate(element);
hostRootFilber.updateQueue = createUpdateQueue()
enqueueUpdate(hostRootFilber.updateQueue, update);
console.log(hostRootFilber)
}
我们将对应的节点打印出来看一下,最外层的FilberNode此时已经有了updateQueue,并且里面的内容就是对应的ReactElement
3.实现beginWork
OK,现在我们最外层的FilberNode已经准备好,我们开始准备构建Filber树。其实构建Filber树的过程,就是创建好所有的FilberNode,并且通过return,sibling,child这三个属性进行构建。
而表示整棵树的结构,都存在updateQueue中的ReactElement,我们就是要通过它去创建这颗Filber树。
现在我们不考虑有sibling属性的情况,只考虑有return和child属性的情况,创建beginWork方法:
javascript
function beginWork(nowFilberNode) {
}
function updateContainer(root, element) {
const hostRootFilber = root.current;
const update = createUpdate(element);
hostRootFilber.updateQueue = createUpdateQueue()
enqueueUpdate(hostRootFilber.updateQueue, update);
beginWork(hostRootFilber);
}
我们主要要做的就是,在beginWork里面,创建好所有的fiberNode。并且找清楚他们之间的对应关系。所以我们的beginWork一定是一个递归的方法:
我们会判断当前filberNode的tag:
javascript
function beginWork(nowFilberNode) {
switch (nowFilberNode) {
case HostRoot: {
return updateHostRoot(nowFilberNode);
}
case HostComponent: {
}
case HostText: {
}
case FunctionComponent: {
}
default: {
console.error('错误的类型')
}
}
}
由于第一次调用,传入的是最外面的filberNode,所以tag应该为HostRoot。我们针对于这种情况写一个方法updateHostRoot。
javascript
function updateHostRoot(filberNode) {
const baseState = filberNode.memoizedState;
const updateQueue = filberNode.updateQueue;
const pending = updateQueue.shared.pending;
updateQueue.shared.pending = null;
const { memoizedState } = processUpdateQueue(baseState, pending);
filberNode.memoizedProps = memoizedState;
const nextChildren = filberNode.memoizedProps;
console.log(nextChildren);
console.log(filberNode);
}
对于首屏渲染,我们知道对于FilberNode来说,更新的内容就是他的子节点。所以我们更新好FilebrNode的updateQueue属性和memoizedState,memoizedProps属性后。
可以直接拿到它的子节点nextChildren,不过这个节点是ReactElement类型,我们要将它转换成FilberNode,所以我们还需要一个方法:reconcilerChildren。
javascript
function reconcileChildren(element) {
let tag;
if(element.$$typeof === REACT_ELEMENT_TYPE){
tag = HostComponent;
}else if(typeof element === 'string'){
}
return new FilberNode(tag, element.props, element.key)
}
我们创建好的子FilberNode,用element的props初始化FilberNode的penddingProps。
这个时候我们在updateHostRoot中调用该方法,并将子FilberNode打印出来。
javascript
function updateHostRoot(filberNode) {
/**
* 其他代码
**/
const newFilberNode = reconcileChildren(nextChildren);
filberNode.child = newFilberNode;
newFilberNode.return = filberNode
console.log(newFilberNode);
beginWork(newFilberNode)
}
可以看到子FilberNode和父节点的关系已经更新好,同时也将自己的ReactElement放在了pendingProps里。
Ok,对于HostRoot类型(最外层的FilberNode),我们有了updateHostRoot方法处理,那对于HostComponent类型,我们自然也需要updateHostComponent方法:
javascript
function updateHostComponent(filberNode) {
const nextChildren = filberNode.pendingProps.children;
const newFilberNode = reconcileChildren(nextChildren);
filberNode.child = newFilberNode;
newFilberNode.return = filberNode;
beginWork(newFilberNode)
}
同样的,对于文本类型的节点,自然也不需要去给它创建FilberNode。这里面我们不做处理就好。
到这里,我们就已经简单的处理了只有child和return属性的Filber树,最终也可以打印出来这颗树的样子:
可以看到每个FilberNode都具有child和return两个属性。