后端前端Web框架开发工具【免费下载链接】redwoodRedwoodGraphQL项目地址https://gitcode.com/gh_mirrors/re/redwood点击查看免费下载导读Cells 是 Redwood 最具标志性的数据获取抽象它用一套命名导出约定QUERY、Loading、Empty、Failure、Success把 GraphQL 查询的完整生命周期加载中、空数据、出错、成功拆解为纯粹的 UI 组件由框架在构建期通过 Babel 插件自动装配让你无需编写任何命令式代码。读完本文你将掌握如何生成 Cell、如何利用beforeQuery/isEmpty/afterQuery三个生命周期钩子精确控制查询行为并能理解 Redwood 在源码层面createCell.tsx是如何把七种导出组合成真实组件的。Cells 是什么声明式数据获取的核心抽象Cells 是一套对数据获取的声明式约定。它的核心思路是由你导出若干命名常量声明你在查询生命周期的每个阶段希望 UI 长成什么样子Redwood 在构建期借助 Babel 插件把这些导出装配成一个组件模板最终替你执行 GraphQL 查询并管理其生命周期。正因为 Redwood 介入了请求与响应之间的过程它才有机会在不改变你任何业务代码的前提下做查询优化等额外工作。从底层看一个 Cell 的本质就是执行一次 GraphQL 查询 管理其生命周期——具体实现在 createCell.tsx 中createCell接收 Cell 的各种导出返回一个标准的 React 组件内部使用useQuery通过GraphQLHooksProvider解耦便于替换 GraphQL 客户端驱动整个流程。生成一个 Cell使用 Redwood 的 Cell 生成器yarn rw generate cell name该命令会在web/src/components下创建一个名为nameCell的目录包含四个文件文件说明nameCell.js真正的 CellnameCell.test.js覆盖 Cell 各状态的 Jest 测试nameCell.stories.js覆盖 Cell 各状态的 Storybook storiesnameCell.mock.js供 Jest 测试与 Storybook stories 共用的 Mock 数据生成器的实际逻辑位于 cell.js它会依次生成 Cell 组件文件基于 cell.tsx.template 或cellList.tsx.template、测试文件test.js.template、stories 文件stories.tsx.template与 mock 文件。单条数据 Cell 与列表 CellRedwood 的 Cell 生成器同时支持渲染单个条目与渲染列表两种形态自动判断单复数生成器先检测name是单数还是复数。例如要生成渲染用户列表的 Cell直接运行yarn rw generate cell users。手动指定列表对于单复数同形的不可数/不规则词如equipment、pokemon可以显式传入--list告诉 Redwood 生成列表 Cellyarn rw generate cell equipment --list在源码层面cell.js 通过isWordPluralizable(cellName) ? isPlural(cellName) : options.list决定shouldGenerateList进而选择cellList模板并对单词强制复数化。同时生成器会为列表与单条查询分别生成唯一且可预期的 operation name列表形如UsersQuery、单条形如FindUserQuery冲突时自动追加数字下标见 utils.js。提示单条 Cell 的QUERY默认基于schema.prisma中的模型生成如按id查询若你的查询字段与模型不同记得修改根查询。教程中有一个很好的实例Tutorial - Cells。深入了解 CellsCells 导出了五个核心常量QUERY、Loading、Empty、Failure和Success。QUERY中的根查询默认与name同名这样当你基于schema.prisma中的模型生成 Cell 时可以立刻从数据库拿到数据。但多数情况下你不会这样直接使用生成结果因此务必按需修改根查询。完整用法七种导出一个 Cell 总共可以有七种导出各司其职名称类型说明QUERYstring, function要执行的查询beforeQueryfunction生命周期钩子为查询准备 variables 与 optionsisEmptyfunction生命周期钩子决定 Cell 是否渲染EmptyafterQueryfunction生命周期钩子净化查询返回的数据Loadingcomponent请求进行中时渲染的组件Emptycomponent无数据null或[]时渲染的组件Failurecomponent出错时渲染的组件Successcomponent数据加载完成后渲染的组件只有QUERY和Success是必需的。若未导出Empty空结果会直接交给Success若未导出Failure错误只会输出到控制台源码中对应 createCell.tsx有Failure则渲染否则直接throw error交由上层错误边界处理。除在正确时机渲染正确组件外Cells 还会把正确的 props 分发给正确的组件Loading、Empty、Failure、Success都能以常规 React 方式访问父级传入的 props并能拿到useQuery返回值的大部分内容作为一个名为queryResult的 prop。Empty与Success额外获得查询返回的data以及一个表示 Cell 当前是否正在拉取新数据的updating布尔值源码中updating{loading}即把loading重命名后暴露给用户便于渲染后台刷新中的指示器。Failure也拥有updating并且独占error与errorCode两个 props。useQuery的确切返回值以 Apollo Client 的 API 文档为准注意error与data在 Cell 中享受了特殊处理。QUERYQUERY可以是字符串或函数如果是函数必须返回一个合法的 GraphQL 文档。一个 Cell 完全支持包含多个根查询。例如export const QUERY gql{ query { posts { id title } authors { id name } } }此时posts与authors都会传给Successexport const Success ({ posts, authors }) { // ... }查询通常带变量。Cells 默认会把父组件传入的任何 props 当作查询变量这一步在beforeQuery中完成。例如下面的BlogPostsCell接收一个numberToShowprop它可以直接在QUERY中使用import BlogPostsCell from src/components/BlogPostsCell const HomePage () { return ( div h1Home/h1 BlogPostsCell numberToShow{3} / /div ) } export default HomePageexport const QUERY gql query ($numberToShow: Int!) { posts(numberToShow: $numberToShow) { id title } } 因此你可以从 SDL 反向推导 Cell 的 propsSDL 里有什么变量Cell 的 props 就应该是什么。beforeQuerybeforeQuery是一个生命周期钩子最恰当的理解是它是配置 Apollo ClientuseQuery选项的机会对应 CreateCellProps.beforeQuery 的类型定义。默认行为是把父组件传入的所有 props 作为查询变量并设置fetchPolicy为cache-and-network团队认为这最符合大多数用户期望的先用缓存立即渲染、同时后台刷新行为同时打开notifyOnNetworkStatusChange对应 createCell.tsx 的默认实现export const beforeQuery (props) { return { variables: props, fetchPolicy: cache-and-network, } }例如想开启 Apollo 的轮询并禁用缓存可以这样导出polling 与 fetchPolicy 详见 Apollo 文档export const beforeQuery (props) { return { variables: props, fetchPolicy: no-cache, pollInterval: 2500 } }beforeQuery还可以用来填充 props 之外的数据例如从 React Context 或全局状态库取值。一旦你提供了beforeQuery函数Cell 的 props 类型会自动变为该函数第一个参数的类型这正是 cellTypes.ts 中CellPropsVariables的推导逻辑// The Cell will take no props: Cell / export const beforeQuery () { const { currentUser } useAuth() return { variables: { userId: currentUser.id }, } }// The cell will take 1 prop named word that is a string: Cell wordabc export const beforeQuery ({ word }: { word: string }) { return { variables: { magicWord: word } } }注意QUERY若是函数它会在beforeQuery之后被调用并接收beforeQuery的返回结果作为参数见 createCell.tsxconst query typeof QUERY function ? QUERY(options) : QUERY。isEmptyisEmpty是可选的生命周期钩子返回布尔值指示 Cell 是否应渲染Empty用于覆盖默认的空数据判断逻辑。默认判断是检查 Cell 的根字段是否为null或空数组。其实现位于 isCellEmpty.ts!data或Object.values(data).every(field field null || 空数组)即所有根字段都为空才算空。例如{ post: null }或{ posts: [] }都视为空单个根字段为null时如posts: [Post!]的可空场景同样成立。它接收两个参数1data2一个包含默认isEmpty函数的对象名为isDataEmpty以便你在其基础上扩展export const isEmpty (data, { isDataEmpty }) { return isDataEmpty(data) || data?.blog?.status hidden }afterQueryafterQuery是生命周期钩子在数据到达Success之前运行用于净化QUERY返回的数据。默认实现是原样返回数据非 Suspense 版为(data) dataSuspense 版为(data) ({ ...data })见 createCell.tsx 与 createSuspendingCell.tsx。Loading如果没有缓存数据且请求仍在进行Cell 渲染Loading。本地开发时可以在浏览器开发者工具 Network 面板把网速调成 Slow 3G观察 Cell 短暂停留在加载态。但更推荐的做法是使用 Storybook生成的*.stories.js覆盖了 Cell 的各个状态无需依赖 Slow 3G 或故意弄坏应用就能轻松开发Loading和Failure组件。EmptyCell 在没有数据时渲染Empty。所谓没有数据指的是响应为1null2空数组[]。若未导出Empty空结果会直接进入Success。FailureCell 在出错时渲染Failure。想快速触发错误可以给QUERY加一个不存在的字段const QUERY gql query { posts { id title unTypedField } } 与Loading一样用 Storybook 开发Failure是更好的选择。Failure能拿到error与errorCode。其中errorCode由 Redwood 在运行时计算优先取useQuery结果中的errorCode否则从error.graphQLErrors?.[0]?.extensions?.[code]提取见 createCell.tsx对应类型定义 CellFailureProps。下面的例子用errorCode条件渲染错误标题并把它作为翻译字符串的 keyexport const Failure ({ error, errorCode }: CellFailureProps) { const { t } useTranslation() return ( div style{{ color: red }} {errorCode NO_CONFIG ? h1NO_CONFIG/h1 : h1ERROR/h1} Error: {error.message} - Code: {errorCode} - {t(error.${errorCode})} /div ) }Success一切正常时Cell 渲染Success。如前所述Success能拿到data但如果你试图从 props 里解构data会发现它并不存在——这是 Redwood 的一层便利Redwood 会把data展开spread进Success让你直接从QUERY期望的数据解构。源码中的对应逻辑是return Success {...props} {...afterQueryData} updating{loading} queryResult{queryResult} /createCell.tsx。所以如果查询posts和authors无需这样写export const Success ({ data }) { const { posts, authors } data // ... }Redwood 允许你直接写export const Success ({ posts, authors }) { // ... }当然你仍然可以向Success传入任意其他 props——毕竟它只是一个普通的 React 组件。TypeScript 下的类型提示可参考 CellSuccessProps / CellSuccessData当查询只有一个根字段时Redwood 能保证该字段非空GuaranteedT多根字段时则无法保证每个属性都有数据这一点与默认isEmpty只检查存在部分数据的行为一致。:::tip 想了解 Cells 与 TypeScript 的配合请参阅 Utility Types 文档。 :::何时应该使用 Cell任何时候你想获取数据都可以使用 Cell。让 Redwood 去处理什么时机显示什么你只需专注这些状态各自长什么样。不过要强调你并不必须使用 Cell。想做任何自定义都是允许的。例如对于一次性的查询始终可以用useApolloClient拿到客户端并直接执行查询// In a react component... client useApolloClient() client.query({ query: gql ... , })可以在 Cell 里执行 Mutation 吗完全可以。Redwood 官方在示例 todo 应用example-todo-mainfixture 中的TodoListCell里就演示了在 Cell 内调用 mutation 的写法。Redwood 也不认为这是反模式——恰恰相反你的 Cell 可能会承载大量逻辑在很多时候成为应用的枢纽。此外请记住除了导出某些特定名字的常量这一条规则外Cells 几乎没有其他限制——常规组件里能做的一切在 Cell 里依然可以做例如在Success中调用useMutation、使用其他 hooks。Redwood 如何识别一个文件是 Cell基本规则是文件名以 Cell 结尾。但还有一条补充规则。Redwood 会扫描所有以 Cell 结尾的文件所以想让组件成为 Cell文件名确实必须以 Cell 结尾但如果该文件1没有导出名为QUERY的常量且 2存在默认导出那么它会被跳过不会被当作 Cell 处理。什么时候会需要这种跳过比如你只是出于某种原因想让某个文件以 Cell 结尾。除此之外不必担心。该逻辑的源码实现在 Babel 插件 babel-plugin-redwood-cell.ts插件在Program.exit时检查是否已有默认导出若已有默认导出则说明不是 Cell或已经是包装好的 Cell因为一个模块只能有一个默认导出以及是否导出了QUERY或data满足条件才会继续装配。具体的构建期装配过程同样在 babel-plugin-redwood-cell.ts包括在文件顶部自动插入import { createCell } from redwoodjs/web若导出的是data而非QUERY则导入createServerCell走服务端 Cell 路径在文件底部自动追加export default createCell({ QUERY, Loading, Success, Failure, Empty, beforeQuery, isEmpty, afterQuery, displayName })自动根据文件名设置displayName便于在 React DevTools 中识别合法的导出名清单见插件中的EXPECTED_EXPORTS_FROM_CELL常量beforeQuery、QUERY、data、isEmpty、afterQuery、Loading、Success、Failure、Empty。高级示例自己动手实现一个 Cell如果没有这些构建期魔法你该如何自己实现一个 Cell以教程中获取 posts 的示例为例export const QUERY gql query { posts { id title body createdAt } } export const Loading () divLoading.../div export const Empty () divNo posts yet!/div export const Failure ({ error }) ( divError loading posts: {error.message}/div ) export const Success ({ posts }) { return posts.map((post) ( article h2{post.title}/h2 div{post.body}/div /article )) }假设 Babel 不会来帮你装配这些导出你大概率会写出这样的东西const QUERY gql query { posts { id title body createdAt } } const Loading () divLoading.../div const Empty () divNo posts yet!/div const Failure ({ error }) ( divError loading posts: {error.message}/div ) const Success ({ posts }) { return posts.map((post) ( article h2{post.title}/h2 div{post.body}/div /article )) } const isEmpty (data) { return isDataNull(data) || isDataEmptyArray(data) } export const Cell () { return ( Query query{QUERY} {({ error, loading, data }) { if (error) { if (Failure) { return Failure error{error} / } else { console.error(error) } } else if (loading) { return Loading / } else if (data) { if (typeof Empty ! undefined isEmpty(data)) { return Empty / } else { return Success {...data} / } } else { throw Cannot render Cell: graphQL success but data is null } }} /Query ) }这是一大段命令式代码——它实际上就是把 createCell.tsx 的内容倒进了你的文件里。可以想象如果每次想获取可能延迟响应的数据都要写一遍那将是多么痛苦。这正是 Cells 存在的意义。值得补充的是仓库中的 createCell.test.tsx 覆盖了上述各分支加载、成功、空、失败、errorCode计算、props 分发等而 createSuspendingCell.tsx 则展示了开启流式 SSRRWJS_ENV.RWJS_EXP_STREAMING_SSR时的另一套实现改用useBackgroundQueryuseReadQuery配合Suspense与CellErrorBoundary完成同样的生命周期分发createCell.tsx 中根据环境变量在两种工厂间切换。如果你好奇 Cells 的边界行为与异常路径直接阅读这两份源码和测试是最佳途径。赞分享后端前端Web框架开发工具【免费下载链接】redwoodRedwoodGraphQL项目地址https://gitcode.com/gh_mirrors/re/redwood点击查看免费下载相关推荐Redwood Cells 完全指南用声明式模式接管 GraphQL 数据获取的生命周期Redwood Cells 完全指南用声明式模式接管 GraphQL 数据获取的生命周期 Cells 是 Redwood 框架最具辨识度的抽象之一它以声明式后端前端Web框架开发工具Redwood Cells 深度指南声明式 GraphQL 数据获取的生命周期管理Redwood Cells 深度指南声明式 GraphQL 数据获取的生命周期管理 导读 Cells 是 Redwood 框架最具标志性的抽象之一它以纯声明后端前端Web框架开发工具Redwood Cells 完全指南声明式数据获取与查询生命周期管理Redwood Cells 完全指南声明式数据获取与查询生命周期管理 Cells 是 Redwood 框架最具标志性的抽象之一它以声明式方式封装 Graph后端前端Web框架开发工具上一篇Kimi-K3-mlx-mxfp4-6bit-aimer91模型性能评测6bit量化与传统模型的终极对比下一篇PlayCanvas npm 包的 ESM 非打包模块树与 sideEffects 配置对 tree-shaking 有何影响创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考