總覽
本小書大部分內(nèi)容來自作者 Jokcy 的 《React 源碼解析》: https://react.jokcy.me/
本文已同步在我的博客: http://ruizhengyun.cn/blog/post/2cb2c6f.html
感謝 Jokcy 讓我深度了解 React。如他所說,在決定閱讀 React 源碼時(shí)認(rèn)為不會(huì)是一件很難的事,但是真正開始閱讀之后才發(fā)現(xiàn),事情沒那么簡(jiǎn)單,因?yàn)樾枰銐虻哪托摹⒛軌颡?dú)立思考和靜下心來(因?yàn)槟銜?huì)碰到之前編碼沒有見過的寫法和概念等等)。
平常我們對(duì)外(后端、產(chǎn)品或其他前端)總喜歡說用的是 React 框架,可是我們并都熟悉 React 內(nèi)部是怎么運(yùn)行的。事實(shí)上,當(dāng) Facebook 將 React 和 ReactDOM 分包發(fā)布后,
React
就不僅僅是前端框架了,15版本后react
源碼越來越少,而React-dom
卻很大。很顯然,react
的很多邏輯都移到react-dom
中了。
版本說明
排版本小書的 React 版本是 V16.8.6
。
先說下 React16 這個(gè)版本節(jié)點(diǎn)吧。
React16 較之前的版本是核心上的一次重寫(想想就瘋狂),雖然之前 API 沒有變化繼續(xù)使用,但同時(shí)也增加了很多好用的功能(不然不是白瞎了么)。這也是首次引入 Fiber
概念,之后新的功能都是圍繞 Fiber
,比如 AsyncMode
和 Profiler
等。
說明:后面章節(jié)貼代碼的部分,我都會(huì)刪除原來英文注釋,加上自己的理解,有問題的地方,還請(qǐng)?jiān)谠u(píng)論中指出(如果有評(píng)論的話,沒有評(píng)論可以加我,有朋自遠(yuǎn)方來...)
上 V16.8.6
代碼
看看截止目前為止,React 暴露出來的 API
// react-16.8.6/packages/react/index.js
'use strict';
const React = require('./src/React');
module.exports = React.default || React;
// react-16.8.6/packages/react/src/React.js
...
const React = {
// packages/react/src/ReactChildren.js
Children: {
map,
forEach,
count,
toArray,
only,
},
// packages/react/src/ReactCreateRef.js
createRef,
// packages/react/src/ReactBaseClasses.js
Component,
PureComponent,
// packages/react/src/ReactContext.js
createContext,
// packages/react/src/forwardRef.js
forwardRef,
lazy,
memo,
// packages/react/src/ReactHooks.js
useCallback,
useContext,
useEffect,
useImperativeHandle,
useDebugValue,
useLayoutEffect,
useMemo,
useReducer,
useRef,
useState,
// packages/shared/ReactSymbols.js
Fragment: REACT_FRAGMENT_TYPE,
StrictMode: REACT_STRICT_MODE_TYPE,
Suspense: REACT_SUSPENSE_TYPE,
// packages/react/src/ReactElementValidator.js
createElement: __DEV__ ? createElementWithValidation : createElement,
cloneElement: __DEV__ ? cloneElementWithValidation : cloneElement,
createFactory: __DEV__ ? createFactoryWithValidation : createFactory,
isValidElement: isValidElement,
// packages/shared/ReactVersion.js
version: ReactVersion,
// packages/shared/ReactSymbols.js
unstable_ConcurrentMode: REACT_CONCURRENT_MODE_TYPE,
unstable_Profiler: REACT_PROFILER_TYPE,
// packages/react/src/ReactSharedInternals.js
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED: ReactSharedInternals,
};
...
上面代碼我們選擇性的去解析,無需所有都要去了解(個(gè)人學(xué)習(xí)方法方式)。
先說 Children
這個(gè)對(duì)象提供了一些處理 props.children
方法,children
是一個(gè)類似數(shù)組但又不是數(shù)組的數(shù)據(jù)結(jié)構(gòu),對(duì)其進(jìn)行處理時(shí)可用 React.Children
外掛方法。
createRef
新 ref
用法,不推薦使用 string ref
用法,比如 <div ref="divRef" />
。那正確姿勢(shì)是怎樣的呢?請(qǐng)看
class App extends React.Component {
constructor() {
this.ref = React.createRef();
}
render() {
return <div ref={this.ref} />
// or
return <div ref={node => this.ref = node} />
}
}
Component
和 PureComponent
看 packages/react/src/ReactBaseClasses.js
代碼
// Component
function Component(props, context, updater) {
this.props = props;
this.context = context;
this.refs = emptyObject;
this.updater = updater || ReactNoopUpdateQueue;
}
Component.prototype.isReactComponent = {};
Component.prototype.setState = function(partialState, callback) {}
Component.prototype.forceUpdate = function(callback) {}
// ComponentDummy => Component仿制品
function ComponentDummy() {}
ComponentDummy.prototype = Component.prototype;
// PureComponent
function PureComponent(props, context, updater) {
this.props = props;
this.context = context;
this.refs = emptyObject;
this.updater = updater || ReactNoopUpdateQueue;
}
const pureComponentPrototype = (PureComponent.prototype = new ComponentDummy());
pureComponentPrototype.constructor = PureComponent;
Object.assign(pureComponentPrototype, Component.prototype);
pureComponentPrototype.isPureReactComponent = true; // 多了一個(gè)標(biāo)識(shí)
export { Component, PureComponent };
這兩個(gè)類基本相同,唯一區(qū)別是 PureComponent
的原型上多了一個(gè)標(biāo)識(shí) isPureReactComponent
if (ComponentExample.prototype && ComponentExample.prototype.isPureReactComponent) {
return (
!shallowEqual(oldProps, newProps) || !shallowEqual(oldState, newState)
);
}
這是檢查組件是否需要更新的一個(gè)判斷,ComponentExample
是你聲明的繼承自 Component
或 PureComponent
的類,他會(huì)判斷你是否繼承自 PureComponent
,如果是的話就用 shallowEqual
比較 state
和 props
。
By the way(順便說一下,允許我騷一下):React 中對(duì)比一個(gè)
ClassComponent
是否需要更新,只有兩個(gè)地方。一是看有沒有
shouldComponentUpdate
方法;二就是這里的PureComponent
判斷;
createContext
createContext
是官方定稿的 context
方案,在這之前我們一直在用的老的 context API
,也是 React 不推薦的 API。Now(現(xiàn)在),新的 API 出來了,官方也已經(jīng)確定在 17 大版本會(huì)把老 API 去除。
新 API 的使用方法
const { Provider, Consumer } = React.createContext('defaultValue');
const ProviderComp = props => (
<Provider value='realValue'>
{props.children}
</Provider>
);
const ConsumerComp = () => (
<Consumer>
{value => <p>{value}</p>}
</Consumer>
)
具體差異,后面講 context
環(huán)節(jié)會(huì)詳細(xì)指出。
forwardRef
forwardRef
是用來解決 HOC 組件傳遞 ref
的問題的,所謂 HOC 就是 Higher Order Component
。就拿redux
來說,使用 connect
來給組件綁定需要的 state
,這其中其實(shí)就是給我們的組件在外部包了一層組件,然后通過 ...props
的方式把外部的 props
傳入到實(shí)際組件。forwardRef
的使用方法如下:
const TargetComponent = React.forwordRef((props, ref) => {
<TargetComponent ref={ref} {...props} />
});
這也說明了為什么要提供 createRef
作為新的 ref
使用方法的原因,如果用 string ref
就沒法當(dāng)作參數(shù)傳遞了。
后面章節(jié)會(huì)詳細(xì)分析 ref
。
lazy
是用來實(shí)現(xiàn)異步加載模塊的功能。
memo
是一個(gè)高階函數(shù),它與 React.PureComponent類似,但是一個(gè)函數(shù)組件而非一個(gè)類。
useXXX
系列
這就是 React16 的 Hooks 了,后續(xù)會(huì)做代碼分解。
類型
Fragment: REACT_FRAGMENT_TYPE,
StrictMode: REACT_STRICT_MODE_TYPE,
Suspense: REACT_SUSPENSE_TYPE,
unstable_ConcurrentMode: REACT_CONCURRENT_MODE_TYPE,
unstable_Profiler: REACT_PROFILER_TYPE,
這 5 個(gè)都是 React 提供的組件,但他們呢其實(shí)都只是占位符,都是一個(gè) Symbol
,在 React 實(shí)際檢測(cè)到他們的時(shí)候會(huì)做一些特殊的處理,比如 StrictMode
和 AsyncMode
會(huì)讓他們的子節(jié)點(diǎn)對(duì)應(yīng)的 Fiber 的 mode
都變成和他們一樣的mode
。
元素
createElement: __DEV__ ? createElementWithValidation : createElement,
cloneElement: __DEV__ ? cloneElementWithValidation : cloneElement,
createFactory: __DEV__ ? createFactoryWithValidation : createFactory,
isValidElement: isValidElement,
createElement
是 React 輸出中最重要的 API 了,是用來創(chuàng)建 ReactElement
的,但是很多前端童鞋卻從沒見過,也沒用過,這是為什么呢?這就得感謝 JSX 了,我們知道 JSX 并不是標(biāo)準(zhǔn)的 js,所以要經(jīng)過編譯才能變成可運(yùn)行的 js,而編譯之后,createElement
就出現(xiàn)了:
// jsx
<div id="app">content</div>
// js
React.createElement('div', { id: 'app' }, 'content')
cloneElement
它就很明顯了,是用來克隆一個(gè) ReactElement
的
createFactory
它是用來創(chuàng)建專門用來創(chuàng)建某一類 ReactElement
的工廠的
export function createFactory(type) {
const factory = createElement.bind(null, type);
factory.type = type;
return factory;
}
其實(shí)就是綁定了第一個(gè)參數(shù)的 createElement
,一般我們用 JSX 進(jìn)行編程的時(shí)候不會(huì)用到這個(gè) API。
isValidElement
是用來驗(yàn)證是否是一個(gè) ReactElement
的,基本也用不太到。