从0开始构建toy react (1)

为了更好地理解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



接下来我们用命令跑起来

npm start
image.png

此时我们的目录结构为

image.png

安装配置需要的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语法


image.png

编写JSX

新建Toyreact.js file

export let ToyReact = {
    createElement() {
        console.log(arguments)
    }
}

然后在main.js引入

import {ToyReact} from './Toyreact'
let a = 

注意Toyreact.js中的ToyReact和createElement必须对应webpack的pragma

此时我们看dev tool 发现是先create里面元素再是外面元素

image.png

通过查看我们可以发现里面的参数分别为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

image.png

但是我们如果要在main函数中添加值得话就会报错
例如

import {ToyReact} from './Toyreact'
let a = 
hello 1 2
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 1
    }
}
let a = 

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 1{this.children}
    }
}
let a = 
    hello
    1
    2

ToyReact.render(a, document.body)
console.log(a)

你可能感兴趣的:(从0开始构建toy react (1))