资讯详情

资讯详情

learnapollo React Native 教程(6/6):在 Apollo Client 中注入多个 Mutation 并实现 Pokemon 的更新与删除

教程前端【免费下载链接】learnapollo‍ Learn Apollo - A hands-on tutorial for Apollo GraphQL Client (created by Graphcool)项目地址https://gitcode.com/gh_mirrors/le/learnapollo点击查看免费下载本篇基于 learnapollo 仓库中的 React Native Track 最后一章 react-native-06.md 展开讲解如何在前一个练习基础 Mutation的基础上为PokemonCard组件同时注入updatePokemon与deletePokemon两个 Mutation 来完成宝可梦的更新和删除并解释 Apollo 缓存中“删除后 UI 不刷新”的根因与fetchPolicy: cache-and-network的强制拉取方案。读完本篇你将掌握graphql()HOC 的name选项多 Mutation 注入、Mutation 中使用 Fragment、以及 Apollo Store 合并行为对增删操作 UI 一致性的影响。图本练习完成后PokemonPage页面的目标效果——卡片内新增可编辑的 name/url 输入框以及 Delete 与 Update 按钮。练习目标与环境准备本练习是React Native Track 的第 6 个也是最后一个练习目标是为 Pokedex 应用增加对已有 Pokemon 的**更新update与删除delete**能力在PokemonCard上增加一个 Update 按钮和一个 Delete 按钮点击时分别调用对应的 Mutation通过一次练习同时理解两个 Mutation 如何注入到同一个组件中。切换到第 6 个练习目录并安装依赖cd pokedex-react/exercise-06 yarn install # or npm install原文档此处写作pokedex-react/exercise-06需要注意 React Native Track 的练习仓库前缀为pokedex-react-native可对照同 Track 上一章 react-native-05.md 中使用的cd pokedex-react-native/exercise-05实际以本地克隆下来的练习仓库目录名为准。本章在站点端的内容加载路径也可以从源码得到印证src/utils/content.ts 中以alias: react-native-06注册了标题为Multiple Mutations的子章节Subchapter.ast()方法src/utils/content.ts通过require(../../content/${chapter.alias}/${alias}.md)加载本 Markdown 文件并用 commonmark 解析为 AST 后渲染。PokemonCard的改动受控输入与按钮在动手写 Mutation 之前先看清楚PokemonCard已经准备好的改动PokemonPage也已同步更新。核心思路是把原本只读展示的name、url提升为组件state使其可通过TextInput编辑。用 props 初始化 statestate { name: this.props.pokemon.name, url: this.props.pokemon.url, }state 由传入的 props 初始化两个新的TextInput的onChangeText事件负责更新对应字段View TextInput style{{ padding: 20, height: 60, fontFamily: HelveticaNeue-Light, }} onChangeText{(name) this.setState({name})} value{this.state.name} placeholderType a name... / TextInput style{{ padding: 20, height: 60, fontFamily: HelveticaNeue-Light, }} onChangeText{(url) this.setState({url})} value{this.state.url} placeholderImage Url / /View添加 Delete 与 Update 按钮按钮区域代码如下其中updateButton是条件渲染的变量View style{{ flexDirection: column, justifyContent: center, alignItems: center, marginTop: 12 }} Button titleDelete onPress{this.handleDelete} colorred / {updateButton} /ViewupdateButton只有在输入框的值被真正修改过时才会显示避免用与服务器一致的旧值触发一次无意义的更新let updateButton null if (this.canUpdate()) { updateButton Button titleUpdate onPress{this.handleUpdate} / }canUpdate()需要你自己实现——它的判断依据就是this.state.name/url与this.props.pokemon.name/url是否发生了变化。图本练习完成后 Pokedex 列表页效果。给 fragment 补上id字段两个 Mutation 都需要 Pokemon 的id因此需要更新PokemonCard中的pokemonfragment把id加进选择集static fragments { pokemon: gql fragment PokemonCardPokemon on Pokemon { id url name } }这个 fragment 是上一章 react-native-04.md 引入的共置co-locatedfragmentPokemonPage的PokemonQuery通过... PokemonCardPokemon引用它。给它增加id字段后fragment 的propType校验会随之要求pokemonprop 必须携带id——这正是 fragment 作为数据需求契约的价值所在。updatePokemonMutation需求是更新 Pokemon 的名字与图片 URL因此使用updatePokemon及其参数name、url。将其添加到PokemonCard中const updatePokemon gql mutation updatePokemon($id: ID!, $name: String!, $url: String!) { updatePokemon(id: $id, name: $name, url: $url) { id name url } } Mutation 中同样可以像 Query 中使用 fragment 一样复用 fragment。上面的 Mutation 也可以写成const updatePokemon gql mutation updatePokemon($id: ID!, $name: String!, $url: String!) { updatePokemon(id: $id, name: $name, url: $url) { id ... PokemonCardPokemon } } ${PokemonCard.fragments.pokemon} 注意即便PokemonCardPokemonfragment 里已经包含id选择在 fragment 之外再显式列出id也是一种“稳妥写法”GraphQL 允许同一字段在选择集中出现多次只会取回一次。deletePokemonMutation删除只需要目标 Pokemon 的id同样加到PokemonCard中const deletePokemon gql mutation deletePokemon($id: ID!) { deletePokemon(id: $id) { id } } 用name选项向同一组件注入多个 Mutation到此可以定义 Mutation接下来是本文的核心如何把两个 Mutation 同时注入PokemonCard。默认情况下注入的 Mutation 通过this.props.mutate访问要注入多个 Mutation只需为每一层graphql()HOC 提供一个name选项const PokemonCardWithMutations graphql(deletePokemon, {name : deletePokemon})( graphql(updatePokemon, {name: updatePokemon})(PokemonCard) ) export default PokemonCardWithMutations两层 HOC 洋葱式包裹后组件会多出updatePokemon和deletePokemon两个 props各为一个可调用的 mutate 函数。同步更新propTypes声明static propTypes { pokemon: propType(pokemonCardFragments.pokemon).isRequired, updatePokemon: React.PropTypes.func.isRequired, deletePokemon: React.PropTypes.func.isRequired, }把按钮与 Mutation 连接起来现在可以在按钮的点击回调中调用 Mutation。两个 handler 都声明为 async 箭头函数利用 Mutation 返回的 Promise在请求完成后才导航回 Pokedex 页面handleUpdate async () { await this.props.updatePokemon({variables: { id: this.props.pokemon.id, name: this.state.name, url: this.state.url }}) Actions.pokedex() } handleDelete async () { await this.props.deletePokemon({variables: { id: this.props.pokemon.id }}) Actions.pokedex() }与上一章this.props.mutate的用法形成对照这里传入的variables对象分别对应两个 Mutation 声明的变量updatePokemon需要id/name/urldeletePokemon只需要id。Actions.pokedex()是 react-navigation 的导航动作把栈顶切回 Pokedex 场景。运行验证yarn start点击某个已有 Pokemon 进入详情页修改其中一个属性后点击 Update 按钮应回到更新后的列表再重复一遍这次执行删除操作。强制拉取force fetch以获得一致的 UI如果你多玩几次删除会注意到一个熟悉的问题删除只反映在刷新页面之后的 UI 上。上一章为了修复“创建后不显示”的问题做了两件事详见 react-native-05.md在App.js中为ApolloClient设置dataIdFromObject: o o.id让 Apollo Store 按id而非查询路径归一化对象让createPokemonMutation 的返回里带上Trainer及其ownedPokemons从而触发缓存中 trainer 节点的合并更新const createPokemonMutation gql mutation createPokemon($name: String!, $url: String!, $trainerId: ID) { createPokemon(name: $name, url: $url, trainerId: $trainerId) { trainer { id ownedPokemons { id } } } } 其原理是Apollo Client 会按id把 Mutation 响应中的trainer与缓存里已有的 trainer 节点做合并从而刷新ownedPokemons列表。虽然效率不高但对创建场景有效。为什么同样的思路对删除无效顺着同样的逻辑可以把deletePokemon改成删除后也查询 trainer 及其全部 Pokemonconst deletePokemon gql mutation deletePokemon($id: ID!) { deletePokemon(id: $id) { trainer { id ownedPokemons { id } } } } 但问题在于Apollo 的合并语义是“取已知数据与响应数据的并集”而不是用响应整体替换列表。由于删除前缓存里已经包含被删 Pokemon 的id合并之后被删的 Pokemon 依然留在 Apollo Store 中。Apollo 无法得知“这次响应里的 Pokemon 全集就是服务器上现有的全部、而不是某个子集”服务端支持按字段过滤查询列表天然可能是部分结果。因此靠 Mutation 响应的合并无法可靠地完成删除后的 UI 更新。解决方案fetchPolicy: cache-and-network一个直接的修复方式是只要 Pokedex 渲染就强制从网络重新拉取 trainer 数据。fetchPolicy: cache-and-network告诉 Apollo先立即返回缓存结果若存在等网络结果返回后再用新数据更新。打开components/Pokedex.js为TrainerQuery的 options 增加该配置const PokedexWithData graphql(TrainerQuery, { options: { variables: { name: __NAME__ }, fetchPolicy: cache-and-network, } })(Pokedex) export default PokedexWithData这里有个值得注意的实现细节__NAME__并不是要手动填写的用户名。learnapollo 站点在渲染时由 src/components/Markdown/Markdown.tsx 用 commonmark 的 AST walker 把文档中的所有__NAME__字面量替换为当前登录用户名storedState.user.name所以示例代码中的name: __NAME__在站点上展示时是动态的在你自己的练习仓库中应替换为createTrainer时使用的真实 trainer 名字。由于 Pokedex 每次出现都会强制走网络上一章为创建流程加重的 Mutation 也可以瘦身了——AddPokemonCard中的createPokemon不再需要在响应里带回整个 trainer 子树const createPokemonMutation gql mutation createPokemon($name: String!, $url: String!, $trainerId: ID) { createPokemon(name: $name, url: $url, trainerId: $trainerId) { id } } 原文档对这一权衡的评价很直白这是一个“consistent but inefficient”一致但低效的方案——代价是 Pokedex 每次显示都产生一次网络请求。更精细的缓存更新方式例如 React 侧的updateQueries在 excursion-02Managing Apollo store 中有专门讨论。仓库源码中的对照Relay 的 Mutation 配置机制从仓库源码结构看站点自身还带有一组 React Relay 实现的 Mutation 示例可作为“更高效缓存更新”思路的直观对照注意这些文件属于站点代码库中的 Relay 演示并非本 React Native/Apollo Client Track 的练习代码src/mutations/AddPokemonMutation.tsgetConfigs()返回RANGE_ADD配置把新 Pokemon 追加到allPokemons连接src/mutations/DeletePokemonMutation.ts使用NODE_DELETE配置依赖响应中的deletedId从连接中移除节点;src/mutations/UpdatePokemonMutation.ts使用FIELDS_CHANGE声明被修改的字段。对比之下可以看出本文所选cache-and-network方案的定位它不依赖任何“变更影响声明”实现最简单适合教学与低频操作场景而 Relay 的 configs 或 Apollo 的updateQueries则属于声明式的增量更新能避免整页强制拉取。本篇小结本章完成了 React Native Track 的收尾关键收获包括更新与删除通过updatePokemon/deletePokemon两个 Mutation配合组件 state 中的受控输入完成了 Pokemon 的编辑与删除多 Mutation 注入为graphql()HOC 提供name选项即可让同一组件拥有多个具名的 mutate 函数 props并在propTypes中分别声明Apollo 缓存的行为边界归一化dataIdFromObject让合并按id生效但合并是“并集”语义因此删除节点无法靠 Mutation 响应刷新fetchPolicy: cache-and-network用每次渲染强制网络拉取换来了 UI 一致性同时允许简化创建 Mutation 的响应选择。至此React Native Track 六章练习全部完成基础查询 → 进阶查询变量与条件字段→ Fragment → 基础 Mutation → 多 Mutation 与缓存一致性。若需继续深入可阅读 excursion-01DevTools 使用 与 excursion-02Managing Apollo store。赞分享教程前端【免费下载链接】learnapollo‍ Learn Apollo - A hands-on tutorial for Apollo GraphQL Client (created by Graphcool)项目地址https://gitcode.com/gh_mirrors/le/learnapollo点击查看免费下载相关推荐learnapollo iOS Track5/6用 GraphQL Mutation 添加新 Pokemon并通过 Apollo 缓存实现 UI 自动刷新learnapollo iOS Track5/6用 GraphQL Mutation 添加新 Pokemon并通过 Apollo 缓存实现 UI 自动刷教程前端LearnApollo项目React与Apollo Client入门教程1/7LearnApollo项目React与Apollo Client入门教程1/7 你还在为GraphQL客户端集成头疼吗 作为一名React开发者教程前端LearnApollo项目React Native基础查询教程2/6LearnApollo项目React Native基础查询教程2/6 还在为React Native中的GraphQL查询烦恼本文手把手教你使用Apol教程前端上一篇FlowGram.AI 可视化编程终极指南5分钟从零到精通下一篇为什么选择TimeMoE-200M对比传统时间序列模型的7大优势创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →