react和reactdom有什么區別
ReactDom 只做和瀏覽器或DOM相關的操作,例如:ReactDOM.render() 和 ReactDOM.findDOMNode()。如果是服務器端渲染,可以 ReactDOM.renderToString()。
React 不僅能通過 ReactDOM 和Web頁面打交道,還能用在服務器端SSR,移動端ReactNative和桌面端Electron。
React 在v0.14之前是沒有 ReactDOM 的,所有功能都包含在 React 里。從v0.14(2015-10)開始,React 才被拆分成React 和 ReactDOM。為什么要把 React 和 ReactDOM 分開呢?因為有了 ReactNative。React 只包含了 Web 和 Mobile 通用的核心部分,負責 Dom 操作的分到 ReactDOM 中,負責 Mobile 的包含在 ReactNative 中。
ReactDom是React的一部分。ReactDOM是React和DOM之間的粘合劑,一般用來定義單一的組件,或者結合ReactDOM.findDOMNode()來使用。更重要的是ReactDOM包已經允許開發者刪除React包添加的非必要的代碼,并將其移動到一個更合適的存儲庫。
react渲染組件方式
ReactDOM.render()適用于React應用的根組件渲染,ReactDOM.createPortal()適用于將組件渲染到DOM樹中的任意位置,而使用React-Portal庫可以更方便地實現ReactDOM.createPortal()的功能。
ReactDOM.render()做了什么
ReactDOM.render函數是整個 React 應用程序首次渲染的入口函數,它的參數是什么,返回值是什么,函數內部做了什么?
ReactDOM.render(<App />, document.getElementById("root"));
首先看下首次渲染時候,函數調用棧。
render函數源碼
源碼地址
export function render(
element: React$Element<any>, // 要渲染的組件
container: Container, // 根容器
callback: ?Function // 回調函數
) {
return legacyRenderSubtreeIntoContainer(
null,
element,
container,
false,
callback
);
}
legacyRenderSubtreeIntoContainer函數
源碼地址
內部實現:
判斷是否為第一次渲染,也就是首次掛載
首次掛載,首先會創建FiberRoot,然后判斷是否有回調函數,有就執行,最后進入updateContainer。
非首次,獲取FiberRoot,然后判斷是否有回調函數,有就執行,最后進入updateContainer。
首次和非首次,最大的區別在于是否創建FiberRoot和走不走批量更新。
最后返回當前應用程序根組件的實例(getPublicRootInstance)
function legacyRenderSubtreeIntoContainer(
parentComponent: ?React$Component<any, any>,
children: ReactNodeList,
container: Container,
forceHydrate: boolean,
callback: ?Function,
) {
let root: RootType = (container._reactRootContainer: any);
let fiberRoot;
// 初始化也就是react, 第一次渲染
if (!root) {
// Initial mount
/**
* 首次掛載時,會通過 legacyCreateRootFromDOMContainer 方法創建 container._reactRootContainer 對象并賦值給 root
*/
root = container._reactRootContainer = legacyCreateRootFromDOMContainer(
container,
forceHydrate,
);
// 也就是ReactDOM.render時候, 產生了fiberRoot
fiberRoot = root._internalRoot;
/**
* 判斷是否存在callback, 也就是ReactDom.render中的第三個參數
*/
if (typeof callback === 'function') {
const originalCallback = callback;
callback = function() {
const instance = getPublicRootInstance(fiberRoot);
// 通過實例去調用originalCallback方法
originalCallback.call(instance);
};
}
// 初始化掛載的時候, 不應該批量更新
unbatchedUpdates(() => {
updateContainer(children, fiberRoot, parentComponent, callback);
});
} else {
fiberRoot = root._internalRoot;
if (typeof callback === 'function') {
const originalCallback = callback;
callback = function() {
const instance = getPublicRootInstance(fiberRoot);
originalCallback.call(instance);
};
}
// Update
updateContainer(children, fiberRoot, parentComponent, callback);
}
return getPublicRootInstance(fiberRoot);
}
legacyCreateRootFromDOMContainer函數
legacyCreateRootFromDOMContainer ===> 。。。 ===> createFiberRoot。
前面調用了一大堆函數,實際上最終目的就是創建Fiber,根據前序的截圖來看,最終會去調用createFiberRoot。
首先創建一個FiberRootNode
然后創建RootFiber
接著將兩者關聯起來
接著初始化隊列updateQueue
最后返回FiberRootNode
fiberRootNode.current = rootFiber;
rootFiber.stateNode = fiberRootNode;
源碼地址
createFiberRoot源碼
export function createFiberRoot(
containerInfo: any,
tag: RootTag,
hydrate: boolean,
hydrationCallbacks: null | SuspenseHydrationCallbacks,
): FiberRoot {
// 創建fiberRoot(fiberRootNode)
const root: FiberRoot = (new FiberRootNode(containerInfo, tag, hydrate): any);
if (enableSuspenseCallback) {
root.hydrationCallbacks = hydrationCallbacks;
}
const uninitializedFiber = createHostRootFiber(tag);// 創建rootFiber(fiberNode)
// 把rootFiber掛載到fiberRoot的current屬性上
root.current = uninitializedFiber;
// 把fiberRoot掛載到rootFiber的stateNode屬性上
uninitializedFiber.stateNode = root;
// 初始化更新隊列
initializeUpdateQueue(uninitializedFiber);
return root;
}
updateContainer函數
這個函數主要是計算當前節點的lane優先級,創建update對象,加入更新隊列,最后被調度。這三個知識點不屬于本文所細講,留個印象,后面再細講。
export function updateContainer(
element: ReactNodeList,
container: OpaqueRoot,
parentComponent: ?React$Component<any, any>,
callback: ?Function
): Lane {
// 獲取fiberRoort.current, 也就是rootFiber, Fiber樹的頭結點
const current = container.current;
/* 當前觸發更新時候的時間戳 */
const eventTime = requestEventTime();
// console.log(eventTime, performance.now())
// 計算當前節點lane(優先級)
const lane = requestUpdateLane(current);
if (enableSchedulingProfiler) {
markRenderScheduled(lane);
}
const context = getContextForSubtree(parentComponent);
if (container.context === null) {
container.context = context;
} else {
container.pendingContext = context;
}
/**
* 根據lane(優先級)計算當前節點的update對象
*/
const update = createUpdate(eventTime, lane);
// Caution: React DevTools currently depends on this property
// being called "element".
update.payload = { element };
callback = callback === undefined ? null : callback;
if (callback !== null) {
update.callback = callback;
}
// 將update對象入隊
enqueueUpdate(current, update);
// 調度當前的Fiber節點(也就是rootFiber)
scheduleUpdateOnFiber(current, lane, eventTime);
return lane;
}
解答開頭提到的三個問題
1.參數是什么:
要渲染的子組件
根容器
要執行的回調函數
2.返回值是什么:
當前應用程序根組件的實例
3.做了什么操作:
創建FiberRoot,計算lane優先級,創建update對象,加入更新隊列,最后被調度器調度更新。