类组件和函数组件

1. src/index.js



 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

import React from './react';
import ReactDOM from './react-dom';
class ClassCounter extends React.Component {
  constructor(props) {
    super(props);
    this.props = props;
  }
  render() {
    let { children, ...props } = this.props;
    return React.createElement('div', { id: 'counter', ...props }, 'hello', children);
  }
}
function FunctionCounter(props) {
  let { children, ...config } = props;
  return React.createElement('div', { id: 'counter', ...config }, 'hello', children);
}
let element1 = React.createElement('div', { id: 'counter' }, 'hello');
let element2 = React.createElement(ClassCounter, { style: { color: 'blue' } }, 'world');
let element3 = React.createElement(FunctionCounter, { style: { color: 'blue' } }, 'world');
ReactDOM.render(
  element3,
  document.getElementById('root')
);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

2. src/react/index.js

 

 













 
 
 
 

 













 
 
 


 
 


import { ELEMENT, CLASS_COMPONENT, FUNCTION_COMPONENT } from './constants';
import { ReactElement } from './vdom';
import { Component } from './component';

function createElement(type, config = {}, children) {
  delete config.__source;//dev环境下变量,不考虑该变量
  delete config.__self;//dev环境下变量,不考虑该变量
  let { key, ref, ...props } = config;
  let $$typeof = null;
  if (typeof type === 'string') {//span div button
    $$typeof = ELEMENT;//是一个原生的DOM类型
    /**
     * 这里要注意,用真实React在测试时,你会发现:
     * let e1 = React.createElement('abc');
     * console.log(e1); // $$typeof 仍然是 react.element类型,type: 'abc'
     */
  } else if (typeof type === 'function' && type.prototype.isReactComponent) {
    $$typeof = CLASS_COMPONENT;
  } else if (typeof type === 'function') {
    $$typeof = FUNCTION_COMPONENT;
  } else {
    console.error(`Warning: React.createElement: type is invalid -- expected a string (for built-in components) or a class/function (for composite components) but got: ${typeof type}.`);
  }
  const childrenLength = arguments.length - 2;
  if (childrenLength === 1) {//children是一个对象或字符串
    props.children = children;
  } else if (childrenLength > 1) {//children是一个数组
    const childArray = Array(childrenLength);
    for (let i = 0; i < childrenLength; i++) {
      childArray[i] = arguments[i + 2];
    }
    props.children = childArray;
  }
  return ReactElement($$typeof, type, key, ref, props);
}
export {
  Component
}
const React = {
  createElement,
  Component
}
export default React;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43

3. src/react/constants.js

 
 
 
 

export const TEXT = Symbol.for('TEXT');
export const ELEMENT = Symbol.for('ELEMENT');
export const FUNCTION_COMPONENT = Symbol.for('FUNCTION_COMPONENT');//函数数件
export const CLASS_COMPONENT = Symbol.for('CLASS_COMPONENT');//类组件
1
2
3
4

4. src/react/component.js

  • 新增
class Component {
  constructor(props) {
    this.props = props;
  }
}
//类组件的本质也是函数(请参考new Class原理),通过`isReactComponent`判断是类组件还是函数组件
Component.prototype.isReactComponent = {};
export {
  Component
}
1
2
3
4
5
6
7
8
9
10

5. src/react/vdom.js

 
 


 
 
 
 
 
 





 
 
 
 

 
 
 
 
 





 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
















 


 
 
 
 
 
 












import { ELEMENT, CLASS_COMPONENT, FUNCTION_COMPONENT } from './constants';
import { setProps, onlyOne, flatten } from './utils';

