本次目标:实现我们的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;
- react.js/createDom打印一下type,看到函数组件的类型,发现function component被解析成vdom的时候,直接把函数赋值给type了
function createDom(type) {
console.log('type', type)
return type === "TEXT_ELEMENT" ?
document.createTextNode("") :
document.createElement(type);
}
解决思路
- 可以把counter这个组件拆解出来,参考看下图
- 我们可以把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)
}
...
}
- 处理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) => {
...
}))
}
- 此时我们可以看到可以渲染出来了,但是函数组件里面的内容没渲染出来
- 这是因为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需要做一下处理
- 处理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;
- 目前会报错
- 调用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
})
}
}
}
- 对数字情况做处理,可以正常展示
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
})
}
}
}
新问题,如果写两个组件,只会渲染出来一个
- app.jsx
function App() {
return <div>
hi-mini-react
<Counter num={10}></Counter>
<Counter num={20}></Counter>
</div>
}
- 画图来参考
- 问题出现在哪里?漏写了逻辑,没有正确找到兄弟节点,需要循环去找
function performWorkOfUnit(fiber) {
...
// 之前这一段逻辑可以删除
// if (fiber.sibling) {
// return fiber.sibling
// }
while(nextFiber) {
if(nextFiber.sibling) return nextFiber.sibling;
nextFiber = nextFiber.parent
}
return fiber.parent?.sibling
}
- 可以正常渲染了