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对象,加入更新队列,最后被调度器调度更新。
网友评论