export function createDOM(element) {
  if (element == null) { // null or undefined
    return null; // appendChild时,如果为null,则不挂载到parent上
  }
  let dom;
  if (typeof element === 'object') { // 如果是对象类型
    element = onlyOne(element); // 如果是数组,只取第一个
    let { $$typeof } = element;
    if (!$$typeof) { // 字符串或者数字
      dom = document.createTextNode(element);
    } else if ($$typeof == ELEMENT) { // 原生DOM节点
      dom = createNativeDOM(element);
    } else if ($$typeof == FUNCTION_COMPONENT) { // 函数组件
      dom = createFunctionComponentDOM(element);
    } else if ($$typeof == CLASS_COMPONENT) { // 类组件
      dom = createClassComponentDOM(element);
    }
    /**
     * `element`是ReactElement创建出来的虚拟DOM,让虚拟的DOM的`dom`属性指向真实DOM
     * 这里是一个预埋设计,或者叫铺垫,通过虚拟DOM能够获取真实DOM
     */
    element.dom = dom;
  } else { // 如果非对象类型,数字,字符串
    dom = document.createTextNode(element);
  }
  return dom;
}
// 创建函数组件真实的DOM对象
function createFunctionComponentDOM(element) {
  //element: $$typeof, type, key, ref, props
  let { type, props } = element;
  /**
   * function FunctionComponent(props) {
   *   return React.createElement('div', { id: 'counter' }, 'hello');
   * }
   */
  let renderElement = type(props);// type === FunctionComponent
  //element 是 React.createElement(FunctionComponent, config, children); 的返回值
  //element 是 FunctionComponent 的父级,当然这里不是DOM的父级,只是理解为父级
  element.renderElement = renderElement; // 这里也是一个预埋设计
  let dom = createDOM(renderElement);
  return dom;
  // 第25行`element.dom = dom;`,可以推导出: element.renderElement.dom=真实DOM
}
// 创建类组件真实的DOM对象
function createClassComponentDOM(element) {
  let {type, props } = element;
  /**
   * class ClassCounter extends React.Component {
   *   constructor(props) {
   *     super(props);
   *   }
   *   render() {
   *     return React.createElement('div', { id: 'counter' }, 'hello');
   *   }
   * }
   */
  let componentInstance = new type(props);
  element.componentInstance = componentInstance; // 这里也是一个预埋设计
  let renderElement = componentInstance.render();
  componentInstance.renderElement = renderElement; // 这里也是一个预埋设计
  let dom = createDOM(renderElement);
  return dom;
  // 第25行`element.dom = dom;`,可以推导出: element.componentInstance.renderElement.dom=真实DOM
}
/**
let element = React.createElement('button',
  { id: 'sayHello', onClick },
  'say', React.createElement('span', { onClick: spanClick, style: { color: 'red' } }, 'Hello')
);
 */
function createNativeDOM(element) {
  let { type, props } = element; // div button span
  let dom = document.createElement(type); //真实DOM对象
  //1,创建虚拟dom的子节点
  createNativeDOMChildren(dom, element.props.children);
  //2,给DOM元素添加属性
  setProps(dom, props);
  return dom;
}
function createNativeDOMChildren(parentNode, ...children) {
  let childrenNodeArr = children && flatten(children);
  if (childrenNodeArr) {
    for (let i = 0; i < childrenNodeArr.length; i++) {
      let child = childrenNodeArr[i];
      if (typeof child === 'object') { // 对象类型,不是字符串或数字
        //child会传递给element,预埋设计,跟第25行`element.dom = dom;`逻辑一样,给element添加索引
        child._mountIndex = i;
      }
      let childDOM = createDOM(child);
      childDOM && parentNode.appendChild(childDOM);
    }
  }
}

export function ReactElement($$typeof, type, key, ref, props) {
  let element = {
    $$typeof, type, key, ref, props
  };
  return element;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104

6. src/react/utils























 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

import { addEvent } from './event';

export function setProps(dom, props) {
  for (let key in props) {
    if (key != 'children') {
      let value = props[key];
      setProp(dom, key, value);
    }
  }
}
function setProp(dom, key, value) {
  if (/^on/.test(key)) {
    addEvent(dom, key, value);
  } else if (key === 'style') {
    for (const styleName in value) {
      dom.style[styleName] = value[styleName];
    }
  } else {
    dom.setAttribute(key, value);
  }
}

export function onlyOne(obj) {
  return Array.isArray(obj) ? obj[0] : obj;
}

// 打平任意多维数组,避免深度克隆
export function flatten(arr) {
  return arr.reduce((prev, curr, index) => {
    if (Array.isArray(curr)) {
      prev = prev.concat(flatten(curr));
    } else {
      prev = prev.concat(curr);
    }
    return prev;
  }, []);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

6. 没有改变的js

src/react-dom/index.js
src/react/event.js

Last Updated: 4/26/2020, 11:29:48 PM