我如何处理 react-apollo 中的删除
How do I handle deletes in react-apollo
我有一个像
这样的突变
mutation deleteRecord($id: ID) {
deleteRecord(id: $id) {
id
}
}
在另一个位置我有一个元素列表。
我可以从服务器 return 获得更好的东西吗?我应该如何更新列表?
更一般地说,在 apollo/graphql 中处理删除的最佳做法是什么?
就个人而言,我 return 一个 int
代表删除的项目数。然后我使用 updateQueries
从缓存中删除文档。
我不确定这是良好的实践风格,但这是我使用 updateQueries 在 react-apollo 中处理项目删除的方法:
import { graphql, compose } from 'react-apollo';
import gql from 'graphql-tag';
import update from 'react-addons-update';
import _ from 'underscore';
const SceneCollectionsQuery = gql `
query SceneCollections {
myScenes: selectedScenes (excludeOwner: false, first: 24) {
edges {
node {
...SceneCollectionScene
}
}
}
}`;
const DeleteSceneMutation = gql `
mutation DeleteScene($sceneId: String!) {
deleteScene(sceneId: $sceneId) {
ok
scene {
id
active
}
}
}`;
const SceneModifierWithStateAndData = compose(
...,
graphql(DeleteSceneMutation, {
props: ({ mutate }) => ({
deleteScene: (sceneId) => mutate({
variables: { sceneId },
updateQueries: {
SceneCollections: (prev, { mutationResult }) => {
const myScenesList = prev.myScenes.edges.map((item) => item.node);
const deleteIndex = _.findIndex(myScenesList, (item) => item.id === sceneId);
if (deleteIndex < 0) {
return prev;
}
return update(prev, {
myScenes: {
edges: {
$splice: [[deleteIndex, 1]]
}
}
});
}
}
})
})
})
)(SceneModifierWithState);
这里有一个类似的解决方案,可以在没有 underscore.js 的情况下使用。它在 2.1.1 版本中使用 react-apollo
进行了测试。并为 delete-button:
创建一个组件
import React from "react";
import { Mutation } from "react-apollo";
const GET_TODOS = gql`
{
allTodos {
id
name
}
}
`;
const DELETE_TODO = gql`
mutation deleteTodo(
$id: ID!
) {
deleteTodo(
id: $id
) {
id
}
}
`;
const DeleteTodo = ({id}) => {
return (
<Mutation
mutation={DELETE_TODO}
update={(cache, { data: { deleteTodo } }) => {
const { allTodos } = cache.readQuery({ query: GET_TODOS });
cache.writeQuery({
query: GET_TODOS,
data: { allTodos: allTodos.filter(e => e.id !== id)}
});
}}
>
{(deleteTodo, { data }) => (
<button
onClick={e => {
deleteTodo({
variables: {
id
}
});
}}
>Delete</button>
)}
</Mutation>
);
};
export default DeleteTodo;
所有这些答案都假定面向查询的缓存管理。
如果我删除 ID 为 1
的 user
,并且在整个应用程序的 20 个查询中引用了该用户,会怎样?阅读上面的答案,我不得不假设我将不得不编写代码来更新所有这些的缓存。这对于代码库的长期可维护性来说是非常糟糕的,并且会使任何重构成为一场噩梦。
我认为最好的解决方案是 apolloClient.removeItem({__typeName: "User", id: "1"})
这样的:
- 将缓存中对该对象的任何直接引用替换为
null
- 在任何查询的任何
[User]
列表中过滤掉此项
但它(还)不存在
这可能是个好主意,也可能更糟(例如,它可能会破坏分页)
关于它的讨论很有趣:https://github.com/apollographql/apollo-client/issues/899
我会小心那些手动查询更新。 起初看起来很有食欲,但如果您的应用程序会增长,它就不会了。至少在它的顶部创建一个坚实的抽象层例如:
- 在您定义的每个查询旁边(例如,在同一个文件中)- 定义可以正确调整它的函数,例如
const MY_QUERY = gql``;
// it's local 'cleaner' - relatively easy to maintain as you can require proper cleaner updates during code review when query will change
export function removeUserFromMyQuery(apolloClient, userId) {
// clean here
}
然后,收集所有这些更新并在最终更新中调用它们
function handleUserDeleted(userId, client) {
removeUserFromMyQuery(userId, client)
removeUserFromSearchQuery(userId, client)
removeIdFrom20MoreQueries(userId, client)
}
当与突变相关的其余 API 可能 return http 204、404 或 500 时,我遇到了同样的问题,为此类突变选择合适的 return 类型。
定义和任意类型然后 return null(默认情况下类型可以为空)似乎不正确,因为您不知道发生了什么,意思是如果它成功与否。
返回一个布尔值 解决了这个问题,你知道突变是否起作用,但你缺少一些信息以防它不起作用,比如更好的错误消息你可以在 FE 上显示,例如,如果我们得到 404,我们可以 return "Not found".
返回自定义类型感觉有点勉强,因为它实际上不是您的模式或业务逻辑的一种类型,它只是用于修复休息之间的"communication issue"和 Graphql。
我最终 return 得到了一个字符串 。如果成功,我可以 return 资源 ID/UUID 或简单地 "ok" ,如果出错则 return 一条错误消息。
不确定这是一个好的做法还是 Graphql 惯用语。
对于 Apollo v3 这对我有用:
const [deleteExpressHelp] = useDeleteExpressHelpMutation({
update: (cache, {data}) => {
cache.evict({
id: cache.identify({
__typename: 'express_help',
id: data?.delete_express_help_by_pk?.id,
}),
});
},
});
来自new docs:
Filtering dangling references out of a cached array field (like the Deity.offspring example above) is so common that Apollo Client performs this filtering automatically for array fields that don't define a read function.
我有一个像
这样的突变mutation deleteRecord($id: ID) {
deleteRecord(id: $id) {
id
}
}
在另一个位置我有一个元素列表。
我可以从服务器 return 获得更好的东西吗?我应该如何更新列表?
更一般地说,在 apollo/graphql 中处理删除的最佳做法是什么?
就个人而言,我 return 一个 int
代表删除的项目数。然后我使用 updateQueries
从缓存中删除文档。
我不确定这是良好的实践风格,但这是我使用 updateQueries 在 react-apollo 中处理项目删除的方法:
import { graphql, compose } from 'react-apollo';
import gql from 'graphql-tag';
import update from 'react-addons-update';
import _ from 'underscore';
const SceneCollectionsQuery = gql `
query SceneCollections {
myScenes: selectedScenes (excludeOwner: false, first: 24) {
edges {
node {
...SceneCollectionScene
}
}
}
}`;
const DeleteSceneMutation = gql `
mutation DeleteScene($sceneId: String!) {
deleteScene(sceneId: $sceneId) {
ok
scene {
id
active
}
}
}`;
const SceneModifierWithStateAndData = compose(
...,
graphql(DeleteSceneMutation, {
props: ({ mutate }) => ({
deleteScene: (sceneId) => mutate({
variables: { sceneId },
updateQueries: {
SceneCollections: (prev, { mutationResult }) => {
const myScenesList = prev.myScenes.edges.map((item) => item.node);
const deleteIndex = _.findIndex(myScenesList, (item) => item.id === sceneId);
if (deleteIndex < 0) {
return prev;
}
return update(prev, {
myScenes: {
edges: {
$splice: [[deleteIndex, 1]]
}
}
});
}
}
})
})
})
)(SceneModifierWithState);
这里有一个类似的解决方案,可以在没有 underscore.js 的情况下使用。它在 2.1.1 版本中使用 react-apollo
进行了测试。并为 delete-button:
import React from "react";
import { Mutation } from "react-apollo";
const GET_TODOS = gql`
{
allTodos {
id
name
}
}
`;
const DELETE_TODO = gql`
mutation deleteTodo(
$id: ID!
) {
deleteTodo(
id: $id
) {
id
}
}
`;
const DeleteTodo = ({id}) => {
return (
<Mutation
mutation={DELETE_TODO}
update={(cache, { data: { deleteTodo } }) => {
const { allTodos } = cache.readQuery({ query: GET_TODOS });
cache.writeQuery({
query: GET_TODOS,
data: { allTodos: allTodos.filter(e => e.id !== id)}
});
}}
>
{(deleteTodo, { data }) => (
<button
onClick={e => {
deleteTodo({
variables: {
id
}
});
}}
>Delete</button>
)}
</Mutation>
);
};
export default DeleteTodo;
所有这些答案都假定面向查询的缓存管理。
如果我删除 ID 为 1
的 user
,并且在整个应用程序的 20 个查询中引用了该用户,会怎样?阅读上面的答案,我不得不假设我将不得不编写代码来更新所有这些的缓存。这对于代码库的长期可维护性来说是非常糟糕的,并且会使任何重构成为一场噩梦。
我认为最好的解决方案是 apolloClient.removeItem({__typeName: "User", id: "1"})
这样的:
- 将缓存中对该对象的任何直接引用替换为
null
- 在任何查询的任何
[User]
列表中过滤掉此项
但它(还)不存在
这可能是个好主意,也可能更糟(例如,它可能会破坏分页)
关于它的讨论很有趣:https://github.com/apollographql/apollo-client/issues/899
我会小心那些手动查询更新。 起初看起来很有食欲,但如果您的应用程序会增长,它就不会了。至少在它的顶部创建一个坚实的抽象层例如:
- 在您定义的每个查询旁边(例如,在同一个文件中)- 定义可以正确调整它的函数,例如
const MY_QUERY = gql``;
// it's local 'cleaner' - relatively easy to maintain as you can require proper cleaner updates during code review when query will change
export function removeUserFromMyQuery(apolloClient, userId) {
// clean here
}
然后,收集所有这些更新并在最终更新中调用它们
function handleUserDeleted(userId, client) {
removeUserFromMyQuery(userId, client)
removeUserFromSearchQuery(userId, client)
removeIdFrom20MoreQueries(userId, client)
}
当与突变相关的其余 API 可能 return http 204、404 或 500 时,我遇到了同样的问题,为此类突变选择合适的 return 类型。
定义和任意类型然后 return null(默认情况下类型可以为空)似乎不正确,因为您不知道发生了什么,意思是如果它成功与否。
返回一个布尔值 解决了这个问题,你知道突变是否起作用,但你缺少一些信息以防它不起作用,比如更好的错误消息你可以在 FE 上显示,例如,如果我们得到 404,我们可以 return "Not found".
返回自定义类型感觉有点勉强,因为它实际上不是您的模式或业务逻辑的一种类型,它只是用于修复休息之间的"communication issue"和 Graphql。
我最终 return 得到了一个字符串 。如果成功,我可以 return 资源 ID/UUID 或简单地 "ok" ,如果出错则 return 一条错误消息。
不确定这是一个好的做法还是 Graphql 惯用语。
对于 Apollo v3 这对我有用:
const [deleteExpressHelp] = useDeleteExpressHelpMutation({
update: (cache, {data}) => {
cache.evict({
id: cache.identify({
__typename: 'express_help',
id: data?.delete_express_help_by_pk?.id,
}),
});
},
});
来自new docs:
Filtering dangling references out of a cached array field (like the Deity.offspring example above) is so common that Apollo Client performs this filtering automatically for array fields that don't define a read function.