Skip to content
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

feat: add ref forwarding to internal img element. Closes #372 #379

Open
wants to merge 7 commits into
base: master
Choose a base branch
from

Conversation

liamli-0822
Copy link

@liamli-0822 liamli-0822 commented Mar 4, 2025

  • Implement React.forwardRef to expose internal img DOM node
  • Support both object refs and callback refs
  • Ensure compatibility with existing getImgRef functionality
  • Add comprehensive test cases for ref forwarding

Closes #372

Summary by CodeRabbit

Summary by CodeRabbit

  • 新功能

    • 图像组件现支持引用转发,使外部组件能够直接访问底层图像元素,实现更灵活的引用管理。
  • 测试

    • 增加了一系列测试用例,全面验证了对象引用和回调引用的转发功能,并确保在组件更新过程中引用保持稳定。

Copy link

vercel bot commented Mar 4, 2025

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
image ✅ Ready (Inspect) Visit Preview 💬 Add feedback Mar 24, 2025 3:10am

Copy link

coderabbitai bot commented Mar 4, 2025

Walkthrough

此次更改对 ImageInternal 组件进行了重构,使其采用 forwardRef 来转发引用,将组件接口更新为 React.ForwardRefExoticComponent,以便父组件可以直接访问内部的 <img> 元素。同时,新添加了 handleRef 函数来整合内部和外部的引用管理。新增的测试套件验证了对象与回调引用的正确转发及在组件重渲染时引用的一致性。

Changes

文件路径 变更摘要
src/Image.tsx 重构 ImageInternal 组件,使用 forwardRef 接收并转发 ref。更新接口及方法签名,新增 handleRef 函数以整合内部与外部引用。
tests/ref.test.tsx 新增测试用例验证图片组件的 ref 转发功能:测试对象引用、回调引用及重渲染时引用的稳定性。

Sequence Diagram(s)

sequenceDiagram
    participant P as 父组件
    participant I as ImageInternal 组件
    participant H as handleRef 函数
    participant IMG as HTML Image 元素

    P->>I: 渲染组件并传入 ref
    I->>H: 调用 handleRef 传入 props 与 ref
    H->>IMG: 分配引用给图片元素
    IMG-->>H: 返回图片元素
    H-->>I: 更新内部引用
    I-->>P: 完成 ref 转发
Loading

Assessment against linked issues

Objective Addressed Explanation
支持使用 forwardRef 定义 Image 组件 (372)

Possibly related PRs

Poem

跳跃在代码花园里,
小兔牵起 ref 的线,
新风采在 forwardRef 闪耀,
合理流转每个节点,
我心悠然,代码同行,
CodeRabbit Inc 带来无限可能! 🐇✨

Warning

There were issues while running some tools. Please review the errors and either fix the tool’s configuration or disable the tool if it’s a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

tests/ref.test.tsx

