手把手帶你用85行程式碼實現一個React.js(詳細講解)

殷榮檜發表於2018-09-24

作者:殷榮檜@騰訊

  啥也不說,先來看看用我們85行的React.js直接替換別人寫好的掃雷專案中的React.js,看看效果怎麼樣?哈哈,是不是正常執行了。
手把手帶你用85行程式碼實現一個React.js(詳細講解)
  Attention:一定不要被下面辣麼多的程式碼嚇到,因為下面辣麼多的程式碼只是每次在上一個commit的基礎上新增幾行而已,大部分都是重複,只是為了讓你在閱讀上方便,不需要翻到上一個commit。所以,你一定要堅持看完,你也一定會有收穫。沒有收穫找我推款。
  85行程式碼分18次commit,如下圖所示。每個commit實現一個小目標,每個小目標實現一個相對完整的功能。最後由這些功能組合成自己的React.js框架。牆裂推薦你結合每一個commit和這篇文章一起看,再動手寫一寫。如果這樣你還是沒有看懂,到上海騰訊來,我真的手把手(氣氛有點不對啊)帶你寫。
手把手帶你用85行程式碼實現一個React.js(詳細講解)

1.第一個commit: initial commit 是在github上建立倉庫時自動生成的。然後在README.md中說明了一下倉庫的用途。用於實現一個基礎的React.js框架
2.第二個commit:實現最簡單的React渲染字元的功能(第一版) 這個commit主要實現了用React輸出一個helloworld到HTML頁面中。期待引用了我們的React.js後直接通過下述方式實現

const helloWorld = React.createElement('div', null, `Hello World`);
ReactDOM.render(helloWorld, document.getElementById('root'));
複製程式碼

此時我們React.js中的程式碼如下:

function createElement(parentEle, props, childEle) {
let parentElement = document.createElement(parentEle);
parentElement.innerHTML = childEle;
return parentElement;
}
function render(insertEle, rootEle) {
    rootEle.appendChild(insertEle);
}
React = {
    createElement
}
ReactDOM = {
    render
}
複製程式碼

3.第三個commit:新增函式元件的功能,能夠實現函式元件(第二版) 主要實現能夠在React建立元件時,實現能夠傳遞函式定義的元件,後期還會擴充套件向函式元件中傳遞引數

const Hello = function () {
return React.createElement('div', null, `Hello Version2.0`);
};
const helloWorld = React.createElement(Hello, null, null);
ReactDOM.render(helloWorld, document.getElementById('root'));
複製程式碼

此時我們的React.js中的程式碼如下:

function createElement(parentEle, props, childEle) {
if(typeof parentEle === 'function') {
    return parentEle();
} else {
    let parentElement = document.createElement(parentEle);
    parentElement.innerHTML = childEle;
    return parentElement;
}
}
function render(insertEle, rootEle) {
    rootEle.appendChild(insertEle);
}
React = {
    createElement
}
ReactDOM = {
    render
}
複製程式碼

4.第四個commit: 主要實現向React中新增子元件的效果(第三版) 實現能夠在React.js中新增子元件的效果,如下程式碼實現新增多個helloworld字元和div片段等。

const HelloVersion3 = function () {
return React.createElement('div', null, `版本3.0`);
};
const helloWorld1 = React.createElement(HelloVersion3, null, null);
const helloWorld2 = React.createElement(HelloVersion3, null, null);
const divEle = React.createElement('div', null, `我被一個div標籤包裹`);

const parent = React.createElement('div', null,
        helloWorld1,
        helloWorld2,
        divEle,
        `我是文字內容哦`
);

ReactDOM.render(parent, document.getElementById('root'));
複製程式碼

具體的React.js實現程式碼如下所示:

function createElement(parentEle, props, ...childEles) {
if(typeof parentEle === 'function') {
    return parentEle();
} else {
    let parentElement = document.createElement(parentEle);
    childEles.forEach(child => {
        if(typeof child === 'string') {
            parentElement.innerHTML += child;
        } else if(typeof child === 'object') {
            parentElement.appendChild(child);
        }
    });
    return parentElement;
}
}
function render(insertEle, rootEle) {
    rootEle.appendChild(insertEle);
}
React = {
    createElement
}
ReactDOM = {
    render
}
複製程式碼

