什麼是JSX什麼是虛擬DOM(React16源碼分析)

const element = <h1>Hello, world!</h1>;

這個有趣的標籤語法既不是字符串也不是 HTML。

它被稱爲 JSX,是一個 JavaScript 的語法擴展。我們建議在 React 中配合使用 JSX,JSX 可以很好地描述 UI 應該呈現出它應有交互的本質形式。JSX 可能會使人聯想到模版語言,但它具有 JavaScript 的全部功能。

爲什麼使用 JSX?

React 認爲渲染邏輯本質上與其他 UI 邏輯內在耦合,比如,在 UI 中需要綁定處理事件、在某些時刻狀態發生變化時需要通知到 UI,以及需要在 UI 中展示準備好的數據。

JSX 表示對象

Babel 會把 JSX 轉譯成一個名爲 React.createElement() 函數調用。

以下兩種示例代碼完全等效:

const element = (
  <h1 className="greeting">
    Hello, world!
  </h1>
);
const element = React.createElement(
  'h1',
  {className: 'greeting'},
  'Hello, world!'
);

React.createElement() 會預先執行一些檢查,以幫助你編寫無錯代碼,但實際上它創建了一個這樣的對象:

// 注意:這是簡化過的結構
const element = {
  type: 'h1',
  props: {
    className: 'greeting',
    children: 'Hello, world!'
  }
};

這些對象被稱爲 “React 元素”(就是我們常說的虛擬DOM)。它們描述了你希望在屏幕上看到的內容。React 通過讀取這些對象,然後使用它們來構建 DOM 以及保持隨時更新。

我們將探討如何將 React 元素渲染爲 DOM。

實現createElement

源碼地址:鏈接

createElement代碼在react/src/ReactElement.js這個目錄裏面,下面附上代碼,然後依次分析內部結構

  1. createElement裏面會調用ReactElement,在ReactElement裏面只是通過函數封裝創建對象的具體過程,這是設計模式裏面的工廠模式,這樣寫的好處就是一眼看上去就知道這個對象有哪些屬性。
  2. 具體的邏輯都在createElement裏面
  3. REACT_ELEMENT_TYPE 是什麼,它來自import {REACT_ELEMENT_TYPE} from 'shared/ReactSymbols';
const hasSymbol = typeof Symbol === 'function' && Symbol.for;

export const REACT_ELEMENT_TYPE = hasSymbol
  ? Symbol.for('react.element')
  : 0xeac7;
  1. 表示React元素的唯一標識
const ReactElement = function(type, key, ref, self, source, owner, props) {
  const element = {
    $$typeof: REACT_ELEMENT_TYPE,
    type: type,
    key: key,
    ref: ref,
    props: props,
    _owner: owner,
  };
  // source 是隻在開發環境顯示的源碼映射,這裏部分代碼被我刪減
  return element;
};

重點討論createElement的實現原理,熟悉原理之前你得先掌握它的用法

// 文檔在這裏:https://zh-hans.reactjs.org/docs/react-api.html#createelement
React.createElement(
  type,
  [props],
  [...children]
)

創建並返回指定類型的新 React 元素。其中的類型參數既可以是標籤名字符串(如 ‘div’ 或 ‘span’),也可以是 React 組件 類型 (class 組件或函數組件),或是 React fragment 類型。

使用 JSX 編寫的代碼將會被轉換成使用 React.createElement() 的形式。如果使用了 JSX 方式,那麼一般來說就不需要直接調用 React.createElement()。

參數1:type表示標籤名或React組件
參數2:config表示屬性
參數3:children表示子元素

const hasOwnProperty = Object.prototype.hasOwnProperty;

const RESERVED_PROPS = {
  key: true,
  ref: true,
  __self: true,
  __source: true,
};
// 判斷是否是有效的ref
function hasValidRef(config) {
  return config.ref !== undefined;
}
// 判斷是否是有效的key
function hasValidKey(config) {
  return config.key !== undefined;
}

export function createElement(type, config, children) {
  let propName;
  const props = {};
  let key = null;
  let ref = null;
  let self = null;
  let source = null;

  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;
    // 把傳入的屬性都複製到props裏面(除了key,ref,__self,__source)!RESERVED_PROPS.hasOwnProperty(propName)就是過濾不要的屬性
    for (propName in config) {
      if (
        hasOwnProperty.call(config, propName) &&
        !RESERVED_PROPS.hasOwnProperty(propName)
      ) {
        props[propName] = config[propName];
      }
    }
  }

  // 子元素可能是一個也可能是多個,所以需要分開處理
  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;
  }

  // defaultProps 可以爲 Class 組件添加默認 props。這一般用於 props 未賦值,但又不能爲 null 的情況。
  // 文檔在這裏:https://zh-hans.reactjs.org/docs/react-component.html#defaultprops
  if (type && type.defaultProps) {
    const 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,
  );
}

注意代碼中還有:ReactCurrentOwner表示什麼,來自import ReactCurrentOwner from './ReactCurrentOwner';具體代碼如下

const ReactCurrentOwner = {
  current: null,
  currentDispatcher: null,
};

export default ReactCurrentOwner;

這就是虛擬DOM的原理,就是實現一個createElement函數,如果有人問你如何實現虛擬DOM,就把這個函數寫出來就可以啦!!!

實現Component

Component基類代碼在這裏:源碼鏈接

const emptyObject = {};

// 基類用於更新組件的狀態
function Component(props, context, updater) {
  this.props = props;
  this.context = context;
  // 如果組件具有字符串ref,將分配一個空對象。
  this.refs = emptyObject;
  // 初始化一個更新器,這個後面講解
  this.updater = updater
}
// 在類的原型上isReactComponent 
Component.prototype.isReactComponent = {};

這裏再提一下PureComponent實現原理

function ComponentDummy() {}
ComponentDummy.prototype = Component.prototype;

function PureComponent(props, context, updater) {
  this.props = props;
  this.context = context;
  this.refs = emptyObject;
  this.updater = updater;
}

const pureComponentPrototype = (PureComponent.prototype = new ComponentDummy());
// 修復了一下構造函數,原本指向ComponentDummy改成PureComponent
pureComponentPrototype.constructor = PureComponent;
// 把Component.prototype方法拷貝到pureComponentPrototype裏面,縮短原型鏈查找提高性能
Object.assign(pureComponentPrototype, Component.prototype);
pureComponentPrototype.isPureReactComponent = true;

// 其實可以理解爲class PureComponent extends Component {};pureComponentPrototype.isPureReactComponent = true;
// 具體實現在更新的時候做淺層比較
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章