首页/新闻资讯/正文详情

用 Redwood 方式构建组件:以评论组件为例的 Storybook 与测试全流程

发布时间:2026/9/23 10:48:08 来源:云帆数科 栏目:资讯中心
用 Redwood 方式构建组件:以评论组件为例的 Storybook 与测试全流程
后端前端Web框架开发工具【免费下载链接】redwoodRedwoodGraphQL项目地址https://gitcode.com/gh_mirrors/re/redwood点击查看免费下载导读本文基于 Redwood 官方教程第五章用 Redwood 的方式构建组件Building a Component the Redwood Way展开以给博客添加评论功能为实战场景完整演示 Redwood 开发工作流的经典路径先用yarn rw g component生成组件骨架再借助 Storybook 以交互方式打磨组件外观与数据结构最后用yarn rw test为组件编写行为级测试。读完本文你将掌握 Redwood 中生成 → 可视化开发 → 测试验证的组件开发闭环以及time标签 datetime属性这类容易被忽略的机器可读性细节。一、需求拆解评论功能的两条主线在开始动手之前先明确我们要构建的功能。博客缺少评论目标很简单让读者在文章下面留下他们完全理性、有理有据的评论。整个功能可以被拆成两大块评论表单与创建Comment form and creation用户输入并提交评论评论检索与展示Comment retrieval and display把已存在的评论取出来并渲染到页面上。两条主线先后顺序没有强制要求。为了循序渐进教程选择先做取数 展示再去做更复杂的表单 Service 创建。当然正如教程所说这是 Redwood连表单和 Service 也没有那么复杂。这种先展示、后写入的顺序安排也符合组件开发的直觉先把静态展示形态确定下来数据结构、样式、测试再接入数据写入能显著降低心智负担。二、用生成器创建 Comment 组件Storybook 先行2.1 生成组件骨架Redwood 的 CLI 提供了组件生成器一条命令即可产出组件文件yarn rw g component Comment执行后Storybook 会自动刷新并生成一个开箱即用的 Generated Comment story。从仓库源码看这条命令背后做了不少事情。component.js 中files()函数定义了生成器的产出逻辑生成主组件文件web/src/components/Comment/Comment.jsx或.tsx生成测试文件web/src/components/Comment/Comment.test.jsx或.test.tsx生成 Story 文件web/src/components/Comment/Comment.stories.jsx或.stories.tsx。其中 TS 与 JS 版本会使用不同的模板JS 版本的主组件内容由transformTSToJS从 TS 模板转换而来而 Story 模板则分别有 stories.tsx.template 与stories.jsx.template两个独立文件因为 TS 模板注释中含类型信息。是否生成测试和 Story 文件取决于命令选项options.stories与options.tests默认都会生成。主组件的初始模板component.tsx.template内容非常简单仅仅渲染一个标题和提示文字const Comment () { return ( div h2{Comment}/h2 p{Find me in web/src/components/Comment/Comment.tsx}/p /div ) } export default Comment生成的测试模板test.tsx.template也只做了最基础的事情——渲染组件不抛异常import { render } from redwoodjs/testing/web import Comment from ./Comment describe(Comment, () { it(renders successfully, () { expect(() { render(Comment /) }).not.toThrow() }) })这就是教程中所说默认测试只是确保不抛错的来源这是生成器给所有组件的最低保障。2.2 明确组件的数据契约接下来要思考我们希望用户提供什么、展示什么最简方案是只收集姓名和评论正文再额外带上评论的创建时间。于是 Comment 组件需要接收一个包含三个属性的comment对象name评论者姓名createdAt评论创建时间body评论正文。JavaScript 版本直接解构 propsconst Comment ({ comment }) { return ( div h2{comment.name}/h2 time dateTime{comment.createdAt}{comment.createdAt}/time p{comment.body}/p /div ) } export default CommentTypeScript 版本则需要先定义一个临时的 Props 类型教程特意注明这只是临时类型后续接入 GraphQL 生成类型后会替换// Just a temporary type. Well replace this later interface Props { comment: { name: string createdAt: string body: string } } const Comment ({ comment }: Props) { return ( div h2{comment.name}/h2 time dateTime{comment.createdAt}{comment.createdAt}/time p{comment.body}/p /div ) } export default Comment注意这里 TypeScript 的类型信息是手工声明的内联结构。在 Redwood 中这类类型最终往往会被替换为由 SDLSchema Definition Language与 Cell 生成出的类型以实现端到端的类型安全——这也是教程中Just a temporary type注释的含义。2.3 修复 Story补上缺失的 props保存文件后Storybook 会立刻报错——因为 Story 仍然按无 props渲染组件而组件现在要求comment对象。需要更新 Story 文件传入一个符合数据契约的示例对象import Comment from ./Comment export const generated () { return ( Comment comment{{ name: Rob Cameron, body: This is the first comment!, createdAt: 2020-01-01T12:34:56Z }} / ) } export default { title: Components/Comment, component: Comment, }TypeScript 版本完全一致import Comment from ./Comment export const generated () { return ( Comment comment{{ name: Rob Cameron, body: This is the first comment!, createdAt: 2020-01-01T12:34:56Z }} / ) } export default { title: Components/Comment, component: Comment, }保存后 Storybook 重新加载组件即可正常渲染。:::info 关于日期格式的一个重要提示 时间值最终会以ISO8601 格式如2020-01-01T12:34:56Z从 GraphQL 返回因此 Story 中必须提供一个该格式的示例值。这保证 Story 与真实运行时数据形态一致避免开发环境好好的、一接真数据就崩的落差。 :::三、让组件像成品样式与日期格式化基础展示没有问题后为组件加上一点样式和日期转换让它成为一个设计完成的组件。我们新增一个formattedDate工具函数把 ISO8601 字符串解析为Date对象提取出日期、月份长名称如 January和年份拼成2 January 2020这样易读的格式同时保留time标签的dateTime属性用于承载机器可读的原始时间戳。JavaScript 版本const formattedDate (datetime) { const parsedDate new Date(datetime) const month parsedDate.toLocaleString(default, { month: long }) return ${parsedDate.getDate()} ${month} ${parsedDate.getFullYear()} } const Comment ({ comment }) { return ( div classNamebg-gray-200 p-8 rounded-lg header classNameflex justify-between h2 classNamefont-semibold text-gray-700{comment.name}/h2 time classNametext-xs text-gray-500 dateTime{comment.createdAt} {formattedDate(comment.createdAt)} /time /header p classNametext-sm mt-2{comment.body}/p /div ) } export default CommentTypeScript 版本的关键差异在于入参类型formattedDate接收的参数类型声明为ConstructorParameterstypeof Date[0]即Date构造函数第一个参数的类型string | number | Date这样既能接受 ISO8601 字符串又保持类型安全const formattedDate (datetime: ConstructorParameterstypeof Date[0]) { const parsedDate new Date(datetime) const month parsedDate.toLocaleString(default, { month: long }) return ${parsedDate.getDate()} ${month} ${parsedDate.getFullYear()} } // Just a temporary type. Well replace this later interface Props { comment: { name: string createdAt: string body: string } } const Comment ({ comment }: Props) { return ( div classNamebg-gray-200 p-8 rounded-lg header classNameflex justify-between h2 classNamefont-semibold text-gray-700{comment.name}/h2 time classNametext-xs text-gray-500 dateTime{comment.createdAt} {formattedDate(comment.createdAt)} /time /header p classNametext-sm mt-2{comment.body}/p /div ) } export default Comment样式上用的是 Tailwind 工具类外层卡片bg-gray-200 p-8 rounded-lg头部用 flex 让姓名左对齐、时间右对齐正文用小号文字。这样组件就从一个裸数据展示器变成了一个有完整视觉形态的 UI 元素此时再回到 Storybook 中可以看到最终效果。四、用测试锁定组件行为样式和展示都正确了接下来用测试确认组件确实按预期工作。测试要点验证作者姓名、评论正文、以及评论发布日期的展示——并且要同时验证用户可读的格式化文本和机器可读的datetime属性。测试代码如下import { render, screen } from redwoodjs/testing import Comment from ./Comment describe(Comment, () { it(renders successfully, () { const comment { name: John Doe, body: This is my comment, createdAt: 2020-01-02T12:34:56Z, } render(Comment comment{comment} /) expect(screen.getByText(comment.name)).toBeInTheDocument() expect(screen.getByText(comment.body)).toBeInTheDocument() const dateExpect screen.getByText(2 January 2020) expect(dateExpect).toBeInTheDocument() expect(dateExpect.nodeName).toEqual(TIME) expect(dateExpect).toHaveAttribute(datetime, comment.createdAt) }) })TypeScript 版本相同仅扩展名与导入路径不同.test.tsximport { render, screen } from redwoodjs/testing import Comment from ./Comment describe(Comment, () { it(renders successfully, () { const comment { name: John Doe, body: This is my comment, createdAt: 2020-01-02T12:34:56Z, } render(Comment comment{comment} /) expect(screen.getByText(comment.name)).toBeInTheDocument() expect(screen.getByText(comment.body)).toBeInTheDocument() const dateExpect screen.getByText(2 January 2020) expect(dateExpect).toBeInTheDocument() expect(dateExpect.nodeName).toEqual(TIME) expect(dateExpect).toHaveAttribute(datetime, comment.createdAt) }) })这段测试值得逐条拆解expect(screen.getByText(comment.name)).toBeInTheDocument()验证姓名文本被渲染expect(screen.getByText(comment.body)).toBeInTheDocument()验证正文文本被渲染screen.getByText(2 January 2020)验证格式化后的日期文本这与前文中测试文章截断文本的思路一致expect(dateExpect.nodeName).toEqual(TIME)验证包裹该文本的元素确实是time标签expect(dateExpect).toHaveAttribute(datetime, comment.createdAt)验证datetime属性携带原始时间戳。第 4、5 条断言看起来像是过度测试但这是有明确目的的datetime属性的存在意义是提供机器可读的时间戳浏览器理论上可以据此做诸如自动转换本地时区、注入日历提醒等能力。断言这两点就是确保我们不会在日后的重构中无意间破坏这种机器可读性。它和测试截断文本在理念上一脉相承——你关心什么行为就锁定什么行为。测试所用的render、screen等 API 全部来自redwoodjs/testing。在仓库源码中packages/testing/src/web/index.ts 通过export * from testing-library/react重新导出了 React Testing Library 的全部能力Redwood 的测试生态因此与社区标准工具链完全兼容。运行测试如果测试还没有在另一个终端窗口中运行现在启动yarn rw testRedwood 的测试 runner 会以 watch 模式运行保存测试文件后自动重跑。:::info 如果改了日期格式化逻辑测试会不会跟着坏会的——正如改动截断长度就要同步修改截断文本的断言一样。一个可选的替代方案是把日期格式化逻辑抽成一个可从组件导出的独立函数然后在测试中导入该函数来生成期望值。这样当你改动格式化公式时测试因为与组件共享同一个函数而自动保持通过无需手工同步两处逻辑。从组件中导出纯函数并在测试中复用的做法在 Redwood 中非常常见——它既保证了测试与实现的同步性也让格式化这类纯逻辑可以被独立单元测试。 :::五、小结Redwood 的组件开发闭环回顾本章的完整流程可以提炼出 Redwood 推荐的组件开发模式生成yarn rw g component Comment一键生成组件、测试、Story 三件套源码见 component.js定义数据契约明确组件接收的 props本例为name、createdAt、bodyTS 项目同步声明类型Storybook 可视化迭代为 Story 提供符合契约的示例数据在浏览器中即时调整样式与展示逻辑测试锁定行为不止断言渲染不报错而是锁定真实业务行为——文本内容、元素语义time、机器可读属性datetime运行yarn rw test持续验证。这套生成器 Storybook 测试的组合拳贯穿 Redwood 的日常开发。本章只完成了评论功能的展示半边接下来将进入更复杂的另一半——评论表单的创建与 Service 层的写入逻辑。那时Comment组件积累的展示与测试基础会直接复用这也正是用 Redwood 的方式渐进式构建功能的精髓所在。赞分享后端前端Web框架开发工具【免费下载链接】redwoodRedwoodGraphQL项目地址https://gitcode.com/gh_mirrors/re/redwood点击查看免费下载相关推荐以 Redwood 方式构建组件从 Storybook 到测试的 Comment 组件实战以 Redwood 方式构建组件从 Storybook 到测试的 Comment 组件实战 这篇技术指南以 Redwood 官方教程第六章《Building后端前端Web框架开发工具Redwood 教程用 Cell 构建评论列表CommentsCell——从 Storybook 开发到组件测试全流程Redwood 教程用 Cell 构建评论列表CommentsCell——从 Storybook 开发到组件测试全流程 本篇教程是 Redwood 官方教后端前端Web框架开发工具Redwood 组件开发实战以 Comment 组件为例解析组件生成 → Storybook → 测试的 The Redwood WayRedwood 组件开发实战以 Comment 组件为例解析组件生成 → Storybook → 测试的 The Redwood Way Redwood后端前端Web框架开发工具上一篇终极指南ggml混合精度训练中FP16与FP32的最佳实践下一篇终极指南co代码规范——编写优雅异步代码的10条黄金准则创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关推荐

