经过之前的几篇文章,我们实现了基本的jsx,在页面渲染的过程。但是如果是通过函数组件写出来的组件,还是不能渲染到页面上的。
所以这一篇,主要是对之前写得方法进行修改,从而能够显示函数组件,所以现在我们在index.js文件中,修改一下jsx的写法。修改成函数组件:
import jsx from '../src/react/jsx.js'
import ReactDOM from '../src/react-dom/index'
const root = document.querySelector('#root');
function App() {
return jsx("div", {
ref: "123",
children: jsx("span", {
children: "456"
})
});
}
ReactDOM.createRoot(root).render(<App />)
这里因为需要使用我们自己的jsx方法。所以在App里面返回的依旧是通过之前的方式进行调用。
我们来回忆一下,在beginWork阶段,我们主要是通过ReactElement,创建FilberNode。而reconcileChildren,就是创建FilberNode的方法。
在之前我们只处理了HostText类型和HostComponent类型,所以在这个方法里面,我们要对函数类型进行兼容,而作为函数组件的ReactElment,它最显而易见的特点就是type的值是一个函数。
例如上面的App组件,对应的ReactElement的type就是App。所以我们可以通过type来判断组件的类型:
function reconcileChildren(element) {
let tag;
if(typeof element.type === 'function') {
tag = FunctionComponent
}
//其他代码
console.log(filberNode)
return filberNode
}
我们打印一下看看,这个函数组件是否满足预期:
现在有了tag为FunctionComponent类型的FilberNode,在beginWork里面,我们就要对这个类型的FilberNode进行处理:
function beginWork(nowFilberNode) {
switch (nowFilberNode.tag) {
//其他代码
case FunctionComponent: {
return updateFunctionComponent(nowFilberNode)
}
//其他代码
}
}
现在我们来实现updateFunctionComponent方法。
之前对于HostComponent类型的FilberNode,它的子节点其实就是它对应的ReactElement。
但是对于函数类型的FilberNode,我们想一下不就是它自己的返回值嘛?所以我们直接调用这个函数就能拿到它的子FilberNode了。
function updateFunctionComponent(filberNode) {
const nextChildren = filberNode.type();
const newFilberNode = reconcileChildren(nextChildren);
filberNode.child = newFilberNode;
newFilberNode.return = filberNode;
beginWork(newFilberNode)
}
对于completeWork方法, 它的主要作用(目前)是给对应的FilberNode增加stateNode,而函数组件并没有自己对应的StateNode,所以直接继续递归就可以了:
export const completeWork = (filberNode) => {
const tag = filberNode.tag
switch (tag) {
//其他代码。。。
case FunctionComponent: {
completeWork(filberNode.child)
}
}
}
对于之前的commitWork,我们是直接将最外层的FilberNode的stateNode挂载了容器上,现在由于最外层的可能是FunctionComponent,它是没有自己的stateNode的。所以我们要找到具有stateNode的最外层FilberNode。
import { HostComponent } from "./filberNode";
export function commitWork(filberRootNode) {
const container = filberRootNode.container;
let node = filberRootNode.finishedWork;
while( node.tag !== HostComponent ){
node = node.child
}
container.appendChild(node.stateNode)
}
OK,经过上面的修改,我们的App组件也可以正常渲染了。