6. 实现 function component

0 阅读2分钟

本次目标:实现我们的react支持function component

先创建一个function component

  • app.jsx,运行发现报错
import React from './core/React.js';
​
function Counter() {
  return <div>count</div>
}
​
const App = <div>
  hi-mini-react
  <Counter></Counter>
</div>
export default App;

1.png

  • react.js/createDom打印一下type,看到函数组件的类型,发现function component被解析成vdom的时候,直接把函数赋值给type了
function createDom(type) {
    console.log('type', type)
    return type === "TEXT_ELEMENT" ?
        document.createTextNode("") :
        document.createElement(type);
}

2.png

解决思路

  • 可以把counter这个组件拆解出来,参考看下图

3.png

  • 我们可以把counter打开,理解成开箱的过程,看一下type的类型
function performWorkOfUnit(fiber) {
    console.log(`fiber.type`, fiber.type)
    console.log(`typeof fiber.type`, typeof fiber.type)
    const isFunctionComponent = typeof fiber.type === 'function'
    if (isFunctionComponent) {
        // todo
    }
    if (!fiber.dom) {
        const dom = createDom(fiber.type)
        fiber.dom = dom
        updateProps(dom, fiber.props)
    }
...
}

4.png

  • 处理function component
function performWorkOfUnit(fiber) {
... 
    const isFunctionComponent = typeof fiber.type === 'function'
    if(!isFunctionComponent) {
        if (!fiber.dom) {
            // 1. 创建dom
            const dom = createDom(fiber.type)
            fiber.dom = dom
            // 添加到父级容器
            // fiber.parent.dom.append(dom)
            // 2. 处理props
            updateProps(dom, fiber.props)
        }
    }
​
    const children = isFunctionComponent ? [fiber.type()]: fiber.props.children
    initChildren(fiber, children)
 ...
    return fiber.parent?.sibling
}
    
function initChildren(fiber, children) {
    let prevChild = null
    children.forEach((child, index) => {
    ...
    }))
}
  • 此时我们可以看到可以渲染出来了,但是函数组件里面的内容没渲染出来

5.png

  • 这是因为function component没有dom,但是commit work的时候要添加到父级,需要继续网上找
function commitWork(fiber) {
    if (!fiber) {
        return
    }
    let fiberParent = fiber.parent
    if(!fiberParent.dom) {
        fiberParent = fiberParent.parent
    }
     fiberParent.dom.append(fiber.dom)
    commitWork(fiber.child)
    commitWork(fiber.sibling)
}
  • 此时可以打印出count,但是还多了个null需要做一下处理 6.png
  • 处理null的边界情况 ,函数组件是没有dom的
    if(fiber.dom) {
        fiberParent.dom.append(fiber.dom)
    }

处理函数组件是嵌套的场景

  • app.jsx,此时又会出现问题
import React from './core/React.js';
​
function Counter() {
  return <div>count</div>
}
function CounterContainer() {
  return <Counter></Counter>
}
const App = <div>
  hi-mini-react
  <CounterContainer></CounterContainer>
</div>
export default App;
  • 如果父级的dom没有值就一直往上找
function commitWork(fiber) {
...
    while(!fiberParent.dom) {
        fiberParent = fiberParent.parent
    } 
    if(fiber.dom) {
        fiberParent.dom.append(fiber.dom)
    }
...

修改App.jsx,改成Function Component的形式

  • app.jsx
import React from './core/React.js';
​
function Counter() {
  return <div>count</div>
}
​
function App() {
  return <div>
    hi-mini-react
    <Counter></Counter>
  </div>
}
export default App;
  • main.jsx
import ReactDOM from './core/ReactDom.js'
import App from './App.jsx'
import React from './core/React.js';
​
ReactDOM.createRoot(document.querySelector("#root")).render(<App></App>)

实现支持props

  • app.jsx
import React from './core/React.js';
​
function Counter({ num }) {
  return <div>count: {num}</div>
}
​
function App() {
  return <div>
    hi-mini-react
    <Counter num={10}></Counter>
  </div>
}
export default App;
  • 目前会报错

7.png

  • 调用type的时候传入值
function performWorkOfUnit(fiber) {
...
    const children = isFunctionComponent ? [fiber.type(fiber.props)]: fiber.props.children
...
}
  • 发现依然报错,是因为生成虚拟dom的时候
function createElement(type, props, ...children) {
    return {
        type,
        props: {
            ...props,
            children: children.map(child => {
                console.log(`child -> `, child);
                return typeof child === "string" ? createTextNode(child) : child
            })
        }
    }
}

8.png

  • 对数字情况做处理,可以正常展示
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
            })
        }
    }
}

9.png

新问题,如果写两个组件,只会渲染出来一个

  • app.jsx
function App() {
  return <div>
    hi-mini-react
    <Counter num={10}></Counter>
    <Counter num={20}></Counter>
  </div>
}
  • 画图来参考

10.png

  • 问题出现在哪里?漏写了逻辑,没有正确找到兄弟节点,需要循环去找
function performWorkOfUnit(fiber) {
    ...
    // 之前这一段逻辑可以删除
    // if (fiber.sibling) {
    //    return fiber.sibling
    // }
    
    while(nextFiber) {
        if(nextFiber.sibling) return nextFiber.sibling;
        nextFiber = nextFiber.parent
    }
    return fiber.parent?.sibling
}
  • 可以正常渲染了

11.png