Oops! Something went wrong! :(

ESLint: 8.57.1

ESLint couldn't find the config "prettier" to extend from. Please check that the name of the config is correct.

The config "prettier" was referenced from the config file in "/.eslintrc.js".

If you still have problems, please stop by https://eslint.org/chat/help to chat with the team.

✨ Finishing Touches
  • 📝 Generate Docstrings

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (3)
tests/ref.test.tsx (2)

43-61: 验证了引用对 img 元素属性的访问能力,但可以考虑扩展测试。

测试成功验证了通过 ref 可以访问 img 元素的宽高属性。注释中提到了可能测试调用 img 元素的方法,但没有实际实现。考虑添加对 img 方法的测试,比如 getBoundingClientRect(),这些方法在 jsdom 环境中可用。

-  // 可以测试调用 img 元素的方法
-  // 注意:某些方法可能在 jsdom 环境中不可用,根据实际情况调整
+  // 测试 img 元素的方法
+  expect(typeof ref.current?.getBoundingClientRect).toBe('function');
+  // 模拟 complete 属性
+  Object.defineProperty(ref.current as HTMLImageElement, 'complete', { value: true });
+  expect(ref.current?.complete).toBe(true);

1-4: 导入语句组织合理。

导入了必要的测试工具和组件,没有多余的导入。注意到导入了 fireEvent,但在测试中没有使用。如果未来不需要,可以考虑移除这个未使用的导入。

-import { act, fireEvent, render } from '@testing-library/react';
+import { act, render } from '@testing-library/react';
src/Image.tsx (1)

183-198: 引用处理函数实现完整,但有微小的格式问题。

handleRef 函数正确处理了内部引用和外部引用的结合,支持对象引用和回调引用两种类型。代码逻辑清晰,但有一行多余的空白行(第187行),应保持代码格式一致性。

  const handleRef = (img: HTMLImageElement | null) => {
    if (img) {
      getImgRef(img);
-      
      // 处理外部传入的 ref
      if (ref) {
        if (typeof ref === 'function') {
          ref(img);
        } else {
          ref.current = img;
        }
      }
    }
  };
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a93f870 and b88ff56.

📒 Files selected for processing (2)
  • src/Image.tsx (5 hunks)
  • tests/ref.test.tsx (1 hunks)
🔇 Additional comments (8)
tests/ref.test.tsx (3)

5-22: 测试用例设计合理,覆盖了对象引用的转发场景。

测试用例很好地验证了对象类型 ref 能够正确转发到内部 img 元素,并且能够访问 img 元素的属性(如 tagName 和 alt)。测试结构清晰,断言完整。


24-41: 回调引用类型的测试实现得当。

这个测试用例很好地验证了回调类型的 ref 能被正确调用并指向正确的 img 元素。这是一个必要的测试,因为 React 支持两种引用类型,测试覆盖了这两种情况。


63-88: 重渲染稳定性测试设计合理。

这个测试用例很好地验证了在组件重新渲染后,ref 引用的仍然是同一个 img 元素,并且属性更新正确反映在 DOM 元素上。这对于确保 ref 的稳定性至关重要。

src/Image.tsx (5)

6-6: 导入语句更新合理,明确分离了具名导入。

从 React 中导入了必要的 forwardRef 函数,并保持了之前的 useContext, useMemo, useState 导入。这种显式导入方式比使用通配符导入更好,有助于代码可读性和树摇。


72-74: 接口更新符合 TypeScript 类型体系。

CompoundedComponent 接口更新为扩展 React.ForwardRefExoticComponent,并添加了 React.RefAttributes<HTMLImageElement> 是正确的做法。这确保了类型安全和 IDE 提示支持。


76-76: 组件重构为 forwardRef 实现得当。

使用 forwardRef 改造组件是正确的实现方式,这允许父组件直接访问内部的 img 元素。定义清晰地包含了泛型参数,指定了正确的引用类型和属性类型。


225-226: img 元素引用属性更新正确。

ref 属性从 getImgRef 更新为 handleRef 是正确的改动,这使得外部引用能够正确转发到内部 img 元素。


276-276: 类型断言使用恰当。

使用 as CompoundedComponent<ImageProps> 类型断言是必要的,因为 forwardRef 返回的类型需要与之前定义的 CompoundedComponent 接口匹配。这确保了组件类型的一致性和 PreviewGroup 属性的可访问性。

@liamli-0822 liamli-0822 changed the title feat: add ref forwarding to internal img element. Closes #373 feat: add ref forwarding to internal img element. Closes #372 Mar 4, 2025
Copy link

codecov bot commented Mar 4, 2025

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 99.41%. Comparing base (9d4b76c) to head (b04e15e).

Additional details and impacted files
@@           Coverage Diff           @@
##           master     #379   +/-   ##
=======================================
  Coverage   99.40%   99.41%           
=======================================
  Files          17       17           
  Lines         506      513    +7     
  Branches      148      149    +1     
=======================================
+ Hits          503      510    +7     
  Misses          3        3           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@afc163
Copy link
Member

afc163 commented Mar 17, 2025

想了下 ref 指向 image 元素也许不是最好的方式,未来可能会加 ref.next ref.rotate ref.focus 这些实例方法。

optimized the logic of the handleRef function

Co-authored-by: afc163 <[email protected]>
@liamli-0822
Copy link
Author

liamli-0822 commented Mar 17, 2025

想了下 ref 指向 image 元素也许不是最好的方式,未来可能会加 ref.next ref.rotate ref.focus 这些实例方法。

那这个先close?

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
src/Image.tsx (1)

183-197: 引用处理实现得很好

处理内部和外部引用的函数实现得很好,同时支持回调引用和对象引用。建议可以考虑将中文注释翻译为英文,以保持代码国际化一致性。

- // 处理外部传入的 ref
+ // Handle externally passed ref
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b88ff56 and 0b6f315.

📒 Files selected for processing (1)
  • src/Image.tsx (5 hunks)
🔇 Additional comments (5)
src/Image.tsx (5)

6-6: 非常好的导入 forwardRef 函数!

从 React 中导入 forwardRef 函数以支持引用转发功能是一个很好的做法。


72-72: 接口类型更新正确适配了 forwardRef 的使用

将 CompoundedComponent 接口从 React.FC 更新为 React.ForwardRefExoticComponent 是正确的做法,这样能够正确地支持 HTMLImageElement 的引用属性。


76-76: 组件定义正确使用了 forwardRef API

使用 forwardRef 包装组件使其能够接收和处理引用参数,实现是正确的。


225-225: 正确更新了图像元素的引用属性

将 img 元素的 ref 属性从 getImgRef 更新为 handleRef 确保了内部功能和转发的引用都能正常工作。


276-276: 组件类型断言正确

使用类型断言将组件转换为 CompoundedComponent 类型是正确的,这样能保持复合组件模式与 PreviewGroup 的兼容性。

@afc163 afc163 closed this Mar 17, 2025
@afc163 afc163 reopened this Mar 17, 2025
@afc163
Copy link
Member

afc163 commented Mar 17, 2025

可以先做个 ref.nativeElement 指向 image 元素。

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
tests/ref.test.tsx (1)

45-63: 测试通过 nativeElement 访问属性的注释需更新

测试验证了通过 nativeElement 访问 img 元素的宽度和高度属性,但有一个未实现的注释提到可以测试方法调用。建议删除或实现该注释以避免混淆。

- // 可以测试调用 img 元素的方法
- // 注意:某些方法可能在 jsdom 环境中不可用,根据实际情况调整
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0b6f315 and 26a8205.

📒 Files selected for processing (2)
  • src/Image.tsx (6 hunks)
  • tests/ref.test.tsx (1 hunks)
🧰 Additional context used
🧬 Code Definitions (1)
tests/ref.test.tsx (1)
src/Image.tsx (1)
  • ImageRef (75-77)
🔇 Additional comments (13)
tests/ref.test.tsx (5)

1-4: 导入和设置文件结构良好

测试文件导入了必要的测试工具和组件,包括正确导入了有类型定义的 ImageRef。这为测试 ref 转发功能提供了良好的基础。


5-23: 对象引用测试覆盖全面

这个测试用例很好地验证了对象引用的功能,确保 ref.current.nativeElement 正确指向内部的 img 元素。测试断言覆盖了多个方面,包括非空检查、DOM 元素匹配和属性访问。


25-43: 回调引用测试完整有效

回调引用的测试实现正确,并确认了与对象引用相同的功能。测试用例清晰地展示了如何使用回调引用模式,并验证了对内部 img 元素的访问。


65-90: 再渲染时引用稳定性测试设计良好

此测试确保在组件重新渲染后引用保持稳定,同时属性可以正确更新。测试设计考虑周全,验证了重要的组件生命周期行为。


92-109: API 边界测试确保引用接口安全性

这个测试验证了 ref 的正确封装,确保不能直接访问 img 元素的原生属性,而必须通过 nativeElement 属性。使用 @ts-ignore 来测试运行时行为是一个合适的方法。

src/Image.tsx (8)

4-4: 增加了必要的 React Hooks 导入

正确导入了 forwardRef, useImperativeHandleuseRef hooks,这些是实现引用转发所必需的。


74-77: 清晰定义了 ImageRef 接口

接口定义简洁明了,只包含一个 nativeElement 属性。这符合 afc163 在评论中建议的实现方法,提供了一个通过 ref.nativeElement 访问内部 img 元素的方式。


79-79: 正确更新了 CompoundedComponent 接口

将接口从 React.FC 更新为 React.ForwardRefExoticComponent,同时添加了对 React.RefAttributes<ImageRef> 的支持,确保类型安全。


83-83: 使用 forwardRef 重构组件

通过 forwardRef 包装组件,允许 ref 从父组件传递到内部的 img 元素,同时保持类型安全。


115-121: 使用 useImperativeHandle 暴露自定义 ref 对象

通过 useImperativeHandle 暴露了符合 ImageRef 接口的对象,只包含 nativeElement 属性。这种方式既提供了对内部 img 元素的访问,又确保了组件的封装性,为未来扩展留出了空间。这符合 afc163 提到的未来可能添加像 ref.next, ref.rotate, ref.focus 等实例方法的考虑。


248-248: 更新了 img 元素的 ref 属性

将 img 元素的 ref 从 getImgRef 更改为 handleRef,确保正确处理内部和外部引用。


294-294: 类型断言确保组件接口一致性

使用类型断言确保 ImageInternal 符合 CompoundedComponent<ImageProps> 接口,保持了组件 API 的一致性。


301-301: 组件导出正确

组件导出没有变化,保持了向后兼容性。

Comment on lines +208 to +216
// ========================== Image Ref ==========================
const handleRef = (img: HTMLImageElement | null) => {
if (!img) {
return;
}
// 保存到内部引用
imageElementRef.current = img;
getImgRef(img);
};
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

集成内部和外部引用的处理函数

handleRef 函数巧妙地处理了内部引用的存储和对 getImgRef 的调用,确保了与现有功能的兼容性。但是,这里有一个潜在的问题:当 img 元素为 null 时直接返回了,但没有更新 imageElementRef.current。应该考虑在这种情况下将 imageElementRef.current 也设置为 null。

 const handleRef = (img: HTMLImageElement | null) => {
   if (!img) {
+    imageElementRef.current = null;
     return;
   }
   // 保存到内部引用
   imageElementRef.current = img;
   getImgRef(img);
 };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// ========================== Image Ref ==========================
const handleRef = (img: HTMLImageElement | null) => {
if (!img) {
return;
}
// 保存到内部引用
imageElementRef.current = img;
getImgRef(img);
};
// ========================== Image Ref ==========================
const handleRef = (img: HTMLImageElement | null) => {
if (!img) {
imageElementRef.current = null;
return;
}
// 保存到内部引用
imageElementRef.current = img;
getImgRef(img);
};

expect(ref.current?.nativeElement?.alt).toBe('updated alt');
});

