-
Notifications
You must be signed in to change notification settings - Fork 45
解决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
base: master
Are you sure you want to change the base?
解决findDOMNode 过期警告问题 #215
Changes from all commits
356a547
1080adb
e4eec9e
b757319
2638094
d7f9ac4
126f66e
551e612
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
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; | ||
} | ||
} | ||
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; |
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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion MutationObserver 回调中未对 MutationRecord 的 target 做更多限制。 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 这里只关系节点的移除和新增 属性变化不关系 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @robertpanvip,明白了,感谢你的说明。仅基于节点的增删场景即可满足当前需求,如果以后需要处理属性或其它更复杂的变动逻辑,我们可以再进行细化。 ✏️ Learnings added
|
||
// 监听子节点的变动(但不监听属性变化) | ||
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; // 导出组件 |
There was a problem hiding this comment.
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
可能导致其他部分还未执行的逻辑出现无法创建注释节点的情况。请确认在创建注释节点的完整生命周期内,是否有必要维持重写状态以兼容后续操作或复合调用场景。