前言
本文的主要目的是阅读源码的过程中做下笔记和分享给有需要的小伙伴, 可能会有纰漏和错误, 请读者自行判断, 头一次写阅读代码的文章, 可能写得有点乱, 有什么问题欢迎一起探讨一起进步.
React 的版本为 16.4, 主分支的代码, 只贴出部分关键代码, 完整代码请到 Github 查看.
虚拟 DOM 的初始化
React.createElement
在阅读源码前, 我们先提出一个问题, React 是如何将虚拟 DOM 转换为真实的 DOM 呢? 有问题以后我们才会更有目标的阅读代码, 下面我们就带着这个问题去思考.
在平时工作中我们常常用 JSX 语法来创建 React 元素实例, 但他们最后都会通过打包工具编译成原生的 JS 代码, 通过 React.createElement 来创建. 例如:
- // class ReactComponent extends React.Component {// render() {
- // return <p className="class">Hello React</p>;
- // }
- // }
- // 以上代码会编译为:
- class ReactComponent extends React.Component {
- render() {
- React.createElement(
- 'p',
- { className: 'class'},
- 'Hello React'
- )
- }
- }
- // <ReactComponent someProp="prop" />
- React.createElement(ReactComponent, { someProp: 'prop' }, null);
复制代码
这样我们就可以创建得到 React 元素实例. 先来看看 createElement 的主要源码(部分代码省略):
- function createElement(type, config, children) {
- let propName;
- const props = {};
- let key = null;
- let ref = null;
- let self = null;
- let source = null;
- if (config != null) {
- if (hasValidRef(config)) {
- // 如果有 ref, 将他取出来
- ref = config.ref;
- }
- if (hasValidKey(config)) {
- // 如果有 key, 将他取出来
- key = '' + config.key;
- }
- self = config.__self === undefined ? null : config.__self;
- source = config.__source === undefined ? null : config.__source;
- for (propName in config) {
- if (
- hasOwnProperty.call(config, propName) &&
- !RESERVED_PROPS.hasOwnProperty(propName)
- ) {
- // 将除 ref,key 等这些特殊的属性放到新的 props 对象里
- props[propName] = config[propName];
- }
- }
- }
- // 获取子元素
- 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;
- }
- // 添加默认 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,
- );
- }
复制代码
- const ReactElement = function(type, key, ref, self, source, owner, props) {
- // 最终得到的 React 元素
- 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;
- };
复制代码
是不是很简单呢, 主要是把我们传进去的东西组成一个 React 元素对象, 而 type 就是我们传进去的组件类型, 他可以是一个类, 函数或字符串(如'div').
ReactDom.render
虽然我们已经得到创建好的 React 元素, 但 React 有是如何把 React 元素转换为我们最终想要的 DOM 呢? 就是通过 ReactDom.render 函数啦.
- ReactDom.render(
- React.createElement(App),
- document.getElementById('root')
- );
复制代码
ReactDom.render 的定义:
- render(
- element: React$Element<any>,
- container: DOMContainer,
- callback: ?Function,
- ) {
- return legacyRenderSubtreeIntoContainer(
- null, /* 父组件 */
- element, /* React 元素 */
- container, /* DOM 容器 */
- false,
- callback,
- );
- }
复制代码
legacyRenderSubtreeIntoContainer 先获取到 React 根容器对象(只贴部分代码):
- ...
- root = container._reactRootContainer = legacyCreateRootFromDOMContainer(
- container,
- forceHydrate,
- );
- ...
复制代码
因代码过多只贴出通过 legacyCreateRootFromDOMContainer 最终得到的 React 根容器对象:
- const NoWork = 0;
- {
- _internalRoot: {
- current: uninitializedFiber, // null
- containerInfo: containerInfo, // DOM 容器
- pendingChildren: null,
- earliestPendingTime: NoWork,
- latestPendingTime: NoWork,
- earliestSuspendedTime: NoWork,
- latestSuspendedTime: NoWork,
- latestPingedTime: NoWork,
- didError: false,
- pendingCommitExpirationTime: NoWork,
- finishedWork: null,
- context: null,
- pendingContext: null,
- hydrate,
- nextExpirationTimeToWorkOn: NoWork,
- expirationTime: NoWork,
- firstBatch: null,
- nextScheduledRoot: null,
- },
- render: (children: ReactNodeList, callback: ?() => mixed) => Work,
- legacy_renderSubtreeIntoContainer: (
- parentComponent: ?React$Component<any, any>,
- children: ReactNodeList,
- callback: ?() => mixed
- ) => Work,
- createBatch: () => Batch
- }
复制代码
在初始化 React 根容器对象 root 后, 调用 root.render 开始虚拟 DOM 的渲染过程.
- DOMRenderer.unbatchedUpdates(() => {
- if (parentComponent != null) {
- root.legacy_renderSubtreeIntoContainer(
- parentComponent,
- children,
- callback,
- );
- } else {
- root.render(children, callback);
- }
- });
复制代码
因代码量过大, 就不逐一贴出详细代码, 只贴出主要的函数的调用过程.
- root.render(children, callback) ->
- DOMRenderer.updateContainer(children, root, null, work._onCommit) ->
- 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)
复制代码
Fiber
Fiber 类型:
- type Fiber = {|
- tag: TypeOfWork,
- key: null | string,
- // The function/class/module associated with this fiber.
- type: any,
- return: Fiber | null,
- // Singly Linked List Tree Structure.
- child: Fiber | null,
- sibling: Fiber | null,
- index: number,
- ref: null | (((handle: mixed) => void) & {_stringRef: ?string}) | RefObject,
- memoizedProps: any, // The props used to create the output.
- updateQueue: UpdateQueue<any> | null,
- memoizedState: any,
- mode: TypeOfMode,
- effectTag: TypeOfSideEffect,
- nextEffect: Fiber | null,
- firstEffect: Fiber | null,
- lastEffect: Fiber | null,
- expirationTime: ExpirationTime,
- alternate: Fiber | null,
- actualDuration?: number,
- actualStartTime?: number,
- selfBaseTime?: number,
- treeBaseTime?: number,
- _debugID?: number,
- _debugSource?: Source | null,
- _debugOwner?: Fiber | null,
- _debugIsCurrentlyTiming?: boolean,
- |};
复制代码
beginWork
按以上函数调用过程, 我们来到 beginWork 函数, 它的作用主要是根据 Fiber 对象的 tag 来对组件进行 mount 或 update:
- function beginWork(
- current: Fiber | null,
- workInProgress: Fiber,
- renderExpirationTime: ExpirationTime,
- ): Fiber | null {
- if (enableProfilerTimer) {
- if (workInProgress.mode & ProfileMode) {
- markActualRenderTimeStarted(workInProgress);
- }
- }
- if (
- workInProgress.expirationTime === NoWork ||
- workInProgress.expirationTime> renderExpirationTime
- ) {
- return bailoutOnLowPriority(current, workInProgress);
- }
- // 根据组件类型来进行不同处理
- switch (workInProgress.tag) {
- case IndeterminateComponent:
- // 不确定的组件类型
- return mountIndeterminateComponent(
- current,
- workInProgress,
- renderExpirationTime,
- );
- case FunctionalComponent:
- // 函数类型的组件
- return updateFunctionalComponent(current, workInProgress);
- case ClassComponent:
- // 类类型的组件, 我们这次主要看这个
- return updateClassComponent(
- current,
- workInProgress,
- renderExpirationTime,
- );
- case HostRoot:
- return updateHostRoot(current, workInProgress, renderExpirationTime);
- case HostComponent:
- return updateHostComponent(current, workInProgress, renderExpirationTime);
- case HostText:
- return updateHostText(current, workInProgress);
- case TimeoutComponent:
- return updateTimeoutComponent(
- current,
- workInProgress,
- renderExpirationTime,
- );
- case HostPortal:
- return updatePortalComponent(
- current,
- workInProgress,
- renderExpirationTime,
- );
- case ForwardRef:
- return updateForwardRef(current, workInProgress);
- case Fragment:
- return updateFragment(current, workInProgress);
- case Mode:
- return updateMode(current, workInProgress);
- case Profiler:
- return updateProfiler(current, workInProgress);
- case ContextProvider:
- return updateContextProvider(
- current,
- workInProgress,
- renderExpirationTime,
- );
- case ContextConsumer:
- return updateContextConsumer(
- current,
- workInProgress,
- renderExpirationTime,
- );
- default:
- invariant(
- false,
- 'Unknown unit of work tag. This error is likely caused by a bug in' +
- 'React. Please file an issue.',
- );
- }
- }
复制代码
updateClassComponent
updateClassComponent 的作用是对未初始化的类组件进行初始化, 对已经初始化的组件更新重用
- function updateClassComponent(
- current: Fiber | null,
- workInProgress: Fiber,
- renderExpirationTime: ExpirationTime,
- ) {
- const hasContext = pushLegacyContextProvider(workInProgress);
- let shouldUpdate;
- if (current === null) {
- if (workInProgress.stateNode === null) {
- // 如果还没创建实例, 初始化
- constructClassInstance(
- workInProgress,
- workInProgress.pendingProps,
- renderExpirationTime,
- );
- mountClassInstance(workInProgress, renderExpirationTime);
- shouldUpdate = true;
- } else {
- // 如果已经创建实例, 则重用实例
- shouldUpdate = resumeMountClassInstance(
- workInProgress,
- renderExpirationTime,
- );
- }
- } else {
- shouldUpdate = updateClassInstance(
- current,
- workInProgress,
- renderExpirationTime,
- );
- }
- return finishClassComponent(
- current,
- workInProgress,
- shouldUpdate,
- hasContext,
- renderExpirationTime,
- );
- }
复制代码
constructClassInstance
实例化类组件:
- function constructClassInstance(
- workInProgress: Fiber,
- props: any,
- renderExpirationTime: ExpirationTime,
- ): any {
- const ctor = workInProgress.type; // 我们传进去的那个类
- const unmaskedContext = getUnmaskedContext(workInProgress);
- const needsContext = isContextConsumer(workInProgress);
- const context = needsContext
- ? getMaskedContext(workInProgress, unmaskedContext)
- : emptyContextObject;
- const instance = new ctor(props, context); // 创建实例
- const state = (workInProgress.memoizedState =
- instance.state !== null && instance.state !== undefined
- ? instance.state
- : null);
- adoptClassInstance(workInProgress, instance);
- if (needsContext) {
- cacheContext(workInProgress, unmaskedContext, context);
- }
- return instance;
- }
复制代码
- adoptClassInstance
- function adoptClassInstance(workInProgress: Fiber, instance: any): void {
- instance.updater = classComponentUpdater;
- workInProgress.stateNode = instance; // 将实例赋值给 stateNode 属性
- }
复制代码
mountClassInstance
下面的代码就有我们熟悉的 componentWillMount 生命周期出现啦, 不过新版 React 已经不建议使用它.
- function mountClassInstance(
- workInProgress: Fiber,
- renderExpirationTime: ExpirationTime,
- ): void {
- const ctor = workInProgress.type;
- const instance = workInProgress.stateNode;
- const props = workInProgress.pendingProps;
- const unmaskedContext = getUnmaskedContext(workInProgress);
- instance.props = props;
- instance.state = workInProgress.memoizedState;
- instance.refs = emptyRefsObject;
- instance.context = getMaskedContext(workInProgress, unmaskedContext);
- let updateQueue = workInProgress.updateQueue;
- if (updateQueue !== null) {
- processUpdateQueue(
- workInProgress,
- updateQueue,
- props,
- instance,
- renderExpirationTime,
- );
- instance.state = workInProgress.memoizedState;
- }
- const getDerivedStateFromProps = workInProgress.type.getDerivedStateFromProps;
- if (typeof getDerivedStateFromProps === 'function') {
- // React 新的生命周期函数
- applyDerivedStateFromProps(workInProgress, getDerivedStateFromProps, props);
- 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,
- props,
- instance,
- renderExpirationTime,
- );
- instance.state = workInProgress.memoizedState;
- }
- }
- if (typeof instance.componentDidMount === 'function') {
- workInProgress.effectTag |= Update;
- }
- }
复制代码
finishClassComponent
调用组件实例的 render 函数获取需渲染的子元素, 并把子元素进行处理为 Fiber 类型, 处理 state 和 props:
- function finishClassComponent(
- current: Fiber | null,
- workInProgress: Fiber,
- shouldUpdate: boolean,
- hasContext: boolean,
- renderExpirationTime: ExpirationTime,
- ) {
- markRef(current, workInProgress);
- const didCaptureError = (workInProgress.effectTag & DidCapture) !== NoEffect;
- if (!shouldUpdate && !didCaptureError) {
- if (hasContext) {
- invalidateContextProvider(workInProgress, false);
- }
- return bailoutOnAlreadyFinishedWork(current, workInProgress);
- }
- const ctor = workInProgress.type;
- const instance = workInProgress.stateNode;
- ReactCurrentOwner.current = workInProgress;
- let nextChildren;
- if (
- didCaptureError &&
- (!enableGetDerivedStateFromCatch ||
- typeof ctor.getDerivedStateFromCatch !== 'function')
- ) {
- nextChildren = null;
- if (enableProfilerTimer) {
- stopBaseRenderTimerIfRunning();
- }
- } else {
- if (__DEV__) {
- ...
- } else {
- // 调用 render 函数获取子元素
- nextChildren = instance.render();
- }
- }
- workInProgress.effectTag |= PerformedWork;
- if (didCaptureError) {
- reconcileChildrenAtExpirationTime(
- current,
- workInProgress,
- null,
- renderExpirationTime,
- );
- workInProgress.child = null;
- }
- // 把子元素转换为 Fiber 类型
- // 如果子元素数量大于一 (即为数组) 的时候,
- // 返回第一个 Fiber 类型子元素
- reconcileChildrenAtExpirationTime(
- current,
- workInProgress,
- nextChildren,
- renderExpirationTime,
- );
- // 处理 state
- memoizeState(workInProgress, instance.state);
- // 处理 props
- memoizeProps(workInProgress, instance.props);
- if (hasContext) {
- invalidateContextProvider(workInProgress, true);
- }
- // 返回 Fiber 类型的子元素给 beginWork 函数,
- // 一直返回到 workLoop 函数(看上面的调用过程)
- return workInProgress.child;
- }
复制代码
workLoop
我们再回头看下 workLoop 和 performUnitOfWork 函数(看上面的函数调用过程), 当 benginWork 对不同类型组件完成相应处理返回子元素后, workLoop 继续通过 performUnitOfWork 来调用 benginWork 对子元素进行处理, 从而遍历虚拟 DOM 树:
- function workLoop(isYieldy) {
- if (!isYieldy) {
- while (nextUnitOfWork !== null) {
- // 遍历整棵虚拟 DOM 树
- nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
- }
- } else {
- while (nextUnitOfWork !== null && !shouldYield()) {
- // 遍历整棵虚拟 DOM 树
- nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
- }
- if (enableProfilerTimer) {
- pauseActualRenderTimerIfRunning();
- }
- }
- }
复制代码
performUnitOfWork
这个函数很接近把虚拟 DOM 转换为真实 DOM 的代码啦, 当遍历完成一颗虚拟 DOM 的子树后(beginWork 返回 null, 即已没有子元素), 调用 completeUnitOfWork 函数开始转换:
- function performUnitOfWork(workInProgress: Fiber): Fiber | null {
- const current = workInProgress.alternate;
- startWorkTimer(workInProgress);
- let next;
- if (enableProfilerTimer) {
- if (workInProgress.mode & ProfileMode) {
- startBaseRenderTimer();
- }
- next = beginWork(current, workInProgress, nextRenderExpirationTime);
- if (workInProgress.mode & ProfileMode) {
- recordElapsedBaseRenderTimeIfRunning(workInProgress);
- stopBaseRenderTimerIfRunning();
- }
- } 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 current = workInProgress.alternate;
- const returnFiber = workInProgress.return;
- const siblingFiber = workInProgress.sibling;
- if ((workInProgress.effectTag & Incomplete) === NoEffect) {
- // 调用 completeWork 转换虚拟 DOM
- let next = completeWork(
- current,
- workInProgress,
- nextRenderExpirationTime,
- );
- stopWorkTimer(workInProgress);
- resetExpirationTime(workInProgress, nextRenderExpirationTime);
- if (next !== null) {
- stopWorkTimer(workInProgress);
- return next;
- }
- // 处理完当前节点后
- if (siblingFiber !== null) {
- // 如果有兄弟节点, 则将其返回
- return siblingFiber;
- } else if (returnFiber !== null) {
- // 没有兄弟节点而有父节点, 则处理父节点
- workInProgress = returnFiber;
- continue;
- } else {
- return null;
- }
- } else {
- ...
- }
- return null;
- }
复制代码
completeWork
根据 Fiber 的类型进行处理和抛出错误, 我们主要看 HostComponent 类型. 对 HostComponent 类型的处理主要是更新属性, 然后通过 createInstance 创建 DOM 节点, 并添加进父节点.
- function completeWork(
- current: Fiber | null,
- workInProgress: Fiber,
- renderExpirationTime: ExpirationTime,
- ): Fiber | null {
- const newProps = workInProgress.pendingProps;
- if (enableProfilerTimer) {
- if (workInProgress.mode & ProfileMode) {
- recordElapsedActualRenderTime(workInProgress);
- }
- }
- switch (workInProgress.tag) {
- ...
- case HostComponent: {
- popHostContext(workInProgress);
- const rootContainerInstance = getRootHostContainer();
- const type = workInProgress.type;
- if (current !== null && workInProgress.stateNode != null) {
- // 更新属性
- const oldProps = current.memoizedProps;
- const instance: Instance = workInProgress.stateNode;
- const currentHostContext = getHostContext();
- const updatePayload = prepareUpdate(
- instance,
- type,
- oldProps,
- newProps,
- rootContainerInstance,
- currentHostContext,
- );
- updateHostComponent(
- current,
- workInProgress,
- updatePayload,
- type,
- oldProps,
- newProps,
- rootContainerInstance,
- currentHostContext,
- );
- if (current.ref !== workInProgress.ref) {
- markRef(workInProgress);
- }
- } else {
- if (!newProps) {
- ...
- return null;
- }
- const currentHostContext = getHostContext();
- let wasHydrated = popHydrationState(workInProgress);
- if (wasHydrated) {
- if (
- prepareToHydrateHostInstance(
- workInProgress,
- rootContainerInstance,
- currentHostContext,
- )
- ) {
- markUpdate(workInProgress);
- }
- } else {
- // 创建并返回 DOM 元素
- let instance = createInstance(
- type,
- newProps,
- rootContainerInstance,
- currentHostContext,
- workInProgress,
- );
- // 将此 DOM 节点添加进父节点
- appendAllChildren(instance, workInProgress);
- if (
- finalizeInitialChildren(
- instance,
- type,
- newProps,
- rootContainerInstance,
- currentHostContext,
- )
- ) {
- markUpdate(workInProgress);
- }
- workInProgress.stateNode = instance;
- }
- if (workInProgress.ref !== null) {
- // If there is a ref on a host node we need to schedule a callback
- markRef(workInProgress);
- }
- }
- return null;
- }
- ...
- }
- }
复制代码
- createInstance
- function createInstance(
- type: string,
- props: Props,
- rootContainerInstance: Container,
- hostContext: HostContext,
- internalInstanceHandle: Object,
- ): Instance {
- let parentNamespace: string;
- if (__DEV__) {
- ...
- } else {
- parentNamespace = ((hostContext: any): HostContextProd);
- }
- const domElement: Instance = createElement(
- type,
- props,
- rootContainerInstance,
- parentNamespace,
- );
- precacheFiberNode(internalInstanceHandle, domElement);
- updateFiberProps(domElement, props);
- return domElement;
- }
复制代码
- createElement
- function createElement(
- type: string,
- props: Object,
- rootContainerElement: Element | Document,
- parentNamespace: string,
- ): Element {
- let isCustomComponentTag;
- const ownerDocument: Document = getOwnerDocumentFromRootContainer(
- rootContainerElement,
- );
- let domElement: Element;
- let namespaceURI = parentNamespace;
- if (namespaceURI === HTML_NAMESPACE) {
- namespaceURI = getIntrinsicNamespace(type);
- }
- if (namespaceURI === HTML_NAMESPACE) {
- if (type === 'script') {
- const div = ownerDocument.createElement('div');
- div.innerHTML = '<script><' + '/script>'; // eslint-disable-line
- const firstChild = ((div.firstChild: any): HTMLScriptElement);
- domElement = div.removeChild(firstChild);
- } else if (typeof props.is === 'string') {
- domElement = ownerDocument.createElement(type, {is: props.is});
- } else {
- domElement = ownerDocument.createElement(type);
- }
- } else {
- domElement = ownerDocument.createElementNS(namespaceURI, type);
- }
- return domElement;
- }
复制代码
总结
到此为止,"React 是如何将虚拟 DOM 转换为真实 DOM" 的问题就被解决啦. 我们来回顾一下.
首先我们要通过 React.createElement 函数来将我们定义好的组件进行转换为 React 元素
将创建好的 React 元素通过调用 ReactDom.render 来进行渲染
ReactDom.render 调用后先创建根对象 root, 然后调用 root.render
然后经过若干函数调用, 来到 workLoop 函数, 它将遍历虚拟 DOM 树, 将下一个需要处理的虚拟 DOM 传给 performUnitOfWork,performUnitOfWork 再将虚拟 DOM 传给 beginWork 后, beginWork 根据虚拟 DOM 的类型不同进行相应处理, 并对儿子进行处理为 Fiber 类型, 为 Fiber 类型虚拟 DOM 添加父节点, 兄弟节点等待细节, 已方便遍历树.
beginWork 处理完后返回需要处理的子元素再继续处理, 直到没有子元素(即返回 null), 此时 performUnitOfWork 调用 completeUnitOfWork 处理这颗虚拟 DOM 子树, 将其转换为真实 DOM.
最后所有的虚拟 DOM 都将转为真实 DOM.
除此之外还有很多细节等待我们去研究, 比如说 React 是如何更新和删除虚拟 DOM 的? setState 的异步实现原理是怎样的? 以后再写文和大家分析和探讨.
来源: https://juejin.im/post/5b4afa526fb9a04fd34387b0