實現react中的createElement和react-dom中的render

前言

你們都很清楚知道React是一個用於構建用戶界面的JavaScript庫,能夠書寫jsx編譯成真正的DOM插入到要顯示的頁面上。下面咱們作一些準備工做了解jsx變成DOM的過程,進而本身去實現一遍。javascript

準備工做

npm install create-react-app -g
create-react-app react-demo
cd react-demo
npm start
複製代碼

這時候咱們已經新建好一個react項目了,接下來在index.js中寫入console.log(<h1 style={{color: 'red'}}>hello world</h1>),打印出來的結果css

{
  props: {
    children: 'hello world',
    style: {color: ''red'} ... }, type: 'h1' } 複製代碼

這就是一個React對象,也就是虛擬DOM。接下來咱們打開babel官網,輸入<h1 style={{color: 'red'}}>hello world</h1>,結果以下圖所示 html

經過babel把jsx轉化成React中的createElement函數,執行後返回React對象。

咱們在項目中實現一個簡單的例子java

// index.js
import React from 'react';
import ReactDom from 'react-dom';

ReactDom.render(<h1 style={{color: 'red'}}>hello world</h1>, document.getElementById('root'));
複製代碼

結果以下圖 react

到這一步你們應該清楚知道寫jsx到咱們看到的頁面效果的實現過程了吧?經過babel轉化jsx成React中的createElement函數並執行,獲得React對象傳入到 ReactDom.render 生成真是的DOM,而且插入到指定的DOM節點上。npm

React中createElement函數

下面來實現一下createElement函數,返回一個React對象,建立react.js數組

// react.js
function ReactElement(type, props) { // 生成react對象 虛擬DOM
  const element = {type, props}
  return element;
}
function createElement(type, config, children){
  let propName;
  const props = {};
  for (propName in  config) {
    props[propName] = config[propName]; // 拷貝config
  }
  const childrenLength = arguments.length - 2; // 獲取children個數
  if (childrenLength === 1) {
    props.children = children;
  } else {
    props.children = Array.prototype.slice.call(arguments, 2) // 傳入了多個children
  }
  return ReactElement(type, props) // react對象,虛擬DOM
}

export default { createElement }

複製代碼

修改一下index.js,驗證一下寫的createElement是否正確bash

import React from './react.js'; // 引入本身寫的
...

console.log( React.createElement('h1', {style: {color: 'red'}}, 'hello world') );
// 或
// console.log(<h1 style={{color: 'red'}}>hello world</h1>)

// 打印結果以下, 則正確了
//{
// type: 'h1',
// props: {
// style: {color: 'red'},
// children: 'hello world',
// ...
// }
}

複製代碼

react-dom中render函數

下面來實現render函數,建立react-dom.jsbabel

function render(element, parentNode) {
  if (typeof element == 'string' || typeof element == 'number') { // 單獨處理
    return parentNode.appendChild(document.createTextNode(element));
  }
  let type, props;
  type = element.type;
  props = element.props;

  let domElement = document.createElement(type);
  for (let propName in props) {
    if (propName === 'children') {
      let children = props[propName];
      children = Array.isArray(children)? children : [children];
      children.forEach(child => {
        render(child, domElement); // 遞歸
      })
    } else if (propName === 'className') { // 生成類名
      domElement.className = props[propName];
    } else if (propName === 'style') { // 生成樣式
      let styleObj = props[propName];
      let cssText = Object.keys(styleObj).map(attr => {
        return `${attr.replace(/([A-Z])/g, function() { return "-" + arguments[1].toLocaleLowerCase() })} : ${styleObj[attr]}`
      }).join(';');
      domElement.style.cssText = cssText;
    }else { // 生成其餘屬性,還有像‘htmlFor’等等這些須要單獨處理的,這裏就不一一處理了
      if(propName.substring(0, 2) !== '__'){
        domElement.setAttribute(propName, props[propName]);
      }
    }
  }
  return parentNode.appendChild(domElement); // 插入生成的真是DOM
}

export default { render }

複製代碼

接下來修改index.js進行驗證app

import React from './react.js';
import ReactDOM from './react-dom.js';

let element = React.createElement('h1', {style: {color: 'red'}}, 'hello world');

ReactDOM.render(element, document.getElementById('root'));
複製代碼

結果以下,代碼運行成功

這裏的代碼,當咱們使用函數組件或類組件時,不能正確生成DOM,繼續拓展下

函數組件

// index.js
import React from './react.js';
import ReactDOM from './react-dom.js';
// 函數組件
function Welcome(props) {
  return <h1 style={props.style}>{props.name}</h1>
}
let element = React.createElement(Welcome, {name: 'hello world', style: {color: 'red'}});
ReactDOM.render(element, document.getElementById('root'));
複製代碼

這時候createElement返回的React對象的type是function,應該在建立DOM以前執行這個函數,拿到Welcome的返回值,再進行解析,那麼就在react-dom.js中加多個判斷

...
  let type, props;
  type = element.type;
  props = element.props;

  if (typeof type === 'function') { // 若是是函數組件,先執行
    element = type(props);
    type = element.type;
    props = element.props;
  }

  let domElement = document.createElement(type);
  ...
複製代碼

這時候就能正常使用函數組件了

類組件

// index.js
import React from './react.js';
import ReactDOM from './react-dom.js';
// 類組件
class Welcome extends React.Component{
  render() {
    return React.createElement('h1', {style: this.props.style}, this.props.name, this.props.age)
  }
}
let element = React.createElement(Welcome, {name: 'hello world', style: {color: 'red'}});
ReactDOM.render(element, document.getElementById('root'));
複製代碼

類組件須要React中的父類Component,那麼就在react.js加上

// react.js
class Component {
  static isClassComponent = true // 用於區分類組件
  constructor(props) {
    this.props = props
  }
}
...
export default {
  createElement,
  Component
}

複製代碼

因爲使用typeof判斷類返回的也是'function',那麼就跟函數組件的判斷有衝突了,並且類是須要new進行實例化的,所以在父類上加多了一個靜態屬性 isClassComponent 繼承給子類進行區分。下面就繼續修改react-dom.js的代碼

// react-dom.js
  ...
  if(type.isClassComponent) { // 類組件
    element = new type(props).render();
    type = element.type;
    props = element.props;
  } else if (typeof type === 'function') { // 函數組件
    element = type(props);
    type = element.type;
    props = element.props;
  }
  ...
複製代碼

這樣也把類組件拓展成功了。

以上過程就簡單實現了react中的createElement和react-dom中的render。若有錯誤,請指出,感謝閱讀。

相關文章
相關標籤/搜索