写在开头,本系列篇幅较长包含大量代码及思考逻辑,主要实现涉及React中的:
- Function Component
- useEffect
- useState
- 点击事件处理及props绑定
- 任务调度器
- Fiber架构(树结构到链表结构转换)
文章会从main.jsx
结合jsx处理后的dom树及源码带入react的大概实现逻辑,先上代码链接(后续会更新优化,方便阅读),文章产出源自@阿崔cxr的mini-react
活动课程。
Step 1 项目搭建
本文使用vite
搭建的js空项目,后续可以直接上jsx
语法,结合react项目的入口文件开始反推;
如上图所示,关键代码在最后一行,意在通过ReactDOM
的createRoot
传入一个根节点,返回一个render
函数处理App
组件;可以猜想到是App
组件在通过jsx
转换后给到render
函数渲染vDom
,最后挂载到根节点上。
拆分任务
- 先上js语法,从最基础的dom创建、vDom生成
- 修改函数名及层级结构,对齐react
js
// 创建 vdom
function createTextNode(text){
return {
type: 'TEXT_ELEMENT',
props: {
nodeValue: text,
children: []
}
}
}
function createElement(type, props, ...children){
return {
type,
props: {
...props,
children: children.map(el => typeof el === 'string'?createTextNode(el):el)
}
}
}
/**
*
* @param {创建元素} el
* @param {容器元素} container
*/
function myRender(el,container) {
// 创建元素
const dom = el.type === 'TEXT_ELEMENT'
? document.createTextNode('')
: document.createElement(el.type)
// 处理props
Object.keys(el.props).map(key =>{
if(key !== 'children'){
// 属性赋值
dom[key] = el['props'][key]
}
})
// 处理children
el['props']['children'].map(child =>{
// 插入父节点
myRender(child, dom)
})
container.appendChild(dom)
}
const root = document.querySelector('#root')
// root.appendChild(dom)
// ?1 优化
const App = createElement('div',{id:'app'},'test 03')
// myRender(App,root)
const ReactDOM = {
createRoot(container){
return {
render(el){
myRender(el,container)
}
}
}
}
ReactDOM.createRoot(root).render(App)
vDom创建与函数名对齐
根据官网的const element = createElement(type, props, ...children)
,我们知道创建dom需要传递的数据及类型,而后区分节点类型是文本节点还是其他dom节点。
关于vDom的数据结构,其中props
嵌套children
,可以拉平也可以嵌套,嵌套是为了和react对齐。
文件层级划分及拆分React.js、ReactDom.js
拆分结果ReactDom.js
图:
App.js 代码结构:
js
import React from './core/React.js';
const App = createElement('div',{id:'app'},'test 03')
export default App
main.js 的代码结构图:
React.js 代码结构,修改myRender
=>render
对齐react:
js
/ 创建 vdom
function createTextNode(text){
return {
type: 'TEXT_ELEMENT',
props: {
nodeValue: text,
children: []
}
}
}
function createElement(type, props, ...children){
return {
type,
props: {
...props,
children: children.map(el => typeof el === 'string'?createTextNode(el):el)
}
}
}
/**
*
* @param {创建元素} el
* @param {容器元素} container
*/
function render(el,container) {
// 创建元素
const dom = el.type === 'TEXT_ELEMENT'
? document.createTextNode('')
: document.createElement(el.type)
// 处理props
Object.keys(el.props).map(key =>{
if(key !== 'children'){
// 属性赋值
dom[key] = el['props'][key]
}
})
// 处理children
el['props']['children'].map(child =>{
// 插入父节点
myRender(child, dom)
})
container.appendChild(dom)
}
const React = {
render,
createElement
}
到此第一步的工作已经完成,mini-react的形有了,下一步丰富内在。