// 测试 ref 不能直接访问 img 元素属性
Copy link
Member

Choose a reason for hiding this comment

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

这个测试存在的意义不是很大。

Copy link
Author

Choose a reason for hiding this comment

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

+1

Comment on lines 19 to 20
expect(ref.current?.nativeElement).not.toBeNull();
expect(ref.current?.nativeElement).toBe(container.querySelector('.rc-image-img'));
Copy link
Member

Choose a reason for hiding this comment

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

expect(ref.current?.nativeElement).not.toBeNull(); 这个是多余的。和下面的互斥。

Copy link
Author

Choose a reason for hiding this comment

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

+1


// 确保回调 ref 被调用,且 nativeElement 指向正确的 img 元素
expect(imgRef).not.toBeNull();
expect(imgRef?.nativeElement).not.toBeNull();
Copy link
Member

Choose a reason for hiding this comment

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

同上。

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (3)
tests/ref.test.tsx (3)

5-22: 测试对象引用的实现很完善

这个测试用例很好地验证了对象引用是否能够正确访问内部的图像元素。测试验证了 ref.current 存在且 nativeElement 指向正确的 img 元素,同时检查了标签名和 alt 属性是否正确。

建议:考虑添加一个额外的测试,验证 ref.current 直接访问属性(如 ref.current.tagNameref.current.src)时的行为,确保这些属性不是直接暴露的。


