ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

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

用 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),仅供参考
返回列表