5.第四個commit:類元件的實現(第四版) 主要是實現採用和React中最常用的使用class來定義元件的方式,如下程式碼所示:

class Hello {
render() {
    return React.createElement('div', null, `版本四,類元件的實現`);
}
}

const helloWorld = React.createElement(Hello, null, null);
ReactDOM.render(helloWorld, document.getElementById('root'));
複製程式碼

React.js程式碼如下:

function createElement(parentEle, props, ...childEles) {
    if (typeof parentEle === 'function' && /^\s*class\s+/.test(parentEle.toString())) {
        let component = new parentEle();
        return component.render();
    }else if (typeof parentEle === 'function'){
        return parentEle();
    }else {
        let parentElement = document.createElement(parentEle);
        childEles.forEach(child => {
            if(typeof child === 'string') {
                parentElement.innerHTML += child;
            } else if(typeof child === 'object') {
                parentElement.appendChild(child);
            }
        });
        return parentElement;
    }
}
function render(insertEle, rootEle) {
    rootEle.appendChild(insertEle);
}
React = {
    createElement
}
ReactDOM = {
    render
}
複製程式碼

6.第五個commit:React函式元件帶引數的實現(第五版) 這個比較簡單,其實原理就是向普通函式中傳遞引數後使用是一個道理,要實現的效果如下程式碼:

const Hello = ({name}) => {
	return React.createElement('div', null, `這是 ${name}`);
};

const helloWorld = React.createElement(Hello, {name: '版本五'}, null);
ReactDOM.render(helloWorld, document.getElementById('root'));
複製程式碼

React.js程式碼如下:

function createElement(parentEle, props, ...childEles) {
    if (typeof parentEle === 'function' && /^\s*class\s+/.test(parentEle.toString())) {
        let component = new parentEle();
        return component.render();
    }else if (typeof parentEle === 'function'){
        return parentEle(props);
    }else {
        let parentElement = document.createElement(parentEle);
        childEles.forEach(child => {
            if(typeof child === 'string') {
                parentElement.innerHTML += child;
            } else if(typeof child === 'object') {
                parentElement.appendChild(child);
            }
        });
        return parentElement;
    }
}
function render(insertEle, rootEle) {
    rootEle.appendChild(insertEle);
}
React = {
    createElement
}
ReactDOM = {
    render
}
複製程式碼

7.第六個commit:類元件屬性的傳遞(第六版) 這個commit主要用來解決如何向內元件中傳遞引數的問題,實現如下程式碼效果

  class Hello extends React.Component {
    constructor(props) {
      super(props);
    }
    render() {
      return React.createElement('div', null, `Hello ${this.props.name}`);
    }
  }
  const helloWorld = React.createElement(Hello, {name: '文字'}, null);
  ReactDOM.render(helloWorld, document.getElementById('root'));
複製程式碼

React.js程式碼如下所示:

class Component {
    constructor(props) {
        this.props = props;
    }
}
function createElement(parentEle, props, ...childEles) {
    if (typeof parentEle === 'function' && /^\s*class\s+/.test(parentEle.toString())) {
        // 當為類元件時
        let component = new parentEle(props);
        return component.render();
    }else if (typeof parentEle === 'function') {
        // 當為函式元件時
        return parentEle(props);
    }else {
        // 當為html標籤元件時
        let parentElement = document.createElement(parentEle);
        childEles.forEach(child => {
            if(typeof child === 'string') {
                parentElement.innerHTML += child;
            } else if(typeof child === 'object') {
                parentElement.appendChild(child);
            }
        });
        return parentElement;
    }
}
function render(insertEle, rootEle) {
    rootEle.appendChild(insertEle);
}
React = {
    createElement,
    Component
}
ReactDOM = {
    render
}
複製程式碼

8.第七個commit:類元件新增事件屬性(第七版) 主要實現能夠繫結click事件的功能,需要實現的效果程式碼如下:

class MyButton extends React.Component {
constructor(props) {
  super(props);
}
render() {
  return React.createElement('button', {onclick: this.props.onClick}, `Click me`);
}
}
const myBtn = React.createElement(MyButton, {onClick: () => alert('點選事件觸發')}, null);
ReactDOM.render(myBtn, document.getElementById('root'));
複製程式碼

React.js程式碼如下:

