为了更好地理解react的工作原理 我们从0开始构建一个玩具react
当然我们从这次构建中可以学到
- react基本组件原理
- 学习vdom的实现思路
- 突破编写自我的难点代码
创建webpack
首先我们创建package.json
npm init
接下来我们创建webpack
npm install webpack --save-dev
创建好后新建webpack.config.js
添加entry 开发者模式 以及不压缩代码
module.exports = {
entry: './main.js',
mode: 'development',
optimization: {
minimize: false
}
};
接下来我们创建main.js然后随便写点代码
console.log(2)
当然你不想一次一次使用webpack我们也可以使用webpack dev server
npm install webpack-dev-server --save-dev
如果启动报错的的话说明没有webpack-cli
npm install webpack-cli --save-dev
接下来我们修改package.json
"start": "webpack-dev-server --open"
添加devserver进webpack.config.json
var path = require('path');
module.exports = {
entry: './main.js',
devServer: {
contentBase: path.join(__dirname, 'dist'),
}, //
mode: 'development',
optimization: {
minimize: false
}
};
我们先在控制台run webpack 这样会自动生成dist folder
看下是不是已经生成main.js 然后创建index.html 引入dist目录下的main.js
<!-- index.html -->
<script src="main.js"></script>
接下来我们用命令跑起来
npm start
此时我们的目录结构为
安装配置需要的loader
在我们webpack中我们需要用到的就是lodaer
loader 用于对模块的源代码进行转换。loader 可以使你在 import 或"加载"模块时预处理文件。因此,loader 类似于其他构建工具中“任务(task)”,并提供了处理前端构建步骤的强大方法。loader 可以将文件从不同的语言(如 TypeScript)转换为 JavaScript,或将内联图像转换为 data URL。loader 甚至允许你直接在 JavaScript 模块中 import CSS文件!
首先我们需要babel-loader
加载 ES2015+ 代码,然后使用 Babel 转译为 ES5
npm install --save-dev babel-loader @babel/core @babel/preset-env @babel/plugin-transform-react-js
接下来添加webpack.config
module: {
rules: [
{
test: /\.js$/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env'],
plugins: [[
"@babel/plugin-transform-react-jsx", // 使用JSX
{pragma:"ToyReact.createElement"} //不加就是React.createElement
]]
}
}
}
]
},
接下来我们测试下看是否ES6变为了ES5
修改main.js
console.log(2)
for (let i of [1,2,3]) {
console.log(i)
}
打开dev tool 果然变成了ES5语法
编写JSX
新建Toyreact.js file
export let ToyReact = {
createElement() {
console.log(arguments)
}
}
然后在main.js引入
import {ToyReact} from './Toyreact'
let a = <div name="a">
<span></span>
<span></span>
<span></span>
</div>
注意Toyreact.js中的ToyReact和createElement必须对应webpack的pragma
此时我们看dev tool 发现是先create里面元素再是外面元素
通过查看我们可以发现里面的参数分别为type,attr和children 但是第三个参数不确定
所以我们修改Toyreact.js并传入参数然后创建DOM
export let ToyReact = {
createElement(type,attr,...children) {
console.log(type,attr,children)
let ele = document.createElement(type) // 创建DOM
for (let name in attr) {
console.log(name)
ele.setAttribute(name, attr[name]) // 设置属性
}
for (let child of children){
console.log(child)
ele.appendChild(child) // 将Children append进去
}
return ele;
}
}
此时我们打印main.js的a
但是我们如果要在main函数中添加值得话就会报错
例如
import {ToyReact} from './Toyreact'
let a = <div name="a">
<span>hello</span>
<span>1</span>
<span>2</span>
</div>
console.log(a)
所以此时我们要在Toyreact.js中加一句判断
if( typeof child === 'string') child = document.createTextNode(child)
export let ToyReact = {
createElement(type,attr,...children) {
console.log(type,attr,children)
let ele = document.createElement(type) // 创建DOM
for (let name in attr) {
console.log(name)
ele.setAttribute(name, attr[name]) // 设置属性
}
for (let child of children){
if( typeof child === 'string') child = document.createTextNode(child) // 转换成文本节点
ele.appendChild(child) // 将Children append进去
}
return ele;
}
}
渲染
一般在react中我们都是渲染采用render所以我们在Toyreact.js中加入render方法
class EleWrapper {
constructor(type) {
this.root = document.createElement(type)
}
setAttribute(name, value) {
this.root.setAttribute(name, value)
}
appendChild(vchild){
vchild.mountTo(this.root)
}
mountTo(parent) {
parent.appendChild(this.root)
}
}
export class Component {
mountTo(parent){
let vdom = this.render()
vdom.mountTo(parent)
}
setAttribute(name, value) {
this[name] = value;
}
}
class TextWrapper {
constructor(content) {
this.root = document.createTextNode(content)
}
mountTo(parent) {
parent.appendChild(this.root)
}
}
export let ToyReact = {
createElement(type,attr,...children) {
let ele;
if( typeof type === 'string')
ele = new EleWrapper(type) // 创建DOM
else
ele= new type;
for (let name in attr) {
console.log(name)
ele.setAttribute(name, attr[name]) // 设置属性
}
for (let child of children){
if( typeof child === 'string')
child = new TextWrapper(child) // 转换成文本节点
ele.appendChild(child) // 将Children append进去
}
return ele;
},
render(vdom, ele){
vdom.mountTo(ele)
}
}
然后我们修改main.js
import {ToyReact, Component} from './Toyreact'
class MyCom extends Component{
render() {
return <span>1</span>
}
}
let a = <MyCom name="a">
</MyCom>
ToyReact.render(
a,
document.body
)
但是你会发现当MyCom组件中有子元素时依然有问题, 我们继续修改
class EleWrapper {
constructor(type) {
this.root = document.createElement(type)
}
setAttribute(name, value) {
this.root.setAttribute(name, value)
}
appendChild(vchild){
vchild.mountTo(this.root)
}
mountTo(parent) {
parent.appendChild(this.root)
}
}
export class Component {
constructor(){
this.children = []
}
mountTo(parent){
let vdom = this.render()
vdom.mountTo(parent)
}
setAttribute(name, value) {
this[name] = value;
}
appendChild(vchild){
this.children.push(vchild)
}
}
class TextWrapper {
constructor(content) {
this.root = document.createTextNode(content)
}
mountTo(parent) {
parent.appendChild(this.root)
}
}
export let ToyReact = {
createElement(type,attr,...children) {
let ele;
if( typeof type === 'string')
ele = new EleWrapper(type) // 创建DOM
else
ele= new type;
for (let name in attr) {
console.log(name)
ele.setAttribute(name, attr[name]) // 设置属性
}
let insertChildren = (children) => {
for (let child of children){
if(typeof child === 'object' && child instanceof Array) {
insertChildren(child)
} else {
if(!(child instanceof Component) && !(child instanceof EleWrapper) && !(child instanceof TextWrapper))
child = String(child)
if( typeof child === 'string')
child = new TextWrapper(child) // 转换成文本节点
ele.appendChild(child) // 将Children append进去
}
}
}
insertChildren(children)
return ele;
},
render(vdom, ele){
vdom.mountTo(ele)
}
}
main.js
import {ToyReact, Component} from './Toyreact'
class MyCom extends Component{
render() {
return <span>1<span>{this.children}</span></span>
}
}
let a = <MyCom name="a">
<span>hello</span>
<span>1</span>
<span>2</span>
</MyCom>
ToyReact.render(a, document.body)
console.log(a)