React系列:有状态组件生成真实DOM结点

上次我们分析了无状态组件生成 DOM 的过程,无状态组件其实就是纯函数,它不维护内部的状态,只是根据外部输入,输出一份视图数据。而今天我们介绍的有状态组件,它有内部的状态,因此在组件的内部,可以自行对状态进行更改,进而渲染出新的视图。下面我们就来分析有状态组件生成真实 DOM 结点的过程。

我们先来写的一个 Greeting 组件,每次点击问候按钮,文字部分会更新问候的次数:

class Greeting extends React.Component {
  constructor() {
    super();

    this.state = {
      count: 0,
    };
  }

  componentDidMount() {
    console.log('did mount');
  }

  greet = () => {
    let {count} = this.state;

    this.setState({
      count: ++count,
    });
  };

  render() {
    let {name} = this.props;

    return (
      <div className="container">
        <div>hello {name} {this.state.count} times</div>
        <button onClick={this.greet}>greet</button>
      </div>
    )
  }
}

const App = <Greeting name="scott"/>;

console.log(App);

ReactDOM.render(App, document.getElementById('root'));

编译之后的代码如下:

// 自执行函数变量 _createClass实际上是用来定义props的
var _createClass = function () {
  
  // 定义属性 props是数组类型 [{key, val}]
  function defineProperties(target, props) {
    for (var i = 0; i < props.length; i++) {
      var descriptor = props[i];

      descriptor.enumerable = descriptor.enumerable || false;
      descriptor.configurable = true;

      if ("value" in descriptor) {
        descriptor.writable = true;
      }

      Object.defineProperty(target, descriptor.key, descriptor);
    }
  }

  return function (Constructor, protoProps, staticProps) {
    // 定义原型props
    if (protoProps) {
      defineProperties(Constructor.prototype, protoProps);
    }

    // 定义静态props
    if (staticProps) {
      defineProperties(Constructor, staticProps);
    }

    return Constructor;
  };

}();

function _possibleConstructorReturn(self, call) {
  return call && (typeof call === "object" || typeof call === "function") ? call : self;
}

// 继承
function _inherits(subClass, superClass) {
  
  // 使用Object.create(prototype, {constructor})来实现继承
  subClass.prototype = Object.create(superClass && superClass.prototype, {
    constructor: {
      value: subClass,
      enumerable: false,
      writable: true,
      configurable: true
    }
  });

  if (superClass) {
    Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
  }
}

// 自执行函数变量 表示用户自定义组件
var Greeting = function (_React$Component) {
  
  // 自定义Greeting组件
  function Greeting() {
    var _this = _possibleConstructorReturn(this, (Greeting.__proto__ || Object.getPrototypeOf(Greeting)).call(this));

    // 组件内部state
    _this.state = {
      count: 1
    };

    // 组件内部greet方法
    _this.greet = function () {
      var count = _this.state.count;

      _this.setState({
        count: ++count
      });
    };

    return _this;
  }

  // 继承ReactComponent
  _inherits(Greeting, _React$Component);

  // 给Greeting定义生命周期方法
  _createClass(Greeting, [
    {
      key: "componentDidMount",
      value: function componentDidMount() {
        console.log('did mount');
      }
    },
    {
      key: "render",
      value: function render() {
        var name = this.props.name;

        return React.createElement(
          "div",
          { className: "container" },
          React.createElement(
            "div",
            null,
            "hello ",
            name,
            " ",
            this.state.count,
            " times"
          ),
          React.createElement(
            "button",
            { onClick: this.greet },
            "greet"
          )
        );
      }
    }
  ]);

  return Greeting;

}(React.Component);

var App = React.createElement(Greeting, { name: "scott" });

console.log(App);

ReactDOM.render(App, document.getElementById('root'));

模拟组件渲染:

const React = {
  // 创建DOM描述对象 即虚拟DOM
  createElement(type, props, ...children) {
    let propsChildren = children;

    // 组件参数的props.children本身是数组
    // 所以调用组件函数时这里需要特殊处理
    if (Array.isArray(children[0])) {
      propsChildren = children[0];
    }

    // 结点
    let vnode = {
      type,
      props: {
        ...props,
        children: propsChildren,
      }
    };

    // 挂载组件函数体的虚拟DOM
    if (typeof type === 'function') {
      let componentProps = {
        ...props,
        children,
      };

      // 有状态组件
      if (type.prototype && type.prototype.render) {
        let component = new type();
        
        component.props = componentProps;
        component.vnode = vnode;

        vnode.body = component.render();
      }

      // 无状态组件
      else {
        vnode.body = type(componentProps);
      }
    }

    return vnode;
  }
};

// ReactComponent基类
function ReactComponent(props) {}

// 实现setState方法
ReactComponent.prototype.setState = function (partialSate) {
  Object.assign(this.state, partialSate);
  
  let oldDom = this.vnode.dom;
  let newDom = ReactDOM.generateDOM(this.render());

  this.vnode.dom = newDom;

  // 替换DOM结点
  oldDom.parentNode.replaceChild(newDom, oldDom);
}

// 模拟React.Component基类
React.Component = ReactComponent;

const ReactDOM = {
  // 渲染真实DOM
  render(vnode, container) {
    container.appendChild(this.generateDOM(vnode));
  },
  // 获取真实DOM结点
  generateDOM(vnode) {
    if (typeof vnode.type === 'function') {
      // 将组件函数体的虚拟DOM生成真实DOM
      let elem = this.generateDOM(vnode.body);

      vnode.dom = elem;

      return elem;
    }

    let elem = document.createElement(vnode.type);

    vnode.dom = elem;

    // 特殊key值映射
    let specialKeyMap = {
      className: 'class',
      fontSize: 'font-size',
    };
    let {props} = vnode;

    // 设置DOM属性
    props && Object.keys(props).forEach(key => {
      if (key === 'children') {
        // 处理子节点
        props.children.forEach(child => {
          if (['string', 'number'].includes(typeof child)) {
            // 纯内容节点
            elem.appendChild(document.createTextNode(child));
          } else {
            // DOM节点
            elem.appendChild(this.generateDOM(child));
          }
        });
      } else if (key === 'style') {
        // 设置样式属性
        let styleObj = props.style;
        let styleItems = [];

        Object.keys(styleObj).forEach(styleKey => {
          styleItems.push(`${specialKeyMap[styleKey] || styleKey}:${styleObj[styleKey]}`);
        });

        elem.setAttribute('style', styleItems.join(';'));
      } else if (['onClick'].includes(key)) {
        let eventName = key.replace(/^on/, '').toLowerCase();

        // 绑定事件
        elem.addEventListener(eventName, function () {
          props[key]();
        });
      } else {
        // 设置其他属性
        elem.setAttribute(specialKeyMap[key] || key, props[key]);
      }
    });

    return elem;
  }
};
posted @ 2018-07-26 13:40 liuhe688 阅读(...) 评论(...) 编辑 收藏