前言
大家好这里是阳九,一个中途转行的野路子码农,热衷于研究和手写前端工具.
我的宗旨就是 万物皆可手写
本专栏会慢慢更新手写react的过程,请关注 造轮子系列-手把手教你手写一个React - 不月阳九的专栏 - 掘金 (juejin.cn)
新手创作不易,有问题欢迎指出和轻喷,谢谢
本专栏适合有一定React开发经验,对webpack,babel有一定了解的前端攻城狮,如果没有请绕道恶补基础知识)
今天我们来手写React的第一部分 JSX和createElement
react中的虚拟dom
首先我们来看一下react中的div是什么
const reactDiv = (
<div>
<span>内容</span>
</div>
)
console.log(reactDiv);
结果如下
我们可以看到 在.jsx文件中写下的标签 首先会转变成一个React.element数据结构,这就是react中的虚拟dom,里面存放了props,type,key等属性, 子节点存放在props.children中,形成虚拟dom树
JSX的转换
我们知道,在react中写的js代码会被转化为jsx,从而生成虚拟dom
JSX的支持是由babel来做的,我们可以看一下babel/preset这个工具做了些什么
我们打开babel官网选择试一试
// div会被转换成一个函数,React.createElement, 里面传入三个参数
React.createElement(tag,props,[...children])
嵌套的dom
所以当我们执行App函数时,会执行一连串的createElement函数
相信看到这里大家已经有一定的思路去实现了,那么我们来通过代码实现一下
使用@babel/preset-react编译.jsx文件内代码
npm i @babel/preset-react
npm i @babel/core
- 先写一个编译.jsx文件的webpack loader
const jsx = require("@babel/core")
// 这里的source会接收.jsx文件内的所有字符串
function jsxLoader(source){
// 使用@babel/preset-react进行编译获取编译后代码
const { code } = jsx.transformSync(source, {
presets: ["@babel/preset-react"],
});
// 将新的字符串返回 重新写入文件
return `${code}`
}
- 配置webpack 使用写下的loader
const myJsxLoader = require('...') // loader路径
module.exports = {
...
rules:[
{
test: /\.jsx$/,
use: [myJsxLoader] //! .jsx文件经过myJsxLoader编译
}
]
}
通过babel编译后,我们就可以获得这样的.jsx文件
"use strict";
function App() {
return /*#__PURE__*/ React.createElement(
"div",
{id: 1},
null
);
}
实现createElement方法
这里是本人实现的简易代码 和真实react有出入 大致思路就是将createElement函数传入的三个参数分别做解析,并生成对应的element数据结构
// 通过解析来的JSX创建Element树
export function createElement(...args: any[]): any {
let key;
let ref;
let children = [];
const tag = args[0]
const config = args[1]
const childNodes = args.slice(2)
// 处理tag为函数组件的情况(创建组件Element 执行函数并返回ElementNode)
if (typeof tag === 'function') {
let fc = tag
return {
$$typeof: Symbol.for('lzyElement'),
tag: fc.name,
ref: fc,
key,
props: config,
children: [],
fiber: undefined
}
}
// 单独处理ref和key
if (config) {
ref = config.ref
key = config.key
// 删除属性
delete config?.ref
delete config?.key
}
// 遍历处理childrenNode
if (childNodes.length > 0) {
childNodes.forEach((child) => {
if (isElement(child)) {
children.push(child)
} else {
children.push({
$$typeof: Symbol.for('lzyElement'),
tag: 'text',
text: child,
fiber: undefined
})
}
})
}
//返回生成的虚拟dom
return {
$$typeof: Symbol.for('lzyElement'),
tag,
ref,
key,
props: config,
children,
fiber: undefined
}
}
最终使用
// --react.js--
// 准备一个自己的React对象, 用于导出各种方法
import {createElement} from './..../createElement'
const React = {createElement}
export default React
编译前
import React from './react.js'
function App(){
return <div id={1}>文字</div>
}
编译后
"use strict";
import React from './react.js'
function App() {
return /*#__PURE__*/ React.createElement("div", {id: 1}, "文字");
}
最后我们执行App函数 即可获得构造好的element树
创作不易,请多多支持