React源码解读1

  • createElement是执行了new么?
  • 为什么会有React 和 ReactDOM?

标重点:版本是17.0.0-alpha.0

先来看一段代码

    class ShoppingList extends React.Component {
        render() {
          return (
            <div className="shopping-list">
              <h1>Shopping List for {this.props.name}</h1>
              <List />
            </div>
          );
        }
      }
      class List extends React.Component{
        constructor(props) {
          super(props);
          this.state = {
            value: ['Instagram', 'WhatsApp','Oculus' ],
          };
        }
        render() {
          const lis = [];
          this.state.value.forEach(item => {
            lis.push(<li>{item}</li>)
          })
          return (
            <ul>
                {lis}
              </ul>
          );
        }
      }
      ReactDOM.render(
        <div>
        <ShoppingList name="Mark" />
        <h1>Hello World!</h1></div>,
        document.getElementById('container')
      );

相信写过react代码的大家都不会读不懂这段代码;
那么,这段代码到底是怎么执行的呢?
首先,如果直接把这段代码放到浏览器里,浏览器能读懂么?
显然是不能,连新手前端工程师都应该明白浏览器看不懂,这也明显不是我们常见的可以执行的代码,那么浏览器真正能看懂的是什么呢?
看经过babel翻译过后的这段代码:

var ShoppingList = function (_React$Component) {
    _inherits(ShoppingList, _React$Component);
    function ShoppingList() {
      _classCallCheck(this, ShoppingList);
      return _possibleConstructorReturn(this, (ShoppingList.__proto__ || Object.getPrototypeOf(ShoppingList)).apply(this, arguments));
  }
  _createClass(ShoppingList, [{
    key: 'render',
    value: function render() {
      return React.createElement(
        'div',
        { className: 'shopping-list' },
        React.createElement(
          'h1',
          null,
          'Shopping List for ',
          this.props.name
        ),
        React.createElement(List, null)
      );
    }
  }]);

  return ShoppingList;
}(React.Component);

var List = function (_React$Component2) {
  _inherits(List, _React$Component2);

  function List(props) {
    _classCallCheck(this, List);

    debugger;

    var _this2 = _possibleConstructorReturn(this, (List.__proto__ || Object.getPrototypeOf(List)).call(this, props));

    _this2.state = {
      value: ['Instagram', 'WhatsApp', 'Oculus']
    };
    return _this2;
  }

  _createClass(List, [{
    key: 'render',
    value: function render() {
      var lis = [];
      this.state.value.forEach(function (item) {
        lis.push(React.createElement(
          'li',
          null,
          item
        ));
      });
      return React.createElement(
        'ul',
        null,
        lis
      );
    }
  }]);

  return List;
}(React.Component);

ReactDOM.render(React.createElement(
  'div',
  null,
  React.createElement(ShoppingList, { name: 'Mark' }),
  React.createElement(
    'h1',
    null,
    'Hello World!'
  )
), document.getElementById('container'));

我们的class都被翻译成了function,说明class本身也就是语法糖而已,那么其中的属性继承什么的就不多说了;
而JSX语法表示的组件都显然被转为React.createElement,而且可以见到子组件是在createElement的第三个参数开始往后列;
render则执行的是ReactDOM.render;
我们知道React的渲染可以在很多环境中,那么有ReactDOM也不奇怪的;那么就看看React里做了什么,ReactDOM又做了什么;

React很小,ReactDOM很大

看到build后的代码之后
(如果想知道怎么获得build后的非压缩代码就看官网,一般开源的代码都会告诉你如何去contribute,[how-to-contribute]https://reactjs.org/docs/how-to-contribute.html,当你读完这个之后就怎么知道把一个大型的库看起来很没有头绪的代码最后打包成能执行的整体的办法)
我们神奇的发现:
React的development非压缩的代码只有3668行;
而ReactDOM则达到了27335行;
那么上文中的React.createElement都干了啥呢??

React的暴露API

[Top API]https://reactjs.org/docs/react-api.html#reactmemo

React.createElement

createElement到底做了什么呢?
简单来说,返回了ReactElement函数的执行结果:

    function createElement(type, config, children) {
        // 中间省略一大段
        return ReactElement(type, key, ref, self, source, ReactCurrentOwner.current, props);
     }

当然除此之外,中间省略的那一大段还做了:

  • 做了各种的类型检查;
  • 把key和ref都从config里面给提出来,单独列出来;
  • 建了一个新的props对象,把props key都挂上去,把default值都附上去先;
  • 做了对props上的key和ref的访问限制;
  • 处理了一下children,如果有多个则形成一个数组,单个的话就一个对象,挂到props上;
    最后就把整理好的参数传递给了function ReactElment;

ReactElement

  • 这是一个工厂函数。用来创造React element,没有实用new来构造,所以并不支持instanceof的检查;
  • 但会增加一个 &&typeof 属性来检测是否是react element ,REACT_ELEMENT_TYPE = symbolFor('react.element');

直接贴上源码吧:

  /**
   * @param {*} self A *temporary* helper to detect places where `this` is
   * different from the `owner` when React.createElement is called, so that we
   * can warn. We want to get rid of owner and replace string `ref`s with arrow
   * functions, and as long as `this` and owner are the same, there will be no
   * change in behavior.
   * @param {*} source An annotation object (added by a transpiler or otherwise)
   * indicating filename, line number, and/or other information.
   * @internal
   */


  var ReactElement = function (type, key, ref, self, source, owner, props) {
    var element = {
      // This tag allows us to uniquely identify this as a React Element
      $$typeof: REACT_ELEMENT_TYPE,
      // Built-in properties that belong on the element
      type: type,
      key: key,
      ref: ref,
      props: props,
      // Record the component responsible for creating this element.
      _owner: owner
    };

    {
      // The validation flag is currently mutative. We put it on
      // an external backing store so that we can freeze the whole object.
      // This can be replaced with a WeakMap once they are implemented in
      // commonly used development environments.
      element._store = {}; // To make comparing ReactElements easier for testing purposes, we make
      // the validation flag non-enumerable (where possible, which should
      // include every environment we run tests in), so the test framework
      // ignores it.

      Object.defineProperty(element._store, 'validated', {
        configurable: false,
        enumerable: false,
        writable: true,
        value: false
      }); // self and source are DEV only properties.

      Object.defineProperty(element, '_self', {
        configurable: false,
        enumerable: false,
        writable: false,
        value: self
      }); // Two elements created in two different places should be considered
      // equal for testing purposes and therefore we hide it from enumeration.

      Object.defineProperty(element, '_source', {
        configurable: false,
        enumerable: false,
        writable: false,
        value: source
      });

      if (Object.freeze) {
        Object.freeze(element.props);
        Object.freeze(element);
      }
    }

    return element;
  };

这就结束了? React.createElment真的就到此就结束了,没有想象中的复杂;
那React在这里还做了什么呢?
我们看到代码里
React.createElement(ShoppingList, { name: 'Mark' })
ShoppingList是什么?可以看到上面的代码ShoppingList是一个自执行函数,最后的返回还是一个function,这个function继承了React.Component,我们可以看出其实在整个createElement执行过后,其实并没有去new ShopingList,只是把这个function挂在了 react element一个属性上,其他什么都没有去做;那么这个new一定不是在React这方执行的;那么只能是在ReactDOM.render这里面才做出的;
还是先来看看React.Component这个基类到底有什么吧;

React.Component

从构造函数看来,props context refs都比较正常;那么涉及到更新的可能就是这个updater了;
构造函数:

function Component(props, context, updater) {
    this.props = props;
    this.context = context; 
   // If a component has string refs, we will assign a different object later.
    this.refs = emptyObject;

 // We initialize the default updater but the real one gets injected by the renderer.
    this.updater = updater || ReactNoopUpdateQueue;
  }

里面有句很重要注释:
We initialize the default updater but the real one gets injected by the renderer
updater这里使用的是默认的值,但是真正的updater是render给的;
那么updater是做什么的呢?
那么涉及到react更新最重要的方法是什么呢?肯定是setState了;
那么我们看看目前挂载在现在这个函数上的setState是怎么写的:

  Component.prototype.setState = function (partialState, callback) {
    if (!(typeof partialState === 'object' || typeof partialState === 'function' || partialState == null)) {
      {
        throw Error( "setState(...): takes an object of state variables to update or a function which returns an object of state variables." );
      }
    }

    this.updater.enqueueSetState(this, partialState, callback, 'setState');
  };

原来如此,那其实setState的执行就是要靠updater来做;而默认ReactNoopUpdateQueue里的enqueueSetState方法是什么呢?

    enqueueSetState: function (publicInstance, partialState, callback, callerName) {
      warnNoop(publicInstance, 'setState');
    }

可以看到只是做了一个warning的判断,没有真正执行真正的逻辑;
的那么是ReactDOM会覆盖基类方法么?ReactDOM的render执行到底怎么执行的呢,请听下回分解吧;

附录React暴露出来的一些方法和属性,感兴趣的可以继续去看看:

  exports.Children = Children;
  exports.Component = Component;
  exports.PureComponent = PureComponent;
exports.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED = ReactSharedInternals$1;
  exports.cloneElement = cloneElement$1;
  exports.createContext = createContext;
  exports.createElement = createElement$1;
  exports.createFactory = createFactory;
  exports.createRef = createRef;
  exports.forwardRef = forwardRef;
  exports.isValidElement = isValidElement;
  exports.lazy = lazy;
  exports.memo = memo;
  exports.unstable_block = block;
  exports.unstable_createMutableSource = createMutableSource;
  exports.unstable_startTransition = startTransition;
  exports.unstable_useDeferredValue = useDeferredValue;
  exports.unstable_useMutableSource = useMutableSource;
  exports.unstable_useOpaqueIdentifier = useOpaqueIdentifier;
  exports.unstable_useTransition = useTransition;
  exports.useCallback = useCallback;
  exports.useContext = useContext;
  exports.useDebugValue = useDebugValue;
  exports.useEffect = useEffect;
  exports.useImperativeHandle = useImperativeHandle;
  exports.useLayoutEffect = useLayoutEffect;
  exports.useMemo = useMemo;
  exports.useReducer = useReducer;
  exports.useRef = useRef;
  exports.useState = useState;
  exports.version = ReactVersion;
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,470评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,393评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,577评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,176评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,189评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,155评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,041评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,903评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,319评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,539评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,703评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,417评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,013评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,664评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,818评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,711评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,601评论 2 353

推荐阅读更多精彩内容