updateHostComponent
1 )概述
- 在
completeWork 阶段的HostComponent 处理,继续前文所述 - 在更新的逻辑里面,调用了
updateHostComponent - 进行前后props对应的dom的attributes变化的对比情况
- 这个方法也是根据不同环境来定义的,我们这里只专注于 react-dom 环境
2 )源码
定位到 packages/react-reconciler/src/ReactFiberCompleteWork.js#L586
这里是
updateHostComponent( current, workInProgress, type, newProps, rootContainerInstance, ); if (current.ref !== workInProgress.ref) { markRef(workInProgress); }
接着,进入其定义
updateHostComponent = function( current: Fiber, workInProgress: Fiber, type: Type, newProps: Props, rootContainerInstance: Container, ) { // If we have an alternate, that means this is an update and we need to // schedule a side-effect to do the updates. const oldProps = current.memoizedProps; // 全等对象,没有变化过,直接 return if (oldProps === newProps) { // In mutation mode, this is sufficient for a bailout because // we won't touch this node even if children changed. return; } // If we get updated because one of our children updated, we don't // have newProps so we'll have to reuse them. // TODO: Split the update API as separate for the props vs. children. // Even better would be if children weren't special cased at all tho. // 获取 dom 和 context const instance: Instance = workInProgress.stateNode; const currentHostContext = getHostContext(); // TODO: Experiencing an error where oldProps is null. Suggests a host // component is hitting the resume path. Figure out why. Possibly // related to `hidden`. // 注意这里, const updatePayload = prepareUpdate( instance, type, oldProps, newProps, rootContainerInstance, currentHostContext, ); // TODO: Type this specific to this type of component. workInProgress.updateQueue = (updatePayload: any); // If the update payload indicates that there is a change or if there // is a new ref we mark this as an update. All the work is done in commitWork. if (updatePayload) { markUpdate(workInProgress); } };
- 进入
prepareUpdate // 忽略 DEV 判断,这里直接 return 了 diffProperties 的调用 export function prepareUpdate( domElement: Instance, type: string, oldProps: Props, newProps: Props, rootContainerInstance: Container, hostContext: HostContext, ): null | Array<mixed> { if (__DEV__) { const hostContextDev = ((hostContext: any): HostContextDev); if ( typeof newProps.children !== typeof oldProps.children && (typeof newProps.children === 'string' || typeof newProps.children === 'number') ) { const string = '' + newProps.children; const ownAncestorInfo = updatedAncestorInfo( hostContextDev.ancestorInfo, type, ); validateDOMNesting(null, string, ownAncestorInfo); } } return diffProperties( domElement, type, oldProps, newProps, rootContainerInstance, ); }
- 进入
diffProperties // 这个方法和前文中的 setInitialProperties 有一定的类似, 根据不同的节点做不同的操作 // Calculate the diff between the two objects. export function diffProperties( domElement: Element, tag: string, lastRawProps: Object, nextRawProps: Object, rootContainerElement: Element | Document, ): null | Array<mixed> { if (__DEV__) { validatePropertiesInDevelopment(tag, nextRawProps); } let updatePayload: null | Array<any> = null; let lastProps: Object; let nextProps: Object; switch (tag) { case 'input': lastProps = ReactDOMInput.getHostProps(domElement, lastRawProps); nextProps = ReactDOMInput.getHostProps(domElement, nextRawProps); updatePayload = []; break; case 'option': lastProps = ReactDOMOption.getHostProps(domElement, lastRawProps); nextProps = ReactDOMOption.getHostProps(domElement, nextRawProps); updatePayload = []; break; case 'select': lastProps = ReactDOMSelect.getHostProps(domElement, lastRawProps); nextProps = ReactDOMSelect.getHostProps(domElement, nextRawProps); updatePayload = []; break; case 'textarea': lastProps = ReactDOMTextarea.getHostProps(domElement, lastRawProps); nextProps = ReactDOMTextarea.getHostProps(domElement, nextRawProps); updatePayload = []; break; default: lastProps = lastRawProps; nextProps = nextRawProps; if ( typeof lastProps.onClick !== 'function' && typeof nextProps.onClick === 'function' ) { // TODO: This cast may not be sound for SVG, MathML or custom elements. trapClickOnNonInteractiveElement(((domElement: any): HTMLElement)); } break; } assertValidProps(tag, nextProps); let propKey; let styleName; let styleUpdates = null; // 那么接下去会有两个循环 // 第一个循环是循环 lastProps, 就是上一次渲染的结果产生的props // 这是第一次大循环: 找到在旧的props上有的属性,在新的props上没有的,也就是找到前后对比中删除的props for (propKey in lastProps) { // 对 新旧 props 进行循环判断: 新props有key 或 旧props没有key 或 旧props为null 则跳过此key if ( nextProps.hasOwnProperty(propKey) || !lastProps.hasOwnProperty(propKey) || lastProps[propKey] == null ) { // 符合这个条件,不是被删除的属性,不会加到最后的 updatePayload continue; } // 对符合被删除属性来说,进行删除 style 的操作 if (propKey === STYLE) { const lastStyle = lastProps[propKey]; for (styleName in lastStyle) { if (lastStyle.hasOwnProperty(styleName)) { if (!styleUpdates) { styleUpdates = {}; } styleUpdates[styleName] = ''; } } // 后面的其他判断,都没有做什么事情,可以跳过了 } else if (propKey === DANGEROUSLY_SET_INNER_HTML || propKey === CHILDREN) { // Noop. This is handled by the clear text mechanism. } else if ( propKey === SUPPRESS_CONTENT_EDITABLE_WARNING || propKey === SUPPRESS_HYDRATION_WARNING ) { // Noop } else if (propKey === AUTOFOCUS) { // Noop. It doesn't work on updates anyway. } else if (registrationNameModules.hasOwnProperty(propKey)) { // This is a special case. If any listener updates we need to ensure // that the "current" fiber pointer gets updated so we need a commit // to update this element. if (!updatePayload) { updatePayload = []; } } else { // For all other deleted properties we add it to the queue. We use // the whitelist in the commit phase instead. // 注意这里,加入 updatePayload 的方式是 一次加入 key, value 这2个 (updatePayload = updatePayload || []).push(propKey, null); } } // 这是第二次大循环, nextProps 就是新的props for (propKey in nextProps) { // 处理 新旧 props const nextProp = nextProps[propKey]; const lastProp = lastProps != null ? lastProps[propKey] : undefined; // 新props没有这个key(这个key在原型链上,不在自己),或 两个 prop 相同,或两个prop 都为 null if ( !nextProps.hasOwnProperty(propKey) || nextProp === lastProp || (nextProp == null && lastProp == null) ) { // 符合上述条件跳过 continue; } // 对于 style 类型的 props 的处理, 判断每个 style 的key是否有变化 // 下面会有两个循环来处理 if (propKey === STYLE) { if (__DEV__) { if (nextProp) { // Freeze the next style object so that we can assume it won't be // mutated. We have already warned for this in the past. Object.freeze(nextProp); } } if (lastProp) { // Unset styles on `lastProp` but not on `nextProp`. for (styleName in lastProp) { if ( lastProp.hasOwnProperty(styleName) && (!nextProp || !nextProp.hasOwnProperty(styleName)) ) { if (!styleUpdates) { styleUpdates = {}; } styleUpdates[styleName] = ''; } } // Update styles that changed since `lastProp`. for (styleName in nextProp) { if ( nextProp.hasOwnProperty(styleName) && lastProp[styleName] !== nextProp[styleName] ) { if (!styleUpdates) { styleUpdates = {}; } styleUpdates[styleName] = nextProp[styleName]; } } } else { // Relies on `updateStylesByID` not mutating `styleUpdates`. if (!styleUpdates) { if (!updatePayload) { updatePayload = []; } updatePayload.push(propKey, styleUpdates); } styleUpdates = nextProp; } } else if (propKey === DANGEROUSLY_SET_INNER_HTML) { const nextHtml = nextProp ? nextProp[HTML] : undefined; const lastHtml = lastProp ? lastProp[HTML] : undefined; if (nextHtml != null) { // 两个html不相等则加入 if (lastHtml !== nextHtml) { (updatePayload = updatePayload || []).push(propKey, '' + nextHtml); } } else { // TODO: It might be too late to clear this if we have children // inserted already. } } else if (propKey === CHILDREN) { // 对于 children 的处理 if ( lastProp !== nextProp && (typeof nextProp === 'string' || typeof nextProp === 'number') ) { // 不同则加入 (updatePayload = updatePayload || []).push(propKey, '' + nextProp); } } else if ( propKey === SUPPRESS_CONTENT_EDITABLE_WARNING || propKey === SUPPRESS_HYDRATION_WARNING ) { // Noop } else if (registrationNameModules.hasOwnProperty(propKey)) { // 这里是事件绑定相关的处理 if (nextProp != null) { // We eagerly listen to this even though we haven't committed yet. if (__DEV__ && typeof nextProp !== 'function') { warnForInvalidEventListener(propKey, nextProp); } ensureListeningTo(rootContainerElement, propKey); } if (!updatePayload && lastProp !== nextProp) { // This is a special case. If any listener updates we need to ensure // that the "current" props pointer gets updated so we need a commit // to update this element. updatePayload = []; } } else { // For any other property we always add it to the queue and then we // filter it out using the whitelist during the commit. // 上面都没找到,这个属性是新增属性,直接加入 (updatePayload = updatePayload || []).push(propKey, nextProp); } } // 最终处理加入 styleUpdates if (styleUpdates) { (updatePayload = updatePayload || []).push(STYLE, styleUpdates); } // 最终返回 return updatePayload; }
- 在
diffProperties 里的updatePayload 一开始是一个空的数组,并且最终被return - 对于 completeWork 来说,拿到的
updatePayload ,就是diffProperties 和prepareUpdate 返回过来的内容 - 接下去,会有一个判断
if (updatePayload) {} 也就是说空数组也是符合这个判断条件的, 也会执行markUpdate - 上述标记之后,在后续的 commit 的时候进行对应的操作
- 进入
- 以上是
updateHostComponent 的过程- 具体细节,参考代码里标注的中文文档和源码
- vdom其实就是这些东西,通过props来进行 attributes 的判断和处理变化的过程
- 主要是 整个 diffProperties 的处理