From 356a5477deeaa109ce82c32a1328e2a67ad64f68 Mon Sep 17 00:00:00 2001 From: robertpanvip Date: Wed, 18 Dec 2024 17:41:50 +0800 Subject: [PATCH 1/7] Create Ref --- src/SingleObserver/Ref | 94 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 src/SingleObserver/Ref diff --git a/src/SingleObserver/Ref b/src/SingleObserver/Ref new file mode 100644 index 0000000..4d75a97 --- /dev/null +++ b/src/SingleObserver/Ref @@ -0,0 +1,94 @@ +import * as React from "react"; +import HTMLComment from "./HTMLComment"; + +export interface RefProps extends React.HTMLAttributes {} + +function updateRef( + node: Element | Text | null, + ref: React.ForwardedRef +) { + 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(null); + const commentEndRef = React.useRef(); + const contentsRef = React.useRef(); + + // 根据注释节点获取目标内容节点 + 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(); + }); + }); + } + }); + // 监听子节点的变动(但不监听属性变化) + ob.observe(parent!, { + childList: true, + subtree: false, + attributes: false, + }); + + return () => { + ob.disconnect(); // 组件卸载时断开观察器 + }; + }, [ref]); + return ( + <> + + {props.children} + { + commentEndRef.current = ref; + // 初次分配 ref 这时候commentRef 肯定已经有值了 + assignRef(); + }} + data="Ref" + /> + + ); +}; + +// 使用 `forwardRef` 将 RefRender 包装成带 ref 的组件 +const Ref = React.forwardRef(RefRender); + +Ref.displayName = "Ref"; // 设置组件的 displayName,方便调试 +export default Ref; // 导出组件 From 1080adbd8b7d3d123acc7e651087b6fdbea22d0f Mon Sep 17 00:00:00 2001 From: robertpanvip Date: Wed, 18 Dec 2024 17:42:33 +0800 Subject: [PATCH 2/7] Create HTMLComment.tsx --- src/SingleObserver/HTMLComment.tsx | 58 ++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 src/SingleObserver/HTMLComment.tsx diff --git a/src/SingleObserver/HTMLComment.tsx b/src/SingleObserver/HTMLComment.tsx new file mode 100644 index 0000000..e59bf80 --- /dev/null +++ b/src/SingleObserver/HTMLComment.tsx @@ -0,0 +1,58 @@ +import React from "react"; + +export interface CommentProps { + data?: string; +} + +type ElementLike = { + setAttribute: () => boolean; + style: object; +}; + +const createElement = document.createElement; +const TagSymbol = `comment__`; + +//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 = {}; + return comment as unknown as HTMLElement; + } + } + return createElement.call(this, tagName, options); +}; + +function CommentRender( + { data = "" }: CommentProps, + ref: React.ForwardedRef +) { + return ( +