43-61: 属性访问测试可以更完善

测试有效验证了通过 nativeElement 访问 img 元素的 width 和 height 属性。

注意到第59-61行有关于测试方法的注释,但没有实际实现。建议要么实现这些方法的测试,要么删除注释,以避免混淆。如果某些方法在 jsdom 环境中确实不可用,可以考虑添加一个简单的 mock 来测试方法调用。


90-90: 考虑添加额外的测试场景

当前的测试套件覆盖了基本的引用转发功能,但可以考虑添加以下场景的测试:

  1. 验证与现有 getImgRef 功能的兼容性(PR 目标中提到)
  2. 测试当未提供引用时的行为
  3. 添加一个显式的测试,确认不能直接通过 ref.current 访问图像元素的属性(如 tagNamesrc

这些额外的测试将有助于确保新功能的完整性和与现有功能的兼容性。

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 26a8205 and b04e15e.

📒 Files selected for processing (1)
  • tests/ref.test.tsx (1 hunks)
🧰 Additional context used
🧬 Code Definitions (1)
tests/ref.test.tsx (1)
src/Image.tsx (1)
  • ImageRef (75-77)
🔇 Additional comments (3)
tests/ref.test.tsx (3)

1-4: 导入和设置看起来很合理

导入了必要的测试工具和组件,包括从源文件导入的 ImageRef 接口。


24-41: 回调引用测试逻辑清晰

这个测试用例有效地验证了回调式引用是否正常工作。测试确保回调被调用并且 nativeElement 指向正确的图像元素。


63-88: 重渲染稳定性测试很好,但有冗余的断言

这个测试用例很好地验证了在组件重新渲染后引用的稳定性,确保 nativeElement 引用的仍然是同一个图像元素,同时属性更新也能正确反映。

第74行的 expect(initialImgElement).not.toBeNull(); 断言是多余的。因为在第86行已经有测试确保 ref.current?.nativeElementinitialImgElement 相同,如果 initialImgElement 为 null,那么第86行的测试将失败。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Image组件是否可以使用forwardRef定义,目前无法传入ref
2 participants