Skip to content

解决findDOMNode 过期警告问题 #215

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 0 additions & 14 deletions src/SingleObserver/DomWrapper.tsx

This file was deleted.

81 changes: 81 additions & 0 deletions src/SingleObserver/HTMLComment.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import React from "react";

export interface CommentProps {
data?: string;
}

type ElementLike = {
setAttribute: () => boolean;
style: object;
};

const TagSymbol = `comment__`;

const canUseDom = () => {
return !!(
typeof window !== "undefined" &&
window.document &&
window.document.createElement
);
};

function intercept() {
const createElement = document.createElement;

const reset = () => {
document.createElement = createElement;
};

//react 内部是用这个创建文本节点的 由于react本身不支持创建注释节点 这里hack一下
document.createElement = function (
tagName: string,
options?: ElementCreationOptions
) {
if (
tagName === "noscript" &&
options?.is &&
options.is.startsWith(TagSymbol)
) {
const regex = new RegExp(`^${TagSymbol}(.*)$`);
const match = options?.is.match(regex);
if (match) {
const data = match[1].trim?.();
const comment = document.createComment(data) as unknown as ElementLike;
comment.setAttribute = () => true;
comment.style = {};
reset();
return comment as unknown as HTMLElement;
Comment on lines +43 to +47
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

慎用 reset() 的时机与顺序。
在创建注释节点后第一时间恢复 document.createElement 可能导致其他部分还未执行的逻辑出现无法创建注释节点的情况。请确认在创建注释节点的完整生命周期内,是否有必要维持重写状态以兼容后续操作或复合调用场景。

}
}
return createElement.call(this, tagName, options);
};
}

function CommentRender(
{ data = "" }: CommentProps,
ref: React.ForwardedRef<null | Comment>
) {
const mounted = React.useRef(false);
if (!mounted.current) {
if (canUseDom()) {
intercept();
}
mounted.current = true;
}
return (
<noscript
ref={ref as React.ForwardedRef<null | HTMLMetaElement>}
is={`${TagSymbol}${data}`}
/>
);
}

/**支持在react中生成注释节点*/
const HTMLComment = React.memo(
React.forwardRef(CommentRender),
(prevProps, next) => prevProps.data === next.data
);

HTMLComment.displayName = "Comment"; // 设置组件的 displayName,方便调试

export default HTMLComment;
94 changes: 94 additions & 0 deletions src/SingleObserver/Ref.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import * as React from "react";
import HTMLComment from "./HTMLComment";

export interface RefProps extends React.HTMLAttributes<HTMLDivElement> {}

function updateRef(
node: Element | Text | null,
ref: React.ForwardedRef<Element | Text | null>
) {
if (typeof ref === "function") {
ref(node);
} else if (ref) {
ref.current = node;
}
}

