react包基礎概念以及React包的兩個核心api閱讀。閱讀React包的源碼版本爲16.8.6。
基礎概念
react包
react包的本質上是建立一個react相關數據類型的抽象屏障,它創建了一系列符合react渲染調度的數據結構,在各個react相關平臺(dom,native,render)上進行渲染調度。
在閱讀源碼前,我一度認爲諸如createElement
和Component
相關包,會有一系列複雜諸如生命週期,組件更新,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
就做了很簡單的兩件事,我在註釋中都有標註出來:
- 把config(標籤屬性定義)數據,移植到prop對象上。在這過程中對
key/ref/defualtProps
等一些特殊屬性做了一個合法以及賦值的處理。 - 處理了一下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
包中進行講述,