🚧 [React] 迷你React: 环境构建

项目目录
shellCopy
.
├── README.md
├── package // react实现
│ ├── index.tsx
│ ├── interface.ts
│ └── utils.ts
├── package-lock.json
├── package.json
├── public
│ └── index.html
├── src // 应用目录
│ └── index.tsx
├── tsconfig.json // typescript配置
└── webpack.config.js // webpack配置目标
webapck 打包src/*和pacakge/* 生成main.js文件。
webpack 打包 public/index.hmtl 引入生成的main.js。
访问index.html; main.js 在root节点生成Dom、展示dom。
依赖安装
shellCopy
> npm install react react-dom --save
> npm intsall @types/react @types/react-dom --save-dev
> npm intsall typescript --save-dev
# webpack-dev-server 用于启动开发服务
> npm intsall webpack webpack-cli webpack-dev-server --save-dev
# ts-loader 用于调用typescrit编译器,编译ts tsx
# html-webpack-plugin 用于编译html
> npm install html-webpack-plugin ts-loader --savepacakge.json
jsonCopy
{
"name": "mini-react",
"scripts": {
"build": "webpack --mode=production --node-env=production",
"serve": "webpack serve"
},
"devDependencies": {
"@types/react": "latest",
"@types/react-dom": "latest",
"html-webpack-plugin": "^5.6.0",
"ts-loader": "^9.5.1",
"typescript": "^5.4.5",
"webpack": "^5.91.0",
"webpack-cli": "^5.1.4",
"webpack-dev-server": "^5.0.4"
},
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0"
}
}应用入口
id为root的div将作为根节点。
htmlCopy
<!DOCTYPE html>
<html>
<head>
<title>React APP</title>
</head>
<body>
<div id="root"></div>
</body>
</html>typescript编译器将React语法编译成React.createElement(type, props, children) 。
typescriptCopy
import React from "../package/index";
const element = (
<div style="background: salmon">
<h1>Hello World</h1>
<h2 style="text-align:right">from myReact</h2>
</div>
);
/**
* 上述代码将会被编译成
* React.createElement('div', { style: 'background: salmon' }, [ React.createElement('h1', null, "Hello World"), React.createElement('h2', { style: "text-align:right"}, "from myReact")])
*/
const container = document.getElementById("root");
React.render(element, container as HTMLElement);React实现
实现createElement ,render (简单实现将节点挂载到Root上)。
typescriptCopy
export function createElement(type: any, props: any, ...children: any[]): JSX.Element {
const result = {
key: "",
type,
props: {
...props,
children: children.map((child) => {
return typeof child === "object" ? child : createTextElement(child);
}),
},
};
return result;
}
export function createTextElement(text: string): JSX.Element {
return {
key: "",
type: "TEXT_ELEMENT",
props: {
nodeValue: text,
children: [],
},
};
}
export function render(element: JSX.Element, container: HTMLElement) {
// document.createTextNode || createElement 创建
const dom = element.type == "TEXT_ELEMENT" ? document.createTextNode("") : document.createElement(element.type);
// 设置props
Object.keys(element.props)
.filter((key) => key !== "children")
.forEach((key) => {
dom[key] = element.props[key];
});
// 深度优先遍历children
if (element.props.children) {
for (const iterator of element.props.children) {
render(iterator, dom);
}
}
// 添加到dom
container.appendChild(dom);
}
const React = {
createElement,
render,
};
export default React;Webpack
javascriptCopy
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const isProduction = process.env.NODE_ENV == "production";
const config = {
entry: "./src/index.tsx", // 入口
output: {
path: path.resolve(__dirname, "dist"), // 输出
},
devServer: { // devServer配置
open: true,
host: "localhost",
},
plugins: [
new HtmlWebpackPlugin({ // 解析HTML
template: path.join(__dirname, "./public/index.html"),
})
],
module: { // 解析tsx
rules: [
{
test: /\.(ts|tsx)$/i,
loader: "ts-loader",
exclude: ["/node_modules/"],
}
],
},
resolve: {
extensions: [".tsx", ".ts", ".jsx", ".js", "..."],
},
};
module.exports = () => {
if (isProduction) {
config.mode = "production";
} else {
config.mode = "development";
}
return config;
};React 实现
typescriptCopy
export function createElement(type: any, props: any, ...children: any[]): JSX.Element {
const result = {
key: "",
type,
props: {
...props,
children: children.map((child) => {
return typeof child === "object" ? child : createTextElement(child);
}),
},
};
debugger;
return result;
}
export function createTextElement(text: string): JSX.Element {
return {
key: "",
type: "TEXT_TEXT_ELEMENT",
props: {
nodeValue: text,
children: [],
},
};
}
function render(element: JSX.Element, container: HTMLElement) {
const dom: any =
element.type === "TEXT_TEXT_ELEMENT" ? document.createTextNode("") : document.createElement(element.type);
element.props.children.forEach((child: any) => {
render(child, dom as any);
});
Object.keys(element.props)
.filter((key) => key !== "children")
.map((key) => {
dom[key] = element.props[key];
});
container.appendChild(dom);
}
const React = {
createElement,
render,
};
export default React;tsconfig.json
jsonCopy
{
"compilerOptions": {
"allowSyntheticDefaultImports": true,
"noImplicitAny": true,
"module": "ES6",
"target": "es5",
"jsx": "react", // 解析jsx
"lib": ["DOM"], // 提供DOM API
},
"include": ["src", "package"],
"exclude": ["node_modules", "dist"]
}
webpack.config.js
javascriptCopy
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const isProduction = process.env.NODE_ENV == "production";
const config = {
entry: "./src/index.tsx",
output: {
path: path.resolve(__dirname, "dist"),
},
devServer: {
open: true,
host: "localhost",
},
plugins: [
new HtmlWebpackPlugin({ // 编译html
template: path.join(__dirname, "./public/index.html"),
}),
],
module: {
rules: [
{ // 编译ts tsx
test: /\.(ts|tsx)$/i,
loader: "ts-loader",
exclude: ["/node_modules/"],
},
],
},
resolve: {
extensions: [".tsx", ".ts", ".jsx", ".js", "..."],
},
};
module.exports = () => {
if (isProduction) {
config.mode = "production";
} else {
config.mode = "development";
}
return config;
};启动
shellCopy
> npm run serve结果如下:

