React(虚拟)DOM术语[一源看世界][之React]

阅读源码前,提前了解下作者对一些术语的定义,有助于更好地理解源码。以下为根据官方文档关于React (Virtual) DOM Terminology描述进行翻译并配上源码,以更深入地了解这些概念

在React的名词术语中,有五种主要的类型需要加以区别
. ReactElement / ReactElement Factory
. ReactNode
. ReactComponent / ReactComponent Class


React Elements

React中主要的类型是ReactElement,它有四种属性: typepropskeyref。从以下源码可以看出它就是一个普通的对象,通过$$typeof属性来识别是否是React Element,值为Symbol.for('react.element’)或60103

var ReactElement = function(type, key, ref, self, source, owner, props) {
  var element = {
    // This tag allow 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,
  };
  ...
  return element;
};

你可以通过React.createElement来创建这些对象。

var root = React.createElement('div');

我们来看下React.createElement的源码,看看它做了啥?

ReactElement.createElement = function(type, config, children) {
  ...

  if (config != null) {
    ...

    if (hasValidRef(config)) {
      ref = config.ref;
    }
    if (hasValidKey(config)) {
      key = '' + config.key;
    }

    self = config.__self === undefined ? null : config.__self;
    source = config.__source === undefined ? null : config.__source;
    // Remaining properties are added to a new props object
    for (propName in config) {
      if (hasOwnProperty.call(config, propName) &&
          !RESERVED_PROPS.hasOwnProperty(propName)) {
        props[propName] = config[propName];
      }
    }
  }

  // Children can be more than one argument, and those are transferred onto
  // the newly allocated props object.
  var childrenLength = arguments.length - 2;
  if (childrenLength === 1) {
    props.children = children;
  } else if (childrenLength > 1) {
    var childArray = Array(childrenLength);
    for (var i = 0; i < childrenLength; i++) {
      childArray[i] = arguments[i + 2];
    }
    props.children = childArray;
  }

  // Resolve default props
  if (type && type.defaultProps) {
    var defaultProps = type.defaultProps;
    for (propName in defaultProps) {
      if (props[propName] === undefined) {
        props[propName] = defaultProps[propName];
      }
    }
  }
  ...
  return ReactElement(
    type,
    key,
    ref,
    self,
    source,
    ReactCurrentOwner.current,
    props
  );
};

从源码中我们可以看到:

  1. config中可以包含保留属性refkey__self__source
  2. React Element的props属性包含了除保留属性外的所有config的属性值,children和type(通过React.createClass创建的类或称构造函数)的defaultProps的所有属性值
  3. ReactElement.createElement可以传不止3个参数,第3个以及后面的参数都作为child传进来,如:
var root = React.createElement('ul', { className: 'my-list' }, child1, child2);

你可以创建多个ReactElement,它们之间的父子包含关系形成了一个树结构,将最top的ReactElement和它将所在的容器(一个普通的DOM element,可以是HTMLElement 或者 SVGElement)传给ReactDOM.render方法,这样就可以在页面上渲染出这个组件及其所包含的子组件

ReactElement不是DOM element,它是DOM element的一个轻量的,无状态的,不可变的虚拟表示,就是传说中的虚拟DOM

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

可以通过传递一个属性对象给第2个参数为组件的DOM element增加属性,可以通过传递子React element对象给第3个参数为组件DOM element增加子的DOM element

var child = React.createElement('li', null, 'Text Content');
var root = React.createElement('ul', { className: 'my-list' }, child);
ReactDOM.render(root, document.getElementById('example'));

如果你用JSX,那么以下代码跟上面的代码是等价,JSX编译器会将以下JSX语法解析成上面的JS代码

var root = <ul className="my-list">
             <li>Text Content</li>
           </ul>;
ReactDOM.render(root, document.getElementById('example'));

看累了吗?小休息一下再接着看呗!!!


Factories

ReactElement-factory是一个可以生成特定type的ReactElement对象的工厂函数。React内置了有用的创建工厂函数的方法

ReactElement.createFactory = function(type) {
  var factory = ReactElement.createElement.bind(null, type);
  ...
  return factory;
};

这里小普及一下Function.prototype.bind函数,它是用来创建一个新的函数,新的函数的this关键字指向第1个参数(如果为null则保持不变),第2, ...个参数的值作为参数值按顺序传给原来的函数。举个栗子吧

var foo = function(p1, p2) { console.log(this, p1, p2); }

var barContext = {};
var bar = foo.bind(barContext, 'hello'); // bar函数的this关键字指向了barContext,传给foo函数的p1参数的值为'hello'

bar('daniel') // 相当于 foo('hello', 'daniel'),但两个函数this关键字指向不同

有了Factory工厂函数你就不用每次都敲打React.createElement('div')了。- Well done,程序员就应该能懒则懒

var div = React.createFactory('div');
var root = div({ className: 'my-div' });
ReactDOM.render(root, document.getElementById('example'));

React本身也内置了很多针对一般的HTML标签的Factory方法

var root = React.DOM.ul({ className: 'my-list' },
             React.DOM.li(null, 'Text Content')
           );

React Nodes

一个React Node可以是以下:
. React Element
. string (亦称ReactText)
. number (亦称ReactText)
. 一组React Node (亦称ReactFragment)
它们已作为其它React Element的属性的形式(props.children)来表示子元素,从而创建了一个树结构的组件


React Components

一个ReactComponent Class就是一个javascript类(或称为构造函数),它同时也称为某个ReactElement的类型(type)

var MyComponent = React.createClass({
  render: function() {
    ...
  }
});

当这个构造函数被调用时,返回的是一个至少有render方法的对象,这个对象被称为ReactComponent(即ReactComponent Class的实例)

var component = new MyComponent(props); // 千万不要这样去创建ReactComponent

除了测试,不要自己去调用构造函数,交给React来处理这事就好。
将ReactComponent Class传给createElement方法,返回一个ReactElement

var element = React.createElement(MyComponent);

或者使用JSX:

var element = <MyComponent />;

当这个element传给ReactDom.render方法时,React会帮你调用构造函数并返回ReactComponent

var component = ReactDOM.render(element, document.getElementById('example'));

如果你传入相同类型的ReactElement和相同的DOM容器继续调用ReactDOM.render,它会一直返回同一个实例。这个实例是有状态的

var componentA = ReactDOM.render(<MyComponent />, document.getElementById('example'));
var componentB = ReactDOM.render(<MyComponent />, document.getElementById('example'));
componentA === componentB; // true

从源码中src/renderers/dom/client/ReactMount.js来验证这个逻辑。

 _renderSubtreeIntoContainer: function(parentComponent, nextElement, container, callback) {
    ...

    var prevComponent = getTopLevelWrapperInContainer(container);

    if (prevComponent) {
      var prevWrappedElement = prevComponent._currentElement;
      var prevElement = prevWrappedElement.props;
      if (shouldUpdateReactComponent(prevElement, nextElement)) {
        var publicInst = prevComponent._renderedComponent.getPublicInstance();
        var updatedCallback = callback && function() {
          callback.call(publicInst);
        };
        ReactMount._updateRootComponent(
          prevComponent,
          nextWrappedElement,
          container,
          updatedCallback
        );
        return publicInst;
      } else {
        ...
      }
    }
    ...
  },

可以看出是通过shouldUpdateReactComponent方法来判断是否只更新并返回原来的实例publicInst,而shouldUpdateReactComponent针对ReactElement会判断typekey属性是否相等

这就是为什么不让你自己创建ReactComponent的原因,因为React作了优化的处理,可不是随随便便就New New New哦。

ReactComponentrender方法(就是你在声明一个组件时定义的render方法)会返回另外一个ReactElement,所以这些Component可以进行组合。最终会解析成DOM element实例并插入到document中


正式的类型定义

入口

ReactDOM.render = (ReactElement, HTMLElement | SVGElement) => ReactComponent;

Nodes 和 Elements

type ReactNode = ReactElement | ReactFragment | ReactText;

type ReactElement = ReactComponentElement | ReactDOMElement;

type ReactDOMElement = {
  type : string,
  props : {
    children : ReactNodeList,
    className : string,
    etc.
  },
  key : string | boolean | number | null,
  ref : string | null
};

type ReactComponentElement<TProps> = {
  type : ReactClass<TProps> | ReactFunctionalComponent<TProps>,
  props : TProps,
  key : string | boolean | number | null,
  ref : string | null
};

type ReactFragment = Array<ReactNode | ReactEmpty>;

type ReactNodeList = ReactNode | ReactEmpty;

type ReactText = string | number;

type ReactEmpty = null | undefined | boolean;

Classes and Components

type ReactClass<TProps> = (TProps) => ReactComponent<TProps>;

type ReactComponent<TProps> = {
  props : TProps,
  render : () => ReactElement
};

type ReactFunctionalComponent<TProps> = (TProps) => ReactElement;

最后,期待吐槽,期待指教!!!

--EOF--

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容

  • 原教程内容详见精益 React 学习指南,这只是我在学习过程中的一些阅读笔记,个人觉得该教程讲解深入浅出,比目前大...
    leonaxiong阅读 2,809评论 1 18
  • 以下内容是我在学习和研究React时,对React的特性、重点和注意事项的提取、精练和总结,可以做为React特性...
    科研者阅读 8,215评论 2 21
  • 本笔记基于React官方文档,当前React版本号为15.4.0。 1. 安装 1.1 尝试 开始之前可以先去co...
    Awey阅读 7,641评论 14 128
  • GUIDS 第一章 为什么使用React? React 一个提供了用户接口的JavaScript库。 诞生于Fac...
    jplyue阅读 3,508评论 1 11
  • 无端的指责 莫名的猜忌 无理的怀疑 陷入了僵局 我不再解释 你没有追问 好似陌生人 缘分就此断裂 彼此没了留恋 从...
    花少颜阅读 138评论 0 4