1 )概述
completeWork
当中,我们需要对 HostComponent
的一些操作有哪些?
diffProperties
来计算,需要更新的内容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 callback
markRef(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 execute
const 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 Components
domElement = 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=1276240
domElement = 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/13222
if (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;
}
// 这里直接 return
if (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
的算法, 内部有注释