Spring Cloud @RefreshScope原理与实战应用
Spring Cloud @RefreshScope原理与实战应用

1. 理解RefreshScope的核心价值在分布式配置中心盛行的今天,动态刷新配置已成为微服务架构的刚需。Spring Cloud提供的RefreshScope注解,本质上是对标准Spring Scope机制的扩展实现。与传统的singleton和prototype不同,refresh scope允许Bean… · 2026/9/23 10:48:08

C语言杨辉三角:二维数组实现与空间优化详解
C语言杨辉三角:二维数组实现与空间优化详解

1. 杨辉三角到底在练什么:从数学规律到C语言落地杨辉三角这个题目,几乎出现在每一本C语言教材的数组章节里。很多人第一次看到它,觉得不过就是打印一堆数字排成三角形,能有多难?但真正动手写的时候,问题就来… · 2026/9/23 10:48:02

qutebrowser 广告拦截测试数据集:ublock-matches.tsv.gz 的生成、结构与用法
qutebrowser 广告拦截测试数据集:ublock-matches.tsv.gz 的生成、结构与用法

桌面应用 【免费下载链接】qutebrowser A keyboard-driven, vim-like browser based on Python and Qt. 项目地址: https://gitcode.com/gh_mirrors/qu/qutebrowser 点击查看 免费下载 导读 本文围绕 qutebrowser 仓库中 tests/end2end/data/brave-adblock/README… · 2026/9/23 10:48:02