class Component {
    constructor(props) {
        this.props = props;
    }
}
function createElement(parentEle, props, ...childEles) {
    if (typeof parentEle === 'function' && /^\s*class\s+/.test(parentEle.toString())) {
        // 當為類元件時
        let component = new parentEle(props);
        return component.render();
    } else if (typeof parentEle === 'function') {
        // 當為函式元件時
        return parentEle(props);
    } else {
        // 當為html標籤元件時
        let parentElement = document.createElement(parentEle);
        Object.keys(props).forEach(key => {
            switch(key) {
                case 'onclick':
                    parentElement.addEventListener('click', props[key]);
                    break;
                default:
                    break;
            }
        });
        childEles.forEach(child => {
            if(typeof child === 'string') {
                parentElement.innerHTML += child;
            } else if(typeof child === 'object') {
                parentElement.appendChild(child);
            }
        });
        return parentElement;
    }
}
function render(insertEle, rootEle) {
    rootEle.appendChild(insertEle);
}
React = {
    createElement,
    Component
}
ReactDOM = {
    render
}
複製程式碼

9.第八個commit:實現類元件的動態渲染(資料的變化能夠同步到頁面上)(第八版) 到這一步已經可以使用上述的功能完成一個小的應用了,我們來試一下,實現一個簡單的計數器的應用。技術器的程式碼如下:

class Counter extends React.Component {
    constructor(props) {
      super(props);
      this.state = {value: 0};
    }
    onPlusClick() {
      this.setState({value: this.state.value + 1});
    }
    onMinusClick() {
      this.setState({value: this.state.value - 1});
    }
    render() {
      return React.createElement('div', null,
        React.createElement('div', null, `The Famous Dan Abramov's Counter`),
        React.createElement('div', null, `${this.state.value}`),
        React.createElement('button', {onClick: this.onPlusClick.bind(this)}, '+'),
        React.createElement('button', {onClick: this.onMinusClick.bind(this)}, '-')
      );
    }
}
let myCounter = React.createElement(Counter,null,null);
ReactDOM.render(myCounter, document.getElementById('root'));
複製程式碼

React.js程式碼如下:

let rootElement, rootReactElement;
// React基礎元件庫
class Component {
    constructor(props) {
        this.props = props;
    }
    setState(state) {
        this.state = state;
        reRender();
    }
}

// React.createElement
function createElement(parentEle, props, ...childEles) {
    if (typeof parentEle === 'function' && /^\s*class\s+/.test(parentEle.toString())) {
        // 當為類元件時
        let component = new parentEle(props);
        return component;
    } else if (typeof parentEle === 'function') {
        // 當為函式元件時
        return parentEle(props);
    } else {
        // 當為html標籤元件時
        let parentElement = document.createElement(parentEle);
        Object.keys(props || {}).forEach(key => {
            switch(key) {
                case 'onclick':
                    parentElement.addEventListener('click', props[key]);
                    break;
                case 'onClick':
                    parentElement.addEventListener('click', props[key]);
                    break;
                default:
                    break;
            }
        });
        childEles.forEach(child => {
            if(typeof child === 'string') {
                parentElement.innerHTML += child;
            } else if(typeof child === 'object') {
                parentElement.appendChild(child);
            }
        });
        return parentElement;
    }
}
function render(insertEle, rootEle) {
    rootElement = rootEle;
    rootReactElement = insertEle;
    rootEle.appendChild(insertEle.render());
}

function reRender() {
    while(rootElement.hasChildNodes()) {
        rootElement.removeChild(rootElement.lastChild);
    }
    ReactDOM.render(rootReactElement, rootElement);
}

React = {
    createElement,
    Component
}
ReactDOM = {
    render
}
複製程式碼

實現的計數器的效果圖如下所示:

手把手帶你用85行程式碼實現一個React.js(詳細講解)

到這裡我們就實現了一個最簡單的React.js框架。因為如果再敘述到實現掃雷文章的長度實在是太長了,如果你覺得還有必要把接下來的幾個commit看完(每個commit其實也就變更了很少行數的程式碼)你可以移步我的github檢視這篇文章的原始碼,逐個檢視Todo.md和變更的程式碼,有什麼疑問都可以在專案的issue中留言,不要吝嗇你的Star哦,你的Star是我繼續寫文章的動力,謝謝。

本文GitHub地址:github.com/jackiewille…

引用: hackernoon.com/build-your-…

相關文章