手把手 實現一套 屬於自己的react(class版)(一 核心)

第1步 新建文件

mkdir toy-myreact
cd toy-myreact

第2步 npm 初始化

npm init

第3步 配置webpack

npm install webpack webpack-cli --save-dev

第3步 執行webpack打包

npx webpack

因爲沒有指定好webpack的config文件,會發現報錯

第4步 新建webpackconfig文件 webpack.config.js

module.exports = {
    entry: {
        main: './main.js',
    },
};

指定一個入口文件,module.exports node的寫法 配置裏不做babel轉化

第5步 新建webpack的入口文件 main.js 空文件並執行npx webpack打包

打包成功後,輸出一個dist目錄,發現這個文件的內容並看不懂,想要看懂打包出來的文件怎麼辦呢??? 請看下一步

第6步 webpack 配置內加入兩個配置項

module.exports = {
    entry: {
        main: './main.js',
    },
    mode: "development",
    optimization: {
        minimize: false,
    },
};

第7步 使用babel

babel 把新版本的js文件翻譯成老版本的js文件

安裝
npm install --save-dev babel-loader @babel/core @babel/preset-env
配置babel-loader

presets babel一系列的快捷方式

module.exports = {
    entry: {
        main: './main.js',
    },
    module: {
        rules: [
            {
                test: /\.js$/,
                use: {
                    loader: 'babel-loader',
                    options: {
                        presets: ['@babel/preset-env'],
                    },
                },
            },
        ],
    },
    mode: "development",
    optimization: {
        minimize: false,
    },
};
main.js內寫一個for循環, 執行webpack,看是否會翻譯成舊版本的js
for (let i of [1, 2, 3]) {
    console.log(i);
}

可以看到一個正常的for循環代碼

第8步 處理jsx

@babel/preset-env 是不包含jsx的執行能力的

main.js內 寫jsx並執行,報錯

const a = <div />;

安裝包
npm install @babel/plugin-transform-react-jsx --save-dev

@babel/plugin-transform-react-jsx是專門用來處理jsxplugin

配置 webpack
rules: [
    {
        test: /\.js$/,
        use: {
            loader: 'babel-loader',
            options: {
                presets: ['@babel/preset-env'],
                plugins: ['@babel/plugin-transform-react-jsx'],
            },
        },
    },
],

執行後 成功

直接翻譯出帶React的函數名字, React.createElement

第9步 帶React的函數名字改成我們想要的設置

繼續更改webpack配置

rules: [
    {
        test: /\.js$/,
        use: {
            loader: 'babel-loader',
            options: {
                presets: ['@babel/preset-env'],
                plugins: [['@babel/plugin-transform-react-jsx', {pragma: 'xixiToy.createElement'}]],
            },
        },
    },
],

執行webpack, 發現React函數名已經去掉了 變成了我們自己想要的名字

第10步 如何去實現jsx翻譯出來的函數

增加屬性和子節點

const xixiToy = {
    createElement,
};

/**
 * @name: chelsea.jiang
 * @test: 
 * @msg: 
 * @param {*} tagName 標籤名
 * @param {*} attributes 屬性
 * @param {array} children 子節點
 * @return {*}
 */
function createElement(tagName, attributes, ...children) {
    let e = document.createElement(tagName);
    for (let attr in attributes) {
        e.setAttribute(attr, attributes[attr]);
    }
    for (let child of children) {
        e.appendChild(child);
    }
    return e;
}

window.a =  (<div id="a" class="x">
    <div></div>
</div>);

執行webpack編譯之後,發現已不再報錯

jsx 已具備初步的可用性

但是當文本節點的時候怎麼辦呢???

第11步 文本節點處理

for (let child of children) {
    if (typeof child === 'string') {
        child = document.createTextNode(child);
    }
    e.appendChild(child);
}

div 改爲自己的組件呢??

window.a =  (<div id="a" class="x">
    <div>aaaa</div>
    <div>bbb</div>
    <MyComponent>cccc</MyComponent>
</div>);

MyComponent 編譯後已不是帶引號的標籤

第12步 組件特殊處理

main.html

<body></body>
<script src="main.js"></script>

xixiToy.js

const xixiToy = {
    createElement,
    render,
};

class ElementWrapper {
    constructor(type) {
        this.root = document.createElement(type);
    }
    setAttribute(name, value) {
        this.root.setAttribute(name, value);
    }
    appendChild(component) {
        this.root.appendChild(component.root);
    }
}

class TextWrapper {
    constructor(content) {
        this.root = document.createTextNode(content);
    }
}

export class Component {
    constructor() {
        this._root = null;
        this.props = Object.create(null);
        this.children = [];
    }
    setAttribute(name, value) {
        this.props[name] = value;
    }
    appendChild(component) {
        this.children.push(component);
    }
    get root() {
        if (!this._root) {
            this._root = this.render().root;
        }
        return this._root;
    }
}

function createElement(type, attributes, ...children) {
    let e;
    if (typeof type  === 'string') {
        e = new ElementWrapper(type);
    } else {
        e = new type;
    }
    for (let attr in attributes) {
        e.setAttribute(attr, attributes[attr]);
    }
    for (let child of children) {
        if (typeof child === 'string') {
            child = new TextWrapper(child);
        }
        e.appendChild(child);
    }
    return e;
}

function render(component, parentElement) {
    parentElement.appendChild(component.root);
}

export default xixiToy;

main.js

import xixiToy, { Component } from './xixiToy.js';
const { render } = xixiToy;
class MyComponent extends Component {
    render() {
        return (
            <div>
                <h1>111111</h1>
                {this.children}
            </div>
        );
    }
}

render(<div id="a" class="x">
    <div>aaaa</div>
    <div>bbb</div>
    <MyComponent id='c'>cccc</MyComponent>
</div>, document.body);

最後會發現控制檯還是報錯

是因爲在createElement child做處理的時候沒有考慮到數組的情況

做完改造後,運行正常

function createElement(type, attributes, ...children) {
    let e;
    if (typeof type  === 'string') {
        e = new ElementWrapper(type);
    } else {
        e = new type;
    }
    for (let attr in attributes) {
        e.setAttribute(attr, attributes[attr]);
    }
    const insertChildren = (children) => {
        for (let child of children) {
            if (typeof child === 'string') {
                child = new TextWrapper(child);
            }
            if (typeof child === 'object' && child instanceof Array) {
                insertChildren(child);
            } else {
                e.appendChild(child);
            }
        }
    }
    insertChildren(children);
    return e;
}

詳細源碼,請看 https://github.com/biubiubiuxixi/toy-myreact

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章