HostComponent
1 )概述
- 在
completeWork
当中,我们需要对HostComponent
的一些操作有哪些?- 首先在一次更新而不是初次渲染的情况下
- 需要去
diffProperties
来计算,需要更新的内容 - 也就是在 vdom 中去进行一个对比来判断这一个节点是否需要真的去更新它
- 以此来最低程度的去更新整个 dom 的一个过程
- 对于不同 dom property,它有一些不同的处理方式
2 )源码
定位到 packages/react-reconciler/src/ReactFiberCompleteWork.js#L581
找到 case HostComponent
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 {// 第一次挂载渲染// 没有 props 说明有问题,提示if (!newProps) {invariant(workInProgress.stateNode !== null,'We must have new props for new mounts. This error is likely ' +'caused by a bug in React. Please file an issue.',);// This can happen when we abort work.break;}// 跳过const currentHostContext = getHostContext();// TODO: Move createInstance to beginWork and keep it on a context// "stack" as the parent. Then append children as we go in beginWork// or completeWork depending on we want to add then top->down or// bottom->up. Top->down is faster in IE11.let wasHydrated = popHydrationState(workInProgress);if (wasHydrated) {// TODO: Move this and createInstance step into the beginPhase// to consolidate.if (prepareToHydrateHostInstance(workInProgress,rootContainerInstance,currentHostContext,)) {// If changes to the hydrated node needs to be applied at the// commit-phase we mark this as such.markUpdate(workInProgress);}} else {// 第一次挂载// 注意,这里,创建 element的过程let instance = createInstance(type,newProps,rootContainerInstance,currentHostContext,workInProgress,);// 第一次渲染,要添加子节点appendAllChildren(instance, workInProgress, false, false);// Certain renderers require commit-time effects for initial mount.// (eg DOM renderer supports auto-focus for certain elements).// Make sure such renderers get scheduled for later work.if (// 主要功能: 对dom节点上面的可能的事件监听,需要初始化整个react的事件体系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 callbackmarkRef(workInProgress);}}break;
}
- 进入
createInstance
// packages/react-dom/src/client/ReactDOMHostConfig.js#L167 // 这个方法就是创建节点的过程 export function createInstance(type: string,props: Props,rootContainerInstance: Container,hostContext: HostContext,internalInstanceHandle: Object, ): Instance {let parentNamespace: string;if (__DEV__) {// TODO: take namespace into account when validating.const hostContextDev = ((hostContext: any): HostContextDev);validateDOMNesting(type, null, hostContextDev.ancestorInfo);if (typeof props.children === 'string' ||typeof props.children === 'number') {const string = '' + props.children;const ownAncestorInfo = updatedAncestorInfo(hostContextDev.ancestorInfo,type,);validateDOMNesting(null, string, ownAncestorInfo);}parentNamespace = hostContextDev.namespace;} else {parentNamespace = ((hostContext: any): HostContextProd);}// 主要在这里const domElement: Instance = createElement(type,props,rootContainerInstance,parentNamespace,);// 后面会进入这个方法precacheFiberNode(internalInstanceHandle, domElement);// 同上updateFiberProps(domElement, props);return domElement; }
- 进入
createElement
// 不展开这个方法里面调用的内容 export function createElement(type: string,props: Object,rootContainerElement: Element | Document,parentNamespace: string, ): Element {let isCustomComponentTag;// We create tags in the namespace of their parent container, except HTML// tags get no namespace.// 先要获取 document, 先要通过 document 的api来创建const ownerDocument: Document = getOwnerDocumentFromRootContainer(rootContainerElement,);let domElement: Element;let namespaceURI = parentNamespace;// 在React中有区分,不同节点类型,都会有一个定义,比如html普通节点, svg节点等if (namespaceURI === HTML_NAMESPACE) {namespaceURI = getIntrinsicNamespace(type);}// 如果是 html 类型节点,做一些特殊处理if (namespaceURI === HTML_NAMESPACE) {if (__DEV__) {isCustomComponentTag = isCustomComponent(type, props);// Should this check be gated by parent namespace? Not sure we want to// allow <SVG> or <mATH>.warning(isCustomComponentTag || type === type.toLowerCase(),'<%s /> is using incorrect casing. ' +'Use PascalCase for React components, ' +'or lowercase for HTML elements.',type,);}// 对script节点进行特殊处理if (type === 'script') {// Create the script via .innerHTML so its "parser-inserted" flag is// set to true and it does not executeconst div = ownerDocument.createElement('div');div.innerHTML = '<script><' + '/script>'; // eslint-disable-line// This is guaranteed to yield a script element.const firstChild = ((div.firstChild: any): HTMLScriptElement);domElement = div.removeChild(firstChild);} else if (typeof props.is === 'string') {// $FlowIssue `createElement` should be updated for Web ComponentsdomElement = ownerDocument.createElement(type, {is: props.is});} else {// Separate else branch instead of using `props.is || undefined` above because of a Firefox bug.// See discussion in https://github.com/facebook/react/pull/6896// and discussion in https://bugzilla.mozilla.org/show_bug.cgi?id=1276240domElement = ownerDocument.createElement(type);// Normally attributes are assigned in `setInitialDOMProperties`, however the `multiple`// attribute on `select`s needs to be added before `option`s are inserted. This prevents// a bug where the `select` does not scroll to the correct option because singular// `select` elements automatically pick the first item.// See https://github.com/facebook/react/issues/13222if (type === 'select' && props.multiple) {const node = ((domElement: any): HTMLSelectElement);node.multiple = true;}}} else {domElement = ownerDocument.createElementNS(namespaceURI, type);}if (__DEV__) {if (namespaceURI === HTML_NAMESPACE) {if (!isCustomComponentTag &&Object.prototype.toString.call(domElement) ==='[object HTMLUnknownElement]' &&!Object.prototype.hasOwnProperty.call(warnedUnknownTags, type)) {warnedUnknownTags[type] = true;warning(false,'The tag <%s> is unrecognized in this browser. ' +'If you meant to render a React component, start its name with ' +'an uppercase letter.',type,);}}}return domElement; }
- 进入
precacheFiberNode
const internalInstanceKey = '__reactInternalInstance$' + randomKey;// 就是在node上挂载上述属性 // hostInst, node 对应着 internalInstanceHandle, domElement // 相当于在 对应dom节点上,通过 internalInstanceKey 这个 key // 去创建一个指向 fiber 对象的引用 // 后期,我们想要从dom对象上获取对应的 fiber就可以方便获取 export function precacheFiberNode(hostInst, node) {node[internalInstanceKey] = hostInst; }
- 进入
updateFiberProps
const internalEventHandlersKey = '__reactEventHandlers$' + randomKey;// 两个参数 node 和 props, 对应着 domElement, props // 在 domElement 上用一个key 存储 props // props 会对应到上面 domElement 的 attribute, 所以,有对应关系,方便后期取用 export function updateFiberProps(node, props) {node[internalEventHandlersKey] = props; }
- 进入
appendAllChildren
appendAllChildren = function(parent: Instance,workInProgress: Fiber,needsVisibilityToggle: boolean,isHidden: boolean, ) {// We only have the top Fiber that was created but we need recurse down its// children to find all the terminal nodes.let node = workInProgress.child;// 这个循环的意义:对当前节点下寻找第一层的dom节点或text节点,不会append 嵌套(继续下一层)的dom节点// 这样做的原因,因为对每个dom节点都会有这样一个执行 completeWork 的阶段// 也就是当前层只找自己下一层的dom或text, 一层一层的向下找,不会存在忽略的问题while (node !== null) {if (node.tag === HostComponent || node.tag === HostText) {// 注意这里,挂载节点 // parent 是刚刚创建的 instance// node.stateNode 是当前节点子节点对应的Fiber对象// 如果说,它的子节点那一层上面发现了原生dom节点或者文本节点,就把它挂载到节点上面appendInitialChild(parent, node.stateNode);} else if (node.tag === HostPortal) {// If we have a portal child, then we don't want to traverse// down its children. Instead, we'll get insertions from each child in// the portal directly.} else if (node.child !== null) {// 向下遍历node.child.return = node;node = node.child;continue;}// 这里直接 returnif (node === workInProgress) {return;}// 没有兄弟节点,向上遍历while (node.sibling === null) {if (node.return === null || node.return === workInProgress) {return;}node = node.return;}// 这个本来如此,对sibling进行挂载操作node.sibling.return = node.return;// 循环找 sibling 节点node = node.sibling;} };
- 进入
appendInitialChild
// 这个函数执行的就是 dom 节点的 appendChild方法 export function appendInitialChild(parentInstance: Instance,child: Instance | TextInstance, ): void {parentInstance.appendChild(child); }
- 以上,相关注释都在代码内,不再单独拿出详解
- 里面没涉及到的一些代码,都比较简单,不再赘述
- 主要关注上面
appendAllChildren
的算法, 内部有注释