平常抄書之一次性搞懂React中Virtual DOM

1、Virtual DOM (虛擬DOM)

虛擬DOM就是React節點的對象樹結構,React的工做原理就是基於虛擬DOM完成。這個對象樹結構中每一個節點的內容都包含了一個原生DOM所具備的屬性。如:標籤名稱,屬性,子節點,id等。以下這種模式:node

{
    tagName: '',
    properties: {
        
    },
    children: [],
    key: ''
}
複製代碼

在React中虛擬DOM稱爲ReactNode。它有三種類型:ReactElementReactFragmentReactTextReactElement又分爲ReactComponentElementReactDOMElementreact

//ReactNode不用類型節點所需的基礎元素
type ReactNode = ReactElement | ReactFragment | ReactText
type ReactElement = ReactDOMElement | ReactComponentElement
type ReactDOMElemnt = {
    type: string,
    props: {
        children: ReactNodeList,
        className: string,
        style: object,
        ...etc
    }
    key: string | boolean | number | null,
    ref: string | null
}
type ReactComponentElement<TProps> = {
    type: ReactClass<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
複製代碼

建立一個React元素

建立一個React元素,它調用的是React.createElement方法。那麼這個方法作了什麼? 看一下JSX和編譯後的JavaScript文件:數組

const app = <Nav ref="nav" key="1" color="blue"><Profile>click</Profile>我是文本</Nav>;
const app = React.createElement(
    Nav,
    { 
        ref: 'nav',
        key: 1,
        color: 'blue' 
        
    },
    React.createElement(Profile, {}, "click" )
)
複製代碼

經過JSX建立的虛擬元素最終會編譯成React的createElement方法。 源碼目錄在此:bash

咱們把複製出來看一下,順便加點註釋:

//react中createElement方法來源於 ReactElement.js

//createElement相似一個工廠方法,只是作了簡單的參數修正,返回一個ReactElement實例對象。
/**
* 傳入了以下參數:
* type: "Nav"
* config: { ref: "nav", key: "1" }
* children:  1.react.createElement(...)
*            2.我是文本
*            
*/
export function createElement(type, config, children) {

  //能夠看到這幾個聲明的變量就是一個初始化參數的做用
  let propName;
  const props = {};
  let key = null;
  let ref = null;
  let self = null;
  let source = null;

  //這裏判斷若是config不爲空的話則提取config上的內容,賦值給前面初始化的變量
  if (config != null) {
    if (hasValidRef(config)) {
      ref = config.ref;  //將config上的ref賦值給ref
    }
    if (hasValidKey(config)) {
      key = '' + config.key; //將config上的key賦值給key
    }
    //將config上的self賦值給self
    self = config.__self === undefined ? null : config.__self; 
    //將config上的source賦值給source
    source = config.__source === undefined ? null : config.__source; 
    //下面的循環是將config上的內容複製到props上
    for (propName in config) {
      if (
        hasOwnProperty.call(config, propName) &&
        !RESERVED_PROPS.hasOwnProperty(propName)
      ) {
        props[propName] = config[propName];
      }
    }
  }

  //這裏是對children的操做,若是childrenLength爲1,那麼表示只有一個children,那麼就直接賦值給props的children屬性
  //若是childrenLength大於1,則表示有多個children,那麼要作合併操做,將他們放到一個數組裏面。而後賦值給props的children屬性
  const childrenLength = arguments.length - 2;
  if (childrenLength === 1) {
    props.children = children;
  } else if (childrenLength > 1) {
    const childArray = Array(childrenLength);
    for (let i = 0; i < childrenLength; i++) {
      childArray[i] = arguments[i + 2];
    }
    props.children = childArray;
  }

  // Resolve default props
  //這個type是建立的標籤名稱
  //這裏處理默認的props,若是存在默認的props,則將默認的props賦值給當前的props
  if (type && type.defaultProps) {
    const defaultProps = type.defaultProps;
    for (propName in defaultProps) {
      if (props[propName] === undefined) {
        props[propName] = defaultProps[propName];
      }
    }
  }
  //最後返回一個ReactElement實例對象
  return ReactElement(
    type, //這個type表明的是組件名
    key, 
    ref,
    self,
    source,
    ReactCurrentOwner.current,
    props,
  );
}
複製代碼

ReactCurrentOwner.current是個啥子東西?app

const ReactCurrentOwner = {
  /**
   * @internal
   * @type {ReactComponent}
   */
  current: (null: null | Fiber),
  currentDispatcher: (null: null | Dispatcher),
};

// 實際上這個current初始時是null,類型能夠是Fiber或null
複製代碼

最後返回的是一個ReactElement實例對象,那麼這個個ReactElement主要作了些什麼呢?ui

//簡化一下,發現最後返回的是這個element對象。這就是最終的虛擬DOM
const ReactElement = function(type, key, ref, self, source, owner, props) {
  const element = {
    //這個標誌表示這個元素是一個React Element 
    $$typeof: REACT_ELEMENT_TYPE,

    //將屬性注入到元素上
    type: type,
    key: key,
    ref: ref,
    props: props,

    // 記錄建立此元素的組建
    _owner: owner,
  };

  return element;
};
複製代碼

能夠看到這個方法最後返回的是一個對象,這個對象結構對應着建立一個DOM的條件。這就是建立了一個ReactNode。發現也是很簡單的吧。哈哈。spa

2、小知識點

在使用React建立組件以前咱們還有一個操做,就是初始化組建入口。經過判斷不一樣node類型來區分不一樣組件的入口。經過調用instantiateReactComponent方法進行初始化。3d

源碼目錄在此(v15.0.0版本)

把代碼複製出來看下:

// Given a ReactNode, create an instance that will actually be mounted
// @param {ReactNode} node //參數是一個ReactNode
 
 //參數是傳一個虛擬DOM節點
function instantiateReactComponent(node) { 
  var instance;

  //若是ReactNode是一個空組件
  //經過ReactEmptyComponent.create初始化一個空組件
  if (node === null || node === false) {
    instance = ReactEmptyComponent.create(instantiateReactComponent);
  //若是ReactNode是一個對象(DOM標籤或者自定義組件)
  } else if (typeof node === 'object') {
    var element = node;
    invariant(
      element && (typeof element.type === 'function' ||
                  typeof element.type === 'string'),
      'Element type is invalid: expected a string (for built-in components) ' +
      'or a class/function (for composite components) but got: %s.%s',
      element.type == null ? element.type : typeof element.type,
      getDeclarationErrorAddendum(element._owner)
    );

   //若是element的type類型爲string
   //則調用ReactNativeComponent.createInternalComponent
    if (typeof element.type === 'string') {
      instance = ReactNativeComponent.createInternalComponent(element);
    } else if (isInternalComponentType(element.type)) {
      instance = new element.type(element);
    //不然初始化自定義組件
    } else {
      instance = new ReactCompositeComponentWrapper(element);
    }
  //若是ReactNode是一個字符串或者數字
  //那麼調用ReactNativeComponent.createInstanceForText
  } else if (typeof node === 'string' || typeof node === 'number') {
    instance = ReactNativeComponent.createInstanceForText(node);
  } else {
    invariant(
      false,
      'Encountered invalid React node of type %s',
      typeof node
    );
  }
  instance._mountIndex = 0;
  instance._mountImage = null;


  return instance;
}
複製代碼
相關文章
相關標籤/搜索