react源碼閱讀-基礎

  react包基礎概念以及React包的兩個核心api閱讀。閱讀React包的源碼版本爲16.8.6

基礎概念

react包

  react包的本質上是建立一個react相關數據類型的抽象屏障,它創建了一系列符合react渲染調度的數據結構,在各個react相關平臺(dom,native,render)上進行渲染調度。

  在閱讀源碼前,我一度認爲諸如createElementComponent相關包,會有一系列複雜諸如生命週期,組件更新,setState等複雜的邏輯,實際上react包在做的是生成符合規範的數據結構,對特定數據進行打標(標誌數據類型),react包當中最複雜的一個js文件就是ReactChildren.js文件,中途進行了對應的遞歸數組展開和到contextPour中拿取一系列數據。

代碼   |    react抽象層輸出數據    |      多端適配渲染

                               -> 服務端渲染     -> 服務端靜態html文件
code  ->  react  ->  reactType -> react-dom    -> html
                               -> react-native -> 移動端

jsx與babel

  jsx是react框架的精髓,jsx允許以js的方式來書寫html和css,使得react的dom更新薰染的方式真正活起來。jsx實質是createElement的語法糖,也是React當中使用最多的api。

const Box = <div id="box" data-num="3">123</div>;
const ContentBox = <Box />;

// bable tarnslate

var Box = React.createElement("div", {
  id: "box",
  "data-num": "3"
}, "123");
var ContentBox = React.createElement(Box, null);

  我們可以看到,在編譯過程中,如果是html自身存在的元素,createElement的第一個參數將爲一個html標籤名的字符串,而如果是自帶的組件,則爲變量名。因此爲了方便編譯區分,自定義組件變量名規定爲大寫

React入口與分層

  我們可以從package.json的main文件入口開始,來找尋React包的入口文件。
// package.json
"main": "index.js"

// index.js
'use strict';

const React = require('./src/React');

// TODO: decide on the top-level export form.
// This is hacky but makes it work with both Rollup and Jest.
module.exports = React.default || React;

  確定主文件入口爲React.js後,來簡單看一下react包的文件目錄結構。
在這裏插入圖片描述

  react包就是以各個api爲js文件,然後React.js爲主文件彙總的暴露的一個總分式文件結構。忽略掉引入文件以及dev模型的判斷語句,React.js就是一個很簡單的對象-api結構。

// __DEV__ 爲是否打開dev模式
const React = {
  Children: {
    map,
    forEach,
    count,
    toArray,
    only,
  },
  createRef,
  Component,
  PureComponent,
  createContext,
  forwardRef,
  lazy,
  memo,
  useCallback,
  useContext,
  useEffect,
  useImperativeHandle,
  useDebugValue,
  useLayoutEffect,
  useMemo,
  useReducer,
  useRef,
  useState,
  Fragment: REACT_FRAGMENT_TYPE,
  Profiler: REACT_PROFILER_TYPE,
  StrictMode: REACT_STRICT_MODE_TYPE,
  Suspense: REACT_SUSPENSE_TYPE,
  unstable_SuspenseList: REACT_SUSPENSE_LIST_TYPE,
  createElement: __DEV__ ? createElementWithValidation : createElement,
  cloneElement: __DEV__ ? cloneElementWithValidation : cloneElement,
  createFactory: __DEV__ ? createFactoryWithValidation : createFactory,
  isValidElement: isValidElement,
  version: ReactVersion,
  unstable_withSuspenseConfig: withSuspenseConfig,
  __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED: ReactSharedInternals,
};

export default React;

  React當中所有存在的api,都存在於此React對象當中。

react-core-api

  上文剛剛分析過jsx的本質其實就是React.createElement的語法糖。因此react當中我們最常使用的api實質上是createElement和classComponent模式下去extends的React.Component。

  在我看源碼之前,我本也以爲這兩個api的代碼量會非常龐大,涉及到及其複雜的調度邏輯。其實這兩個文件代碼平均下來每個文件不過百來行。去掉註釋和dev相關的一些異常輸出處理,核心的邏輯不過50行。這兩個api,實質上更多的是去創建定義一個Reactly的數據結構。這份數據結構,能夠作爲一個起點,來供react-dom以及react-native去在不同環境下拆分和使用。

ReactElement

  根據React.js的文件引用,我們可以很快找到createElement的api在同級目錄的ReactElement.js下。我們來看一下去掉dev調試邏輯後,createElement部分的代碼。

/** 
 * type 標籤類型
 * config 標籤屬性對象
 * children 除了前兩個之外剩下的都爲 children,創建element的子節點
*/
function createElement(type, config, children) {
  let propName;
  // 1.處理config屬性
  const props = {};
  let key = null;
  let ref = null;
  let self = null;
  let source = null;
  // 如果存在element的屬性對象
  if (config != null) {
    // ref和key格式檢查
    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
    // 從config上拷貝一份數據到props對象上面
    for (propName in config) {
      if (
        hasOwnProperty.call(config, propName) &&
        !RESERVED_PROPS.hasOwnProperty(propName)
      ) {
        props[propName] = config[propName];
      }
    }
  }
  // 2. 處理 children, 小於1直接等於,否則複製到數組上
  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
  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,
  );
}

  createElement就做了很簡單的兩件事,我在註釋中都有標註出來:

  1. 把config(標籤屬性定義)數據,移植到prop對象上。在這過程中對key/ref/defualtProps等一些特殊屬性做了一個合法以及賦值的處理。
  2. 處理了一下children,把入參的children轉換成數組,賦值到props上。最後調用了ReactElement函數。

  ReactElement函數就更簡單了,它直接返回一個打了標的react對象。其中self/source的概念都是用於dev狀態下的調試。無需去關心。

const ReactElement = function(type, key, ref, self, source, owner, props) {
  const 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,
  };
  return element;
}

  我們可以看到,我們寫的jsx代碼,最終就是轉換爲這樣一份對象數據。這份對象數據作爲一個起點,作用在react各個渲染平臺上dom/native/server,去實現各自的渲染邏輯。

React.Component

  Component代碼邏輯在React.js同目錄下的ReactBaseClasses.js文件中。Component的代碼更簡單,就是初始化了一個Component實例,在原型上綁定了常用的一些方法。

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;
}

Component.prototype.isReactComponent = {};

Component.prototype.setState = function(partialState, callback) {
  this.updater.enqueueSetState(this, partialState, callback, 'setState');
};

Component.prototype.forceUpdate = function(callback) {
  this.updater.enqueueForceUpdate(this, callback, 'forceUpdate');
};

  我們最關心的setState方法,其實什麼也沒做,只是調用了一個updater.enqueueSetState方法。而這個方法具體實現,是在React各個包中實現,這個方法的使用,將在後續react-dom包中進行講述,

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章