如何使用apollo-link-state观测Apollo Client中的派生状态?
Apollo Link State 观测派生状态的最佳方式
其实你说的这个痛点我太懂了——手动在每个Todo增删改操作里同步更新totalTodos不仅繁琐,还特别容易遗漏出错。好在Apollo Link State本身就支持通过本地解析器动态计算派生状态,完全不用把totalTodos这类计算值存在缓存里,而且它是可观测的,只要缓存里的Todo列表一变,查询结果会自动更新,完美解决你的问题!
核心思路很简单:在本地Schema里定义派生字段(比如totalTodos),然后在解析器函数中直接从缓存读取原始的Todo列表,动态计算出需要的值。这样所有增删改操作只需要维护原始的todos缓存,派生字段会自动跟随原始数据变化。
下面给你一个简化的完整示例:
1. 定义本地Schema
首先在本地Schema中声明totalTodos这个派生字段,和todos放在同一个Query类型下:
import { gql } from '@apollo/client'; const typeDefs = gql` type Todo { id: ID! text: String! completed: Boolean! } type Query { todos: [Todo]! totalTodos: Int! # 派生字段,不需要存在缓存中 } type Mutation { addTodo(text: String!): Todo! toggleTodo(id: ID!): Todo! deleteTodo(id: ID!): Boolean! } `;
2. 配置解析器(重点看totalTodos的实现)
在解析器中,totalTodos的逻辑就是从缓存读取todos列表,返回它的长度。而所有Mutation操作只需要更新todos缓存即可,完全不用管totalTodos:
const resolvers = { Query: { // 读取缓存中的Todo列表 todos: (_root, _args, { cache }) => { const data = cache.readQuery({ query: GET_TODOS }); return data.todos || []; }, // 动态计算Todo总数,完全不需要手动维护 totalTodos: (_root, _args, { cache }) => { const data = cache.readQuery({ query: GET_TODOS }); return (data.todos || []).length; }, }, Mutation: { addTodo: (_root, { text }, { cache }) => { const data = cache.readQuery({ query: GET_TODOS }); const newTodo = { id: Date.now().toString(), text, completed: false, __typename: 'Todo', }; // 只更新todos缓存 cache.writeQuery({ query: GET_TODOS, data: { todos: [...data.todos, newTodo] }, }); return newTodo; }, toggleTodo: (_root, { id }, { cache }) => { const todoFragment = gql` fragment TodoFragment on Todo { id completed } `; const todo = cache.readFragment({ id: `Todo:${id}`, fragment: todoFragment, }); const updatedTodo = { ...todo, completed: !todo.completed, __typename: 'Todo', }; // 更新单个Todo的缓存,todos列表会自动同步 cache.writeFragment({ id: `Todo:${id}`, fragment: todoFragment, data: updatedTodo, }); return updatedTodo; }, deleteTodo: (_root, { id }, { cache }) => { const data = cache.readQuery({ query: GET_TODOS }); const newTodos = data.todos.filter(todo => todo.id !== id); // 只更新todos缓存 cache.writeQuery({ query: GET_TODOS, data: { todos: newTodos }, }); return true; }, }, }; // 定义读取Todo列表的基础查询 const GET_TODOS = gql` query GetTodos { todos { id text completed } } `;
3. 在组件中使用派生字段
现在你可以直接在组件中查询totalTodos,它会自动响应缓存中todos的变化:
import { useQuery } from '@apollo/client'; const TodoStats = () => { const { data, loading } = useQuery(gql` query TodoStats { totalTodos } `); if (loading) return <div>Loading...</div>; return <div>Total Todos: {data.totalTodos}</div>; };
扩展:更复杂的派生状态
如果需要计算已完成的Todo数量这类更复杂的派生值,逻辑也是一样的,只需要在解析器中添加过滤逻辑:
// 在Query解析器中添加 completedTodos: (_root, _args, { cache }) => { const data = cache.readQuery({ query: GET_TODOS }); return (data.todos || []).filter(todo => todo.completed).length; }
这种方式的优势非常明显:
- 不需要维护冗余的派生状态缓存,所有逻辑集中在解析器里
- 天然响应式:缓存中原始数据变化时,派生字段会自动重新计算并更新组件
- 减少出错概率,不用在每个Mutation里重复写更新派生状态的代码
内容的提问来源于stack exchange,提问作者tgk
相关产品推荐
相关产品推荐