dMMR与免疫治疗:从DNA错配修复缺陷到精准抗癌入场券
dMMR与免疫治疗:从DNA错配修复缺陷到精准抗癌入场券

如果你在病理报告上看到“dMMR”这三个字母,八成会愣一下:这是坏消息还是好消息?我的答案是:放在十年前,它多半意味着预后很差;放在今天,它更像一张“精准免疫治疗优先入场券”。dMMR的全称叫DN… · 2026/9/23 11:25:18

交通流量预测毕业设计:Python机器学习端到端实战指南
交通流量预测毕业设计:Python机器学习端到端实战指南

简介:本资源是一套完整的Python机器学习交通流量预测实战项目,专为本科毕业设计、课程设计及期末大作业打造,面向具备基础Python与机器学习知识的学习者,解决城市交通流时序建模与短期预测这一典型应用场景。压缩包共267个文件&am… · 2026/9/23 11:25:18

三桑实战:新手避坑指南,保姆级教程教你从零跑通
三桑实战:新手避坑指南,保姆级教程教你从零跑通

三桑实战:新手避坑指南,保姆级教程教你从零跑通 复制来的代码跑不通,报错满屏红字,盯着屏幕发呆不知道从哪调起?别慌,这正是很多初学者面对【三桑】这类复杂项目时的常态。今天这篇【保姆级教程】,不玩虚的,直接带你从零搭建一个可运行的实战项目。我… · 2026/9/23 11:25:18

Akka Streams Source.lazyFuture 操作符完全指南:延迟创建单元素 Future 的惰性数据源
Akka Streams Source.lazyFuture 操作符完全指南:延迟创建单元素 Future 的惰性数据源

后端并发编程异步编程 【免费下载链接】akka-core A platform to build and run apps that are elastic, agile, and resilient. SDK, libraries, and hosted environments. 项目地址: https://gitcode.com/gh_mirrors/ak/akka-core 点击查看 免费下载 本文围绕 Ak… · 2026/9/23 11:25:18

手把手搭建开源股票行情监控系统:Python+AKShare实现自动盯盘与告警
手把手搭建开源股票行情监控系统:Python+AKShare实现自动盯盘与告警

先说结论:OpenStock是我近段时间从零搭起来的一套开源股票行情监控服务,核心目标只有一个——把盯盘、提醒、复盘这三件事,从手动操作变成半自动流程,让信息在正确的时间主动来找我,而不是我每天多个App来回切。取名带… · 2026/9/23 11:25:18

AI本地部署必修课:驱动、CUDA与电源设置协同配置指南
AI本地部署必修课:驱动、CUDA与电源设置协同配置指南

1. 为什么“玩AI”不是装个软件就完事——从显卡驱动崩溃说起 你是不是也经历过:刚下载好一个热门AI绘画工具,点开就报错;或者本地部署大模型时,GPU显存明明有24GB,却只识别出0MB;又或者运行 nvidia-smi … · 2026/9/23 11:25:12

3招搞定手机怎么下载微信面试难题实战项目解析
3招搞定手机怎么下载微信面试难题实战项目解析

3招搞定手机怎么下载微信面试难题实战项目解析 面试被问“手机怎么下载微信”背后的原理,90%的人答不上来。别笑,这看似弱智的问题,实则是考察你对移动应用分发机制、安全校验及网络协议理解的试金石。我带过不少校招新人,他们背了八股文,却连一个A… · 2026/9/23 0:00:03

你有新短消息请注意查收:3个新手避坑指南搞定消息系统选型
你有新短消息请注意查收:3个新手避坑指南搞定消息系统选型

你有新短消息请注意查收:3个新手避坑指南搞定消息系统选型 面试被问“高并发下如何保证消息不丢失”,你张口就是“用Redis”,结果面试官追问“如果Redis宕机了怎么办”,你瞬间卡壳。这种场景太常见了,很多新手在背八股文时,只记住了技术名词… · 2026/9/23 0:00:29

Win7无线热点配置工具源码解析:解决API失效的3个实战技巧
Win7无线热点配置工具源码解析:解决API失效的3个实战技巧

Win7无线热点配置工具源码解析:解决API失效的3个实战技巧 Win7无线热点配置工具在Win10/11上跑不动?不是你的问题,是版本升级后 API 全变了。很多老项目里的 netsh wlan… · 2026/9/23 0:00:36

了解更多?预约专属演示

我们的顾问将为您一对一讲解产品与方案

企业微信二维码