1. 在react里,用的最多的就是function component,我们现在来实现一下,定义一个Count的函数组件引入到视图,毋庸置疑会报错,因为之前并没有写针对function的逻辑。首先我们可以通过在createDom方法里通过log打印每次创建节点的type值,如图:
(1)通过上图,得知,我们获取节点类型既不是标签也不是TEXT_ELEMENT而是一个function函数类型。
(2)我们可以在performWorkOfUnit方法里,通过判断fiber.type的类型是否是function,如果不是function,则执行创建dom,处理props逻辑;在处理children时,f如果类型为function,我们则调用fiber.type()并将其转化为数组,反之则取fiber.props.children.
具体代码实现:
(3)接着我们需要处理提交代码commitWork时的情况,由于我们的function节点是没有dom的,所以在执行提交代码时,一定会报错,因为在处理将节点加入到父节点时,function类型调用后获取的dom节点其父节点的dom为null,所以我们需要采用向上查找的形式向其父级的父级找直到找到有dom的父级节点,这里需要注意,不可以写if判断,如果写if判断则只会往上查找一次,需要使用while循环,不断向上查找,找到后才会退出循环。将其节点添加到父节点里边。
具体代码实现:
function commitWork(fiber) {
if(!fiber) return
let fiberParent = fiber.parent
// 向上不断查找
while(!fiberParent.dom) {
fiberParent = fiberParent.parent
}
if(fiber.dom) {
fiberParent.dom.append(fiber.dom)
}
commitWork(fiber.child)
commitWork(fiber.sibling)
}
(4)到此,我们就可以将我们的App.jsx更改函数类型,以及main.jsx
import React from "./core/React.js"
function Counter({num}) {
return <div>count:{num}</div>
}
function App() {
return (
<div id="hi">
hello world
<Counter num={10}></Counter>
</div>
)
}
export default App
import React from "./core/React.js"
import ReactDom from './core/ReactDom.js'
import App from './App.jsx'
ReactDom.createRoot(document.querySelector('#app')).render(<App></App>)
现在这样的写法就和react彻底对齐了。
2. 处理function component的props传参
(1)我们想要在function组件里进行props传参,只需要在我们调用fiber.type时,将fiber.props传入即可,当然如果我们传的参数为number类型,是需要在createElement里边进行优化的。
代码如下:
children:children.map((child) => {
const isTextNode = typeof child === 'string' || typeof child === 'number'
return isTextNode ? createTextNode(child) : child
})
(2)如果我们在App.js里多复制一个function组件,发现第二个并没有显示,因为在performWorkOfUnit方法尾部返回下一个要执行的任务,最后的逻辑有待优化。如果fiber.parent.sibling为空,则直接跳出,这里需要考虑到function组件情况,在处理fiber.parent时,采用while循环向上查找,如果fiber.sibling不存在则找fiber父级的sibling,直至找到。如果找不到,则说明树形结构已经完全转化为链表。可以进行统一提交代码渲染视图。
代码如下:
// 4.返回下一个要执行的任务
if(fiber.child) {
return fiber.child
}
let nextFiber = fiber
while(nextFiber) {
if(nextFiber.sibling) return nextFiber.sibling;
nextFiber = nextFiber.parent
}
3. 重构function component
(1)将处理function component和处理正常dom节点的分开封装起来。代码更加的简洁。
(2)完整代码如下:
function createElement(type,props,...children) {
return {
type,
props:{
...props,
children:children.map((child) => {
const isTextNode = typeof child === 'string' || typeof child === 'number'
return isTextNode ? createTextNode(child) : child
})
}
}
}
function createTextNode(text) {
return {
type:'TEXT_ELEMENT',
props:{
nodeValue:text,
children:[]
}
}
}
function render(el,container) {
nextWorkOfUnit = {
dom:container,
props:{
children:[el]
}
}
root = nextWorkOfUnit
}
let root = null
let nextWorkOfUnit = null
function workLoop(deadline) {
let shouldYield = false;
while(!shouldYield && nextWorkOfUnit) {
nextWorkOfUnit = performWorkOfUnit(nextWorkOfUnit)
shouldYield = deadline.timeRemaining() < 1
}
if(!nextWorkOfUnit && root) {
commitRoot()
}
requestIdleCallback(workLoop)
}
function commitRoot() {
commitWork(root.child)
root = null
}
function commitWork(fiber) {
if(!fiber) return
let fiberParent = fiber.parent
// 向上不断查找
while(!fiberParent.dom) {
fiberParent = fiberParent.parent
}
if(fiber.dom) {
fiberParent.dom.append(fiber.dom)
}
commitWork(fiber.child)
commitWork(fiber.sibling)
}
function createDom(type) {
return type === 'TEXT_ELEMENT' ?
document.createTextNode("") :
document.createElement(type)
}
function updateProps(props,dom) {
for (const key in props) {
if(key !== 'children') {
dom[key] = props[key]
}
}
}
function initChildren(fiber,children) {
let prevChild = null
children.forEach((child,index) => {
// 由于我们要给孩子节点定义父亲等属性,所以定义新的对象,这样可以不直接破坏之前vdom的结构
const newFiber = {
type:child.type,
props:child.props,
child:null,
parent:fiber,
sibling:null,
dom:null
}
if(index == 0) {
fiber.child = newFiber
} else {
prevChild.sibling = newFiber
}
prevChild = newFiber
});
}
function updateFunctionComponent(fiber) {
const children = [fiber.type(fiber.props)]
// 3.转化链表 设置指针
initChildren(fiber,children)
}
function updateHostComponent(fiber) {
if(!fiber.dom) {
// 1. 创建dom
const dom = (fiber.dom = createDom(fiber.type))
// fiber.parent.dom.append(dom)
// 2. 处理props
updateProps(fiber.props,dom)
}
const children = fiber.props.children
// 3.转化链表 设置指针
initChildren(fiber,children)
}
function performWorkOfUnit(fiber) {
const isFunctionComponent = typeof fiber.type === 'function'
if(isFunctionComponent) {
updateFunctionComponent(fiber)
} else {
updateHostComponent(fiber)
}
// 4.返回下一个要执行的任务
if(fiber.child) {
return fiber.child
}
let nextFiber = fiber
while(nextFiber) {
if(nextFiber.sibling) return nextFiber.sibling;
nextFiber = nextFiber.parent
}
}
requestIdleCallback(workLoop)
const React = {
createElement,
render
}
export default React