複習一下常常寫的JSX
ReactDOM.render(
<h1>Hello World</h1>,
document.getElementById('root')
);
// babel 轉義之後,JSX語法其實是React.createElement()的語法糖
ReactDOM.render(React.createElement(
'h1', // type--節點型別
null, // props
'Hello World' // children
), document.getElementById('root'));
// createElement 部分程式碼詳細過程不做分析
export function createElement(type, config, children) {
...
// 返回一個ReactElement
return ReactElement(
type,
key,
ref,
self,
source,
ReactCurrentOwner.current,
props,
);
}
// ReactElement 部分程式碼
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,
};
...
return element; // 最後返回一個element
}
複製程式碼
createElement對應的原始碼部分是ReactElement.js,感興趣可以自己看看
這裡不細說JSX,感興趣可以看看官網介紹JSX
ReactDom.render 本地渲染過程
版本16之前,不做分析,詳細過程參考連結 ReactDom 元件渲染過程
版本16之後,渲染過程分析
宣告一個簡單元件
class Simple extends React.Component {
render() {
return (
<h1>Simple test</h1>
)
}
}
console.log(<Simple />);
複製程式碼
經過babel轉義之後
var Simple = (function(_React$Component2) {
// 繼承 Component 的屬性和方法
_inherits(Simple, _React$Component2);
function Simple() {
_classCallCheck(this, Simple);
return _possibleConstructorReturn(
this,
(Simple.__proto__ || Object.getPrototypeOf(Simple)).apply(this, arguments)
);
}
_createClass(Simple, [
{
key: "render",
value: function render() {
return _react2.default.createElement("h1", null, "Simple text");
}
}
]);
return Simple;
})(_react2.default.Component);
// 直接列印的是 createElement 返回的物件: 包括當前節點的所有資訊。 即: `render` 方法
console.log(_react2.default.createElement(Simple, null));
// 實際返回 建構函式。
exports.default = Simple;
複製程式碼
列印結果
{$$typeof: Symbol(react.element), type: ƒ, key: null, ref: null, props: {…}, …}
$$typeof:Symbol(react.element)
key:null
props:{}
ref:null
type:ƒ Simple()
_owner:null
_store:{validated: false}
_self:null
_source:{fileName: "D:\CodeSpace\Scheele2\src\index.js", lineNumber: 33}
__proto__:Object
複製程式碼
元件的掛載
先看chrome瀏覽器render過程,給大家一個印象,然後根據函式,逐一分析涉及DOM的部分
render --ReactDOM.render
const ReactDOM: Object = {
...
render(
element: React$Element<any>,
container: DOMContainer,
callback: ?Function,
) {
return legacyRenderSubtreeIntoContainer(
null, // parent
element, // children
container, // Dom容器
false, // 渲染標記
callback, // 回撥
);
},
}
複製程式碼
legacyRenderSubtreeIntoContainer
// 結合上方的render函式來看
function legacyRenderSubtreeIntoContainer(
parentComponent: ?React$Component<any, any>,
children: ReactNodeList,
container: DOMContainer,
forceHydrate: boolean,
callback: ?Function,
) {
...
let root: Root = (container._reactRootContainer: any);
if (!root) {
// 初始化掛載,獲得React根容器物件
root = container._reactRootContainer = legacyCreateRootFromDOMContainer(
container,
forceHydrate,
);
}
}
複製程式碼
ReactDOM.legacyCreateRootFromDOMContainer
function legacyCreateRootFromDOMContainer(
container: DOMContainer,
forceHydrate: boolean, // 渲染標記
): Root {
const shouldHydrate =
forceHydrate || shouldHydrateDueToLegacyHeuristic(container);
// 第一次渲染,刪除其餘的所有節點
if (!shouldHydrate) {
let warned = false;
let rootSibling;
while ((rootSibling = container.lastChild)) {
...
container.removeChild(rootSibling);
}
}
...
const isAsync = false; // 是否非同步
// 返回一個根節點物件
return new ReactRoot(container, isAsync, shouldHydrate);
}
...
// 通過DOMRenderer建立一個root
function ReactRoot(container: Container, isAsync: boolean, hydrate: boolean) {
const root = DOMRenderer.createContainer(container, isAsync, hydrate);
this._internalRoot = root;
}
複製程式碼
// createContainer涉及到了React新推出的Fiber
export function createContainer(
containerInfo: Container,
isAsync: boolean,
hydrate: boolean,
): OpaqueRoot {
return createFiberRoot(containerInfo, isAsync, hydrate);
}
複製程式碼
// 最終返回的物件
export function createFiberRoot(
containerInfo: any,
isAsync: boolean,
hydrate: boolean,
): FiberRoot {
...
// 沒有改造成Fiber之前,節點型別可能就幾種,Fiber之後嘛...
// 這裡是將節點初始化成Fiber的節點,感興趣可以看看Fiber,這裡不細說
const uninitializedFiber = createHostRootFiber(isAsync);
let root;
// 這個是個 export const enableSchedulerTracing = __PROFILE__;
if (enableSchedulerTracing) {
...
}else {
// 最後返回的root物件
root = ({
current: uninitializedFiber,
containerInfo: containerInfo, // DOM容器
pendingChildren: null,
earliestPendingTime: NoWork,
latestPendingTime: NoWork,
earliestSuspendedTime: NoWork,
latestSuspendedTime: NoWork,
latestPingedTime: NoWork,
didError: false,
pendingCommitExpirationTime: NoWork,
finishedWork: null,
timeoutHandle: noTimeout,
context: null,
pendingContext: null,
hydrate,
nextExpirationTimeToWorkOn: NoWork,
expirationTime: NoWork,
firstBatch: null,
nextScheduledRoot: null,
}: BaseFiberRootProperties);
}
uninitializedFiber.stateNode = root;
return ((root: any): FiberRoot);
}
...
複製程式碼
unbatchedUpdates
初始化root物件完成之後,呼叫unbatchedUpdates函式
// 不管是if,還是else,本質上都是初始化work = new ReactWork(),然後執行updateContainer操作
DOMRenderer.unbatchedUpdates(() => {
if (parentComponent != null) { // 如果根節點不為空,將節點渲染進去
root.legacy_renderSubtreeIntoContainer(
parentComponent,
children,
callback,
);
} else { // 渲染根節點
root.render(children, callback);
}
});
...
複製程式碼
ReactRoot.render,updateContainer
ReactRoot.prototype.render = function(
children: ReactNodeList,
callback: ?() => mixed,
): Work {
const root = this._internalRoot;
const work = new ReactWork();
callback = callback === undefined ? null : callback;
...
if (callback !== null) {
work.then(callback);
}
// 執行updateContainer函式,返回一個work物件(引數就不解釋了吧...)
DOMRenderer.updateContainer(children, root, null, work._onCommit);
return work;
};
複製程式碼
剩下的呼叫過程(這裡主要是react-reconciler部分的程式碼)
程式碼量過多,先貼出呼叫過程
// 這些函式呼叫的過程,可以簡單理解成是為了配合Fiber的批度更新以及非同步更新而進行的
updateContainerAtExpirationTime(element,container,parentComponent,expirationTime,callback)->
scheduleRootUpdate(current, element, expirationTime, callback) ->
scheduleWork(current, expirationTime) ->
requestWork(root, rootExpirationTime) ->
performWorkOnRoot(root, Sync, false) ->
renderRoot(root, false) ->
workLoop(isYieldy) -> // 開啟一個迴圈過程,這個函式還是挺有意思的
performUnitOfWork(nextUnitOfWork: Fiber) => Fiber | null ->
beginWork(current, workInProgress, nextRenderExpirationTime) // 這個函式開啟一個work流程
複製程式碼
beginWork
主要作用是根據Fiber物件的tag來對元件進行mount或update
function beginWork(
current: Fiber | null,
workInProgress: Fiber,
renderExpirationTime: ExpirationTime,
): Fiber | null {
const updateExpirationTime = workInProgress.expirationTime; // 更新所需時間
if (
!hasLegacyContextChanged() &&
(updateExpirationTime === NoWork ||updateExpirationTime > renderExpirationTime)
) {
...
switch (workInProgress.tag) {
case IndeterminateComponent: { // 不確定的元件型別
...
}
case FunctionalComponent: { // 函式型別元件
...
}
...
case ClassComponent: { // 對應我們之前建立的元件 Simple
const Component = workInProgress.type;
const unresolvedProps = workInProgress.pendingProps;
// 返回一個classComponent
return updateClassComponent(
current,
workInProgress,
Component,
unresolvedProps,
renderExpirationTime,
);
}
...
case HostRoot: // 對應根節點
return updateHostRoot(current, workInProgress, renderExpirationTime);
case HostComponent: // 對應 <h1 />
return updateHostComponent(current, workInProgress, renderExpirationTime);
case HostText: // 對應Simple test
return updateHostText(current, workInProgress);
... // 後面還有就不一一列舉了,感興趣可以自己看
}
複製程式碼
updateClassComponent
主要作用是對未初始化的元件進行初始化,對已初始化的元件進行更新和重用
function updateClassComponent(
current: Fiber | null,
workInProgress: Fiber,
Component: any,
nextProps,
renderExpirationTime: ExpirationTime,
) {
...
if (current === null) {
if (workInProgress.stateNode === null) {
// 例項化類元件
constructClassInstance(
workInProgress, // Fiber更新進度標識
Component, // 元件
nextProps, // props
renderExpirationTime, // 所需時間
);
// 初始化mount
mountClassInstance(
workInProgress,
Component,
nextProps,
renderExpirationTime,
);
shouldUpdate = true;
} else {
// 如果已經建立例項,則重用
shouldUpdate = resumeMountClassInstance(
workInProgress,
Component,
nextProps,
renderExpirationTime,
);
}
} else {
shouldUpdate = updateClassInstance(
current,
workInProgress,
Component,
nextProps,
renderExpirationTime,
);
}
return finishClassComponent(
current,
workInProgress,
Component,
shouldUpdate,
hasContext,
renderExpirationTime,
);
}
...
}
複製程式碼
mountClassInstance
主要是掛載一些新的生命週期函式,除了比較熟悉的componentWillMount外,還有dev環境下的警告生命週期函式,以及16後才推出的getDerivedStateFromProps
function mountClassInstance(
workInProgress: Fiber,
ctor: any,
newProps: any,
renderExpirationTime: ExpirationTime,
): void {
...
if (__DEV__) {
if (workInProgress.mode & StrictMode) {
ReactStrictModeWarnings.recordUnsafeLifecycleWarnings(
workInProgress,
instance,
);
ReactStrictModeWarnings.recordLegacyContextWarning(
workInProgress,
instance,
);
}
if (warnAboutDeprecatedLifecycles) {
ReactStrictModeWarnings.recordDeprecationWarnings(
workInProgress,
instance,
);
}
}
// 處理狀態更新的操作
let updateQueue = workInProgress.updateQueue;
if (updateQueue !== null) {
processUpdateQueue(
workInProgress,
updateQueue,
newProps,
instance,
renderExpirationTime,
);
instance.state = workInProgress.memoizedState;
}
const getDerivedStateFromProps = ctor.getDerivedStateFromProps;
if (typeof getDerivedStateFromProps === 'function') { // 掛載新的生命週期函式
applyDerivedStateFromProps(
workInProgress,
ctor,
getDerivedStateFromProps,
newProps,
);
instance.state = workInProgress.memoizedState;
}
if (
typeof ctor.getDerivedStateFromProps !== 'function' &&
typeof instance.getSnapshotBeforeUpdate !== 'function' &&
(typeof instance.UNSAFE_componentWillMount === 'function' ||
typeof instance.componentWillMount === 'function')
) {
// 如果沒有使用getDerivedStateFromProps就使用componentWillMount
callComponentWillMount(workInProgress, instance);
// 如果在這個生命週期中有狀態更新,則馬上開始處理它
updateQueue = workInProgress.updateQueue;
if (updateQueue !== null) {
processUpdateQueue(
workInProgress,
updateQueue,
newProps,
instance,
renderExpirationTime,
);
instance.state = workInProgress.memoizedState;
}
}
if (typeof instance.componentDidMount === 'function') {
workInProgress.effectTag |= Update;
}
// 所以說使用getDerivedStateFromProps這個新的狀態函式,第一次載入跟更新都會立即執行
}
複製程式碼
finishClassComponent
呼叫元件例項的render函式獲取需要處理的子元素,並把子元素處理為Fiber型別,處理state和props
function finishClassComponent(
current: Fiber | null,
workInProgress: Fiber,
Component: any,
shouldUpdate: boolean,
hasContext: boolean,
renderExpirationTime: ExpirationTime,
) {
...
// 把子元素轉換為Fiber型別,如果子元素為陣列的時候,返回第一個Fiber型別子元素
reconcileChildren( // 感興趣可以去github看看原始碼的說明
current,
workInProgress,
nextChildren,
renderExpirationTime,
);
// TODO: Restructure so we never read values from the instance.
// 處理state和props
memoizeState(workInProgress, instance.state);
memoizeProps(workInProgress, instance.props);
if (hasContext) {
invalidateContextProvider(workInProgress, Component, true);
}
// 返回Fiber型別的子元素
return workInProgress.child;
}
複製程式碼
workLoop
workLoop遍歷虛擬DOM樹,呼叫performUnitOfWork對子元素進行處理。
function workLoop(isYieldy) {
if (!isYieldy) {
// 迴圈遍歷整棵樹
while (nextUnitOfWork !== null) {
nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
}
} else {
// 一直遍歷,直到這一次的處理時間用完
while (nextUnitOfWork !== null && !shouldYield()) {
nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
}
}
}
複製程式碼
performUnitOfWork
呼叫beginWork對子元素進行處理返回,沒有新工作產生,呼叫completeUnitOfWork開始轉換
function performUnitOfWork(workInProgress: Fiber): Fiber | null {
const current = workInProgress.alternate;
// 檢查是否有其它工作需要展開
startWorkTimer(workInProgress);
...
let next;
// 判斷是否在一個更新時間內
if (enableProfilerTimer) {
if (workInProgress.mode & ProfileMode) {
startProfilerTimer(workInProgress);
}
next = beginWork(current, workInProgress, nextRenderExpirationTime);
if (workInProgress.mode & ProfileMode) {
// 記錄渲染時間
stopProfilerTimerIfRunningAndRecordDelta(workInProgress, true);
}
} else {
next = beginWork(current, workInProgress, nextRenderExpirationTime);
}
...
if (next === null) {
// 如果沒有新的工作產生,則完成當前工作
next = completeUnitOfWork(workInProgress);
}
ReactCurrentOwner.current = null;
return next;
}
複製程式碼
completeUnitOfWork
主要作用是將Fiber節點元素轉換為真實的DOM節點
function completeUnitOfWork(workInProgress: Fiber): Fiber | null {
while (true) {
const returnFiber = workInProgress.return; // 父節點
const siblingFiber = workInProgress.sibling; // 兄弟節點
if ((workInProgress.effectTag & Incomplete) === NoEffect) {
// 在一次渲染週期內
if (enableProfilerTimer) {
if (workInProgress.mode & ProfileMode) {
startProfilerTimer(workInProgress);
}
// 呼叫completeWork轉換虛擬DOM
nextUnitOfWork = completeWork(
current,
workInProgress,
nextRenderExpirationTime,
);
if (workInProgress.mode & ProfileMode) {
// 更新渲染時間
stopProfilerTimerIfRunningAndRecordDelta(workInProgress, false);
}
} else {
nextUnitOfWork = completeWork(
current,
workInProgress,
nextRenderExpirationTime,
);
}
let next = nextUnitOfWork;
stopWorkTimer(workInProgress); // 結束一個work
resetChildExpirationTime(workInProgress, nextRenderExpirationTime);
...
if (next !== null) { // 如果還有新的工作,return回去,繼續執行
stopWorkTimer(workInProgress);
return next;
}
// 這個地方就不註釋了,原始碼裡面已經解釋的足夠清楚
if (
returnFiber !== null &&
// Do not append effects to parents if a sibling failed to complete
(returnFiber.effectTag & Incomplete) === NoEffect
) {
// Append all the effects of the subtree and this fiber onto the effect
// list of the parent. The completion order of the children affects the
// side-effect order.
if (returnFiber.firstEffect === null) {
returnFiber.firstEffect = workInProgress.firstEffect;
}
if (workInProgress.lastEffect !== null) {
if (returnFiber.lastEffect !== null) {
returnFiber.lastEffect.nextEffect = workInProgress.firstEffect;
}
returnFiber.lastEffect = workInProgress.lastEffect;
}
// If this fiber had side-effects, we append it AFTER the children's
// side-effects. We can perform certain side-effects earlier if
// needed, by doing multiple passes over the effect list. We don't want
// to schedule our own side-effect on our own list because if end up
// reusing children we'll schedule this effect onto itself since we're
// at the end.
const effectTag = workInProgress.effectTag;
// Skip both NoWork and PerformedWork tags when creating the effect list.
// PerformedWork effect is read by React DevTools but shouldn't be committed.
if (effectTag > PerformedWork) {
if (returnFiber.lastEffect !== null) {
returnFiber.lastEffect.nextEffect = workInProgress;
} else {
returnFiber.firstEffect = workInProgress;
}
returnFiber.lastEffect = workInProgress;
}
}
...
if (siblingFiber !== null) {
// 如果有兄弟節點,則返回一個兄弟節點
return siblingFiber;
} else if (returnFiber !== null) {
// 完成這次工作
workInProgress = returnFiber;
continue;
} else {
// 已經遍歷到了根部
return null;
}
}
}else{
... // 感興趣自己看
}
return null;
}
複製程式碼
completeWork
根據Fiber節點型別進行處理,渲染真實DOM
function completeWork(
current: Fiber | null,
workInProgress: Fiber,
renderExpirationTime: ExpirationTime,
): Fiber | null {
...
switch (workInProgress.tag) {
...
// 只看重點程式碼
case HostComponent: {
popHostContext(workInProgress);
const rootContainerInstance = getRootHostContainer();
const type = workInProgress.type;
if (current !== null && workInProgress.stateNode != null) {
// 更新
updateHostComponent(
current,
workInProgress,
type,
newProps,
rootContainerInstance,
);
if (current.ref !== workInProgress.ref) {
markRef(workInProgress);
}
} else {
...
const currentHostContext = getHostContext();
let wasHydrated = popHydrationState(workInProgress);
if (wasHydrated) {
...
} else {
// 建立並返回DOM元素
let instance = createInstance(
type,
newProps,
rootContainerInstance,
currentHostContext,
workInProgress,
);
// 新增節點
appendAllChildren(instance, workInProgress);
// 判斷是否有其它的渲染器,進行標記
if (
finalizeInitialChildren(
instance,
type,
newProps,
rootContainerInstance,
currentHostContext,
)
) {
markUpdate(workInProgress);
}
workInProgress.stateNode = instance;
}
if (workInProgress.ref !== null) {
// 如果有ref,則需要呼叫
markRef(workInProgress);
}
}
break;
}
}
}
複製程式碼
簡單的總結
- 首先我們寫的JSX語法,都被Babel進行轉義,然後使用React.creatElement進行建立,轉換為React的元素
- 使用ReactDom.render建立root物件,執行root.render。
- 一系列函式呼叫之後,workLoop在一次渲染週期內,遍歷虛擬DOM,將這些虛擬DOM傳遞給performUnitOfWork函式,performUnitOfWork函式開啟一次workTime,將虛擬DOM傳遞給beginWork。
- beginWork根據虛擬DOM的型別,進行不同的處理,將子元素處理為Fiber型別,為Fiber型別的虛擬DOM新增父節點、兄弟節點等(就是轉換為Fiber樹)。
- beginWork處理完一次操作之後,返回需要處理的子元素再繼續處理,直到沒有子元素(即返回null),
- 此時performUnitOfWork呼叫completeUnitOfWork進行初始化生命週期的掛載,以及呼叫completeWork進行DOM的渲染。
- completeWork對節點型別進行操作,發現是html相關的節點型別,新增渲染為真實的DOM。
- 最後將所有的虛擬DOM,渲染為真實的DOM。
元件的解除安裝 todo.
服務端渲染過程 todo.
未來的瞎扯
在檢視原始碼的過程中,發現涉及非同步的操作,都寫上了unstable,而這些非同步的操作,才是Fiber這個利器展現獠牙的時候,在未來的某個時刻,我會補上這部分的東西
ps: 有不對的地方歡迎斧正