实现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。若有错误,请指出,感谢阅读。

相关文章
相关标签/搜索