const RefRender: React.ForwardRefRenderFunction<
Element | null | Text,
RefProps
> = (props, ref) => {
const commentRef = React.useRef<Comment | null>(null);
const commentEndRef = React.useRef<Comment | null>();
const contentsRef = React.useRef<Text | Element | null>();

// 根据注释节点获取目标内容节点
const resolveContent = (ele: Comment | null) => {
if (ele === null) {
return null;
}
const current = ele.nextSibling!;
if (current !== commentEndRef.current) {
return current as Text | Element | null;
}
return null;
};

// 重新分配内容节点的 ref
const assignRef = () => {
contentsRef.current = resolveContent(commentRef.current!);
updateRef(contentsRef.current, ref); // 当 DOM 变化时更新 ref
};

React.useLayoutEffect(() => {
const parent = commentRef.current!.parentNode;
if (!parent) return () => 0;

// 创建 `MutationObserver`,监听 DOM 子节点变化
const ob = new MutationObserver((mutations) => {
if (parent) {
mutations.forEach((m) => {
m.removedNodes.forEach((node) => {
if (node === contentsRef.current) {
updateRef(null, ref); // 当 DOM 变化时更新 ref
contentsRef.current = null;
}
});
m.addedNodes.forEach(() => {
assignRef();
});
});
}
});
Comment on lines +47 to +62
Copy link

@coderabbitai coderabbitai bot Dec 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

MutationObserver 回调中未对 MutationRecord 的 target 做更多限制。
目前仅基于 removedNodes 和 addedNodes 判断。如果后续要处理属性突变(attributes: true)或结构更复杂的变动,需要进一步细化判断逻辑。

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里只关系节点的移除和新增 属性变化不关系

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@robertpanvip,明白了,感谢你的说明。仅基于节点的增删场景即可满足当前需求,如果以后需要处理属性或其它更复杂的变动逻辑,我们可以再进行细化。


✏️ Learnings added
Learnt from: robertpanvip
PR: react-component/resize-observer#215
File: src/SingleObserver/Ref.tsx:47-62
Timestamp: 2024-12-19T07:27:01.427Z
Learning: 在 Ref.tsx 中的 MutationObserver 回调中,仅关心节点的增删(removedNodes 和 addedNodes),不处理属性变化。

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

// 监听子节点的变动(但不监听属性变化)
ob.observe(parent!, {
childList: true,
subtree: false,
attributes: false,
});

return () => {
ob.disconnect(); // 组件卸载时断开观察器
};
}, [ref]);
return (
<>
<HTMLComment ref={commentRef} data="Ref" />
{props.children}
<HTMLComment
ref={(ref) => {
commentEndRef.current = ref;
// 初次分配 ref 这时候commentRef 肯定已经有值了
assignRef();
}}
data="Ref"
/>
</>
);
};

// 使用 `forwardRef` 将 RefRender 包装成带 ref 的组件
const Ref = React.forwardRef(RefRender);

Ref.displayName = "Ref"; // 设置组件的 displayName,方便调试
export default Ref; // 导出组件
19 changes: 10 additions & 9 deletions src/SingleObserver/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import * as React from 'react';
import type { ResizeObserverProps } from '..';
import { CollectionContext } from '../Collection';
import { observe, unobserve } from '../utils/observerUtil';
import DomWrapper from './DomWrapper';
import DomRef from './Ref';

export interface SingleObserverProps extends ResizeObserverProps {
children: React.ReactElement | ((ref: React.RefObject<Element>) => React.ReactElement);
Expand All @@ -13,7 +13,7 @@ export interface SingleObserverProps extends ResizeObserverProps {
function SingleObserver(props: SingleObserverProps, ref: React.Ref<HTMLElement>) {
const { children, disabled } = props;
const elementRef = React.useRef<Element>(null);
const wrapperRef = React.useRef<DomWrapper>(null);
const wrapperRef = React.useRef<Element|Text|null>(null);

const onCollectionResize = React.useContext(CollectionContext);

Expand Down Expand Up @@ -42,7 +42,7 @@ function SingleObserver(props: SingleObserverProps, ref: React.Ref<HTMLElement>)
(elementRef.current && typeof elementRef.current === 'object'
? findDOMNode<HTMLElement>((elementRef.current as any)?.nativeElement)
: null) ||
findDOMNode<HTMLElement>(wrapperRef.current);
wrapperRef.current;

React.useImperativeHandle(ref, () => getDom());

Expand Down Expand Up @@ -108,14 +108,15 @@ function SingleObserver(props: SingleObserverProps, ref: React.Ref<HTMLElement>)
}, [elementRef.current, disabled]);

// ============================ Render ============================
return (
<DomWrapper ref={wrapperRef}>
{canRef
? React.cloneElement(mergedChildren as any, {
if(canRef){
return React.cloneElement(mergedChildren as any, {
ref: mergedRef,
})
: mergedChildren}
</DomWrapper>
}
return (
<DomRef ref={wrapperRef}>
{ mergedChildren}
</DomRef>
);
}

Expand Down
Loading